[HID] Add a generic keyboard input driver for non-Windows builds

This commit is contained in:
nikolay-kyosev
2026-04-27 00:57:10 +09:00
committed by Herman S.
parent 89b81da8df
commit d659fcd8b7
11 changed files with 665 additions and 8 deletions
+2
View File
@@ -58,4 +58,6 @@ if(WIN32)
add_subdirectory(hid/winkey)
add_subdirectory(hid/xinput)
add_subdirectory(ui/d3d12)
else()
add_subdirectory(hid/keyboard)
endif()
+2
View File
@@ -105,6 +105,7 @@ if(WIN32)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_libraries(xenia-app PRIVATE
xenia-apu-alsa
xenia-hid-keyboard
X11
xcb
X11-xcb
@@ -113,6 +114,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
elseif(APPLE)
target_link_libraries(xenia-app PRIVATE
xenia-gpu-metal
xenia-hid-keyboard
xenia-ui-metal
)
endif()
+28 -8
View File
@@ -63,6 +63,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"
@@ -301,7 +304,19 @@ class EmulatorApp final : public xe::ui::WindowedApp {
Args... args) {
std::vector<std::unique_ptr<T>> instances;
if (name != "winkey") {
// Drivers that are always loaded as a keyboard fallback alongside
// whatever the user selected via `name`. winkey on Windows; keyboard
// on Linux/macOS. Only one is registered on any given build.
static constexpr std::string_view kAlwaysOn[] = {"winkey", "keyboard"};
auto is_always_on = [&](std::string_view n) {
for (auto a : kAlwaysOn) {
if (n == a) return true;
}
return false;
};
if (!is_always_on(name)) {
auto it = std::find_if(
creators_.cbegin(), creators_.cend(),
[&name](const auto& f) { return name.compare(f.name) == 0; });
@@ -314,13 +329,15 @@ class EmulatorApp final : public xe::ui::WindowedApp {
}
}
auto it = std::find_if(
creators_.cbegin(), creators_.cend(),
[&name](const auto& f) { return f.name.compare("winkey") == 0; });
if (it != creators_.cend() && (*it).is_available()) {
auto instance = (*it).instantiate(std::forward<Args>(args)...);
if (instance) {
instances.emplace_back(std::move(instance));
for (auto always : kAlwaysOn) {
auto it = std::find_if(
creators_.cbegin(), creators_.cend(),
[&always](const auto& f) { return f.name.compare(always) == 0; });
if (it != creators_.cend() && (*it).is_available()) {
auto instance = (*it).instantiate(std::forward<Args>(args)...);
if (instance) {
instances.emplace_back(std::move(instance));
}
}
}
return instances;
@@ -530,6 +547,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);
+16
View File
@@ -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())) {
+4
View File
@@ -0,0 +1,4 @@
add_library(xenia-hid-keyboard STATIC)
xe_platform_sources(xenia-hid-keyboard ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(xenia-hid-keyboard PUBLIC xenia-base xenia-hid xenia-ui)
xe_target_defaults(xenia-hid-keyboard)
@@ -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
+26
View File
@@ -0,0 +1,26 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2026 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 keyboard
} // namespace hid
} // namespace xe
+28
View File
@@ -0,0 +1,28 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2026 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 keyboard
} // namespace hid
} // namespace xe
#endif // XENIA_HID_KEYBOARD_KEYBOARD_HID_H_
@@ -0,0 +1,411 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2026 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 <cstring>
#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.Key")
#include "keyboard_binding_table.inc"
#undef XE_HID_KEYBOARD_BINDING
DEFINE_int32(keyboard_mode, 0,
"Allows user do specify keyboard working mode. Possible values: 0 "
"- Disabled, 1 - Enabled, 2 - Passthrough. Passthrough requires "
"controller being connected!",
"HID");
DEFINE_int32(
keyboard_user_index, 0,
"Controller port that keyboard emulates. [0, 3] - Keyboard is assigned to "
"selected slot. Passthrough does not require assigning slot.",
"HID");
namespace xe {
namespace hid {
namespace keyboard {
static bool IsPassthroughEnabled() {
return static_cast<KeyboardMode>(cvars::keyboard_mode) ==
KeyboardMode::Passthrough;
}
static bool IsKeyboardForUserEnabled(uint32_t user_index) {
if (static_cast<KeyboardMode>(cvars::keyboard_mode) !=
KeyboardMode::Enabled) {
return false;
}
return cvars::keyboard_user_index == static_cast<int32_t>(user_index);
}
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(
"keyboard HID: failed to parse binding \"{}\" for controller input "
"{}.",
source_token, description);
continue;
}
key_bindings_.push_back(key_binding);
XELOGI("keyboard HID: \"{}\" binds key 0x{:X} to controller input {}.",
source_token, static_cast<uint16_t>(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 (!IsKeyboardForUserEnabled(user_index) && !IsPassthroughEnabled()) {
return X_ERROR_DEVICE_NOT_CONNECTED;
}
if (IsPassthroughEnabled()) {
out_caps->type = X_INPUT_DEVTYPE::XINPUT_DEVTYPE_KEYBOARD;
out_caps->sub_type = X_INPUT_DEVSUBTYPE::XINPUT_DEVSUBTYPE_USB_KEYBOARD;
return X_ERROR_SUCCESS;
}
out_caps->type = X_INPUT_DEVTYPE::XINPUT_DEVTYPE_GAMEPAD;
out_caps->sub_type = X_INPUT_DEVSUBTYPE::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 (!IsKeyboardForUserEnabled(user_index)) {
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()) {
for (const KeyBinding& b : key_bindings_) {
if (b.is_pressed) {
switch (b.output_key) {
case ui::VirtualKey::kXInputPadA:
buttons |= X_INPUT_GAMEPAD_A;
break;
case ui::VirtualKey::kXInputPadY:
buttons |= X_INPUT_GAMEPAD_Y;
break;
case ui::VirtualKey::kXInputPadB:
buttons |= X_INPUT_GAMEPAD_B;
break;
case ui::VirtualKey::kXInputPadX:
buttons |= X_INPUT_GAMEPAD_X;
break;
case ui::VirtualKey::kXInputPadGuide:
buttons |= X_INPUT_GAMEPAD_GUIDE;
break;
case ui::VirtualKey::kXInputPadDpadLeft:
buttons |= X_INPUT_GAMEPAD_DPAD_LEFT;
break;
case ui::VirtualKey::kXInputPadDpadRight:
buttons |= X_INPUT_GAMEPAD_DPAD_RIGHT;
break;
case ui::VirtualKey::kXInputPadDpadDown:
buttons |= X_INPUT_GAMEPAD_DPAD_DOWN;
break;
case ui::VirtualKey::kXInputPadDpadUp:
buttons |= X_INPUT_GAMEPAD_DPAD_UP;
break;
case ui::VirtualKey::kXInputPadRThumbPress:
buttons |= X_INPUT_GAMEPAD_RIGHT_THUMB;
break;
case ui::VirtualKey::kXInputPadLThumbPress:
buttons |= X_INPUT_GAMEPAD_LEFT_THUMB;
break;
case ui::VirtualKey::kXInputPadBack:
buttons |= X_INPUT_GAMEPAD_BACK;
break;
case ui::VirtualKey::kXInputPadStart:
buttons |= X_INPUT_GAMEPAD_START;
break;
case ui::VirtualKey::kXInputPadLShoulder:
buttons |= X_INPUT_GAMEPAD_LEFT_SHOULDER;
break;
case ui::VirtualKey::kXInputPadRShoulder:
buttons |= X_INPUT_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;
if (IsPassthroughEnabled()) {
std::memset(out_state, 0, sizeof(*out_state));
}
return X_ERROR_SUCCESS;
}
X_RESULT KeyboardInputDriver::SetState(uint32_t user_index,
X_INPUT_VIBRATION* vibration) {
if (!IsKeyboardForUserEnabled(user_index) && !IsPassthroughEnabled()) {
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 (!IsKeyboardForUserEnabled(user_index) && !IsPassthroughEnabled()) {
return X_ERROR_DEVICE_NOT_CONNECTED;
}
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();
}
if (!IsPassthroughEnabled()) {
if (IsKeyboardForUserEnabled(user_index)) {
for (const KeyBinding& b : key_bindings_) {
if (b.input_key == evt.virtual_key && b.uppercase == evt.is_capital) {
xinput_virtual_key = b.output_key;
}
}
}
} else {
// Passthrough: surface the raw key plus modifier flags.
// TODO(has207): plumb unicode (via OnKeyChar text()) and a VirtualKey ->
// HID usage table so passthrough matches winkey's full keystroke shape.
xinput_virtual_key = evt.virtual_key;
if (evt.is_capital) {
keystroke_flags |= 0x0008; // XINPUT_KEYSTROKE_SHIFT
}
if (evt.is_ctrl_pressed) {
keystroke_flags |= 0x0010; // XINPUT_KEYSTROKE_CTRL
}
if (evt.is_alt_pressed) {
keystroke_flags |= 0x0020; // XINPUT_KEYSTROKE_ALT
}
}
if (xinput_virtual_key != ui::VirtualKey::kNone) {
if (evt.transition == true) {
keystroke_flags |= 0x0001; // XINPUT_KEYSTROKE_KEYDOWN
if (evt.prev_state == evt.transition) {
keystroke_flags |= 0x0004; // XINPUT_KEYSTROKE_REPEAT
}
} else if (evt.transition == false) {
keystroke_flags |= 0x0002; // XINPUT_KEYSTROKE_KEYUP
}
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 = user_index;
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 (static_cast<KeyboardMode>(cvars::keyboard_mode) ==
KeyboardMode::Disabled) {
return;
}
auto global_lock = global_critical_region_.Acquire();
const bool is_capital = e.is_shift_pressed();
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 = is_capital;
key.is_ctrl_pressed = e.is_ctrl_pressed();
key.is_alt_pressed = e.is_alt_pressed();
key_events_.push(key);
for (auto& key_binding : key_bindings_) {
if (key_binding.input_key != key.virtual_key) {
continue;
}
if (is_down) {
if (key_binding.uppercase == is_capital) {
key_binding.is_pressed = true;
}
} else {
// Clear all variants on release so a shift/caps change mid-press
// can't leave a binding stuck.
key_binding.is_pressed = false;
}
}
}
InputType KeyboardInputDriver::GetInputType() const {
switch (static_cast<KeyboardMode>(cvars::keyboard_mode)) {
case KeyboardMode::Disabled:
return InputType::None;
case KeyboardMode::Enabled:
return InputType::Controller;
case KeyboardMode::Passthrough:
return InputType::Keyboard;
default:
break;
}
return InputType::Controller;
}
} // namespace keyboard
} // namespace hid
} // namespace xe
@@ -0,0 +1,90 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2026 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 {
enum class KeyboardMode { Disabled, Enabled, Passthrough };
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;
virtual InputType GetInputType() const 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; // shift held at event time
bool is_ctrl_pressed = false;
bool is_alt_pressed = 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 keyboard
} // namespace hid
} // namespace xe
#endif // XENIA_HID_KEYBOARD_KEYBOARD_INPUT_DRIVER_H_
+20
View File
@@ -840,6 +840,26 @@ VirtualKey QtWindow::TranslateVirtualKey(int qt_key) {
return VirtualKey::kAdd;
case Qt::Key_Minus:
return VirtualKey::kSubtract;
case Qt::Key_Semicolon:
return VirtualKey::kOem1; // ;: (US)
case Qt::Key_Equal:
return VirtualKey::kOemPlus;
case Qt::Key_Comma:
return VirtualKey::kOemComma;
case Qt::Key_Period:
return VirtualKey::kOemPeriod;
case Qt::Key_Slash:
return VirtualKey::kOem2; // /? (US)
case Qt::Key_QuoteLeft:
return VirtualKey::kOem3; // `~ (US)
case Qt::Key_BracketLeft:
return VirtualKey::kOem4; // [{ (US)
case Qt::Key_Backslash:
return VirtualKey::kOem5; // \| (US)
case Qt::Key_BracketRight:
return VirtualKey::kOem6; // ]} (US)
case Qt::Key_Apostrophe:
return VirtualKey::kOem7; // '" (US)
default:
return VirtualKey::kNone;
}