mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 14:50:41 -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:
@@ -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