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
+3 -1
View File
@@ -91,7 +91,9 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
} }
} }
case rawinput::XINPUT_GAMEPAD: case rawinput::XINPUT_GAMEPAD:
return xinput::get_analog_string(static_cast<xinput::XInputAnalogEnum>(index)) + " (" + device->desc + ")"; return fmt::format("{} ({})",
xinput::get_analog_string(static_cast<xinput::XInputAnalogEnum>(index)),
device->desc);
case rawinput::DESTROYED: case rawinput::DESTROYED:
return "Device unplugged (" + indexString + ")"; return "Device unplugged (" + indexString + ")";
default: default:
+15 -3
View File
@@ -927,7 +927,7 @@ void GameAPI::Lights::sortLightsWithCategory(
} }
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) { void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value) {
// check device // check device
if (!device) { if (!device) {
@@ -1014,6 +1014,18 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu
} }
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
if (index < static_cast<int>(xinput::XInputOutputEnum::COUNT)) {
manager->XINPUT_MGR->set_output_state(
player, static_cast<xinput::XInputOutputEnum>(index), value);
// no need to set output_pending; xinput output handled immediately
} else {
log_warning("api", "invalid xinput light index: {}", index);
}
break;
}
default: default:
break; break;
} }
@@ -1039,9 +1051,9 @@ void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, Light &ligh
// write state // write state
if (light.override_enabled) { if (light.override_enabled) {
writeLight(device, light.getIndex(), light.override_state); writeLight(manager, device, light.getIndex(), light.override_state);
} else { } else {
writeLight(device, light.getIndex(), value); writeLight(manager, device, light.getIndex(), value);
} }
} }
+1 -1
View File
@@ -135,7 +135,7 @@ namespace GameAPI {
void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list); void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list);
void writeLight(rawinput::Device *device, int index, float value); void writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value);
void writeLight(rawinput::RawInputManager *manager, Light &light, float value); void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value); void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
+3 -1
View File
@@ -418,7 +418,9 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
case rawinput::PIUIO_DEVICE: case rawinput::PIUIO_DEVICE:
return "PIUIO " + vKeyString; return "PIUIO " + vKeyString;
case rawinput::XINPUT_GAMEPAD: case rawinput::XINPUT_GAMEPAD:
return xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey)); return fmt::format("{} ({})",
xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey)),
device->desc);
case rawinput::DESTROYED: case rawinput::DESTROYED:
return "Device unplugged (" + vKeyString + ")"; return "Device unplugged (" + vKeyString + ")";
default: default:
+9
View File
@@ -85,6 +85,15 @@ std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
return "Invalid SMX Dedicab Light (" + index_string + ")"; return "Invalid SMX Dedicab Light (" + index_string + ")";
} }
case rawinput::XINPUT_GAMEPAD: {
if (index < static_cast<size_t>(xinput::XInputOutputEnum::COUNT)) {
return fmt::format("{} ({})",
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(index)),
device->desc);
}
return "Invalid XInput Gamepad Light (" + index_string + ")";
}
case rawinput::DESTROYED: case rawinput::DESTROYED:
return "Unplugged device (" + index_string + ")"; return "Unplugged device (" + index_string + ")";
default: default:
+43 -8
View File
@@ -673,7 +673,7 @@ namespace overlay::windows {
// longest column is probably "Toggle Virtual Keypad P1" in Overlay tab // longest column is probably "Toggle Virtual Keypad P1" in Overlay tab
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220)); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220));
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(140)); ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(170));
// check if empty // check if empty
if (!buttons || buttons->empty()) { if (!buttons || buttons->empty()) {
@@ -913,8 +913,8 @@ namespace overlay::windows {
// naive binding // naive binding
ImGui::SameLine(); ImGui::SameLine();
std::string naive_string = "Naive " + button_name; std::string naive_string = "Naive/XInput " + button_name;
if (ImGui::Button("Naive") if (ImGui::Button("Naive/XInput")
|| (buttons_many_active && buttons_many_active_section == name && !buttons_bind_active || (buttons_many_active && buttons_many_active_section == name && !buttons_bind_active
&& alt_index == 0 && alt_index == 0
&& buttons_many_naive && buttons_many_index == button_it && buttons_many_naive && buttons_many_index == button_it
@@ -934,11 +934,12 @@ namespace overlay::windows {
} }
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) { if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip( ImGui::HelpTooltip(
"Uses GetAsyncKeyState to check for any keyboard / mouse input. " "Alternative to Bind, using GetAsyncKeyState for KB/Mouse and XInput for controllers.\n\n"
"For best performance, Bind should be preferred, but this can be used when:\n" "This can be used when:\n"
" * you don't care about which device is used\n" " * you want to use XInput for gamepads (for analog triggers)\n"
" * you don't care about which keyboard/mouse is used\n"
" * you want to use input remapping or automation software\n" " * you want to use input remapping or automation software\n"
" * if you have NKRO issues with Bind"); " * you have NKRO issues with Bind");
} }
naive_button_popup(naive_string, button, button_it_max, alt_index); naive_button_popup(naive_string, button, button_it_max, alt_index);
@@ -998,7 +999,7 @@ namespace overlay::windows {
} }
// Naive Many // Naive Many
if (ImGui::MenuItem("Naive (many)")) { if (ImGui::MenuItem("Naive/XInput (many)")) {
buttons_many_active = true; buttons_many_active = true;
buttons_many_index = button_it; buttons_many_index = button_it;
buttons_many_naive = true; buttons_many_naive = true;
@@ -1555,6 +1556,22 @@ namespace overlay::windows {
} }
} }
// check xinput
if (check_devices) {
xinput::XINPUT_NEW_BUTTON xinput;
if (RI_MGR->XINPUT_MGR->get_any_button_pressed(xinput)) {
button->setDeviceIdentifier(xinput::get_device_desc(xinput.player));
button->setVKey(static_cast<uint16_t>(xinput.button));
::Config::getInstance().updateBinding(
games_list[games_selected], *button, alt_index - 1);
inc_buttons_many_index(button_it_max);
buttons_bind_active = false;
ImGui::CloseCurrentPopup();
check_devices = false;
}
}
// check GetAsyncKeyState
if (check_devices) { if (check_devices) {
// get new keyboard state // get new keyboard state
// these are async, and some keys generate multiple vKeys (e.g., VK_SHIFT, VK_LSHIFT) // these are async, and some keys generate multiple vKeys (e.g., VK_SHIFT, VK_LSHIFT)
@@ -2747,6 +2764,7 @@ namespace overlay::windows {
case rawinput::PIUIO_DEVICE: case rawinput::PIUIO_DEVICE:
case rawinput::SMX_STAGE: case rawinput::SMX_STAGE:
case rawinput::SMX_DEDICAB: case rawinput::SMX_DEDICAB:
case rawinput::XINPUT_GAMEPAD:
this->auto_match_devices.emplace_back(&device); this->auto_match_devices.emplace_back(&device);
break; break;
default: default:
@@ -2913,6 +2931,7 @@ namespace overlay::windows {
case rawinput::PIUIO_DEVICE: case rawinput::PIUIO_DEVICE:
case rawinput::SMX_STAGE: case rawinput::SMX_STAGE:
case rawinput::SMX_DEDICAB: case rawinput::SMX_DEDICAB:
case rawinput::XINPUT_GAMEPAD:
this->lights_devices.emplace_back(&device); this->lights_devices.emplace_back(&device);
break; break;
default: default:
@@ -3066,6 +3085,15 @@ namespace overlay::windows {
} }
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
// add all names of XInput gamepad device
for (int i = 0; i < static_cast<int>(xinput::XInputOutputEnum::COUNT); i++) {
control_names.emplace_back(
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(i)));
}
break;
}
default: default:
break; break;
} }
@@ -3611,6 +3639,13 @@ namespace overlay::windows {
} }
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
for (int i = 0; i < static_cast<int>(xinput::XInputOutputEnum::COUNT); i++) {
raw_names.emplace_back(
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(i)));
}
break;
}
default: default:
break; break;
} }
+3 -1
View File
@@ -119,8 +119,10 @@ namespace rawinput {
// success // success
return TRUE; return TRUE;
} }
case 7: { // windows 10 reports this for MIDI? case DBT_DEVNODES_CHANGED: {
// TODO: this can be a little noisy as it gets called on every device
this->ri_mgr->devices_scan_midi(); this->ri_mgr->devices_scan_midi();
this->ri_mgr->devices_scan_xinput();
break; break;
} }
} }
+64 -13
View File
@@ -8,6 +8,7 @@
#include <setupapi.h> #include <setupapi.h>
#include "util/logging.h" #include "util/logging.h"
#include "external/robin_hood.h"
#include "util/time.h" #include "util/time.h"
#include "util/utils.h" #include "util/utils.h"
@@ -1017,21 +1018,67 @@ void rawinput::RawInputManager::devices_scan_smxdedicab() {
void rawinput::RawInputManager::devices_scan_xinput() { void rawinput::RawInputManager::devices_scan_xinput() {
log_misc("rawinput", "scan XInput devices..."); log_misc("rawinput", "scan XInput devices...");
const auto players = XINPUT_MGR->get_available_players();
for (const auto player : players) {
auto *new_xinput_device = new Device();
new_xinput_device->type = XINPUT_GAMEPAD;
new_xinput_device->name = fmt::format(";XINPUT;{}", player);
new_xinput_device->desc = fmt::format("XInput Gamepad P{}", player + 1);
new_xinput_device->handle = reinterpret_cast<HANDLE>(player);
new_xinput_device->mutex = new std::mutex();
new_xinput_device->mutex_out = new std::mutex();
auto &device = this->devices.emplace_back(*new_xinput_device); const auto connected_players = XINPUT_MGR->get_available_players();
// notify add // first, destroy missing devices
for (auto &cb : this->callback_add) { std::vector<std::string> devices_to_remove;
cb.f(cb.data, &device); for (auto &device : this->devices) {
if (device.type != XINPUT_GAMEPAD) {
continue;
}
const uint8_t player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device.handle));
if (std::find(connected_players.begin(), connected_players.end(), player) == connected_players.end()) {
devices_to_remove.push_back(device.name);
}
}
for (const auto &name : devices_to_remove) {
this->devices_remove(name);
}
auto create_device = [](const uint8_t player) -> Device {
Device device = {};
device.type = XINPUT_GAMEPAD;
device.name = xinput::get_device_desc(player);
device.desc = fmt::format("XInput Gamepad P{}", player + 1);
device.handle = reinterpret_cast<HANDLE>(player);
device.mutex = new std::mutex();
device.mutex_out = new std::mutex();
return device;
};
// add new devices
for (const auto player : connected_players) {
bool duplicate_found = false;
// check for duplicates first
for (auto &prev_device : this->devices) {
if (prev_device.name != xinput::get_device_desc(player)) {
continue;
}
if (prev_device.type == DESTROYED) {
log_info("rawinput", "overwriting previously destroyed XInput device: {}", prev_device.name);
prev_device = create_device(player);
// notify change
for (auto &cb : this->callback_change) {
cb.f(cb.data, &prev_device);
}
}
duplicate_found = true;
break;
}
if (!duplicate_found) {
// add new device
log_info("rawinput", "adding new XInput device: player {}", player + 1);
auto new_xinput_device = create_device(player);
auto &device = this->devices.emplace_back(new_xinput_device);
// notify add
for (auto &cb : this->callback_add) {
cb.f(cb.data, &device);
}
} }
} }
} }
@@ -2594,6 +2641,10 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
device->smxdedicabInfo->Update(); device->smxdedicabInfo->Update();
break; break;
} }
case XINPUT_GAMEPAD: {
// nothing - updates to these are instant
break;
}
default: default:
break; break;
} }
+1 -1
View File
@@ -85,7 +85,6 @@ namespace rawinput {
void devices_scan_piuio(); void devices_scan_piuio();
void devices_scan_smxstage(); void devices_scan_smxstage();
void devices_scan_smxdedicab(); void devices_scan_smxdedicab();
void devices_scan_xinput();
void devices_destruct(); void devices_destruct();
void devices_destruct(Device *device, bool log = true); void devices_destruct(Device *device, bool log = true);
void flush_start(); void flush_start();
@@ -117,6 +116,7 @@ namespace rawinput {
void devices_scan_rawinput(const std::string &device_name = ""); void devices_scan_rawinput(const std::string &device_name = "");
void devices_scan_midi(); void devices_scan_midi();
void devices_scan_xinput();
void devices_remove(const std::string &name); void devices_remove(const std::string &name);
void devices_register(); void devices_register();
+111 -33
View File
@@ -30,23 +30,36 @@ typedef struct {
} XINPUT_STATE; } XINPUT_STATE;
DWORD DWORD
WINAPI
XInputGetState( XInputGetState(
DWORD dwUserIndex, DWORD dwUserIndex,
XINPUT_STATE *pState XINPUT_STATE *pState
); );
typedef struct {
uint16_t wLeftMotorSpeed;
uint16_t wRightMotorSpeed;
} XINPUT_VIBRATION;
DWORD
WINAPI
XInputSetState(
DWORD dwUserIndex,
XINPUT_VIBRATION* pVibration
);
// end xinput definitions // end xinput definitions
std::string get_button_string(XInputButtonEnum button) { std::string get_button_string(XInputButtonEnum button) {
switch (button) { switch (button) {
case XInputButtonEnum::DPAD_UP: case XInputButtonEnum::DPAD_UP:
return "Up"; return "Dpad Up";
case XInputButtonEnum::DPAD_DOWN: case XInputButtonEnum::DPAD_DOWN:
return "Down"; return "Dpad Down";
case XInputButtonEnum::DPAD_LEFT: case XInputButtonEnum::DPAD_LEFT:
return "Left"; return "Dpad Left";
case XInputButtonEnum::DPAD_RIGHT: case XInputButtonEnum::DPAD_RIGHT:
return "Right"; return "Dpad Right";
case XInputButtonEnum::START: case XInputButtonEnum::START:
return "Start"; return "Start";
case XInputButtonEnum::BACK: case XInputButtonEnum::BACK:
@@ -72,21 +85,21 @@ XInputGetState(
case XInputButtonEnum::RIGHT_TRIGGER: case XInputButtonEnum::RIGHT_TRIGGER:
return "Right Trigger"; return "Right Trigger";
case XInputButtonEnum::LEFT_STICK_UP: case XInputButtonEnum::LEFT_STICK_UP:
return "Left Stick Up"; return "Left Stick, Up";
case XInputButtonEnum::LEFT_STICK_DOWN: case XInputButtonEnum::LEFT_STICK_DOWN:
return "Left Stick Down"; return "Left Stick, Down";
case XInputButtonEnum::LEFT_STICK_LEFT: case XInputButtonEnum::LEFT_STICK_LEFT:
return "Left Stick Left"; return "Left Stick, Left";
case XInputButtonEnum::LEFT_STICK_RIGHT: case XInputButtonEnum::LEFT_STICK_RIGHT:
return "Left Stick Right"; return "Left Stick, Right";
case XInputButtonEnum::RIGHT_STICK_UP: case XInputButtonEnum::RIGHT_STICK_UP:
return "Right Stick Up"; return "Right Stick, Up";
case XInputButtonEnum::RIGHT_STICK_DOWN: case XInputButtonEnum::RIGHT_STICK_DOWN:
return "Right Stick Down"; return "Right Stick, Down";
case XInputButtonEnum::RIGHT_STICK_LEFT: case XInputButtonEnum::RIGHT_STICK_LEFT:
return "Right Stick Left"; return "Right Stick, Left";
case XInputButtonEnum::RIGHT_STICK_RIGHT: case XInputButtonEnum::RIGHT_STICK_RIGHT:
return "Right Stick Right"; return "Right Stick, Right";
default: default:
break; break;
} }
@@ -113,6 +126,22 @@ XInputGetState(
return fmt::format("Unknown Analog ({})", static_cast<int>(analog)); 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) #if defined(SPICE_XP)
XInputManager::XInputManager() {} XInputManager::XInputManager() {}
@@ -127,10 +156,17 @@ XInputGetState(
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) { bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
return false; 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 #else
static decltype(XInputGetState) *XInputGetState_addr = nullptr; static decltype(XInputGetState) *XInputGetState_addr = nullptr;
static decltype(XInputSetState) *XInputSetState_addr = nullptr;
XInputManager::XInputManager() { XInputManager::XInputManager() {
log_info("xinput", "initialize..."); log_info("xinput", "initialize...");
@@ -146,6 +182,13 @@ XInputGetState(
this->stop(); this->stop();
return; 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; initialized = true;
log_info("xinput", "initialized"); log_info("xinput", "initialized");
} }
@@ -155,12 +198,13 @@ XInputGetState(
} }
void XInputManager::stop() { void XInputManager::stop() {
this->initialized = false;
if (this->xinput_lib) { if (this->xinput_lib) {
FreeLibrary(this->xinput_lib); FreeLibrary(this->xinput_lib);
this->xinput_lib = nullptr; this->xinput_lib = nullptr;
} }
XInputGetState_addr = nullptr; XInputGetState_addr = nullptr;
this->initialized = false; XInputSetState_addr = nullptr;
log_info("xinput", "destroyed"); log_info("xinput", "destroyed");
} }
@@ -169,17 +213,22 @@ XInputGetState(
if (!this->initialized) { if (!this->initialized) {
return players; return players;
} }
for (uint8_t i = 0; i < XUSER_MAX_COUNT; i++) { for (uint8_t i = 0; i < XUSER_MAX_COUNT; i++) {
XINPUT_STATE x; XINPUT_STATE x;
if (XInputGetState_addr(i, &x) == ERROR_SUCCESS) { if (XInputGetState_addr(i, &x) == ERROR_SUCCESS) {
players.emplace_back(i); players.push_back(i);
} }
} }
return players; return players;
} }
void XInputManager::get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED *state) { void XInputManager::get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED &state) {
*state = {}; state = {};
state.sThumbLX = 0.5f;
state.sThumbLY = 0.5f;
state.sThumbRX = 0.5f;
state.sThumbRY = 0.5f;
if (!this->initialized) { if (!this->initialized) {
return; return;
} }
@@ -189,9 +238,9 @@ XInputGetState(
return; return;
} }
state->wButtons = x.Gamepad.wButtons; state.wButtons = x.Gamepad.wButtons;
state->bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f; state.bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f;
state->bRightTrigger = x.Gamepad.bRightTrigger / 255.0f; state.bRightTrigger = x.Gamepad.bRightTrigger / 255.0f;
// left stick circular dead zone // left stick circular dead zone
{ {
@@ -203,16 +252,16 @@ XInputGetState(
(magnitude - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) / (magnitude - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) /
(32767.0f - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); (32767.0f - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
state->sThumbLX = (x_raw / magnitude) * scaled; state.sThumbLX = (x_raw / magnitude) * scaled;
state->sThumbLY = (y_raw / magnitude) * scaled; state.sThumbLY = (y_raw / magnitude) * scaled;
// normalize to [0, 1] // normalize to [0, 1]
state->sThumbLX = std::clamp((state->sThumbLX + 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); state.sThumbLY = std::clamp((state.sThumbLY + 1.f) / 2.f, 0.f, 1.f);
} else { } else {
// within deadzone // within deadzone
state->sThumbLX = 0.5f; state.sThumbLX = 0.5f;
state->sThumbLY = 0.5f; state.sThumbLY = 0.5f;
} }
} }
@@ -226,16 +275,16 @@ XInputGetState(
(magnitude - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) / (magnitude - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) /
(32767.0f - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); (32767.0f - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
state->sThumbRX = (x_raw / magnitude) * scaled; state.sThumbRX = (x_raw / magnitude) * scaled;
state->sThumbRY = (y_raw / magnitude) * scaled; state.sThumbRY = (y_raw / magnitude) * scaled;
// normalize to [0, 1] // normalize to [0, 1]
state->sThumbRX = std::clamp((state->sThumbRX + 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); state.sThumbRY = std::clamp((state.sThumbRY + 1.f) / 2.f, 0.f, 1.f);
} else { } else {
// within deadzone // within deadzone
state->sThumbRX = 0.5f; state.sThumbRX = 0.5f;
state->sThumbRY = 0.5f; state.sThumbRY = 0.5f;
} }
} }
} }
@@ -243,7 +292,7 @@ XInputGetState(
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) { bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
XINPUT_GAMEPAD_STATE_NORMALIZED state; XINPUT_GAMEPAD_STATE_NORMALIZED state;
get_state(player, &state); get_state(player, state);
switch (button) { switch (button) {
case XInputButtonEnum::DPAD_UP: case XInputButtonEnum::DPAD_UP:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0; return (state.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
@@ -303,7 +352,7 @@ XInputGetState(
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) { float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
XINPUT_GAMEPAD_STATE_NORMALIZED state; XINPUT_GAMEPAD_STATE_NORMALIZED state;
get_state(player, &state); get_state(player, state);
switch (analog) { switch (analog) {
case XInputAnalogEnum::LEFT_TRIGGER: case XInputAnalogEnum::LEFT_TRIGGER:
return state.bLeftTrigger; return state.bLeftTrigger;
@@ -324,6 +373,35 @@ XInputGetState(
return 0.5f; 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 #endif
} }
+22 -6
View File
@@ -11,7 +11,7 @@ namespace xinput {
#define XUSER_MAX_COUNT 4 #define XUSER_MAX_COUNT 4
typedef struct _XINPUT_GAMEPAD_STATE { struct XINPUT_GAMEPAD_STATE {
uint16_t wButtons; uint16_t wButtons;
uint8_t bLeftTrigger; uint8_t bLeftTrigger;
uint8_t bRightTrigger; uint8_t bRightTrigger;
@@ -19,9 +19,9 @@ namespace xinput {
int16_t sThumbLY; int16_t sThumbLY;
int16_t sThumbRX; int16_t sThumbRX;
int16_t sThumbRY; int16_t sThumbRY;
} XINPUT_GAMEPAD_STATE; };
typedef struct _XINPUT_GAMEPAD_STATE_NORMALIZED { struct XINPUT_GAMEPAD_STATE_NORMALIZED {
uint16_t wButtons; uint16_t wButtons;
float bLeftTrigger; float bLeftTrigger;
float bRightTrigger; float bRightTrigger;
@@ -29,9 +29,9 @@ namespace xinput {
float sThumbLY; float sThumbLY;
float sThumbRX; float sThumbRX;
float sThumbRY; float sThumbRY;
} XINPUT_GAMEPAD_STATE_NORMALIZED; };
enum class XInputButtonEnum { enum class XInputButtonEnum : uint16_t {
// actual buttons // actual buttons
DPAD_UP, DPAD_UP,
DPAD_DOWN, DPAD_DOWN,
@@ -75,14 +75,28 @@ namespace xinput {
COUNT COUNT
}; };
enum class XInputOutputEnum : uint16_t {
LEFT_RUMBLE,
RIGHT_RUMBLE,
COUNT
};
struct XINPUT_NEW_BUTTON {
uint8_t player;
XInputButtonEnum button;
};
std::string get_button_string(XInputButtonEnum button); std::string get_button_string(XInputButtonEnum button);
std::string get_analog_string(XInputAnalogEnum analog); std::string get_analog_string(XInputAnalogEnum analog);
std::string get_output_string(XInputOutputEnum output);
std::string get_device_desc(uint8_t player);
class XInputManager { class XInputManager {
private: private:
bool initialized = false; bool initialized = false;
HMODULE xinput_lib = nullptr; HMODULE xinput_lib = nullptr;
void get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED *state); void get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED &state);
public: public:
XInputManager(); XInputManager();
~XInputManager(); ~XInputManager();
@@ -90,5 +104,7 @@ namespace xinput {
std::vector<uint8_t> get_available_players(); std::vector<uint8_t> get_available_players();
bool is_button_pressed(uint8_t player, XInputButtonEnum button); bool is_button_pressed(uint8_t player, XInputButtonEnum button);
float get_analog_state(uint8_t player, XInputAnalogEnum analog); float get_analog_state(uint8_t player, XInputAnalogEnum analog);
bool get_any_button_pressed(XINPUT_NEW_BUTTON &button);
void set_output_state(uint8_t player, XInputOutputEnum output, float value);
}; };
} }