123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 2025 Mitchell Cairns <[email protected]>
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
- #include "SDL_internal.h"
- #ifdef SDL_JOYSTICK_HIDAPI
- #include "../../SDL_hints_c.h"
- #include "../SDL_sysjoystick.h"
- #include "SDL_hidapijoystick_c.h"
- #include "SDL_hidapi_rumble.h"
- #ifdef SDL_JOYSTICK_HIDAPI_SINPUT
- /*****************************************************************************************************/
- // This protocol is documented at:
- // https://docs.handheldlegend.com/s/sinput/doc/sinput-hid-protocol-TkPYWlDMAg
- /*****************************************************************************************************/
- // Define this if you want to log all packets from the controller
- #if 0
- #define DEBUG_SINPUT_PROTOCOL
- #endif
- #if 0
- #define DEBUG_SINPUT_INIT
- #endif
- #define SINPUT_DEVICE_REPORT_SIZE 64 // Size of input reports (And CMD Input reports)
- #define SINPUT_DEVICE_REPORT_COMMAND_SIZE 48 // Size of command OUTPUT reports
- #define SINPUT_DEVICE_REPORT_ID_JOYSTICK_INPUT 0x01
- #define SINPUT_DEVICE_REPORT_ID_INPUT_CMDDAT 0x02
- #define SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT 0x03
- #define SINPUT_DEVICE_COMMAND_HAPTIC 0x01
- #define SINPUT_DEVICE_COMMAND_FEATURES 0x02
- #define SINPUT_DEVICE_COMMAND_PLAYERLED 0x03
- #define SINPUT_DEVICE_COMMAND_JOYSTICKRGB 0x04
- #define SINPUT_HAPTIC_TYPE_PRECISE 0x01
- #define SINPUT_HAPTIC_TYPE_ERMSIMULATION 0x02
- #define SINPUT_DEFAULT_GYRO_SENS 2000
- #define SINPUT_DEFAULT_ACCEL_SENS 8
- #define SINPUT_REPORT_IDX_BUTTONS_0 3
- #define SINPUT_REPORT_IDX_BUTTONS_1 4
- #define SINPUT_REPORT_IDX_BUTTONS_2 5
- #define SINPUT_REPORT_IDX_BUTTONS_3 6
- #define SINPUT_REPORT_IDX_LEFT_X 7
- #define SINPUT_REPORT_IDX_LEFT_Y 9
- #define SINPUT_REPORT_IDX_RIGHT_X 11
- #define SINPUT_REPORT_IDX_RIGHT_Y 13
- #define SINPUT_REPORT_IDX_LEFT_TRIGGER 15
- #define SINPUT_REPORT_IDX_RIGHT_TRIGGER 17
- #define SINPUT_REPORT_IDX_IMU_TIMESTAMP 19
- #define SINPUT_REPORT_IDX_IMU_ACCEL_X 23
- #define SINPUT_REPORT_IDX_IMU_ACCEL_Y 25
- #define SINPUT_REPORT_IDX_IMU_ACCEL_Z 27
- #define SINPUT_REPORT_IDX_IMU_GYRO_X 29
- #define SINPUT_REPORT_IDX_IMU_GYRO_Y 31
- #define SINPUT_REPORT_IDX_IMU_GYRO_Z 33
- #define SINPUT_REPORT_IDX_TOUCH1_X 35
- #define SINPUT_REPORT_IDX_TOUCH1_Y 37
- #define SINPUT_REPORT_IDX_TOUCH1_P 39
- #define SINPUT_REPORT_IDX_TOUCH2_X 41
- #define SINPUT_REPORT_IDX_TOUCH2_Y 43
- #define SINPUT_REPORT_IDX_TOUCH2_P 45
- #define SINPUT_BUTTON_IDX_SOUTH 0
- #define SINPUT_BUTTON_IDX_EAST 1
- #define SINPUT_BUTTON_IDX_WEST 2
- #define SINPUT_BUTTON_IDX_NORTH 3
- #define SINPUT_BUTTON_IDX_DPAD_UP 4
- #define SINPUT_BUTTON_IDX_DPAD_DOWN 5
- #define SINPUT_BUTTON_IDX_DPAD_LEFT 6
- #define SINPUT_BUTTON_IDX_DPAD_RIGHT 7
- #define SINPUT_BUTTON_IDX_LEFT_STICK 8
- #define SINPUT_BUTTON_IDX_RIGHT_STICK 9
- #define SINPUT_BUTTON_IDX_LEFT_BUMPER 10
- #define SINPUT_BUTTON_IDX_RIGHT_BUMPER 11
- #define SINPUT_BUTTON_IDX_LEFT_TRIGGER 12
- #define SINPUT_BUTTON_IDX_RIGHT_TRIGGER 13
- #define SINPUT_BUTTON_IDX_LEFT_PADDLE1 14
- #define SINPUT_BUTTON_IDX_RIGHT_PADDLE1 15
- #define SINPUT_BUTTON_IDX_START 16
- #define SINPUT_BUTTON_IDX_BACK 17
- #define SINPUT_BUTTON_IDX_GUIDE 18
- #define SINPUT_BUTTON_IDX_CAPTURE 19
- #define SINPUT_BUTTON_IDX_LEFT_PADDLE2 20
- #define SINPUT_BUTTON_IDX_RIGHT_PADDLE2 21
- #define SINPUT_BUTTON_IDX_TOUCHPAD1 22
- #define SINPUT_BUTTON_IDX_TOUCHPAD2 23
- #define SINPUT_BUTTON_IDX_POWER 24
- #define SINPUT_BUTTON_IDX_MISC4 25
- #define SINPUT_BUTTON_IDX_MISC5 26
- #define SINPUT_BUTTON_IDX_MISC6 27
- #define SINPUT_BUTTON_IDX_MISC7 28
- #define SINPUT_BUTTON_IDX_MISC8 29
- #define SINPUT_BUTTON_IDX_MISC9 30
- #define SINPUT_BUTTON_IDX_MISC10 31
- #define SINPUT_REPORT_IDX_COMMAND_RESPONSE_ID 1
- #define SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK 2
- #define SINPUT_REPORT_IDX_PLUG_STATUS 1
- #define SINPUT_REPORT_IDX_CHARGE_LEVEL 2
- #define SINPUT_MAX_ALLOWED_TOUCHPADS 2
- #ifndef EXTRACTSINT16
- #define EXTRACTSINT16(data, idx) ((Sint16)((data)[(idx)] | ((data)[(idx) + 1] << 8)))
- #endif
- #ifndef EXTRACTUINT16
- #define EXTRACTUINT16(data, idx) ((Uint16)((data)[(idx)] | ((data)[(idx) + 1] << 8)))
- #endif
- #ifndef EXTRACTUINT32
- #define EXTRACTUINT32(data, idx) ((Uint32)((data)[(idx)] | ((data)[(idx) + 1] << 8) | ((data)[(idx) + 2] << 16) | ((data)[(idx) + 3] << 24)))
- #endif
- typedef struct
- {
- uint8_t type;
- union {
- // Frequency Amplitude pairs
- struct {
- struct {
- uint16_t frequency_1;
- uint16_t amplitude_1;
- uint16_t frequency_2;
- uint16_t amplitude_2;
- } left;
- struct {
- uint16_t frequency_1;
- uint16_t amplitude_1;
- uint16_t frequency_2;
- uint16_t amplitude_2;
- } right;
- } type_1;
- // Basic ERM simulation model
- struct {
- struct {
- uint8_t amplitude;
- bool brake;
- } left;
- struct {
- uint8_t amplitude;
- bool brake;
- } right;
- } type_2;
- };
- } SINPUT_HAPTIC_S;
- typedef struct
- {
- SDL_HIDAPI_Device *device;
- Uint16 protocol_version;
- bool sensors_enabled;
- Uint8 player_idx;
- bool player_leds_supported;
- bool joystick_rgb_supported;
- bool rumble_supported;
- bool accelerometer_supported;
- bool gyroscope_supported;
- bool left_analog_stick_supported;
- bool right_analog_stick_supported;
- bool left_analog_trigger_supported;
- bool right_analog_trigger_supported;
- bool dpad_supported;
- bool touchpad_supported;
- Uint8 touchpad_count; // 2 touchpads maximum
- Uint8 touchpad_finger_count; // 2 fingers for one touchpad, or 1 per touchpad (2 max)
- Uint8 polling_rate_ms;
- Uint8 sub_type; // Subtype of the device, 0 in most cases
- Uint16 accelRange; // Example would be 2,4,8,16 +/- (g-force)
- Uint16 gyroRange; // Example would be 1000,2000,4000 +/- (degrees per second)
- float accelScale; // Scale factor for accelerometer values
- float gyroScale; // Scale factor for gyroscope values
- Uint8 last_state[USB_PACKET_LENGTH];
- Uint8 buttons_count;
- Uint8 usage_masks[4];
- Uint32 last_imu_timestamp_us;
- Uint64 imu_timestamp_ns; // Nanoseconds. We accumulate with received deltas
- } SDL_DriverSInput_Context;
- // Converts raw int16_t gyro scale setting
- static inline float CalculateGyroScale(uint16_t dps_range)
- {
- return SDL_PI_F / 180.0f / (32768.0f / (float)dps_range);
- }
- // Converts raw int16_t accel scale setting
- static inline float CalculateAccelScale(uint16_t g_range)
- {
- return SDL_STANDARD_GRAVITY / (32768.0f / (float)g_range);
- }
- static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data)
- {
- SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
- // Obtain protocol version
- ctx->protocol_version = EXTRACTUINT16(data, 0);
- // Bitfields are not portable, so we unpack them into a struct value
- ctx->rumble_supported = (data[2] & 0x01) != 0;
- ctx->player_leds_supported = (data[2] & 0x02) != 0;
- ctx->accelerometer_supported = (data[2] & 0x04) != 0;
- ctx->gyroscope_supported = (data[2] & 0x08) != 0;
- ctx->left_analog_stick_supported = (data[2] & 0x10) != 0;
- ctx->right_analog_stick_supported = (data[2] & 0x20) != 0;
- ctx->left_analog_trigger_supported = (data[2] & 0x40) != 0;
- ctx->right_analog_trigger_supported = (data[2] & 0x80) != 0;
- ctx->touchpad_supported = (data[3] & 0x01) != 0;
- ctx->joystick_rgb_supported = (data[3] & 0x02) != 0;
- SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;
- type = (SDL_GamepadType)SDL_clamp(data[4], SDL_GAMEPAD_TYPE_UNKNOWN, SDL_GAMEPAD_TYPE_COUNT);
- device->type = type;
- // The 4 MSB represent a button layout style SDL_GamepadFaceStyle
- // The 4 LSB represent a device sub-type
- device->guid.data[15] = data[5];
- #if defined(DEBUG_SINPUT_INIT)
- SDL_Log("SInput Face Style: %d", (data[5] & 0xF0) >> 4);
- SDL_Log("SInput Sub-type: %d", (data[5] & 0xF));
- #endif
- ctx->polling_rate_ms = data[6];
- ctx->accelRange = EXTRACTUINT16(data, 8);
- ctx->gyroRange = EXTRACTUINT16(data, 10);
- // Masks in LSB to MSB
- // South, East, West, North, DUp, DDown, DLeft, DRight
- ctx->usage_masks[0] = data[12];
- // Stick Left, Stick Right, L Shoulder, R Shoulder,
- // L Trigger, R Trigger, L Paddle 1, R Paddle 1
- ctx->usage_masks[1] = data[13];
- // Start, Back, Guide, Capture, L Paddle 2, R Paddle 2, Touchpad L, Touchpad R
- ctx->usage_masks[2] = data[14];
- // Power, Misc 4 to 10
- ctx->usage_masks[3] = data[15];
- // Derive button count from mask
- for (Uint8 byte = 0; byte < 4; ++byte) {
- for (Uint8 bit = 0; bit < 8; ++bit) {
- if ((ctx->usage_masks[byte] & (1 << bit)) != 0) {
- ++ctx->buttons_count;
- }
- }
- }
- // Convert DPAD to hat
- const int DPAD_MASK = (1 << SINPUT_BUTTON_IDX_DPAD_UP) |
- (1 << SINPUT_BUTTON_IDX_DPAD_DOWN) |
- (1 << SINPUT_BUTTON_IDX_DPAD_LEFT) |
- (1 << SINPUT_BUTTON_IDX_DPAD_RIGHT);
- if ((ctx->usage_masks[0] & DPAD_MASK) == DPAD_MASK) {
- ctx->dpad_supported = true;
- ctx->usage_masks[0] &= ~DPAD_MASK;
- ctx->buttons_count -= 4;
- }
- #if defined(DEBUG_SINPUT_INIT)
- SDL_Log("Buttons count: %d", ctx->buttons_count);
- #endif
- // Get and validate touchpad parameters
- ctx->touchpad_count = data[16];
- ctx->touchpad_finger_count = data[17];
- #if defined(DEBUG_SINPUT_INIT)
- SDL_Log("Accelerometer Range: %d", ctx->accelRange);
- #endif
- #if defined(DEBUG_SINPUT_INIT)
- SDL_Log("Gyro Range: %d", ctx->gyroRange);
- #endif
- ctx->accelScale = CalculateAccelScale(ctx->accelRange);
- ctx->gyroScale = CalculateGyroScale(ctx->gyroRange);
- }
- static bool RetrieveSDLFeatures(SDL_HIDAPI_Device *device)
- {
- int written = 0;
- // Attempt to send the SDL features get command.
- for (int attempt = 0; attempt < 8; ++attempt) {
- const Uint8 featuresGetCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_FEATURES };
- // This write will occasionally return -1, so ignore failure here and try again
- written = SDL_hid_write(device->dev, featuresGetCommand, sizeof(featuresGetCommand));
- if (written == SINPUT_DEVICE_REPORT_COMMAND_SIZE) {
- break;
- }
- }
- if (written < SINPUT_DEVICE_REPORT_COMMAND_SIZE) {
- SDL_SetError("SInput device SDL Features GET command could not write");
- return false;
- }
- int read = 0;
- // Read the reply
- for (int i = 0; i < 100; ++i) {
- SDL_Delay(1);
- Uint8 data[USB_PACKET_LENGTH];
- read = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0);
- if (read < 0) {
- SDL_SetError("SInput device SDL Features GET command could not read");
- return false;
- }
- if (read == 0) {
- continue;
- }
- #ifdef DEBUG_SINPUT_PROTOCOL
- HIDAPI_DumpPacket("SInput packet: size = %d", data, size);
- #endif
- if ((read == USB_PACKET_LENGTH) && (data[0] == SINPUT_DEVICE_REPORT_ID_INPUT_CMDDAT) && (data[1] == SINPUT_DEVICE_COMMAND_FEATURES)) {
- ProcessSDLFeaturesResponse(device, &(data[SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK]));
- #if defined(DEBUG_SINPUT_INIT)
- SDL_Log("Received SInput SDL Features command response");
- #endif
- return true;
- }
- }
- return false;
- }
- // Type 2 haptics are for more traditional rumble such as
- // ERM motors or simulated ERM motors
- static inline void HapticsType2Pack(SINPUT_HAPTIC_S *in, Uint8 *out)
- {
- // Type of haptics
- out[0] = 2;
- out[1] = in->type_2.left.amplitude;
- out[2] = in->type_2.left.brake;
- out[3] = in->type_2.right.amplitude;
- out[4] = in->type_2.right.brake;
- }
- static void HIDAPI_DriverSInput_RegisterHints(SDL_HintCallback callback, void *userdata)
- {
- SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, callback, userdata);
- }
- static void HIDAPI_DriverSInput_UnregisterHints(SDL_HintCallback callback, void *userdata)
- {
- SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, callback, userdata);
- }
- static bool HIDAPI_DriverSInput_IsEnabled(void)
- {
- return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
- }
- static bool HIDAPI_DriverSInput_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
- {
- return SDL_IsJoystickSInputController(vendor_id, product_id);
- }
- static bool HIDAPI_DriverSInput_InitDevice(SDL_HIDAPI_Device *device)
- {
- #if defined(DEBUG_SINPUT_INIT)
- SDL_Log("SInput device Init");
- #endif
- SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)SDL_calloc(1, sizeof(*ctx));
- if (!ctx) {
- return false;
- }
- ctx->device = device;
- device->context = ctx;
- if (!RetrieveSDLFeatures(device)) {
- return false;
- }
- return HIDAPI_JoystickConnected(device, NULL);
- }
- static int HIDAPI_DriverSInput_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
- {
- return -1;
- }
- static void HIDAPI_DriverSInput_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
- {
- SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
- if (ctx->player_leds_supported) {
- player_index = SDL_clamp(player_index + 1, 0, 255);
- Uint8 player_num = (Uint8)player_index;
- ctx->player_idx = player_num;
- // Set player number, finalizing the setup
- Uint8 playerLedCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_PLAYERLED, ctx->player_idx };
- int playerNumBytesWritten = SDL_hid_write(device->dev, playerLedCommand, SINPUT_DEVICE_REPORT_COMMAND_SIZE);
- if (playerNumBytesWritten < 0) {
- SDL_SetError("SInput device player led command could not write");
- }
- }
- }
- #ifndef DEG2RAD
- #define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f))
- #endif
- static bool HIDAPI_DriverSInput_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
- {
- #if defined(DEBUG_SINPUT_INIT)
- SDL_Log("SInput device Open");
- #endif
- SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
- SDL_AssertJoysticksLocked();
- joystick->nbuttons = ctx->buttons_count;
- SDL_zeroa(ctx->last_state);
- int axes = 0;
- if (ctx->left_analog_stick_supported) {
- axes += 2;
- }
- if (ctx->right_analog_stick_supported) {
- axes += 2;
- }
- if (ctx->left_analog_trigger_supported) {
- ++axes;
- }
- if (ctx->right_analog_trigger_supported) {
- ++axes;
- }
- joystick->naxes = axes;
- if (ctx->dpad_supported) {
- joystick->nhats = 1;
- }
- if (ctx->accelerometer_supported) {
- SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 1000.0f / ctx->polling_rate_ms);
- }
- if (ctx->gyroscope_supported) {
- SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 1000.0f / ctx->polling_rate_ms);
- }
- if (ctx->touchpad_supported) {
- // If touchpad is supported, minimum 1, max is capped
- ctx->touchpad_count = SDL_clamp(ctx->touchpad_count, 1, SINPUT_MAX_ALLOWED_TOUCHPADS);
- if (ctx->touchpad_count > 1) {
- // Support two separate touchpads with 1 finger each
- // or support one touchpad with 2 fingers max
- ctx->touchpad_finger_count = 1;
- }
- if (ctx->touchpad_count > 0) {
- SDL_PrivateJoystickAddTouchpad(joystick, ctx->touchpad_finger_count);
- }
- if (ctx->touchpad_count > 1) {
- SDL_PrivateJoystickAddTouchpad(joystick, ctx->touchpad_finger_count);
- }
- }
- return true;
- }
- static bool HIDAPI_DriverSInput_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
- {
- SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
- if (ctx->rumble_supported) {
- SINPUT_HAPTIC_S hapticData = { 0 };
- Uint8 hapticReport[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_HAPTIC };
- // Low Frequency = Left
- // High Frequency = Right
- hapticData.type_2.left.amplitude = (Uint8) (low_frequency_rumble >> 8);
- hapticData.type_2.right.amplitude = (Uint8)(high_frequency_rumble >> 8);
- HapticsType2Pack(&hapticData, &(hapticReport[2]));
- SDL_HIDAPI_SendRumble(device, hapticReport, SINPUT_DEVICE_REPORT_COMMAND_SIZE);
- return true;
- }
- return SDL_Unsupported();
- }
- static bool HIDAPI_DriverSInput_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
- {
- return SDL_Unsupported();
- }
- static Uint32 HIDAPI_DriverSInput_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
- {
- SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
- Uint32 caps = 0;
- if (ctx->rumble_supported) {
- caps |= SDL_JOYSTICK_CAP_RUMBLE;
- }
- if (ctx->player_leds_supported) {
- caps |= SDL_JOYSTICK_CAP_PLAYER_LED;
- }
- if (ctx->joystick_rgb_supported) {
- caps |= SDL_JOYSTICK_CAP_RGB_LED;
- }
- return caps;
- }
- static bool HIDAPI_DriverSInput_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
- {
- SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
- if (ctx->player_leds_supported) {
- // Set player number, finalizing the setup
- Uint8 joystickRGBCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_JOYSTICKRGB, red, green, blue };
- int joystickRGBBytesWritten = SDL_hid_write(device->dev, joystickRGBCommand, SINPUT_DEVICE_REPORT_COMMAND_SIZE);
- if (joystickRGBBytesWritten < 0) {
- SDL_SetError("SInput device joystick rgb command could not write");
- return false;
- }
- return true;
- }
- return SDL_Unsupported();
- }
- static bool HIDAPI_DriverSInput_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
- {
- return SDL_Unsupported();
- }
- static bool HIDAPI_DriverSInput_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
- {
- SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
- if (ctx->accelerometer_supported || ctx->gyroscope_supported) {
- ctx->sensors_enabled = enabled;
- return true;
- }
- return SDL_Unsupported();
- }
- static void HIDAPI_DriverSInput_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSInput_Context *ctx, Uint8 *data, int size)
- {
- Sint16 axis = 0;
- Sint16 accel = 0;
- Sint16 gyro = 0;
- Uint64 timestamp = SDL_GetTicksNS();
- float imu_values[3] = { 0 };
- Uint8 output_idx = 0;
- // Process digital buttons according to the supplied
- // button mask to create a contiguous button input set
- for (Uint8 processes = 0; processes < 4; ++processes) {
- Uint8 button_idx = SINPUT_REPORT_IDX_BUTTONS_0 + processes;
- for (Uint8 buttons = 0; buttons < 8; ++buttons) {
- // If a button is enabled by our usage mask
- const Uint8 mask = (0x01 << buttons);
- if ((ctx->usage_masks[processes] & mask) != 0) {
- bool down = (data[button_idx] & mask) != 0;
- if ( (output_idx < SDL_GAMEPAD_BUTTON_COUNT) && (ctx->last_state[button_idx] != data[button_idx]) ) {
- SDL_SendJoystickButton(timestamp, joystick, output_idx, down);
- }
- ++output_idx;
- }
- }
- }
- if (ctx->dpad_supported) {
- Uint8 hat = SDL_HAT_CENTERED;
- if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_UP)) {
- hat |= SDL_HAT_UP;
- }
- if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_DOWN)) {
- hat |= SDL_HAT_DOWN;
- }
- if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_LEFT)) {
- hat |= SDL_HAT_LEFT;
- }
- if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_RIGHT)) {
- hat |= SDL_HAT_RIGHT;
- }
- SDL_SendJoystickHat(timestamp, joystick, 0, hat);
- }
- // Analog inputs map to a signed Sint16 range of -32768 to 32767 from the device.
- // Use an axis index because not all gamepads will have the same axis inputs.
- Uint8 axis_idx = 0;
- // Left Analog Stick
- axis = 0; // Reset axis value for joystick
- if (ctx->left_analog_stick_supported) {
- axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_X);
- SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
- ++axis_idx;
- axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_Y);
- SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
- ++axis_idx;
- }
- // Right Analog Stick
- axis = 0; // Reset axis value for joystick
- if (ctx->right_analog_stick_supported) {
- axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_X);
- SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
- ++axis_idx;
- axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_Y);
- SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
- ++axis_idx;
- }
- // Left Analog Trigger
- axis = SDL_MIN_SINT16; // Reset axis value for trigger
- if (ctx->left_analog_trigger_supported) {
- axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_TRIGGER);
- SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
- ++axis_idx;
- }
- // Right Analog Trigger
- axis = SDL_MIN_SINT16; // Reset axis value for trigger
- if (ctx->right_analog_trigger_supported) {
- axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_TRIGGER);
- SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
- }
- // Battery/Power state handling
- if (ctx->last_state[SINPUT_REPORT_IDX_PLUG_STATUS] != data[SINPUT_REPORT_IDX_PLUG_STATUS] ||
- ctx->last_state[SINPUT_REPORT_IDX_CHARGE_LEVEL] != data[SINPUT_REPORT_IDX_CHARGE_LEVEL]) {
- SDL_PowerState state = SDL_POWERSTATE_UNKNOWN;
- Uint8 status = data[SINPUT_REPORT_IDX_PLUG_STATUS];
- int percent = data[SINPUT_REPORT_IDX_CHARGE_LEVEL];
- percent = SDL_clamp(percent, 0, 100); // Ensure percent is within valid range
- switch (status) {
- case 1:
- state = SDL_POWERSTATE_NO_BATTERY;
- percent = 0;
- break;
- case 2:
- state = SDL_POWERSTATE_CHARGING;
- break;
- case 3:
- state = SDL_POWERSTATE_CHARGED;
- percent = 100;
- break;
- case 4:
- state = SDL_POWERSTATE_ON_BATTERY;
- break;
- default:
- break;
- }
- if (state != SDL_POWERSTATE_UNKNOWN) {
- SDL_SendJoystickPowerInfo(joystick, state, percent);
- }
- }
- // Extract the IMU timestamp delta (in microseconds)
- Uint32 imu_timestamp_us = EXTRACTUINT32(data, SINPUT_REPORT_IDX_IMU_TIMESTAMP);
- Uint32 imu_time_delta_us = 0;
- // Check if we should process IMU data and if sensors are enabled
- if (ctx->sensors_enabled) {
- if (imu_timestamp_us >= ctx->last_imu_timestamp_us) {
- imu_time_delta_us = (imu_timestamp_us - ctx->last_imu_timestamp_us);
- } else {
- // Handle rollover case
- imu_time_delta_us = (UINT32_MAX - ctx->last_imu_timestamp_us) + imu_timestamp_us + 1;
- }
- // Convert delta to nanoseconds and update running timestamp
- ctx->imu_timestamp_ns += (Uint64)imu_time_delta_us * 1000;
- // Update last timestamp
- ctx->last_imu_timestamp_us = imu_timestamp_us;
- // Process Accelerometer
- if (ctx->accelerometer_supported) {
- accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_Y);
- imu_values[2] = -(float)accel * ctx->accelScale; // Y-axis acceleration
- accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_Z);
- imu_values[1] = (float)accel * ctx->accelScale; // Z-axis acceleration
- accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_X);
- imu_values[0] = -(float)accel * ctx->accelScale; // X-axis acceleration
- SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->imu_timestamp_ns, imu_values, 3);
- }
- // Process Gyroscope
- if (ctx->gyroscope_supported) {
- gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_Y);
- imu_values[2] = -(float)gyro * ctx->gyroScale; // Y-axis rotation
- gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_Z);
- imu_values[1] = (float)gyro * ctx->gyroScale; // Z-axis rotation
- gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_X);
- imu_values[0] = -(float)gyro * ctx->gyroScale; // X-axis rotation
- SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->imu_timestamp_ns, imu_values, 3);
- }
- }
- // Check if we should process touchpad
- if (ctx->touchpad_supported && ctx->touchpad_count > 0) {
- Uint8 touchpad = 0;
- Uint8 finger = 0;
- Sint16 touch1X = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH1_X);
- Sint16 touch1Y = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH1_Y);
- Uint16 touch1P = EXTRACTUINT16(data, SINPUT_REPORT_IDX_TOUCH1_P);
- Sint16 touch2X = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH2_X);
- Sint16 touch2Y = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH2_Y);
- Uint16 touch2P = EXTRACTUINT16(data, SINPUT_REPORT_IDX_TOUCH2_P);
- SDL_SendJoystickTouchpad(timestamp, joystick, touchpad, finger,
- touch1P > 0,
- touch1X / 65536.0f + 0.5f,
- touch1Y / 65536.0f + 0.5f,
- touch1P / 32768.0f);
- if (ctx->touchpad_count > 1) {
- ++touchpad;
- } else if (ctx->touchpad_finger_count > 1) {
- ++finger;
- }
- if ((touchpad > 0) || (finger > 0)) {
- SDL_SendJoystickTouchpad(timestamp, joystick, touchpad, finger,
- touch2P > 0,
- touch2X / 65536.0f + 0.5f,
- touch2Y / 65536.0f + 0.5f,
- touch2P / 32768.0f);
- }
- }
- SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
- }
- static bool HIDAPI_DriverSInput_UpdateDevice(SDL_HIDAPI_Device *device)
- {
- SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
- SDL_Joystick *joystick = NULL;
- Uint8 data[USB_PACKET_LENGTH];
- int size = 0;
- if (device->num_joysticks > 0) {
- joystick = SDL_GetJoystickFromID(device->joysticks[0]);
- } else {
- return false;
- }
- while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
- #ifdef DEBUG_SINPUT_PROTOCOL
- HIDAPI_DumpPacket("SInput packet: size = %d", data, size);
- #endif
- if (!joystick) {
- continue;
- }
- // Handle command response information
- if (data[0] == SINPUT_DEVICE_REPORT_ID_JOYSTICK_INPUT) {
- HIDAPI_DriverSInput_HandleStatePacket(joystick, ctx, data, size);
- }
- }
- if (size < 0) {
- // Read error, device is disconnected
- HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
- }
- return (size >= 0);
- }
- static void HIDAPI_DriverSInput_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
- {
- }
- static void HIDAPI_DriverSInput_FreeDevice(SDL_HIDAPI_Device *device)
- {
- }
- SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSInput = {
- SDL_HINT_JOYSTICK_HIDAPI_SINPUT,
- true,
- HIDAPI_DriverSInput_RegisterHints,
- HIDAPI_DriverSInput_UnregisterHints,
- HIDAPI_DriverSInput_IsEnabled,
- HIDAPI_DriverSInput_IsSupportedDevice,
- HIDAPI_DriverSInput_InitDevice,
- HIDAPI_DriverSInput_GetDevicePlayerIndex,
- HIDAPI_DriverSInput_SetDevicePlayerIndex,
- HIDAPI_DriverSInput_UpdateDevice,
- HIDAPI_DriverSInput_OpenJoystick,
- HIDAPI_DriverSInput_RumbleJoystick,
- HIDAPI_DriverSInput_RumbleJoystickTriggers,
- HIDAPI_DriverSInput_GetJoystickCapabilities,
- HIDAPI_DriverSInput_SetJoystickLED,
- HIDAPI_DriverSInput_SendJoystickEffect,
- HIDAPI_DriverSInput_SetJoystickSensorsEnabled,
- HIDAPI_DriverSInput_CloseJoystick,
- HIDAPI_DriverSInput_FreeDevice,
- };
- #endif // SDL_JOYSTICK_HIDAPI_SINPUT
- #endif // SDL_JOYSTICK_HIDAPI
|