mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-10-04 04:38:01 +03:00
482 lines
17 KiB
C++
482 lines
17 KiB
C++
#include "rawinput/xinput.h"
|
|
#include "util/logging.h"
|
|
|
|
// std::min
|
|
#ifdef min
|
|
#undef min
|
|
#endif
|
|
|
|
namespace xinput {
|
|
|
|
// this is all we need to emulate xinput.h which we avoid including here...
|
|
|
|
#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD (30 / 255.0f)
|
|
#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849
|
|
#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
|
|
|
|
#define XINPUT_GAMEPAD_DPAD_UP 0x0001
|
|
#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002
|
|
#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004
|
|
#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008
|
|
#define XINPUT_GAMEPAD_START 0x0010
|
|
#define XINPUT_GAMEPAD_BACK 0x0020
|
|
#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040
|
|
#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080
|
|
#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100
|
|
#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200
|
|
#define XINPUT_GAMEPAD_A 0x1000
|
|
#define XINPUT_GAMEPAD_B 0x2000
|
|
#define XINPUT_GAMEPAD_X 0x4000
|
|
#define XINPUT_GAMEPAD_Y 0x8000
|
|
|
|
// custom
|
|
|
|
static constexpr float GAMEPAD_THUMB_DIGITAL_THRESHOLD = 0.2f;
|
|
|
|
typedef struct {
|
|
uint32_t dwPacketNumber;
|
|
XINPUT_GAMEPAD_STATE Gamepad;
|
|
} 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 "Dpad Up";
|
|
case XInputButtonEnum::DPAD_DOWN:
|
|
return "Dpad Down";
|
|
case XInputButtonEnum::DPAD_LEFT:
|
|
return "Dpad Left";
|
|
case XInputButtonEnum::DPAD_RIGHT:
|
|
return "Dpad Right";
|
|
case XInputButtonEnum::START:
|
|
return "Start";
|
|
case XInputButtonEnum::BACK:
|
|
return "Back";
|
|
case XInputButtonEnum::LEFT_STICK:
|
|
return "Left Stick Click";
|
|
case XInputButtonEnum::RIGHT_STICK:
|
|
return "Right Stick Click";
|
|
case XInputButtonEnum::LEFT_SHOULDER:
|
|
return "Left Bumper";
|
|
case XInputButtonEnum::RIGHT_SHOULDER:
|
|
return "Right Bumper";
|
|
case XInputButtonEnum::BUTTON_A:
|
|
return "Button A";
|
|
case XInputButtonEnum::BUTTON_B:
|
|
return "Button B";
|
|
case XInputButtonEnum::BUTTON_X:
|
|
return "Button X";
|
|
case XInputButtonEnum::BUTTON_Y:
|
|
return "Button Y";
|
|
case XInputButtonEnum::LEFT_TRIGGER:
|
|
return "Left Trigger";
|
|
case XInputButtonEnum::RIGHT_TRIGGER:
|
|
return "Right Trigger";
|
|
case XInputButtonEnum::LEFT_STICK_UP:
|
|
return "Left Stick, Up";
|
|
case XInputButtonEnum::LEFT_STICK_DOWN:
|
|
return "Left Stick, Down";
|
|
case XInputButtonEnum::LEFT_STICK_LEFT:
|
|
return "Left Stick, Left";
|
|
case XInputButtonEnum::LEFT_STICK_RIGHT:
|
|
return "Left Stick, Right";
|
|
case XInputButtonEnum::RIGHT_STICK_UP:
|
|
return "Right Stick, Up";
|
|
case XInputButtonEnum::RIGHT_STICK_DOWN:
|
|
return "Right Stick, Down";
|
|
case XInputButtonEnum::RIGHT_STICK_LEFT:
|
|
return "Right Stick, Left";
|
|
case XInputButtonEnum::RIGHT_STICK_RIGHT:
|
|
return "Right Stick, Right";
|
|
default:
|
|
break;
|
|
}
|
|
return fmt::format("Unknown Button ({})", static_cast<int>(button));
|
|
}
|
|
|
|
std::string get_analog_string(XInputAnalogEnum analog) {
|
|
switch (analog) {
|
|
case XInputAnalogEnum::LEFT_TRIGGER:
|
|
return "Left Trigger";
|
|
case XInputAnalogEnum::RIGHT_TRIGGER:
|
|
return "Right Trigger";
|
|
case XInputAnalogEnum::LEFT_STICK_X:
|
|
return "Left Stick X";
|
|
case XInputAnalogEnum::LEFT_STICK_Y:
|
|
return "Left Stick Y";
|
|
case XInputAnalogEnum::RIGHT_STICK_X:
|
|
return "Right Stick X";
|
|
case XInputAnalogEnum::RIGHT_STICK_Y:
|
|
return "Right Stick Y";
|
|
default:
|
|
break;
|
|
}
|
|
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)
|
|
|
|
XInputManager::XInputManager() {}
|
|
XInputManager::~XInputManager() {}
|
|
void XInputManager::stop() {}
|
|
std::vector<uint8_t> XInputManager::get_available_players() {
|
|
return {};
|
|
}
|
|
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
|
|
return 0.5f;
|
|
}
|
|
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in) {
|
|
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...");
|
|
|
|
// many systems ship only a newer XInput runtime, so try the known
|
|
// DLL names in order of preference until one loads
|
|
static const char *xinput_dll_names[] = {
|
|
"xinput1_4.dll",
|
|
"xinput1_3.dll",
|
|
"xinput9_1_0.dll",
|
|
};
|
|
const char *loaded_name = nullptr;
|
|
for (const char *name : xinput_dll_names) {
|
|
this->xinput_lib = LoadLibraryA(name);
|
|
if (this->xinput_lib) {
|
|
loaded_name = name;
|
|
break;
|
|
}
|
|
}
|
|
if (!this->xinput_lib) {
|
|
log_warning("xinput", "failed to load any XInput DLL");
|
|
return;
|
|
}
|
|
log_info("xinput", "loaded {}", loaded_name);
|
|
XInputGetState_addr = reinterpret_cast<decltype(XInputGetState) *>(
|
|
GetProcAddress(this->xinput_lib, "XInputGetState"));
|
|
if (!XInputGetState_addr) {
|
|
log_warning("xinput", "failed to get XInputGetState address");
|
|
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");
|
|
}
|
|
|
|
XInputManager::~XInputManager() {
|
|
this->stop();
|
|
}
|
|
|
|
void XInputManager::stop() {
|
|
this->initialized = false;
|
|
if (this->xinput_lib) {
|
|
FreeLibrary(this->xinput_lib);
|
|
this->xinput_lib = nullptr;
|
|
}
|
|
XInputGetState_addr = nullptr;
|
|
XInputSetState_addr = nullptr;
|
|
log_info("xinput", "destroyed");
|
|
}
|
|
|
|
std::vector<uint8_t> XInputManager::get_available_players() {
|
|
std::vector<uint8_t> players;
|
|
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.push_back(i);
|
|
}
|
|
}
|
|
return players;
|
|
}
|
|
|
|
static void normalize_stick(
|
|
SHORT raw_x,
|
|
SHORT raw_y,
|
|
float deadzone,
|
|
float &out_x,
|
|
float &out_y) {
|
|
|
|
const int x_i = static_cast<int>(raw_x);
|
|
const int y_i = -static_cast<int>(raw_y);
|
|
|
|
const float x_f = static_cast<float>(x_i);
|
|
const float y_f = static_cast<float>(y_i);
|
|
const float magnitude = std::sqrt(x_f * x_f + y_f * y_f);
|
|
|
|
// within deadzone; ignore
|
|
if (magnitude <= deadzone) {
|
|
out_x = 0.5f;
|
|
out_y = 0.5f;
|
|
return;
|
|
}
|
|
|
|
// scale value starting with 0 from deadzone border
|
|
const float magnitude_clamped = std::min(magnitude, 32767.0f);
|
|
const float scaled = (magnitude_clamped - deadzone) / (32767.0f - deadzone);
|
|
|
|
// normalize
|
|
const float normalized_x = (x_f / magnitude) * scaled;
|
|
const float normalized_y = (y_f / magnitude) * scaled;
|
|
|
|
// convert range to [0, 1] with 0.5 as center
|
|
out_x = std::clamp((normalized_x + 1.f) / 2.f, 0.f, 1.f);
|
|
out_y = std::clamp((normalized_y + 1.f) / 2.f, 0.f, 1.f);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
XINPUT_STATE x = {};
|
|
if (XInputGetState_addr(player, &x) != ERROR_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
state.wButtons = x.Gamepad.wButtons;
|
|
state.bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f;
|
|
state.bRightTrigger = x.Gamepad.bRightTrigger / 255.0f;
|
|
|
|
// apply circular deadzone logic to left stick
|
|
normalize_stick(
|
|
x.Gamepad.sThumbLX,
|
|
x.Gamepad.sThumbLY,
|
|
static_cast<float>(XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE),
|
|
state.sThumbLX,
|
|
state.sThumbLY);
|
|
|
|
// apply circular deadzone logic to right stick
|
|
normalize_stick(
|
|
x.Gamepad.sThumbRX,
|
|
x.Gamepad.sThumbRY,
|
|
static_cast<float>(XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE),
|
|
state.sThumbRX,
|
|
state.sThumbRY);
|
|
}
|
|
|
|
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in) {
|
|
XINPUT_GAMEPAD_STATE_NORMALIZED state_on_stack;
|
|
XINPUT_GAMEPAD_STATE_NORMALIZED *state;
|
|
|
|
if (state_in) {
|
|
state = state_in;
|
|
} else {
|
|
state = &state_on_stack;
|
|
get_state(player, *state);
|
|
}
|
|
|
|
switch (button) {
|
|
case XInputButtonEnum::DPAD_UP:
|
|
return (state->wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
|
|
case XInputButtonEnum::DPAD_DOWN:
|
|
return (state->wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
|
|
case XInputButtonEnum::DPAD_LEFT:
|
|
return (state->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
|
|
case XInputButtonEnum::DPAD_RIGHT:
|
|
return (state->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
|
|
case XInputButtonEnum::START:
|
|
return (state->wButtons & XINPUT_GAMEPAD_START) != 0;
|
|
case XInputButtonEnum::BACK:
|
|
return (state->wButtons & XINPUT_GAMEPAD_BACK) != 0;
|
|
case XInputButtonEnum::LEFT_STICK:
|
|
return (state->wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
|
|
case XInputButtonEnum::RIGHT_STICK:
|
|
return (state->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
|
|
case XInputButtonEnum::LEFT_SHOULDER:
|
|
return (state->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
|
|
case XInputButtonEnum::RIGHT_SHOULDER:
|
|
return (state->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
|
|
case XInputButtonEnum::BUTTON_A:
|
|
return (state->wButtons & XINPUT_GAMEPAD_A) != 0;
|
|
case XInputButtonEnum::BUTTON_B:
|
|
return (state->wButtons & XINPUT_GAMEPAD_B) != 0;
|
|
case XInputButtonEnum::BUTTON_X:
|
|
return (state->wButtons & XINPUT_GAMEPAD_X) != 0;
|
|
case XInputButtonEnum::BUTTON_Y:
|
|
return (state->wButtons & XINPUT_GAMEPAD_Y) != 0;
|
|
case XInputButtonEnum::LEFT_TRIGGER:
|
|
return state->bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
|
case XInputButtonEnum::RIGHT_TRIGGER:
|
|
return state->bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
|
case XInputButtonEnum::LEFT_STICK_UP:
|
|
return state->sThumbLY < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
|
case XInputButtonEnum::LEFT_STICK_DOWN:
|
|
return state->sThumbLY > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
|
case XInputButtonEnum::LEFT_STICK_LEFT:
|
|
return state->sThumbLX < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
|
case XInputButtonEnum::LEFT_STICK_RIGHT:
|
|
return state->sThumbLX > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
|
case XInputButtonEnum::RIGHT_STICK_UP:
|
|
return state->sThumbRY < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
|
case XInputButtonEnum::RIGHT_STICK_DOWN:
|
|
return state->sThumbRY > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
|
case XInputButtonEnum::RIGHT_STICK_LEFT:
|
|
return state->sThumbRX < (0.5f - GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
|
case XInputButtonEnum::RIGHT_STICK_RIGHT:
|
|
return state->sThumbRX > (0.5f + GAMEPAD_THUMB_DIGITAL_THRESHOLD);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
|
|
XINPUT_GAMEPAD_STATE_NORMALIZED state;
|
|
|
|
get_state(player, state);
|
|
switch (analog) {
|
|
case XInputAnalogEnum::LEFT_TRIGGER:
|
|
return state.bLeftTrigger;
|
|
case XInputAnalogEnum::RIGHT_TRIGGER:
|
|
return state.bRightTrigger;
|
|
case XInputAnalogEnum::LEFT_STICK_X:
|
|
return state.sThumbLX;
|
|
case XInputAnalogEnum::LEFT_STICK_Y:
|
|
return state.sThumbLY;
|
|
case XInputAnalogEnum::RIGHT_STICK_X:
|
|
return state.sThumbRX;
|
|
case XInputAnalogEnum::RIGHT_STICK_Y:
|
|
return state.sThumbRY;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0.5f;
|
|
}
|
|
|
|
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
|
|
|
|
constexpr std::array<XInputButtonEnum, static_cast<size_t>(XInputButtonEnum::COUNT)> button_priority = {
|
|
// the ordering here is important; we want to check buttons first, then dpad, then analog
|
|
// this is to help with cases like DDR pads that output multiple at the same time for arrows
|
|
|
|
// actual buttons
|
|
XInputButtonEnum::BUTTON_A,
|
|
XInputButtonEnum::BUTTON_B,
|
|
XInputButtonEnum::BUTTON_X,
|
|
XInputButtonEnum::BUTTON_Y,
|
|
XInputButtonEnum::START,
|
|
XInputButtonEnum::BACK,
|
|
XInputButtonEnum::LEFT_STICK,
|
|
XInputButtonEnum::RIGHT_STICK,
|
|
XInputButtonEnum::LEFT_SHOULDER,
|
|
XInputButtonEnum::RIGHT_SHOULDER,
|
|
|
|
// dpad
|
|
XInputButtonEnum::DPAD_UP,
|
|
XInputButtonEnum::DPAD_DOWN,
|
|
XInputButtonEnum::DPAD_LEFT,
|
|
XInputButtonEnum::DPAD_RIGHT,
|
|
|
|
// analog values that can be used as buttons
|
|
XInputButtonEnum::LEFT_TRIGGER,
|
|
XInputButtonEnum::RIGHT_TRIGGER,
|
|
XInputButtonEnum::LEFT_STICK_UP,
|
|
XInputButtonEnum::LEFT_STICK_DOWN,
|
|
XInputButtonEnum::LEFT_STICK_LEFT,
|
|
XInputButtonEnum::LEFT_STICK_RIGHT,
|
|
XInputButtonEnum::RIGHT_STICK_UP,
|
|
XInputButtonEnum::RIGHT_STICK_DOWN,
|
|
XInputButtonEnum::RIGHT_STICK_LEFT,
|
|
XInputButtonEnum::RIGHT_STICK_RIGHT,
|
|
};
|
|
|
|
for (uint8_t player = 0; player < XUSER_MAX_COUNT; player++) {
|
|
XINPUT_GAMEPAD_STATE_NORMALIZED state;
|
|
get_state(player, state);
|
|
for (uint16_t b = 0; b < static_cast<uint16_t>(XInputButtonEnum::COUNT); b++) {
|
|
if (is_button_pressed(player, button_priority[b], &state)) {
|
|
button.player = player;
|
|
button.button = button_priority[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
|
|
|
|
}
|