From e495ef5148a54c2d9b225e09a28ac69a530a25db Mon Sep 17 00:00:00 2001 From: Collecting Date: Sat, 1 Nov 2025 21:01:39 +0000 Subject: [PATCH 1/5] fix/hotkeys Signed-off-by: Collecting --- src/citron/hotkeys.cpp | 77 ++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/src/citron/hotkeys.cpp b/src/citron/hotkeys.cpp index 7346e8a53..755ccc429 100644 --- a/src/citron/hotkeys.cpp +++ b/src/citron/hotkeys.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-FileCopyrightText: 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -14,45 +15,79 @@ HotkeyRegistry::HotkeyRegistry() = default; HotkeyRegistry::~HotkeyRegistry() = default; void HotkeyRegistry::SaveHotkeys() { + std::map> default_groups; + for (const auto& def : UISettings::default_hotkeys) { + default_groups[def.group][def.name] = + Hotkey{QKeySequence::fromString(QString::fromStdString(def.shortcut.keyseq)), + def.shortcut.controller_keyseq, + nullptr, + nullptr, + static_cast(def.shortcut.context), + def.shortcut.repeat}; + } + UISettings::values.shortcuts.clear(); - for (const auto& group : hotkey_groups) { - for (const auto& hotkey : group.second) { - UISettings::values.shortcuts.push_back( - {hotkey.first, group.first, - UISettings::ContextualShortcut({hotkey.second.keyseq.toString().toStdString(), - hotkey.second.controller_keyseq, - hotkey.second.context, hotkey.second.repeat})}); + for (const auto& [group_name, actions] : hotkey_groups) { + for (const auto& [action_name, current_hotkey] : actions) { + Hotkey default_hotkey; + auto group_it = default_groups.find(group_name); + if (group_it != default_groups.end()) { + auto action_it = group_it->second.find(action_name); + if (action_it != group_it->second.end()) { + default_hotkey = action_it->second; + } + } + + bool is_modified = + (current_hotkey.keyseq != default_hotkey.keyseq) || + (current_hotkey.controller_keyseq != default_hotkey.controller_keyseq) || + (current_hotkey.context != default_hotkey.context); + + if (is_modified) { + UISettings::values.shortcuts.push_back( + {action_name, group_name, + UISettings::ContextualShortcut( + {current_hotkey.keyseq.toString().toStdString(), + current_hotkey.controller_keyseq, current_hotkey.context, + current_hotkey.repeat})}); + } } } } void HotkeyRegistry::LoadHotkeys() { - // Make sure NOT to use a reference here because it would become invalid once we call - // beginGroup() - for (auto shortcut : UISettings::values.shortcuts) { + // First, populate the registry with ALL default hotkeys, including blank ones. + hotkey_groups.clear(); + for (const auto& def : UISettings::default_hotkeys) { + Hotkey& hk = hotkey_groups[def.group][def.name]; + hk.keyseq = QKeySequence::fromString(QString::fromStdString(def.shortcut.keyseq)); + hk.controller_keyseq = def.shortcut.controller_keyseq; + hk.context = static_cast(def.shortcut.context); + hk.repeat = def.shortcut.repeat; + } + + // Now, layer the user's saved (non-default) settings on top. + for (const auto& shortcut : UISettings::values.shortcuts) { Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name]; if (!shortcut.shortcut.keyseq.empty()) { hk.keyseq = QKeySequence::fromString(QString::fromStdString(shortcut.shortcut.keyseq), QKeySequence::NativeText); - hk.context = static_cast(shortcut.shortcut.context); - } - if (!shortcut.shortcut.controller_keyseq.empty()) { - hk.controller_keyseq = shortcut.shortcut.controller_keyseq; } + hk.controller_keyseq = shortcut.shortcut.controller_keyseq; + hk.context = static_cast(shortcut.shortcut.context); + hk.repeat = shortcut.shortcut.repeat; + if (hk.shortcut) { - hk.shortcut->disconnect(); hk.shortcut->setKey(hk.keyseq); } if (hk.controller_shortcut) { - hk.controller_shortcut->disconnect(); hk.controller_shortcut->SetKey(hk.controller_keyseq); } - hk.repeat = shortcut.shortcut.repeat; } } QShortcut* HotkeyRegistry::GetHotkey(const std::string& group, const std::string& action, - QWidget* widget) { + QWidget* widget) const { Hotkey& hk = hotkey_groups[group][action]; if (!hk.shortcut) { @@ -65,7 +100,7 @@ QShortcut* HotkeyRegistry::GetHotkey(const std::string& group, const std::string ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const std::string& group, const std::string& action, - Core::HID::EmulatedController* controller) { + Core::HID::EmulatedController* controller) const { Hotkey& hk = hotkey_groups[group][action]; if (!hk.controller_shortcut) { @@ -76,12 +111,12 @@ ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const std::string& group return hk.controller_shortcut; } -QKeySequence HotkeyRegistry::GetKeySequence(const std::string& group, const std::string& action) { +QKeySequence HotkeyRegistry::GetKeySequence(const std::string& group, const std::string& action) const { return hotkey_groups[group][action].keyseq; } Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const std::string& group, - const std::string& action) { + const std::string& action) const { return hotkey_groups[group][action].context; } From 45cfbbeb6b1b1e7c95a30ad7d21df69c6dce3e92 Mon Sep 17 00:00:00 2001 From: Collecting Date: Sat, 1 Nov 2025 21:02:21 +0000 Subject: [PATCH 2/5] fix/hotkeys Signed-off-by: Collecting --- src/citron/hotkeys.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/citron/hotkeys.h b/src/citron/hotkeys.h index bdc081649..da699225d 100644 --- a/src/citron/hotkeys.h +++ b/src/citron/hotkeys.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-FileCopyrightText: 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -88,9 +89,9 @@ public: * will be the same. Thus, you shouldn't rely on the caller really being the * QShortcut's parent. */ - QShortcut* GetHotkey(const std::string& group, const std::string& action, QWidget* widget); + QShortcut* GetHotkey(const std::string& group, const std::string& action, QWidget* widget) const; ControllerShortcut* GetControllerHotkey(const std::string& group, const std::string& action, - Core::HID::EmulatedController* controller); + Core::HID::EmulatedController* controller) const; /** * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut. @@ -98,7 +99,7 @@ public: * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger"). * @param action Name of the action (e.g. "Start Emulation", "Load Image"). */ - QKeySequence GetKeySequence(const std::string& group, const std::string& action); + QKeySequence GetKeySequence(const std::string& group, const std::string& action) const; /** * Returns a Qt::ShortcutContext object who can be connected to other @@ -108,7 +109,7 @@ public: * "Debugger"). * @param action Name of the action (e.g. "Start Emulation", "Load Image"). */ - Qt::ShortcutContext GetShortcutContext(const std::string& group, const std::string& action); + Qt::ShortcutContext GetShortcutContext(const std::string& group, const std::string& action) const; private: struct Hotkey { @@ -123,5 +124,5 @@ private: using HotkeyMap = std::map; using HotkeyGroupMap = std::map; - HotkeyGroupMap hotkey_groups; + mutable HotkeyGroupMap hotkey_groups; }; From 0d5506030bd1178b0bba8782e7a844ca0448cdf4 Mon Sep 17 00:00:00 2001 From: Collecting Date: Sat, 1 Nov 2025 21:02:55 +0000 Subject: [PATCH 3/5] fix/hotkey-memory Signed-off-by: Collecting --- src/citron/configuration/qt_config.cpp | 58 ++++++++++---------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/src/citron/configuration/qt_config.cpp b/src/citron/configuration/qt_config.cpp index 39decf2a0..00eb7dfc6 100644 --- a/src/citron/configuration/qt_config.cpp +++ b/src/citron/configuration/qt_config.cpp @@ -240,24 +240,21 @@ void QtConfig::ReadPathValues() { void QtConfig::ReadShortcutValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts)); - for (const auto& [name, group, shortcut] : UISettings::default_hotkeys) { - BeginGroup(group); - BeginGroup(name); + UISettings::values.shortcuts.clear(); + const int shortcuts_size = BeginArray(std::string("shortcuts")); + for (int i = 0; i < shortcuts_size; ++i) { + SetArrayIndex(i); + const std::string name = ReadStringSetting(std::string("name")); + const std::string group = ReadStringSetting(std::string("group")); + const std::string keyseq = ReadStringSetting(std::string("keyseq")); + const std::string controller_keyseq = ReadStringSetting(std::string("controller_keyseq")); + const int context = ReadIntegerSetting(std::string("context"), Qt::WindowShortcut); + const bool repeat = ReadBooleanSetting(std::string("repeat"), false); - // No longer using ReadSetting for shortcut.second as it inaccurately returns a value of 1 - // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open - // a file dialog in windowed mode UISettings::values.shortcuts.push_back( - {name, - group, - {ReadStringSetting(std::string("KeySeq"), shortcut.keyseq), - ReadStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq), - shortcut.context, - ReadBooleanSetting(std::string("Repeat"), std::optional(shortcut.repeat))}}); - - EndGroup(); // name - EndGroup(); // group + {name, group, {keyseq, controller_keyseq, context, repeat}}); } + EndArray(); EndGroup(); } @@ -445,27 +442,18 @@ void QtConfig::SavePathValues() { void QtConfig::SaveShortcutValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts)); - // Lengths of UISettings::values.shortcuts & default_hotkeys are same. - // However, their ordering must also be the same. - for (std::size_t i = 0; i < UISettings::default_hotkeys.size(); i++) { - const auto& [name, group, shortcut] = UISettings::values.shortcuts[i]; - const auto& default_hotkey = UISettings::default_hotkeys[i].shortcut; - - BeginGroup(group); - BeginGroup(name); - - WriteStringSetting(std::string("KeySeq"), shortcut.keyseq, - std::make_optional(default_hotkey.keyseq)); - WriteStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq, - std::make_optional(default_hotkey.controller_keyseq)); - WriteIntegerSetting(std::string("Context"), shortcut.context, - std::make_optional(default_hotkey.context)); - WriteBooleanSetting(std::string("Repeat"), shortcut.repeat, - std::make_optional(default_hotkey.repeat)); - - EndGroup(); // name - EndGroup(); // group + BeginArray(std::string("shortcuts")); + for (std::size_t i = 0; i < UISettings::values.shortcuts.size(); ++i) { + SetArrayIndex(static_cast(i)); // SetArrayIndex expects an int, so we cast here. + const auto& shortcut = UISettings::values.shortcuts[i]; + WriteStringSetting(std::string("name"), shortcut.name); + WriteStringSetting(std::string("group"), shortcut.group); + WriteStringSetting(std::string("keyseq"), shortcut.shortcut.keyseq); + WriteStringSetting(std::string("controller_keyseq"), shortcut.shortcut.controller_keyseq); + WriteIntegerSetting(std::string("context"), shortcut.shortcut.context); + WriteBooleanSetting(std::string("repeat"), shortcut.shortcut.repeat); } + EndArray(); EndGroup(); } From 60f314daefa8005961112c26a01953fef1d17c6d Mon Sep 17 00:00:00 2001 From: Collecting Date: Sat, 1 Nov 2025 21:03:34 +0000 Subject: [PATCH 4/5] add/controller-overlay-toggle Signed-off-by: Collecting --- src/citron/uisettings.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/citron/uisettings.h b/src/citron/uisettings.h index 289990615..a8a6aa99d 100644 --- a/src/citron/uisettings.h +++ b/src/citron/uisettings.h @@ -239,7 +239,7 @@ namespace UISettings { // This must be in alphabetical order according to action name as it must have the same order as // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off - const std::array default_hotkeys{{ + const std::array default_hotkeys{{ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+M"), std::string("Home+Dpad_Right"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("-"), std::string("Home+Dpad_Down"), Qt::ApplicationShortcut, true}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("="), std::string("Home+Dpad_Up"), Qt::ApplicationShortcut, true}}, @@ -263,6 +263,7 @@ namespace UISettings { {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F7"), std::string(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F6"), std::string(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F5"), std::string(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Controller Overlay")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F"), std::string(""), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Grid View")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+G"), std::string(""), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+U"), std::string("Home+Y"), Qt::ApplicationShortcut, false}}, From ac777fabd93633c68327be7d3bcf88133dd96d47 Mon Sep 17 00:00:00 2001 From: Collecting Date: Sat, 1 Nov 2025 21:06:32 +0000 Subject: [PATCH 5/5] fix/controller-overlay-toggle Signed-off-by: Collecting --- src/citron/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/citron/main.cpp b/src/citron/main.cpp index ccb27fd34..ae7c13992 100644 --- a/src/citron/main.cpp +++ b/src/citron/main.cpp @@ -357,6 +357,9 @@ GMainWindow::GMainWindow(std::unique_ptr config_, bool has_broken_vulk ui->setupUi(this); statusBar()->hide(); + // Controller Overlay toggle + ui->actionControllerOverlay->setCheckable(true); + // Check dark mode before a theme is loaded os_dark_mode = CheckDarkMode(); startup_icon_theme = QIcon::themeName(); @@ -1388,6 +1391,7 @@ void GMainWindow::InitializeHotkeys() { LinkActionShortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar")); LinkActionShortcut(ui->action_Show_Performance_Overlay, QStringLiteral("Toggle Performance Overlay")); LinkActionShortcut(ui->action_Show_Vram_Overlay, QStringLiteral("Toggle VRAM Overlay")); + LinkActionShortcut(ui->actionControllerOverlay, QStringLiteral("Toggle Controller Overlay")); LinkActionShortcut(ui->action_Show_Multiplayer_Room_Overlay, QStringLiteral("Toggle Multiplayer Room Overlay")); LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen")); LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot")); @@ -6069,6 +6073,7 @@ int main(int argc, char* argv[]) { #endif #ifdef CITRON_USE_AUTO_UPDATER + // Check for and apply staged updates before starting the main application std::filesystem::path app_dir = std::filesystem::path(QCoreApplication::applicationDirPath().toStdString()); #ifdef _WIN32