Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_steam.c
9906 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_hints_c.h"25#include "../SDL_sysjoystick.h"26#include "SDL_hidapijoystick_c.h"2728#ifdef SDL_JOYSTICK_HIDAPI_STEAM2930// Define this if you want to log all packets from the controller31// #define DEBUG_STEAM_PROTOCOL3233#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED "SDL_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED"3435#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)36// This requires prompting for Bluetooth permissions, so make sure the application really wants it37#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT false38#else39#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)40#endif4142#define PAIRING_STATE_DURATION_SECONDS 60434445/*****************************************************************************************************/4647#include "steam/controller_constants.h"48#include "steam/controller_structs.h"4950enum51{52SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE = 11,53SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE,54SDL_GAMEPAD_NUM_STEAM_BUTTONS,55};5657typedef struct SteamControllerStateInternal_t58{59// Controller Type for this Controller State60Uint32 eControllerType;6162// If packet num matches that on your prior call, then the controller state hasn't been changed since63// your last call and there is no need to process it64Uint32 unPacketNum;6566// bit flags for each of the buttons67Uint64 ulButtons;6869// Left pad coordinates70short sLeftPadX;71short sLeftPadY;7273// Right pad coordinates74short sRightPadX;75short sRightPadY;7677// Center pad coordinates78short sCenterPadX;79short sCenterPadY;8081// Left analog stick coordinates82short sLeftStickX;83short sLeftStickY;8485// Right analog stick coordinates86short sRightStickX;87short sRightStickY;8889unsigned short sTriggerL;90unsigned short sTriggerR;9192short sAccelX;93short sAccelY;94short sAccelZ;9596short sGyroX;97short sGyroY;98short sGyroZ;99100float sGyroQuatW;101float sGyroQuatX;102float sGyroQuatY;103float sGyroQuatZ;104105short sGyroSteeringAngle;106107unsigned short sBatteryLevel;108109// Pressure sensor data.110unsigned short sPressurePadLeft;111unsigned short sPressurePadRight;112113unsigned short sPressureBumperLeft;114unsigned short sPressureBumperRight;115116// Internal state data117short sPrevLeftPad[2];118short sPrevLeftStick[2];119} SteamControllerStateInternal_t;120121// Defines for ulButtons in SteamControllerStateInternal_t122#define STEAM_RIGHT_TRIGGER_MASK 0x00000001123#define STEAM_LEFT_TRIGGER_MASK 0x00000002124#define STEAM_RIGHT_BUMPER_MASK 0x00000004125#define STEAM_LEFT_BUMPER_MASK 0x00000008126#define STEAM_BUTTON_NORTH_MASK 0x00000010 // Y127#define STEAM_BUTTON_EAST_MASK 0x00000020 // B128#define STEAM_BUTTON_WEST_MASK 0x00000040 // X129#define STEAM_BUTTON_SOUTH_MASK 0x00000080 // A130#define STEAM_DPAD_UP_MASK 0x00000100 // DPAD UP131#define STEAM_DPAD_RIGHT_MASK 0x00000200 // DPAD RIGHT132#define STEAM_DPAD_LEFT_MASK 0x00000400 // DPAD LEFT133#define STEAM_DPAD_DOWN_MASK 0x00000800 // DPAD DOWN134#define STEAM_BUTTON_MENU_MASK 0x00001000 // SELECT135#define STEAM_BUTTON_STEAM_MASK 0x00002000 // GUIDE136#define STEAM_BUTTON_ESCAPE_MASK 0x00004000 // START137#define STEAM_BUTTON_BACK_LEFT_MASK 0x00008000138#define STEAM_BUTTON_BACK_RIGHT_MASK 0x00010000139#define STEAM_BUTTON_LEFTPAD_CLICKED_MASK 0x00020000140#define STEAM_BUTTON_RIGHTPAD_CLICKED_MASK 0x00040000141#define STEAM_LEFTPAD_FINGERDOWN_MASK 0x00080000142#define STEAM_RIGHTPAD_FINGERDOWN_MASK 0x00100000143#define STEAM_JOYSTICK_BUTTON_MASK 0x00400000144#define STEAM_LEFTPAD_AND_JOYSTICK_MASK 0x00800000145146// Look for report version 0x0001, type WIRELESS (3), length >= 1 byte147#define D0G_IS_VALID_WIRELESS_EVENT(data, len) ((len) >= 5 && (data)[0] == 1 && (data)[1] == 0 && (data)[2] == 3 && (data)[3] >= 1)148#define D0G_GET_WIRELESS_EVENT_TYPE(data) ((data)[4])149#define D0G_WIRELESS_DISCONNECTED 1150#define D0G_WIRELESS_ESTABLISHED 2151#define D0G_WIRELESS_NEWLYPAIRED 3152153#define D0G_IS_WIRELESS_DISCONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) == D0G_WIRELESS_DISCONNECTED)154#define D0G_IS_WIRELESS_CONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) != D0G_WIRELESS_DISCONNECTED)155156157#define MAX_REPORT_SEGMENT_PAYLOAD_SIZE 18158/*159* SteamControllerPacketAssembler has to be used when reading output repots from controllers.160*/161typedef struct162{163uint8_t uBuffer[MAX_REPORT_SEGMENT_PAYLOAD_SIZE * 8 + 1];164int nExpectedSegmentNumber;165bool bIsBle;166} SteamControllerPacketAssembler;167168#undef clamp169#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))170171#undef offsetof172#define offsetof(s, m) (size_t) & (((s *)0)->m)173174#ifdef DEBUG_STEAM_CONTROLLER175#define DPRINTF(format, ...) printf(format, ##__VA_ARGS__)176#define HEXDUMP(ptr, len) hexdump(ptr, len)177#else178#define DPRINTF(format, ...)179#define HEXDUMP(ptr, len)180#endif181#define printf SDL_Log182183#define MAX_REPORT_SEGMENT_SIZE (MAX_REPORT_SEGMENT_PAYLOAD_SIZE + 2)184#define CALC_REPORT_SEGMENT_NUM(index) ((index / MAX_REPORT_SEGMENT_PAYLOAD_SIZE) & 0x07)185#define REPORT_SEGMENT_DATA_FLAG 0x80186#define REPORT_SEGMENT_LAST_FLAG 0x40187#define BLE_REPORT_NUMBER 0x03188189#define STEAMCONTROLLER_TRIGGER_MAX_ANALOG 26000190191// Enable mouse mode when using the Steam Controller locally192#undef ENABLE_MOUSE_MODE193194// Wireless firmware quirk: the firmware intentionally signals "failure" when performing195// SET_FEATURE / GET_FEATURE when it actually means "pending radio roundtrip". The only196// way to make SET_FEATURE / GET_FEATURE work is to loop several times with a sleep. If197// it takes more than 50ms to get the response for SET_FEATURE / GET_FEATURE, we assume198// that the controller has failed.199#define RADIO_WORKAROUND_SLEEP_ATTEMPTS 50200#define RADIO_WORKAROUND_SLEEP_DURATION_US 500201202// This was defined by experimentation. 2000 seemed to work but to give that extra bit of margin, set to 3ms.203#define CONTROLLER_CONFIGURATION_DELAY_US 3000204205static uint8_t GetSegmentHeader(int nSegmentNumber, bool bLastPacket)206{207uint8_t header = REPORT_SEGMENT_DATA_FLAG;208header |= nSegmentNumber;209if (bLastPacket) {210header |= REPORT_SEGMENT_LAST_FLAG;211}212213return header;214}215216static void hexdump(const uint8_t *ptr, int len)217{218HIDAPI_DumpPacket("Data", ptr, len);219}220221static void ResetSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler)222{223SDL_memset(pAssembler->uBuffer, 0, sizeof(pAssembler->uBuffer));224pAssembler->nExpectedSegmentNumber = 0;225}226227static void InitializeSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, bool bIsBle)228{229pAssembler->bIsBle = bIsBle;230ResetSteamControllerPacketAssembler(pAssembler);231}232233// Returns:234// <0 on error235// 0 on not ready236// Complete packet size on completion237static int WriteSegmentToSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, const uint8_t *pSegment, int nSegmentLength)238{239if (pAssembler->bIsBle) {240uint8_t uSegmentHeader = pSegment[1];241int nSegmentNumber = uSegmentHeader & 0x07;242243HEXDUMP(pSegment, nSegmentLength);244245if (pSegment[0] != BLE_REPORT_NUMBER) {246// We may get keyboard/mouse input events until controller stops sending them247return 0;248}249250if (nSegmentLength != MAX_REPORT_SEGMENT_SIZE) {251printf("Bad segment size! %d\n", nSegmentLength);252hexdump(pSegment, nSegmentLength);253ResetSteamControllerPacketAssembler(pAssembler);254return -1;255}256257DPRINTF("GOT PACKET HEADER = 0x%x\n", uSegmentHeader);258259if (!(uSegmentHeader & REPORT_SEGMENT_DATA_FLAG)) {260// We get empty segments, just ignore them261return 0;262}263264if (nSegmentNumber != pAssembler->nExpectedSegmentNumber) {265ResetSteamControllerPacketAssembler(pAssembler);266267if (nSegmentNumber) {268// This happens occasionally269DPRINTF("Bad segment number, got %d, expected %d\n",270nSegmentNumber, pAssembler->nExpectedSegmentNumber);271return -1;272}273}274275SDL_memcpy(pAssembler->uBuffer + nSegmentNumber * MAX_REPORT_SEGMENT_PAYLOAD_SIZE,276pSegment + 2, // ignore header and report number277MAX_REPORT_SEGMENT_PAYLOAD_SIZE);278279if (uSegmentHeader & REPORT_SEGMENT_LAST_FLAG) {280pAssembler->nExpectedSegmentNumber = 0;281return (nSegmentNumber + 1) * MAX_REPORT_SEGMENT_PAYLOAD_SIZE;282}283284pAssembler->nExpectedSegmentNumber++;285} else {286// Just pass through287SDL_memcpy(pAssembler->uBuffer,288pSegment,289nSegmentLength);290return nSegmentLength;291}292293return 0;294}295296#define BLE_MAX_READ_RETRIES 8297298static int SetFeatureReport(SDL_HIDAPI_Device *dev, const unsigned char uBuffer[65], int nActualDataLen)299{300int nRet = -1;301302DPRINTF("SetFeatureReport %p %p %d\n", dev, uBuffer, nActualDataLen);303304if (dev->is_bluetooth) {305int nSegmentNumber = 0;306uint8_t uPacketBuffer[MAX_REPORT_SEGMENT_SIZE];307const unsigned char *pBufferPtr = uBuffer + 1;308309if (nActualDataLen < 1) {310return -1;311}312313// Skip report number in data314nActualDataLen--;315316while (nActualDataLen > 0) {317int nBytesInPacket = nActualDataLen > MAX_REPORT_SEGMENT_PAYLOAD_SIZE ? MAX_REPORT_SEGMENT_PAYLOAD_SIZE : nActualDataLen;318319nActualDataLen -= nBytesInPacket;320321// Construct packet322SDL_memset(uPacketBuffer, 0, sizeof(uPacketBuffer));323uPacketBuffer[0] = BLE_REPORT_NUMBER;324uPacketBuffer[1] = GetSegmentHeader(nSegmentNumber, nActualDataLen == 0);325SDL_memcpy(&uPacketBuffer[2], pBufferPtr, nBytesInPacket);326327pBufferPtr += nBytesInPacket;328nSegmentNumber++;329330nRet = SDL_hid_send_feature_report(dev->dev, uPacketBuffer, sizeof(uPacketBuffer));331}332} else {333for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) {334nRet = SDL_hid_send_feature_report(dev->dev, uBuffer, 65);335if (nRet >= 0) {336break;337}338339SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000);340}341}342343DPRINTF("SetFeatureReport() ret = %d\n", nRet);344345return nRet;346}347348static int GetFeatureReport(SDL_HIDAPI_Device *dev, unsigned char uBuffer[65])349{350int nRet = -1;351352DPRINTF("GetFeatureReport( %p %p )\n", dev, uBuffer);353354if (dev->is_bluetooth) {355int nRetries = 0;356uint8_t uSegmentBuffer[MAX_REPORT_SEGMENT_SIZE + 1];357uint8_t ucBytesToRead = MAX_REPORT_SEGMENT_SIZE;358uint8_t ucDataStartOffset = 0;359360SteamControllerPacketAssembler assembler;361InitializeSteamControllerPacketAssembler(&assembler, dev->is_bluetooth);362363// On Windows and macOS, BLE devices get 2 copies of the feature report ID, one that is removed by ReadFeatureReport,364// and one that's included in the buffer we receive. We pad the bytes to read and skip over the report ID365// if necessary.366#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_MACOS)367++ucBytesToRead;368++ucDataStartOffset;369#endif370371while (nRetries < BLE_MAX_READ_RETRIES) {372SDL_memset(uSegmentBuffer, 0, sizeof(uSegmentBuffer));373uSegmentBuffer[0] = BLE_REPORT_NUMBER;374nRet = SDL_hid_get_feature_report(dev->dev, uSegmentBuffer, ucBytesToRead);375376DPRINTF("GetFeatureReport ble ret=%d\n", nRet);377HEXDUMP(uSegmentBuffer, nRet);378379// Zero retry counter if we got data380if (nRet > 2 && (uSegmentBuffer[ucDataStartOffset + 1] & REPORT_SEGMENT_DATA_FLAG)) {381nRetries = 0;382} else {383nRetries++;384}385386if (nRet > 0) {387int nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&assembler,388uSegmentBuffer + ucDataStartOffset,389nRet - ucDataStartOffset);390391if (nPacketLength > 0 && nPacketLength < 65) {392// Leave space for "report number"393uBuffer[0] = 0;394SDL_memcpy(uBuffer + 1, assembler.uBuffer, nPacketLength);395return nPacketLength;396}397}398}399printf("Could not get a full ble packet after %d retries\n", nRetries);400return -1;401} else {402SDL_memset(uBuffer, 0, 65);403404for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) {405nRet = SDL_hid_get_feature_report(dev->dev, uBuffer, 65);406if (nRet >= 0) {407break;408}409410SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000);411}412413DPRINTF("GetFeatureReport USB ret=%d\n", nRet);414HEXDUMP(uBuffer, nRet);415}416417return nRet;418}419420static int ReadResponse(SDL_HIDAPI_Device *dev, uint8_t uBuffer[65], int nExpectedResponse)421{422for (int nRetries = 0; nRetries < 10; nRetries++) {423int nRet = GetFeatureReport(dev, uBuffer);424425DPRINTF("ReadResponse( %p %p 0x%x )\n", dev, uBuffer, nExpectedResponse);426427if (nRet < 0) {428continue;429}430431DPRINTF("ReadResponse got %d bytes of data: ", nRet);432HEXDUMP(uBuffer, nRet);433434if (uBuffer[1] != nExpectedResponse) {435continue;436}437438return nRet;439}440return -1;441}442443//---------------------------------------------------------------------------444// Reset steam controller (unmap buttons and pads) and re-fetch capability bits445//---------------------------------------------------------------------------446static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew, uint32_t *punUpdateRateUS)447{448// Firmware quirk: Set Feature and Get Feature requests always require a 65-byte buffer.449unsigned char buf[65];450unsigned int i;451int res = -1;452int nSettings = 0;453int nAttributesLength;454FeatureReportMsg *msg;455uint32_t unUpdateRateUS = 9000; // Good default rate456457DPRINTF("ResetSteamController hid=%p\n", dev);458459buf[0] = 0;460buf[1] = ID_GET_ATTRIBUTES_VALUES;461res = SetFeatureReport(dev, buf, 2);462if (res < 0) {463if (!bSuppressErrorSpew) {464printf("GET_ATTRIBUTES_VALUES failed for controller %p\n", dev);465}466return false;467}468469// Retrieve GET_ATTRIBUTES_VALUES result470// Wireless controller endpoints without a connected controller will return nAttrs == 0471res = ReadResponse(dev, buf, ID_GET_ATTRIBUTES_VALUES);472if (res < 0 || buf[1] != ID_GET_ATTRIBUTES_VALUES) {473HEXDUMP(buf, res);474if (!bSuppressErrorSpew) {475printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev);476}477return false;478}479480nAttributesLength = buf[2];481if (nAttributesLength > res) {482if (!bSuppressErrorSpew) {483printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev);484}485return false;486}487488msg = (FeatureReportMsg *)&buf[1];489for (i = 0; i < (int)msg->header.length / sizeof(ControllerAttribute); ++i) {490uint8_t unAttribute = msg->payload.getAttributes.attributes[i].attributeTag;491uint32_t unValue = msg->payload.getAttributes.attributes[i].attributeValue;492493switch (unAttribute) {494case ATTRIB_UNIQUE_ID:495break;496case ATTRIB_PRODUCT_ID:497break;498case ATTRIB_CAPABILITIES:499break;500case ATTRIB_CONNECTION_INTERVAL_IN_US:501unUpdateRateUS = unValue;502break;503default:504break;505}506}507if (punUpdateRateUS) {508*punUpdateRateUS = unUpdateRateUS;509}510511// Clear digital button mappings512buf[0] = 0;513buf[1] = ID_CLEAR_DIGITAL_MAPPINGS;514res = SetFeatureReport(dev, buf, 2);515if (res < 0) {516if (!bSuppressErrorSpew) {517printf("CLEAR_DIGITAL_MAPPINGS failed for controller %p\n", dev);518}519return false;520}521522// Reset the default settings523SDL_memset(buf, 0, 65);524buf[1] = ID_LOAD_DEFAULT_SETTINGS;525buf[2] = 0;526res = SetFeatureReport(dev, buf, 3);527if (res < 0) {528if (!bSuppressErrorSpew) {529printf("LOAD_DEFAULT_SETTINGS failed for controller %p\n", dev);530}531return false;532}533534// Apply custom settings - clear trackpad modes (cancel mouse emulation), etc535#define ADD_SETTING(SETTING, VALUE) \536buf[3 + nSettings * 3] = SETTING; \537buf[3 + nSettings * 3 + 1] = ((uint16_t)VALUE) & 0xFF; \538buf[3 + nSettings * 3 + 2] = ((uint16_t)VALUE) >> 8; \539++nSettings;540541SDL_memset(buf, 0, 65);542buf[1] = ID_SET_SETTINGS_VALUES;543ADD_SETTING(SETTING_WIRELESS_PACKET_VERSION, 2);544ADD_SETTING(SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE);545#ifdef ENABLE_MOUSE_MODE546ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE);547ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 1);548ADD_SETTING(SETTING_MOMENTUM_MAXIMUM_VELOCITY, 20000); // [0-20000] default 8000549ADD_SETTING(SETTING_MOMENTUM_DECAY_AMOUNT, 50); // [0-50] default 5550#else551ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE);552ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 0);553#endif554buf[2] = (unsigned char)(nSettings * 3);555556res = SetFeatureReport(dev, buf, 3 + nSettings * 3);557if (res < 0) {558if (!bSuppressErrorSpew) {559printf("SET_SETTINGS failed for controller %p\n", dev);560}561return false;562}563564#ifdef ENABLE_MOUSE_MODE565// Wait for ID_CLEAR_DIGITAL_MAPPINGS to be processed on the controller566bool bMappingsCleared = false;567int iRetry;568for (iRetry = 0; iRetry < 2; ++iRetry) {569SDL_memset(buf, 0, 65);570buf[1] = ID_GET_DIGITAL_MAPPINGS;571buf[2] = 1; // one byte - requesting from index 0572buf[3] = 0;573res = SetFeatureReport(dev, buf, 4);574if (res < 0) {575printf("GET_DIGITAL_MAPPINGS failed for controller %p\n", dev);576return false;577}578579res = ReadResponse(dev, buf, ID_GET_DIGITAL_MAPPINGS);580if (res < 0 || buf[1] != ID_GET_DIGITAL_MAPPINGS) {581printf("Bad GET_DIGITAL_MAPPINGS response for controller %p\n", dev);582return false;583}584585// If the length of the digital mappings result is not 1 (index byte, no mappings) then clearing hasn't executed586if (buf[2] == 1 && buf[3] == 0xFF) {587bMappingsCleared = true;588break;589}590usleep(CONTROLLER_CONFIGURATION_DELAY_US);591}592593if (!bMappingsCleared && !bSuppressErrorSpew) {594printf("Warning: CLEAR_DIGITAL_MAPPINGS never completed for controller %p\n", dev);595}596597// Set our new mappings598SDL_memset(buf, 0, 65);599buf[1] = ID_SET_DIGITAL_MAPPINGS;600buf[2] = 6; // 2 settings x 3 bytes601buf[3] = IO_DIGITAL_BUTTON_RIGHT_TRIGGER;602buf[4] = DEVICE_MOUSE;603buf[5] = MOUSE_BTN_LEFT;604buf[6] = IO_DIGITAL_BUTTON_LEFT_TRIGGER;605buf[7] = DEVICE_MOUSE;606buf[8] = MOUSE_BTN_RIGHT;607608res = SetFeatureReport(dev, buf, 9);609if (res < 0) {610if (!bSuppressErrorSpew) {611printf("SET_DIGITAL_MAPPINGS failed for controller %p\n", dev);612}613return false;614}615#endif // ENABLE_MOUSE_MODE616617return true;618}619620//---------------------------------------------------------------------------621// Read from a Steam Controller622//---------------------------------------------------------------------------623static int ReadSteamController(SDL_hid_device *dev, uint8_t *pData, int nDataSize)624{625SDL_memset(pData, 0, nDataSize);626pData[0] = BLE_REPORT_NUMBER; // hid_read will also overwrite this with the same value, 0x03627return SDL_hid_read(dev, pData, nDataSize);628}629630//---------------------------------------------------------------------------631// Set Steam Controller pairing state632//---------------------------------------------------------------------------633static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing)634{635unsigned char buf[65];636SDL_memset(buf, 0, 65);637buf[1] = ID_ENABLE_PAIRING;638buf[2] = 2; // 2 payload bytes: bool + timeout639buf[3] = bEnablePairing ? 1 : 0;640buf[4] = bEnablePairing ? PAIRING_STATE_DURATION_SECONDS : 0;641SetFeatureReport(dev, buf, 5);642}643644//---------------------------------------------------------------------------645// Commit Steam Controller pairing646//---------------------------------------------------------------------------647static void CommitPairing(SDL_HIDAPI_Device *dev)648{649unsigned char buf[65];650SDL_memset(buf, 0, 65);651buf[1] = ID_DONGLE_COMMIT_DEVICE;652SetFeatureReport(dev, buf, 2);653}654655//---------------------------------------------------------------------------656// Close a Steam Controller657//---------------------------------------------------------------------------658static void CloseSteamController(SDL_HIDAPI_Device *dev)659{660// Switch the Steam Controller back to lizard mode so it works with the OS661unsigned char buf[65];662int nSettings = 0;663664// Reset digital button mappings665SDL_memset(buf, 0, 65);666buf[1] = ID_SET_DEFAULT_DIGITAL_MAPPINGS;667SetFeatureReport(dev, buf, 2);668669// Reset the default settings670SDL_memset(buf, 0, 65);671buf[1] = ID_LOAD_DEFAULT_SETTINGS;672buf[2] = 0;673SetFeatureReport(dev, buf, 3);674675// Reset mouse mode for lizard mode676SDL_memset(buf, 0, 65);677buf[1] = ID_SET_SETTINGS_VALUES;678ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE);679buf[2] = (unsigned char)(nSettings * 3);680SetFeatureReport(dev, buf, 3 + nSettings * 3);681}682683//---------------------------------------------------------------------------684// Scale and clamp values to a range685//---------------------------------------------------------------------------686static float RemapValClamped(float val, float A, float B, float C, float D)687{688if (A == B) {689return (val - B) >= 0.0f ? D : C;690} else {691float cVal = (val - A) / (B - A);692cVal = clamp(cVal, 0.0f, 1.0f);693694return C + (D - C) * cVal;695}696}697698//---------------------------------------------------------------------------699// Rotate the pad coordinates700//---------------------------------------------------------------------------701static void RotatePad(int *pX, int *pY, float flAngleInRad)702{703int origX = *pX, origY = *pY;704705*pX = (int)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY);706*pY = (int)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY);707}708static void RotatePadShort(short *pX, short *pY, float flAngleInRad)709{710int origX = *pX, origY = *pY;711712*pX = (short)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY);713*pY = (short)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY);714}715716//---------------------------------------------------------------------------717// Format the first part of the state packet718//---------------------------------------------------------------------------719static void FormatStatePacketUntilGyro(SteamControllerStateInternal_t *pState, ValveControllerStatePacket_t *pStatePacket)720{721int nLeftPadX;722int nLeftPadY;723int nRightPadX;724int nRightPadY;725int nPadOffset;726727// 15 degrees in rad728const float flRotationAngle = 0.261799f;729730SDL_memset(pState, 0, offsetof(SteamControllerStateInternal_t, sBatteryLevel));731732// pState->eControllerType = m_eControllerType;733pState->eControllerType = 2; // k_eControllerType_SteamController;734pState->unPacketNum = pStatePacket->unPacketNum;735736// We have a chunk of trigger data in the packet format here, so zero it out afterwards737SDL_memcpy(&pState->ulButtons, &pStatePacket->ButtonTriggerData.ulButtons, 8);738pState->ulButtons &= ~0xFFFF000000LL;739740// The firmware uses this bit to tell us what kind of data is packed into the left two axes741if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {742// Finger-down bit not set; "left pad" is actually trackpad743pState->sLeftPadX = pState->sPrevLeftPad[0] = pStatePacket->sLeftPadX;744pState->sLeftPadY = pState->sPrevLeftPad[1] = pStatePacket->sLeftPadY;745746if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {747// The controller is interleaving both stick and pad data, both are active748pState->sLeftStickX = pState->sPrevLeftStick[0];749pState->sLeftStickY = pState->sPrevLeftStick[1];750} else {751// The stick is not active752pState->sPrevLeftStick[0] = 0;753pState->sPrevLeftStick[1] = 0;754}755} else {756// Finger-down bit not set; "left pad" is actually joystick757758// XXX there's a firmware bug where sometimes padX is 0 and padY is a large number (actually the battery voltage)759// If that happens skip this packet and report last frames stick760/*761if ( m_eControllerType == k_eControllerType_SteamControllerV2 && pStatePacket->sLeftPadY > 900 ) {762pState->sLeftStickX = pState->sPrevLeftStick[0];763pState->sLeftStickY = pState->sPrevLeftStick[1];764} else765*/766{767pState->sPrevLeftStick[0] = pState->sLeftStickX = pStatePacket->sLeftPadX;768pState->sPrevLeftStick[1] = pState->sLeftStickY = pStatePacket->sLeftPadY;769}770/*771if (m_eControllerType == k_eControllerType_SteamControllerV2) {772UpdateV2JoystickCap(&state);773}774*/775776if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {777// The controller is interleaving both stick and pad data, both are active778pState->sLeftPadX = pState->sPrevLeftPad[0];779pState->sLeftPadY = pState->sPrevLeftPad[1];780} else {781// The trackpad is not active782pState->sPrevLeftPad[0] = 0;783pState->sPrevLeftPad[1] = 0;784785// Old controllers send trackpad click for joystick button when trackpad is not active786if (pState->ulButtons & STEAM_BUTTON_LEFTPAD_CLICKED_MASK) {787pState->ulButtons &= ~STEAM_BUTTON_LEFTPAD_CLICKED_MASK;788pState->ulButtons |= STEAM_JOYSTICK_BUTTON_MASK;789}790}791}792793// Fingerdown bit indicates if the packed left axis data was joystick or pad,794// but if we are interleaving both, the left finger is definitely on the pad.795if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {796pState->ulButtons |= STEAM_LEFTPAD_FINGERDOWN_MASK;797}798799pState->sRightPadX = pStatePacket->sRightPadX;800pState->sRightPadY = pStatePacket->sRightPadY;801802nLeftPadX = pState->sLeftPadX;803nLeftPadY = pState->sLeftPadY;804nRightPadX = pState->sRightPadX;805nRightPadY = pState->sRightPadY;806807RotatePad(&nLeftPadX, &nLeftPadY, -flRotationAngle);808RotatePad(&nRightPadX, &nRightPadY, flRotationAngle);809810if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {811nPadOffset = 1000;812} else {813nPadOffset = 0;814}815816pState->sLeftPadX = (short)clamp(nLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);817pState->sLeftPadY = (short)clamp(nLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);818819nPadOffset = 0;820if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) {821nPadOffset = 1000;822} else {823nPadOffset = 0;824}825826pState->sRightPadX = (short)clamp(nRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);827pState->sRightPadY = (short)clamp(nRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);828829pState->sTriggerL = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nLeft << 7) | pStatePacket->ButtonTriggerData.Triggers.nLeft), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);830pState->sTriggerR = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nRight << 7) | pStatePacket->ButtonTriggerData.Triggers.nRight), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);831}832833//---------------------------------------------------------------------------834// Update Steam Controller state from a BLE data packet, returns true if it parsed data835//---------------------------------------------------------------------------836static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState)837{838const float flRotationAngle = 0.261799f;839uint32_t ucOptionDataMask;840841pState->unPacketNum++;842ucOptionDataMask = (*pData++ & 0xF0);843ucOptionDataMask |= (uint32_t)(*pData++) << 8;844if (ucOptionDataMask & k_EBLEButtonChunk1) {845SDL_memcpy(&pState->ulButtons, pData, 3);846pData += 3;847}848if (ucOptionDataMask & k_EBLEButtonChunk2) {849// The middle 2 bytes of the button bits over the wire are triggers when over the wire and non-SC buttons in the internal controller state packet850pState->sTriggerL = (unsigned short)RemapValClamped((float)((pData[0] << 7) | pData[0]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);851pState->sTriggerR = (unsigned short)RemapValClamped((float)((pData[1] << 7) | pData[1]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);852pData += 2;853}854if (ucOptionDataMask & k_EBLEButtonChunk3) {855uint8_t *pButtonByte = (uint8_t *)&pState->ulButtons;856pButtonByte[5] = *pData++;857pButtonByte[6] = *pData++;858pButtonByte[7] = *pData++;859}860if (ucOptionDataMask & k_EBLELeftJoystickChunk) {861// This doesn't handle any of the special headcrab stuff for raw joystick which is OK for now since that FW doesn't support862// this protocol yet either863int nLength = sizeof(pState->sLeftStickX) + sizeof(pState->sLeftStickY);864SDL_memcpy(&pState->sLeftStickX, pData, nLength);865pData += nLength;866}867if (ucOptionDataMask & k_EBLELeftTrackpadChunk) {868int nLength = sizeof(pState->sLeftPadX) + sizeof(pState->sLeftPadY);869int nPadOffset;870SDL_memcpy(&pState->sLeftPadX, pData, nLength);871if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {872nPadOffset = 1000;873} else {874nPadOffset = 0;875}876877RotatePadShort(&pState->sLeftPadX, &pState->sLeftPadY, -flRotationAngle);878pState->sLeftPadX = (short)clamp(pState->sLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);879pState->sLeftPadY = (short)clamp(pState->sLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);880pData += nLength;881}882if (ucOptionDataMask & k_EBLERightTrackpadChunk) {883int nLength = sizeof(pState->sRightPadX) + sizeof(pState->sRightPadY);884int nPadOffset = 0;885886SDL_memcpy(&pState->sRightPadX, pData, nLength);887888if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) {889nPadOffset = 1000;890} else {891nPadOffset = 0;892}893894RotatePadShort(&pState->sRightPadX, &pState->sRightPadY, flRotationAngle);895pState->sRightPadX = (short)clamp(pState->sRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);896pState->sRightPadY = (short)clamp(pState->sRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);897pData += nLength;898}899if (ucOptionDataMask & k_EBLEIMUAccelChunk) {900int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ);901SDL_memcpy(&pState->sAccelX, pData, nLength);902pData += nLength;903}904if (ucOptionDataMask & k_EBLEIMUGyroChunk) {905int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ);906SDL_memcpy(&pState->sGyroX, pData, nLength);907pData += nLength;908}909if (ucOptionDataMask & k_EBLEIMUQuatChunk) {910int nLength = sizeof(pState->sGyroQuatW) + sizeof(pState->sGyroQuatX) + sizeof(pState->sGyroQuatY) + sizeof(pState->sGyroQuatZ);911SDL_memcpy(&pState->sGyroQuatW, pData, nLength);912pData += nLength;913}914return true;915}916917//---------------------------------------------------------------------------918// Update Steam Controller state from a data packet, returns true if it parsed data919//---------------------------------------------------------------------------920static bool UpdateSteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState)921{922ValveInReport_t *pInReport = (ValveInReport_t *)pData;923924if (pInReport->header.unReportVersion != k_ValveInReportMsgVersion) {925if ((pData[0] & 0x0F) == k_EBLEReportState) {926return UpdateBLESteamControllerState(pData, nDataSize, pState);927}928return false;929}930931if ((pInReport->header.ucType != ID_CONTROLLER_STATE) &&932(pInReport->header.ucType != ID_CONTROLLER_BLE_STATE)) {933return false;934}935936if (pInReport->header.ucType == ID_CONTROLLER_STATE) {937ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState;938939// No new data to process; indicate that we received a state packet, but otherwise do nothing.940if (pState->unPacketNum == pStatePacket->unPacketNum) {941return true;942}943944FormatStatePacketUntilGyro(pState, pStatePacket);945946pState->sAccelX = pStatePacket->sAccelX;947pState->sAccelY = pStatePacket->sAccelY;948pState->sAccelZ = pStatePacket->sAccelZ;949950pState->sGyroQuatW = pStatePacket->sGyroQuatW;951pState->sGyroQuatX = pStatePacket->sGyroQuatX;952pState->sGyroQuatY = pStatePacket->sGyroQuatY;953pState->sGyroQuatZ = pStatePacket->sGyroQuatZ;954955pState->sGyroX = pStatePacket->sGyroX;956pState->sGyroY = pStatePacket->sGyroY;957pState->sGyroZ = pStatePacket->sGyroZ;958959} else if (pInReport->header.ucType == ID_CONTROLLER_BLE_STATE) {960ValveControllerBLEStatePacket_t *pBLEStatePacket = &pInReport->payload.controllerBLEState;961ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState;962963// No new data to process; indicate that we received a state packet, but otherwise do nothing.964if (pState->unPacketNum == pStatePacket->unPacketNum) {965return true;966}967968FormatStatePacketUntilGyro(pState, pStatePacket);969970switch (pBLEStatePacket->ucGyroDataType) {971case 1:972pState->sGyroQuatW = ((float)pBLEStatePacket->sGyro[0]);973pState->sGyroQuatX = ((float)pBLEStatePacket->sGyro[1]);974pState->sGyroQuatY = ((float)pBLEStatePacket->sGyro[2]);975pState->sGyroQuatZ = ((float)pBLEStatePacket->sGyro[3]);976break;977978case 2:979pState->sAccelX = pBLEStatePacket->sGyro[0];980pState->sAccelY = pBLEStatePacket->sGyro[1];981pState->sAccelZ = pBLEStatePacket->sGyro[2];982break;983984case 3:985pState->sGyroX = pBLEStatePacket->sGyro[0];986pState->sGyroY = pBLEStatePacket->sGyro[1];987pState->sGyroZ = pBLEStatePacket->sGyro[2];988break;989990default:991break;992}993}994995return true;996}997998/*****************************************************************************************************/9991000typedef struct1001{1002SDL_HIDAPI_Device *device;1003bool connected;1004bool report_sensors;1005uint32_t update_rate_in_us;1006Uint64 sensor_timestamp;1007Uint64 pairing_time;10081009SteamControllerPacketAssembler m_assembler;1010SteamControllerStateInternal_t m_state;1011SteamControllerStateInternal_t m_last_state;1012} SDL_DriverSteam_Context;10131014static bool IsDongle(Uint16 product_id)1015{1016return (product_id == USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE);1017}10181019static void HIDAPI_DriverSteam_RegisterHints(SDL_HintCallback callback, void *userdata)1020{1021SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);1022}10231024static void HIDAPI_DriverSteam_UnregisterHints(SDL_HintCallback callback, void *userdata)1025{1026SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);1027}10281029static bool HIDAPI_DriverSteam_IsEnabled(void)1030{1031return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT);1032}10331034static bool HIDAPI_DriverSteam_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)1035{1036if (!SDL_IsJoystickSteamController(vendor_id, product_id)) {1037return false;1038}10391040if (device->is_bluetooth) {1041return true;1042}10431044if (IsDongle(product_id)) {1045if (interface_number >= 1 && interface_number <= 4) {1046// This is one of the wireless controller interfaces1047return true;1048}1049} else {1050if (interface_number == 2) {1051// This is the controller interface (not mouse or keyboard)1052return true;1053}1054}1055return false;1056}10571058static void HIDAPI_DriverSteam_SetPairingState(SDL_DriverSteam_Context *ctx, bool enabled)1059{1060// Only have one dongle in pairing mode at a time1061static SDL_DriverSteam_Context *s_PairingContext = NULL;10621063if (enabled && s_PairingContext != NULL) {1064return;1065}10661067if (!enabled && s_PairingContext != ctx) {1068return;1069}10701071if (ctx->connected) {1072return;1073}10741075SetPairingState(ctx->device, enabled);10761077if (enabled) {1078ctx->pairing_time = SDL_GetTicks();1079s_PairingContext = ctx;1080} else {1081ctx->pairing_time = 0;1082s_PairingContext = NULL;1083}1084}10851086static void HIDAPI_DriverSteam_RenewPairingState(SDL_DriverSteam_Context *ctx)1087{1088Uint64 now = SDL_GetTicks();10891090if (now >= ctx->pairing_time + PAIRING_STATE_DURATION_SECONDS * 1000) {1091SetPairingState(ctx->device, true);1092ctx->pairing_time = now;1093}1094}10951096static void HIDAPI_DriverSteam_CommitPairing(SDL_DriverSteam_Context *ctx)1097{1098CommitPairing(ctx->device);1099}11001101static void SDLCALL SDL_PairingEnabledHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)1102{1103SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata;1104bool enabled = SDL_GetStringBoolean(hint, false);11051106HIDAPI_DriverSteam_SetPairingState(ctx, enabled);1107}11081109static bool HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device)1110{1111SDL_DriverSteam_Context *ctx;11121113ctx = (SDL_DriverSteam_Context *)SDL_calloc(1, sizeof(*ctx));1114if (!ctx) {1115return false;1116}1117ctx->device = device;1118device->context = ctx;11191120#ifdef SDL_PLATFORM_WIN321121if (device->serial) {1122// We get a garbage serial number on Windows1123SDL_free(device->serial);1124device->serial = NULL;1125}1126#endif // SDL_PLATFORM_WIN3211271128HIDAPI_SetDeviceName(device, "Steam Controller");11291130// If this is a wireless dongle, request a wireless state update1131if (IsDongle(device->product_id)) {1132unsigned char buf[65];1133int res;11341135buf[0] = 0;1136buf[1] = ID_DONGLE_GET_WIRELESS_STATE;1137res = SetFeatureReport(device, buf, 2);1138if (res < 0) {1139return SDL_SetError("Failed to send ID_DONGLE_GET_WIRELESS_STATE request");1140}11411142for (int attempt = 0; attempt < 5; ++attempt) {1143uint8_t data[128];11441145res = ReadSteamController(device->dev, data, sizeof(data));1146if (res == 0) {1147SDL_Delay(1);1148continue;1149}1150if (res < 0) {1151break;1152}11531154#ifdef DEBUG_STEAM_PROTOCOL1155HIDAPI_DumpPacket("Initial dongle packet: size = %d", data, res);1156#endif11571158if (D0G_IS_WIRELESS_CONNECT(data, res)) {1159ctx->connected = true;1160break;1161} else if (D0G_IS_WIRELESS_DISCONNECT(data, res)) {1162ctx->connected = false;1163break;1164}1165}11661167SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED,1168SDL_PairingEnabledHintChanged, ctx);1169} else {1170// Wired and BLE controllers are always connected if HIDAPI can see them1171ctx->connected = true;1172}11731174if (ctx->connected) {1175return HIDAPI_JoystickConnected(device, NULL);1176} else {1177// We will enumerate any attached controllers in UpdateDevice()1178return true;1179}1180}11811182static int HIDAPI_DriverSteam_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)1183{1184return -1;1185}11861187static void HIDAPI_DriverSteam_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)1188{1189}11901191static bool SetHomeLED(SDL_HIDAPI_Device *device, Uint8 value)1192{1193unsigned char buf[65];1194int nSettings = 0;11951196SDL_memset(buf, 0, 65);1197buf[1] = ID_SET_SETTINGS_VALUES;1198ADD_SETTING(SETTING_LED_USER_BRIGHTNESS, value);1199buf[2] = (unsigned char)(nSettings * 3);1200if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) {1201return SDL_SetError("Couldn't write feature report");1202}1203return true;1204}12051206static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)1207{1208SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata;12091210if (hint && *hint) {1211int value;12121213if (SDL_strchr(hint, '.') != NULL) {1214value = (int)(100.0f * SDL_atof(hint));1215if (value > 255) {1216value = 255;1217}1218} else if (SDL_GetStringBoolean(hint, true)) {1219value = 100;1220} else {1221value = 0;1222}1223SetHomeLED(ctx->device, (Uint8)value);1224}1225}12261227static bool HIDAPI_DriverSteam_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)1228{1229SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;1230float update_rate_in_hz = 0.0f;12311232SDL_AssertJoysticksLocked();12331234ctx->report_sensors = false;1235SDL_zero(ctx->m_assembler);1236SDL_zero(ctx->m_state);1237SDL_zero(ctx->m_last_state);12381239if (!ResetSteamController(device, false, &ctx->update_rate_in_us)) {1240SDL_SetError("Couldn't reset controller");1241return false;1242}1243if (ctx->update_rate_in_us > 0) {1244update_rate_in_hz = 1000000.0f / ctx->update_rate_in_us;1245}12461247InitializeSteamControllerPacketAssembler(&ctx->m_assembler, device->is_bluetooth);12481249// Initialize the joystick capabilities1250joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_BUTTONS;1251joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;1252joystick->nhats = 1;12531254if (IsDongle(device->product_id)) {1255joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;1256}12571258SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz);1259SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz);12601261SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED,1262SDL_HomeLEDHintChanged, ctx);12631264return true;1265}12661267static bool HIDAPI_DriverSteam_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)1268{1269// You should use the full Steam Input API for rumble support1270return SDL_Unsupported();1271}12721273static bool HIDAPI_DriverSteam_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)1274{1275return SDL_Unsupported();1276}12771278static Uint32 HIDAPI_DriverSteam_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)1279{1280// You should use the full Steam Input API for extended capabilities1281return 0;1282}12831284static bool HIDAPI_DriverSteam_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)1285{1286// You should use the full Steam Input API for LED support1287return SDL_Unsupported();1288}12891290static bool HIDAPI_DriverSteam_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)1291{1292if (size == 65) {1293if (SetFeatureReport(device, data, size) < 0) {1294return SDL_SetError("Couldn't write feature report");1295}1296return true;1297}1298return SDL_Unsupported();1299}13001301static bool HIDAPI_DriverSteam_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)1302{1303SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;1304unsigned char buf[65];1305int nSettings = 0;13061307SDL_memset(buf, 0, 65);1308buf[1] = ID_SET_SETTINGS_VALUES;1309if (enabled) {1310ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO);1311} else {1312ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_OFF);1313}1314buf[2] = (unsigned char)(nSettings * 3);1315if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) {1316return SDL_SetError("Couldn't write feature report");1317}13181319ctx->report_sensors = enabled;13201321return true;1322}13231324static bool ControllerConnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick)1325{1326SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;13271328if (!HIDAPI_JoystickConnected(device, NULL)) {1329return false;1330}13311332// We'll automatically accept this controller if we're in pairing mode1333HIDAPI_DriverSteam_CommitPairing(ctx);13341335*joystick = SDL_GetJoystickFromID(device->joysticks[0]);1336ctx->connected = true;1337return true;1338}13391340static void ControllerDisconnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick)1341{1342SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;13431344if (device->joysticks) {1345HIDAPI_JoystickDisconnected(device, device->joysticks[0]);1346}1347ctx->connected = false;1348*joystick = NULL;1349}13501351static bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device)1352{1353SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;1354SDL_Joystick *joystick = NULL;13551356if (device->num_joysticks > 0) {1357joystick = SDL_GetJoystickFromID(device->joysticks[0]);1358}13591360if (ctx->pairing_time) {1361HIDAPI_DriverSteam_RenewPairingState(ctx);1362}13631364for (;;) {1365uint8_t data[128];1366int r, nPacketLength;1367const Uint8 *pPacket;13681369r = ReadSteamController(device->dev, data, sizeof(data));1370if (r == 0) {1371break;1372}1373if (r < 0) {1374// Failed to read from controller1375ControllerDisconnected(device, &joystick);1376return false;1377}13781379#ifdef DEBUG_STEAM_PROTOCOL1380HIDAPI_DumpPacket("Steam Controller packet: size = %d", data, r);1381#endif13821383nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&ctx->m_assembler, data, r);1384pPacket = ctx->m_assembler.uBuffer;13851386if (nPacketLength > 0 && UpdateSteamControllerState(pPacket, nPacketLength, &ctx->m_state)) {1387Uint64 timestamp = SDL_GetTicksNS();13881389if (!ctx->connected) {1390// Maybe we missed a wireless status packet?1391ControllerConnected(device, &joystick);1392}13931394if (!joystick) {1395continue;1396}13971398if (ctx->m_state.ulButtons != ctx->m_last_state.ulButtons) {1399Uint8 hat = 0;14001401SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH,1402((ctx->m_state.ulButtons & STEAM_BUTTON_SOUTH_MASK) != 0));14031404SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST,1405((ctx->m_state.ulButtons & STEAM_BUTTON_EAST_MASK) != 0));14061407SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST,1408((ctx->m_state.ulButtons & STEAM_BUTTON_WEST_MASK) != 0));14091410SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH,1411((ctx->m_state.ulButtons & STEAM_BUTTON_NORTH_MASK) != 0));14121413SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,1414((ctx->m_state.ulButtons & STEAM_LEFT_BUMPER_MASK) != 0));14151416SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,1417((ctx->m_state.ulButtons & STEAM_RIGHT_BUMPER_MASK) != 0));14181419SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK,1420((ctx->m_state.ulButtons & STEAM_BUTTON_MENU_MASK) != 0));14211422SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START,1423((ctx->m_state.ulButtons & STEAM_BUTTON_ESCAPE_MASK) != 0));14241425SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE,1426((ctx->m_state.ulButtons & STEAM_BUTTON_STEAM_MASK) != 0));14271428SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK,1429((ctx->m_state.ulButtons & STEAM_JOYSTICK_BUTTON_MASK) != 0));1430SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE,1431((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_LEFT_MASK) != 0));1432SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE,1433((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_RIGHT_MASK) != 0));14341435SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK,1436((ctx->m_state.ulButtons & STEAM_BUTTON_RIGHTPAD_CLICKED_MASK) != 0));14371438if (ctx->m_state.ulButtons & STEAM_DPAD_UP_MASK) {1439hat |= SDL_HAT_UP;1440}1441if (ctx->m_state.ulButtons & STEAM_DPAD_DOWN_MASK) {1442hat |= SDL_HAT_DOWN;1443}1444if (ctx->m_state.ulButtons & STEAM_DPAD_LEFT_MASK) {1445hat |= SDL_HAT_LEFT;1446}1447if (ctx->m_state.ulButtons & STEAM_DPAD_RIGHT_MASK) {1448hat |= SDL_HAT_RIGHT;1449}1450SDL_SendJoystickHat(timestamp, joystick, 0, hat);1451}14521453SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (int)ctx->m_state.sTriggerL * 2 - 32768);1454SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (int)ctx->m_state.sTriggerR * 2 - 32768);14551456SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, ctx->m_state.sLeftStickX);1457SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~ctx->m_state.sLeftStickY);1458SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, ctx->m_state.sRightPadX);1459SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~ctx->m_state.sRightPadY);14601461if (ctx->report_sensors) {1462float values[3];14631464ctx->sensor_timestamp += SDL_US_TO_NS(ctx->update_rate_in_us);14651466values[0] = (ctx->m_state.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));1467values[1] = (ctx->m_state.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));1468values[2] = (ctx->m_state.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));1469SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp, values, 3);14701471values[0] = (ctx->m_state.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;1472values[1] = (ctx->m_state.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;1473values[2] = (-ctx->m_state.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;1474SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp, values, 3);1475}14761477ctx->m_last_state = ctx->m_state;1478} else if (!ctx->connected && D0G_IS_WIRELESS_CONNECT(pPacket, nPacketLength)) {1479ControllerConnected(device, &joystick);1480} else if (ctx->connected && D0G_IS_WIRELESS_DISCONNECT(pPacket, nPacketLength)) {1481ControllerDisconnected(device, &joystick);1482}1483}1484return true;1485}14861487static void HIDAPI_DriverSteam_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)1488{1489SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;14901491SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED,1492SDL_HomeLEDHintChanged, ctx);14931494CloseSteamController(device);1495}14961497static void HIDAPI_DriverSteam_FreeDevice(SDL_HIDAPI_Device *device)1498{1499SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;15001501if (IsDongle(device->product_id)) {1502SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED,1503SDL_PairingEnabledHintChanged, ctx);15041505HIDAPI_DriverSteam_SetPairingState(ctx, false);1506}1507}15081509SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam = {1510SDL_HINT_JOYSTICK_HIDAPI_STEAM,1511true,1512HIDAPI_DriverSteam_RegisterHints,1513HIDAPI_DriverSteam_UnregisterHints,1514HIDAPI_DriverSteam_IsEnabled,1515HIDAPI_DriverSteam_IsSupportedDevice,1516HIDAPI_DriverSteam_InitDevice,1517HIDAPI_DriverSteam_GetDevicePlayerIndex,1518HIDAPI_DriverSteam_SetDevicePlayerIndex,1519HIDAPI_DriverSteam_UpdateDevice,1520HIDAPI_DriverSteam_OpenJoystick,1521HIDAPI_DriverSteam_RumbleJoystick,1522HIDAPI_DriverSteam_RumbleJoystickTriggers,1523HIDAPI_DriverSteam_GetJoystickCapabilities,1524HIDAPI_DriverSteam_SetJoystickLED,1525HIDAPI_DriverSteam_SendJoystickEffect,1526HIDAPI_DriverSteam_SetSensorsEnabled,1527HIDAPI_DriverSteam_CloseJoystick,1528HIDAPI_DriverSteam_FreeDevice,1529};15301531#endif // SDL_JOYSTICK_HIDAPI_STEAM15321533#endif // SDL_JOYSTICK_HIDAPI153415351536