xinput: bind UI, vibration, hotplug (#620)

## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
`Naive` button is now called `Naive/XInput` and detects XInput
controllers

Add vibration output as "Lights"

Add proper hotplug support and multiple controllers

Fix misc bugs on x86

## Testing
I tested with two xbox controllers.
This commit is contained in:
bicarus
2026-04-10 01:04:20 -07:00
committed by GitHub
parent ca70f7b19c
commit a159691097
11 changed files with 275 additions and 68 deletions
+111 -33
View File
@@ -30,23 +30,36 @@ typedef struct {
} 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 "Up";
return "Dpad Up";
case XInputButtonEnum::DPAD_DOWN:
return "Down";
return "Dpad Down";
case XInputButtonEnum::DPAD_LEFT:
return "Left";
return "Dpad Left";
case XInputButtonEnum::DPAD_RIGHT:
return "Right";
return "Dpad Right";
case XInputButtonEnum::START:
return "Start";
case XInputButtonEnum::BACK:
@@ -72,21 +85,21 @@ XInputGetState(
case XInputButtonEnum::RIGHT_TRIGGER:
return "Right Trigger";
case XInputButtonEnum::LEFT_STICK_UP:
return "Left Stick Up";
return "Left Stick, Up";
case XInputButtonEnum::LEFT_STICK_DOWN:
return "Left Stick Down";
return "Left Stick, Down";
case XInputButtonEnum::LEFT_STICK_LEFT:
return "Left Stick Left";
return "Left Stick, Left";
case XInputButtonEnum::LEFT_STICK_RIGHT:
return "Left Stick Right";
return "Left Stick, Right";
case XInputButtonEnum::RIGHT_STICK_UP:
return "Right Stick Up";
return "Right Stick, Up";
case XInputButtonEnum::RIGHT_STICK_DOWN:
return "Right Stick Down";
return "Right Stick, Down";
case XInputButtonEnum::RIGHT_STICK_LEFT:
return "Right Stick Left";
return "Right Stick, Left";
case XInputButtonEnum::RIGHT_STICK_RIGHT:
return "Right Stick Right";
return "Right Stick, Right";
default:
break;
}
@@ -112,6 +125,22 @@ XInputGetState(
}
return fmt::format("Unknown Analog ({})", static_cast<int>(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<int>(output));
}
std::string get_device_desc(uint8_t player) {
return fmt::format(";XINPUT;{}", player);
}
#if defined(SPICE_XP)
@@ -127,10 +156,17 @@ XInputGetState(
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
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...");
@@ -146,6 +182,13 @@ XInputGetState(
this->stop();
return;
}
XInputSetState_addr = reinterpret_cast<decltype(XInputSetState) *>(
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");
}
@@ -155,12 +198,13 @@ XInputGetState(
}
void XInputManager::stop() {
this->initialized = false;
if (this->xinput_lib) {
FreeLibrary(this->xinput_lib);
this->xinput_lib = nullptr;
}
XInputGetState_addr = nullptr;
this->initialized = false;
XInputSetState_addr = nullptr;
log_info("xinput", "destroyed");
}
@@ -169,17 +213,22 @@ XInputGetState(
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.emplace_back(i);
players.push_back(i);
}
}
return players;
}
void XInputManager::get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED *state) {
*state = {};
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;
}
@@ -189,9 +238,9 @@ XInputGetState(
return;
}
state->wButtons = x.Gamepad.wButtons;
state->bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f;
state->bRightTrigger = x.Gamepad.bRightTrigger / 255.0f;
state.wButtons = x.Gamepad.wButtons;
state.bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f;
state.bRightTrigger = x.Gamepad.bRightTrigger / 255.0f;
// left stick circular dead zone
{
@@ -203,16 +252,16 @@ XInputGetState(
(magnitude - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) /
(32767.0f - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
state->sThumbLX = (x_raw / magnitude) * scaled;
state->sThumbLY = (y_raw / magnitude) * scaled;
state.sThumbLX = (x_raw / magnitude) * scaled;
state.sThumbLY = (y_raw / magnitude) * scaled;
// normalize to [0, 1]
state->sThumbLX = std::clamp((state->sThumbLX + 1.f) / 2.f, 0.f, 1.f);
state->sThumbLY = std::clamp((state->sThumbLY + 1.f) / 2.f, 0.f, 1.f);
state.sThumbLX = std::clamp((state.sThumbLX + 1.f) / 2.f, 0.f, 1.f);
state.sThumbLY = std::clamp((state.sThumbLY + 1.f) / 2.f, 0.f, 1.f);
} else {
// within deadzone
state->sThumbLX = 0.5f;
state->sThumbLY = 0.5f;
state.sThumbLX = 0.5f;
state.sThumbLY = 0.5f;
}
}
@@ -226,16 +275,16 @@ XInputGetState(
(magnitude - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) /
(32767.0f - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
state->sThumbRX = (x_raw / magnitude) * scaled;
state->sThumbRY = (y_raw / magnitude) * scaled;
state.sThumbRX = (x_raw / magnitude) * scaled;
state.sThumbRY = (y_raw / magnitude) * scaled;
// normalize to [0, 1]
state->sThumbRX = std::clamp((state->sThumbRX + 1.f) / 2.f, 0.f, 1.f);
state->sThumbRY = std::clamp((state->sThumbRY + 1.f) / 2.f, 0.f, 1.f);
state.sThumbRX = std::clamp((state.sThumbRX + 1.f) / 2.f, 0.f, 1.f);
state.sThumbRY = std::clamp((state.sThumbRY + 1.f) / 2.f, 0.f, 1.f);
} else {
// within deadzone
state->sThumbRX = 0.5f;
state->sThumbRY = 0.5f;
state.sThumbRX = 0.5f;
state.sThumbRY = 0.5f;
}
}
}
@@ -243,7 +292,7 @@ XInputGetState(
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
XINPUT_GAMEPAD_STATE_NORMALIZED state;
get_state(player, &state);
get_state(player, state);
switch (button) {
case XInputButtonEnum::DPAD_UP:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
@@ -303,7 +352,7 @@ XInputGetState(
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
XINPUT_GAMEPAD_STATE_NORMALIZED state;
get_state(player, &state);
get_state(player, state);
switch (analog) {
case XInputAnalogEnum::LEFT_TRIGGER:
return state.bLeftTrigger;
@@ -324,6 +373,35 @@ XInputGetState(
return 0.5f;
}
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
for (uint8_t player = 0; player < XUSER_MAX_COUNT; player++) {
for (uint16_t b = 0; b < static_cast<uint16_t>(XInputButtonEnum::COUNT); b++) {
if (is_button_pressed(player, static_cast<XInputButtonEnum>(b))) {
button.player = player;
button.button = static_cast<XInputButtonEnum>(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<uint16_t>(value * 65535);
} else if (output == XInputOutputEnum::RIGHT_RUMBLE) {
vibration.wRightMotorSpeed = static_cast<uint16_t>(value * 65535);
}
XInputSetState_addr(player, &vibration);
}
#endif
}