diff --git a/src/spice2x/cfg/analog.cpp b/src/spice2x/cfg/analog.cpp index 7e89e9a..e8c6ba9 100644 --- a/src/spice2x/cfg/analog.cpp +++ b/src/spice2x/cfg/analog.cpp @@ -91,7 +91,9 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) { } } case rawinput::XINPUT_GAMEPAD: - return xinput::get_analog_string(static_cast(index)) + " (" + device->desc + ")"; + return fmt::format("{} ({})", + xinput::get_analog_string(static_cast(index)), + device->desc); case rawinput::DESTROYED: return "Device unplugged (" + indexString + ")"; default: diff --git a/src/spice2x/cfg/api.cpp b/src/spice2x/cfg/api.cpp index 6f467e9..704004d 100644 --- a/src/spice2x/cfg/api.cpp +++ b/src/spice2x/cfg/api.cpp @@ -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 if (!device) { @@ -1014,6 +1014,18 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu } break; } + case rawinput::XINPUT_GAMEPAD: { + assert(reinterpret_cast(device->handle) < XUSER_MAX_COUNT); + const auto player = static_cast(reinterpret_cast(device->handle)); + if (index < static_cast(xinput::XInputOutputEnum::COUNT)) { + manager->XINPUT_MGR->set_output_state( + player, static_cast(index), value); + // no need to set output_pending; xinput output handled immediately + } else { + log_warning("api", "invalid xinput light index: {}", index); + } + break; + } default: break; } @@ -1039,9 +1051,9 @@ void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, Light &ligh // write state if (light.override_enabled) { - writeLight(device, light.getIndex(), light.override_state); + writeLight(manager, device, light.getIndex(), light.override_state); } else { - writeLight(device, light.getIndex(), value); + writeLight(manager, device, light.getIndex(), value); } } diff --git a/src/spice2x/cfg/api.h b/src/spice2x/cfg/api.h index 3cb4d37..e8ed32f 100644 --- a/src/spice2x/cfg/api.h +++ b/src/spice2x/cfg/api.h @@ -135,7 +135,7 @@ namespace GameAPI { void sortLightsWithCategory(std::vector *lights, const std::initializer_list 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(std::unique_ptr &manager, Light &light, float value); diff --git a/src/spice2x/cfg/button.cpp b/src/spice2x/cfg/button.cpp index 40a234e..be2ca2a 100644 --- a/src/spice2x/cfg/button.cpp +++ b/src/spice2x/cfg/button.cpp @@ -418,7 +418,9 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) { case rawinput::PIUIO_DEVICE: return "PIUIO " + vKeyString; case rawinput::XINPUT_GAMEPAD: - return xinput::get_button_string(static_cast(vKey)); + return fmt::format("{} ({})", + xinput::get_button_string(static_cast(vKey)), + device->desc); case rawinput::DESTROYED: return "Device unplugged (" + vKeyString + ")"; default: diff --git a/src/spice2x/cfg/light.cpp b/src/spice2x/cfg/light.cpp index 4166443..ac32396 100644 --- a/src/spice2x/cfg/light.cpp +++ b/src/spice2x/cfg/light.cpp @@ -85,6 +85,15 @@ std::string Light::getDisplayString(rawinput::RawInputManager* manager) { return "Invalid SMX Dedicab Light (" + index_string + ")"; } + case rawinput::XINPUT_GAMEPAD: { + if (index < static_cast(xinput::XInputOutputEnum::COUNT)) { + return fmt::format("{} ({})", + xinput::get_output_string(static_cast(index)), + device->desc); + } + + return "Invalid XInput Gamepad Light (" + index_string + ")"; + } case rawinput::DESTROYED: return "Unplugged device (" + index_string + ")"; default: diff --git a/src/spice2x/overlay/windows/config.cpp b/src/spice2x/overlay/windows/config.cpp index f4e146e..b44e3f3 100644 --- a/src/spice2x/overlay/windows/config.cpp +++ b/src/spice2x/overlay/windows/config.cpp @@ -673,7 +673,7 @@ namespace overlay::windows { // longest column is probably "Toggle Virtual Keypad P1" in Overlay tab ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220)); 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 if (!buttons || buttons->empty()) { @@ -913,8 +913,8 @@ namespace overlay::windows { // naive binding ImGui::SameLine(); - std::string naive_string = "Naive " + button_name; - if (ImGui::Button("Naive") + std::string naive_string = "Naive/XInput " + button_name; + if (ImGui::Button("Naive/XInput") || (buttons_many_active && buttons_many_active_section == name && !buttons_bind_active && alt_index == 0 && buttons_many_naive && buttons_many_index == button_it @@ -934,11 +934,12 @@ namespace overlay::windows { } if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) { ImGui::HelpTooltip( - "Uses GetAsyncKeyState to check for any keyboard / mouse input. " - "For best performance, Bind should be preferred, but this can be used when:\n" - " * you don't care about which device is used\n" + "Alternative to Bind, using GetAsyncKeyState for KB/Mouse and XInput for controllers.\n\n" + "This can be used when:\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" - " * if you have NKRO issues with Bind"); + " * you have NKRO issues with Bind"); } naive_button_popup(naive_string, button, button_it_max, alt_index); @@ -998,7 +999,7 @@ namespace overlay::windows { } // Naive Many - if (ImGui::MenuItem("Naive (many)")) { + if (ImGui::MenuItem("Naive/XInput (many)")) { buttons_many_active = true; buttons_many_index = button_it; 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(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) { // get new keyboard state // 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::SMX_STAGE: case rawinput::SMX_DEDICAB: + case rawinput::XINPUT_GAMEPAD: this->auto_match_devices.emplace_back(&device); break; default: @@ -2913,6 +2931,7 @@ namespace overlay::windows { case rawinput::PIUIO_DEVICE: case rawinput::SMX_STAGE: case rawinput::SMX_DEDICAB: + case rawinput::XINPUT_GAMEPAD: this->lights_devices.emplace_back(&device); break; default: @@ -3066,6 +3085,15 @@ namespace overlay::windows { } break; } + case rawinput::XINPUT_GAMEPAD: { + + // add all names of XInput gamepad device + for (int i = 0; i < static_cast(xinput::XInputOutputEnum::COUNT); i++) { + control_names.emplace_back( + xinput::get_output_string(static_cast(i))); + } + break; + } default: break; } @@ -3611,6 +3639,13 @@ namespace overlay::windows { } break; } + case rawinput::XINPUT_GAMEPAD: { + for (int i = 0; i < static_cast(xinput::XInputOutputEnum::COUNT); i++) { + raw_names.emplace_back( + xinput::get_output_string(static_cast(i))); + } + break; + } default: break; } diff --git a/src/spice2x/rawinput/hotplug.cpp b/src/spice2x/rawinput/hotplug.cpp index 198faaf..35bc237 100644 --- a/src/spice2x/rawinput/hotplug.cpp +++ b/src/spice2x/rawinput/hotplug.cpp @@ -119,8 +119,10 @@ namespace rawinput { // success 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_xinput(); break; } } diff --git a/src/spice2x/rawinput/rawinput.cpp b/src/spice2x/rawinput/rawinput.cpp index ae5568c..36b5490 100644 --- a/src/spice2x/rawinput/rawinput.cpp +++ b/src/spice2x/rawinput/rawinput.cpp @@ -8,6 +8,7 @@ #include #include "util/logging.h" +#include "external/robin_hood.h" #include "util/time.h" #include "util/utils.h" @@ -1017,21 +1018,67 @@ void rawinput::RawInputManager::devices_scan_smxdedicab() { void rawinput::RawInputManager::devices_scan_xinput() { 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(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 - for (auto &cb : this->callback_add) { - cb.f(cb.data, &device); + // first, destroy missing devices + std::vector devices_to_remove; + for (auto &device : this->devices) { + if (device.type != XINPUT_GAMEPAD) { + continue; + } + const uint8_t player = static_cast(reinterpret_cast(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(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(); break; } + case XINPUT_GAMEPAD: { + // nothing - updates to these are instant + break; + } default: break; } diff --git a/src/spice2x/rawinput/rawinput.h b/src/spice2x/rawinput/rawinput.h index e91ee73..a4b17e0 100644 --- a/src/spice2x/rawinput/rawinput.h +++ b/src/spice2x/rawinput/rawinput.h @@ -85,7 +85,6 @@ namespace rawinput { void devices_scan_piuio(); void devices_scan_smxstage(); void devices_scan_smxdedicab(); - void devices_scan_xinput(); void devices_destruct(); void devices_destruct(Device *device, bool log = true); void flush_start(); @@ -117,6 +116,7 @@ namespace rawinput { void devices_scan_rawinput(const std::string &device_name = ""); void devices_scan_midi(); + void devices_scan_xinput(); void devices_remove(const std::string &name); void devices_register(); diff --git a/src/spice2x/rawinput/xinput.cpp b/src/spice2x/rawinput/xinput.cpp index 155a7e4..a05296e 100644 --- a/src/spice2x/rawinput/xinput.cpp +++ b/src/spice2x/rawinput/xinput.cpp @@ -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(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) @@ -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( + 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(XInputButtonEnum::COUNT); b++) { + if (is_button_pressed(player, static_cast(b))) { + button.player = player; + button.button = static_cast(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 } diff --git a/src/spice2x/rawinput/xinput.h b/src/spice2x/rawinput/xinput.h index fbf9b83..258ed56 100644 --- a/src/spice2x/rawinput/xinput.h +++ b/src/spice2x/rawinput/xinput.h @@ -11,7 +11,7 @@ namespace xinput { #define XUSER_MAX_COUNT 4 - typedef struct _XINPUT_GAMEPAD_STATE { + struct XINPUT_GAMEPAD_STATE { uint16_t wButtons; uint8_t bLeftTrigger; uint8_t bRightTrigger; @@ -19,9 +19,9 @@ namespace xinput { int16_t sThumbLY; int16_t sThumbRX; int16_t sThumbRY; - } XINPUT_GAMEPAD_STATE; + }; - typedef struct _XINPUT_GAMEPAD_STATE_NORMALIZED { + struct XINPUT_GAMEPAD_STATE_NORMALIZED { uint16_t wButtons; float bLeftTrigger; float bRightTrigger; @@ -29,9 +29,9 @@ namespace xinput { float sThumbLY; float sThumbRX; float sThumbRY; - } XINPUT_GAMEPAD_STATE_NORMALIZED; + }; - enum class XInputButtonEnum { + enum class XInputButtonEnum : uint16_t { // actual buttons DPAD_UP, DPAD_DOWN, @@ -75,14 +75,28 @@ namespace xinput { 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_analog_string(XInputAnalogEnum analog); + std::string get_output_string(XInputOutputEnum output); + std::string get_device_desc(uint8_t player); class XInputManager { private: bool initialized = false; 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: XInputManager(); ~XInputManager(); @@ -90,5 +104,7 @@ namespace xinput { std::vector get_available_players(); bool is_button_pressed(uint8_t player, XInputButtonEnum button); 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); }; }