From 459497f0b6ab0629f3d8eabf954722a3d76a61d4 Mon Sep 17 00:00:00 2001 From: Adrian <78108584+AdrianCassar@users.noreply.github.com> Date: Fri, 13 Jan 2023 08:17:43 +0000 Subject: [PATCH] Implemented Controller Hotkeys (#111) Implemented Controller Hotkeys Added controller hotkeys Added guide button support for XInput and winkey The hotkey configurations can be found in HID -> Display controller hotkeys If the Xbox Gamebar overlay is enabled then use the Back button instead of the Guide button. - Fixed hotkey thread destruction - Fixed XINPUT_STATE by padding 4 bytes - Added hotkey vibration for user feedback - Replaced MessageBoxA with ImGuiDialog::ShowMessageBox Co-authored-by: Margen67 --- src/xenia/app/emulator_window.cc | 341 +++++++++++++++++- src/xenia/app/emulator_window.h | 52 +++ .../gpu/d3d12/d3d12_command_processor.cc | 13 + src/xenia/gpu/d3d12/d3d12_command_processor.h | 8 + src/xenia/hid/hid_demo.cc | 1 + src/xenia/hid/hid_flags.cc | 2 +- src/xenia/hid/sdl/sdl_input_driver.cc | 5 +- src/xenia/hid/winkey/winkey_binding_table.inc | 1 + src/xenia/hid/winkey/winkey_input_driver.cc | 31 +- src/xenia/hid/xinput/xinput_input_driver.cc | 45 ++- src/xenia/hid/xinput/xinput_input_driver.h | 1 + src/xenia/ui/virtual_key.h | 5 +- 12 files changed, 455 insertions(+), 50 deletions(-) diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 1e1b84785..9d6809683 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -31,6 +31,7 @@ #include "xenia/cpu/processor.h" #include "xenia/emulator.h" #include "xenia/gpu/command_processor.h" +#include "xenia/gpu/d3d12/d3d12_command_processor.h" #include "xenia/gpu/graphics_system.h" #include "xenia/hid/input_system.h" #include "xenia/ui/file_picker.h" @@ -47,6 +48,14 @@ DECLARE_bool(debug); +DECLARE_string(hid); + +DECLARE_bool(guide_button); + +DECLARE_bool(d3d12_readback_resolve); + +DECLARE_bool(d3d12_clear_memory_page_state); + DEFINE_bool(fullscreen, false, "Whether to launch the emulator in fullscreen.", "Display"); @@ -134,6 +143,9 @@ using xe::ui::KeyEvent; using xe::ui::MenuItem; using xe::ui::UIEvent; +using namespace xe::hid; +using namespace xe::gpu; + const std::string kBaseTitle = "Xenia-canary"; EmulatorWindow::EmulatorWindow(Emulator* emulator, @@ -230,6 +242,15 @@ void EmulatorWindow::OnEmulatorInitialized() { if (cvars::fullscreen) { SetFullscreen(true); } + + if (IsUseNexusForGameBarEnabled()) { + XELOGE("Xbox Gamebar Enabled, using BACK button instead of GUIDE!!!"); + } + + // Create a thread to listen for controller hotkeys. + Gamepad_HotKeys_Listener = + threading::Thread::Create({}, [&] { GamepadHotKeys(); }); + Gamepad_HotKeys_Listener->set_name("Gamepad HotKeys Listener"); } void EmulatorWindow::EmulatorWindowListener::OnClosing(ui::UIEvent& e) { @@ -608,6 +629,9 @@ bool EmulatorWindow::Initialize() { hid_menu->AddChild(MenuItem::Create( MenuItem::Type::kString, "&Toggle controller vibration", "", std::bind(&EmulatorWindow::ToggleControllerVibration, this))); + hid_menu->AddChild(MenuItem::Create( + MenuItem::Type::kString, "&Display controller hotkeys", "", + std::bind(&EmulatorWindow::DisplayHotKeysConfig, this))); } main_menu->AddChild(std::move(hid_menu)); @@ -888,10 +912,10 @@ void EmulatorWindow::InstallContent() { if (result != X_STATUS_SUCCESS) { XELOGE("Failed to install content! Error code: {:08X}", result); - MessageBoxA(nullptr, - "Failed to install content!\n\nCheck xenia.log for technical " - "details.", - "Failed to install content!", MB_ICONERROR); + xe::ui::ImGuiDialog::ShowMessageBox( + imgui_drawer_.get(), "Failed to install content!", + "Failed to install content!\n\nCheck xenia.log for technical " + "details."); } } } @@ -1076,26 +1100,308 @@ void EmulatorWindow::SetInitializingShaderStorage(bool initializing) { UpdateTitle(); } -void EmulatorWindow::RunPreviouslyPlayedTitle() { - if (recently_launched_titles_.size() >= 1) { - RunTitle(recently_launched_titles_[0].path_to_file); +// Notes: +// Assumes titles do not use the guide button. +// For titles that do such as dashboards these titles could be excluded based on +// their title ID. +// +// If the Xbox Gamebar overlay is enabled Windows will consume the guide +// button's input, this can be seen using hid-demo. +// +// Workaround: Detect if the Xbox Gamebar overlay is enabled then use the BACK +// button instead of the GUIDE button. Therefore BACK and GUIDE are reserved +// buttons for hotkeys. +// +// This is not an issue with DualShock controllers because Windows will not +// open the gamebar overlay using the PlayStation menu button. +// +// Steam: +// If guide button focus is enabled steam will open. +// Steam uses BACK + GUIDE to open an On-Screen keyboard, however this is not a +// problem since both these buttons are reserved. +const std::map controller_hotkey_map = { + // Must use the Guide Button for all pass through hotkeys + {X_INPUT_GAMEPAD_A | X_INPUT_GAMEPAD_GUIDE, + EmulatorWindow::ControllerHotKey( + EmulatorWindow::ButtonFunctions::ReadbackResolve, + "A + Guide = Toggle Readback Resolve", true)}, + {X_INPUT_GAMEPAD_B | X_INPUT_GAMEPAD_GUIDE, + EmulatorWindow::ControllerHotKey( + EmulatorWindow::ButtonFunctions::CloseWindow, + "B + Guide = Close Window")}, + {X_INPUT_GAMEPAD_Y | X_INPUT_GAMEPAD_GUIDE, + EmulatorWindow::ControllerHotKey( + EmulatorWindow::ButtonFunctions::ToggleFullscreen, + "Y + Guide = Toggle Fullscreen", true)}, + {X_INPUT_GAMEPAD_X | X_INPUT_GAMEPAD_GUIDE, + EmulatorWindow::ControllerHotKey( + EmulatorWindow::ButtonFunctions::ClearMemoryPageState, + "X + Guide = Toggle Clear Memory Page State", true)}, + + {X_INPUT_GAMEPAD_RIGHT_SHOULDER | X_INPUT_GAMEPAD_GUIDE, + EmulatorWindow::ControllerHotKey( + EmulatorWindow::ButtonFunctions::ToggleControllerVibration, + "Right Shoulder + Guide = Toggle Controller Vibration", true)}, + + // CPU Time Scalar with no rumble feedback + {X_INPUT_GAMEPAD_DPAD_DOWN | X_INPUT_GAMEPAD_GUIDE, + EmulatorWindow::ControllerHotKey( + EmulatorWindow::ButtonFunctions::CpuTimeScalarSetHalf, + "D-PAD Down + Guide = Half CPU Scalar")}, + {X_INPUT_GAMEPAD_DPAD_UP | X_INPUT_GAMEPAD_GUIDE, + EmulatorWindow::ControllerHotKey( + EmulatorWindow::ButtonFunctions::CpuTimeScalarSetDouble, + "D-PAD Up + Guide = Double CPU Scalar")}, + {X_INPUT_GAMEPAD_DPAD_RIGHT | X_INPUT_GAMEPAD_GUIDE, + EmulatorWindow::ControllerHotKey( + EmulatorWindow::ButtonFunctions::CpuTimeScalarReset, + "D-PAD Right + Guide = Reset CPU Scalar")}, + + // non-pass through hotkeys + {X_INPUT_GAMEPAD_Y, EmulatorWindow::ControllerHotKey( + EmulatorWindow::ButtonFunctions::ToggleFullscreen, + "Y = Toggle Fullscreen", true, false)}, + {X_INPUT_GAMEPAD_START, + EmulatorWindow::ControllerHotKey( + EmulatorWindow::ButtonFunctions::RunPreviouslyPlayedTitle, + "Start = Run previously played title", false, false)}, + {X_INPUT_GAMEPAD_BACK | X_INPUT_GAMEPAD_START, + EmulatorWindow::ControllerHotKey( + EmulatorWindow::ButtonFunctions::CloseWindow, + "Back + Start = Close Window", false, false)}}; + +EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey( + int buttons) { + // Default return value + EmulatorWindow::ControllerHotKey Unknown_hotkey = {}; + + if (buttons == 0) return Unknown_hotkey; + + // Hotkey cool-down to prevent toggling too fast + const std::chrono::milliseconds delay(75); + + // If the Xbox Gamebar is enabled or the Guide button is disabled then + // replace the Guide button with the Back button without redeclaring the key + // mappings + if (IsUseNexusForGameBarEnabled() || !cvars::guide_button) { + if ((buttons & X_INPUT_GAMEPAD_BACK) == X_INPUT_GAMEPAD_BACK) { + buttons &= ~X_INPUT_GAMEPAD_BACK; + buttons |= X_INPUT_GAMEPAD_GUIDE; + } } + + auto it = controller_hotkey_map.find(buttons); + if (it == controller_hotkey_map.end()) { + return Unknown_hotkey; + } + + // Do not activate hotkeys that are not intended for activation during + // gameplay + if (emulator_->is_title_open() && !it->second.title_passthru) { + return Unknown_hotkey; + } + + EmulatorWindow::ControllerHotKey button_combination = it->second; + + switch (button_combination.function) { + case ButtonFunctions::ToggleFullscreen: + ToggleFullscreen(); + + // Extra Sleep + xe::threading::Sleep(delay); + break; + case ButtonFunctions::RunPreviouslyPlayedTitle: + RunPreviouslyPlayedTitle(); + break; + case ButtonFunctions::ClearMemoryPageState: + ToggleGPUSetting(gpu_cvar::ClearMemoryPageState); + + // Assume the user wants ClearCaches as well + if (cvars::d3d12_clear_memory_page_state) { + GpuClearCaches(); + } + + // Extra Sleep + xe::threading::Sleep(delay); + break; + case ButtonFunctions::ReadbackResolve: + ToggleGPUSetting(gpu_cvar::ReadbackResolve); + + // Extra Sleep + xe::threading::Sleep(delay); + break; + case ButtonFunctions::CpuTimeScalarSetHalf: + CpuTimeScalarSetHalf(); + break; + case ButtonFunctions::CpuTimeScalarSetDouble: + CpuTimeScalarSetDouble(); + break; + case ButtonFunctions::CpuTimeScalarReset: + CpuTimeScalarReset(); + break; + case ButtonFunctions::ToggleControllerVibration: + ToggleControllerVibration(); + + // Extra Sleep + xe::threading::Sleep(delay); + break; + case ButtonFunctions::CloseWindow: + window_->RequestClose(); + break; + case ButtonFunctions::Unknown: + default: + break; + } + + xe::threading::Sleep(delay); + + return it->second; +} + +void EmulatorWindow::VibrateController(xe::hid::InputSystem* input_sys, + bool toggle_rumble) { + const std::chrono::milliseconds rumble_duration(100); + + // Hold lock while sleeping this thread for the duration of the rumble, + // otherwise the rumble may fail. + auto input_lock = input_sys->lock(); + + X_INPUT_VIBRATION vibration; + + vibration.left_motor_speed = toggle_rumble ? UINT16_MAX : 0; + vibration.right_motor_speed = toggle_rumble ? UINT16_MAX : 0; + + input_sys->SetState(0, &vibration); + + // Vibration duration + if (toggle_rumble) { + xe::threading::Sleep(rumble_duration); + } +} + +void EmulatorWindow::GamepadHotKeys() { + X_INPUT_STATE state; + + const std::chrono::milliseconds thread_delay(75); + + auto input_sys = emulator_->input_system(); + + if (input_sys) { + // SDL and XInput both support the guide button + while (true) { + // Block scope surrounding input_lock used to release the lock + { + auto input_lock = input_sys->lock(); + + for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) { + input_sys->GetState(user_index, &state); + } + } + + if (ProcessControllerHotkey(state.gamepad.buttons).rumble) { + // Enable Vibration + VibrateController(input_sys, true); + + // Disable Vibration + VibrateController(input_sys, false); + } + + xe::threading::Sleep(thread_delay); + } + } +} + +void EmulatorWindow::ToggleGPUSetting(gpu_cvar value) { + switch (value) { + case gpu_cvar::ClearMemoryPageState: + D3D12SaveGPUSetting(D3D12GPUSetting::ClearMemoryPageState, + !cvars::d3d12_clear_memory_page_state); + break; + case gpu_cvar::ReadbackResolve: + D3D12SaveGPUSetting(D3D12GPUSetting::ReadbackResolve, + !cvars::d3d12_readback_resolve); + break; + } +} + +// Determine if the Xbox Gamebar is enabled via the Windows registry +bool EmulatorWindow::IsUseNexusForGameBarEnabled() { +#ifdef _WIN32 + const LPWSTR reg_path = L"SOFTWARE\\Microsoft\\GameBar"; + const LPWSTR key = L"UseNexusForGameBarEnabled"; + + DWORD value = 0; + DWORD dataSize = sizeof(value); + + RegGetValue(HKEY_CURRENT_USER, reg_path, key, RRF_RT_DWORD, nullptr, &value, + &dataSize); + + return (bool)value; +#else + return false; +#endif +} + +void EmulatorWindow::DisplayHotKeysConfig() { + std::string msg = ""; + std::string msg_passthru = ""; + + bool guide_enabled = !IsUseNexusForGameBarEnabled() && cvars::guide_button; + + for (auto const& [key, val] : controller_hotkey_map) { + std::string pretty_text = val.pretty; + + if (!guide_enabled) { + pretty_text = std::regex_replace( + pretty_text, + std::regex("Guide", std::regex_constants::syntax_option_type::icase), + "Back"); + } + + if (emulator_->is_title_open() && !val.title_passthru) { + pretty_text += " (Disabled)"; + } + + if (val.title_passthru) { + msg += pretty_text + "\n"; + } else { + msg_passthru += pretty_text + "\n"; + } + } + + // Add Title + msg.insert(0, "Gameplay Hotkeys\n"); + + // Prepend non-passthru hotkeys + msg_passthru += "\n"; + msg.insert(0, msg_passthru); + + msg += "\n"; + msg += "Readback Resolve: " + + std::string(cvars::d3d12_readback_resolve ? "true\n" : "false\n"); + msg += + "Clear Memory Page State: " + + std::string(cvars::d3d12_clear_memory_page_state ? "true\n" : "false\n"); + + xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), "Controller Hotkeys", + msg); } xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) { bool titleExists = !std::filesystem::exists(path); if (path.empty() || titleExists) { - char* log_msg = path.empty() ? "Failed to launch title path is empty" - : "Failed to launch title path is invalid"; + char* log_msg = path.empty() ? "Failed to launch title path is empty." + : "Failed to launch title path is invalid."; XELOGE(log_msg); - MessageBoxA(nullptr, log_msg, "Title Launch Failed!", MB_ICONERROR); + xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), + "Title Launch Failed!", log_msg); return X_STATUS_NO_SUCH_FILE; } - + if (emulator_->is_title_open()) { // Terminate the current title and start a new title. // if (emulator_->TerminateTitle() == X_STATUS_SUCCESS) { @@ -1113,10 +1419,9 @@ xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) { if (result) { XELOGE("Failed to launch target: {:08X}", result); - MessageBoxA( - nullptr, - "Failed to launch title.\n\nCheck xenia.log for technical details.", - "Title Launch Failed!", MB_ICONERROR); + xe::ui::ImGuiDialog::ShowMessageBox( + imgui_drawer_.get(), "Title Launch Failed!", + "Failed to launch title.\n\nCheck xenia.log for technical details."); } else { AddRecentlyLaunchedTitle(path, emulator_->title_name()); } @@ -1124,6 +1429,12 @@ xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) { return result; } +void EmulatorWindow::RunPreviouslyPlayedTitle() { + if (recently_launched_titles_.size() >= 1) { + RunTitle(recently_launched_titles_[0].path_to_file); + } +} + void EmulatorWindow::FillRecentlyLaunchedTitlesMenu( xe::ui::MenuItem* recent_menu) { unsigned int index = 0; diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index 6654d4524..f02d2d335 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -25,6 +25,8 @@ #include "xenia/ui/windowed_app_context.h" #include "xenia/xbox.h" +#define MAX_USERS 4 + namespace xe { namespace app { @@ -53,6 +55,8 @@ class EmulatorWindow { static std::unique_ptr Create( Emulator* emulator, ui::WindowedAppContext& app_context); + std::unique_ptr Gamepad_HotKeys_Listener; + Emulator* emulator() const { return emulator_; } ui::WindowedAppContext& app_context() const { return app_context_; } ui::Window* window() const { return window_.get(); } @@ -69,6 +73,47 @@ class EmulatorWindow { void ToggleFullscreen(); void SetInitializingShaderStorage(bool initializing); + // Types of button functions for hotkeys. + enum class ButtonFunctions { + ToggleFullscreen, + RunPreviouslyPlayedTitle, + CpuTimeScalarSetHalf, + CpuTimeScalarSetDouble, + CpuTimeScalarReset, + ToggleControllerVibration, + ClearMemoryPageState, + ReadbackResolve, + CloseWindow, + Unknown + }; + + enum class gpu_cvar { + ClearMemoryPageState, + ReadbackResolve, + }; + + class ControllerHotKey { + public: + // If true the hotkey can be activated while a title is running, otherwise + // false. + bool title_passthru; + + // If true vibrate the controller after activating the hotkey, otherwise + // false. + bool rumble; + std::string pretty; + ButtonFunctions function; + + ControllerHotKey(ButtonFunctions fn = ButtonFunctions::Unknown, + std::string pretty = "", bool rumble = false, + bool active = true) { + function = fn; + this->pretty = pretty; + title_passthru = active; + this->rumble = rumble; + } + }; + private: class EmulatorWindowListener final : public ui::WindowListener, public ui::WindowInputListener { @@ -152,6 +197,13 @@ class EmulatorWindow { void ShowFAQ(); void ShowBuildCommit(); + EmulatorWindow::ControllerHotKey ProcessControllerHotkey(int buttons); + void VibrateController(xe::hid::InputSystem* input_sys, bool vibrate = true); + void GamepadHotKeys(); + void ToggleGPUSetting(gpu_cvar index); + bool IsUseNexusForGameBarEnabled(); + void DisplayHotKeysConfig(); + xe::X_STATUS RunTitle(std::filesystem::path path); void RunPreviouslyPlayedTitle(); void FillRecentlyLaunchedTitlesMenu(xe::ui::MenuItem* recent_menu); diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.cc b/src/xenia/gpu/d3d12/d3d12_command_processor.cc index f4e2cfe90..164de4a0a 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.cc +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.cc @@ -55,8 +55,21 @@ DEFINE_bool(d3d12_clear_memory_page_state, false, namespace xe { namespace gpu { + +void D3D12SaveGPUSetting(D3D12GPUSetting setting, uint64_t value) { + switch (setting) { + case D3D12GPUSetting::ClearMemoryPageState: + OVERRIDE_bool(d3d12_clear_memory_page_state, (bool)value); + break; + case D3D12GPUSetting::ReadbackResolve: + OVERRIDE_bool(d3d12_readback_resolve, (bool)value); + break; + } +} + namespace d3d12 { + // Generated with `xb buildshaders`. namespace shaders { #include "xenia/gpu/shaders/bytecode/d3d12_5_1/apply_gamma_pwl_cs.h" diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.h b/src/xenia/gpu/d3d12/d3d12_command_processor.h index e476a49a3..3c0ebb98e 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.h +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.h @@ -42,6 +42,14 @@ namespace xe { namespace gpu { + +enum class D3D12GPUSetting { + ReadbackResolve, + ClearMemoryPageState, +}; + +void D3D12SaveGPUSetting(D3D12GPUSetting setting, uint64_t value); + namespace d3d12 { struct MemExportRange { uint32_t base_address_dwords; diff --git a/src/xenia/hid/hid_demo.cc b/src/xenia/hid/hid_demo.cc index 46111fc84..dd4198cb5 100644 --- a/src/xenia/hid/hid_demo.cc +++ b/src/xenia/hid/hid_demo.cc @@ -345,6 +345,7 @@ void HidDemoApp::DrawUserInputGetKeystroke(uint32_t user_index, bool poll, bool clear_log) const { static const std::unordered_map kVkPretty = { + {ui::VirtualKey::kXInputPadGuide, "Guide"}, {ui::VirtualKey::kXInputPadA, "A"}, {ui::VirtualKey::kXInputPadB, "B"}, {ui::VirtualKey::kXInputPadX, "X"}, diff --git a/src/xenia/hid/hid_flags.cc b/src/xenia/hid/hid_flags.cc index 19a2b867f..9dff22fbf 100644 --- a/src/xenia/hid/hid_flags.cc +++ b/src/xenia/hid/hid_flags.cc @@ -9,5 +9,5 @@ #include "xenia/hid/hid_flags.h" -DEFINE_bool(guide_button, false, "Forward guide button presses to guest.", +DEFINE_bool(guide_button, true, "Forward guide button presses to guest.", "HID"); diff --git a/src/xenia/hid/sdl/sdl_input_driver.cc b/src/xenia/hid/sdl/sdl_input_driver.cc index 044c7dcd0..1a82f625c 100644 --- a/src/xenia/hid/sdl/sdl_input_driver.cc +++ b/src/xenia/hid/sdl/sdl_input_driver.cc @@ -249,7 +249,7 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags, // The order of this list is also the order in which events are send if // multiple buttons change at once. static_assert(sizeof(X_INPUT_GAMEPAD::buttons) == 2); - static constexpr std::array kVkLookup = { + static constexpr std::array kVkLookup = { // 00 - True buttons from xinput button field ui::VirtualKey::kXInputPadDpadUp, ui::VirtualKey::kXInputPadDpadDown, @@ -261,7 +261,8 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags, ui::VirtualKey::kXInputPadRThumbPress, ui::VirtualKey::kXInputPadLShoulder, ui::VirtualKey::kXInputPadRShoulder, - ui::VirtualKey::kNone, /* Guide has no VK */ + // Guide has no VK (kNone), however using kXInputPadGuide. + ui::VirtualKey::kXInputPadGuide, ui::VirtualKey::kNone, /* Unknown */ ui::VirtualKey::kXInputPadA, ui::VirtualKey::kXInputPadB, diff --git a/src/xenia/hid/winkey/winkey_binding_table.inc b/src/xenia/hid/winkey/winkey_binding_table.inc index c11b277a0..253f8fdae 100644 --- a/src/xenia/hid/winkey/winkey_binding_table.inc +++ b/src/xenia/hid/winkey/winkey_binding_table.inc @@ -11,6 +11,7 @@ // constructing various tables. // clang-format off +XE_HID_WINKEY_BINDING(Guide, "GUIDE" , keybind_guide , "0x08") XE_HID_WINKEY_BINDING(DpadLeft, "DPAD_LEFT" , keybind_dpad_left , "^A" ) XE_HID_WINKEY_BINDING(DpadRight, "DPAD_RIGHT" , keybind_dpad_right , "^D" ) XE_HID_WINKEY_BINDING(DpadDown, "DPAD_DOWN" , keybind_dpad_down , "^S" ) diff --git a/src/xenia/hid/winkey/winkey_input_driver.cc b/src/xenia/hid/winkey/winkey_input_driver.cc index 53c5ef359..8fdb8f6e7 100644 --- a/src/xenia/hid/winkey/winkey_input_driver.cc +++ b/src/xenia/hid/winkey/winkey_input_driver.cc @@ -145,46 +145,49 @@ X_RESULT WinKeyInputDriver::GetState(uint32_t user_index, IsKeyDown(b.input_key)) { switch (b.output_key) { case ui::VirtualKey::kXInputPadA: - buttons |= 0x1000; // XINPUT_GAMEPAD_A + buttons |= X_INPUT_GAMEPAD_A; break; case ui::VirtualKey::kXInputPadY: - buttons |= 0x8000; // XINPUT_GAMEPAD_Y + buttons |= X_INPUT_GAMEPAD_Y; break; case ui::VirtualKey::kXInputPadB: - buttons |= 0x2000; // XINPUT_GAMEPAD_B + buttons |= X_INPUT_GAMEPAD_B; break; case ui::VirtualKey::kXInputPadX: - buttons |= 0x4000; // XINPUT_GAMEPAD_X + buttons |= X_INPUT_GAMEPAD_X; + break; + case ui::VirtualKey::kXInputPadGuide: + buttons |= X_INPUT_GAMEPAD_GUIDE; break; case ui::VirtualKey::kXInputPadDpadLeft: - buttons |= 0x0004; // XINPUT_GAMEPAD_DPAD_LEFT + buttons |= X_INPUT_GAMEPAD_DPAD_LEFT; break; case ui::VirtualKey::kXInputPadDpadRight: - buttons |= 0x0008; // XINPUT_GAMEPAD_DPAD_RIGHT + buttons |= X_INPUT_GAMEPAD_DPAD_RIGHT; break; case ui::VirtualKey::kXInputPadDpadDown: - buttons |= 0x0002; // XINPUT_GAMEPAD_DPAD_DOWN + buttons |= X_INPUT_GAMEPAD_DPAD_DOWN; break; case ui::VirtualKey::kXInputPadDpadUp: - buttons |= 0x0001; // XINPUT_GAMEPAD_DPAD_UP + buttons |= X_INPUT_GAMEPAD_DPAD_UP; break; case ui::VirtualKey::kXInputPadRThumbPress: - buttons |= 0x0080; // XINPUT_GAMEPAD_RIGHT_THUMB + buttons |= X_INPUT_GAMEPAD_RIGHT_THUMB; break; case ui::VirtualKey::kXInputPadLThumbPress: - buttons |= 0x0040; // XINPUT_GAMEPAD_LEFT_THUMB + buttons |= X_INPUT_GAMEPAD_LEFT_THUMB; break; case ui::VirtualKey::kXInputPadBack: - buttons |= 0x0020; // XINPUT_GAMEPAD_BACK + buttons |= X_INPUT_GAMEPAD_BACK; break; case ui::VirtualKey::kXInputPadStart: - buttons |= 0x0010; // XINPUT_GAMEPAD_START + buttons |= X_INPUT_GAMEPAD_START; break; case ui::VirtualKey::kXInputPadLShoulder: - buttons |= 0x0100; // XINPUT_GAMEPAD_LEFT_SHOULDER + buttons |= X_INPUT_GAMEPAD_LEFT_SHOULDER; break; case ui::VirtualKey::kXInputPadRShoulder: - buttons |= 0x0200; // XINPUT_GAMEPAD_RIGHT_SHOULDER + buttons |= X_INPUT_GAMEPAD_RIGHT_SHOULDER; break; case ui::VirtualKey::kXInputPadLTrigger: left_trigger = 0xFF; diff --git a/src/xenia/hid/xinput/xinput_input_driver.cc b/src/xenia/hid/xinput/xinput_input_driver.cc index 284e005c5..6a92fa0fb 100644 --- a/src/xenia/hid/xinput/xinput_input_driver.cc +++ b/src/xenia/hid/xinput/xinput_input_driver.cc @@ -27,6 +27,7 @@ XInputInputDriver::XInputInputDriver(xe::ui::Window* window, module_(nullptr), XInputGetCapabilities_(nullptr), XInputGetState_(nullptr), + XInputGetStateEx_(nullptr), XInputGetKeystroke_(nullptr), XInputSetState_(nullptr), XInputEnable_(nullptr) {} @@ -37,6 +38,7 @@ XInputInputDriver::~XInputInputDriver() { module_ = nullptr; XInputGetCapabilities_ = nullptr; XInputGetState_ = nullptr; + XInputGetStateEx_ = nullptr; XInputGetKeystroke_ = nullptr; XInputSetState_ = nullptr; XInputEnable_ = nullptr; @@ -49,9 +51,13 @@ X_STATUS XInputInputDriver::Setup() { return X_STATUS_DLL_NOT_FOUND; } + // Support guide button with XInput using XInputGetStateEx + auto const XInputGetStateEx = (LPCSTR)100; + // Required. auto xigc = GetProcAddress(module, "XInputGetCapabilities"); auto xigs = GetProcAddress(module, "XInputGetState"); + auto xigsEx = GetProcAddress(module, XInputGetStateEx); auto xigk = GetProcAddress(module, "XInputGetKeystroke"); auto xiss = GetProcAddress(module, "XInputSetState"); @@ -67,17 +73,14 @@ X_STATUS XInputInputDriver::Setup() { module_ = module; XInputGetCapabilities_ = xigc; XInputGetState_ = xigs; + XInputGetStateEx_ = xigsEx; XInputGetKeystroke_ = xigk; XInputSetState_ = xiss; XInputEnable_ = xie; - - if (cvars::guide_button) { - // Theoretically there is XInputGetStateEx - // but thats undocumented and milage varies. - XELOGW("XInput: Guide button support is not implemented."); - } + return X_STATUS_SUCCESS; } + constexpr uint64_t SKIP_INVALID_CONTROLLER_TIME = 1100; static uint64_t last_invalid_time[4]; @@ -137,10 +140,18 @@ X_RESULT XInputInputDriver::GetState(uint32_t user_index, if (skipper) { return skipper; } - XINPUT_STATE native_state; - auto xigs = (decltype(&XInputGetState))XInputGetState_; + + struct { + XINPUT_STATE state; + unsigned int xinput_state_ex_padding; // Add padding in case we are using XInputGetStateEx + } native_state; - DWORD result = xigs(user_index, &native_state); + // If the guide button is enabled use XInputGetStateEx, otherwise use the default XInputGetState. + auto xigs = cvars::guide_button + ? (decltype(&XInputGetState))XInputGetStateEx_ + : (decltype(&XInputGetState))XInputGetState_; + + DWORD result = xigs(user_index, &native_state.state); if (result) { if (result == ERROR_DEVICE_NOT_CONNECTED) { set_skip(user_index); @@ -148,14 +159,14 @@ X_RESULT XInputInputDriver::GetState(uint32_t user_index, return result; } - out_state->packet_number = native_state.dwPacketNumber; - out_state->gamepad.buttons = native_state.Gamepad.wButtons; - out_state->gamepad.left_trigger = native_state.Gamepad.bLeftTrigger; - out_state->gamepad.right_trigger = native_state.Gamepad.bRightTrigger; - out_state->gamepad.thumb_lx = native_state.Gamepad.sThumbLX; - out_state->gamepad.thumb_ly = native_state.Gamepad.sThumbLY; - out_state->gamepad.thumb_rx = native_state.Gamepad.sThumbRX; - out_state->gamepad.thumb_ry = native_state.Gamepad.sThumbRY; + out_state->packet_number = native_state.state.dwPacketNumber; + out_state->gamepad.buttons = native_state.state.Gamepad.wButtons; + out_state->gamepad.left_trigger = native_state.state.Gamepad.bLeftTrigger; + out_state->gamepad.right_trigger = native_state.state.Gamepad.bRightTrigger; + out_state->gamepad.thumb_lx = native_state.state.Gamepad.sThumbLX; + out_state->gamepad.thumb_ly = native_state.state.Gamepad.sThumbLY; + out_state->gamepad.thumb_rx = native_state.state.Gamepad.sThumbRX; + out_state->gamepad.thumb_ry = native_state.state.Gamepad.sThumbRY; return result; } diff --git a/src/xenia/hid/xinput/xinput_input_driver.h b/src/xenia/hid/xinput/xinput_input_driver.h index b14113963..520a8bbe5 100644 --- a/src/xenia/hid/xinput/xinput_input_driver.h +++ b/src/xenia/hid/xinput/xinput_input_driver.h @@ -34,6 +34,7 @@ class XInputInputDriver final : public InputDriver { void* module_; void* XInputGetCapabilities_; void* XInputGetState_; + void* XInputGetStateEx_; void* XInputGetKeystroke_; void* XInputSetState_; void* XInputEnable_; diff --git a/src/xenia/ui/virtual_key.h b/src/xenia/ui/virtual_key.h index 8bda0667a..e2491f69c 100644 --- a/src/xenia/ui/virtual_key.h +++ b/src/xenia/ui/virtual_key.h @@ -243,6 +243,7 @@ enum class VirtualKey : uint16_t { // because XInput is the API used for the Xbox 360 controller. // To avoid confusion between VK_GAMEPAD_* and VK_PAD_*, here they are // prefixed with kXboxOne and kXInput respectively. + // https://learn.microsoft.com/en-us/uwp/api/windows.system.virtualkey kXboxOneGamepadA = 0xC3, kXboxOneGamepadB = 0xC4, kXboxOneGamepadX = 0xC5, @@ -317,7 +318,7 @@ enum class VirtualKey : uint16_t { // VK_PAD_* from XInput.h for XInputGetKeystroke. kXInput prefix added to // distinguish from VK_GAMEPAD_*, added much later for the Xbox One // controller. - + // https://learn.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_keystroke kXInputPadA = 0x5800, kXInputPadB = 0x5801, kXInputPadX = 0x5802, @@ -354,6 +355,8 @@ enum class VirtualKey : uint16_t { kXInputPadRThumbUpRight = 0x5835, kXInputPadRThumbDownRight = 0x5836, kXInputPadRThumbDownLeft = 0x5837, + // Undocumented therefore kNone however using 0x5838 for now. + kXInputPadGuide = 0x5838, }; } // namespace ui