[HID] Implement GetKeystroke() for SDL

This commit is contained in:
Joel Linn 2020-04-20 17:20:44 +02:00 committed by Rick Gibbed
parent d7d5d92e59
commit 5463798631
2 changed files with 223 additions and 7 deletions

View File

@ -9,10 +9,13 @@
#include "xenia/hid/sdl/sdl_input_driver.h"
#include <array>
#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<std::underlying_type<X_INPUT_GAMEPAD_VK>::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<std::mutex> 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<uint16_t, SDL_CONTROLLER_BUTTON_MAX>
static const std::array<std::underlying_type<X_INPUT_GAMEPAD_BUTTON>::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<uint64_t>(gamepad.left_trigger > HID_SDL_TRIGG_THRES) << 16;
f |= static_cast<uint64_t>(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

View File

@ -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<bool, size_t> 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<bool> sdl_pumpevents_queued_;
std::array<ControllerState, HID_SDL_USER_COUNT> controllers_;
std::mutex controllers_mutex_;
std::array<KeystrokeState, HID_SDL_USER_COUNT> keystroke_states_;
};
} // namespace sdl