Files
Xenia-Canary/src/xenia/hid/winkey/winkey_input_driver.cc
Gliniak 11f14e8488 [HID] Fixed spam caused by incorrect controller visibility
- Little cleanup in HID related code
2024-12-21 19:51:01 +01:00

393 lines
13 KiB
C++

/**
******************************************************************************
* 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/winkey/winkey_input_driver.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform_win.h"
#include "xenia/hid/hid_flags.h"
#include "xenia/hid/input_system.h"
#include "xenia/ui/virtual_key.h"
#include "xenia/ui/window.h"
#define XE_HID_WINKEY_BINDING(button, description, cvar_name, \
cvar_default_value) \
DEFINE_string(cvar_name, cvar_default_value, \
"List of keys to bind to " description \
", separated by spaces", \
"HID.WinKey")
#include "winkey_binding_table.inc"
#undef XE_HID_WINKEY_BINDING
DEFINE_int32(keyboard_mode, 1,
"Allows user do specify keyboard working mode. Possible values: 0 "
"- Disabled, 1 - Enabled, 2 - Passthrough",
"HID");
DEFINE_int32(keyboard_user_index, 0,
"Controller port that keyboard emulates. -1 - Keyboard usage "
"disabled, [0, 3] - Keyboard is assigned to selected slot.",
"HID");
namespace xe {
namespace hid {
namespace winkey {
bool static IsPassthroughEnabled() {
return static_cast<KeyboardMode>(cvars::keyboard_mode) ==
KeyboardMode::Passthrough;
}
bool static IsKeyboardForUserEnabled(uint32_t user_index) {
if (static_cast<KeyboardMode>(cvars::keyboard_mode) !=
KeyboardMode::Enabled) {
return false;
}
return cvars::keyboard_user_index == user_index;
}
bool __inline IsKeyToggled(uint8_t key) {
return (GetKeyState(key) & 0x1) == 0x1;
}
bool __inline IsKeyDown(uint8_t key) {
return (GetAsyncKeyState(key) & 0x8000) == 0x8000;
}
bool __inline IsKeyDown(ui::VirtualKey virtual_key) {
return IsKeyDown(static_cast<uint8_t>(virtual_key));
}
void WinKeyInputDriver::ParseKeyBinding(ui::VirtualKey output_key,
const std::string_view description,
const std::string_view source_tokens) {
for (const std::string_view source_token :
utf8::split(source_tokens, " ", true)) {
KeyBinding key_binding;
key_binding.output_key = output_key;
std::string_view token = source_token;
if (utf8::starts_with(token, "_")) {
key_binding.lowercase = true;
token = token.substr(1);
} else if (utf8::starts_with(token, "^")) {
key_binding.uppercase = true;
token = token.substr(1);
}
if (utf8::starts_with(token, "0x")) {
token = token.substr(2);
key_binding.input_key = static_cast<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, static_cast<uint16_t>(key_binding.input_key),
description);
}
}
WinKeyInputDriver::WinKeyInputDriver(xe::ui::Window* window,
size_t window_z_order)
: InputDriver(window, window_z_order), window_input_listener_(*this) {
#define XE_HID_WINKEY_BINDING(button, description, cvar_name, \
cvar_default_value) \
ParseKeyBinding(xe::ui::VirtualKey::kXInputPad##button, description, \
cvars::cvar_name);
#include "winkey_binding_table.inc"
#undef XE_HID_WINKEY_BINDING
window->AddInputListener(&window_input_listener_, window_z_order);
}
WinKeyInputDriver::~WinKeyInputDriver() {
window()->RemoveInputListener(&window_input_listener_);
}
X_STATUS WinKeyInputDriver::Setup() { return X_STATUS_SUCCESS; }
X_RESULT WinKeyInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
X_INPUT_CAPABILITIES* out_caps) {
if (!IsKeyboardForUserEnabled(user_index)) {
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 WinKeyInputDriver::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() && is_active()) {
bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT);
for (const KeyBinding& b : key_bindings_) {
if (((b.lowercase == b.uppercase) || (b.lowercase && !capital) ||
(b.uppercase && capital)) &&
IsKeyDown(b.input_key)) {
switch (b.output_key) {
case ui::VirtualKey::kXInputPadA:
buttons |= 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;
}
}
}
}
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 WinKeyInputDriver::SetState(uint32_t user_index,
X_INPUT_VIBRATION* vibration) {
if (!IsKeyboardForUserEnabled(user_index)) {
return X_ERROR_DEVICE_NOT_CONNECTED;
}
return X_ERROR_SUCCESS;
}
X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
X_INPUT_KEYSTROKE* out_keystroke) {
if (!is_active()) {
return X_ERROR_DEVICE_NOT_CONNECTED;
}
if (!IsKeyboardForUserEnabled(user_index) && !IsPassthroughEnabled()) {
return X_ERROR_DEVICE_NOT_CONNECTED;
}
// 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();
}
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;
bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT);
if (!IsPassthroughEnabled()) {
if (IsKeyboardForUserEnabled(user_index)) {
for (const KeyBinding& b : key_bindings_) {
if (b.input_key == evt.virtual_key &&
((b.lowercase == b.uppercase) || (b.lowercase && !capital) ||
(b.uppercase && capital))) {
xinput_virtual_key = b.output_key;
}
}
}
} else {
xinput_virtual_key = evt.virtual_key;
if (capital) {
keystroke_flags |= 0x0008; // XINPUT_KEYSTROKE_SHIFT
}
if (IsKeyToggled(VK_CONTROL)) {
keystroke_flags |= 0x0010; // XINPUT_KEYSTROKE_CTRL
}
if (IsKeyToggled(VK_MENU)) {
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
}
if (IsPassthroughEnabled()) {
if (GetKeyboardState(key_map_)) {
WCHAR buf;
if (ToUnicode(uint8_t(xinput_virtual_key), 0, key_map_, &buf, 1, 0) ==
1) {
keystroke_flags |= 0x1000; // XINPUT_KEYSTROKE_VALIDUNICODE
unicode = buf;
}
}
}
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 WinKeyInputDriver::WinKeyWindowInputListener::OnKeyDown(ui::KeyEvent& e) {
driver_.OnKey(e, true);
}
void WinKeyInputDriver::WinKeyWindowInputListener::OnKeyUp(ui::KeyEvent& e) {
driver_.OnKey(e, false);
}
void WinKeyInputDriver::OnKey(ui::KeyEvent& e, bool is_down) {
if (!is_active() || static_cast<KeyboardMode>(cvars::keyboard_mode) ==
KeyboardMode::Disabled) {
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();
auto global_lock = global_critical_region_.Acquire();
key_events_.push(key);
}
} // namespace winkey
} // namespace hid
} // namespace xe