diff options
| author | 2022-01-11 10:49:23 -0800 | |
|---|---|---|
| committer | 2022-01-11 10:49:23 -0800 | |
| commit | c65c651b6fb174084a26039ce6ea78e9cd3aedf0 (patch) | |
| tree | 9790b6abe3e9d05649629fe851031438ed6ad139 /src | |
| parent | Merge pull request #7683 from liushuyu/fmt-8.1 (diff) | |
| parent | yuzu: Add controller hotkeys (diff) | |
| download | yuzu-c65c651b6fb174084a26039ce6ea78e9cd3aedf0.tar.gz yuzu-c65c651b6fb174084a26039ce6ea78e9cd3aedf0.tar.xz yuzu-c65c651b6fb174084a26039ce6ea78e9cd3aedf0.zip | |
Merge pull request #7633 from german77/hotkeys
yuzu: Add controller hotkeys
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/hid/emulated_controller.cpp | 36 | ||||
| -rw-r--r-- | src/core/hid/emulated_controller.h | 20 | ||||
| -rw-r--r-- | src/core/hid/hid_types.h | 20 | ||||
| -rw-r--r-- | src/input_common/drivers/sdl_driver.cpp | 2 | ||||
| -rw-r--r-- | src/input_common/drivers/sdl_driver.h | 2 | ||||
| -rw-r--r-- | src/input_common/drivers/tas_input.cpp | 7 | ||||
| -rw-r--r-- | src/yuzu/configuration/config.cpp | 54 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_dialog.cpp | 2 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_hotkeys.cpp | 242 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_hotkeys.h | 23 | ||||
| -rw-r--r-- | src/yuzu/hotkeys.cpp | 165 | ||||
| -rw-r--r-- | src/yuzu/hotkeys.h | 46 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 64 | ||||
| -rw-r--r-- | src/yuzu/main.h | 17 | ||||
| -rw-r--r-- | src/yuzu/uisettings.h | 6 |
15 files changed, 626 insertions, 80 deletions
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 52a56ef1a..13edb7332 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp | |||
| @@ -351,6 +351,19 @@ void EmulatedController::DisableConfiguration() { | |||
| 351 | } | 351 | } |
| 352 | } | 352 | } |
| 353 | 353 | ||
| 354 | void EmulatedController::EnableSystemButtons() { | ||
| 355 | system_buttons_enabled = true; | ||
| 356 | } | ||
| 357 | |||
| 358 | void EmulatedController::DisableSystemButtons() { | ||
| 359 | system_buttons_enabled = false; | ||
| 360 | } | ||
| 361 | |||
| 362 | void EmulatedController::ResetSystemButtons() { | ||
| 363 | controller.home_button_state.home.Assign(false); | ||
| 364 | controller.capture_button_state.capture.Assign(false); | ||
| 365 | } | ||
| 366 | |||
| 354 | bool EmulatedController::IsConfiguring() const { | 367 | bool EmulatedController::IsConfiguring() const { |
| 355 | return is_configuring; | 368 | return is_configuring; |
| 356 | } | 369 | } |
| @@ -600,7 +613,16 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback | |||
| 600 | controller.npad_button_state.right_sr.Assign(current_status.value); | 613 | controller.npad_button_state.right_sr.Assign(current_status.value); |
| 601 | break; | 614 | break; |
| 602 | case Settings::NativeButton::Home: | 615 | case Settings::NativeButton::Home: |
| 616 | if (!system_buttons_enabled) { | ||
| 617 | break; | ||
| 618 | } | ||
| 619 | controller.home_button_state.home.Assign(current_status.value); | ||
| 620 | break; | ||
| 603 | case Settings::NativeButton::Screenshot: | 621 | case Settings::NativeButton::Screenshot: |
| 622 | if (!system_buttons_enabled) { | ||
| 623 | break; | ||
| 624 | } | ||
| 625 | controller.capture_button_state.capture.Assign(current_status.value); | ||
| 604 | break; | 626 | break; |
| 605 | } | 627 | } |
| 606 | } | 628 | } |
| @@ -1081,6 +1103,20 @@ BatteryValues EmulatedController::GetBatteryValues() const { | |||
| 1081 | return controller.battery_values; | 1103 | return controller.battery_values; |
| 1082 | } | 1104 | } |
| 1083 | 1105 | ||
| 1106 | HomeButtonState EmulatedController::GetHomeButtons() const { | ||
| 1107 | if (is_configuring) { | ||
| 1108 | return {}; | ||
| 1109 | } | ||
| 1110 | return controller.home_button_state; | ||
| 1111 | } | ||
| 1112 | |||
| 1113 | CaptureButtonState EmulatedController::GetCaptureButtons() const { | ||
| 1114 | if (is_configuring) { | ||
| 1115 | return {}; | ||
| 1116 | } | ||
| 1117 | return controller.capture_button_state; | ||
| 1118 | } | ||
| 1119 | |||
| 1084 | NpadButtonState EmulatedController::GetNpadButtons() const { | 1120 | NpadButtonState EmulatedController::GetNpadButtons() const { |
| 1085 | if (is_configuring) { | 1121 | if (is_configuring) { |
| 1086 | return {}; | 1122 | return {}; |
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index c0994ab4d..a63a83cce 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h | |||
| @@ -101,6 +101,8 @@ struct ControllerStatus { | |||
| 101 | VibrationValues vibration_values{}; | 101 | VibrationValues vibration_values{}; |
| 102 | 102 | ||
| 103 | // Data for HID serices | 103 | // Data for HID serices |
| 104 | HomeButtonState home_button_state{}; | ||
| 105 | CaptureButtonState capture_button_state{}; | ||
| 104 | NpadButtonState npad_button_state{}; | 106 | NpadButtonState npad_button_state{}; |
| 105 | DebugPadButton debug_pad_button_state{}; | 107 | DebugPadButton debug_pad_button_state{}; |
| 106 | AnalogSticks analog_stick_state{}; | 108 | AnalogSticks analog_stick_state{}; |
| @@ -198,6 +200,15 @@ public: | |||
| 198 | /// Returns the emulated controller into normal mode, allowing the modification of the HID state | 200 | /// Returns the emulated controller into normal mode, allowing the modification of the HID state |
| 199 | void DisableConfiguration(); | 201 | void DisableConfiguration(); |
| 200 | 202 | ||
| 203 | /// Enables Home and Screenshot buttons | ||
| 204 | void EnableSystemButtons(); | ||
| 205 | |||
| 206 | /// Disables Home and Screenshot buttons | ||
| 207 | void DisableSystemButtons(); | ||
| 208 | |||
| 209 | /// Sets Home and Screenshot buttons to false | ||
| 210 | void ResetSystemButtons(); | ||
| 211 | |||
| 201 | /// Returns true if the emulated controller is in configuring mode | 212 | /// Returns true if the emulated controller is in configuring mode |
| 202 | bool IsConfiguring() const; | 213 | bool IsConfiguring() const; |
| 203 | 214 | ||
| @@ -261,7 +272,13 @@ public: | |||
| 261 | /// Returns the latest battery status from the controller with parameters | 272 | /// Returns the latest battery status from the controller with parameters |
| 262 | BatteryValues GetBatteryValues() const; | 273 | BatteryValues GetBatteryValues() const; |
| 263 | 274 | ||
| 264 | /// Returns the latest status of button input for the npad service | 275 | /// Returns the latest status of button input for the hid::HomeButton service |
| 276 | HomeButtonState GetHomeButtons() const; | ||
| 277 | |||
| 278 | /// Returns the latest status of button input for the hid::CaptureButton service | ||
| 279 | CaptureButtonState GetCaptureButtons() const; | ||
| 280 | |||
| 281 | /// Returns the latest status of button input for the hid::Npad service | ||
| 265 | NpadButtonState GetNpadButtons() const; | 282 | NpadButtonState GetNpadButtons() const; |
| 266 | 283 | ||
| 267 | /// Returns the latest status of button input for the debug pad service | 284 | /// Returns the latest status of button input for the debug pad service |
| @@ -383,6 +400,7 @@ private: | |||
| 383 | NpadStyleTag supported_style_tag{NpadStyleSet::All}; | 400 | NpadStyleTag supported_style_tag{NpadStyleSet::All}; |
| 384 | bool is_connected{false}; | 401 | bool is_connected{false}; |
| 385 | bool is_configuring{false}; | 402 | bool is_configuring{false}; |
| 403 | bool system_buttons_enabled{true}; | ||
| 386 | f32 motion_sensitivity{0.01f}; | 404 | f32 motion_sensitivity{0.01f}; |
| 387 | bool force_update_motion{false}; | 405 | bool force_update_motion{false}; |
| 388 | 406 | ||
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h index 4eca68533..778b328b9 100644 --- a/src/core/hid/hid_types.h +++ b/src/core/hid/hid_types.h | |||
| @@ -378,6 +378,26 @@ struct LedPattern { | |||
| 378 | }; | 378 | }; |
| 379 | }; | 379 | }; |
| 380 | 380 | ||
| 381 | struct HomeButtonState { | ||
| 382 | union { | ||
| 383 | u64 raw{}; | ||
| 384 | |||
| 385 | // Buttons | ||
| 386 | BitField<0, 1, u64> home; | ||
| 387 | }; | ||
| 388 | }; | ||
| 389 | static_assert(sizeof(HomeButtonState) == 0x8, "HomeButtonState has incorrect size."); | ||
| 390 | |||
| 391 | struct CaptureButtonState { | ||
| 392 | union { | ||
| 393 | u64 raw{}; | ||
| 394 | |||
| 395 | // Buttons | ||
| 396 | BitField<0, 1, u64> capture; | ||
| 397 | }; | ||
| 398 | }; | ||
| 399 | static_assert(sizeof(CaptureButtonState) == 0x8, "CaptureButtonState has incorrect size."); | ||
| 400 | |||
| 381 | struct NpadButtonState { | 401 | struct NpadButtonState { |
| 382 | union { | 402 | union { |
| 383 | NpadButton raw{}; | 403 | NpadButton raw{}; |
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 0cda9df62..757117f2b 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp | |||
| @@ -663,6 +663,7 @@ ButtonBindings SDLDriver::GetDefaultButtonBinding() const { | |||
| 663 | {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, | 663 | {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, |
| 664 | {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, | 664 | {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, |
| 665 | {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, | 665 | {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, |
| 666 | {Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1}, | ||
| 666 | }; | 667 | }; |
| 667 | } | 668 | } |
| 668 | 669 | ||
| @@ -699,6 +700,7 @@ ButtonBindings SDLDriver::GetNintendoButtonBinding( | |||
| 699 | {Settings::NativeButton::SL, sl_button}, | 700 | {Settings::NativeButton::SL, sl_button}, |
| 700 | {Settings::NativeButton::SR, sr_button}, | 701 | {Settings::NativeButton::SR, sr_button}, |
| 701 | {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, | 702 | {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, |
| 703 | {Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1}, | ||
| 702 | }; | 704 | }; |
| 703 | } | 705 | } |
| 704 | 706 | ||
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index e9a5d2e26..4cde3606f 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h | |||
| @@ -24,7 +24,7 @@ namespace InputCommon { | |||
| 24 | class SDLJoystick; | 24 | class SDLJoystick; |
| 25 | 25 | ||
| 26 | using ButtonBindings = | 26 | using ButtonBindings = |
| 27 | std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>; | 27 | std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 18>; |
| 28 | using ZButtonBindings = | 28 | using ZButtonBindings = |
| 29 | std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>; | 29 | std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>; |
| 30 | 30 | ||
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp index d78228b50..944e141bf 100644 --- a/src/input_common/drivers/tas_input.cpp +++ b/src/input_common/drivers/tas_input.cpp | |||
| @@ -23,7 +23,7 @@ enum class Tas::TasAxis : u8 { | |||
| 23 | }; | 23 | }; |
| 24 | 24 | ||
| 25 | // Supported keywords and buttons from a TAS file | 25 | // Supported keywords and buttons from a TAS file |
| 26 | constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = { | 26 | constexpr std::array<std::pair<std::string_view, TasButton>, 18> text_to_tas_button = { |
| 27 | std::pair{"KEY_A", TasButton::BUTTON_A}, | 27 | std::pair{"KEY_A", TasButton::BUTTON_A}, |
| 28 | {"KEY_B", TasButton::BUTTON_B}, | 28 | {"KEY_B", TasButton::BUTTON_B}, |
| 29 | {"KEY_X", TasButton::BUTTON_X}, | 29 | {"KEY_X", TasButton::BUTTON_X}, |
| @@ -40,8 +40,9 @@ constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_but | |||
| 40 | {"KEY_DDOWN", TasButton::BUTTON_DOWN}, | 40 | {"KEY_DDOWN", TasButton::BUTTON_DOWN}, |
| 41 | {"KEY_SL", TasButton::BUTTON_SL}, | 41 | {"KEY_SL", TasButton::BUTTON_SL}, |
| 42 | {"KEY_SR", TasButton::BUTTON_SR}, | 42 | {"KEY_SR", TasButton::BUTTON_SR}, |
| 43 | {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, | 43 | // These buttons are disabled to avoid TAS input from activating hotkeys |
| 44 | {"KEY_HOME", TasButton::BUTTON_HOME}, | 44 | // {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, |
| 45 | // {"KEY_HOME", TasButton::BUTTON_HOME}, | ||
| 45 | {"KEY_ZL", TasButton::TRIGGER_ZL}, | 46 | {"KEY_ZL", TasButton::TRIGGER_ZL}, |
| 46 | {"KEY_ZR", TasButton::TRIGGER_ZR}, | 47 | {"KEY_ZR", TasButton::TRIGGER_ZR}, |
| 47 | }; | 48 | }; |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 0f679c37e..99a7397fc 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -66,27 +66,27 @@ const std::array<int, 2> Config::default_stick_mod = { | |||
| 66 | // UISetting::values.shortcuts, which is alphabetically ordered. | 66 | // UISetting::values.shortcuts, which is alphabetically ordered. |
| 67 | // clang-format off | 67 | // clang-format off |
| 68 | const std::array<UISettings::Shortcut, 21> Config::default_hotkeys{{ | 68 | const std::array<UISettings::Shortcut, 21> Config::default_hotkeys{{ |
| 69 | {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, | 69 | {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}}, |
| 70 | {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}}, | 70 | {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}}, |
| 71 | {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, | 71 | {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut}}, |
| 72 | {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}}, | 72 | {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 73 | {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}}, | 73 | {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 74 | {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}}, | 74 | {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut}}, |
| 75 | {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}}, | 75 | {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut}}, |
| 76 | {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}}, | 76 | {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 77 | {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}}, | 77 | {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}}, |
| 78 | {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}}, | 78 | {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, |
| 79 | {QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, | 79 | {QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 80 | {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, | 80 | {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 81 | {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, | 81 | {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 82 | {QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), Qt::ApplicationShortcut}}, | 82 | {QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 83 | {QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), Qt::ApplicationShortcut}}, | 83 | {QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 84 | {QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), Qt::ApplicationShortcut}}, | 84 | {QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 85 | {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, | 85 | {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 86 | {QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}}, | 86 | {QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut}}, |
| 87 | {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}}, | 87 | {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 88 | {QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}}, | 88 | {QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), QStringLiteral(""), Qt::ApplicationShortcut}}, |
| 89 | {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, | 89 | {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}}, |
| 90 | }}; | 90 | }}; |
| 91 | // clang-format on | 91 | // clang-format on |
| 92 | 92 | ||
| @@ -679,7 +679,6 @@ void Config::ReadShortcutValues() { | |||
| 679 | qt_config->beginGroup(QStringLiteral("Shortcuts")); | 679 | qt_config->beginGroup(QStringLiteral("Shortcuts")); |
| 680 | 680 | ||
| 681 | for (const auto& [name, group, shortcut] : default_hotkeys) { | 681 | for (const auto& [name, group, shortcut] : default_hotkeys) { |
| 682 | const auto& [keyseq, context] = shortcut; | ||
| 683 | qt_config->beginGroup(group); | 682 | qt_config->beginGroup(group); |
| 684 | qt_config->beginGroup(name); | 683 | qt_config->beginGroup(name); |
| 685 | // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1 | 684 | // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1 |
| @@ -688,7 +687,10 @@ void Config::ReadShortcutValues() { | |||
| 688 | UISettings::values.shortcuts.push_back( | 687 | UISettings::values.shortcuts.push_back( |
| 689 | {name, | 688 | {name, |
| 690 | group, | 689 | group, |
| 691 | {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), shortcut.second}}); | 690 | {ReadSetting(QStringLiteral("KeySeq"), shortcut.keyseq).toString(), |
| 691 | ReadSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq) | ||
| 692 | .toString(), | ||
| 693 | shortcut.context}}); | ||
| 692 | qt_config->endGroup(); | 694 | qt_config->endGroup(); |
| 693 | qt_config->endGroup(); | 695 | qt_config->endGroup(); |
| 694 | } | 696 | } |
| @@ -1227,8 +1229,10 @@ void Config::SaveShortcutValues() { | |||
| 1227 | 1229 | ||
| 1228 | qt_config->beginGroup(group); | 1230 | qt_config->beginGroup(group); |
| 1229 | qt_config->beginGroup(name); | 1231 | qt_config->beginGroup(name); |
| 1230 | WriteSetting(QStringLiteral("KeySeq"), shortcut.first, default_hotkey.first); | 1232 | WriteSetting(QStringLiteral("KeySeq"), shortcut.keyseq, default_hotkey.keyseq); |
| 1231 | WriteSetting(QStringLiteral("Context"), shortcut.second, default_hotkey.second); | 1233 | WriteSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq, |
| 1234 | default_hotkey.controller_keyseq); | ||
| 1235 | WriteSetting(QStringLiteral("Context"), shortcut.context, default_hotkey.context); | ||
| 1232 | qt_config->endGroup(); | 1236 | qt_config->endGroup(); |
| 1233 | qt_config->endGroup(); | 1237 | qt_config->endGroup(); |
| 1234 | } | 1238 | } |
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 642a5f966..464e7a489 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp | |||
| @@ -45,7 +45,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, | |||
| 45 | general_tab{std::make_unique<ConfigureGeneral>(system_, this)}, | 45 | general_tab{std::make_unique<ConfigureGeneral>(system_, this)}, |
| 46 | graphics_tab{std::make_unique<ConfigureGraphics>(system_, this)}, | 46 | graphics_tab{std::make_unique<ConfigureGraphics>(system_, this)}, |
| 47 | graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)}, | 47 | graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)}, |
| 48 | hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)}, | 48 | hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)}, |
| 49 | input_tab{std::make_unique<ConfigureInput>(system_, this)}, | 49 | input_tab{std::make_unique<ConfigureInput>(system_, this)}, |
| 50 | network_tab{std::make_unique<ConfigureNetwork>(system_, this)}, | 50 | network_tab{std::make_unique<ConfigureNetwork>(system_, this)}, |
| 51 | profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)}, | 51 | profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)}, |
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index ed76fe18e..be10e0a31 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp | |||
| @@ -5,15 +5,24 @@ | |||
| 5 | #include <QMenu> | 5 | #include <QMenu> |
| 6 | #include <QMessageBox> | 6 | #include <QMessageBox> |
| 7 | #include <QStandardItemModel> | 7 | #include <QStandardItemModel> |
| 8 | #include "common/settings.h" | 8 | #include <QTimer> |
| 9 | |||
| 10 | #include "core/hid/emulated_controller.h" | ||
| 11 | #include "core/hid/hid_core.h" | ||
| 12 | |||
| 9 | #include "ui_configure_hotkeys.h" | 13 | #include "ui_configure_hotkeys.h" |
| 10 | #include "yuzu/configuration/config.h" | 14 | #include "yuzu/configuration/config.h" |
| 11 | #include "yuzu/configuration/configure_hotkeys.h" | 15 | #include "yuzu/configuration/configure_hotkeys.h" |
| 12 | #include "yuzu/hotkeys.h" | 16 | #include "yuzu/hotkeys.h" |
| 13 | #include "yuzu/util/sequence_dialog/sequence_dialog.h" | 17 | #include "yuzu/util/sequence_dialog/sequence_dialog.h" |
| 14 | 18 | ||
| 15 | ConfigureHotkeys::ConfigureHotkeys(QWidget* parent) | 19 | constexpr int name_column = 0; |
| 16 | : QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) { | 20 | constexpr int hotkey_column = 1; |
| 21 | constexpr int controller_column = 2; | ||
| 22 | |||
| 23 | ConfigureHotkeys::ConfigureHotkeys(Core::HID::HIDCore& hid_core, QWidget* parent) | ||
| 24 | : QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()), | ||
| 25 | timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { | ||
| 17 | ui->setupUi(this); | 26 | ui->setupUi(this); |
| 18 | setFocusPolicy(Qt::ClickFocus); | 27 | setFocusPolicy(Qt::ClickFocus); |
| 19 | 28 | ||
| @@ -26,16 +35,24 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent) | |||
| 26 | ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu); | 35 | ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu); |
| 27 | ui->hotkey_list->setModel(model); | 36 | ui->hotkey_list->setModel(model); |
| 28 | 37 | ||
| 29 | // TODO(Kloen): Make context configurable as well (hiding the column for now) | 38 | ui->hotkey_list->setColumnWidth(name_column, 200); |
| 30 | ui->hotkey_list->hideColumn(2); | 39 | ui->hotkey_list->resizeColumnToContents(hotkey_column); |
| 31 | |||
| 32 | ui->hotkey_list->setColumnWidth(0, 200); | ||
| 33 | ui->hotkey_list->resizeColumnToContents(1); | ||
| 34 | 40 | ||
| 35 | connect(ui->button_restore_defaults, &QPushButton::clicked, this, | 41 | connect(ui->button_restore_defaults, &QPushButton::clicked, this, |
| 36 | &ConfigureHotkeys::RestoreDefaults); | 42 | &ConfigureHotkeys::RestoreDefaults); |
| 37 | connect(ui->button_clear_all, &QPushButton::clicked, this, &ConfigureHotkeys::ClearAll); | 43 | connect(ui->button_clear_all, &QPushButton::clicked, this, &ConfigureHotkeys::ClearAll); |
| 38 | 44 | ||
| 45 | controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 46 | |||
| 47 | connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); | ||
| 48 | |||
| 49 | connect(poll_timer.get(), &QTimer::timeout, [this] { | ||
| 50 | const auto buttons = controller->GetNpadButtons(); | ||
| 51 | if (buttons.raw != Core::HID::NpadButton::None) { | ||
| 52 | SetPollingResult(buttons.raw, false); | ||
| 53 | return; | ||
| 54 | } | ||
| 55 | }); | ||
| 39 | RetranslateUI(); | 56 | RetranslateUI(); |
| 40 | } | 57 | } |
| 41 | 58 | ||
| @@ -49,15 +66,18 @@ void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) { | |||
| 49 | auto* action = new QStandardItem(hotkey.first); | 66 | auto* action = new QStandardItem(hotkey.first); |
| 50 | auto* keyseq = | 67 | auto* keyseq = |
| 51 | new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText)); | 68 | new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText)); |
| 69 | auto* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq); | ||
| 52 | action->setEditable(false); | 70 | action->setEditable(false); |
| 53 | keyseq->setEditable(false); | 71 | keyseq->setEditable(false); |
| 54 | parent_item->appendRow({action, keyseq}); | 72 | controller_keyseq->setEditable(false); |
| 73 | parent_item->appendRow({action, keyseq, controller_keyseq}); | ||
| 55 | } | 74 | } |
| 56 | model->appendRow(parent_item); | 75 | model->appendRow(parent_item); |
| 57 | } | 76 | } |
| 58 | 77 | ||
| 59 | ui->hotkey_list->expandAll(); | 78 | ui->hotkey_list->expandAll(); |
| 60 | ui->hotkey_list->resizeColumnToContents(0); | 79 | ui->hotkey_list->resizeColumnToContents(name_column); |
| 80 | ui->hotkey_list->resizeColumnToContents(hotkey_column); | ||
| 61 | } | 81 | } |
| 62 | 82 | ||
| 63 | void ConfigureHotkeys::changeEvent(QEvent* event) { | 83 | void ConfigureHotkeys::changeEvent(QEvent* event) { |
| @@ -71,7 +91,7 @@ void ConfigureHotkeys::changeEvent(QEvent* event) { | |||
| 71 | void ConfigureHotkeys::RetranslateUI() { | 91 | void ConfigureHotkeys::RetranslateUI() { |
| 72 | ui->retranslateUi(this); | 92 | ui->retranslateUi(this); |
| 73 | 93 | ||
| 74 | model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")}); | 94 | model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Controller Hotkey")}); |
| 75 | } | 95 | } |
| 76 | 96 | ||
| 77 | void ConfigureHotkeys::Configure(QModelIndex index) { | 97 | void ConfigureHotkeys::Configure(QModelIndex index) { |
| @@ -79,7 +99,15 @@ void ConfigureHotkeys::Configure(QModelIndex index) { | |||
| 79 | return; | 99 | return; |
| 80 | } | 100 | } |
| 81 | 101 | ||
| 82 | index = index.sibling(index.row(), 1); | 102 | // Controller configuration is selected |
| 103 | if (index.column() == controller_column) { | ||
| 104 | ConfigureController(index); | ||
| 105 | return; | ||
| 106 | } | ||
| 107 | |||
| 108 | // Swap to the hotkey column | ||
| 109 | index = index.sibling(index.row(), hotkey_column); | ||
| 110 | |||
| 83 | const auto previous_key = model->data(index); | 111 | const auto previous_key = model->data(index); |
| 84 | 112 | ||
| 85 | SequenceDialog hotkey_dialog{this}; | 113 | SequenceDialog hotkey_dialog{this}; |
| @@ -99,13 +127,113 @@ void ConfigureHotkeys::Configure(QModelIndex index) { | |||
| 99 | model->setData(index, key_sequence.toString(QKeySequence::NativeText)); | 127 | model->setData(index, key_sequence.toString(QKeySequence::NativeText)); |
| 100 | } | 128 | } |
| 101 | } | 129 | } |
| 130 | void ConfigureHotkeys::ConfigureController(QModelIndex index) { | ||
| 131 | if (timeout_timer->isActive()) { | ||
| 132 | return; | ||
| 133 | } | ||
| 134 | |||
| 135 | const auto previous_key = model->data(index); | ||
| 136 | |||
| 137 | input_setter = [this, index, previous_key](const Core::HID::NpadButton button, | ||
| 138 | const bool cancel) { | ||
| 139 | if (cancel) { | ||
| 140 | model->setData(index, previous_key); | ||
| 141 | return; | ||
| 142 | } | ||
| 143 | |||
| 144 | const QString button_string = tr("Home+%1").arg(GetButtonName(button)); | ||
| 145 | |||
| 146 | const auto [key_sequence_used, used_action] = IsUsedControllerKey(button_string); | ||
| 147 | |||
| 148 | if (key_sequence_used) { | ||
| 149 | QMessageBox::warning( | ||
| 150 | this, tr("Conflicting Key Sequence"), | ||
| 151 | tr("The entered key sequence is already assigned to: %1").arg(used_action)); | ||
| 152 | model->setData(index, previous_key); | ||
| 153 | } else { | ||
| 154 | model->setData(index, button_string); | ||
| 155 | } | ||
| 156 | }; | ||
| 157 | |||
| 158 | model->setData(index, tr("[waiting]")); | ||
| 159 | timeout_timer->start(2500); // Cancel after 2.5 seconds | ||
| 160 | poll_timer->start(200); // Check for new inputs every 200ms | ||
| 161 | // We need to disable configuration to be able to read npad buttons | ||
| 162 | controller->DisableConfiguration(); | ||
| 163 | controller->DisableSystemButtons(); | ||
| 164 | } | ||
| 165 | |||
| 166 | void ConfigureHotkeys::SetPollingResult(Core::HID::NpadButton button, const bool cancel) { | ||
| 167 | timeout_timer->stop(); | ||
| 168 | poll_timer->stop(); | ||
| 169 | // Re-Enable configuration | ||
| 170 | controller->EnableConfiguration(); | ||
| 171 | controller->EnableSystemButtons(); | ||
| 172 | |||
| 173 | (*input_setter)(button, cancel); | ||
| 174 | |||
| 175 | input_setter = std::nullopt; | ||
| 176 | } | ||
| 177 | |||
| 178 | QString ConfigureHotkeys::GetButtonName(Core::HID::NpadButton button) const { | ||
| 179 | Core::HID::NpadButtonState state{button}; | ||
| 180 | if (state.a) { | ||
| 181 | return tr("A"); | ||
| 182 | } | ||
| 183 | if (state.b) { | ||
| 184 | return tr("B"); | ||
| 185 | } | ||
| 186 | if (state.x) { | ||
| 187 | return tr("X"); | ||
| 188 | } | ||
| 189 | if (state.y) { | ||
| 190 | return tr("Y"); | ||
| 191 | } | ||
| 192 | if (state.l || state.right_sl || state.left_sl) { | ||
| 193 | return tr("L"); | ||
| 194 | } | ||
| 195 | if (state.r || state.right_sr || state.left_sr) { | ||
| 196 | return tr("R"); | ||
| 197 | } | ||
| 198 | if (state.zl) { | ||
| 199 | return tr("ZL"); | ||
| 200 | } | ||
| 201 | if (state.zr) { | ||
| 202 | return tr("ZR"); | ||
| 203 | } | ||
| 204 | if (state.left) { | ||
| 205 | return tr("Dpad_Left"); | ||
| 206 | } | ||
| 207 | if (state.right) { | ||
| 208 | return tr("Dpad_Right"); | ||
| 209 | } | ||
| 210 | if (state.up) { | ||
| 211 | return tr("Dpad_Up"); | ||
| 212 | } | ||
| 213 | if (state.down) { | ||
| 214 | return tr("Dpad_Down"); | ||
| 215 | } | ||
| 216 | if (state.stick_l) { | ||
| 217 | return tr("Left_Stick"); | ||
| 218 | } | ||
| 219 | if (state.stick_r) { | ||
| 220 | return tr("Right_Stick"); | ||
| 221 | } | ||
| 222 | if (state.minus) { | ||
| 223 | return tr("Minus"); | ||
| 224 | } | ||
| 225 | if (state.plus) { | ||
| 226 | return tr("Plus"); | ||
| 227 | } | ||
| 228 | return tr("Invalid"); | ||
| 229 | } | ||
| 102 | 230 | ||
| 103 | std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const { | 231 | std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const { |
| 104 | for (int r = 0; r < model->rowCount(); ++r) { | 232 | for (int r = 0; r < model->rowCount(); ++r) { |
| 105 | const QStandardItem* const parent = model->item(r, 0); | 233 | const QStandardItem* const parent = model->item(r, 0); |
| 106 | 234 | ||
| 107 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { | 235 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { |
| 108 | const QStandardItem* const key_seq_item = parent->child(r2, 1); | 236 | const QStandardItem* const key_seq_item = parent->child(r2, hotkey_column); |
| 109 | const auto key_seq_str = key_seq_item->text(); | 237 | const auto key_seq_str = key_seq_item->text(); |
| 110 | const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText); | 238 | const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText); |
| 111 | 239 | ||
| @@ -118,12 +246,31 @@ std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) | |||
| 118 | return std::make_pair(false, QString()); | 246 | return std::make_pair(false, QString()); |
| 119 | } | 247 | } |
| 120 | 248 | ||
| 249 | std::pair<bool, QString> ConfigureHotkeys::IsUsedControllerKey(const QString& key_sequence) const { | ||
| 250 | for (int r = 0; r < model->rowCount(); ++r) { | ||
| 251 | const QStandardItem* const parent = model->item(r, 0); | ||
| 252 | |||
| 253 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { | ||
| 254 | const QStandardItem* const key_seq_item = parent->child(r2, controller_column); | ||
| 255 | const auto key_seq_str = key_seq_item->text(); | ||
| 256 | |||
| 257 | if (key_sequence == key_seq_str) { | ||
| 258 | return std::make_pair(true, parent->child(r2, 0)->text()); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | return std::make_pair(false, QString()); | ||
| 264 | } | ||
| 265 | |||
| 121 | void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { | 266 | void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { |
| 122 | for (int key_id = 0; key_id < model->rowCount(); key_id++) { | 267 | for (int key_id = 0; key_id < model->rowCount(); key_id++) { |
| 123 | const QStandardItem* parent = model->item(key_id, 0); | 268 | const QStandardItem* parent = model->item(key_id, 0); |
| 124 | for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) { | 269 | for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) { |
| 125 | const QStandardItem* action = parent->child(key_column_id, 0); | 270 | const QStandardItem* action = parent->child(key_column_id, name_column); |
| 126 | const QStandardItem* keyseq = parent->child(key_column_id, 1); | 271 | const QStandardItem* keyseq = parent->child(key_column_id, hotkey_column); |
| 272 | const QStandardItem* controller_keyseq = | ||
| 273 | parent->child(key_column_id, controller_column); | ||
| 127 | for (auto& [group, sub_actions] : registry.hotkey_groups) { | 274 | for (auto& [group, sub_actions] : registry.hotkey_groups) { |
| 128 | if (group != parent->text()) | 275 | if (group != parent->text()) |
| 129 | continue; | 276 | continue; |
| @@ -131,6 +278,7 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { | |||
| 131 | if (action_name != action->text()) | 278 | if (action_name != action->text()) |
| 132 | continue; | 279 | continue; |
| 133 | hotkey.keyseq = QKeySequence(keyseq->text()); | 280 | hotkey.keyseq = QKeySequence(keyseq->text()); |
| 281 | hotkey.controller_keyseq = controller_keyseq->text(); | ||
| 134 | } | 282 | } |
| 135 | } | 283 | } |
| 136 | } | 284 | } |
| @@ -144,7 +292,12 @@ void ConfigureHotkeys::RestoreDefaults() { | |||
| 144 | const QStandardItem* parent = model->item(r, 0); | 292 | const QStandardItem* parent = model->item(r, 0); |
| 145 | 293 | ||
| 146 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { | 294 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { |
| 147 | model->item(r, 0)->child(r2, 1)->setText(Config::default_hotkeys[r2].shortcut.first); | 295 | model->item(r, 0) |
| 296 | ->child(r2, hotkey_column) | ||
| 297 | ->setText(Config::default_hotkeys[r2].shortcut.keyseq); | ||
| 298 | model->item(r, 0) | ||
| 299 | ->child(r2, controller_column) | ||
| 300 | ->setText(Config::default_hotkeys[r2].shortcut.controller_keyseq); | ||
| 148 | } | 301 | } |
| 149 | } | 302 | } |
| 150 | } | 303 | } |
| @@ -154,7 +307,8 @@ void ConfigureHotkeys::ClearAll() { | |||
| 154 | const QStandardItem* parent = model->item(r, 0); | 307 | const QStandardItem* parent = model->item(r, 0); |
| 155 | 308 | ||
| 156 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { | 309 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { |
| 157 | model->item(r, 0)->child(r2, 1)->setText(QString{}); | 310 | model->item(r, 0)->child(r2, hotkey_column)->setText(QString{}); |
| 311 | model->item(r, 0)->child(r2, controller_column)->setText(QString{}); | ||
| 158 | } | 312 | } |
| 159 | } | 313 | } |
| 160 | } | 314 | } |
| @@ -165,28 +319,52 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) { | |||
| 165 | return; | 319 | return; |
| 166 | } | 320 | } |
| 167 | 321 | ||
| 168 | const auto selected = index.sibling(index.row(), 1); | 322 | // Swap to the hotkey column if the controller hotkey column is not selected |
| 323 | if (index.column() != controller_column) { | ||
| 324 | index = index.sibling(index.row(), hotkey_column); | ||
| 325 | } | ||
| 326 | |||
| 169 | QMenu context_menu; | 327 | QMenu context_menu; |
| 170 | 328 | ||
| 171 | QAction* restore_default = context_menu.addAction(tr("Restore Default")); | 329 | QAction* restore_default = context_menu.addAction(tr("Restore Default")); |
| 172 | QAction* clear = context_menu.addAction(tr("Clear")); | 330 | QAction* clear = context_menu.addAction(tr("Clear")); |
| 173 | 331 | ||
| 174 | connect(restore_default, &QAction::triggered, [this, selected] { | 332 | connect(restore_default, &QAction::triggered, [this, index] { |
| 175 | const QKeySequence& default_key_sequence = QKeySequence::fromString( | 333 | if (index.column() == controller_column) { |
| 176 | Config::default_hotkeys[selected.row()].shortcut.first, QKeySequence::NativeText); | 334 | RestoreControllerHotkey(index); |
| 177 | const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence); | 335 | return; |
| 178 | |||
| 179 | if (key_sequence_used && | ||
| 180 | default_key_sequence != QKeySequence(model->data(selected).toString())) { | ||
| 181 | |||
| 182 | QMessageBox::warning( | ||
| 183 | this, tr("Conflicting Key Sequence"), | ||
| 184 | tr("The default key sequence is already assigned to: %1").arg(used_action)); | ||
| 185 | } else { | ||
| 186 | model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText)); | ||
| 187 | } | 336 | } |
| 337 | RestoreHotkey(index); | ||
| 188 | }); | 338 | }); |
| 189 | connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, QString{}); }); | 339 | connect(clear, &QAction::triggered, [this, index] { model->setData(index, QString{}); }); |
| 190 | 340 | ||
| 191 | context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location)); | 341 | context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location)); |
| 192 | } | 342 | } |
| 343 | |||
| 344 | void ConfigureHotkeys::RestoreControllerHotkey(QModelIndex index) { | ||
| 345 | const QString& default_key_sequence = | ||
| 346 | Config::default_hotkeys[index.row()].shortcut.controller_keyseq; | ||
| 347 | const auto [key_sequence_used, used_action] = IsUsedControllerKey(default_key_sequence); | ||
| 348 | |||
| 349 | if (key_sequence_used && default_key_sequence != model->data(index).toString()) { | ||
| 350 | QMessageBox::warning( | ||
| 351 | this, tr("Conflicting Button Sequence"), | ||
| 352 | tr("The default button sequence is already assigned to: %1").arg(used_action)); | ||
| 353 | } else { | ||
| 354 | model->setData(index, default_key_sequence); | ||
| 355 | } | ||
| 356 | } | ||
| 357 | |||
| 358 | void ConfigureHotkeys::RestoreHotkey(QModelIndex index) { | ||
| 359 | const QKeySequence& default_key_sequence = QKeySequence::fromString( | ||
| 360 | Config::default_hotkeys[index.row()].shortcut.keyseq, QKeySequence::NativeText); | ||
| 361 | const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence); | ||
| 362 | |||
| 363 | if (key_sequence_used && default_key_sequence != QKeySequence(model->data(index).toString())) { | ||
| 364 | QMessageBox::warning( | ||
| 365 | this, tr("Conflicting Key Sequence"), | ||
| 366 | tr("The default key sequence is already assigned to: %1").arg(used_action)); | ||
| 367 | } else { | ||
| 368 | model->setData(index, default_key_sequence.toString(QKeySequence::NativeText)); | ||
| 369 | } | ||
| 370 | } | ||
diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h index a2ec3323e..f943ec538 100644 --- a/src/yuzu/configuration/configure_hotkeys.h +++ b/src/yuzu/configuration/configure_hotkeys.h | |||
| @@ -7,6 +7,16 @@ | |||
| 7 | #include <memory> | 7 | #include <memory> |
| 8 | #include <QWidget> | 8 | #include <QWidget> |
| 9 | 9 | ||
| 10 | namespace Common { | ||
| 11 | class ParamPackage; | ||
| 12 | } | ||
| 13 | |||
| 14 | namespace Core::HID { | ||
| 15 | class HIDCore; | ||
| 16 | class EmulatedController; | ||
| 17 | enum class NpadButton : u64; | ||
| 18 | } // namespace Core::HID | ||
| 19 | |||
| 10 | namespace Ui { | 20 | namespace Ui { |
| 11 | class ConfigureHotkeys; | 21 | class ConfigureHotkeys; |
| 12 | } | 22 | } |
| @@ -18,7 +28,7 @@ class ConfigureHotkeys : public QWidget { | |||
| 18 | Q_OBJECT | 28 | Q_OBJECT |
| 19 | 29 | ||
| 20 | public: | 30 | public: |
| 21 | explicit ConfigureHotkeys(QWidget* parent = nullptr); | 31 | explicit ConfigureHotkeys(Core::HID::HIDCore& hid_core_, QWidget* parent = nullptr); |
| 22 | ~ConfigureHotkeys() override; | 32 | ~ConfigureHotkeys() override; |
| 23 | 33 | ||
| 24 | void ApplyConfiguration(HotkeyRegistry& registry); | 34 | void ApplyConfiguration(HotkeyRegistry& registry); |
| @@ -35,13 +45,24 @@ private: | |||
| 35 | void RetranslateUI(); | 45 | void RetranslateUI(); |
| 36 | 46 | ||
| 37 | void Configure(QModelIndex index); | 47 | void Configure(QModelIndex index); |
| 48 | void ConfigureController(QModelIndex index); | ||
| 38 | std::pair<bool, QString> IsUsedKey(QKeySequence key_sequence) const; | 49 | std::pair<bool, QString> IsUsedKey(QKeySequence key_sequence) const; |
| 50 | std::pair<bool, QString> IsUsedControllerKey(const QString& key_sequence) const; | ||
| 39 | 51 | ||
| 40 | void RestoreDefaults(); | 52 | void RestoreDefaults(); |
| 41 | void ClearAll(); | 53 | void ClearAll(); |
| 42 | void PopupContextMenu(const QPoint& menu_location); | 54 | void PopupContextMenu(const QPoint& menu_location); |
| 55 | void RestoreControllerHotkey(QModelIndex index); | ||
| 56 | void RestoreHotkey(QModelIndex index); | ||
| 43 | 57 | ||
| 44 | std::unique_ptr<Ui::ConfigureHotkeys> ui; | 58 | std::unique_ptr<Ui::ConfigureHotkeys> ui; |
| 45 | 59 | ||
| 46 | QStandardItemModel* model; | 60 | QStandardItemModel* model; |
| 61 | |||
| 62 | void SetPollingResult(Core::HID::NpadButton button, bool cancel); | ||
| 63 | QString GetButtonName(Core::HID::NpadButton button) const; | ||
| 64 | Core::HID::EmulatedController* controller; | ||
| 65 | std::unique_ptr<QTimer> timeout_timer; | ||
| 66 | std::unique_ptr<QTimer> poll_timer; | ||
| 67 | std::optional<std::function<void(Core::HID::NpadButton, bool)>> input_setter; | ||
| 47 | }; | 68 | }; |
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp index e7e58f314..d96497c4e 100644 --- a/src/yuzu/hotkeys.cpp +++ b/src/yuzu/hotkeys.cpp | |||
| @@ -2,10 +2,13 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <sstream> | ||
| 5 | #include <QKeySequence> | 6 | #include <QKeySequence> |
| 6 | #include <QShortcut> | 7 | #include <QShortcut> |
| 7 | #include <QTreeWidgetItem> | 8 | #include <QTreeWidgetItem> |
| 8 | #include <QtGlobal> | 9 | #include <QtGlobal> |
| 10 | |||
| 11 | #include "core/hid/emulated_controller.h" | ||
| 9 | #include "yuzu/hotkeys.h" | 12 | #include "yuzu/hotkeys.h" |
| 10 | #include "yuzu/uisettings.h" | 13 | #include "yuzu/uisettings.h" |
| 11 | 14 | ||
| @@ -18,8 +21,9 @@ void HotkeyRegistry::SaveHotkeys() { | |||
| 18 | for (const auto& hotkey : group.second) { | 21 | for (const auto& hotkey : group.second) { |
| 19 | UISettings::values.shortcuts.push_back( | 22 | UISettings::values.shortcuts.push_back( |
| 20 | {hotkey.first, group.first, | 23 | {hotkey.first, group.first, |
| 21 | UISettings::ContextualShortcut(hotkey.second.keyseq.toString(), | 24 | UISettings::ContextualShortcut({hotkey.second.keyseq.toString(), |
| 22 | hotkey.second.context)}); | 25 | hotkey.second.controller_keyseq, |
| 26 | hotkey.second.context})}); | ||
| 23 | } | 27 | } |
| 24 | } | 28 | } |
| 25 | } | 29 | } |
| @@ -29,28 +33,49 @@ void HotkeyRegistry::LoadHotkeys() { | |||
| 29 | // beginGroup() | 33 | // beginGroup() |
| 30 | for (auto shortcut : UISettings::values.shortcuts) { | 34 | for (auto shortcut : UISettings::values.shortcuts) { |
| 31 | Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name]; | 35 | Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name]; |
| 32 | if (!shortcut.shortcut.first.isEmpty()) { | 36 | if (!shortcut.shortcut.keyseq.isEmpty()) { |
| 33 | hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText); | 37 | hk.keyseq = |
| 34 | hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second); | 38 | QKeySequence::fromString(shortcut.shortcut.keyseq, QKeySequence::NativeText); |
| 39 | hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.context); | ||
| 40 | } | ||
| 41 | if (!shortcut.shortcut.controller_keyseq.isEmpty()) { | ||
| 42 | hk.controller_keyseq = shortcut.shortcut.controller_keyseq; | ||
| 35 | } | 43 | } |
| 36 | if (hk.shortcut) { | 44 | if (hk.shortcut) { |
| 37 | hk.shortcut->disconnect(); | 45 | hk.shortcut->disconnect(); |
| 38 | hk.shortcut->setKey(hk.keyseq); | 46 | hk.shortcut->setKey(hk.keyseq); |
| 39 | } | 47 | } |
| 48 | if (hk.controller_shortcut) { | ||
| 49 | hk.controller_shortcut->disconnect(); | ||
| 50 | hk.controller_shortcut->SetKey(hk.controller_keyseq); | ||
| 51 | } | ||
| 40 | } | 52 | } |
| 41 | } | 53 | } |
| 42 | 54 | ||
| 43 | QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) { | 55 | QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) { |
| 44 | Hotkey& hk = hotkey_groups[group][action]; | 56 | Hotkey& hk = hotkey_groups[group][action]; |
| 45 | 57 | ||
| 46 | if (!hk.shortcut) | 58 | if (!hk.shortcut) { |
| 47 | hk.shortcut = new QShortcut(hk.keyseq, widget, nullptr, nullptr, hk.context); | 59 | hk.shortcut = new QShortcut(hk.keyseq, widget, nullptr, nullptr, hk.context); |
| 60 | } | ||
| 48 | 61 | ||
| 49 | hk.shortcut->setAutoRepeat(false); | 62 | hk.shortcut->setAutoRepeat(false); |
| 50 | 63 | ||
| 51 | return hk.shortcut; | 64 | return hk.shortcut; |
| 52 | } | 65 | } |
| 53 | 66 | ||
| 67 | ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const QString& group, const QString& action, | ||
| 68 | Core::HID::EmulatedController* controller) { | ||
| 69 | Hotkey& hk = hotkey_groups[group][action]; | ||
| 70 | |||
| 71 | if (!hk.controller_shortcut) { | ||
| 72 | hk.controller_shortcut = new ControllerShortcut(controller); | ||
| 73 | hk.controller_shortcut->SetKey(hk.controller_keyseq); | ||
| 74 | } | ||
| 75 | |||
| 76 | return hk.controller_shortcut; | ||
| 77 | } | ||
| 78 | |||
| 54 | QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) { | 79 | QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) { |
| 55 | return hotkey_groups[group][action].keyseq; | 80 | return hotkey_groups[group][action].keyseq; |
| 56 | } | 81 | } |
| @@ -59,3 +84,131 @@ Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group, | |||
| 59 | const QString& action) { | 84 | const QString& action) { |
| 60 | return hotkey_groups[group][action].context; | 85 | return hotkey_groups[group][action].context; |
| 61 | } | 86 | } |
| 87 | |||
| 88 | ControllerShortcut::ControllerShortcut(Core::HID::EmulatedController* controller) { | ||
| 89 | emulated_controller = controller; | ||
| 90 | Core::HID::ControllerUpdateCallback engine_callback{ | ||
| 91 | .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdateEvent(type); }, | ||
| 92 | .is_npad_service = false, | ||
| 93 | }; | ||
| 94 | callback_key = emulated_controller->SetCallback(engine_callback); | ||
| 95 | is_enabled = true; | ||
| 96 | } | ||
| 97 | |||
| 98 | ControllerShortcut::~ControllerShortcut() { | ||
| 99 | emulated_controller->DeleteCallback(callback_key); | ||
| 100 | } | ||
| 101 | |||
| 102 | void ControllerShortcut::SetKey(const ControllerButtonSequence& buttons) { | ||
| 103 | button_sequence = buttons; | ||
| 104 | } | ||
| 105 | |||
| 106 | void ControllerShortcut::SetKey(const QString& buttons_shortcut) { | ||
| 107 | ControllerButtonSequence sequence{}; | ||
| 108 | name = buttons_shortcut.toStdString(); | ||
| 109 | std::istringstream command_line(buttons_shortcut.toStdString()); | ||
| 110 | std::string line; | ||
| 111 | while (std::getline(command_line, line, '+')) { | ||
| 112 | if (line.empty()) { | ||
| 113 | continue; | ||
| 114 | } | ||
| 115 | if (line == "A") { | ||
| 116 | sequence.npad.a.Assign(1); | ||
| 117 | } | ||
| 118 | if (line == "B") { | ||
| 119 | sequence.npad.b.Assign(1); | ||
| 120 | } | ||
| 121 | if (line == "X") { | ||
| 122 | sequence.npad.x.Assign(1); | ||
| 123 | } | ||
| 124 | if (line == "Y") { | ||
| 125 | sequence.npad.y.Assign(1); | ||
| 126 | } | ||
| 127 | if (line == "L") { | ||
| 128 | sequence.npad.l.Assign(1); | ||
| 129 | } | ||
| 130 | if (line == "R") { | ||
| 131 | sequence.npad.r.Assign(1); | ||
| 132 | } | ||
| 133 | if (line == "ZL") { | ||
| 134 | sequence.npad.zl.Assign(1); | ||
| 135 | } | ||
| 136 | if (line == "ZR") { | ||
| 137 | sequence.npad.zr.Assign(1); | ||
| 138 | } | ||
| 139 | if (line == "Dpad_Left") { | ||
| 140 | sequence.npad.left.Assign(1); | ||
| 141 | } | ||
| 142 | if (line == "Dpad_Right") { | ||
| 143 | sequence.npad.right.Assign(1); | ||
| 144 | } | ||
| 145 | if (line == "Dpad_Up") { | ||
| 146 | sequence.npad.up.Assign(1); | ||
| 147 | } | ||
| 148 | if (line == "Dpad_Down") { | ||
| 149 | sequence.npad.down.Assign(1); | ||
| 150 | } | ||
| 151 | if (line == "Left_Stick") { | ||
| 152 | sequence.npad.stick_l.Assign(1); | ||
| 153 | } | ||
| 154 | if (line == "Right_Stick") { | ||
| 155 | sequence.npad.stick_r.Assign(1); | ||
| 156 | } | ||
| 157 | if (line == "Minus") { | ||
| 158 | sequence.npad.minus.Assign(1); | ||
| 159 | } | ||
| 160 | if (line == "Plus") { | ||
| 161 | sequence.npad.plus.Assign(1); | ||
| 162 | } | ||
| 163 | if (line == "Home") { | ||
| 164 | sequence.home.home.Assign(1); | ||
| 165 | } | ||
| 166 | if (line == "Screenshot") { | ||
| 167 | sequence.capture.capture.Assign(1); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | button_sequence = sequence; | ||
| 172 | } | ||
| 173 | |||
| 174 | ControllerButtonSequence ControllerShortcut::ButtonSequence() const { | ||
| 175 | return button_sequence; | ||
| 176 | } | ||
| 177 | |||
| 178 | void ControllerShortcut::SetEnabled(bool enable) { | ||
| 179 | is_enabled = enable; | ||
| 180 | } | ||
| 181 | |||
| 182 | bool ControllerShortcut::IsEnabled() const { | ||
| 183 | return is_enabled; | ||
| 184 | } | ||
| 185 | |||
| 186 | void ControllerShortcut::ControllerUpdateEvent(Core::HID::ControllerTriggerType type) { | ||
| 187 | if (!is_enabled) { | ||
| 188 | return; | ||
| 189 | } | ||
| 190 | if (type != Core::HID::ControllerTriggerType::Button) { | ||
| 191 | return; | ||
| 192 | } | ||
| 193 | if (button_sequence.npad.raw == Core::HID::NpadButton::None && | ||
| 194 | button_sequence.capture.raw == 0 && button_sequence.home.raw == 0) { | ||
| 195 | return; | ||
| 196 | } | ||
| 197 | |||
| 198 | const auto player_npad_buttons = | ||
| 199 | emulated_controller->GetNpadButtons().raw & button_sequence.npad.raw; | ||
| 200 | const u64 player_capture_buttons = | ||
| 201 | emulated_controller->GetCaptureButtons().raw & button_sequence.capture.raw; | ||
| 202 | const u64 player_home_buttons = | ||
| 203 | emulated_controller->GetHomeButtons().raw & button_sequence.home.raw; | ||
| 204 | |||
| 205 | if (player_npad_buttons == button_sequence.npad.raw && | ||
| 206 | player_capture_buttons == button_sequence.capture.raw && | ||
| 207 | player_home_buttons == button_sequence.home.raw && !active) { | ||
| 208 | // Force user to press the home or capture button again | ||
| 209 | active = true; | ||
| 210 | emit Activated(); | ||
| 211 | return; | ||
| 212 | } | ||
| 213 | active = false; | ||
| 214 | } | ||
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h index 248fadaf3..57a7c7da5 100644 --- a/src/yuzu/hotkeys.h +++ b/src/yuzu/hotkeys.h | |||
| @@ -5,11 +5,53 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <map> | 7 | #include <map> |
| 8 | #include "core/hid/hid_types.h" | ||
| 8 | 9 | ||
| 9 | class QDialog; | 10 | class QDialog; |
| 10 | class QKeySequence; | 11 | class QKeySequence; |
| 11 | class QSettings; | 12 | class QSettings; |
| 12 | class QShortcut; | 13 | class QShortcut; |
| 14 | class ControllerShortcut; | ||
| 15 | |||
| 16 | namespace Core::HID { | ||
| 17 | enum class ControllerTriggerType; | ||
| 18 | class EmulatedController; | ||
| 19 | } // namespace Core::HID | ||
| 20 | |||
| 21 | struct ControllerButtonSequence { | ||
| 22 | Core::HID::CaptureButtonState capture{}; | ||
| 23 | Core::HID::HomeButtonState home{}; | ||
| 24 | Core::HID::NpadButtonState npad{}; | ||
| 25 | }; | ||
| 26 | |||
| 27 | class ControllerShortcut : public QObject { | ||
| 28 | Q_OBJECT | ||
| 29 | |||
| 30 | public: | ||
| 31 | explicit ControllerShortcut(Core::HID::EmulatedController* controller); | ||
| 32 | ~ControllerShortcut(); | ||
| 33 | |||
| 34 | void SetKey(const ControllerButtonSequence& buttons); | ||
| 35 | void SetKey(const QString& buttons_shortcut); | ||
| 36 | |||
| 37 | ControllerButtonSequence ButtonSequence() const; | ||
| 38 | |||
| 39 | void SetEnabled(bool enable); | ||
| 40 | bool IsEnabled() const; | ||
| 41 | |||
| 42 | Q_SIGNALS: | ||
| 43 | void Activated(); | ||
| 44 | |||
| 45 | private: | ||
| 46 | void ControllerUpdateEvent(Core::HID::ControllerTriggerType type); | ||
| 47 | |||
| 48 | bool is_enabled{}; | ||
| 49 | bool active{}; | ||
| 50 | int callback_key{}; | ||
| 51 | ControllerButtonSequence button_sequence{}; | ||
| 52 | std::string name{}; | ||
| 53 | Core::HID::EmulatedController* emulated_controller = nullptr; | ||
| 54 | }; | ||
| 13 | 55 | ||
| 14 | class HotkeyRegistry final { | 56 | class HotkeyRegistry final { |
| 15 | public: | 57 | public: |
| @@ -46,6 +88,8 @@ public: | |||
| 46 | * QShortcut's parent. | 88 | * QShortcut's parent. |
| 47 | */ | 89 | */ |
| 48 | QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget); | 90 | QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget); |
| 91 | ControllerShortcut* GetControllerHotkey(const QString& group, const QString& action, | ||
| 92 | Core::HID::EmulatedController* controller); | ||
| 49 | 93 | ||
| 50 | /** | 94 | /** |
| 51 | * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut. | 95 | * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut. |
| @@ -68,7 +112,9 @@ public: | |||
| 68 | private: | 112 | private: |
| 69 | struct Hotkey { | 113 | struct Hotkey { |
| 70 | QKeySequence keyseq; | 114 | QKeySequence keyseq; |
| 115 | QString controller_keyseq; | ||
| 71 | QShortcut* shortcut = nullptr; | 116 | QShortcut* shortcut = nullptr; |
| 117 | ControllerShortcut* controller_shortcut = nullptr; | ||
| 72 | Qt::ShortcutContext context = Qt::WindowShortcut; | 118 | Qt::ShortcutContext context = Qt::WindowShortcut; |
| 73 | }; | 119 | }; |
| 74 | 120 | ||
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 53f11a9ac..e8a4ac918 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -32,6 +32,7 @@ | |||
| 32 | #include "core/hle/service/am/applet_ae.h" | 32 | #include "core/hle/service/am/applet_ae.h" |
| 33 | #include "core/hle/service/am/applet_oe.h" | 33 | #include "core/hle/service/am/applet_oe.h" |
| 34 | #include "core/hle/service/am/applets/applets.h" | 34 | #include "core/hle/service/am/applets/applets.h" |
| 35 | #include "yuzu/util/controller_navigation.h" | ||
| 35 | 36 | ||
| 36 | // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows | 37 | // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows |
| 37 | // defines. | 38 | // defines. |
| @@ -966,6 +967,12 @@ void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name | |||
| 966 | action->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, action_name)); | 967 | action->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, action_name)); |
| 967 | 968 | ||
| 968 | this->addAction(action); | 969 | this->addAction(action); |
| 970 | |||
| 971 | auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 972 | const auto* controller_hotkey = | ||
| 973 | hotkey_registry.GetControllerHotkey(main_window, action_name, controller); | ||
| 974 | connect(controller_hotkey, &ControllerShortcut::Activated, this, | ||
| 975 | [action] { action->trigger(); }); | ||
| 969 | } | 976 | } |
| 970 | 977 | ||
| 971 | void GMainWindow::InitializeHotkeys() { | 978 | void GMainWindow::InitializeHotkeys() { |
| @@ -987,8 +994,12 @@ void GMainWindow::InitializeHotkeys() { | |||
| 987 | 994 | ||
| 988 | static const QString main_window = QStringLiteral("Main Window"); | 995 | static const QString main_window = QStringLiteral("Main Window"); |
| 989 | const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) { | 996 | const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) { |
| 990 | const QShortcut* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this); | 997 | const auto* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this); |
| 998 | auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 999 | const auto* controller_hotkey = | ||
| 1000 | hotkey_registry.GetControllerHotkey(main_window, action_name, controller); | ||
| 991 | connect(hotkey, &QShortcut::activated, this, function); | 1001 | connect(hotkey, &QShortcut::activated, this, function); |
| 1002 | connect(controller_hotkey, &ControllerShortcut::Activated, this, function); | ||
| 992 | }; | 1003 | }; |
| 993 | 1004 | ||
| 994 | connect_shortcut(QStringLiteral("Exit Fullscreen"), [&] { | 1005 | connect_shortcut(QStringLiteral("Exit Fullscreen"), [&] { |
| @@ -1165,8 +1176,7 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 1165 | connect_menu(ui->action_Single_Window_Mode, &GMainWindow::ToggleWindowMode); | 1176 | connect_menu(ui->action_Single_Window_Mode, &GMainWindow::ToggleWindowMode); |
| 1166 | connect_menu(ui->action_Display_Dock_Widget_Headers, &GMainWindow::OnDisplayTitleBars); | 1177 | connect_menu(ui->action_Display_Dock_Widget_Headers, &GMainWindow::OnDisplayTitleBars); |
| 1167 | connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar); | 1178 | connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar); |
| 1168 | 1179 | connect_menu(ui->action_Show_Status_Bar, &GMainWindow::OnToggleStatusBar); | |
| 1169 | connect(ui->action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); | ||
| 1170 | 1180 | ||
| 1171 | connect_menu(ui->action_Reset_Window_Size_720, &GMainWindow::ResetWindowSize720); | 1181 | connect_menu(ui->action_Reset_Window_Size_720, &GMainWindow::ResetWindowSize720); |
| 1172 | connect_menu(ui->action_Reset_Window_Size_900, &GMainWindow::ResetWindowSize900); | 1182 | connect_menu(ui->action_Reset_Window_Size_900, &GMainWindow::ResetWindowSize900); |
| @@ -2168,6 +2178,11 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { | |||
| 2168 | } | 2178 | } |
| 2169 | 2179 | ||
| 2170 | void GMainWindow::OnMenuLoadFile() { | 2180 | void GMainWindow::OnMenuLoadFile() { |
| 2181 | if (is_load_file_select_active) { | ||
| 2182 | return; | ||
| 2183 | } | ||
| 2184 | |||
| 2185 | is_load_file_select_active = true; | ||
| 2171 | const QString extensions = | 2186 | const QString extensions = |
| 2172 | QStringLiteral("*.") | 2187 | QStringLiteral("*.") |
| 2173 | .append(GameList::supported_file_extensions.join(QStringLiteral(" *."))) | 2188 | .append(GameList::supported_file_extensions.join(QStringLiteral(" *."))) |
| @@ -2177,6 +2192,7 @@ void GMainWindow::OnMenuLoadFile() { | |||
| 2177 | .arg(extensions); | 2192 | .arg(extensions); |
| 2178 | const QString filename = QFileDialog::getOpenFileName( | 2193 | const QString filename = QFileDialog::getOpenFileName( |
| 2179 | this, tr("Load File"), UISettings::values.roms_path, file_filter); | 2194 | this, tr("Load File"), UISettings::values.roms_path, file_filter); |
| 2195 | is_load_file_select_active = false; | ||
| 2180 | 2196 | ||
| 2181 | if (filename.isEmpty()) { | 2197 | if (filename.isEmpty()) { |
| 2182 | return; | 2198 | return; |
| @@ -2809,6 +2825,11 @@ void GMainWindow::OnTasStartStop() { | |||
| 2809 | if (!emulation_running) { | 2825 | if (!emulation_running) { |
| 2810 | return; | 2826 | return; |
| 2811 | } | 2827 | } |
| 2828 | |||
| 2829 | // Disable system buttons to prevent TAS from executing a hotkey | ||
| 2830 | auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 2831 | controller->ResetSystemButtons(); | ||
| 2832 | |||
| 2812 | input_subsystem->GetTas()->StartStop(); | 2833 | input_subsystem->GetTas()->StartStop(); |
| 2813 | OnTasStateChanged(); | 2834 | OnTasStateChanged(); |
| 2814 | } | 2835 | } |
| @@ -2817,12 +2838,34 @@ void GMainWindow::OnTasRecord() { | |||
| 2817 | if (!emulation_running) { | 2838 | if (!emulation_running) { |
| 2818 | return; | 2839 | return; |
| 2819 | } | 2840 | } |
| 2841 | if (is_tas_recording_dialog_active) { | ||
| 2842 | return; | ||
| 2843 | } | ||
| 2844 | |||
| 2845 | // Disable system buttons to prevent TAS from recording a hotkey | ||
| 2846 | auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 2847 | controller->ResetSystemButtons(); | ||
| 2848 | |||
| 2820 | const bool is_recording = input_subsystem->GetTas()->Record(); | 2849 | const bool is_recording = input_subsystem->GetTas()->Record(); |
| 2821 | if (!is_recording) { | 2850 | if (!is_recording) { |
| 2822 | const auto res = | 2851 | is_tas_recording_dialog_active = true; |
| 2823 | QMessageBox::question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"), | 2852 | ControllerNavigation* controller_navigation = |
| 2824 | QMessageBox::Yes | QMessageBox::No); | 2853 | new ControllerNavigation(system->HIDCore(), this); |
| 2854 | // Use QMessageBox instead of question so we can link controller navigation | ||
| 2855 | QMessageBox* box_dialog = new QMessageBox(); | ||
| 2856 | box_dialog->setWindowTitle(tr("TAS Recording")); | ||
| 2857 | box_dialog->setText(tr("Overwrite file of player 1?")); | ||
| 2858 | box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); | ||
| 2859 | box_dialog->setDefaultButton(QMessageBox::Yes); | ||
| 2860 | connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, | ||
| 2861 | [box_dialog](Qt::Key key) { | ||
| 2862 | QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); | ||
| 2863 | QCoreApplication::postEvent(box_dialog, event); | ||
| 2864 | }); | ||
| 2865 | int res = box_dialog->exec(); | ||
| 2866 | controller_navigation->UnloadController(); | ||
| 2825 | input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes); | 2867 | input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes); |
| 2868 | is_tas_recording_dialog_active = false; | ||
| 2826 | } | 2869 | } |
| 2827 | OnTasStateChanged(); | 2870 | OnTasStateChanged(); |
| 2828 | } | 2871 | } |
| @@ -2871,10 +2914,15 @@ void GMainWindow::OnLoadAmiibo() { | |||
| 2871 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { | 2914 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { |
| 2872 | return; | 2915 | return; |
| 2873 | } | 2916 | } |
| 2917 | if (is_amiibo_file_select_active) { | ||
| 2918 | return; | ||
| 2919 | } | ||
| 2874 | 2920 | ||
| 2921 | is_amiibo_file_select_active = true; | ||
| 2875 | const QString extensions{QStringLiteral("*.bin")}; | 2922 | const QString extensions{QStringLiteral("*.bin")}; |
| 2876 | const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); | 2923 | const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); |
| 2877 | const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter); | 2924 | const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter); |
| 2925 | is_amiibo_file_select_active = false; | ||
| 2878 | 2926 | ||
| 2879 | if (filename.isEmpty()) { | 2927 | if (filename.isEmpty()) { |
| 2880 | return; | 2928 | return; |
| @@ -2934,6 +2982,10 @@ void GMainWindow::OnToggleFilterBar() { | |||
| 2934 | } | 2982 | } |
| 2935 | } | 2983 | } |
| 2936 | 2984 | ||
| 2985 | void GMainWindow::OnToggleStatusBar() { | ||
| 2986 | statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); | ||
| 2987 | } | ||
| 2988 | |||
| 2937 | void GMainWindow::OnCaptureScreenshot() { | 2989 | void GMainWindow::OnCaptureScreenshot() { |
| 2938 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { | 2990 | if (emu_thread == nullptr || !emu_thread->IsRunning()) { |
| 2939 | return; | 2991 | return; |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 7870bb963..ca4ab9af5 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -186,6 +186,9 @@ public slots: | |||
| 186 | void OnTasStateChanged(); | 186 | void OnTasStateChanged(); |
| 187 | 187 | ||
| 188 | private: | 188 | private: |
| 189 | /// Updates an action's shortcut and text to reflect an updated hotkey from the hotkey registry. | ||
| 190 | void LinkActionShortcut(QAction* action, const QString& action_name); | ||
| 191 | |||
| 189 | void RegisterMetaTypes(); | 192 | void RegisterMetaTypes(); |
| 190 | 193 | ||
| 191 | void InitializeWidgets(); | 194 | void InitializeWidgets(); |
| @@ -286,6 +289,7 @@ private slots: | |||
| 286 | void OnOpenYuzuFolder(); | 289 | void OnOpenYuzuFolder(); |
| 287 | void OnAbout(); | 290 | void OnAbout(); |
| 288 | void OnToggleFilterBar(); | 291 | void OnToggleFilterBar(); |
| 292 | void OnToggleStatusBar(); | ||
| 289 | void OnDisplayTitleBars(bool); | 293 | void OnDisplayTitleBars(bool); |
| 290 | void InitializeHotkeys(); | 294 | void InitializeHotkeys(); |
| 291 | void ToggleFullscreen(); | 295 | void ToggleFullscreen(); |
| @@ -303,9 +307,6 @@ private slots: | |||
| 303 | void OnMouseActivity(); | 307 | void OnMouseActivity(); |
| 304 | 308 | ||
| 305 | private: | 309 | private: |
| 306 | /// Updates an action's shortcut and text to reflect an updated hotkey from the hotkey registry. | ||
| 307 | void LinkActionShortcut(QAction* action, const QString& action_name); | ||
| 308 | |||
| 309 | void RemoveBaseContent(u64 program_id, const QString& entry_type); | 310 | void RemoveBaseContent(u64 program_id, const QString& entry_type); |
| 310 | void RemoveUpdateContent(u64 program_id, const QString& entry_type); | 311 | void RemoveUpdateContent(u64 program_id, const QString& entry_type); |
| 311 | void RemoveAddOnContent(u64 program_id, const QString& entry_type); | 312 | void RemoveAddOnContent(u64 program_id, const QString& entry_type); |
| @@ -400,6 +401,16 @@ private: | |||
| 400 | 401 | ||
| 401 | // Applets | 402 | // Applets |
| 402 | QtSoftwareKeyboardDialog* software_keyboard = nullptr; | 403 | QtSoftwareKeyboardDialog* software_keyboard = nullptr; |
| 404 | |||
| 405 | // True if amiibo file select is visible | ||
| 406 | bool is_amiibo_file_select_active{}; | ||
| 407 | |||
| 408 | // True if load file select is visible | ||
| 409 | bool is_load_file_select_active{}; | ||
| 410 | |||
| 411 | // True if TAS recording dialog is visible | ||
| 412 | bool is_tas_recording_dialog_active{}; | ||
| 413 | |||
| 403 | #ifdef __linux__ | 414 | #ifdef __linux__ |
| 404 | QDBusObjectPath wake_lock{}; | 415 | QDBusObjectPath wake_lock{}; |
| 405 | #endif | 416 | #endif |
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index a610e7e25..402c4556d 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h | |||
| @@ -17,7 +17,11 @@ | |||
| 17 | 17 | ||
| 18 | namespace UISettings { | 18 | namespace UISettings { |
| 19 | 19 | ||
| 20 | using ContextualShortcut = std::pair<QString, int>; | 20 | struct ContextualShortcut { |
| 21 | QString keyseq; | ||
| 22 | QString controller_keyseq; | ||
| 23 | int context; | ||
| 24 | }; | ||
| 21 | 25 | ||
| 22 | struct Shortcut { | 26 | struct Shortcut { |
| 23 | QString name; | 27 | QString name; |