#include "rawinput/xinput.h" #include "util/logging.h" // std::min #ifdef min #undef min #endif namespace xinput { // this is all we need to emulate xinput.h which we avoid including here... #define XINPUT_GAMEPAD_TRIGGER_THRESHOLD (30 / 255.0f) #define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849 #define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 #define XINPUT_GAMEPAD_DPAD_UP 0x0001 #define XINPUT_GAMEPAD_DPAD_DOWN 0x0002 #define XINPUT_GAMEPAD_DPAD_LEFT 0x0004 #define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008 #define XINPUT_GAMEPAD_START 0x0010 #define XINPUT_GAMEPAD_BACK 0x0020 #define XINPUT_GAMEPAD_LEFT_THUMB 0x0040 #define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080 #define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100 #define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200 #define XINPUT_GAMEPAD_A 0x1000 #define XINPUT_GAMEPAD_B 0x2000 #define XINPUT_GAMEPAD_X 0x4000 #define XINPUT_GAMEPAD_Y 0x8000 // custom static constexpr float GAMEPAD_THUMB_DIGITAL_THRESHOLD = 0.2f; typedef struct { uint32_t dwPacketNumber; XINPUT_GAMEPAD_STATE Gamepad; } XINPUT_STATE; DWORD WINAPI XInputGetState( DWORD dwUserIndex, XINPUT_STATE *pState ); typedef struct { uint16_t wLeftMotorSpeed; uint16_t wRightMotorSpeed; } XINPUT_VIBRATION; DWORD WINAPI XInputSetState( DWORD dwUserIndex, XINPUT_VIBRATION* pVibration ); // end xinput definitions std::string get_button_string(XInputButtonEnum button) { switch (button) { case XInputButtonEnum::DPAD_UP: return "Dpad Up"; case XInputButtonEnum::DPAD_DOWN: return "Dpad Down"; case XInputButtonEnum::DPAD_LEFT: return "Dpad Left"; case XInputButtonEnum::DPAD_RIGHT: return "Dpad Right"; case XInputButtonEnum::START: return "Start"; case XInputButtonEnum::BACK: return "Back"; case XInputButtonEnum::LEFT_STICK: return "Left Stick Click"; case XInputButtonEnum::RIGHT_STICK: return "Right Stick Click"; case XInputButtonEnum::LEFT_SHOULDER: return "Left Bumper"; case XInputButtonEnum::RIGHT_SHOULDER: return "Right Bumper"; case XInputButtonEnum::BUTTON_A: return "Button A"; case XInputButtonEnum::BUTTON_B: return "Button B"; case XInputButtonEnum::BUTTON_X: return "Button X"; case XInputButtonEnum::BUTTON_Y: return "Button Y"; case XInputButtonEnum::LEFT_TRIGGER: return "Left Trigger"; case XInputButtonEnum::RIGHT_TRIGGER: return "Right Trigger"; case XInputButtonEnum::LEFT_STICK_UP: return "Left Stick, Up"; case XInputButtonEnum::LEFT_STICK_DOWN: return "Left Stick, Down"; case XInputButtonEnum::LEFT_STICK_LEFT: return "Left Stick, Left"; case XInputButtonEnum::LEFT_STICK_RIGHT: return "Left Stick, Right"; case XInputButtonEnum::RIGHT_STICK_UP: return "Right Stick, Up"; case XInputButtonEnum::RIGHT_STICK_DOWN: return "Right Stick, Down"; case XInputButtonEnum::RIGHT_STICK_LEFT: return "Right Stick, Left"; case XInputButtonEnum::RIGHT_STICK_RIGHT: return "Right Stick, Right"; default: break; } return fmt::format("Unknown Button ({})", static_cast(button)); } std::string get_analog_string(XInputAnalogEnum analog) { switch (analog) { case XInputAnalogEnum::LEFT_TRIGGER: return "Left Trigger"; case XInputAnalogEnum::RIGHT_TRIGGER: return "Right Trigger"; case XInputAnalogEnum::LEFT_STICK_X: return "Left Stick X"; case XInputAnalogEnum::LEFT_STICK_Y: return "Left Stick Y"; case XInputAnalogEnum::RIGHT_STICK_X: return "Right Stick X"; case XInputAnalogEnum::RIGHT_STICK_Y: return "Right Stick Y"; default: break; } return fmt::format("Unknown Analog ({})", static_cast(analog)); } std::string get_output_string(XInputOutputEnum output) { switch (output) { case XInputOutputEnum::LEFT_RUMBLE: return "Left Rumble"; case XInputOutputEnum::RIGHT_RUMBLE: return "Right Rumble"; default: break; } return fmt::format("Unknown Output ({})", static_cast(output)); } std::string get_device_desc(uint8_t player) { return fmt::format(";XINPUT;{}", player); } #if defined(SPICE_XP) XInputManager::XInputManager() {} XInputManager::~XInputManager() {} void XInputManager::stop() {} std::vector XInputManager::get_available_players() { return {}; } float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) { return 0.5f; } bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in) { return false; } bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) { return false; } void XInputManager::set_output_state(uint8_t player, XInputOutputEnum output, float value) { return; } #else static decltype(XInputGetState) *XInputGetState_addr = nullptr; static decltype(XInputSetState) *XInputSetState_addr = nullptr; XInputManager::XInputManager() { log_info("xinput", "initialize..."); // many systems ship only a newer XInput runtime, so try the known // DLL names in order of preference until one loads static const char *xinput_dll_names[] = { "xinput1_4.dll", "xinput1_3.dll", "xinput9_1_0.dll", }; const char *loaded_name = nullptr; for (const char *name : xinput_dll_names) { this->xinput_lib = LoadLibraryA(name); if (this->xinput_lib) { loaded_name = name; break; } } if (!this->xinput_lib) { log_warning("xinput", "failed to load any XInput DLL"); return; } log_info("xinput", "loaded {}", loaded_name); XInputGetState_addr = reinterpret_cast( GetProcAddress(this->xinput_lib, "XInputGetState")); if (!XInputGetState_addr) { log_warning("xinput", "failed to get XInputGetState address"); this->stop(); return; } XInputSetState_addr = reinterpret_cast( GetProcAddress(this->xinput_lib, "XInputSetState")); if (!XInputSetState_addr) { log_warning("xinput", "failed to get XInputSetState address"); this->stop(); return; } initialized = true; log_info("xinput", "initialized"); } XInputManager::~XInputManager() { this->stop(); } void XInputManager::stop() { this->initialized = false; if (this->xinput_lib) { FreeLibrary(this->xinput_lib); this->xinput_lib = nullptr; } XInputGetState_addr = nullptr; XInputSetState_addr = nullptr; log_info("xinput", "destroyed"); } std::vector XInputManager::get_available_players() { std::vector players; if (!this->initialized) { return players; } for (uint8_t i = 0; i < XUSER_MAX_COUNT; i++) { XINPUT_STATE x; if (XInputGetState_addr(i, &x) == ERROR_SUCCESS) { players.push_back(i); } } return players; } static void normalize_stick( SHORT raw_x, SHORT raw_y, float deadzone, float &out_x, float &out_y) { const int x_i = static_cast(raw_x); const int y_i = -static_cast(raw_y); const float x_f = static_cast(x_i); const float y_f = static_cast(y_i); const float magnitude = std::sqrt(x_f * x_f + y_f * y_f); // within deadzone; ignore if (magnitude <= deadzone) { out_x = 0.5f; out_y = 0.5f; return; } // scale value starting with 0 from deadzone border const float magnitude_clamped = std::min(magnitude, 32767.0f); const float scaled = (magnitude_clamped - deadzone) / (32767.0f - deadzone); // normalize const float normalized_x = (x_f / magnitude) * scaled; const float normalized_y = (y_f / magnitude) * scaled; // convert range to [0, 1] with 0.5 as center out_x = std::clamp((normalized_x + 1.f) / 2.f, 0.f, 1.f); out_y = std::clamp((normalized_y + 1.f) / 2.f, 0.f, 1.f); } void XInputManager::get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED &state) { state = {}; state.sThumbLX = 0.5f; state.sThumbLY = 0.5f; state.sThumbRX = 0.5f; state.sThumbRY = 0.5f; if (!this->initialized) { return; } XINPUT_STATE x = {}; if (XInputGetState_addr(player, &x) != ERROR_SUCCESS) { return; } state.wButtons = x.Gamepad.wButtons; state.bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f; state.bRightTrigger = x.Gamepad.bRightTrigger / 255.0f; // apply circular deadzone logic to left stick normalize_stick( x.Gamepad.sThumbLX, x.Gamepad.sThumbLY, static_cast(XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE), state.sThumbLX, state.sThumbLY); // apply circular deadzone logic to right stick normalize_stick( x.Gamepad.sThumbRX, x.Gamepad.sThumbRY, static_cast(XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE), state.sThumbRX, state.sThumbRY); } bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in) { XINPUT_GAMEPAD_STATE_NORMALIZED state_on_stack; XINPUT_GAMEPAD_STATE_NORMALIZED *state; if (state_in) { state = state_in; } else { state = &state_on_stack; get_state(player, *state); } switch (button) { case XInputButtonEnum::DPAD_UP: return (state->wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0; case XInputButtonEnum::DPAD_DOWN: return (state->wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0; case XInputButtonEnum::DPAD_LEFT: return (state->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0; case XInputButtonEnum::DPAD_RIGHT: return (state->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0; case XInputButtonEnum::START: return (state->wButtons & XINPUT_GAMEPAD_START) != 0; case XInputButtonEnum::BACK: return (state->wButtons & XINPUT_GAMEPAD_BACK) != 0; case XInputButtonEnum::LEFT_STICK: return (state->wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0; case XInputButtonEnum::RIGHT_STICK: return (state->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0; case XInputButtonEnum::LEFT_SHOULDER: return (state->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0; case XInputButtonEnum::RIGHT_SHOULDER: return (state->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0; case XInputButtonEnum::BUTTON_A: return (state->wButtons & XINPUT_GAMEPAD_A) != 0; case XInputButtonEnum::BUTTON_B: return (state->wButtons & XINPUT_GAMEPAD_B) != 0; case XInputButtonEnum::BUTTON_X: return (state->wButtons & XINPUT_GAMEPAD_X) != 0; case XInputButtonEnum::BUTTON_Y: return (state->wButtons & XINPUT_GAMEPAD_Y) != 0; case XInputButtonEnum::LEFT_TRIGGER: return state->bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; case XInputButtonEnum::RIGHT_TRIGGER: return state->bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; case XInputButtonEnum::LEFT_STICK_UP: return state->sThumbLY < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD); case XInputButtonEnum::LEFT_STICK_DOWN: return state->sThumbLY > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD); case XInputButtonEnum::LEFT_STICK_LEFT: return state->sThumbLX < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD); case XInputButtonEnum::LEFT_STICK_RIGHT: return state->sThumbLX > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD); case XInputButtonEnum::RIGHT_STICK_UP: return state->sThumbRY < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD); case XInputButtonEnum::RIGHT_STICK_DOWN: return state->sThumbRY > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD); case XInputButtonEnum::RIGHT_STICK_LEFT: return state->sThumbRX < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD); case XInputButtonEnum::RIGHT_STICK_RIGHT: return state->sThumbRX > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD); default: break; } return false; } float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) { XINPUT_GAMEPAD_STATE_NORMALIZED state; get_state(player, state); switch (analog) { case XInputAnalogEnum::LEFT_TRIGGER: return state.bLeftTrigger; case XInputAnalogEnum::RIGHT_TRIGGER: return state.bRightTrigger; case XInputAnalogEnum::LEFT_STICK_X: return state.sThumbLX; case XInputAnalogEnum::LEFT_STICK_Y: return state.sThumbLY; case XInputAnalogEnum::RIGHT_STICK_X: return state.sThumbRX; case XInputAnalogEnum::RIGHT_STICK_Y: return state.sThumbRY; default: break; } return 0.5f; } bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) { constexpr std::array(XInputButtonEnum::COUNT)> button_priority = { // the ordering here is important; we want to check buttons first, then dpad, then analog // this is to help with cases like DDR pads that output multiple at the same time for arrows // actual buttons XInputButtonEnum::BUTTON_A, XInputButtonEnum::BUTTON_B, XInputButtonEnum::BUTTON_X, XInputButtonEnum::BUTTON_Y, XInputButtonEnum::START, XInputButtonEnum::BACK, XInputButtonEnum::LEFT_STICK, XInputButtonEnum::RIGHT_STICK, XInputButtonEnum::LEFT_SHOULDER, XInputButtonEnum::RIGHT_SHOULDER, // dpad XInputButtonEnum::DPAD_UP, XInputButtonEnum::DPAD_DOWN, XInputButtonEnum::DPAD_LEFT, XInputButtonEnum::DPAD_RIGHT, // analog values that can be used as buttons XInputButtonEnum::LEFT_TRIGGER, XInputButtonEnum::RIGHT_TRIGGER, XInputButtonEnum::LEFT_STICK_UP, XInputButtonEnum::LEFT_STICK_DOWN, XInputButtonEnum::LEFT_STICK_LEFT, XInputButtonEnum::LEFT_STICK_RIGHT, XInputButtonEnum::RIGHT_STICK_UP, XInputButtonEnum::RIGHT_STICK_DOWN, XInputButtonEnum::RIGHT_STICK_LEFT, XInputButtonEnum::RIGHT_STICK_RIGHT, }; for (uint8_t player = 0; player < XUSER_MAX_COUNT; player++) { XINPUT_GAMEPAD_STATE_NORMALIZED state; get_state(player, state); for (uint16_t b = 0; b < static_cast(XInputButtonEnum::COUNT); b++) { if (is_button_pressed(player, button_priority[b], &state)) { button.player = player; button.button = button_priority[b]; return true; } } } return false; } void XInputManager::set_output_state(uint8_t player, XInputOutputEnum output, float value) { if (!this->initialized) { return; } if (player >= XUSER_MAX_COUNT) { return; } XINPUT_VIBRATION vibration = {}; if (output == XInputOutputEnum::LEFT_RUMBLE) { vibration.wLeftMotorSpeed = static_cast(value * 65535); } else if (output == XInputOutputEnum::RIGHT_RUMBLE) { vibration.wRightMotorSpeed = static_cast(value * 65535); } XInputSetState_addr(player, &vibration); } #endif }