Merge pull request 'feat(hotkeys): Add controller overlay hotkey and fix saving bug' (#14) from fix/hotkey-memory into main

Reviewed-on: https://git.citron-emu.org/Citron/Emulator/pulls/14
This commit is contained in:
Collecting
2025-11-01 21:11:19 +00:00
5 changed files with 92 additions and 62 deletions

View File

@@ -240,24 +240,21 @@ void QtConfig::ReadPathValues() {
void QtConfig::ReadShortcutValues() { void QtConfig::ReadShortcutValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts)); BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));
for (const auto& [name, group, shortcut] : UISettings::default_hotkeys) { UISettings::values.shortcuts.clear();
BeginGroup(group); const int shortcuts_size = BeginArray(std::string("shortcuts"));
BeginGroup(name); 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( UISettings::values.shortcuts.push_back(
{name, {name, group, {keyseq, controller_keyseq, context, repeat}});
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
} }
EndArray();
EndGroup(); EndGroup();
} }
@@ -445,27 +442,18 @@ void QtConfig::SavePathValues() {
void QtConfig::SaveShortcutValues() { void QtConfig::SaveShortcutValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts)); BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));
// Lengths of UISettings::values.shortcuts & default_hotkeys are same. BeginArray(std::string("shortcuts"));
// However, their ordering must also be the same. for (std::size_t i = 0; i < UISettings::values.shortcuts.size(); ++i) {
for (std::size_t i = 0; i < UISettings::default_hotkeys.size(); i++) { SetArrayIndex(static_cast<int>(i)); // SetArrayIndex expects an int, so we cast here.
const auto& [name, group, shortcut] = UISettings::values.shortcuts[i]; const auto& shortcut = UISettings::values.shortcuts[i];
const auto& default_hotkey = UISettings::default_hotkeys[i].shortcut; WriteStringSetting(std::string("name"), shortcut.name);
WriteStringSetting(std::string("group"), shortcut.group);
BeginGroup(group); WriteStringSetting(std::string("keyseq"), shortcut.shortcut.keyseq);
BeginGroup(name); WriteStringSetting(std::string("controller_keyseq"), shortcut.shortcut.controller_keyseq);
WriteIntegerSetting(std::string("context"), shortcut.shortcut.context);
WriteStringSetting(std::string("KeySeq"), shortcut.keyseq, WriteBooleanSetting(std::string("repeat"), shortcut.shortcut.repeat);
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
} }
EndArray();
EndGroup(); EndGroup();
} }

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-FileCopyrightText: 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <sstream> #include <sstream>
@@ -14,45 +15,79 @@ HotkeyRegistry::HotkeyRegistry() = default;
HotkeyRegistry::~HotkeyRegistry() = default; HotkeyRegistry::~HotkeyRegistry() = default;
void HotkeyRegistry::SaveHotkeys() { void HotkeyRegistry::SaveHotkeys() {
std::map<std::string, std::map<std::string, Hotkey>> 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<Qt::ShortcutContext>(def.shortcut.context),
def.shortcut.repeat};
}
UISettings::values.shortcuts.clear(); UISettings::values.shortcuts.clear();
for (const auto& group : hotkey_groups) { for (const auto& [group_name, actions] : hotkey_groups) {
for (const auto& hotkey : group.second) { for (const auto& [action_name, current_hotkey] : actions) {
UISettings::values.shortcuts.push_back( Hotkey default_hotkey;
{hotkey.first, group.first, auto group_it = default_groups.find(group_name);
UISettings::ContextualShortcut({hotkey.second.keyseq.toString().toStdString(), if (group_it != default_groups.end()) {
hotkey.second.controller_keyseq, auto action_it = group_it->second.find(action_name);
hotkey.second.context, hotkey.second.repeat})}); 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() { void HotkeyRegistry::LoadHotkeys() {
// Make sure NOT to use a reference here because it would become invalid once we call // First, populate the registry with ALL default hotkeys, including blank ones.
// beginGroup() hotkey_groups.clear();
for (auto shortcut : UISettings::values.shortcuts) { 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<Qt::ShortcutContext>(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]; Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
if (!shortcut.shortcut.keyseq.empty()) { if (!shortcut.shortcut.keyseq.empty()) {
hk.keyseq = QKeySequence::fromString(QString::fromStdString(shortcut.shortcut.keyseq), hk.keyseq = QKeySequence::fromString(QString::fromStdString(shortcut.shortcut.keyseq),
QKeySequence::NativeText); QKeySequence::NativeText);
hk.context = static_cast<Qt::ShortcutContext>(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<Qt::ShortcutContext>(shortcut.shortcut.context);
hk.repeat = shortcut.shortcut.repeat;
if (hk.shortcut) { if (hk.shortcut) {
hk.shortcut->disconnect();
hk.shortcut->setKey(hk.keyseq); hk.shortcut->setKey(hk.keyseq);
} }
if (hk.controller_shortcut) { if (hk.controller_shortcut) {
hk.controller_shortcut->disconnect();
hk.controller_shortcut->SetKey(hk.controller_keyseq); hk.controller_shortcut->SetKey(hk.controller_keyseq);
} }
hk.repeat = shortcut.shortcut.repeat;
} }
} }
QShortcut* HotkeyRegistry::GetHotkey(const std::string& group, const std::string& action, QShortcut* HotkeyRegistry::GetHotkey(const std::string& group, const std::string& action,
QWidget* widget) { QWidget* widget) const {
Hotkey& hk = hotkey_groups[group][action]; Hotkey& hk = hotkey_groups[group][action];
if (!hk.shortcut) { if (!hk.shortcut) {
@@ -65,7 +100,7 @@ QShortcut* HotkeyRegistry::GetHotkey(const std::string& group, const std::string
ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const std::string& group, ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const std::string& group,
const std::string& action, const std::string& action,
Core::HID::EmulatedController* controller) { Core::HID::EmulatedController* controller) const {
Hotkey& hk = hotkey_groups[group][action]; Hotkey& hk = hotkey_groups[group][action];
if (!hk.controller_shortcut) { if (!hk.controller_shortcut) {
@@ -76,12 +111,12 @@ ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const std::string& group
return hk.controller_shortcut; 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; return hotkey_groups[group][action].keyseq;
} }
Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const std::string& group, Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const std::string& group,
const std::string& action) { const std::string& action) const {
return hotkey_groups[group][action].context; return hotkey_groups[group][action].context;
} }

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-FileCopyrightText: 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
@@ -88,9 +89,9 @@ public:
* will be the same. Thus, you shouldn't rely on the caller really being the * will be the same. Thus, you shouldn't rely on the caller really being the
* QShortcut's parent. * 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, 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. * 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 group General group this hotkey belongs to (e.g. "Main Window", "Debugger").
* @param action Name of the action (e.g. "Start Emulation", "Load Image"). * @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 * Returns a Qt::ShortcutContext object who can be connected to other
@@ -108,7 +109,7 @@ public:
* "Debugger"). * "Debugger").
* @param action Name of the action (e.g. "Start Emulation", "Load Image"). * @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: private:
struct Hotkey { struct Hotkey {
@@ -123,5 +124,5 @@ private:
using HotkeyMap = std::map<std::string, Hotkey>; using HotkeyMap = std::map<std::string, Hotkey>;
using HotkeyGroupMap = std::map<std::string, HotkeyMap>; using HotkeyGroupMap = std::map<std::string, HotkeyMap>;
HotkeyGroupMap hotkey_groups; mutable HotkeyGroupMap hotkey_groups;
}; };

View File

@@ -357,6 +357,9 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
ui->setupUi(this); ui->setupUi(this);
statusBar()->hide(); statusBar()->hide();
// Controller Overlay toggle
ui->actionControllerOverlay->setCheckable(true);
// Check dark mode before a theme is loaded // Check dark mode before a theme is loaded
os_dark_mode = CheckDarkMode(); os_dark_mode = CheckDarkMode();
startup_icon_theme = QIcon::themeName(); 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_Status_Bar, QStringLiteral("Toggle Status Bar"));
LinkActionShortcut(ui->action_Show_Performance_Overlay, QStringLiteral("Toggle Performance Overlay")); LinkActionShortcut(ui->action_Show_Performance_Overlay, QStringLiteral("Toggle Performance Overlay"));
LinkActionShortcut(ui->action_Show_Vram_Overlay, QStringLiteral("Toggle VRAM 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_Show_Multiplayer_Room_Overlay, QStringLiteral("Toggle Multiplayer Room Overlay"));
LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen")); LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen"));
LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot")); LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot"));
@@ -6069,6 +6073,7 @@ int main(int argc, char* argv[]) {
#endif #endif
#ifdef CITRON_USE_AUTO_UPDATER #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()); std::filesystem::path app_dir = std::filesystem::path(QCoreApplication::applicationDirPath().toStdString());
#ifdef _WIN32 #ifdef _WIN32

View File

@@ -239,7 +239,7 @@ namespace UISettings {
// This must be in alphabetical order according to action name as it must have the same order as // 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. // UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off // clang-format off
const std::array<Shortcut, 29> default_hotkeys{{ const std::array<Shortcut, 30> 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 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 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}}, {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 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 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", "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 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 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}}, {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}},