Compare commits

..

3 commits

Author SHA1 Message Date
xbzk
9423a33fc2
[android,ui] fix addons list not refresh upon rotation (#3687)
Some checks are pending
tx-src / sources (push) Waiting to run
Check Strings / check-strings (push) Waiting to run
Fixes a bug reported by Pavel, in which an RC as returning empty patchList after phone rotated.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3687
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-08 00:41:40 +01:00
lizzie
afec66f598
core/hle/services/audio] Fix audioctl being started before audout/audin (which are required for ctor) (#3695)
`audctl` depends on `audout`, simply start `audout` and `audin` before!

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3695
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-07 23:44:58 +01:00
SchweGELBin
a022560991
[desktop] tas: add option to disable file overwrite dialog (#3657)
Hello everybody, thank you for letting me participate in the development of the Eden emulator!

I've been playing around with the TAS functionality and didn't want to always click "Yes" in the dialog that askes if I want to "Overwrite file of player 1?" after recording the inputs.
So I can't record and play TAS files with keybinds only, because I'd still need to switching from my contoller to my keyboard and back.

So I added the option "Show recording dialog" into the configure_tas screen.
(The final naming and string can be changed of course.)
It's a checkbox that is enabled by default (so no changes if ignored), but can be unchecked to disable the popup.
The change has been tested on top of the current master branch.

I've also created a commit to add the relevant translation data, where german is translated and the rest unfinished.
I'm not sure how this would be handled as this project uses transifex for it localization, so I can remove this commit if preferred.

Have a great day!
- Michi

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3657
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: SchweGELBin <abramjannikmichael06@gmail.com>
Co-committed-by: SchweGELBin <abramjannikmichael06@gmail.com>
2026-03-07 20:08:47 +01:00
8 changed files with 96 additions and 39 deletions

View file

@ -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
package org.yuzu.yuzu_emu.fragments
@ -19,7 +19,6 @@ import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.AddonAdapter
import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding
@ -42,7 +41,7 @@ class AddonsFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addonViewModel.onOpenAddons(args.game)
addonViewModel.onAddonsViewCreated(args.game)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
@ -122,13 +121,15 @@ class AddonsFragment : Fragment() {
override fun onResume() {
super.onResume()
addonViewModel.refreshAddons()
addonViewModel.onAddonsViewStarted(args.game)
}
override fun onDestroy() {
super.onDestroy()
if (activity?.isChangingConfigurations != true) {
addonViewModel.onCloseAddons()
}
super.onDestroy()
}
val installAddon =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
@ -167,7 +168,7 @@ class AddonsFragment : Fragment() {
} catch (_: Exception) {
return@newInstance errorMessage
}
addonViewModel.refreshAddons()
addonViewModel.refreshAddons(force = true)
return@newInstance getString(R.string.addon_installed_successfully)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
} else {

View file

@ -18,7 +18,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
import java.util.concurrent.atomic.AtomicBoolean
class AddonViewModel : ViewModel() {
private val _patchList = MutableStateFlow(mutableListOf<Patch>())
private val _patchList = MutableStateFlow<List<Patch>>(emptyList())
val addonList get() = _patchList.asStateFlow()
private val _showModInstallPicker = MutableStateFlow(false)
@ -31,34 +31,62 @@ class AddonViewModel : ViewModel() {
val addonToDelete = _addonToDelete.asStateFlow()
var game: Game? = null
private var loadedGameKey: String? = null
private val isRefreshing = AtomicBoolean(false)
private val pendingRefresh = AtomicBoolean(false)
fun onOpenAddons(game: Game) {
fun onAddonsViewCreated(game: Game) {
this.game = game
refreshAddons()
refreshAddons(commitEmpty = false)
}
fun refreshAddons() {
if (isRefreshing.get() || game == null) {
fun onAddonsViewStarted(game: Game) {
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
}
isRefreshing.set(true)
if (!isRefreshing.compareAndSet(false, true)) {
if (force) {
pendingRefresh.set(true)
}
return
}
viewModelScope.launch {
withContext(Dispatchers.IO) {
val patchList = (
NativeLibrary.getPatchesForFile(game!!.path, game!!.programId)
?: emptyArray()
).toMutableList()
try {
val patches = withContext(Dispatchers.IO) {
NativeLibrary.getPatchesForFile(currentGame.path, currentGame.programId)
} ?: return@launch
val patchList = patches.toMutableList()
patchList.sortBy { it.name }
// Ensure only one update is enabled
ensureSingleUpdateEnabled(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)
if (pendingRefresh.compareAndSet(true, false)) {
refreshAddons(force = true)
}
}
}
}
@ -119,17 +147,26 @@ class AddonViewModel : ViewModel() {
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
}
refreshAddons()
refreshAddons(force = true)
}
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
}
NativeConfig.setDisabledAddons(
game!!.programId,
_patchList.value.mapNotNull {
currentGame.programId,
currentList.mapNotNull {
if (it.enabled) {
null
} else {
@ -148,7 +185,8 @@ class AddonViewModel : ViewModel() {
}.toTypedArray()
)
NativeConfig.saveGlobalConfig()
_patchList.value.clear()
_patchList.value = emptyList()
loadedGameKey = null
game = null
}
@ -159,4 +197,8 @@ class AddonViewModel : ViewModel() {
fun showModNoticeDialog(show: Boolean) {
_showModNoticeDialog.value = show
}
private fun gameKey(game: Game): String {
return "${game.programId}|${game.path}"
}
}

View file

@ -642,7 +642,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
addonViewModel.refreshAddons()
addonViewModel.refreshAddons(force = true)
val separator = System.lineSeparator() ?: "\n"
val installResult = StringBuilder()

View file

@ -706,6 +706,7 @@ struct Values {
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_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{
linkage, false, "mouse_panning", Category::Controls, Specialization::Default, false};

View file

@ -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-License-Identifier: GPL-2.0-or-later
@ -18,17 +21,14 @@ namespace Service::Audio {
void LoopProcess(Core::System& 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("audout:u", std::make_shared<IAudioOutManager>(system));
server_manager->RegisterNamedService(
"audrec:a", std::make_shared<IFinalOutputRecorderManagerForApplet>(system));
server_manager->RegisterNamedService("audrec:u",
std::make_shared<IFinalOutputRecorderManager>(system));
server_manager->RegisterNamedService("audren:u",
std::make_shared<IAudioRendererManager>(system));
server_manager->RegisterNamedService("hwopus",
std::make_shared<IHardwareOpusDecoderManager>(system));
// Depends on audout:u and audin:u on ctor!
server_manager->RegisterNamedService("audctl", std::make_shared<IAudioController>(system));
server_manager->RegisterNamedService("audrec:a", std::make_shared<IFinalOutputRecorderManagerForApplet>(system));
server_manager->RegisterNamedService("audrec:u", std::make_shared<IFinalOutputRecorderManager>(system));
server_manager->RegisterNamedService("audren:u", std::make_shared<IAudioRendererManager>(system));
server_manager->RegisterNamedService("hwopus", std::make_shared<IHardwareOpusDecoderManager>(system));
ServerManager::RunServer(std::move(server_manager));
}

View file

@ -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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@ -35,6 +35,7 @@ void ConfigureTasDialog::LoadConfiguration() {
ui->tas_enable->setChecked(Settings::values.tas_enable.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_show_recording_dialog->setChecked(Settings::values.tas_show_recording_dialog.GetValue());
}
void ConfigureTasDialog::ApplyConfiguration() {
@ -42,6 +43,7 @@ void ConfigureTasDialog::ApplyConfiguration() {
Settings::values.tas_enable.SetValue(ui->tas_enable->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.tas_show_recording_dialog.SetValue(ui->tas_show_recording_dialog->isChecked());
}
void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) {

View file

@ -78,6 +78,13 @@
</property>
</widget>
</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>
</widget>
</item>

View file

@ -3665,6 +3665,7 @@ void MainWindow::OnTasRecord() {
const bool is_recording = input_subsystem->GetTas()->Record();
if (!is_recording) {
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?"),
@ -3672,6 +3673,9 @@ void MainWindow::OnTasRecord() {
input_subsystem->GetTas()->SaveRecording(answer);
is_tas_recording_dialog_active = false;
} else {
input_subsystem->GetTas()->SaveRecording(true);
}
}
OnTasStateChanged();
}