From 5e314291284d6f846bef033878495bc70402c170 Mon Sep 17 00:00:00 2001 From: Enrico Pozzobon Date: Mon, 20 Sep 2021 11:23:27 +0200 Subject: [PATCH] [WinKey] Rebindable keyboard controls. --- src/xenia/hid/winkey/winkey_binding_table.inc | 38 ++ src/xenia/hid/winkey/winkey_input_driver.cc | 350 ++++++++---------- src/xenia/hid/winkey/winkey_input_driver.h | 14 +- 3 files changed, 206 insertions(+), 196 deletions(-) create mode 100644 src/xenia/hid/winkey/winkey_binding_table.inc diff --git a/src/xenia/hid/winkey/winkey_binding_table.inc b/src/xenia/hid/winkey/winkey_binding_table.inc new file mode 100644 index 000000000..fdf0261f3 --- /dev/null +++ b/src/xenia/hid/winkey/winkey_binding_table.inc @@ -0,0 +1,38 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +// This is a partial file designed to be included by other files when +// constructing various tables. + +// clang-format off +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" ) +XE_HID_WINKEY_BINDING(DpadUp, "DPAD_UP" , keybind_dpad_up , "^W" ) +XE_HID_WINKEY_BINDING(LThumbLeft, "LEFT_THUMB_LEFT" , keybind_left_thumb_left , "_A" ) +XE_HID_WINKEY_BINDING(LThumbRight, "LEFT_THUMB_RIGHT" , keybind_left_thumb_right , "_D" ) +XE_HID_WINKEY_BINDING(LThumbDown, "LEFT_THUMB_DOWN" , keybind_left_thumb_down , "_S" ) +XE_HID_WINKEY_BINDING(LThumbUp, "LEFT_THUMB_UP" , keybind_left_thumb_up , "_W" ) +XE_HID_WINKEY_BINDING(LThumbPress, "LEFT_THUMB_PRESSED" , keybind_left_thumb , "F" ) +XE_HID_WINKEY_BINDING(RThumbUp, "RIGHT_THUMB_UP" , keybind_right_thumb_up , "0x26") +XE_HID_WINKEY_BINDING(RThumbLeft, "RIGHT_THUMB_DOWN" , keybind_right_thumb_down , "0x28") +XE_HID_WINKEY_BINDING(RThumbRight, "RIGHT_THUMB_RIGHT" , keybind_right_thumb_right, "0x27") +XE_HID_WINKEY_BINDING(RThumbLeft, "RIGHT_THUMB_LEFT" , keybind_right_thumb_left , "0x25") +XE_HID_WINKEY_BINDING(RThumbPress, "RIGHT_THUMB_PRESSED", keybind_right_thumb , "K" ) +XE_HID_WINKEY_BINDING(X, "X" , keybind_x , "L" ) +XE_HID_WINKEY_BINDING(B, "B" , keybind_b , "0xDE") +XE_HID_WINKEY_BINDING(A, "A" , keybind_a , "0xBA") +XE_HID_WINKEY_BINDING(Y, "Y" , keybind_y , "P" ) +XE_HID_WINKEY_BINDING(LTrigger, "LEFT_TRIGGER" , keybind_left_trigger , "Q I" ) +XE_HID_WINKEY_BINDING(RTrigger, "RIGHT_TRIGGER" , keybind_right_trigger , "E O" ) +XE_HID_WINKEY_BINDING(Back, "BACK" , keybind_back , "Z" ) +XE_HID_WINKEY_BINDING(Start, "START" , keybind_start , "X" ) +XE_HID_WINKEY_BINDING(LShoulder, "LEFT_SHOULDER" , keybind_left_shoulder , "1" ) +XE_HID_WINKEY_BINDING(RShoulder, "RIGHT_SHOULDER" , keybind_right_shoulder , "3" ) +// clang-format on diff --git a/src/xenia/hid/winkey/winkey_input_driver.cc b/src/xenia/hid/winkey/winkey_input_driver.cc index ba0b12cdf..c038e69db 100644 --- a/src/xenia/hid/winkey/winkey_input_driver.cc +++ b/src/xenia/hid/winkey/winkey_input_driver.cc @@ -2,23 +2,84 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * + * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/hid/winkey/winkey_input_driver.h" +#include "xenia/base/logging.h" #include "xenia/base/platform_win.h" #include "xenia/hid/hid_flags.h" #include "xenia/hid/input_system.h" #include "xenia/ui/virtual_key.h" #include "xenia/ui/window.h" +#define XE_HID_WINKEY_BINDING(button, description, cvar_name, \ + cvar_default_value) \ + DEFINE_string(cvar_name, cvar_default_value, \ + "List of keys to bind to " description \ + ", separated by spaces", \ + "HID.WinKey") +#include "winkey_binding_table.inc" +#undef XE_HID_WINKEY_BINDING + namespace xe { namespace hid { namespace winkey { +bool __inline IsKeyToggled(uint8_t key) { + return (GetKeyState(key) & 0x1) == 0x1; +} + +bool __inline IsKeyDown(uint8_t key) { + return (GetAsyncKeyState(key) & 0x8000) == 0x8000; +} + +bool __inline IsKeyDown(ui::VirtualKey virtual_key) { + return IsKeyDown(static_cast(virtual_key)); +} + +void WinKeyInputDriver::ParseKeyBinding(ui::VirtualKey output_key, + const std::string_view description, + const std::string_view source_tokens) { + for (const std::string_view source_token : + utf8::split(source_tokens, " ", true)) { + KeyBinding key_binding; + key_binding.output_key = output_key; + + std::string_view token = source_token; + + if (utf8::starts_with(token, "_")) { + key_binding.lowercase = true; + token = token.substr(1); + } else if (utf8::starts_with(token, "^")) { + key_binding.uppercase = true; + token = token.substr(1); + } + + if (utf8::starts_with(token, "0x")) { + token = token.substr(2); + key_binding.input_key = static_cast( + string_util::from_string(token, true)); + } else if (token.size() == 1 && (token[0] >= 'A' && token[0] <= 'Z') || + (token[0] >= '0' && token[0] <= '9')) { + key_binding.input_key = static_cast(token[0]); + } + + if (key_binding.input_key == ui::VirtualKey::kNone) { + XELOGW("winkey: failed to parse binding \"{}\" for controller input {}.", + source_token, description); + continue; + } + + key_bindings_.push_back(key_binding); + XELOGI("winkey: \"{}\" binds key 0x{:X} to controller input {}.", + source_token, key_binding.input_key, description); + } +} + WinKeyInputDriver::WinKeyInputDriver(xe::ui::Window* window) : InputDriver(window), packet_number_(1) { // Register a key listener. @@ -50,6 +111,13 @@ WinKeyInputDriver::WinKeyInputDriver(xe::ui::Window* window) key.repeat_count = evt->repeat_count(); key_events_.push(key); }); + +#define XE_HID_WINKEY_BINDING(button, description, cvar_name, \ + cvar_default_value) \ + ParseKeyBinding(xe::ui::VirtualKey::kXInputPad##button, description, \ + cvars::cvar_name); +#include "winkey_binding_table.inc" +#undef XE_HID_WINKEY_BINDING } WinKeyInputDriver::~WinKeyInputDriver() = default; @@ -78,9 +146,6 @@ X_RESULT WinKeyInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags, return X_ERROR_SUCCESS; } -#define IS_KEY_TOGGLED(key) ((GetKeyState(key) & 0x1) == 0x1) -#define IS_KEY_DOWN(key) ((GetAsyncKeyState(key) & 0x8000) == 0x8000) - X_RESULT WinKeyInputDriver::GetState(uint32_t user_index, X_INPUT_STATE* out_state) { if (user_index != 0) { @@ -98,114 +163,86 @@ X_RESULT WinKeyInputDriver::GetState(uint32_t user_index, int16_t thumb_ry = 0; if (window()->has_focus() && is_active()) { - if (IS_KEY_TOGGLED(VK_CAPITAL) || IS_KEY_DOWN(VK_SHIFT)) { - // dpad toggled - if (IS_KEY_DOWN('A')) { - // A - buttons |= 0x0004; // XINPUT_GAMEPAD_DPAD_LEFT + bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT); + for (const KeyBinding& b : key_bindings_) { + if (((b.lowercase == b.uppercase) || (b.lowercase && !capital) || + (b.uppercase && capital)) && + IsKeyDown(b.input_key)) { + switch (b.output_key) { + case ui::VirtualKey::kXInputPadA: + buttons |= 0x1000; // XINPUT_GAMEPAD_A + break; + case ui::VirtualKey::kXInputPadY: + buttons |= 0x8000; // XINPUT_GAMEPAD_Y + break; + case ui::VirtualKey::kXInputPadB: + buttons |= 0x2000; // XINPUT_GAMEPAD_B + break; + case ui::VirtualKey::kXInputPadX: + buttons |= 0x4000; // XINPUT_GAMEPAD_X + break; + case ui::VirtualKey::kXInputPadDpadLeft: + buttons |= 0x0004; // XINPUT_GAMEPAD_DPAD_LEFT + break; + case ui::VirtualKey::kXInputPadDpadRight: + buttons |= 0x0008; // XINPUT_GAMEPAD_DPAD_RIGHT + break; + case ui::VirtualKey::kXInputPadDpadDown: + buttons |= 0x0002; // XINPUT_GAMEPAD_DPAD_DOWN + break; + case ui::VirtualKey::kXInputPadDpadUp: + buttons |= 0x0001; // XINPUT_GAMEPAD_DPAD_UP + break; + case ui::VirtualKey::kXInputPadRThumbPress: + buttons |= 0x0080; // XINPUT_GAMEPAD_RIGHT_THUMB + break; + case ui::VirtualKey::kXInputPadLThumbPress: + buttons |= 0x0040; // XINPUT_GAMEPAD_LEFT_THUMB + break; + case ui::VirtualKey::kXInputPadBack: + buttons |= 0x0020; // XINPUT_GAMEPAD_BACK + break; + case ui::VirtualKey::kXInputPadStart: + buttons |= 0x0010; // XINPUT_GAMEPAD_START + break; + case ui::VirtualKey::kXInputPadLShoulder: + buttons |= 0x0100; // XINPUT_GAMEPAD_LEFT_SHOULDER + break; + case ui::VirtualKey::kXInputPadRShoulder: + buttons |= 0x0200; // XINPUT_GAMEPAD_RIGHT_SHOULDER + break; + case ui::VirtualKey::kXInputPadLTrigger: + left_trigger = 0xFF; + break; + case ui::VirtualKey::kXInputPadRTrigger: + right_trigger = 0xFF; + break; + case ui::VirtualKey::kXInputPadLThumbLeft: + thumb_lx += SHRT_MIN; + break; + case ui::VirtualKey::kXInputPadLThumbRight: + thumb_lx += SHRT_MAX; + break; + case ui::VirtualKey::kXInputPadLThumbDown: + thumb_ly += SHRT_MIN; + break; + case ui::VirtualKey::kXInputPadLThumbUp: + thumb_ly += SHRT_MAX; + break; + case ui::VirtualKey::kXInputPadRThumbUp: + thumb_ry += SHRT_MAX; + break; + case ui::VirtualKey::kXInputPadRThumbDown: + thumb_ry += SHRT_MIN; + break; + case ui::VirtualKey::kXInputPadRThumbRight: + thumb_rx += SHRT_MAX; + break; + case ui::VirtualKey::kXInputPadRThumbLeft: + thumb_rx += SHRT_MIN; + break; + } } - if (IS_KEY_DOWN('D')) { - // D - buttons |= 0x0008; // XINPUT_GAMEPAD_DPAD_RIGHT - } - if (IS_KEY_DOWN('S')) { - // S - buttons |= 0x0002; // XINPUT_GAMEPAD_DPAD_DOWN - } - if (IS_KEY_DOWN('W')) { - // W - buttons |= 0x0001; // XINPUT_GAMEPAD_DPAD_UP - } - } else { - // left stick - if (IS_KEY_DOWN('A')) { - // A - thumb_lx += SHRT_MIN; - } - if (IS_KEY_DOWN('D')) { - // D - thumb_lx += SHRT_MAX; - } - if (IS_KEY_DOWN('S')) { - // S - thumb_ly += SHRT_MIN; - } - if (IS_KEY_DOWN('W')) { - // W - thumb_ly += SHRT_MAX; - } - } - - if (IS_KEY_DOWN('F')) { - // F - buttons |= 0x0040; // XINPUT_GAMEPAD_LEFT_THUMB - } - - // Right stick - if (IS_KEY_DOWN(VK_UP)) { - // Up - thumb_ry += SHRT_MAX; - } - if (IS_KEY_DOWN(VK_DOWN)) { - // Down - thumb_ry += SHRT_MIN; - } - if (IS_KEY_DOWN(VK_RIGHT)) { - // Right - thumb_rx += SHRT_MAX; - } - if (IS_KEY_DOWN(VK_LEFT)) { - // Left - thumb_rx += SHRT_MIN; - } - - if (IS_KEY_DOWN('L')) { - // L - buttons |= 0x4000; // XINPUT_GAMEPAD_X - } - if (IS_KEY_DOWN(VK_OEM_7)) { - // ' - buttons |= 0x2000; // XINPUT_GAMEPAD_B - } - if (IS_KEY_DOWN(VK_OEM_1)) { - // ; - buttons |= 0x1000; // XINPUT_GAMEPAD_A - } - if (IS_KEY_DOWN('P')) { - // P - buttons |= 0x8000; // XINPUT_GAMEPAD_Y - } - - if (IS_KEY_DOWN('K')) { - // K - buttons |= 0x0080; // XINPUT_GAMEPAD_RIGHT_THUMB - } - - if (IS_KEY_DOWN('Q') || IS_KEY_DOWN('I')) { - // Q / I - left_trigger = 0xFF; - } - - if (IS_KEY_DOWN('E') || IS_KEY_DOWN('O')) { - // E / O - right_trigger = 0xFF; - } - - if (IS_KEY_DOWN('Z')) { - // Z - buttons |= 0x0020; // XINPUT_GAMEPAD_BACK - } - if (IS_KEY_DOWN('X')) { - // X - buttons |= 0x0010; // XINPUT_GAMEPAD_START - } - if (IS_KEY_DOWN('1')) { - // 1 - buttons |= 0x0100; // XINPUT_GAMEPAD_LEFT_SHOULDER - } - if (IS_KEY_DOWN('3')) { - // 3 - buttons |= 0x0200; // XINPUT_GAMEPAD_RIGHT_SHOULDER } } @@ -259,90 +296,13 @@ X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags, key_events_.pop(); } - switch (evt.virtual_key) { - case ui::VirtualKey::kOem1: // ; - xinput_virtual_key = ui::VirtualKey::kXInputPadA; - break; - case ui::VirtualKey::kOem7: // ' - xinput_virtual_key = ui::VirtualKey::kXInputPadB; - break; - case ui::VirtualKey::kL: - xinput_virtual_key = ui::VirtualKey::kXInputPadX; - break; - case ui::VirtualKey::kP: - xinput_virtual_key = ui::VirtualKey::kXInputPadY; - break; - case ui::VirtualKey::k3: - xinput_virtual_key = ui::VirtualKey::kXInputPadRShoulder; - break; - case ui::VirtualKey::k1: - xinput_virtual_key = ui::VirtualKey::kXInputPadLShoulder; - break; - case ui::VirtualKey::kQ: - case ui::VirtualKey::kI: - xinput_virtual_key = ui::VirtualKey::kXInputPadLTrigger; - break; - case ui::VirtualKey::kE: - case ui::VirtualKey::kO: - xinput_virtual_key = ui::VirtualKey::kXInputPadRTrigger; - break; - case ui::VirtualKey::kX: - xinput_virtual_key = ui::VirtualKey::kXInputPadStart; - break; - case ui::VirtualKey::kZ: - xinput_virtual_key = ui::VirtualKey::kXInputPadBack; - break; - case ui::VirtualKey::kUp: - xinput_virtual_key = ui::VirtualKey::kXInputPadRThumbUp; - break; - case ui::VirtualKey::kDown: - xinput_virtual_key = ui::VirtualKey::kXInputPadRThumbDown; - break; - case ui::VirtualKey::kRight: - xinput_virtual_key = ui::VirtualKey::kXInputPadRThumbRight; - break; - case ui::VirtualKey::kLeft: - xinput_virtual_key = ui::VirtualKey::kXInputPadRThumbLeft; - break; - default: - // TODO(DrChat): Some other way to toggle this... - if (IS_KEY_TOGGLED(VK_CAPITAL) || IS_KEY_DOWN(VK_SHIFT)) { - // D-pad toggled. - switch (evt.virtual_key) { - case ui::VirtualKey::kW: - xinput_virtual_key = ui::VirtualKey::kXInputPadDpadUp; - break; - case ui::VirtualKey::kS: - xinput_virtual_key = ui::VirtualKey::kXInputPadDpadDown; - break; - case ui::VirtualKey::kA: - xinput_virtual_key = ui::VirtualKey::kXInputPadDpadLeft; - break; - case ui::VirtualKey::kD: - xinput_virtual_key = ui::VirtualKey::kXInputPadDpadRight; - break; - default: - break; - } - } else { - // Left thumbstick. - switch (evt.virtual_key) { - case ui::VirtualKey::kW: - xinput_virtual_key = ui::VirtualKey::kXInputPadLThumbUp; - break; - case ui::VirtualKey::kS: - xinput_virtual_key = ui::VirtualKey::kXInputPadLThumbDown; - break; - case ui::VirtualKey::kA: - xinput_virtual_key = ui::VirtualKey::kXInputPadLThumbLeft; - break; - case ui::VirtualKey::kD: - xinput_virtual_key = ui::VirtualKey::kXInputPadLThumbRight; - break; - default: - break; - } - } + bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT); + for (const KeyBinding& b : key_bindings_) { + if (b.input_key == evt.virtual_key && + ((b.lowercase == b.uppercase) || (b.lowercase && !capital) || + (b.uppercase && capital))) { + xinput_virtual_key = b.output_key; + } } if (xinput_virtual_key != ui::VirtualKey::kNone) { diff --git a/src/xenia/hid/winkey/winkey_input_driver.h b/src/xenia/hid/winkey/winkey_input_driver.h index f015b7416..8b45c1457 100644 --- a/src/xenia/hid/winkey/winkey_input_driver.h +++ b/src/xenia/hid/winkey/winkey_input_driver.h @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * + * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -42,10 +42,22 @@ class WinKeyInputDriver : public InputDriver { bool prev_state = false; // down(true) or up(false) }; + struct KeyBinding { + ui::VirtualKey input_key = ui::VirtualKey::kNone; + ui::VirtualKey output_key = ui::VirtualKey::kNone; + bool uppercase = false; + bool lowercase = false; + }; + xe::global_critical_region global_critical_region_; std::queue key_events_; + std::vector key_bindings_; uint32_t packet_number_; + + void ParseKeyBinding(ui::VirtualKey virtual_key, + const std::string_view description, + const std::string_view binding); }; } // namespace winkey