mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
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:
@@ -91,7 +91,9 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
|
||||
}
|
||||
}
|
||||
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:
|
||||
return "Device unplugged (" + indexString + ")";
|
||||
default:
|
||||
|
||||
+15
-3
@@ -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<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:
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace GameAPI {
|
||||
|
||||
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(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
|
||||
|
||||
|
||||
@@ -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<xinput::XInputButtonEnum>(vKey));
|
||||
return fmt::format("{} ({})",
|
||||
xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey)),
|
||||
device->desc);
|
||||
case rawinput::DESTROYED:
|
||||
return "Device unplugged (" + vKeyString + ")";
|
||||
default:
|
||||
|
||||
@@ -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<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:
|
||||
return "Unplugged device (" + index_string + ")";
|
||||
default:
|
||||
|
||||
@@ -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<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) {
|
||||
// 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<int>(xinput::XInputOutputEnum::COUNT); i++) {
|
||||
control_names.emplace_back(
|
||||
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(i)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -3611,6 +3639,13 @@ namespace overlay::windows {
|
||||
}
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <setupapi.h>
|
||||
|
||||
#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<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
|
||||
for (auto &cb : this->callback_add) {
|
||||
cb.f(cb.data, &device);
|
||||
// first, destroy missing devices
|
||||
std::vector<std::string> devices_to_remove;
|
||||
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();
|
||||
break;
|
||||
}
|
||||
case XINPUT_GAMEPAD: {
|
||||
// nothing - updates to these are instant
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
+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
|
||||
|
||||
}
|
||||
|
||||
@@ -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<uint8_t> 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);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user