[HID] Implement GetKeystroke() for SDL
This commit is contained in:
parent
d7d5d92e59
commit
5463798631
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue