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:
|
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
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -112,6 +125,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)
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user