Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_steam_hori.c
9912 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#ifdef SDL_JOYSTICK_HIDAPI2324#include "../SDL_sysjoystick.h"25#include "SDL_hidapijoystick_c.h"26#include "SDL_hidapi_rumble.h"27#include "../SDL_joystick_c.h"2829#ifdef SDL_JOYSTICK_HIDAPI_STEAM_HORI3031/* Define this if you want to log all packets from the controller */32/*#define DEBUG_HORI_PROTOCOL*/3334#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))3536enum37{38SDL_GAMEPAD_BUTTON_HORI_QAM = 11,39SDL_GAMEPAD_BUTTON_HORI_FR,40SDL_GAMEPAD_BUTTON_HORI_FL,41SDL_GAMEPAD_BUTTON_HORI_M1,42SDL_GAMEPAD_BUTTON_HORI_M2,43SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L,44SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R,45SDL_GAMEPAD_NUM_HORI_BUTTONS46};4748typedef struct49{50Uint8 last_state[USB_PACKET_LENGTH];51Uint64 sensor_ticks;52Uint32 last_tick;53bool wireless;54bool serial_needs_init;55} SDL_DriverSteamHori_Context;5657static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device);5859static void HIDAPI_DriverSteamHori_RegisterHints(SDL_HintCallback callback, void *userdata)60{61SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata);62}6364static void HIDAPI_DriverSteamHori_UnregisterHints(SDL_HintCallback callback, void *userdata)65{66SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata);67}6869static bool HIDAPI_DriverSteamHori_IsEnabled(void)70{71return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));72}7374static bool HIDAPI_DriverSteamHori_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)75{76return SDL_IsJoystickHoriSteamController(vendor_id, product_id);77}7879static bool HIDAPI_DriverSteamHori_InitDevice(SDL_HIDAPI_Device *device)80{81SDL_DriverSteamHori_Context *ctx;8283ctx = (SDL_DriverSteamHori_Context *)SDL_calloc(1, sizeof(*ctx));84if (!ctx) {85return false;86}8788device->context = ctx;89ctx->serial_needs_init = true;9091HIDAPI_SetDeviceName(device, "Wireless HORIPAD For Steam");9293return HIDAPI_JoystickConnected(device, NULL);94}9596static int HIDAPI_DriverSteamHori_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)97{98return -1;99}100101static void HIDAPI_DriverSteamHori_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)102{103}104105static bool HIDAPI_DriverSteamHori_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)106{107SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context;108109SDL_AssertJoysticksLocked();110111SDL_zeroa(ctx->last_state);112113/* Initialize the joystick capabilities */114joystick->nbuttons = SDL_GAMEPAD_NUM_HORI_BUTTONS;115joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;116joystick->nhats = 1;117118ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT;119120if (ctx->wireless && device->serial) {121joystick->serial = SDL_strdup(device->serial);122ctx->serial_needs_init = false;123} else if (!ctx->wireless) {124// Need to actual read from the device to init the serial125HIDAPI_DriverSteamHori_UpdateDevice(device);126}127128SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);129SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);130131return true;132}133134static bool HIDAPI_DriverSteamHori_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)135{136// Device doesn't support rumble137return SDL_Unsupported();138}139140static bool HIDAPI_DriverSteamHori_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)141{142return SDL_Unsupported();143}144145static Uint32 HIDAPI_DriverSteamHori_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)146{147return 0;148}149150static bool HIDAPI_DriverSteamHori_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)151{152return SDL_Unsupported();153}154155static bool HIDAPI_DriverSteamHori_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)156{157return SDL_Unsupported();158}159160static bool HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)161{162return true;163}164165#undef clamp166#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))167168#ifndef DEG2RAD169#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f))170#endif171172//---------------------------------------------------------------------------173// Scale and clamp values to a range174//---------------------------------------------------------------------------175static float RemapValClamped(float val, float A, float B, float C, float D)176{177if (A == B) {178return (val - B) >= 0.0f ? D : C;179} else {180float cVal = (val - A) / (B - A);181cVal = clamp(cVal, 0.0f, 1.0f);182183return C + (D - C) * cVal;184}185}186187#define REPORT_HEADER_USB 0x07188#define REPORT_HEADER_BT 0x00189190static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSteamHori_Context *ctx, Uint8 *data, int size)191{192Sint16 axis;193Uint64 timestamp = SDL_GetTicksNS();194195// Make sure it's gamepad state and not OTA FW update info196if (data[0] != REPORT_HEADER_USB && data[0] != REPORT_HEADER_BT) {197/* We don't know how to handle this report */198return;199}200201#define READ_STICK_AXIS(offset) \202(data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), -0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16))203{204axis = READ_STICK_AXIS(1);205SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);206axis = READ_STICK_AXIS(2);207SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);208axis = READ_STICK_AXIS(3);209SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);210axis = READ_STICK_AXIS(4);211SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);212}213#undef READ_STICK_AXIS214215if (ctx->last_state[5] != data[5]) {216Uint8 hat;217218switch (data[5] & 0xF) {219case 0:220hat = SDL_HAT_UP;221break;222case 1:223hat = SDL_HAT_RIGHTUP;224break;225case 2:226hat = SDL_HAT_RIGHT;227break;228case 3:229hat = SDL_HAT_RIGHTDOWN;230break;231case 4:232hat = SDL_HAT_DOWN;233break;234case 5:235hat = SDL_HAT_LEFTDOWN;236break;237case 6:238hat = SDL_HAT_LEFT;239break;240case 7:241hat = SDL_HAT_LEFTUP;242break;243default:244hat = SDL_HAT_CENTERED;245break;246}247SDL_SendJoystickHat(timestamp, joystick, 0, hat);248SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x10) != 0));249SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x20) != 0));250SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_QAM, ((data[5] & 0x40) != 0));251SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x80) != 0));252253}254255if (ctx->last_state[6] != data[6]) {256SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[6] & 0x01) != 0));257SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M1 /* M1 */, ((data[6] & 0x02) != 0));258SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[6] & 0x04) != 0));259SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[6] & 0x08) != 0));260261// TODO: can we handle the digital trigger mode? The data seems to come through analog regardless of the trigger state262SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x40) != 0));263SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x80) != 0));264}265266if (ctx->last_state[7] != data[7]) {267SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[7] & 0x01) != 0));268SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[7] & 0x02) != 0));269SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[7] & 0x04) != 0));270SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M2, ((data[7] & 0x08) != 0));271SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, ((data[7] & 0x10) != 0));272SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, ((data[7] & 0x20) != 0));273SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FR, ((data[7] & 0x40) != 0));274SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0));275}276277if (!ctx->wireless && ctx->serial_needs_init) {278char serial[18];279(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",280data[38], data[39], data[40], data[41], data[42], data[43]);281282joystick->serial = SDL_strdup(serial);283ctx->serial_needs_init = false;284}285286#define READ_TRIGGER_AXIS(offset) \287(Sint16)(((int)data[offset] * 257) - 32768)288{289axis = READ_TRIGGER_AXIS(8);290SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);291axis = READ_TRIGGER_AXIS(9);292SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);293}294#undef READ_TRIGGER_AXIS295296if (1) {297Uint64 sensor_timestamp;298float imu_data[3];299300/* 16-bit timestamp */301Uint32 delta;302Uint16 tick = LOAD16(data[10],303data[11]);304if (ctx->last_tick < tick) {305delta = (tick - ctx->last_tick);306} else {307delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1);308}309310ctx->last_tick = tick;311ctx->sensor_ticks += delta;312313/* Sensor timestamp is in 1us units, but there seems to be some issues with the values reported from the device */314sensor_timestamp = timestamp; // if the values were good we woudl call SDL_US_TO_NS(ctx->sensor_ticks);315316const float accelScale = SDL_STANDARD_GRAVITY * 8 / 32768.0f;317const float gyroScale = DEG2RAD(2048);318319imu_data[1] = RemapValClamped(-1.0f * LOAD16(data[12], data[13]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);320imu_data[2] = RemapValClamped(-1.0f * LOAD16(data[14], data[15]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);321imu_data[0] = RemapValClamped(-1.0f * LOAD16(data[16], data[17]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);322323324SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, imu_data, 3);325326// SDL_Log("%u %f, %f, %f ", data[0], imu_data[0], imu_data[1], imu_data[2] );327imu_data[2] = LOAD16(data[18], data[19]) * accelScale;328imu_data[1] = -1 * LOAD16(data[20], data[21]) * accelScale;329imu_data[0] = LOAD16(data[22], data[23]) * accelScale;330SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, imu_data, 3);331}332333if (ctx->last_state[24] != data[24]) {334bool bCharging = (data[24] & 0x10) != 0;335int percent = (data[24] & 0xF) * 10;336SDL_PowerState state;337if (bCharging) {338state = SDL_POWERSTATE_CHARGING;339} else if (ctx->wireless) {340state = SDL_POWERSTATE_ON_BATTERY;341} else {342state = SDL_POWERSTATE_CHARGED;343}344345SDL_SendJoystickPowerInfo(joystick, state, percent);346}347348SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));349}350351static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device)352{353SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context;354SDL_Joystick *joystick = NULL;355Uint8 data[USB_PACKET_LENGTH];356int size = 0;357358if (device->num_joysticks > 0) {359joystick = SDL_GetJoystickFromID(device->joysticks[0]);360} else {361return false;362}363364while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {365#ifdef DEBUG_HORI_PROTOCOL366HIDAPI_DumpPacket("Google Hori packet: size = %d", data, size);367#endif368if (!joystick) {369continue;370}371372HIDAPI_DriverSteamHori_HandleStatePacket(joystick, ctx, data, size);373}374375if (size < 0) {376/* Read error, device is disconnected */377HIDAPI_JoystickDisconnected(device, device->joysticks[0]);378}379return (size >= 0);380}381382static void HIDAPI_DriverSteamHori_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)383{384}385386static void HIDAPI_DriverSteamHori_FreeDevice(SDL_HIDAPI_Device *device)387{388}389390SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori = {391SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI,392true,393HIDAPI_DriverSteamHori_RegisterHints,394HIDAPI_DriverSteamHori_UnregisterHints,395HIDAPI_DriverSteamHori_IsEnabled,396HIDAPI_DriverSteamHori_IsSupportedDevice,397HIDAPI_DriverSteamHori_InitDevice,398HIDAPI_DriverSteamHori_GetDevicePlayerIndex,399HIDAPI_DriverSteamHori_SetDevicePlayerIndex,400HIDAPI_DriverSteamHori_UpdateDevice,401HIDAPI_DriverSteamHori_OpenJoystick,402HIDAPI_DriverSteamHori_RumbleJoystick,403HIDAPI_DriverSteamHori_RumbleJoystickTriggers,404HIDAPI_DriverSteamHori_GetJoystickCapabilities,405HIDAPI_DriverSteamHori_SetJoystickLED,406HIDAPI_DriverSteamHori_SendJoystickEffect,407HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled,408HIDAPI_DriverSteamHori_CloseJoystick,409HIDAPI_DriverSteamHori_FreeDevice,410};411412#endif /* SDL_JOYSTICK_HIDAPI_STEAM_HORI */413414#endif /* SDL_JOYSTICK_HIDAPI */415416417