diff --git a/src/xenia/hid/sdl/sdl_input_driver.cc b/src/xenia/hid/sdl/sdl_input_driver.cc index 762caf7bb..fa3057cfd 100644 --- a/src/xenia/hid/sdl/sdl_input_driver.cc +++ b/src/xenia/hid/sdl/sdl_input_driver.cc @@ -9,10 +9,13 @@ #include "xenia/hid/sdl/sdl_input_driver.h" +#include + #if XE_PLATFORM_WIN32 #include "xenia/base/platform_win.h" #endif // XE_PLATFORM_WIN32 +#include "xenia/base/clock.h" #include "xenia/base/cvar.h" #include "xenia/base/logging.h" #include "xenia/hid/hid_flags.h" @@ -34,7 +37,8 @@ SDLInputDriver::SDLInputDriver(xe::ui::Window* window) sdl_events_unflushed_(0), sdl_pumpevents_queued_(false), controllers_(), - controllers_mutex_() {} + controllers_mutex_(), + keystroke_states_() {} SDLInputDriver::~SDLInputDriver() { for (size_t i = 0; i < controllers_.size(); i++) { @@ -150,6 +154,9 @@ X_STATUS SDLInputDriver::Setup() { X_RESULT SDLInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags, X_INPUT_CAPABILITIES* out_caps) { assert(sdl_events_initialized_ && sdl_gamecontroller_initialized_); + if (user_index >= HID_SDL_USER_COUNT) { + return X_ERROR_BAD_ARGUMENTS; + } QueueControllerUpdate(); @@ -179,6 +186,9 @@ X_RESULT SDLInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags, X_RESULT SDLInputDriver::GetState(uint32_t user_index, X_INPUT_STATE* out_state) { assert(sdl_events_initialized_ && sdl_gamecontroller_initialized_); + if (user_index >= HID_SDL_USER_COUNT) { + return X_ERROR_BAD_ARGUMENTS; + } QueueControllerUpdate(); @@ -202,6 +212,9 @@ X_RESULT SDLInputDriver::GetState(uint32_t user_index, X_RESULT SDLInputDriver::SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration) { assert(sdl_events_initialized_ && sdl_gamecontroller_initialized_); + if (user_index >= HID_SDL_USER_COUNT) { + return X_ERROR_BAD_ARGUMENTS; + } QueueControllerUpdate(); @@ -224,9 +237,147 @@ X_RESULT SDLInputDriver::SetState(uint32_t user_index, #endif } -X_RESULT SDLInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags, +X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags, X_INPUT_KEYSTROKE* out_keystroke) { - // TODO(joellinn) translate keyboard events for chatpad emulation. + assert(sdl_events_initialized_ && sdl_gamecontroller_initialized_); + bool user_any = users == 0xFF; + if (users >= HID_SDL_USER_COUNT && !user_any) { + return X_ERROR_BAD_ARGUMENTS; + } + if (!out_keystroke) { + return X_ERROR_BAD_ARGUMENTS; + } + + // The order of this list is also the order in which events are send if + // multiple buttons change at once. + static_assert(sizeof(X_INPUT_GAMEPAD::buttons) == 2); + static const std::array::type, 34> + vk_lookup = { + // 00 - True buttons from xinput button field + X_INPUT_GAMEPAD_VK_DPAD_UP, + X_INPUT_GAMEPAD_VK_DPAD_DOWN, + X_INPUT_GAMEPAD_VK_DPAD_LEFT, + X_INPUT_GAMEPAD_VK_DPAD_RIGHT, + X_INPUT_GAMEPAD_VK_START, + X_INPUT_GAMEPAD_VK_BACK, + X_INPUT_GAMEPAD_VK_LTHUMB_PRESS, + X_INPUT_GAMEPAD_VK_RTHUMB_PRESS, + X_INPUT_GAMEPAD_VK_LSHOULDER, + X_INPUT_GAMEPAD_VK_RSHOULDER, + 0, /* Guide has no VK */ + 0, /* Unknown */ + X_INPUT_GAMEPAD_VK_A, + X_INPUT_GAMEPAD_VK_B, + X_INPUT_GAMEPAD_VK_X, + X_INPUT_GAMEPAD_VK_Y, + // 16 - Fake buttons generated from analog inputs + X_INPUT_GAMEPAD_VK_LTRIGGER, + X_INPUT_GAMEPAD_VK_RTRIGGER, + // 18 + X_INPUT_GAMEPAD_VK_LTHUMB_UP, + X_INPUT_GAMEPAD_VK_LTHUMB_DOWN, + X_INPUT_GAMEPAD_VK_LTHUMB_RIGHT, + X_INPUT_GAMEPAD_VK_LTHUMB_LEFT, + X_INPUT_GAMEPAD_VK_LTHUMB_UPLEFT, + X_INPUT_GAMEPAD_VK_LTHUMB_UPRIGHT, + X_INPUT_GAMEPAD_VK_LTHUMB_DOWNRIGHT, + X_INPUT_GAMEPAD_VK_LTHUMB_DOWNLEFT, + // 26 + X_INPUT_GAMEPAD_VK_RTHUMB_UP, + X_INPUT_GAMEPAD_VK_RTHUMB_DOWN, + X_INPUT_GAMEPAD_VK_RTHUMB_RIGHT, + X_INPUT_GAMEPAD_VK_RTHUMB_LEFT, + X_INPUT_GAMEPAD_VK_RTHUMB_UPLEFT, + X_INPUT_GAMEPAD_VK_RTHUMB_UPRIGHT, + X_INPUT_GAMEPAD_VK_RTHUMB_DOWNRIGHT, + X_INPUT_GAMEPAD_VK_RTHUMB_DOWNLEFT, + }; + + QueueControllerUpdate(); + + std::unique_lock guard(controllers_mutex_); + + for (uint32_t user_index = (user_any ? 0 : users); + user_index < (user_any ? HID_SDL_USER_COUNT : users + 1); user_index++) { + auto controller = GetControllerState(user_index); + if (!controller) { + if (user_any) { + continue; + } else { + return X_ERROR_DEVICE_NOT_CONNECTED; + } + } + + const uint64_t curr_butts = controller->state.gamepad.buttons | + AnalogToKeyfield(controller->state.gamepad); + KeystrokeState& last = keystroke_states_.at(user_index); + + // Handle repeating + auto guest_now = Clock::QueryGuestUptimeMillis(); + static_assert(HID_SDL_REPEAT_DELAY >= HID_SDL_REPEAT_RATE); + if (last.repeat_state == RepeatState::Waiting && + (last.repeat_time + HID_SDL_REPEAT_DELAY < guest_now)) { + last.repeat_state = RepeatState::Repeating; + } + if (last.repeat_state == RepeatState::Repeating && + (last.repeat_time + HID_SDL_REPEAT_RATE < guest_now)) { + last.repeat_time = guest_now; + auto vk = vk_lookup.at(last.repeat_butt_idx); + assert_not_zero(vk); + out_keystroke->virtual_key = vk; + out_keystroke->unicode = 0; + out_keystroke->user_index = user_index; + out_keystroke->hid_code = 0; + out_keystroke->flags = + X_INPUT_KEYSTROKE_KEYDOWN | X_INPUT_KEYSTROKE_REPEAT; + return X_ERROR_SUCCESS; + } + + auto butts_changed = curr_butts ^ last.buttons; + if (!butts_changed) { + continue; + } + + // First try to clear buttons with up events. This is to match xinput + // behaviour when transitioning thumb sticks, e.g. so that THUMB_UPLEFT is + // up before THUMB_LEFT is down. + for (auto [clear_pass, i] = std::tuple{true, 0}; i < 2; + clear_pass = false, i++) { + for (uint8_t i = 0; i < std::size(vk_lookup); i++) { + auto fbutton = uint64_t(1) << i; + if (!(butts_changed & fbutton)) { + continue; + } + auto vk = vk_lookup.at(i); + if (!vk) { + continue; + } + + out_keystroke->virtual_key = vk; + out_keystroke->unicode = 0; + out_keystroke->user_index = user_index; + out_keystroke->hid_code = 0; + + bool is_pressed = curr_butts & fbutton; + if (clear_pass && !is_pressed) { + // up + out_keystroke->flags = X_INPUT_KEYSTROKE_KEYUP; + last.buttons &= ~fbutton; + last.repeat_state = RepeatState::Idle; + return X_ERROR_SUCCESS; + } + if (!clear_pass && is_pressed) { + // down + out_keystroke->flags = X_INPUT_KEYSTROKE_KEYDOWN; + last.buttons |= fbutton; + last.repeat_state = RepeatState::Waiting; + last.repeat_butt_idx = i; + last.repeat_time = guest_now; + return X_ERROR_SUCCESS; + } + } + } + } return X_ERROR_EMPTY; } @@ -278,6 +429,7 @@ void SDLInputDriver::OnControllerDeviceRemoved(SDL_Event* event) { assert(found); SDL_GameControllerClose(controllers_.at(i).sdl); controllers_.at(i) = {}; + keystroke_states_.at(i) = {}; } void SDLInputDriver::OnControllerDeviceAxisMotion(SDL_Event* event) { @@ -319,7 +471,8 @@ void SDLInputDriver::OnControllerDeviceButtonChanged(SDL_Event* event) { // Define a lookup table to map between SDL and XInput button codes. // These need to be in the order of the SDL_GameControllerButton enum. - static constexpr std::array + static const std::array::type, + SDL_CONTROLLER_BUTTON_MAX> xbutton_lookup = {X_INPUT_GAMEPAD_A, X_INPUT_GAMEPAD_B, X_INPUT_GAMEPAD_X, @@ -387,7 +540,7 @@ SDLInputDriver::ControllerState* SDLInputDriver::GetControllerState( return controller; } -bool SDLInputDriver::TestSDLVersion() { +bool SDLInputDriver::TestSDLVersion() const { #if SDL_VERSION_ATLEAST(2, 0, 9) // SDL 2.0.9 or newer is required for simple rumble support and player // index. @@ -420,6 +573,49 @@ void SDLInputDriver::QueueControllerUpdate() { } } +// Check if the analog inputs exceed their thresholds to become a button press +// and build the bitfield. +inline uint64_t SDLInputDriver::AnalogToKeyfield( + const X_INPUT_GAMEPAD& gamepad) const { + uint64_t f = 0; + + f |= static_cast(gamepad.left_trigger > HID_SDL_TRIGG_THRES) << 16; + f |= static_cast(gamepad.right_trigger > HID_SDL_TRIGG_THRES) << 17; + + auto thumb_x = gamepad.thumb_lx; + auto thumb_y = gamepad.thumb_ly; + for (size_t i = 0; i <= 8; i = i + 8) { + uint64_t u = thumb_y > HID_SDL_THUMB_THRES; + uint64_t d = thumb_y < ~HID_SDL_THUMB_THRES; + uint64_t r = thumb_x > HID_SDL_THUMB_THRES; + uint64_t l = thumb_x < ~HID_SDL_THUMB_THRES; + if (u && l) { + u = l = 0; + f |= uint64_t(1) << (22 + i); + } + if (u && r) { + u = r = 0; + f |= uint64_t(1) << (23 + i); + } + if (d && r) { + d = r = 0; + f |= uint64_t(1) << (24 + i); + } + if (d && l) { + d = l = 0; + f |= uint64_t(1) << (25 + i); + } + f |= u << (18 + i); + f |= d << (19 + i); + f |= r << (20 + i); + f |= l << (21 + i); + + thumb_x = gamepad.thumb_rx; + thumb_y = gamepad.thumb_ry; + } + return f; +} + } // namespace sdl } // namespace hid } // namespace xe diff --git a/src/xenia/hid/sdl/sdl_input_driver.h b/src/xenia/hid/sdl/sdl_input_driver.h index 542508084..84555f70d 100644 --- a/src/xenia/hid/sdl/sdl_input_driver.h +++ b/src/xenia/hid/sdl/sdl_input_driver.h @@ -17,7 +17,11 @@ #include "SDL.h" #include "xenia/hid/input_driver.h" -#define HID_SDL_USER_COUNT (4) +#define HID_SDL_USER_COUNT 4 +#define HID_SDL_THUMB_THRES 0x4E00 +#define HID_SDL_TRIGG_THRES 0x1F +#define HID_SDL_REPEAT_DELAY 400 +#define HID_SDL_REPEAT_RATE 100 namespace xe { namespace hid { @@ -44,6 +48,20 @@ class SDLInputDriver : public InputDriver { X_INPUT_STATE state; }; + enum class RepeatState { + Idle, // no buttons pressed or repeating has ended + Waiting, // a button is held and the delay is awaited + Repeating, // actively repeating at a rate + }; + struct KeystrokeState { + uint64_t buttons; + RepeatState repeat_state; + // the button number that was pressed last: + uint8_t repeat_butt_idx; + // the last time (ms) a down (and/or repeat) event for that button was send: + uint32_t repeat_time; + }; + protected: void OnControllerDeviceAdded(SDL_Event* event); void OnControllerDeviceRemoved(SDL_Event* event); @@ -52,8 +70,9 @@ class SDLInputDriver : public InputDriver { std::pair GetControllerIndexFromInstanceID( SDL_JoystickID instance_id); ControllerState* GetControllerState(uint32_t user_index); - bool TestSDLVersion(); + bool TestSDLVersion() const; void QueueControllerUpdate(); + inline uint64_t AnalogToKeyfield(const X_INPUT_GAMEPAD& gamepad) const; protected: bool sdl_events_initialized_; @@ -62,6 +81,7 @@ class SDLInputDriver : public InputDriver { std::atomic sdl_pumpevents_queued_; std::array controllers_; std::mutex controllers_mutex_; + std::array keystroke_states_; }; } // namespace sdl