diff --git a/premake5.lua b/premake5.lua index 982f61bad..e5158612d 100644 --- a/premake5.lua +++ b/premake5.lua @@ -239,6 +239,7 @@ solution("xenia") include("src/xenia/gpu/vulkan") include("src/xenia/hid") include("src/xenia/hid/nop") + include("src/xenia/hid/sdl") include("src/xenia/kernel") include("src/xenia/ui") include("src/xenia/ui/spirv") diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index 00285820e..e5fd4ba8f 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -34,6 +34,7 @@ project("xenia-app") "xenia-gpu-vulkan", "xenia-hid", "xenia-hid-nop", + "xenia-hid-sdl", "xenia-kernel", "xenia-ui", "xenia-ui-spirv", diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index f2c58ffdc..bcd76d553 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2019 Ben Vanik. All rights reserved. * + * Copyright 2020 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -37,6 +37,7 @@ // Available input drivers: #include "xenia/hid/nop/nop_hid.h" +#include "xenia/hid/sdl/sdl_hid.h" #if XE_PLATFORM_WIN32 #include "xenia/hid/winkey/winkey_hid.h" #include "xenia/hid/xinput/xinput_hid.h" @@ -47,7 +48,7 @@ DEFINE_string(apu, "any", "Audio system. Use: [any, nop, xaudio2]", "APU"); DEFINE_string(gpu, "any", "Graphics system. Use: [any, d3d12, vulkan, vk, null]", "GPU"); -DEFINE_string(hid, "any", "Input system. Use: [any, nop, winkey, xinput]", +DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]", "HID"); DEFINE_bool(fullscreen, false, "Toggles fullscreen", "GPU"); @@ -179,6 +180,7 @@ std::vector> CreateInputDrivers( // WinKey input driver should always be the last input driver added! factory.Add("winkey", xe::hid::winkey::Create); #endif // XE_PLATFORM_WIN32 + factory.Add("sdl", xe::hid::sdl::Create); for (auto& driver : factory.CreateAll(cvars::hid, window)) { if (XSUCCEEDED(driver->Setup())) { drivers.emplace_back(std::move(driver)); diff --git a/src/xenia/hid/hid_demo.cc b/src/xenia/hid/hid_demo.cc index 5f4b740b9..74c894b01 100644 --- a/src/xenia/hid/hid_demo.cc +++ b/src/xenia/hid/hid_demo.cc @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2019 Ben Vanik. All rights reserved. * + * Copyright 2020 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -22,12 +22,13 @@ // Available input drivers: #include "xenia/hid/nop/nop_hid.h" +#include "xenia/hid/sdl/sdl_hid.h" #if XE_PLATFORM_WIN32 #include "xenia/hid/winkey/winkey_hid.h" #include "xenia/hid/xinput/xinput_hid.h" #endif // XE_PLATFORM_WIN32 -DEFINE_string(hid, "any", "Input system. Use: [any, nop, winkey, xinput]", +DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]", "General"); namespace xe { @@ -40,6 +41,11 @@ std::vector> CreateInputDrivers( std::vector> drivers; if (cvars::hid.compare("nop") == 0) { drivers.emplace_back(xe::hid::nop::Create(window)); + } else if (cvars::hid.compare("sdl") == 0) { + auto driver = xe::hid::sdl::Create(window); + if (XSUCCEEDED(driver->Setup())) { + drivers.emplace_back(std::move(driver)); + } #if XE_PLATFORM_WIN32 } else if (cvars::hid.compare("winkey") == 0) { auto driver = xe::hid::winkey::Create(window); @@ -53,6 +59,10 @@ std::vector> CreateInputDrivers( } #endif // XE_PLATFORM_WIN32 } else { + auto sdl_driver = xe::hid::sdl::Create(window); + if (sdl_driver && XSUCCEEDED(sdl_driver->Setup())) { + drivers.emplace_back(std::move(sdl_driver)); + } #if XE_PLATFORM_WIN32 auto xinput_driver = xe::hid::xinput::Create(window); if (xinput_driver && XSUCCEEDED(xinput_driver->Setup())) { diff --git a/src/xenia/hid/premake5.lua b/src/xenia/hid/premake5.lua index 891fccb56..d7f7b8066 100644 --- a/src/xenia/hid/premake5.lua +++ b/src/xenia/hid/premake5.lua @@ -25,6 +25,7 @@ project("xenia-hid-demo") "xenia-base", "xenia-hid", "xenia-hid-nop", + "xenia-hid-sdl", "xenia-ui", "xenia-ui-vulkan", }) @@ -42,6 +43,7 @@ project("xenia-hid-demo") "xcb", "X11-xcb", "vulkan", + "SDL2", }) filter("platforms:Windows") diff --git a/src/xenia/hid/sdl/premake5.lua b/src/xenia/hid/sdl/premake5.lua new file mode 100644 index 000000000..07fba14a7 --- /dev/null +++ b/src/xenia/hid/sdl/premake5.lua @@ -0,0 +1,30 @@ +project_root = "../../../.." +include(project_root.."/tools/build") + +group("src") +project("xenia-hid-sdl") + uuid("44f5b9a1-00f8-4825-acf1-5c93f26eba9b") + kind("StaticLib") + language("C++") + links({ + "xenia-base", + "xenia-hid", + "xenia-ui", + "SDL2", + }) + defines({ + }) + local_platform_files() + + filter("platforms:Windows") + -- On linux we build against the system version (libsdl2-dev) + includedirs({ + project_root.."/third_party/SDL2-devel-VC/include/", + }) + libdirs({ + project_root.."/third_party/SDL2-devel-VC/lib/x64/", + }) + -- Copy the dll to the output folder + postbuildcommands({ + "{COPY} %{prj.basedir}/"..project_root.."/third_party/SDL2-devel-VC/lib/x64/SDL2.dll %{cfg.targetdir}", + }) diff --git a/src/xenia/hid/sdl/sdl_hid.cc b/src/xenia/hid/sdl/sdl_hid.cc new file mode 100644 index 000000000..336423ae2 --- /dev/null +++ b/src/xenia/hid/sdl/sdl_hid.cc @@ -0,0 +1,24 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2020 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/hid/sdl/sdl_hid.h" + +#include "xenia/hid/sdl/sdl_input_driver.h" + +namespace xe { +namespace hid { +namespace sdl { + +std::unique_ptr Create(xe::ui::Window* window) { + return std::make_unique(window); +} + +} // namespace sdl +} // namespace hid +} // namespace xe diff --git a/src/xenia/hid/sdl/sdl_hid.h b/src/xenia/hid/sdl/sdl_hid.h new file mode 100644 index 000000000..ba02f0d6c --- /dev/null +++ b/src/xenia/hid/sdl/sdl_hid.h @@ -0,0 +1,27 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2020 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_HID_SDL_SDL_HID_H_ +#define XENIA_HID_SDL_SDL_HID_H_ + +#include + +#include "xenia/hid/input_system.h" + +namespace xe { +namespace hid { +namespace sdl { + +std::unique_ptr Create(xe::ui::Window* window); + +} // namespace sdl +} // namespace hid +} // namespace xe + +#endif // XENIA_HID_SDL_SDL_HID_H_ diff --git a/src/xenia/hid/sdl/sdl_input_driver.cc b/src/xenia/hid/sdl/sdl_input_driver.cc new file mode 100644 index 000000000..c73151c1c --- /dev/null +++ b/src/xenia/hid/sdl/sdl_input_driver.cc @@ -0,0 +1,412 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2020 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/hid/sdl/sdl_input_driver.h" + +#if XE_PLATFORM_WIN32 +#include "xenia/base/platform_win.h" +#endif // XE_PLATFORM_WIN32 + +#include "xenia/base/cvar.h" +#include "xenia/ui/window.h" + +// TODO(joellinn) make this path relative to the config folder. +DEFINE_string(mappings_file, "gamecontrollerdb.txt", + "Filename of a database with custom game controller mappings.", + "SDL"); + +namespace xe { +namespace hid { +namespace sdl { + +SDLInputDriver::SDLInputDriver(xe::ui::Window* window) + : InputDriver(window), + sdl_events_initialized_(false), + sdl_gamecontroller_initialized_(false), + sdl_events_unflushed_(0), + sdl_pumpevents_queued_(false), + controllers_(), + controllers_mutex_() {} + +SDLInputDriver::~SDLInputDriver() { + for (size_t i = 0; i < controllers_.size(); i++) { + if (controllers_.at(i).sdl) { + SDL_GameControllerClose(controllers_.at(i).sdl); + controllers_.at(i) = {}; + } + } + if (sdl_events_initialized_) { + SDL_QuitSubSystem(SDL_INIT_EVENTS); + sdl_events_initialized_ = false; + } + if (sdl_gamecontroller_initialized_) { + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + sdl_gamecontroller_initialized_ = false; + } +} + +X_STATUS SDLInputDriver::Setup() { + if (!TestSDLVersion()) { + return X_STATUS_UNSUCCESSFUL; + } + + // SDL_PumpEvents should only be run in the thread that initialized SDL - we + // are hijacking the window loop thread for that. + window_->loop()->PostSynchronous([&]() { + // Initialize the event system early, so we catch device events for already + // connected controllers. + if (SDL_InitSubSystem(SDL_INIT_EVENTS) < 0) { + return; + } + sdl_events_initialized_ = true; + + SDL_EventFilter event_filter{[](void* userdata, SDL_Event* event) -> int { + if (!userdata) { + assert_always(); + return 0; + } + + // Event queue should never be (this) full + assert(SDL_PeepEvents(nullptr, 0, SDL_PEEKEVENT, SDL_FIRSTEVENT, + SDL_LASTEVENT) < 0xFFFF); + + const auto type = event->type; + if (type < SDL_JOYAXISMOTION || type > SDL_CONTROLLERDEVICEREMAPPED) { + return 0; + } + + // If another part of xenia uses another SDL subsystem that generates + // events, this may seem like a bad idea. They will however not subscribe + // to controller events so we get away with that. + const auto driver = static_cast(userdata); + // The queue could grow up to 3.5MB since it is never polled. + if (++driver->sdl_events_unflushed_ > 64) { + SDL_FlushEvents(SDL_JOYAXISMOTION, SDL_CONTROLLERDEVICEREMAPPED); + driver->sdl_events_unflushed_ = 0; + } + switch (type) { + case SDL_CONTROLLERDEVICEADDED: + driver->OnControllerDeviceAdded(event); + break; + case SDL_CONTROLLERDEVICEREMOVED: + driver->OnControllerDeviceRemoved(event); + break; + case SDL_CONTROLLERAXISMOTION: + driver->OnControllerDeviceAxisMotion(event); + break; + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + driver->OnControllerDeviceButtonChanged(event); + break; + default: + break; + } + return 0; + }}; + // With an event watch we will always get notified, even if the event queue + // is full, which can happen if another subsystem does not clear its events. + SDL_AddEventWatch(event_filter, this); + + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) { + return; + } + sdl_gamecontroller_initialized_ = true; + + SDL_GameControllerAddMappingsFromFile(cvars::mappings_file.c_str()); + }); + return sdl_events_initialized_ && sdl_gamecontroller_initialized_; +} + +X_RESULT SDLInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags, + X_INPUT_CAPABILITIES* out_caps) { + assert(sdl_events_initialized_ && sdl_gamecontroller_initialized_); + + QueueControllerUpdate(); + + std::unique_lock guard(controllers_mutex_); + + auto controller = GetControllerState(user_index); + if (!controller) { + return X_ERROR_DEVICE_NOT_CONNECTED; + } + + out_caps->type = 0x01; // XINPUT_DEVTYPE_GAMEPAD + out_caps->sub_type = 0x01; // XINPUT_DEVSUBTYPE_GAMEPAD + out_caps->flags = 0; + out_caps->gamepad.buttons = 0xF3FF; + out_caps->gamepad.left_trigger = 0xFF; + out_caps->gamepad.right_trigger = 0xFF; + out_caps->gamepad.thumb_lx = static_cast(0xFFFFu); + out_caps->gamepad.thumb_ly = static_cast(0xFFFFu); + out_caps->gamepad.thumb_rx = static_cast(0xFFFFu); + out_caps->gamepad.thumb_ry = static_cast(0xFFFFu); + out_caps->vibration.left_motor_speed = 0xFFFFu; + out_caps->vibration.right_motor_speed = 0xFFFFu; + return X_ERROR_SUCCESS; +} + +X_RESULT SDLInputDriver::GetState(uint32_t user_index, + X_INPUT_STATE* out_state) { + assert(sdl_events_initialized_ && sdl_gamecontroller_initialized_); + + QueueControllerUpdate(); + + std::unique_lock guard(controllers_mutex_); + + auto controller = GetControllerState(user_index); + if (!controller) { + return X_ERROR_DEVICE_NOT_CONNECTED; + } + + // Make sure packet_number is only incremented by 1, even if there have been + // multiple updates between GetState calls. + if (controller->state_changed) { + controller->state.packet_number++; + controller->state_changed = false; + } + *out_state = controller->state; + return X_ERROR_SUCCESS; +} + +X_RESULT SDLInputDriver::SetState(uint32_t user_index, + X_INPUT_VIBRATION* vibration) { + assert(sdl_events_initialized_ && sdl_gamecontroller_initialized_); + + QueueControllerUpdate(); + + std::unique_lock guard(controllers_mutex_); + + auto controller = GetControllerState(user_index); + if (!controller) { + return X_ERROR_DEVICE_NOT_CONNECTED; + } + +#if SDL_VERSION_ATLEAST(2, 0, 9) + if (SDL_GameControllerRumble(controller->sdl, vibration->left_motor_speed, + vibration->right_motor_speed, 0)) { + return X_ERROR_FUNCTION_FAILED; + } else { + return X_ERROR_SUCCESS; + } +#else + return X_ERROR_SUCCESS; +#endif +} + +X_RESULT SDLInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags, + X_INPUT_KEYSTROKE* out_keystroke) { + // TODO(joellinn) translate keyboard events for chatpad emulation. + return X_ERROR_EMPTY; +} + +void SDLInputDriver::OnControllerDeviceAdded(SDL_Event* event) { + assert(window_->loop()->is_on_loop_thread()); + std::unique_lock guard(controllers_mutex_); + + // Open the controller. + const auto controller = SDL_GameControllerOpen(event->cdevice.which); + if (!controller) { + assert_always(); + return; + } + int user_id = -1; +#if SDL_VERSION_ATLEAST(2, 0, 9) + // Check if the controller has a player index LED. + user_id = SDL_GameControllerGetPlayerIndex(controller); + // Is that id already taken? + if (user_id < 0 || user_id >= controllers_.size() || + controllers_.at(user_id).sdl) { + user_id = -1; + } +#endif + // No player index or already taken, just take the first free slot. + if (user_id < 0) { + for (size_t i = 0; i < controllers_.size(); i++) { + if (!controllers_.at(i).sdl) { + user_id = static_cast(i); + break; + } + } + } + if (user_id >= 0) { + controllers_.at(user_id) = {controller, {}}; + // XInput seems to start with packet_number = 1 . + controllers_.at(user_id).state_changed = true; + } else { + // No more controllers needed, close it. + SDL_GameControllerClose(controller); + } +} + +void SDLInputDriver::OnControllerDeviceRemoved(SDL_Event* event) { + assert(window_->loop()->is_on_loop_thread()); + std::unique_lock guard(controllers_mutex_); + + // Find the disconnected gamecontroller and close it. + bool found; + size_t i; + std::tie(found, i) = GetControllerIndexFromInstanceID(event->cdevice.which); + assert(found); + SDL_GameControllerClose(controllers_.at(i).sdl); + controllers_.at(i) = {}; +} + +void SDLInputDriver::OnControllerDeviceAxisMotion(SDL_Event* event) { + assert(window_->loop()->is_on_loop_thread()); + std::unique_lock guard(controllers_mutex_); + + bool found; + size_t i; + std::tie(found, i) = GetControllerIndexFromInstanceID(event->caxis.which); + assert(found); + const auto pad = &controllers_.at(i).state.gamepad; + switch (event->caxis.axis) { + case SDL_CONTROLLER_AXIS_LEFTX: + pad->thumb_lx = event->caxis.value; + break; + case SDL_CONTROLLER_AXIS_LEFTY: + pad->thumb_ly = ~event->caxis.value; + break; + case SDL_CONTROLLER_AXIS_RIGHTX: + pad->thumb_rx = event->caxis.value; + break; + case SDL_CONTROLLER_AXIS_RIGHTY: + pad->thumb_ry = ~event->caxis.value; + break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + pad->left_trigger = static_cast(event->caxis.value >> 7); + break; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + pad->right_trigger = static_cast(event->caxis.value >> 7); + break; + default: + assert_always(); + break; + } + controllers_.at(i).state_changed = true; +} + +void SDLInputDriver::OnControllerDeviceButtonChanged(SDL_Event* event) { + assert(window_->loop()->is_on_loop_thread()); + std::unique_lock guard(controllers_mutex_); + + // 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 + xbutton_lookup = {X_INPUT_GAMEPAD_A, + X_INPUT_GAMEPAD_B, + X_INPUT_GAMEPAD_X, + X_INPUT_GAMEPAD_Y, + X_INPUT_GAMEPAD_BACK, + 0 /* Guide button */, + X_INPUT_GAMEPAD_START, + X_INPUT_GAMEPAD_LEFT_THUMB, + X_INPUT_GAMEPAD_RIGHT_THUMB, + X_INPUT_GAMEPAD_LEFT_SHOULDER, + X_INPUT_GAMEPAD_RIGHT_SHOULDER, + X_INPUT_GAMEPAD_DPAD_UP, + X_INPUT_GAMEPAD_DPAD_DOWN, + X_INPUT_GAMEPAD_DPAD_LEFT, + X_INPUT_GAMEPAD_DPAD_RIGHT}; + + bool found; + size_t i; + std::tie(found, i) = GetControllerIndexFromInstanceID(event->cbutton.which); + assert(found); + const auto controller = &controllers_.at(i); + + uint16_t xbuttons = controller->state.gamepad.buttons; + // Lookup the XInput button code. + auto xbutton = xbutton_lookup.at(event->cbutton.button); + // Pressed or released? + if (event->cbutton.state == SDL_PRESSED) { + xbuttons |= xbutton; + } else { + xbuttons &= ~xbutton; + } + controller->state.gamepad.buttons = xbuttons; + controller->state_changed = true; +} + +std::pair SDLInputDriver::GetControllerIndexFromInstanceID( + SDL_JoystickID instance_id) { + // Loop through our controllers and try to match the given ID. + for (size_t i = 0; i < controllers_.size(); i++) { + auto controller = controllers_.at(i).sdl; + if (!controller) { + continue; + } + auto joystick = SDL_GameControllerGetJoystick(controller); + assert(joystick); + auto joy_instance_id = SDL_JoystickInstanceID(joystick); + assert(joy_instance_id >= 0); + if (joy_instance_id == instance_id) { + return {true, i}; + } + } + return {false, 0}; +} + +SDLInputDriver::ControllerState* SDLInputDriver::GetControllerState( + uint32_t user_index) { + if (user_index >= controllers_.size()) { + return nullptr; + } + auto controller = &controllers_.at(user_index); + if (!controller->sdl) { + return nullptr; + } + return controller; +} + +bool SDLInputDriver::TestSDLVersion() { +#if SDL_VERSION_ATLEAST(2, 0, 9) + // SDL 2.0.9 or newer is required for simple rumble support and player + // index. + const Uint8 min_patchlevel = 9; +#else + // SDL 2.0.4 or newer is required to read game controller mappings from + // file. + const Uint8 min_patchlevel = 4; +#endif + + // With msvc delayed loading, exceptions are used to determine dll presence. +#if XE_PLATFORM_WIN32 + __try { +#endif // XE_PLATFORM_WIN32 + SDL_version ver = {}; + SDL_GetVersion(&ver); + if ((ver.major < 2) || + (ver.major == 2 && ver.minor == 0 && ver.patch < min_patchlevel)) { + return false; + } +#if XE_PLATFORM_WIN32 + } __except (EXCEPTION_EXECUTE_HANDLER) { + return false; + } +#endif // XE_PLATFORM_WIN32 + return true; +} + +void SDLInputDriver::QueueControllerUpdate() { + // To minimize consecutive event pumps do not queue before previous pump is + // finished. + bool is_queued = false; + sdl_pumpevents_queued_.compare_exchange_strong(is_queued, true); + if (!is_queued) { + window_->loop()->Post([this]() { + SDL_PumpEvents(); + sdl_pumpevents_queued_ = false; + }); + } +} + +} // 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 new file mode 100644 index 000000000..bfa16c995 --- /dev/null +++ b/src/xenia/hid/sdl/sdl_input_driver.h @@ -0,0 +1,72 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2020 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_HID_SDL_SDL_INPUT_DRIVER_H_ +#define XENIA_HID_SDL_SDL_INPUT_DRIVER_H_ + +#include + +#include +#include +#include + +#include "xenia/hid/input_driver.h" + +#define HID_SDL_USER_COUNT (4) + +namespace xe { +namespace hid { +namespace sdl { + +class SDLInputDriver : public InputDriver { + public: + explicit SDLInputDriver(xe::ui::Window* window); + ~SDLInputDriver() 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; + + protected: + struct ControllerState { + SDL_GameController* sdl; + bool state_changed; + X_INPUT_STATE state; + }; + + protected: + void OnControllerDeviceAdded(SDL_Event* event); + void OnControllerDeviceRemoved(SDL_Event* event); + void OnControllerDeviceAxisMotion(SDL_Event* event); + void OnControllerDeviceButtonChanged(SDL_Event* event); + std::pair GetControllerIndexFromInstanceID( + SDL_JoystickID instance_id); + ControllerState* GetControllerState(uint32_t user_index); + bool TestSDLVersion(); + void QueueControllerUpdate(); + + protected: + bool sdl_events_initialized_; + bool sdl_gamecontroller_initialized_; + int sdl_events_unflushed_; + std::atomic sdl_pumpevents_queued_; + std::array controllers_; + std::mutex controllers_mutex_; +}; + +} // namespace sdl +} // namespace hid +} // namespace xe + +#endif // XENIA_HID_SDL_SDL_INPUT_DRIVER_H_