File xenia_PR2233.patch of Package xenia-canary
From 1ba81e9151fb1939e5e9858ba81effe032caeebf Mon Sep 17 00:00:00 2001
From: guccigang420 <>
Date: Fri, 6 Oct 2023 17:00:18 +0200
Subject: [PATCH] Created a generic keyboard hid driver
---
premake5.lua | 4 +
src/xenia/app/premake5.lua | 5 +
src/xenia/app/xenia_main.cc | 6 +
src/xenia/hid/hid_demo.cc | 16 +
.../hid/keyboard/keyboard_binding_table.inc | 38 ++
src/xenia/hid/keyboard/keyboard_hid.cc | 25 ++
src/xenia/hid/keyboard/keyboard_hid.h | 28 ++
.../hid/keyboard/keyboard_input_driver.cc | 325 ++++++++++++++++++
.../hid/keyboard/keyboard_input_driver.h | 87 +++++
src/xenia/hid/keyboard/premake5.lua | 16 +
src/xenia/hid/premake5.lua | 5 +
11 files changed, 555 insertions(+)
create mode 100644 src/xenia/hid/keyboard/keyboard_binding_table.inc
create mode 100644 src/xenia/hid/keyboard/keyboard_hid.cc
create mode 100644 src/xenia/hid/keyboard/keyboard_hid.h
create mode 100644 src/xenia/hid/keyboard/keyboard_input_driver.cc
create mode 100644 src/xenia/hid/keyboard/keyboard_input_driver.h
create mode 100644 src/xenia/hid/keyboard/premake5.lua
diff --git a/premake5.lua b/premake5.lua
index 6739d6dba1..295628fed5 100644
--- a/premake5.lua
+++ b/premake5.lua
@@ -302,6 +302,10 @@ workspace("xenia")
include("src/xenia/hid/sdl")
end
+ if not os.istarget("windows") then
+ include("src/xenia/hid/keyboard")
+ end
+
if os.istarget("windows") then
include("src/xenia/apu/xaudio2")
include("src/xenia/gpu/d3d12")
diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua
index 86fcef7581..19d6ecc707 100644
--- a/src/xenia/app/premake5.lua
+++ b/src/xenia/app/premake5.lua
@@ -96,6 +96,11 @@ project("xenia-app")
"xenia-hid-sdl",
})
+ filter("platforms:not Windows")
+ links({
+ "xenia-hid-keyboard"
+ })
+
filter("platforms:Linux")
links({
"X11",
diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc
index 15f01a892d..9c517c91cc 100644
--- a/src/xenia/app/xenia_main.cc
+++ b/src/xenia/app/xenia_main.cc
@@ -55,6 +55,9 @@
#if !XE_PLATFORM_ANDROID
#include "xenia/hid/sdl/sdl_hid.h"
#endif // !XE_PLATFORM_ANDROID
+#if !XE_PLATFORM_WIN32
+#include "xenia/hid/keyboard/keyboard_hid.h"
+#endif
#if XE_PLATFORM_WIN32
#include "xenia/hid/winkey/winkey_hid.h"
#include "xenia/hid/xinput/xinput_hid.h"
@@ -359,6 +362,9 @@ std::vector<std::unique_ptr<hid::InputDriver>> EmulatorApp::CreateInputDrivers(
#if !XE_PLATFORM_ANDROID
factory.Add("sdl", xe::hid::sdl::Create);
#endif // !XE_PLATFORM_ANDROID
+#if !XE_PLATFORM_WIN32
+ factory.Add("keyboard", xe::hid::keyboard::Create);
+#endif
#if XE_PLATFORM_WIN32
// WinKey input driver should always be the last input driver added!
factory.Add("winkey", xe::hid::winkey::Create);
diff --git a/src/xenia/hid/hid_demo.cc b/src/xenia/hid/hid_demo.cc
index 46111fc843..a3aba329d9 100644
--- a/src/xenia/hid/hid_demo.cc
+++ b/src/xenia/hid/hid_demo.cc
@@ -40,6 +40,9 @@
#if !XE_PLATFORM_ANDROID
#include "xenia/hid/sdl/sdl_hid.h"
#endif // !XE_PLATFORM_ANDROID
+#if !XE_PLATFORM_WIN32
+#include "xenia/hid/keyboard/keyboard_hid.h"
+#endif
#if XE_PLATFORM_WIN32
#include "xenia/hid/winkey/winkey_hid.h"
#include "xenia/hid/xinput/xinput_hid.h"
@@ -132,6 +135,13 @@ std::vector<std::unique_ptr<hid::InputDriver>> HidDemoApp::CreateInputDrivers(
drivers.emplace_back(std::move(driver));
}
#endif // !XE_PLATFORM_ANDROID
+#if !XE_PLATFORM_WIN32
+ } else if (cvars::hid.compare("keyboard") == 0) {
+ auto driver = xe::hid::keyboard::Create(window, kZOrderHidInput);
+ if (driver && XSUCCEEDED(driver->Setup())) {
+ drivers.emplace_back(std::move(driver));
+ }
+#endif
#if XE_PLATFORM_WIN32
} else if (cvars::hid.compare("winkey") == 0) {
auto driver = xe::hid::winkey::Create(window, kZOrderHidInput);
@@ -151,6 +161,12 @@ std::vector<std::unique_ptr<hid::InputDriver>> HidDemoApp::CreateInputDrivers(
drivers.emplace_back(std::move(sdl_driver));
}
#endif // !XE_PLATFORM_ANDROID
+#if !XE_PLATFORM_WIN32
+ auto keyboard_driver = xe::hid::keyboard::Create(window, kZOrderHidInput);
+ if (keyboard_driver && XSUCCEEDED(keyboard_driver->Setup())) {
+ drivers.emplace_back(std::move(keyboard_driver));
+ }
+#endif
#if XE_PLATFORM_WIN32
auto xinput_driver = xe::hid::xinput::Create(window, kZOrderHidInput);
if (xinput_driver && XSUCCEEDED(xinput_driver->Setup())) {
diff --git a/src/xenia/hid/keyboard/keyboard_binding_table.inc b/src/xenia/hid/keyboard/keyboard_binding_table.inc
new file mode 100644
index 0000000000..4e4db3d1c7
--- /dev/null
+++ b/src/xenia/hid/keyboard/keyboard_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_KEYBOARD_BINDING(DpadLeft, "DPAD_LEFT" , keybind_dpad_left , "^A" )
+XE_HID_KEYBOARD_BINDING(DpadRight, "DPAD_RIGHT" , keybind_dpad_right , "^D" )
+XE_HID_KEYBOARD_BINDING(DpadDown, "DPAD_DOWN" , keybind_dpad_down , "^S" )
+XE_HID_KEYBOARD_BINDING(DpadUp, "DPAD_UP" , keybind_dpad_up , "^W" )
+XE_HID_KEYBOARD_BINDING(LThumbLeft, "LEFT_THUMB_LEFT" , keybind_left_thumb_left , "_A" )
+XE_HID_KEYBOARD_BINDING(LThumbRight, "LEFT_THUMB_RIGHT" , keybind_left_thumb_right , "_D" )
+XE_HID_KEYBOARD_BINDING(LThumbDown, "LEFT_THUMB_DOWN" , keybind_left_thumb_down , "_S" )
+XE_HID_KEYBOARD_BINDING(LThumbUp, "LEFT_THUMB_UP" , keybind_left_thumb_up , "_W" )
+XE_HID_KEYBOARD_BINDING(LThumbPress, "LEFT_THUMB_PRESSED" , keybind_left_thumb , "F" )
+XE_HID_KEYBOARD_BINDING(RThumbUp, "RIGHT_THUMB_UP" , keybind_right_thumb_up , "0x26")
+XE_HID_KEYBOARD_BINDING(RThumbDown, "RIGHT_THUMB_DOWN" , keybind_right_thumb_down , "0x28")
+XE_HID_KEYBOARD_BINDING(RThumbRight, "RIGHT_THUMB_RIGHT" , keybind_right_thumb_right, "0x27")
+XE_HID_KEYBOARD_BINDING(RThumbLeft, "RIGHT_THUMB_LEFT" , keybind_right_thumb_left , "0x25")
+XE_HID_KEYBOARD_BINDING(RThumbPress, "RIGHT_THUMB_PRESSED", keybind_right_thumb , "K" )
+XE_HID_KEYBOARD_BINDING(X, "X" , keybind_x , "L" )
+XE_HID_KEYBOARD_BINDING(B, "B" , keybind_b , "0xDE")
+XE_HID_KEYBOARD_BINDING(A, "A" , keybind_a , "0xBA")
+XE_HID_KEYBOARD_BINDING(Y, "Y" , keybind_y , "P" )
+XE_HID_KEYBOARD_BINDING(LTrigger, "LEFT_TRIGGER" , keybind_left_trigger , "Q I" )
+XE_HID_KEYBOARD_BINDING(RTrigger, "RIGHT_TRIGGER" , keybind_right_trigger , "E O" )
+XE_HID_KEYBOARD_BINDING(Back, "BACK" , keybind_back , "Z" )
+XE_HID_KEYBOARD_BINDING(Start, "START" , keybind_start , "X" )
+XE_HID_KEYBOARD_BINDING(LShoulder, "LEFT_SHOULDER" , keybind_left_shoulder , "1" )
+XE_HID_KEYBOARD_BINDING(RShoulder, "RIGHT_SHOULDER" , keybind_right_shoulder , "3" )
+// clang-format on
diff --git a/src/xenia/hid/keyboard/keyboard_hid.cc b/src/xenia/hid/keyboard/keyboard_hid.cc
new file mode 100644
index 0000000000..e6df0c46ef
--- /dev/null
+++ b/src/xenia/hid/keyboard/keyboard_hid.cc
@@ -0,0 +1,25 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Copyright 2014 Ben Vanik. All rights reserved. *
+ * Released under the BSD license - see LICENSE in the root for more details. *
+ ******************************************************************************
+ */
+
+#include "xenia/hid/keyboard/keyboard_hid.h"
+
+#include "xenia/hid/keyboard/keyboard_input_driver.h"
+
+namespace xe {
+namespace hid {
+namespace keyboard {
+
+std::unique_ptr<InputDriver> Create(xe::ui::Window* window,
+ size_t window_z_order) {
+ return std::make_unique<xe::hid::keyboard::KeyboardInputDriver>(window, window_z_order);
+}
+
+} // namespace winkey
+} // namespace hid
+} // namespace xe
diff --git a/src/xenia/hid/keyboard/keyboard_hid.h b/src/xenia/hid/keyboard/keyboard_hid.h
new file mode 100644
index 0000000000..db734d2c3b
--- /dev/null
+++ b/src/xenia/hid/keyboard/keyboard_hid.h
@@ -0,0 +1,28 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Copyright 2014 Ben Vanik. All rights reserved. *
+ * Released under the BSD license - see LICENSE in the root for more details. *
+ ******************************************************************************
+ */
+
+#ifndef XENIA_HID_KEYBOARD_KEYBOARD_HID_H_
+#define XENIA_HID_KEYBOARD_KEYBOARD_HID_H_
+
+#include <memory>
+
+#include "xenia/hid/input_system.h"
+
+namespace xe {
+namespace hid {
+namespace keyboard {
+
+std::unique_ptr<InputDriver> Create(xe::ui::Window* window,
+ size_t window_z_order);
+
+} // namespace winkey
+} // namespace hid
+} // namespace xe
+
+#endif // XENIA_HID_WINKEY_WINKEY_HID_H_
diff --git a/src/xenia/hid/keyboard/keyboard_input_driver.cc b/src/xenia/hid/keyboard/keyboard_input_driver.cc
new file mode 100644
index 0000000000..5d6dd7382c
--- /dev/null
+++ b/src/xenia/hid/keyboard/keyboard_input_driver.cc
@@ -0,0 +1,325 @@
+/**
+ ******************************************************************************
+ * 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. *
+ ******************************************************************************
+ */
+
+#include "xenia/hid/keyboard/keyboard_input_driver.h"
+
+#include "xenia/base/logging.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_KEYBOARD_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 "keyboard_binding_table.inc"
+#undef XE_HID_KEYBOARD_BINDING
+
+namespace xe {
+namespace hid {
+namespace keyboard {
+
+void KeyboardInputDriver::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.uppercase = false;
+ 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<ui::VirtualKey>(
+ string_util::from_string<uint16_t>(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<ui::VirtualKey>(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);
+ }
+}
+
+KeyboardInputDriver::KeyboardInputDriver(xe::ui::Window* window,
+ size_t window_z_order)
+ : InputDriver(window, window_z_order), window_input_listener_(*this) {
+#define XE_HID_KEYBOARD_BINDING(button, description, cvar_name, \
+ cvar_default_value) \
+ ParseKeyBinding(xe::ui::VirtualKey::kXInputPad##button, description, \
+ cvars::cvar_name);
+#include "keyboard_binding_table.inc"
+#undef XE_HID_KEYBOARD_BINDING
+
+ window->AddInputListener(&window_input_listener_, window_z_order);
+}
+
+KeyboardInputDriver::~KeyboardInputDriver() {
+ window()->RemoveInputListener(&window_input_listener_);
+}
+
+X_STATUS KeyboardInputDriver::Setup() { return X_STATUS_SUCCESS; }
+
+X_RESULT KeyboardInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
+ X_INPUT_CAPABILITIES* out_caps) {
+ if (user_index != 0) {
+ return X_ERROR_DEVICE_NOT_CONNECTED;
+ }
+
+ // TODO(benvanik): confirm with a real XInput controller.
+ out_caps->type = 0x01; // XINPUT_DEVTYPE_GAMEPAD
+ out_caps->sub_type = 0x01; // XINPUT_DEVSUBTYPE_GAMEPAD
+ out_caps->flags = 0;
+ out_caps->gamepad.buttons = 0xFFFF;
+ out_caps->gamepad.left_trigger = 0xFF;
+ out_caps->gamepad.right_trigger = 0xFF;
+ out_caps->gamepad.thumb_lx = (int16_t)0xFFFFu;
+ out_caps->gamepad.thumb_ly = (int16_t)0xFFFFu;
+ out_caps->gamepad.thumb_rx = (int16_t)0xFFFFu;
+ out_caps->gamepad.thumb_ry = (int16_t)0xFFFFu;
+ out_caps->vibration.left_motor_speed = 0;
+ out_caps->vibration.right_motor_speed = 0;
+ return X_ERROR_SUCCESS;
+}
+
+X_RESULT KeyboardInputDriver::GetState(uint32_t user_index,
+ X_INPUT_STATE* out_state) {
+ if (user_index != 0) {
+ return X_ERROR_DEVICE_NOT_CONNECTED;
+ }
+
+ packet_number_++;
+
+ uint16_t buttons = 0;
+ uint8_t left_trigger = 0;
+ uint8_t right_trigger = 0;
+ int16_t thumb_lx = 0;
+ int16_t thumb_ly = 0;
+ int16_t thumb_rx = 0;
+ int16_t thumb_ry = 0;
+
+ if (window()->HasFocus() && is_active()) {
+ for (const KeyBinding& b : key_bindings_) {
+ if (b.is_pressed) {
+ 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;
+ default:
+ assert_unhandled_case(b.output_key);
+ }
+ }
+ }
+ }
+
+ out_state->packet_number = packet_number_;
+ out_state->gamepad.buttons = buttons;
+ out_state->gamepad.left_trigger = left_trigger;
+ out_state->gamepad.right_trigger = right_trigger;
+ out_state->gamepad.thumb_lx = thumb_lx;
+ out_state->gamepad.thumb_ly = thumb_ly;
+ out_state->gamepad.thumb_rx = thumb_rx;
+ out_state->gamepad.thumb_ry = thumb_ry;
+
+ return X_ERROR_SUCCESS;
+}
+
+X_RESULT KeyboardInputDriver::SetState(uint32_t user_index,
+ X_INPUT_VIBRATION* vibration) {
+ if (user_index != 0) {
+ return X_ERROR_DEVICE_NOT_CONNECTED;
+ }
+
+ return X_ERROR_SUCCESS;
+}
+
+X_RESULT KeyboardInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
+ X_INPUT_KEYSTROKE* out_keystroke) {
+ if (user_index != 0) {
+ return X_ERROR_DEVICE_NOT_CONNECTED;
+ }
+
+ if (!is_active()) {
+ return X_ERROR_EMPTY;
+ }
+
+ X_RESULT result = X_ERROR_EMPTY;
+
+ ui::VirtualKey xinput_virtual_key = ui::VirtualKey::kNone;
+ uint16_t unicode = 0;
+ uint16_t keystroke_flags = 0;
+ uint8_t hid_code = 0;
+
+ // Pop from the queue.
+ KeyEvent evt;
+ {
+ auto global_lock = global_critical_region_.Acquire();
+ if (key_events_.empty()) {
+ // No keys!
+ return X_ERROR_EMPTY;
+ }
+ evt = key_events_.front();
+ key_events_.pop();
+ }
+
+ for (const KeyBinding& b : key_bindings_) {
+ if (b.input_key == evt.virtual_key && b.uppercase == evt.is_capital) {
+ xinput_virtual_key = b.output_key;
+ }
+ }
+
+ if (xinput_virtual_key != ui::VirtualKey::kNone) {
+ if (evt.transition == true) {
+ keystroke_flags |= 0x0001; // XINPUT_KEYSTROKE_KEYDOWN
+ } else if (evt.transition == false) {
+ keystroke_flags |= 0x0002; // XINPUT_KEYSTROKE_KEYUP
+ }
+
+ if (evt.prev_state == evt.transition) {
+ keystroke_flags |= 0x0004; // XINPUT_KEYSTROKE_REPEAT
+ }
+
+ result = X_ERROR_SUCCESS;
+ }
+
+ out_keystroke->virtual_key = uint16_t(xinput_virtual_key);
+ out_keystroke->unicode = unicode;
+ out_keystroke->flags = keystroke_flags;
+ out_keystroke->user_index = 0;
+ out_keystroke->hid_code = hid_code;
+
+ // X_ERROR_EMPTY if no new keys
+ // X_ERROR_DEVICE_NOT_CONNECTED if no device
+ // X_ERROR_SUCCESS if key
+ return result;
+}
+
+void KeyboardInputDriver::KeyboardWindowInputListener::OnKeyDown(ui::KeyEvent& e) {
+ driver_.OnKey(e, true);
+}
+
+void KeyboardInputDriver::KeyboardWindowInputListener::OnKeyUp(ui::KeyEvent& e) {
+ driver_.OnKey(e, false);
+}
+
+void KeyboardInputDriver::OnKey(ui::KeyEvent& e, bool is_down) {
+ if (!is_active()) {
+ return;
+ }
+
+ KeyEvent key;
+ key.virtual_key = e.virtual_key();
+ key.transition = is_down;
+ key.prev_state = e.prev_state();
+ key.repeat_count = e.repeat_count();
+ key.is_capital = e.is_shift_pressed();
+
+ auto global_lock = global_critical_region_.Acquire();
+ key_events_.push(key);
+ for (auto& key_binding : key_bindings_) {
+ if (key_binding.input_key == key.virtual_key) {
+ if (key_binding.uppercase == e.is_shift_pressed()) {
+ key_binding.is_pressed = is_down;
+ }
+ }
+ }
+}
+
+} // namespace winkey
+} // namespace hid
+} // namespace xe
diff --git a/src/xenia/hid/keyboard/keyboard_input_driver.h b/src/xenia/hid/keyboard/keyboard_input_driver.h
new file mode 100644
index 0000000000..4f07b15ed0
--- /dev/null
+++ b/src/xenia/hid/keyboard/keyboard_input_driver.h
@@ -0,0 +1,87 @@
+/**
+ ******************************************************************************
+ * 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. *
+ ******************************************************************************
+ */
+
+#ifndef XENIA_HID_KEYBOARD_KEYBOARD_INPUT_DRIVER_H_
+#define XENIA_HID_KEYBOARD_KEYBOARD_INPUT_DRIVER_H_
+
+#include <queue>
+
+#include "xenia/base/mutex.h"
+#include "xenia/hid/input_driver.h"
+#include "xenia/ui/virtual_key.h"
+
+namespace xe {
+namespace hid {
+namespace keyboard {
+
+class KeyboardInputDriver final : public InputDriver {
+ public:
+ explicit KeyboardInputDriver(xe::ui::Window* window, size_t window_z_order);
+ ~KeyboardInputDriver() override;
+
+ X_STATUS Setup() override;
+
+ X_RESULT GetCapabilities(uint32_t user_index, uint32_t flags,
+ X_INPUT_CAPABILITIES* out_caps) override;
+ X_RESULT GetState(uint32_t user_index, X_INPUT_STATE* out_state) override;
+ X_RESULT SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration) override;
+ X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags,
+ X_INPUT_KEYSTROKE* out_keystroke) override;
+
+
+ protected:
+ class KeyboardWindowInputListener final : public ui::WindowInputListener {
+ public:
+ explicit KeyboardWindowInputListener(KeyboardInputDriver& driver)
+ : driver_(driver) {}
+
+ void OnKeyDown(ui::KeyEvent& e) override;
+ void OnKeyUp(ui::KeyEvent& e) override;
+
+ private:
+ KeyboardInputDriver& driver_;
+ };
+
+ struct KeyEvent {
+ ui::VirtualKey virtual_key = ui::VirtualKey::kNone;
+ int repeat_count = 0;
+ bool transition = false; // going up(false) or going down(true)
+ bool prev_state = false; // down(true) or up(false)
+ bool is_capital = false;
+ };
+
+ struct KeyBinding {
+ ui::VirtualKey input_key = ui::VirtualKey::kNone;
+ ui::VirtualKey output_key = ui::VirtualKey::kNone;
+ bool uppercase = false;
+ bool is_pressed = false;
+ };
+
+ void ParseKeyBinding(ui::VirtualKey virtual_key,
+ const std::string_view description,
+ const std::string_view binding);
+
+ void OnKey(ui::KeyEvent& e, bool is_down);
+
+ xe::global_critical_region global_critical_region_;
+
+ std::queue<KeyEvent> key_events_;
+ std::vector<KeyBinding> key_bindings_;
+
+ KeyboardWindowInputListener window_input_listener_;
+
+ uint32_t packet_number_ = 1;
+
+};
+
+} // namespace winkey
+} // namespace hid
+} // namespace xe
+
+#endif // XENIA_HID_WINKEY_WINKEY_INPUT_DRIVER_H_
diff --git a/src/xenia/hid/keyboard/premake5.lua b/src/xenia/hid/keyboard/premake5.lua
new file mode 100644
index 0000000000..1516da5867
--- /dev/null
+++ b/src/xenia/hid/keyboard/premake5.lua
@@ -0,0 +1,16 @@
+project_root = "../../../.."
+include(project_root.."/tools/build")
+
+group("src")
+project("xenia-hid-keyboard")
+ uuid("ab5cfd8d-7877-44cd-9526-63f7c5738609")
+ kind("StaticLib")
+ language("C++")
+ links({
+ "xenia-base",
+ "xenia-hid",
+ "xenia-ui",
+ })
+ defines({
+ })
+ local_platform_files()
diff --git a/src/xenia/hid/premake5.lua b/src/xenia/hid/premake5.lua
index 4e961f6233..6ceb162897 100644
--- a/src/xenia/hid/premake5.lua
+++ b/src/xenia/hid/premake5.lua
@@ -45,6 +45,11 @@ project("xenia-hid-demo")
"xenia-hid-sdl",
})
+ filter("platforms:not Windows")
+ links({
+ "xenia-hid-keyboard"
+ })
+
filter("platforms:Linux")
links({
"SDL2",