mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-10 03:18:55 +02:00
Compare commits
3 commits
11ad71b1e7
...
9423a33fc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9423a33fc2 | ||
|
|
afec66f598 | ||
|
|
a022560991 |
8 changed files with 96 additions and 39 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
@ -19,7 +19,6 @@ import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.adapters.AddonAdapter
|
import org.yuzu.yuzu_emu.adapters.AddonAdapter
|
||||||
import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding
|
||||||
|
|
@ -42,7 +41,7 @@ class AddonsFragment : Fragment() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
addonViewModel.onOpenAddons(args.game)
|
addonViewModel.onAddonsViewCreated(args.game)
|
||||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||||
|
|
@ -122,12 +121,14 @@ class AddonsFragment : Fragment() {
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
addonViewModel.refreshAddons()
|
addonViewModel.onAddonsViewStarted(args.game)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
if (activity?.isChangingConfigurations != true) {
|
||||||
|
addonViewModel.onCloseAddons()
|
||||||
|
}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
addonViewModel.onCloseAddons()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val installAddon =
|
val installAddon =
|
||||||
|
|
@ -167,7 +168,7 @@ class AddonsFragment : Fragment() {
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
return@newInstance errorMessage
|
return@newInstance errorMessage
|
||||||
}
|
}
|
||||||
addonViewModel.refreshAddons()
|
addonViewModel.refreshAddons(force = true)
|
||||||
return@newInstance getString(R.string.addon_installed_successfully)
|
return@newInstance getString(R.string.addon_installed_successfully)
|
||||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
class AddonViewModel : ViewModel() {
|
class AddonViewModel : ViewModel() {
|
||||||
private val _patchList = MutableStateFlow(mutableListOf<Patch>())
|
private val _patchList = MutableStateFlow<List<Patch>>(emptyList())
|
||||||
val addonList get() = _patchList.asStateFlow()
|
val addonList get() = _patchList.asStateFlow()
|
||||||
|
|
||||||
private val _showModInstallPicker = MutableStateFlow(false)
|
private val _showModInstallPicker = MutableStateFlow(false)
|
||||||
|
|
@ -31,34 +31,62 @@ class AddonViewModel : ViewModel() {
|
||||||
val addonToDelete = _addonToDelete.asStateFlow()
|
val addonToDelete = _addonToDelete.asStateFlow()
|
||||||
|
|
||||||
var game: Game? = null
|
var game: Game? = null
|
||||||
|
private var loadedGameKey: String? = null
|
||||||
|
|
||||||
private val isRefreshing = AtomicBoolean(false)
|
private val isRefreshing = AtomicBoolean(false)
|
||||||
|
private val pendingRefresh = AtomicBoolean(false)
|
||||||
|
|
||||||
fun onOpenAddons(game: Game) {
|
fun onAddonsViewCreated(game: Game) {
|
||||||
this.game = game
|
this.game = game
|
||||||
refreshAddons()
|
refreshAddons(commitEmpty = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshAddons() {
|
fun onAddonsViewStarted(game: Game) {
|
||||||
if (isRefreshing.get() || game == null) {
|
this.game = game
|
||||||
|
val hasLoadedCurrentGame = loadedGameKey == gameKey(game)
|
||||||
|
refreshAddons(force = !hasLoadedCurrentGame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshAddons(force: Boolean = false, commitEmpty: Boolean = true) {
|
||||||
|
val currentGame = game ?: return
|
||||||
|
val currentGameKey = gameKey(currentGame)
|
||||||
|
if (!force && loadedGameKey == currentGameKey) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isRefreshing.set(true)
|
if (!isRefreshing.compareAndSet(false, true)) {
|
||||||
|
if (force) {
|
||||||
|
pendingRefresh.set(true)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
try {
|
||||||
val patchList = (
|
val patches = withContext(Dispatchers.IO) {
|
||||||
NativeLibrary.getPatchesForFile(game!!.path, game!!.programId)
|
NativeLibrary.getPatchesForFile(currentGame.path, currentGame.programId)
|
||||||
?: emptyArray()
|
} ?: return@launch
|
||||||
).toMutableList()
|
|
||||||
|
val patchList = patches.toMutableList()
|
||||||
patchList.sortBy { it.name }
|
patchList.sortBy { it.name }
|
||||||
|
|
||||||
// Ensure only one update is enabled
|
// Ensure only one update is enabled
|
||||||
ensureSingleUpdateEnabled(patchList)
|
ensureSingleUpdateEnabled(patchList)
|
||||||
|
|
||||||
removeDuplicates(patchList)
|
removeDuplicates(patchList)
|
||||||
|
if (patchList.isEmpty() && !commitEmpty) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
if (gameKey(game ?: return@launch) != currentGameKey) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
_patchList.value = patchList
|
_patchList.value = patchList.toList()
|
||||||
|
loadedGameKey = currentGameKey
|
||||||
|
} finally {
|
||||||
isRefreshing.set(false)
|
isRefreshing.set(false)
|
||||||
|
if (pendingRefresh.compareAndSet(true, false)) {
|
||||||
|
refreshAddons(force = true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -119,17 +147,26 @@ class AddonViewModel : ViewModel() {
|
||||||
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
|
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
|
||||||
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
|
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
|
||||||
}
|
}
|
||||||
refreshAddons()
|
refreshAddons(force = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onCloseAddons() {
|
fun onCloseAddons() {
|
||||||
if (_patchList.value.isEmpty()) {
|
val currentGame = game ?: run {
|
||||||
|
_patchList.value = emptyList()
|
||||||
|
loadedGameKey = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val currentList = _patchList.value
|
||||||
|
if (currentList.isEmpty()) {
|
||||||
|
_patchList.value = emptyList()
|
||||||
|
loadedGameKey = null
|
||||||
|
game = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeConfig.setDisabledAddons(
|
NativeConfig.setDisabledAddons(
|
||||||
game!!.programId,
|
currentGame.programId,
|
||||||
_patchList.value.mapNotNull {
|
currentList.mapNotNull {
|
||||||
if (it.enabled) {
|
if (it.enabled) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -148,7 +185,8 @@ class AddonViewModel : ViewModel() {
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
)
|
)
|
||||||
NativeConfig.saveGlobalConfig()
|
NativeConfig.saveGlobalConfig()
|
||||||
_patchList.value.clear()
|
_patchList.value = emptyList()
|
||||||
|
loadedGameKey = null
|
||||||
game = null
|
game = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,4 +197,8 @@ class AddonViewModel : ViewModel() {
|
||||||
fun showModNoticeDialog(show: Boolean) {
|
fun showModNoticeDialog(show: Boolean) {
|
||||||
_showModNoticeDialog.value = show
|
_showModNoticeDialog.value = show
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun gameKey(game: Game): String {
|
||||||
|
return "${game.programId}|${game.path}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -642,7 +642,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addonViewModel.refreshAddons()
|
addonViewModel.refreshAddons(force = true)
|
||||||
|
|
||||||
val separator = System.lineSeparator() ?: "\n"
|
val separator = System.lineSeparator() ?: "\n"
|
||||||
val installResult = StringBuilder()
|
val installResult = StringBuilder()
|
||||||
|
|
|
||||||
|
|
@ -706,6 +706,7 @@ struct Values {
|
||||||
Setting<bool> pause_tas_on_load{linkage, true, "pause_tas_on_load", Category::Controls};
|
Setting<bool> pause_tas_on_load{linkage, true, "pause_tas_on_load", Category::Controls};
|
||||||
Setting<bool> tas_enable{linkage, false, "tas_enable", Category::Controls};
|
Setting<bool> tas_enable{linkage, false, "tas_enable", Category::Controls};
|
||||||
Setting<bool> tas_loop{linkage, false, "tas_loop", Category::Controls};
|
Setting<bool> tas_loop{linkage, false, "tas_loop", Category::Controls};
|
||||||
|
Setting<bool> tas_show_recording_dialog{linkage, true, "tas_show_recording_dialog", Category::Controls};
|
||||||
|
|
||||||
Setting<bool> mouse_panning{
|
Setting<bool> mouse_panning{
|
||||||
linkage, false, "mouse_panning", Category::Controls, Specialization::Default, false};
|
linkage, false, "mouse_panning", Category::Controls, Specialization::Default, false};
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
|
@ -18,17 +21,14 @@ namespace Service::Audio {
|
||||||
void LoopProcess(Core::System& system) {
|
void LoopProcess(Core::System& system) {
|
||||||
auto server_manager = std::make_unique<ServerManager>(system);
|
auto server_manager = std::make_unique<ServerManager>(system);
|
||||||
|
|
||||||
server_manager->RegisterNamedService("audctl", std::make_shared<IAudioController>(system));
|
|
||||||
server_manager->RegisterNamedService("audin:u", std::make_shared<IAudioInManager>(system));
|
server_manager->RegisterNamedService("audin:u", std::make_shared<IAudioInManager>(system));
|
||||||
server_manager->RegisterNamedService("audout:u", std::make_shared<IAudioOutManager>(system));
|
server_manager->RegisterNamedService("audout:u", std::make_shared<IAudioOutManager>(system));
|
||||||
server_manager->RegisterNamedService(
|
// Depends on audout:u and audin:u on ctor!
|
||||||
"audrec:a", std::make_shared<IFinalOutputRecorderManagerForApplet>(system));
|
server_manager->RegisterNamedService("audctl", std::make_shared<IAudioController>(system));
|
||||||
server_manager->RegisterNamedService("audrec:u",
|
server_manager->RegisterNamedService("audrec:a", std::make_shared<IFinalOutputRecorderManagerForApplet>(system));
|
||||||
std::make_shared<IFinalOutputRecorderManager>(system));
|
server_manager->RegisterNamedService("audrec:u", std::make_shared<IFinalOutputRecorderManager>(system));
|
||||||
server_manager->RegisterNamedService("audren:u",
|
server_manager->RegisterNamedService("audren:u", std::make_shared<IAudioRendererManager>(system));
|
||||||
std::make_shared<IAudioRendererManager>(system));
|
server_manager->RegisterNamedService("hwopus", std::make_shared<IHardwareOpusDecoderManager>(system));
|
||||||
server_manager->RegisterNamedService("hwopus",
|
|
||||||
std::make_shared<IHardwareOpusDecoderManager>(system));
|
|
||||||
ServerManager::RunServer(std::move(server_manager));
|
ServerManager::RunServer(std::move(server_manager));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
|
@ -35,6 +35,7 @@ void ConfigureTasDialog::LoadConfiguration() {
|
||||||
ui->tas_enable->setChecked(Settings::values.tas_enable.GetValue());
|
ui->tas_enable->setChecked(Settings::values.tas_enable.GetValue());
|
||||||
ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue());
|
ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue());
|
||||||
ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue());
|
ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue());
|
||||||
|
ui->tas_show_recording_dialog->setChecked(Settings::values.tas_show_recording_dialog.GetValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureTasDialog::ApplyConfiguration() {
|
void ConfigureTasDialog::ApplyConfiguration() {
|
||||||
|
|
@ -42,6 +43,7 @@ void ConfigureTasDialog::ApplyConfiguration() {
|
||||||
Settings::values.tas_enable.SetValue(ui->tas_enable->isChecked());
|
Settings::values.tas_enable.SetValue(ui->tas_enable->isChecked());
|
||||||
Settings::values.tas_loop.SetValue(ui->tas_loop_script->isChecked());
|
Settings::values.tas_loop.SetValue(ui->tas_loop_script->isChecked());
|
||||||
Settings::values.pause_tas_on_load.SetValue(ui->tas_pause_on_load->isChecked());
|
Settings::values.pause_tas_on_load.SetValue(ui->tas_pause_on_load->isChecked());
|
||||||
|
Settings::values.tas_show_recording_dialog.SetValue(ui->tas_show_recording_dialog->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
|
void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,13 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="0" colspan="4">
|
||||||
|
<widget class="QCheckBox" name="tas_show_recording_dialog">
|
||||||
|
<property name="text">
|
||||||
|
<string>Show recording dialog</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
||||||
|
|
@ -3665,13 +3665,17 @@ void MainWindow::OnTasRecord() {
|
||||||
|
|
||||||
const bool is_recording = input_subsystem->GetTas()->Record();
|
const bool is_recording = input_subsystem->GetTas()->Record();
|
||||||
if (!is_recording) {
|
if (!is_recording) {
|
||||||
is_tas_recording_dialog_active = true;
|
if (Settings::values.tas_show_recording_dialog.GetValue()) {
|
||||||
|
is_tas_recording_dialog_active = true;
|
||||||
|
|
||||||
bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
|
bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
|
||||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||||
|
|
||||||
input_subsystem->GetTas()->SaveRecording(answer);
|
input_subsystem->GetTas()->SaveRecording(answer);
|
||||||
is_tas_recording_dialog_active = false;
|
is_tas_recording_dialog_active = false;
|
||||||
|
} else {
|
||||||
|
input_subsystem->GetTas()->SaveRecording(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
OnTasStateChanged();
|
OnTasStateChanged();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue