[LINUX/HID] Added a generic keyboard input driver.

This commit is contained in:
nikolay-kyosev 2025-01-29 13:23:56 +01:00
parent 4cc074df63
commit 7e8e8164ed
10 changed files with 564 additions and 0 deletions

View File

@ -327,6 +327,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")

View File

@ -97,6 +97,10 @@ project("xenia-app")
"xenia-hid-sdl",
})
filter("platforms:not Windows")
links({
"xenia-hid-keyboard"
})
filter("platforms:Linux")
links({
"X11",

View File

@ -56,6 +56,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"
@ -388,6 +391,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);

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())) {

View File

@ -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

View File

@ -0,0 +1,26 @@
/**
******************************************************************************
* 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 keyboard
} // namespace hid
} // namespace xe

View File

@ -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 keyboard
} // namespace hid
} // namespace xe
#endif // XENIA_HID_WINKEY_WINKEY_HID_H_

View File

@ -0,0 +1,340 @@
/**
******************************************************************************
* 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.Key")
#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(
"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 (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 |= 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;
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);
bool found = false;
for (auto& key_binding : key_bindings_) {
if (key_binding.input_key == key.virtual_key) {
found = true;
if (key_binding.uppercase == e.is_shift_pressed()) {
key_binding.is_pressed = is_down;
}
}
}
}
InputType KeyboardInputDriver::GetInputType() const {
return InputType::Controller;
}
} // namespace keyboard
} // namespace hid
} // namespace xe

View File

@ -0,0 +1,86 @@
/**
******************************************************************************
* 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;
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;
};
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_WINKEY_WINKEY_INPUT_DRIVER_H_

View File

@ -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()