From 75f00f756f09d3c42366462c1b52bee9850af11e Mon Sep 17 00:00:00 2001 From: Johnny Cai Date: Sat, 26 Nov 2022 08:33:28 -0500 Subject: [PATCH] Make configure dialog non-blocking Currently, when the configure dialog is open, it prevents inputs to the main window. This is annoying if you are trying to change your keybinds as you have to constantly reopen the configure dialog to test your keys. To make the window non-blocking, we use .show() instead of .exec() on the configure dialog, and have a slot handle when the confirmation buttons (ok, cancel, x) are pressed. However, entering the configuration dialog disables inputs to prevent sending inputs to the running game while changing inputs. We modify this by preventing inputs from being sent to the game only when focus is not on the main menu. The steps are as follows: - Determine focus in/focus out on main window with QGuiApplication::focusWindowChanged - Send focus in/focus out signal to ConfigureDialog slot - ConfigureDialog slot calls EnableConfiguration/DisableConfiguration for ConfigureInput - ConfigureInput calls EnableConfiguration/DisableConfiguration for ConfigureInputPlayer Added: - Signal FocusIn/FocusOut for GMainWindow - EnableConfiguration()/DisableConfiguration() for ConfigureInputPlayer and ConfigureInput to control whether inputs to the game should be blocked. ConfigureInput::EnableConfiguration/DisableConfiguration simply forwards the call to ConfigureInputPlayer. --- src/yuzu/configuration/configure_dialog.cpp | 8 +++ src/yuzu/configuration/configure_dialog.h | 4 ++ src/yuzu/configuration/configure_input.cpp | 12 +++++ src/yuzu/configuration/configure_input.h | 6 +++ .../configuration/configure_input_player.cpp | 48 +++++++++-------- .../configuration/configure_input_player.h | 6 +++ src/yuzu/main.cpp | 51 +++++++++++++++---- src/yuzu/main.h | 21 ++++++++ 8 files changed, 126 insertions(+), 30 deletions(-) diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 4301313cfd..6b059a3ead 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -117,6 +117,14 @@ void ConfigureDialog::ApplyConfiguration() { Settings::LogSettings(); } +void ConfigureDialog::MainWindowFocusIn() { + input_tab->DisableConfiguration(); +} + +void ConfigureDialog::MainWindowFocusOut() { + input_tab->EnableConfiguration(); +} + void ConfigureDialog::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { RetranslateUI(); diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index 1f724834aa..cd54b31be8 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -46,6 +46,10 @@ public: void ApplyConfiguration(); +public slots: + void MainWindowFocusIn(); + void MainWindowFocusOut(); + private slots: void OnLanguageChanged(const QString& locale); diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 1db374d4a2..d85417fb5f 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -181,6 +181,18 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, LoadConfiguration(); } +void ConfigureInput::EnableConfiguration() { + for (auto controller : player_controllers) { + controller->EnableConfiguration(); + } +} + +void ConfigureInput::DisableConfiguration() { + for (auto controller : player_controllers) { + controller->DisableConfiguration(); + } +} + QList ConfigureInput::GetSubTabs() const { return { ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index c89189c365..3380a51162 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -43,6 +43,12 @@ public: /// Initializes the input dialog with the given input subsystem. void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8); + /// Enables configuration on controllers + void EnableConfiguration(); + + /// Disables configuration on controllers + void DisableConfiguration(); + /// Save all button configurations to settings file. void ApplyConfiguration(); diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 9e5a40fe74..508ddbd05f 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -274,26 +274,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i is_powered_on{is_powered_on_}, input_subsystem{input_subsystem_}, profiles(profiles_), timeout_timer(std::make_unique()), poll_timer(std::make_unique()), bottom_row{bottom_row_}, hid_core{hid_core_} { - if (player_index == 0) { - auto* emulated_controller_p1 = - hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); - auto* emulated_controller_handheld = - hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); - emulated_controller_p1->SaveCurrentConfig(); - emulated_controller_p1->EnableConfiguration(); - emulated_controller_handheld->SaveCurrentConfig(); - emulated_controller_handheld->EnableConfiguration(); - if (emulated_controller_handheld->IsConnected(true)) { - emulated_controller_p1->Disconnect(); - emulated_controller = emulated_controller_handheld; - } else { - emulated_controller = emulated_controller_p1; - } - } else { - emulated_controller = hid_core.GetEmulatedControllerByIndex(player_index); - emulated_controller->SaveCurrentConfig(); - emulated_controller->EnableConfiguration(); - } + EnableConfiguration(); ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -771,6 +752,33 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } ConfigureInputPlayer::~ConfigureInputPlayer() { + DisableConfiguration(); +} + +void ConfigureInputPlayer::EnableConfiguration() { + if (player_index == 0) { + auto* emulated_controller_p1 = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* emulated_controller_handheld = + hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); + emulated_controller_p1->SaveCurrentConfig(); + emulated_controller_p1->EnableConfiguration(); + emulated_controller_handheld->SaveCurrentConfig(); + emulated_controller_handheld->EnableConfiguration(); + if (emulated_controller_handheld->IsConnected(true)) { + emulated_controller_p1->Disconnect(); + emulated_controller = emulated_controller_handheld; + } else { + emulated_controller = emulated_controller_p1; + } + } else { + emulated_controller = hid_core.GetEmulatedControllerByIndex(player_index); + emulated_controller->SaveCurrentConfig(); + emulated_controller->EnableConfiguration(); + } +} + +void ConfigureInputPlayer::DisableConfiguration() { if (player_index == 0) { auto* emulated_controller_p1 = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index 79434fdd8d..fe885c6fb6 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -56,6 +56,12 @@ public: bool is_powered_on_, bool debug = false); ~ConfigureInputPlayer() override; + /// Enables configuration on controllers + void EnableConfiguration(); + + /// Disables configuration on controllers + void DisableConfiguration(); + /// Save all button configurations to settings file. void ApplyConfiguration(); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 4081af391d..3456d0c338 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -72,6 +72,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include #include #include +#include #include #ifdef HAVE_SDL2 @@ -1246,6 +1247,16 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) { } } +void GMainWindow::FocusWindowChanged(QWindow* focusWindow) { + if (focusWindow && focusWindow->winId() == winId() && !main_window_in_focus) { + emit FocusIn(); + main_window_in_focus = true; + } else if (main_window_in_focus) { + emit FocusOut(); + main_window_in_focus = false; + } +} + void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::BootGame, this, &GMainWindow::BootGame); connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); @@ -3023,16 +3034,33 @@ void GMainWindow::ResetWindowSize1080() { } void GMainWindow::OnConfigure() { - const auto old_theme = UISettings::values.theme; - const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); + old_configure_value = {UISettings::values.theme, + UISettings::values.enable_discord_presence.GetValue()}; Settings::SetConfiguringGlobal(true); - ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system, - !multiplayer_state->IsHostingPublicRoom()); - connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this, - &GMainWindow::OnLanguageChanged); - const auto result = configure_dialog.exec(); + configure_dialog.reset(); // The destructor must run first + configure_dialog = + std::make_unique(this, hotkey_registry, input_subsystem.get(), *system, + !multiplayer_state->IsHostingPublicRoom()); + connect(configure_dialog.get(), &ConfigureDialog::LanguageChanged, this, + &GMainWindow::OnLanguageChanged); + connect(configure_dialog.get(), &ConfigureDialog::finished, this, + &GMainWindow::OnConfigureFinished); + + configure_dialog->show(); + + connect(this, &GMainWindow::FocusIn, configure_dialog.get(), + &ConfigureDialog::MainWindowFocusIn); + connect(this, &GMainWindow::FocusOut, configure_dialog.get(), + &ConfigureDialog::MainWindowFocusOut); +} + +void GMainWindow::OnConfigureFinished(int result) { + // Configure dialog needs to be deleted after this function finishes to run the appropriate + // destructors + std::unique_ptr config_dialog(std::move(configure_dialog)); + if (result != QDialog::Accepted && !UISettings::values.configuration_applied && !UISettings::values.reset_to_defaults) { // Runs if the user hit Cancel or closed the window, and did not ever press the Apply button @@ -3042,7 +3070,7 @@ void GMainWindow::OnConfigure() { // Only apply new changes if user hit Okay // This is here to avoid applying changes if the user hit Apply, made some changes, then hit // Cancel - configure_dialog.ApplyConfiguration(); + config_dialog->ApplyConfiguration(); } else if (UISettings::values.reset_to_defaults) { LOG_INFO(Frontend, "Resetting all settings to defaults"); if (!Common::FS::RemoveFile(config->GetConfigFilePath())) { @@ -3079,10 +3107,11 @@ void GMainWindow::OnConfigure() { } InitializeHotkeys(); - if (UISettings::values.theme != old_theme) { + if (UISettings::values.theme != old_configure_value.theme) { UpdateUITheme(); } - if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) { + if (UISettings::values.enable_discord_presence.GetValue() != + old_configure_value.discord_presence) { SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); } @@ -4232,6 +4261,8 @@ int main(int argc, char* argv[]) { QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window, &GMainWindow::OnAppFocusStateChanged); + QObject::connect(&app, &QGuiApplication::focusWindowChanged, &main_window, + &GMainWindow::FocusWindowChanged); int result = app.exec(); detached_tasks.WaitForAllTasks(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 6a9992d05b..60e776e566 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -23,6 +23,7 @@ class Config; class ClickableLabel; +class ConfigureDialog; class EmuThread; class GameList; class GImageInfo; @@ -122,6 +123,12 @@ class GMainWindow : public QMainWindow { UI_EMU_STOPPING, }; + // Used to save previous configure values to check for changes + struct OldConfigureValue { + QString theme; + bool discord_presence; + }; + public: void filterBarSetChecked(bool state); void UpdateUITheme(); @@ -132,6 +139,11 @@ public: void AcceptDropEvent(QDropEvent* event); signals: + /// Emitted when this main window receives focus + void FocusIn(); + + /// Emitted when this main window loses focus + void FocusOut(); /** * Signal that is emitted when a new EmuThread has been created and an emulation session is @@ -195,6 +207,7 @@ public slots: void WebBrowserOpenWebPage(const std::string& main_url, const std::string& additional_args, bool is_local); void OnAppFocusStateChanged(Qt::ApplicationState state); + void FocusWindowChanged(QWindow* focusWindow); void OnTasStateChanged(); private: @@ -303,6 +316,7 @@ private slots: void OnMenuInstallToNAND(); void OnMenuRecentFile(); void OnConfigure(); + void OnConfigureFinished(int result); void OnConfigureTas(); void OnTasStartStop(); void OnTasRecord(); @@ -430,6 +444,11 @@ private: // Install progress dialog QProgressDialog* install_progress; + // Configuration dialog, use unique_ptr as it is possible for the user to open the configure + // menu multiple times, thus we must delete the old menu + std::unique_ptr configure_dialog; + OldConfigureValue old_configure_value; + // Last game booted, used for multi-process apps QString last_filename_booted; @@ -445,6 +464,8 @@ private: // True if TAS recording dialog is visible bool is_tas_recording_dialog_active{}; + bool main_window_in_focus = false; + #ifdef __unix__ QSocketNotifier* sig_interrupt_notifier; static std::array sig_interrupt_fds;