mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
rawinput: add basic support for xinput (#617)
## Link to GitHub Issue or related Pull Request, if one exists #616 ## Description of change Adds preliminary support for XInput controllers. Enough to manually bind buttons and map analogs.
This commit is contained in:
@@ -596,6 +596,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
rawinput/smxstage.h
|
rawinput/smxstage.h
|
||||||
rawinput/smxdedicab.cpp
|
rawinput/smxdedicab.cpp
|
||||||
rawinput/smxdedicab.h
|
rawinput/smxdedicab.h
|
||||||
|
rawinput/xinput.cpp
|
||||||
|
rawinput/xinput.h
|
||||||
|
|
||||||
# reader
|
# reader
|
||||||
reader/reader.cpp
|
reader/reader.cpp
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip"
|
|||||||
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
|
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
|
||||||
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
|
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
|
||||||
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
|
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
|
||||||
|
TARGETS_XP32="spicetools_cfg spicetools_spice"
|
||||||
|
TARGETS_XP64="spicetools_spice64"
|
||||||
|
|
||||||
# determine build type
|
# determine build type
|
||||||
BUILD_TYPE="Release"
|
BUILD_TYPE="Release"
|
||||||
@@ -169,7 +171,7 @@ time (
|
|||||||
fi
|
fi
|
||||||
mkdir -p ${BUILDDIR_WINXP_32}
|
mkdir -p ${BUILDDIR_WINXP_32}
|
||||||
pushd ${BUILDDIR_WINXP_32} > /dev/null
|
pushd ${BUILDDIR_WINXP_32} > /dev/null
|
||||||
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_32}
|
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP32}
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
|
|
||||||
# 64 bit Windows XP
|
# 64 bit Windows XP
|
||||||
@@ -182,7 +184,7 @@ time (
|
|||||||
fi
|
fi
|
||||||
mkdir -p ${BUILDDIR_WINXP_64}
|
mkdir -p ${BUILDDIR_WINXP_64}
|
||||||
pushd ${BUILDDIR_WINXP_64} > /dev/null
|
pushd ${BUILDDIR_WINXP_64} > /dev/null
|
||||||
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
|
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP64}
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
else
|
else
|
||||||
echo "Skipping WinXP builds, toolchain not specified"
|
echo "Skipping WinXP builds, toolchain not specified"
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
|
|||||||
return "MIDI Unknown Index " + indexString + " (" + device->desc + ")";
|
return "MIDI Unknown Index " + indexString + " (" + device->desc + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case rawinput::XINPUT_GAMEPAD:
|
||||||
|
return 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:
|
||||||
|
|||||||
+25
-2
@@ -1,5 +1,6 @@
|
|||||||
#include "api.h"
|
#include "api.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "launcher/superexit.h"
|
#include "launcher/superexit.h"
|
||||||
@@ -389,6 +390,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
|||||||
}
|
}
|
||||||
case rawinput::PIUIO_DEVICE: {
|
case rawinput::PIUIO_DEVICE: {
|
||||||
state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||||
|
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));
|
||||||
|
state = manager->XINPUT_MGR->is_button_pressed(
|
||||||
|
player,
|
||||||
|
static_cast<xinput::XInputButtonEnum>(vKey)) ?
|
||||||
|
BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -586,7 +597,7 @@ float Buttons::getVelocity(std::unique_ptr<rawinput::RawInputManager> &manager,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog) {
|
||||||
float value = 0.5f;
|
float value = 0.5f;
|
||||||
if (!device) {
|
if (!device) {
|
||||||
return value;
|
return value;
|
||||||
@@ -767,6 +778,18 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
|||||||
}
|
}
|
||||||
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));
|
||||||
|
value = manager->XINPUT_MGR->get_analog_state(
|
||||||
|
player,
|
||||||
|
static_cast<xinput::XInputAnalogEnum>(index));
|
||||||
|
// invert value
|
||||||
|
if (inverted) {
|
||||||
|
value = 1.f - value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -822,7 +845,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &ana
|
|||||||
return analog.getLastState();
|
return analog.getLastState();
|
||||||
}
|
}
|
||||||
|
|
||||||
float state = getState(device, analog);
|
float state = getState(manager, device, analog);
|
||||||
analog.setLastState(state);
|
analog.setLastState(state);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ namespace GameAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float getState(rawinput::Device *device, Analog &analog);
|
float getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog);
|
||||||
float getState(rawinput::RawInputManager *manager, Analog &analog);
|
float getState(rawinput::RawInputManager *manager, Analog &analog);
|
||||||
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
|
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -417,6 +417,8 @@ 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:
|
||||||
|
return xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey));
|
||||||
case rawinput::DESTROYED:
|
case rawinput::DESTROYED:
|
||||||
return "Device unplugged (" + vKeyString + ")";
|
return "Device unplugged (" + vKeyString + ")";
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -2165,6 +2165,9 @@ namespace overlay::windows {
|
|||||||
case rawinput::MIDI:
|
case rawinput::MIDI:
|
||||||
this->analogs_devices.emplace_back(&device);
|
this->analogs_devices.emplace_back(&device);
|
||||||
break;
|
break;
|
||||||
|
case rawinput::XINPUT_GAMEPAD:
|
||||||
|
this->analogs_devices.emplace_back(&device);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2289,6 +2292,15 @@ namespace overlay::windows {
|
|||||||
onoff.size());
|
onoff.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case rawinput::XINPUT_GAMEPAD: {
|
||||||
|
for (int i = 0; i < static_cast<int>(xinput::XInputAnalogEnum::COUNT); i++) {
|
||||||
|
const auto v = static_cast<xinput::XInputAnalogEnum>(i);
|
||||||
|
control_names.push_back(xinput::get_analog_string(v));
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -732,6 +732,10 @@ namespace overlay::windows {
|
|||||||
ImGui::Text("Type: PIUIO");
|
ImGui::Text("Type: PIUIO");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case rawinput::XINPUT_GAMEPAD: {
|
||||||
|
ImGui::Text("Type: XInput");
|
||||||
|
break;
|
||||||
|
}
|
||||||
case rawinput::DESTROYED: {
|
case rawinput::DESTROYED: {
|
||||||
ImGui::Text("Disconnected.");
|
ImGui::Text("Disconnected.");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ namespace rawinput {
|
|||||||
SEXTET_OUTPUT,
|
SEXTET_OUTPUT,
|
||||||
PIUIO_DEVICE,
|
PIUIO_DEVICE,
|
||||||
SMX_STAGE,
|
SMX_STAGE,
|
||||||
SMX_DEDICAB
|
SMX_DEDICAB,
|
||||||
|
XINPUT_GAMEPAD,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum MouseKeys {
|
enum MouseKeys {
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ void rawinput::set_midi_algorithm(rawinput::MidiNoteAlgorithm new_algo) {
|
|||||||
|
|
||||||
rawinput::RawInputManager::RawInputManager() {
|
rawinput::RawInputManager::RawInputManager() {
|
||||||
|
|
||||||
|
XINPUT_MGR = std::make_unique<xinput::XInputManager>();
|
||||||
|
|
||||||
// create input window and load in devices
|
// create input window and load in devices
|
||||||
this->input_hwnd_create();
|
this->input_hwnd_create();
|
||||||
this->devices_reload();
|
this->devices_reload();
|
||||||
@@ -106,6 +108,8 @@ void rawinput::RawInputManager::stop() {
|
|||||||
// destruct all devices and input window
|
// destruct all devices and input window
|
||||||
this->devices_destruct();
|
this->devices_destruct();
|
||||||
this->input_hwnd_destroy();
|
this->input_hwnd_destroy();
|
||||||
|
|
||||||
|
XINPUT_MGR.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void rawinput::RawInputManager::input_hwnd_create() {
|
void rawinput::RawInputManager::input_hwnd_create() {
|
||||||
@@ -191,6 +195,8 @@ void rawinput::RawInputManager::devices_reload() {
|
|||||||
this->devices_scan_smxdedicab();
|
this->devices_scan_smxdedicab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->devices_scan_xinput();
|
||||||
|
|
||||||
// check for LIT Board
|
// check for LIT Board
|
||||||
sextet_register("COM54", "LIT Board", false);
|
sextet_register("COM54", "LIT Board", false);
|
||||||
|
|
||||||
@@ -1009,6 +1015,27 @@ 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);
|
||||||
|
|
||||||
|
// notify add
|
||||||
|
for (auto &cb : this->callback_add) {
|
||||||
|
cb.f(cb.data, &device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void rawinput::RawInputManager::flush_start() {
|
void rawinput::RawInputManager::flush_start() {
|
||||||
|
|
||||||
// start flush thread
|
// start flush thread
|
||||||
@@ -2734,6 +2761,10 @@ void rawinput::RawInputManager::devices_print() {
|
|||||||
log_misc("rawinput", "device type: SMX_DEDICAB");
|
log_misc("rawinput", "device type: SMX_DEDICAB");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case XINPUT_GAMEPAD: {
|
||||||
|
log_misc("rawinput", "device type: XINPUT");
|
||||||
|
break;
|
||||||
|
}
|
||||||
case UNKNOWN:
|
case UNKNOWN:
|
||||||
default:
|
default:
|
||||||
log_warning("rawinput", "device type: UNKNOWN");
|
log_warning("rawinput", "device type: UNKNOWN");
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "hotplug.h"
|
#include "hotplug.h"
|
||||||
|
#include "rawinput/xinput.h"
|
||||||
#include "util/scope_guard.h"
|
#include "util/scope_guard.h"
|
||||||
|
|
||||||
namespace rawinput {
|
namespace rawinput {
|
||||||
@@ -84,6 +85,7 @@ 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();
|
||||||
@@ -101,6 +103,7 @@ namespace rawinput {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
HWND input_hwnd = nullptr;
|
HWND input_hwnd = nullptr;
|
||||||
|
std::unique_ptr<xinput::XInputManager> XINPUT_MGR = nullptr;
|
||||||
|
|
||||||
RawInputManager();
|
RawInputManager();
|
||||||
~RawInputManager();
|
~RawInputManager();
|
||||||
|
|||||||
@@ -0,0 +1,329 @@
|
|||||||
|
#include "rawinput/xinput.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t dwPacketNumber;
|
||||||
|
XINPUT_GAMEPAD_STATE Gamepad;
|
||||||
|
} XINPUT_STATE;
|
||||||
|
|
||||||
|
DWORD
|
||||||
|
XInputGetState(
|
||||||
|
DWORD dwUserIndex,
|
||||||
|
XINPUT_STATE *pState
|
||||||
|
);
|
||||||
|
|
||||||
|
// end xinput definitions
|
||||||
|
|
||||||
|
std::string get_button_string(XInputButtonEnum button) {
|
||||||
|
switch (button) {
|
||||||
|
case XInputButtonEnum::DPAD_UP:
|
||||||
|
return "Up";
|
||||||
|
case XInputButtonEnum::DPAD_DOWN:
|
||||||
|
return "Down";
|
||||||
|
case XInputButtonEnum::DPAD_LEFT:
|
||||||
|
return "Left";
|
||||||
|
case XInputButtonEnum::DPAD_RIGHT:
|
||||||
|
return "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));
|
||||||
|
}
|
||||||
|
|
||||||
|
#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) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
static decltype(XInputGetState) *XInputGetState_addr = nullptr;
|
||||||
|
|
||||||
|
XInputManager::XInputManager() {
|
||||||
|
log_info("xinput", "initialize...");
|
||||||
|
this->xinput_lib = LoadLibraryA("xinput1_3.dll");
|
||||||
|
if (!this->xinput_lib) {
|
||||||
|
log_warning("xinput", "failed to load xinput1_3.dll");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
initialized = true;
|
||||||
|
log_info("xinput", "initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
XInputManager::~XInputManager() {
|
||||||
|
this->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XInputManager::stop() {
|
||||||
|
if (this->xinput_lib) {
|
||||||
|
FreeLibrary(this->xinput_lib);
|
||||||
|
this->xinput_lib = nullptr;
|
||||||
|
}
|
||||||
|
XInputGetState_addr = nullptr;
|
||||||
|
this->initialized = false;
|
||||||
|
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.emplace_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XInputManager::get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED *state) {
|
||||||
|
*state = {};
|
||||||
|
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;
|
||||||
|
|
||||||
|
// left stick circular dead zone
|
||||||
|
{
|
||||||
|
const auto x_raw = x.Gamepad.sThumbLX;
|
||||||
|
const auto y_raw = x.Gamepad.sThumbLY;
|
||||||
|
const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw);
|
||||||
|
if (magnitude > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
|
||||||
|
const float scaled =
|
||||||
|
(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;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
} else {
|
||||||
|
// within deadzone
|
||||||
|
state->sThumbLX = 0.5f;
|
||||||
|
state->sThumbLY = 0.5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// right stick circular dead zone
|
||||||
|
{
|
||||||
|
const auto x_raw = x.Gamepad.sThumbRX;
|
||||||
|
const auto y_raw = x.Gamepad.sThumbRY;
|
||||||
|
const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw);
|
||||||
|
if (magnitude > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) {
|
||||||
|
const float scaled =
|
||||||
|
(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;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
} else {
|
||||||
|
// within deadzone
|
||||||
|
state->sThumbRX = 0.5f;
|
||||||
|
state->sThumbRY = 0.5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
|
||||||
|
XINPUT_GAMEPAD_STATE_NORMALIZED state;
|
||||||
|
|
||||||
|
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.6f;
|
||||||
|
case XInputButtonEnum::LEFT_STICK_DOWN:
|
||||||
|
return state.sThumbLY < 0.4f;
|
||||||
|
case XInputButtonEnum::LEFT_STICK_LEFT:
|
||||||
|
return state.sThumbLX < 0.4f;
|
||||||
|
case XInputButtonEnum::LEFT_STICK_RIGHT:
|
||||||
|
return state.sThumbLX > 0.6f;
|
||||||
|
case XInputButtonEnum::RIGHT_STICK_UP:
|
||||||
|
return state.sThumbRY > 0.6f;
|
||||||
|
case XInputButtonEnum::RIGHT_STICK_DOWN:
|
||||||
|
return state.sThumbRY < 0.4f;
|
||||||
|
case XInputButtonEnum::RIGHT_STICK_LEFT:
|
||||||
|
return state.sThumbRX < 0.4f;
|
||||||
|
case XInputButtonEnum::RIGHT_STICK_RIGHT:
|
||||||
|
return state.sThumbRX > 0.6f;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace xinput {
|
||||||
|
|
||||||
|
#define XUSER_MAX_COUNT 4
|
||||||
|
|
||||||
|
typedef struct _XINPUT_GAMEPAD_STATE {
|
||||||
|
uint16_t wButtons;
|
||||||
|
uint8_t bLeftTrigger;
|
||||||
|
uint8_t bRightTrigger;
|
||||||
|
int16_t sThumbLX;
|
||||||
|
int16_t sThumbLY;
|
||||||
|
int16_t sThumbRX;
|
||||||
|
int16_t sThumbRY;
|
||||||
|
} XINPUT_GAMEPAD_STATE;
|
||||||
|
|
||||||
|
typedef struct _XINPUT_GAMEPAD_STATE_NORMALIZED {
|
||||||
|
uint16_t wButtons;
|
||||||
|
float bLeftTrigger;
|
||||||
|
float bRightTrigger;
|
||||||
|
float sThumbLX;
|
||||||
|
float sThumbLY;
|
||||||
|
float sThumbRX;
|
||||||
|
float sThumbRY;
|
||||||
|
} XINPUT_GAMEPAD_STATE_NORMALIZED;
|
||||||
|
|
||||||
|
enum class XInputButtonEnum {
|
||||||
|
// actual buttons
|
||||||
|
DPAD_UP,
|
||||||
|
DPAD_DOWN,
|
||||||
|
DPAD_LEFT,
|
||||||
|
DPAD_RIGHT,
|
||||||
|
START,
|
||||||
|
BACK,
|
||||||
|
LEFT_STICK,
|
||||||
|
RIGHT_STICK,
|
||||||
|
LEFT_SHOULDER,
|
||||||
|
RIGHT_SHOULDER,
|
||||||
|
BUTTON_A,
|
||||||
|
BUTTON_B,
|
||||||
|
BUTTON_X,
|
||||||
|
BUTTON_Y,
|
||||||
|
|
||||||
|
// analog values that can be used as buttons
|
||||||
|
LEFT_TRIGGER,
|
||||||
|
RIGHT_TRIGGER,
|
||||||
|
LEFT_STICK_UP,
|
||||||
|
LEFT_STICK_DOWN,
|
||||||
|
LEFT_STICK_LEFT,
|
||||||
|
LEFT_STICK_RIGHT,
|
||||||
|
RIGHT_STICK_UP,
|
||||||
|
RIGHT_STICK_DOWN,
|
||||||
|
RIGHT_STICK_LEFT,
|
||||||
|
RIGHT_STICK_RIGHT,
|
||||||
|
|
||||||
|
COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class XInputAnalogEnum {
|
||||||
|
// analog values
|
||||||
|
LEFT_TRIGGER,
|
||||||
|
RIGHT_TRIGGER,
|
||||||
|
LEFT_STICK_X,
|
||||||
|
LEFT_STICK_Y,
|
||||||
|
RIGHT_STICK_X,
|
||||||
|
RIGHT_STICK_Y,
|
||||||
|
|
||||||
|
COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string get_button_string(XInputButtonEnum button);
|
||||||
|
std::string get_analog_string(XInputAnalogEnum analog);
|
||||||
|
|
||||||
|
class XInputManager {
|
||||||
|
private:
|
||||||
|
bool initialized = false;
|
||||||
|
HMODULE xinput_lib = nullptr;
|
||||||
|
void get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED *state);
|
||||||
|
public:
|
||||||
|
XInputManager();
|
||||||
|
~XInputManager();
|
||||||
|
void stop();
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user