mirror of
https://github.com/izzy2lost/xenia-edge.git
synced 2026-07-06 00:20:26 -07:00
[HID] Add a generic keyboard input driver for non-Windows builds
This commit is contained in:
committed by
Herman S.
parent
89b81da8df
commit
d659fcd8b7
@@ -58,4 +58,6 @@ if(WIN32)
|
||||
add_subdirectory(hid/winkey)
|
||||
add_subdirectory(hid/xinput)
|
||||
add_subdirectory(ui/d3d12)
|
||||
else()
|
||||
add_subdirectory(hid/keyboard)
|
||||
endif()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user