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:
+111
-33
@@ -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
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user