From 7e9800378911732f044f0b5b4a115de5b8567ba1 Mon Sep 17 00:00:00 2001 From: NovaChild Date: Fri, 10 Jul 2026 17:48:14 -0500 Subject: [PATCH] Bugfix: All desktop hotkeys now work in dual-window mode (#1967) * Bugfix: All desktop hotkeys now work in dual-window mode. The fullscreen hotkey now works differently. It always changes the fullscreen setting, and attempts to fullscreen both windows in dual-screen mode *if* they are on different screens. Othewise it only fullscreens the main screen for consistency between layouts. Geometry is always saved and restored from UISettings for more consistency. * return to single-window fullscreening behavior --- CMakeModules/GenerateSettingKeys.cmake | 1 + src/citra_qt/citra_qt.cpp | 83 ++++++++++++++------------ src/citra_qt/configuration/config.cpp | 56 +++++++++-------- src/citra_qt/uisettings.h | 2 +- 4 files changed, 77 insertions(+), 65 deletions(-) diff --git a/CMakeModules/GenerateSettingKeys.cmake b/CMakeModules/GenerateSettingKeys.cmake index ead467ef5..ae540b944 100644 --- a/CMakeModules/GenerateSettingKeys.cmake +++ b/CMakeModules/GenerateSettingKeys.cmake @@ -201,6 +201,7 @@ if (ENABLE_QT) "geometry" "state" "geometryRenderWindow" + "geometrySecondaryWindow" "gameListHeaderState" "microProfileDialogGeometry" "name" diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index b91605085..f08a7ea2f 100644 --- a/src/citra_qt/citra_qt.cpp +++ b/src/citra_qt/citra_qt.cpp @@ -555,11 +555,6 @@ void GMainWindow::InitializeWidgets() { secondary_window->hide(); secondary_window->setParent(nullptr); - action_secondary_fullscreen = new QAction(secondary_window); - action_secondary_toggle_screen = new QAction(secondary_window); - action_secondary_swap_screen = new QAction(secondary_window); - action_secondary_rotate_screen = new QAction(secondary_window); - game_list = new GameList(*play_time_manager, this); ui->horizontalLayout->addWidget(game_list); @@ -865,11 +860,19 @@ void GMainWindow::InitializeHotkeys() { const bool primary_only = false, const bool auto_repeat = false) { static const QString main_window = QStringLiteral("Main Window"); - action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name)); + auto context = hotkey_registry.GetShortcutContext(main_window, action_name); + auto shortcut = hotkey_registry.GetKeySequence(main_window, action_name); + action->setShortcut(shortcut); + action->setShortcutContext(context); action->setAutoRepeat(auto_repeat); this->addAction(action); - if (!primary_only) - secondary_window->addAction(action); + // handle the shortcuts that are different per-screen + if (context == Qt::WidgetShortcut) { + render_window->addAction(action); + if (!primary_only) { + secondary_window->addAction(action); + } + } hotkey_registry.SetAction(main_window, action_name, action); }; @@ -882,7 +885,7 @@ void GMainWindow::InitializeHotkeys() { link_action_shortcut(ui->action_Stop, QStringLiteral("Stop Emulation")); link_action_shortcut(ui->action_Show_Filter_Bar, QStringLiteral("Toggle Filter Bar")); link_action_shortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar")); - link_action_shortcut(ui->action_Fullscreen, fullscreen, true); + link_action_shortcut(ui->action_Fullscreen, fullscreen); link_action_shortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot")); link_action_shortcut(ui->action_Debug_Pause, QStringLiteral("Debug Pause")); link_action_shortcut(ui->action_Debug_Resume, QStringLiteral("Debug Resume")); @@ -909,19 +912,21 @@ void GMainWindow::InitializeHotkeys() { // QShortcut Hotkeys const auto connect_shortcut = [&](const QString& action_name, const auto& function) { const auto* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this); - const auto* secondary_hotkey = - hotkey_registry.GetHotkey(main_window, action_name, secondary_window); connect(hotkey, &QShortcut::activated, this, function); - connect(secondary_hotkey, &QShortcut::activated, this, function); }; connect_shortcut(QStringLiteral("Toggle Screen Layout"), &GMainWindow::ToggleScreenLayout); connect_shortcut(QStringLiteral("Exit Fullscreen"), [&] { if (emulation_running) { - ui->action_Fullscreen->setChecked(false); - ToggleFullscreen(); + if (secondary_window->isActiveWindow()) { + secondary_window->showNormal(); + } else { + ui->action_Fullscreen->setChecked(false); + ToggleFullscreen(); + } } }); + connect_shortcut(QStringLiteral("Toggle Per-Application Speed"), [&] { if (!hotkey_registry .GetKeySequence(QStringLiteral("Main Window"), QStringLiteral("Toggle Turbo Mode")) @@ -972,21 +977,6 @@ void GMainWindow::InitializeHotkeys() { UpdateStatusBar(); } }); - - // Secondary Window QAction Hotkeys - const auto add_secondary_window_hotkey = [this](QAction* action, QKeySequence hotkey, - const char* slot) { - // This action will fire specifically when secondary_window is in focus - action->setShortcut(hotkey); - disconnect(action, SIGNAL(triggered()), this, slot); - connect(action, SIGNAL(triggered()), this, slot); - secondary_window->addAction(action); - }; - - // Use the same fullscreen hotkey as the main window - const auto fullscreen_hotkey = hotkey_registry.GetKeySequence(main_window, fullscreen); - add_secondary_window_hotkey(action_secondary_fullscreen, fullscreen_hotkey, - SLOT(ToggleSecondaryFullscreen())); } void GMainWindow::SetDefaultUIGeometry() { @@ -1005,6 +995,7 @@ void GMainWindow::RestoreUIState() { restoreGeometry(UISettings::values.geometry); restoreState(UISettings::values.state); render_window->restoreGeometry(UISettings::values.renderwindow_geometry); + secondary_window->restoreGeometry(UISettings::values.secondarywindow_geometry); #if MICROPROFILE_ENABLED microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry); microProfileDialog->setVisible(UISettings::values.microprofile_visible.GetValue()); @@ -2696,10 +2687,16 @@ void GMainWindow::ToggleFullscreen() { if (!emulation_running) { return; } - if (ui->action_Fullscreen->isChecked()) { - ShowFullscreen(); + if (secondary_window->isVisible() && secondary_window->isActiveWindow()) { + // undo the action and fullscreen secondary manually + ui->action_Fullscreen->toggle(); + ToggleSecondaryFullscreen(); } else { - HideFullscreen(); + if (ui->action_Fullscreen->isChecked()) { + ShowFullscreen(); + } else { + HideFullscreen(); + } } } @@ -2711,11 +2708,14 @@ void GMainWindow::ToggleSecondaryFullscreen() { #ifdef NEEDS_ROUND_CORNERS_FIX WindowCornerManager::instance().blockRoundedCorners(secondary_window, false); #endif + secondary_window->restoreGeometry(UISettings::values.secondarywindow_geometry); secondary_window->showNormal(); } else { #ifdef NEEDS_ROUND_CORNERS_FIX WindowCornerManager::instance().blockRoundedCorners(secondary_window, true); #endif + UISettings::values.secondarywindow_geometry = secondary_window->saveGeometry(); + LOG_INFO(Frontend, "Attempting to fullscreen secondary window"); secondary_window->showFullScreen(); } } @@ -2759,7 +2759,7 @@ void GMainWindow::HideFullscreen() { void GMainWindow::ToggleWindowMode() { if (ui->action_Single_Window_Mode->isChecked()) { // Render in the main window... - render_window->BackupGeometry(); + UISettings::values.renderwindow_geometry = render_window->saveGeometry(); ui->horizontalLayout->addWidget(render_window); render_window->setFocusPolicy(Qt::StrongFocus); if (emulation_running) { @@ -2772,10 +2772,9 @@ void GMainWindow::ToggleWindowMode() { // Render in a separate window... ui->horizontalLayout->removeWidget(render_window); render_window->setParent(nullptr); - render_window->setFocusPolicy(Qt::NoFocus); if (emulation_running) { render_window->setVisible(true); - render_window->RestoreGeometry(); + render_window->restoreGeometry(UISettings::values.renderwindow_geometry); game_list->show(); } } @@ -2786,12 +2785,18 @@ void GMainWindow::UpdateSecondaryWindowVisibility() { return; } if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) { - secondary_window->RestoreGeometry(); + secondary_window->restoreGeometry(UISettings::values.secondarywindow_geometry); secondary_window->show(); } else { - secondary_window->BackupGeometry(); + UISettings::values.secondarywindow_geometry = secondary_window->saveGeometry(); secondary_window->hide(); } + // make sure focus is on primary window whenever this changes + if (UISettings::values.single_window_mode.GetValue()) { + QApplication::setActiveWindow(this); + } else { + QApplication::setActiveWindow(render_window); + } } void GMainWindow::ChangeScreenLayout() { @@ -3235,7 +3240,6 @@ void GMainWindow::OnCaptureScreenshot() { .toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z")) .toStdString(); path.append(fmt::format("/{}_{}.png", filename, timestamp)); - auto* const screenshot_window = secondary_window->HasFocus() ? secondary_window : render_window; screenshot_window->CaptureScreenshot( @@ -4285,6 +4289,9 @@ void GMainWindow::UpdateUISettings() { UISettings::values.geometry = saveGeometry(); UISettings::values.renderwindow_geometry = render_window->saveGeometry(); } + if (!secondary_window->isFullScreen()) { + UISettings::values.secondarywindow_geometry = secondary_window->saveGeometry(); + } UISettings::values.state = saveState(); #if MICROPROFILE_ENABLED UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry(); diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 87633cf40..4653ddcd6 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -58,46 +58,46 @@ const std::array, Settings::NativeAnalog::NumAnalogs> QtConfi // clang-format off const std::vector QtConfig::default_hotkeys{ { {QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Audio Volume Up"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Debug Pause"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Debug Resume"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Debug Step"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Debug Unschedule All"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Debug Schedule All"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Audio Volume Up"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Debug Pause"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Debug Resume"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Debug Step"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Debug Unschedule All"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Debug Schedule All"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Decrease 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+-"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral("Exit Azahar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral("Exit Azahar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Increase 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl++"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Load from Newest Non-Quicksave Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Load from Newest Non-Quicksave Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Multiplayer Browse Public Rooms"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Multiplayer Create Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Multiplayer Direct Connect to Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Multiplayer Leave Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Multiplayer Show Current Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral("Quick Save"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Quick Load"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral("Quick Save"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Quick Load"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Save to Oldest Non-Quicksave Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+C"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Swap Screens"), QStringLiteral("Main Window"), {QStringLiteral("F9"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Save to Oldest Non-Quicksave Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+C"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Swap Screens"), QStringLiteral("Main Window"), {QStringLiteral("F9"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle 3D"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+3"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Custom Textures"), QStringLiteral("Main Window"), {QStringLiteral("F7"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Frame Advancing"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+A"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Per-Application Speed"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), QStringLiteral(""), Qt::ApplicationShortcut}}, - {QStringLiteral("Toggle Screen Layout"), QStringLiteral("Main Window"), {QStringLiteral("F10"), QStringLiteral(""), Qt::WindowShortcut}}, - {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}}, + {QStringLiteral("Toggle Screen Layout"), QStringLiteral("Main Window"), {QStringLiteral("F10"), QStringLiteral(""), Qt::ApplicationShortcut}}, + {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Texture Dumping"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Turbo Mode"), QStringLiteral("Main Window"), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut}}, } }; @@ -904,6 +904,8 @@ void QtConfig::ReadUILayoutValues() { UISettings::values.state = ReadSetting(Settings::QKeys::state).toByteArray(); UISettings::values.renderwindow_geometry = ReadSetting(Settings::QKeys::geometryRenderWindow).toByteArray(); + UISettings::values.secondarywindow_geometry = + ReadSetting(Settings::QKeys::geometrySecondaryWindow).toByteArray(); UISettings::values.gamelist_header_state = ReadSetting(Settings::QKeys::gameListHeaderState).toByteArray(); UISettings::values.microprofile_geometry = @@ -1436,6 +1438,8 @@ void QtConfig::SaveUILayoutValues() { WriteSetting(Settings::QKeys::geometry, UISettings::values.geometry); WriteSetting(Settings::QKeys::state, UISettings::values.state); WriteSetting(Settings::QKeys::geometryRenderWindow, UISettings::values.renderwindow_geometry); + WriteSetting(Settings::QKeys::geometrySecondaryWindow, + UISettings::values.secondarywindow_geometry); WriteSetting(Settings::QKeys::gameListHeaderState, UISettings::values.gamelist_header_state); WriteSetting(Settings::QKeys::microProfileDialogGeometry, UISettings::values.microprofile_geometry); diff --git a/src/citra_qt/uisettings.h b/src/citra_qt/uisettings.h index 7d1af29fc..b69d1cce5 100644 --- a/src/citra_qt/uisettings.h +++ b/src/citra_qt/uisettings.h @@ -72,7 +72,7 @@ struct Values { QByteArray state; QByteArray renderwindow_geometry; - + QByteArray secondarywindow_geometry; QByteArray gamelist_header_state; QByteArray microprofile_geometry;