1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../../SDL_hints_c.h"
26#include "../SDL_sysjoystick.h"
27#include "SDL_hidapijoystick_c.h"
28
29#ifdef SDL_JOYSTICK_HIDAPI_STEAM
30
31// Define this if you want to log all packets from the controller
32// #define DEBUG_STEAM_PROTOCOL
33
34#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED "SDL_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED"
35
36#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
37// This requires prompting for Bluetooth permissions, so make sure the application really wants it
38#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT false
39#else
40#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)
41#endif
42
43#define PAIRING_STATE_DURATION_SECONDS 60
44
45
46/*****************************************************************************************************/
47
48#include "steam/controller_constants.h"
49#include "steam/controller_structs.h"
50
51enum
52{
53 SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE = 11,
54 SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE,
55 SDL_GAMEPAD_NUM_STEAM_BUTTONS,
56};
57
58typedef struct SteamControllerStateInternal_t
59{
60 // Controller Type for this Controller State
61 Uint32 eControllerType;
62
63 // If packet num matches that on your prior call, then the controller state hasn't been changed since
64 // your last call and there is no need to process it
65 Uint32 unPacketNum;
66
67 // bit flags for each of the buttons
68 Uint64 ulButtons;
69
70 // Left pad coordinates
71 short sLeftPadX;
72 short sLeftPadY;
73
74 // Right pad coordinates
75 short sRightPadX;
76 short sRightPadY;
77
78 // Center pad coordinates
79 short sCenterPadX;
80 short sCenterPadY;
81
82 // Left analog stick coordinates
83 short sLeftStickX;
84 short sLeftStickY;
85
86 // Right analog stick coordinates
87 short sRightStickX;
88 short sRightStickY;
89
90 unsigned short sTriggerL;
91 unsigned short sTriggerR;
92
93 short sAccelX;
94 short sAccelY;
95 short sAccelZ;
96
97 short sGyroX;
98 short sGyroY;
99 short sGyroZ;
100
101 float sGyroQuatW;
102 float sGyroQuatX;
103 float sGyroQuatY;
104 float sGyroQuatZ;
105
106 short sGyroSteeringAngle;
107
108 unsigned short sBatteryLevel;
109
110 // Pressure sensor data.
111 unsigned short sPressurePadLeft;
112 unsigned short sPressurePadRight;
113
114 unsigned short sPressureBumperLeft;
115 unsigned short sPressureBumperRight;
116
117 // Internal state data
118 short sPrevLeftPad[2];
119 short sPrevLeftStick[2];
120} SteamControllerStateInternal_t;
121
122// Defines for ulButtons in SteamControllerStateInternal_t
123#define STEAM_RIGHT_TRIGGER_MASK 0x00000001
124#define STEAM_LEFT_TRIGGER_MASK 0x00000002
125#define STEAM_RIGHT_BUMPER_MASK 0x00000004
126#define STEAM_LEFT_BUMPER_MASK 0x00000008
127#define STEAM_BUTTON_NORTH_MASK 0x00000010 // Y
128#define STEAM_BUTTON_EAST_MASK 0x00000020 // B
129#define STEAM_BUTTON_WEST_MASK 0x00000040 // X
130#define STEAM_BUTTON_SOUTH_MASK 0x00000080 // A
131#define STEAM_DPAD_UP_MASK 0x00000100 // DPAD UP
132#define STEAM_DPAD_RIGHT_MASK 0x00000200 // DPAD RIGHT
133#define STEAM_DPAD_LEFT_MASK 0x00000400 // DPAD LEFT
134#define STEAM_DPAD_DOWN_MASK 0x00000800 // DPAD DOWN
135#define STEAM_BUTTON_MENU_MASK 0x00001000 // SELECT
136#define STEAM_BUTTON_STEAM_MASK 0x00002000 // GUIDE
137#define STEAM_BUTTON_ESCAPE_MASK 0x00004000 // START
138#define STEAM_BUTTON_BACK_LEFT_MASK 0x00008000
139#define STEAM_BUTTON_BACK_RIGHT_MASK 0x00010000
140#define STEAM_BUTTON_LEFTPAD_CLICKED_MASK 0x00020000
141#define STEAM_BUTTON_RIGHTPAD_CLICKED_MASK 0x00040000
142#define STEAM_LEFTPAD_FINGERDOWN_MASK 0x00080000
143#define STEAM_RIGHTPAD_FINGERDOWN_MASK 0x00100000
144#define STEAM_JOYSTICK_BUTTON_MASK 0x00400000
145#define STEAM_LEFTPAD_AND_JOYSTICK_MASK 0x00800000
146
147// Look for report version 0x0001, type WIRELESS (3), length >= 1 byte
148#define D0G_IS_VALID_WIRELESS_EVENT(data, len) ((len) >= 5 && (data)[0] == 1 && (data)[1] == 0 && (data)[2] == 3 && (data)[3] >= 1)
149#define D0G_GET_WIRELESS_EVENT_TYPE(data) ((data)[4])
150#define D0G_WIRELESS_DISCONNECTED 1
151#define D0G_WIRELESS_ESTABLISHED 2
152#define D0G_WIRELESS_NEWLYPAIRED 3
153
154#define D0G_IS_WIRELESS_DISCONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) == D0G_WIRELESS_DISCONNECTED)
155#define D0G_IS_WIRELESS_CONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) != D0G_WIRELESS_DISCONNECTED)
156
157
158#define MAX_REPORT_SEGMENT_PAYLOAD_SIZE 18
159/*
160 * SteamControllerPacketAssembler has to be used when reading output repots from controllers.
161 */
162typedef struct
163{
164 uint8_t uBuffer[MAX_REPORT_SEGMENT_PAYLOAD_SIZE * 8 + 1];
165 int nExpectedSegmentNumber;
166 bool bIsBle;
167} SteamControllerPacketAssembler;
168
169#undef clamp
170#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))
171
172#undef offsetof
173#define offsetof(s, m) (size_t) & (((s *)0)->m)
174
175#ifdef DEBUG_STEAM_CONTROLLER
176#define DPRINTF(format, ...) printf(format, ##__VA_ARGS__)
177#define HEXDUMP(ptr, len) hexdump(ptr, len)
178#else
179#define DPRINTF(format, ...)
180#define HEXDUMP(ptr, len)
181#endif
182#define printf SDL_Log
183
184#define MAX_REPORT_SEGMENT_SIZE (MAX_REPORT_SEGMENT_PAYLOAD_SIZE + 2)
185#define CALC_REPORT_SEGMENT_NUM(index) ((index / MAX_REPORT_SEGMENT_PAYLOAD_SIZE) & 0x07)
186#define REPORT_SEGMENT_DATA_FLAG 0x80
187#define REPORT_SEGMENT_LAST_FLAG 0x40
188#define BLE_REPORT_NUMBER 0x03
189
190#define STEAMCONTROLLER_TRIGGER_MAX_ANALOG 26000
191
192// Enable mouse mode when using the Steam Controller locally
193#undef ENABLE_MOUSE_MODE
194
195// Wireless firmware quirk: the firmware intentionally signals "failure" when performing
196// SET_FEATURE / GET_FEATURE when it actually means "pending radio roundtrip". The only
197// way to make SET_FEATURE / GET_FEATURE work is to loop several times with a sleep. If
198// it takes more than 50ms to get the response for SET_FEATURE / GET_FEATURE, we assume
199// that the controller has failed.
200#define RADIO_WORKAROUND_SLEEP_ATTEMPTS 50
201#define RADIO_WORKAROUND_SLEEP_DURATION_US 500
202
203// This was defined by experimentation. 2000 seemed to work but to give that extra bit of margin, set to 3ms.
204#define CONTROLLER_CONFIGURATION_DELAY_US 3000
205
206static uint8_t GetSegmentHeader(int nSegmentNumber, bool bLastPacket)
207{
208 uint8_t header = REPORT_SEGMENT_DATA_FLAG;
209 header |= nSegmentNumber;
210 if (bLastPacket) {
211 header |= REPORT_SEGMENT_LAST_FLAG;
212 }
213
214 return header;
215}
216
217static void hexdump(const uint8_t *ptr, int len)
218{
219 HIDAPI_DumpPacket("Data", ptr, len);
220}
221
222static void ResetSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler)
223{
224 SDL_memset(pAssembler->uBuffer, 0, sizeof(pAssembler->uBuffer));
225 pAssembler->nExpectedSegmentNumber = 0;
226}
227
228static void InitializeSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, bool bIsBle)
229{
230 pAssembler->bIsBle = bIsBle;
231 ResetSteamControllerPacketAssembler(pAssembler);
232}
233
234// Returns:
235// <0 on error
236// 0 on not ready
237// Complete packet size on completion
238static int WriteSegmentToSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, const uint8_t *pSegment, int nSegmentLength)
239{
240 if (pAssembler->bIsBle) {
241 uint8_t uSegmentHeader = pSegment[1];
242 int nSegmentNumber = uSegmentHeader & 0x07;
243
244 HEXDUMP(pSegment, nSegmentLength);
245
246 if (pSegment[0] != BLE_REPORT_NUMBER) {
247 // We may get keyboard/mouse input events until controller stops sending them
248 return 0;
249 }
250
251 if (nSegmentLength != MAX_REPORT_SEGMENT_SIZE) {
252 printf("Bad segment size! %d\n", nSegmentLength);
253 hexdump(pSegment, nSegmentLength);
254 ResetSteamControllerPacketAssembler(pAssembler);
255 return -1;
256 }
257
258 DPRINTF("GOT PACKET HEADER = 0x%x\n", uSegmentHeader);
259
260 if (!(uSegmentHeader & REPORT_SEGMENT_DATA_FLAG)) {
261 // We get empty segments, just ignore them
262 return 0;
263 }
264
265 if (nSegmentNumber != pAssembler->nExpectedSegmentNumber) {
266 ResetSteamControllerPacketAssembler(pAssembler);
267
268 if (nSegmentNumber) {
269 // This happens occasionally
270 DPRINTF("Bad segment number, got %d, expected %d\n",
271 nSegmentNumber, pAssembler->nExpectedSegmentNumber);
272 return -1;
273 }
274 }
275
276 SDL_memcpy(pAssembler->uBuffer + nSegmentNumber * MAX_REPORT_SEGMENT_PAYLOAD_SIZE,
277 pSegment + 2, // ignore header and report number
278 MAX_REPORT_SEGMENT_PAYLOAD_SIZE);
279
280 if (uSegmentHeader & REPORT_SEGMENT_LAST_FLAG) {
281 pAssembler->nExpectedSegmentNumber = 0;
282 return (nSegmentNumber + 1) * MAX_REPORT_SEGMENT_PAYLOAD_SIZE;
283 }
284
285 pAssembler->nExpectedSegmentNumber++;
286 } else {
287 // Just pass through
288 SDL_memcpy(pAssembler->uBuffer,
289 pSegment,
290 nSegmentLength);
291 return nSegmentLength;
292 }
293
294 return 0;
295}
296
297#define BLE_MAX_READ_RETRIES 8
298
299static int SetFeatureReport(SDL_HIDAPI_Device *dev, const unsigned char uBuffer[65], int nActualDataLen)
300{
301 int nRet = -1;
302
303 DPRINTF("SetFeatureReport %p %p %d\n", dev, uBuffer, nActualDataLen);
304
305 if (dev->is_bluetooth) {
306 int nSegmentNumber = 0;
307 uint8_t uPacketBuffer[MAX_REPORT_SEGMENT_SIZE];
308 const unsigned char *pBufferPtr = uBuffer + 1;
309
310 if (nActualDataLen < 1) {
311 return -1;
312 }
313
314 // Skip report number in data
315 nActualDataLen--;
316
317 while (nActualDataLen > 0) {
318 int nBytesInPacket = nActualDataLen > MAX_REPORT_SEGMENT_PAYLOAD_SIZE ? MAX_REPORT_SEGMENT_PAYLOAD_SIZE : nActualDataLen;
319
320 nActualDataLen -= nBytesInPacket;
321
322 // Construct packet
323 SDL_memset(uPacketBuffer, 0, sizeof(uPacketBuffer));
324 uPacketBuffer[0] = BLE_REPORT_NUMBER;
325 uPacketBuffer[1] = GetSegmentHeader(nSegmentNumber, nActualDataLen == 0);
326 SDL_memcpy(&uPacketBuffer[2], pBufferPtr, nBytesInPacket);
327
328 pBufferPtr += nBytesInPacket;
329 nSegmentNumber++;
330
331 nRet = SDL_hid_send_feature_report(dev->dev, uPacketBuffer, sizeof(uPacketBuffer));
332 }
333 } else {
334 for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) {
335 nRet = SDL_hid_send_feature_report(dev->dev, uBuffer, 65);
336 if (nRet >= 0) {
337 break;
338 }
339
340 SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000);
341 }
342 }
343
344 DPRINTF("SetFeatureReport() ret = %d\n", nRet);
345
346 return nRet;
347}
348
349static int GetFeatureReport(SDL_HIDAPI_Device *dev, unsigned char uBuffer[65])
350{
351 int nRet = -1;
352
353 DPRINTF("GetFeatureReport( %p %p )\n", dev, uBuffer);
354
355 if (dev->is_bluetooth) {
356 int nRetries = 0;
357 uint8_t uSegmentBuffer[MAX_REPORT_SEGMENT_SIZE + 1];
358 uint8_t ucBytesToRead = MAX_REPORT_SEGMENT_SIZE;
359 uint8_t ucDataStartOffset = 0;
360
361 SteamControllerPacketAssembler assembler;
362 InitializeSteamControllerPacketAssembler(&assembler, dev->is_bluetooth);
363
364 // On Windows and macOS, BLE devices get 2 copies of the feature report ID, one that is removed by ReadFeatureReport,
365 // and one that's included in the buffer we receive. We pad the bytes to read and skip over the report ID
366 // if necessary.
367#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_MACOS)
368 ++ucBytesToRead;
369 ++ucDataStartOffset;
370#endif
371
372 while (nRetries < BLE_MAX_READ_RETRIES) {
373 SDL_memset(uSegmentBuffer, 0, sizeof(uSegmentBuffer));
374 uSegmentBuffer[0] = BLE_REPORT_NUMBER;
375 nRet = SDL_hid_get_feature_report(dev->dev, uSegmentBuffer, ucBytesToRead);
376
377 DPRINTF("GetFeatureReport ble ret=%d\n", nRet);
378 HEXDUMP(uSegmentBuffer, nRet);
379
380 // Zero retry counter if we got data
381 if (nRet > 2 && (uSegmentBuffer[ucDataStartOffset + 1] & REPORT_SEGMENT_DATA_FLAG)) {
382 nRetries = 0;
383 } else {
384 nRetries++;
385 }
386
387 if (nRet > 0) {
388 int nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&assembler,
389 uSegmentBuffer + ucDataStartOffset,
390 nRet - ucDataStartOffset);
391
392 if (nPacketLength > 0 && nPacketLength < 65) {
393 // Leave space for "report number"
394 uBuffer[0] = 0;
395 SDL_memcpy(uBuffer + 1, assembler.uBuffer, nPacketLength);
396 return nPacketLength;
397 }
398 }
399 }
400 printf("Could not get a full ble packet after %d retries\n", nRetries);
401 return -1;
402 } else {
403 SDL_memset(uBuffer, 0, 65);
404
405 for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) {
406 nRet = SDL_hid_get_feature_report(dev->dev, uBuffer, 65);
407 if (nRet >= 0) {
408 break;
409 }
410
411 SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000);
412 }
413
414 DPRINTF("GetFeatureReport USB ret=%d\n", nRet);
415 HEXDUMP(uBuffer, nRet);
416 }
417
418 return nRet;
419}
420
421static int ReadResponse(SDL_HIDAPI_Device *dev, uint8_t uBuffer[65], int nExpectedResponse)
422{
423 for (int nRetries = 0; nRetries < 10; nRetries++) {
424 int nRet = GetFeatureReport(dev, uBuffer);
425
426 DPRINTF("ReadResponse( %p %p 0x%x )\n", dev, uBuffer, nExpectedResponse);
427
428 if (nRet < 0) {
429 continue;
430 }
431
432 DPRINTF("ReadResponse got %d bytes of data: ", nRet);
433 HEXDUMP(uBuffer, nRet);
434
435 if (uBuffer[1] != nExpectedResponse) {
436 continue;
437 }
438
439 return nRet;
440 }
441 return -1;
442}
443
444//---------------------------------------------------------------------------
445// Reset steam controller (unmap buttons and pads) and re-fetch capability bits
446//---------------------------------------------------------------------------
447static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew, uint32_t *punUpdateRateUS)
448{
449 // Firmware quirk: Set Feature and Get Feature requests always require a 65-byte buffer.
450 unsigned char buf[65];
451 unsigned int i;
452 int res = -1;
453 int nSettings = 0;
454 int nAttributesLength;
455 FeatureReportMsg *msg;
456 uint32_t unUpdateRateUS = 9000; // Good default rate
457
458 DPRINTF("ResetSteamController hid=%p\n", dev);
459
460 buf[0] = 0;
461 buf[1] = ID_GET_ATTRIBUTES_VALUES;
462 res = SetFeatureReport(dev, buf, 2);
463 if (res < 0) {
464 if (!bSuppressErrorSpew) {
465 printf("GET_ATTRIBUTES_VALUES failed for controller %p\n", dev);
466 }
467 return false;
468 }
469
470 // Retrieve GET_ATTRIBUTES_VALUES result
471 // Wireless controller endpoints without a connected controller will return nAttrs == 0
472 res = ReadResponse(dev, buf, ID_GET_ATTRIBUTES_VALUES);
473 if (res < 0 || buf[1] != ID_GET_ATTRIBUTES_VALUES) {
474 HEXDUMP(buf, res);
475 if (!bSuppressErrorSpew) {
476 printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev);
477 }
478 return false;
479 }
480
481 nAttributesLength = buf[2];
482 if (nAttributesLength > res) {
483 if (!bSuppressErrorSpew) {
484 printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev);
485 }
486 return false;
487 }
488
489 msg = (FeatureReportMsg *)&buf[1];
490 for (i = 0; i < (int)msg->header.length / sizeof(ControllerAttribute); ++i) {
491 uint8_t unAttribute = msg->payload.getAttributes.attributes[i].attributeTag;
492 uint32_t unValue = msg->payload.getAttributes.attributes[i].attributeValue;
493
494 switch (unAttribute) {
495 case ATTRIB_UNIQUE_ID:
496 break;
497 case ATTRIB_PRODUCT_ID:
498 break;
499 case ATTRIB_CAPABILITIES:
500 break;
501 case ATTRIB_CONNECTION_INTERVAL_IN_US:
502 unUpdateRateUS = unValue;
503 break;
504 default:
505 break;
506 }
507 }
508 if (punUpdateRateUS) {
509 *punUpdateRateUS = unUpdateRateUS;
510 }
511
512 // Clear digital button mappings
513 buf[0] = 0;
514 buf[1] = ID_CLEAR_DIGITAL_MAPPINGS;
515 res = SetFeatureReport(dev, buf, 2);
516 if (res < 0) {
517 if (!bSuppressErrorSpew) {
518 printf("CLEAR_DIGITAL_MAPPINGS failed for controller %p\n", dev);
519 }
520 return false;
521 }
522
523 // Reset the default settings
524 SDL_memset(buf, 0, 65);
525 buf[1] = ID_LOAD_DEFAULT_SETTINGS;
526 buf[2] = 0;
527 res = SetFeatureReport(dev, buf, 3);
528 if (res < 0) {
529 if (!bSuppressErrorSpew) {
530 printf("LOAD_DEFAULT_SETTINGS failed for controller %p\n", dev);
531 }
532 return false;
533 }
534
535 // Apply custom settings - clear trackpad modes (cancel mouse emulation), etc
536#define ADD_SETTING(SETTING, VALUE) \
537 buf[3 + nSettings * 3] = SETTING; \
538 buf[3 + nSettings * 3 + 1] = ((uint16_t)VALUE) & 0xFF; \
539 buf[3 + nSettings * 3 + 2] = ((uint16_t)VALUE) >> 8; \
540 ++nSettings;
541
542 SDL_memset(buf, 0, 65);
543 buf[1] = ID_SET_SETTINGS_VALUES;
544 ADD_SETTING(SETTING_WIRELESS_PACKET_VERSION, 2);
545 ADD_SETTING(SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE);
546#ifdef ENABLE_MOUSE_MODE
547 ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE);
548 ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 1);
549 ADD_SETTING(SETTING_MOMENTUM_MAXIMUM_VELOCITY, 20000); // [0-20000] default 8000
550 ADD_SETTING(SETTING_MOMENTUM_DECAY_AMOUNT, 50); // [0-50] default 5
551#else
552 ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE);
553 ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 0);
554#endif
555 buf[2] = (unsigned char)(nSettings * 3);
556
557 res = SetFeatureReport(dev, buf, 3 + nSettings * 3);
558 if (res < 0) {
559 if (!bSuppressErrorSpew) {
560 printf("SET_SETTINGS failed for controller %p\n", dev);
561 }
562 return false;
563 }
564
565#ifdef ENABLE_MOUSE_MODE
566 // Wait for ID_CLEAR_DIGITAL_MAPPINGS to be processed on the controller
567 bool bMappingsCleared = false;
568 int iRetry;
569 for (iRetry = 0; iRetry < 2; ++iRetry) {
570 SDL_memset(buf, 0, 65);
571 buf[1] = ID_GET_DIGITAL_MAPPINGS;
572 buf[2] = 1; // one byte - requesting from index 0
573 buf[3] = 0;
574 res = SetFeatureReport(dev, buf, 4);
575 if (res < 0) {
576 printf("GET_DIGITAL_MAPPINGS failed for controller %p\n", dev);
577 return false;
578 }
579
580 res = ReadResponse(dev, buf, ID_GET_DIGITAL_MAPPINGS);
581 if (res < 0 || buf[1] != ID_GET_DIGITAL_MAPPINGS) {
582 printf("Bad GET_DIGITAL_MAPPINGS response for controller %p\n", dev);
583 return false;
584 }
585
586 // If the length of the digital mappings result is not 1 (index byte, no mappings) then clearing hasn't executed
587 if (buf[2] == 1 && buf[3] == 0xFF) {
588 bMappingsCleared = true;
589 break;
590 }
591 usleep(CONTROLLER_CONFIGURATION_DELAY_US);
592 }
593
594 if (!bMappingsCleared && !bSuppressErrorSpew) {
595 printf("Warning: CLEAR_DIGITAL_MAPPINGS never completed for controller %p\n", dev);
596 }
597
598 // Set our new mappings
599 SDL_memset(buf, 0, 65);
600 buf[1] = ID_SET_DIGITAL_MAPPINGS;
601 buf[2] = 6; // 2 settings x 3 bytes
602 buf[3] = IO_DIGITAL_BUTTON_RIGHT_TRIGGER;
603 buf[4] = DEVICE_MOUSE;
604 buf[5] = MOUSE_BTN_LEFT;
605 buf[6] = IO_DIGITAL_BUTTON_LEFT_TRIGGER;
606 buf[7] = DEVICE_MOUSE;
607 buf[8] = MOUSE_BTN_RIGHT;
608
609 res = SetFeatureReport(dev, buf, 9);
610 if (res < 0) {
611 if (!bSuppressErrorSpew) {
612 printf("SET_DIGITAL_MAPPINGS failed for controller %p\n", dev);
613 }
614 return false;
615 }
616#endif // ENABLE_MOUSE_MODE
617
618 return true;
619}
620
621//---------------------------------------------------------------------------
622// Read from a Steam Controller
623//---------------------------------------------------------------------------
624static int ReadSteamController(SDL_hid_device *dev, uint8_t *pData, int nDataSize)
625{
626 SDL_memset(pData, 0, nDataSize);
627 pData[0] = BLE_REPORT_NUMBER; // hid_read will also overwrite this with the same value, 0x03
628 return SDL_hid_read(dev, pData, nDataSize);
629}
630
631//---------------------------------------------------------------------------
632// Set Steam Controller pairing state
633//---------------------------------------------------------------------------
634static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing)
635{
636 unsigned char buf[65];
637 SDL_memset(buf, 0, 65);
638 buf[1] = ID_ENABLE_PAIRING;
639 buf[2] = 2; // 2 payload bytes: bool + timeout
640 buf[3] = bEnablePairing ? 1 : 0;
641 buf[4] = bEnablePairing ? PAIRING_STATE_DURATION_SECONDS : 0;
642 SetFeatureReport(dev, buf, 5);
643}
644
645//---------------------------------------------------------------------------
646// Commit Steam Controller pairing
647//---------------------------------------------------------------------------
648static void CommitPairing(SDL_HIDAPI_Device *dev)
649{
650 unsigned char buf[65];
651 SDL_memset(buf, 0, 65);
652 buf[1] = ID_DONGLE_COMMIT_DEVICE;
653 SetFeatureReport(dev, buf, 2);
654}
655
656//---------------------------------------------------------------------------
657// Close a Steam Controller
658//---------------------------------------------------------------------------
659static void CloseSteamController(SDL_HIDAPI_Device *dev)
660{
661 // Switch the Steam Controller back to lizard mode so it works with the OS
662 unsigned char buf[65];
663 int nSettings = 0;
664
665 // Reset digital button mappings
666 SDL_memset(buf, 0, 65);
667 buf[1] = ID_SET_DEFAULT_DIGITAL_MAPPINGS;
668 SetFeatureReport(dev, buf, 2);
669
670 // Reset the default settings
671 SDL_memset(buf, 0, 65);
672 buf[1] = ID_LOAD_DEFAULT_SETTINGS;
673 buf[2] = 0;
674 SetFeatureReport(dev, buf, 3);
675
676 // Reset mouse mode for lizard mode
677 SDL_memset(buf, 0, 65);
678 buf[1] = ID_SET_SETTINGS_VALUES;
679 ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE);
680 buf[2] = (unsigned char)(nSettings * 3);
681 SetFeatureReport(dev, buf, 3 + nSettings * 3);
682}
683
684//---------------------------------------------------------------------------
685// Scale and clamp values to a range
686//---------------------------------------------------------------------------
687static float RemapValClamped(float val, float A, float B, float C, float D)
688{
689 if (A == B) {
690 return (val - B) >= 0.0f ? D : C;
691 } else {
692 float cVal = (val - A) / (B - A);
693 cVal = clamp(cVal, 0.0f, 1.0f);
694
695 return C + (D - C) * cVal;
696 }
697}
698
699//---------------------------------------------------------------------------
700// Rotate the pad coordinates
701//---------------------------------------------------------------------------
702static void RotatePad(int *pX, int *pY, float flAngleInRad)
703{
704 int origX = *pX, origY = *pY;
705
706 *pX = (int)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY);
707 *pY = (int)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY);
708}
709static void RotatePadShort(short *pX, short *pY, float flAngleInRad)
710{
711 int origX = *pX, origY = *pY;
712
713 *pX = (short)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY);
714 *pY = (short)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY);
715}
716
717//---------------------------------------------------------------------------
718// Format the first part of the state packet
719//---------------------------------------------------------------------------
720static void FormatStatePacketUntilGyro(SteamControllerStateInternal_t *pState, ValveControllerStatePacket_t *pStatePacket)
721{
722 int nLeftPadX;
723 int nLeftPadY;
724 int nRightPadX;
725 int nRightPadY;
726 int nPadOffset;
727
728 // 15 degrees in rad
729 const float flRotationAngle = 0.261799f;
730
731 SDL_memset(pState, 0, offsetof(SteamControllerStateInternal_t, sBatteryLevel));
732
733 // pState->eControllerType = m_eControllerType;
734 pState->eControllerType = 2; // k_eControllerType_SteamController;
735 pState->unPacketNum = pStatePacket->unPacketNum;
736
737 // We have a chunk of trigger data in the packet format here, so zero it out afterwards
738 SDL_memcpy(&pState->ulButtons, &pStatePacket->ButtonTriggerData.ulButtons, 8);
739 pState->ulButtons &= ~0xFFFF000000LL;
740
741 // The firmware uses this bit to tell us what kind of data is packed into the left two axes
742 if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {
743 // Finger-down bit not set; "left pad" is actually trackpad
744 pState->sLeftPadX = pState->sPrevLeftPad[0] = pStatePacket->sLeftPadX;
745 pState->sLeftPadY = pState->sPrevLeftPad[1] = pStatePacket->sLeftPadY;
746
747 if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {
748 // The controller is interleaving both stick and pad data, both are active
749 pState->sLeftStickX = pState->sPrevLeftStick[0];
750 pState->sLeftStickY = pState->sPrevLeftStick[1];
751 } else {
752 // The stick is not active
753 pState->sPrevLeftStick[0] = 0;
754 pState->sPrevLeftStick[1] = 0;
755 }
756 } else {
757 // Finger-down bit not set; "left pad" is actually joystick
758
759 // XXX there's a firmware bug where sometimes padX is 0 and padY is a large number (actually the battery voltage)
760 // If that happens skip this packet and report last frames stick
761 /*
762 if ( m_eControllerType == k_eControllerType_SteamControllerV2 && pStatePacket->sLeftPadY > 900 ) {
763 pState->sLeftStickX = pState->sPrevLeftStick[0];
764 pState->sLeftStickY = pState->sPrevLeftStick[1];
765 } else
766 */
767 {
768 pState->sPrevLeftStick[0] = pState->sLeftStickX = pStatePacket->sLeftPadX;
769 pState->sPrevLeftStick[1] = pState->sLeftStickY = pStatePacket->sLeftPadY;
770 }
771 /*
772 if (m_eControllerType == k_eControllerType_SteamControllerV2) {
773 UpdateV2JoystickCap(&state);
774 }
775 */
776
777 if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {
778 // The controller is interleaving both stick and pad data, both are active
779 pState->sLeftPadX = pState->sPrevLeftPad[0];
780 pState->sLeftPadY = pState->sPrevLeftPad[1];
781 } else {
782 // The trackpad is not active
783 pState->sPrevLeftPad[0] = 0;
784 pState->sPrevLeftPad[1] = 0;
785
786 // Old controllers send trackpad click for joystick button when trackpad is not active
787 if (pState->ulButtons & STEAM_BUTTON_LEFTPAD_CLICKED_MASK) {
788 pState->ulButtons &= ~STEAM_BUTTON_LEFTPAD_CLICKED_MASK;
789 pState->ulButtons |= STEAM_JOYSTICK_BUTTON_MASK;
790 }
791 }
792 }
793
794 // Fingerdown bit indicates if the packed left axis data was joystick or pad,
795 // but if we are interleaving both, the left finger is definitely on the pad.
796 if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {
797 pState->ulButtons |= STEAM_LEFTPAD_FINGERDOWN_MASK;
798 }
799
800 pState->sRightPadX = pStatePacket->sRightPadX;
801 pState->sRightPadY = pStatePacket->sRightPadY;
802
803 nLeftPadX = pState->sLeftPadX;
804 nLeftPadY = pState->sLeftPadY;
805 nRightPadX = pState->sRightPadX;
806 nRightPadY = pState->sRightPadY;
807
808 RotatePad(&nLeftPadX, &nLeftPadY, -flRotationAngle);
809 RotatePad(&nRightPadX, &nRightPadY, flRotationAngle);
810
811 if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {
812 nPadOffset = 1000;
813 } else {
814 nPadOffset = 0;
815 }
816
817 pState->sLeftPadX = (short)clamp(nLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
818 pState->sLeftPadY = (short)clamp(nLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
819
820 nPadOffset = 0;
821 if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) {
822 nPadOffset = 1000;
823 } else {
824 nPadOffset = 0;
825 }
826
827 pState->sRightPadX = (short)clamp(nRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
828 pState->sRightPadY = (short)clamp(nRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
829
830 pState->sTriggerL = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nLeft << 7) | pStatePacket->ButtonTriggerData.Triggers.nLeft), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
831 pState->sTriggerR = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nRight << 7) | pStatePacket->ButtonTriggerData.Triggers.nRight), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
832}
833
834//---------------------------------------------------------------------------
835// Update Steam Controller state from a BLE data packet, returns true if it parsed data
836//---------------------------------------------------------------------------
837static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState)
838{
839 const float flRotationAngle = 0.261799f;
840 uint32_t ucOptionDataMask;
841
842 pState->unPacketNum++;
843 ucOptionDataMask = (*pData++ & 0xF0);
844 ucOptionDataMask |= (uint32_t)(*pData++) << 8;
845 if (ucOptionDataMask & k_EBLEButtonChunk1) {
846 SDL_memcpy(&pState->ulButtons, pData, 3);
847 pData += 3;
848 }
849 if (ucOptionDataMask & k_EBLEButtonChunk2) {
850 // 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 packet
851 pState->sTriggerL = (unsigned short)RemapValClamped((float)((pData[0] << 7) | pData[0]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
852 pState->sTriggerR = (unsigned short)RemapValClamped((float)((pData[1] << 7) | pData[1]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
853 pData += 2;
854 }
855 if (ucOptionDataMask & k_EBLEButtonChunk3) {
856 uint8_t *pButtonByte = (uint8_t *)&pState->ulButtons;
857 pButtonByte[5] = *pData++;
858 pButtonByte[6] = *pData++;
859 pButtonByte[7] = *pData++;
860 }
861 if (ucOptionDataMask & k_EBLELeftJoystickChunk) {
862 // This doesn't handle any of the special headcrab stuff for raw joystick which is OK for now since that FW doesn't support
863 // this protocol yet either
864 int nLength = sizeof(pState->sLeftStickX) + sizeof(pState->sLeftStickY);
865 SDL_memcpy(&pState->sLeftStickX, pData, nLength);
866 pData += nLength;
867 }
868 if (ucOptionDataMask & k_EBLELeftTrackpadChunk) {
869 int nLength = sizeof(pState->sLeftPadX) + sizeof(pState->sLeftPadY);
870 int nPadOffset;
871 SDL_memcpy(&pState->sLeftPadX, pData, nLength);
872 if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {
873 nPadOffset = 1000;
874 } else {
875 nPadOffset = 0;
876 }
877
878 RotatePadShort(&pState->sLeftPadX, &pState->sLeftPadY, -flRotationAngle);
879 pState->sLeftPadX = (short)clamp(pState->sLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
880 pState->sLeftPadY = (short)clamp(pState->sLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
881 pData += nLength;
882 }
883 if (ucOptionDataMask & k_EBLERightTrackpadChunk) {
884 int nLength = sizeof(pState->sRightPadX) + sizeof(pState->sRightPadY);
885 int nPadOffset = 0;
886
887 SDL_memcpy(&pState->sRightPadX, pData, nLength);
888
889 if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) {
890 nPadOffset = 1000;
891 } else {
892 nPadOffset = 0;
893 }
894
895 RotatePadShort(&pState->sRightPadX, &pState->sRightPadY, flRotationAngle);
896 pState->sRightPadX = (short)clamp(pState->sRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
897 pState->sRightPadY = (short)clamp(pState->sRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
898 pData += nLength;
899 }
900 if (ucOptionDataMask & k_EBLEIMUAccelChunk) {
901 int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ);
902 SDL_memcpy(&pState->sAccelX, pData, nLength);
903 pData += nLength;
904 }
905 if (ucOptionDataMask & k_EBLEIMUGyroChunk) {
906 int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ);
907 SDL_memcpy(&pState->sGyroX, pData, nLength);
908 pData += nLength;
909 }
910 if (ucOptionDataMask & k_EBLEIMUQuatChunk) {
911 int nLength = sizeof(pState->sGyroQuatW) + sizeof(pState->sGyroQuatX) + sizeof(pState->sGyroQuatY) + sizeof(pState->sGyroQuatZ);
912 SDL_memcpy(&pState->sGyroQuatW, pData, nLength);
913 pData += nLength;
914 }
915 return true;
916}
917
918//---------------------------------------------------------------------------
919// Update Steam Controller state from a data packet, returns true if it parsed data
920//---------------------------------------------------------------------------
921static bool UpdateSteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState)
922{
923 ValveInReport_t *pInReport = (ValveInReport_t *)pData;
924
925 if (pInReport->header.unReportVersion != k_ValveInReportMsgVersion) {
926 if ((pData[0] & 0x0F) == k_EBLEReportState) {
927 return UpdateBLESteamControllerState(pData, nDataSize, pState);
928 }
929 return false;
930 }
931
932 if ((pInReport->header.ucType != ID_CONTROLLER_STATE) &&
933 (pInReport->header.ucType != ID_CONTROLLER_BLE_STATE)) {
934 return false;
935 }
936
937 if (pInReport->header.ucType == ID_CONTROLLER_STATE) {
938 ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState;
939
940 // No new data to process; indicate that we received a state packet, but otherwise do nothing.
941 if (pState->unPacketNum == pStatePacket->unPacketNum) {
942 return true;
943 }
944
945 FormatStatePacketUntilGyro(pState, pStatePacket);
946
947 pState->sAccelX = pStatePacket->sAccelX;
948 pState->sAccelY = pStatePacket->sAccelY;
949 pState->sAccelZ = pStatePacket->sAccelZ;
950
951 pState->sGyroQuatW = pStatePacket->sGyroQuatW;
952 pState->sGyroQuatX = pStatePacket->sGyroQuatX;
953 pState->sGyroQuatY = pStatePacket->sGyroQuatY;
954 pState->sGyroQuatZ = pStatePacket->sGyroQuatZ;
955
956 pState->sGyroX = pStatePacket->sGyroX;
957 pState->sGyroY = pStatePacket->sGyroY;
958 pState->sGyroZ = pStatePacket->sGyroZ;
959
960 } else if (pInReport->header.ucType == ID_CONTROLLER_BLE_STATE) {
961 ValveControllerBLEStatePacket_t *pBLEStatePacket = &pInReport->payload.controllerBLEState;
962 ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState;
963
964 // No new data to process; indicate that we received a state packet, but otherwise do nothing.
965 if (pState->unPacketNum == pStatePacket->unPacketNum) {
966 return true;
967 }
968
969 FormatStatePacketUntilGyro(pState, pStatePacket);
970
971 switch (pBLEStatePacket->ucGyroDataType) {
972 case 1:
973 pState->sGyroQuatW = ((float)pBLEStatePacket->sGyro[0]);
974 pState->sGyroQuatX = ((float)pBLEStatePacket->sGyro[1]);
975 pState->sGyroQuatY = ((float)pBLEStatePacket->sGyro[2]);
976 pState->sGyroQuatZ = ((float)pBLEStatePacket->sGyro[3]);
977 break;
978
979 case 2:
980 pState->sAccelX = pBLEStatePacket->sGyro[0];
981 pState->sAccelY = pBLEStatePacket->sGyro[1];
982 pState->sAccelZ = pBLEStatePacket->sGyro[2];
983 break;
984
985 case 3:
986 pState->sGyroX = pBLEStatePacket->sGyro[0];
987 pState->sGyroY = pBLEStatePacket->sGyro[1];
988 pState->sGyroZ = pBLEStatePacket->sGyro[2];
989 break;
990
991 default:
992 break;
993 }
994 }
995
996 return true;
997}
998
999/*****************************************************************************************************/
1000
1001typedef struct
1002{
1003 SDL_HIDAPI_Device *device;
1004 bool connected;
1005 bool report_sensors;
1006 uint32_t update_rate_in_us;
1007 Uint64 sensor_timestamp;
1008 Uint64 pairing_time;
1009
1010 SteamControllerPacketAssembler m_assembler;
1011 SteamControllerStateInternal_t m_state;
1012 SteamControllerStateInternal_t m_last_state;
1013} SDL_DriverSteam_Context;
1014
1015static bool IsDongle(Uint16 product_id)
1016{
1017 return (product_id == USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE);
1018}
1019
1020static void HIDAPI_DriverSteam_RegisterHints(SDL_HintCallback callback, void *userdata)
1021{
1022 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);
1023}
1024
1025static void HIDAPI_DriverSteam_UnregisterHints(SDL_HintCallback callback, void *userdata)
1026{
1027 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);
1028}
1029
1030static bool HIDAPI_DriverSteam_IsEnabled(void)
1031{
1032 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT);
1033}
1034
1035static 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)
1036{
1037 if (!SDL_IsJoystickSteamController(vendor_id, product_id)) {
1038 return false;
1039 }
1040
1041 if (device->is_bluetooth) {
1042 return true;
1043 }
1044
1045 if (IsDongle(product_id)) {
1046 if (interface_number >= 1 && interface_number <= 4) {
1047 // This is one of the wireless controller interfaces
1048 return true;
1049 }
1050 } else {
1051 if (interface_number == 2) {
1052 // This is the controller interface (not mouse or keyboard)
1053 return true;
1054 }
1055 }
1056 return false;
1057}
1058
1059static void HIDAPI_DriverSteam_SetPairingState(SDL_DriverSteam_Context *ctx, bool enabled)
1060{
1061 // Only have one dongle in pairing mode at a time
1062 static SDL_DriverSteam_Context *s_PairingContext = NULL;
1063
1064 if (enabled && s_PairingContext != NULL) {
1065 return;
1066 }
1067
1068 if (!enabled && s_PairingContext != ctx) {
1069 return;
1070 }
1071
1072 if (ctx->connected) {
1073 return;
1074 }
1075
1076 SetPairingState(ctx->device, enabled);
1077
1078 if (enabled) {
1079 ctx->pairing_time = SDL_GetTicks();
1080 s_PairingContext = ctx;
1081 } else {
1082 ctx->pairing_time = 0;
1083 s_PairingContext = NULL;
1084 }
1085}
1086
1087static void HIDAPI_DriverSteam_RenewPairingState(SDL_DriverSteam_Context *ctx)
1088{
1089 Uint64 now = SDL_GetTicks();
1090
1091 if (now >= ctx->pairing_time + PAIRING_STATE_DURATION_SECONDS * 1000) {
1092 SetPairingState(ctx->device, true);
1093 ctx->pairing_time = now;
1094 }
1095}
1096
1097static void HIDAPI_DriverSteam_CommitPairing(SDL_DriverSteam_Context *ctx)
1098{
1099 CommitPairing(ctx->device);
1100}
1101
1102static void SDLCALL SDL_PairingEnabledHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
1103{
1104 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata;
1105 bool enabled = SDL_GetStringBoolean(hint, false);
1106
1107 HIDAPI_DriverSteam_SetPairingState(ctx, enabled);
1108}
1109
1110static bool HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device)
1111{
1112 SDL_DriverSteam_Context *ctx;
1113
1114 ctx = (SDL_DriverSteam_Context *)SDL_calloc(1, sizeof(*ctx));
1115 if (!ctx) {
1116 return false;
1117 }
1118 ctx->device = device;
1119 device->context = ctx;
1120
1121#ifdef SDL_PLATFORM_WIN32
1122 if (device->serial) {
1123 // We get a garbage serial number on Windows
1124 SDL_free(device->serial);
1125 device->serial = NULL;
1126 }
1127#endif // SDL_PLATFORM_WIN32
1128
1129 HIDAPI_SetDeviceName(device, "Steam Controller");
1130
1131 // If this is a wireless dongle, request a wireless state update
1132 if (IsDongle(device->product_id)) {
1133 unsigned char buf[65];
1134 int res;
1135
1136 buf[0] = 0;
1137 buf[1] = ID_DONGLE_GET_WIRELESS_STATE;
1138 res = SetFeatureReport(device, buf, 2);
1139 if (res < 0) {
1140 return SDL_SetError("Failed to send ID_DONGLE_GET_WIRELESS_STATE request");
1141 }
1142
1143 for (int attempt = 0; attempt < 5; ++attempt) {
1144 uint8_t data[128];
1145
1146 res = ReadSteamController(device->dev, data, sizeof(data));
1147 if (res == 0) {
1148 SDL_Delay(1);
1149 continue;
1150 }
1151 if (res < 0) {
1152 break;
1153 }
1154
1155#ifdef DEBUG_STEAM_PROTOCOL
1156 HIDAPI_DumpPacket("Initial dongle packet: size = %d", data, res);
1157#endif
1158
1159 if (D0G_IS_WIRELESS_CONNECT(data, res)) {
1160 ctx->connected = true;
1161 break;
1162 } else if (D0G_IS_WIRELESS_DISCONNECT(data, res)) {
1163 ctx->connected = false;
1164 break;
1165 }
1166 }
1167
1168 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED,
1169 SDL_PairingEnabledHintChanged, ctx);
1170 } else {
1171 // Wired and BLE controllers are always connected if HIDAPI can see them
1172 ctx->connected = true;
1173 }
1174
1175 if (ctx->connected) {
1176 return HIDAPI_JoystickConnected(device, NULL);
1177 } else {
1178 // We will enumerate any attached controllers in UpdateDevice()
1179 return true;
1180 }
1181}
1182
1183static int HIDAPI_DriverSteam_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
1184{
1185 return -1;
1186}
1187
1188static void HIDAPI_DriverSteam_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
1189{
1190}
1191
1192static bool SetHomeLED(SDL_HIDAPI_Device *device, Uint8 value)
1193{
1194 unsigned char buf[65];
1195 int nSettings = 0;
1196
1197 SDL_memset(buf, 0, 65);
1198 buf[1] = ID_SET_SETTINGS_VALUES;
1199 ADD_SETTING(SETTING_LED_USER_BRIGHTNESS, value);
1200 buf[2] = (unsigned char)(nSettings * 3);
1201 if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) {
1202 return SDL_SetError("Couldn't write feature report");
1203 }
1204 return true;
1205}
1206
1207static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
1208{
1209 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata;
1210
1211 if (hint && *hint) {
1212 int value;
1213
1214 if (SDL_strchr(hint, '.') != NULL) {
1215 value = (int)(100.0f * SDL_atof(hint));
1216 if (value > 255) {
1217 value = 255;
1218 }
1219 } else if (SDL_GetStringBoolean(hint, true)) {
1220 value = 100;
1221 } else {
1222 value = 0;
1223 }
1224 SetHomeLED(ctx->device, (Uint8)value);
1225 }
1226}
1227
1228static bool HIDAPI_DriverSteam_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1229{
1230 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1231 float update_rate_in_hz = 0.0f;
1232
1233 SDL_AssertJoysticksLocked();
1234
1235 ctx->report_sensors = false;
1236 SDL_zero(ctx->m_assembler);
1237 SDL_zero(ctx->m_state);
1238 SDL_zero(ctx->m_last_state);
1239
1240 if (!ResetSteamController(device, false, &ctx->update_rate_in_us)) {
1241 SDL_SetError("Couldn't reset controller");
1242 return false;
1243 }
1244 if (ctx->update_rate_in_us > 0) {
1245 update_rate_in_hz = 1000000.0f / ctx->update_rate_in_us;
1246 }
1247
1248 InitializeSteamControllerPacketAssembler(&ctx->m_assembler, device->is_bluetooth);
1249
1250 // Initialize the joystick capabilities
1251 joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_BUTTONS;
1252 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
1253 joystick->nhats = 1;
1254
1255 if (IsDongle(device->product_id)) {
1256 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
1257 }
1258
1259 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz);
1260 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz);
1261
1262 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED,
1263 SDL_HomeLEDHintChanged, ctx);
1264
1265 return true;
1266}
1267
1268static bool HIDAPI_DriverSteam_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1269{
1270 // You should use the full Steam Input API for rumble support
1271 return SDL_Unsupported();
1272}
1273
1274static bool HIDAPI_DriverSteam_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
1275{
1276 return SDL_Unsupported();
1277}
1278
1279static Uint32 HIDAPI_DriverSteam_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1280{
1281 // You should use the full Steam Input API for extended capabilities
1282 return 0;
1283}
1284
1285static bool HIDAPI_DriverSteam_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1286{
1287 // You should use the full Steam Input API for LED support
1288 return SDL_Unsupported();
1289}
1290
1291static bool HIDAPI_DriverSteam_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
1292{
1293 if (size == 65) {
1294 if (SetFeatureReport(device, data, size) < 0) {
1295 return SDL_SetError("Couldn't write feature report");
1296 }
1297 return true;
1298 }
1299 return SDL_Unsupported();
1300}
1301
1302static bool HIDAPI_DriverSteam_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
1303{
1304 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1305 unsigned char buf[65];
1306 int nSettings = 0;
1307
1308 SDL_memset(buf, 0, 65);
1309 buf[1] = ID_SET_SETTINGS_VALUES;
1310 if (enabled) {
1311 ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO);
1312 } else {
1313 ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_OFF);
1314 }
1315 buf[2] = (unsigned char)(nSettings * 3);
1316 if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) {
1317 return SDL_SetError("Couldn't write feature report");
1318 }
1319
1320 ctx->report_sensors = enabled;
1321
1322 return true;
1323}
1324
1325static bool ControllerConnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick)
1326{
1327 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1328
1329 if (!HIDAPI_JoystickConnected(device, NULL)) {
1330 return false;
1331 }
1332
1333 // We'll automatically accept this controller if we're in pairing mode
1334 HIDAPI_DriverSteam_CommitPairing(ctx);
1335
1336 *joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1337 ctx->connected = true;
1338 return true;
1339}
1340
1341static void ControllerDisconnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick)
1342{
1343 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1344
1345 if (device->joysticks) {
1346 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1347 }
1348 ctx->connected = false;
1349 *joystick = NULL;
1350}
1351
1352static bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device)
1353{
1354 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1355 SDL_Joystick *joystick = NULL;
1356
1357 if (device->num_joysticks > 0) {
1358 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1359 }
1360
1361 if (ctx->pairing_time) {
1362 HIDAPI_DriverSteam_RenewPairingState(ctx);
1363 }
1364
1365 for (;;) {
1366 uint8_t data[128];
1367 int r, nPacketLength;
1368 const Uint8 *pPacket;
1369
1370 r = ReadSteamController(device->dev, data, sizeof(data));
1371 if (r == 0) {
1372 break;
1373 }
1374 if (r < 0) {
1375 // Failed to read from controller
1376 ControllerDisconnected(device, &joystick);
1377 return false;
1378 }
1379
1380#ifdef DEBUG_STEAM_PROTOCOL
1381 HIDAPI_DumpPacket("Steam Controller packet: size = %d", data, r);
1382#endif
1383
1384 nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&ctx->m_assembler, data, r);
1385 pPacket = ctx->m_assembler.uBuffer;
1386
1387 if (nPacketLength > 0 && UpdateSteamControllerState(pPacket, nPacketLength, &ctx->m_state)) {
1388 Uint64 timestamp = SDL_GetTicksNS();
1389
1390 if (!ctx->connected) {
1391 // Maybe we missed a wireless status packet?
1392 ControllerConnected(device, &joystick);
1393 }
1394
1395 if (!joystick) {
1396 continue;
1397 }
1398
1399 if (ctx->m_state.ulButtons != ctx->m_last_state.ulButtons) {
1400 Uint8 hat = 0;
1401
1402 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH,
1403 ((ctx->m_state.ulButtons & STEAM_BUTTON_SOUTH_MASK) != 0));
1404
1405 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST,
1406 ((ctx->m_state.ulButtons & STEAM_BUTTON_EAST_MASK) != 0));
1407
1408 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST,
1409 ((ctx->m_state.ulButtons & STEAM_BUTTON_WEST_MASK) != 0));
1410
1411 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH,
1412 ((ctx->m_state.ulButtons & STEAM_BUTTON_NORTH_MASK) != 0));
1413
1414 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
1415 ((ctx->m_state.ulButtons & STEAM_LEFT_BUMPER_MASK) != 0));
1416
1417 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
1418 ((ctx->m_state.ulButtons & STEAM_RIGHT_BUMPER_MASK) != 0));
1419
1420 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK,
1421 ((ctx->m_state.ulButtons & STEAM_BUTTON_MENU_MASK) != 0));
1422
1423 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START,
1424 ((ctx->m_state.ulButtons & STEAM_BUTTON_ESCAPE_MASK) != 0));
1425
1426 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE,
1427 ((ctx->m_state.ulButtons & STEAM_BUTTON_STEAM_MASK) != 0));
1428
1429 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK,
1430 ((ctx->m_state.ulButtons & STEAM_JOYSTICK_BUTTON_MASK) != 0));
1431 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE,
1432 ((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_LEFT_MASK) != 0));
1433 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE,
1434 ((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_RIGHT_MASK) != 0));
1435
1436 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK,
1437 ((ctx->m_state.ulButtons & STEAM_BUTTON_RIGHTPAD_CLICKED_MASK) != 0));
1438
1439 if (ctx->m_state.ulButtons & STEAM_DPAD_UP_MASK) {
1440 hat |= SDL_HAT_UP;
1441 }
1442 if (ctx->m_state.ulButtons & STEAM_DPAD_DOWN_MASK) {
1443 hat |= SDL_HAT_DOWN;
1444 }
1445 if (ctx->m_state.ulButtons & STEAM_DPAD_LEFT_MASK) {
1446 hat |= SDL_HAT_LEFT;
1447 }
1448 if (ctx->m_state.ulButtons & STEAM_DPAD_RIGHT_MASK) {
1449 hat |= SDL_HAT_RIGHT;
1450 }
1451 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1452 }
1453
1454 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (int)ctx->m_state.sTriggerL * 2 - 32768);
1455 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (int)ctx->m_state.sTriggerR * 2 - 32768);
1456
1457 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, ctx->m_state.sLeftStickX);
1458 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~ctx->m_state.sLeftStickY);
1459 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, ctx->m_state.sRightPadX);
1460 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~ctx->m_state.sRightPadY);
1461
1462 if (ctx->report_sensors) {
1463 float values[3];
1464
1465 ctx->sensor_timestamp += SDL_US_TO_NS(ctx->update_rate_in_us);
1466
1467 values[0] = (ctx->m_state.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
1468 values[1] = (ctx->m_state.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
1469 values[2] = (ctx->m_state.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
1470 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp, values, 3);
1471
1472 values[0] = (ctx->m_state.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
1473 values[1] = (ctx->m_state.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
1474 values[2] = (-ctx->m_state.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
1475 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp, values, 3);
1476 }
1477
1478 ctx->m_last_state = ctx->m_state;
1479 } else if (!ctx->connected && D0G_IS_WIRELESS_CONNECT(pPacket, nPacketLength)) {
1480 ControllerConnected(device, &joystick);
1481 } else if (ctx->connected && D0G_IS_WIRELESS_DISCONNECT(pPacket, nPacketLength)) {
1482 ControllerDisconnected(device, &joystick);
1483 }
1484 }
1485 return true;
1486}
1487
1488static void HIDAPI_DriverSteam_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1489{
1490 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1491
1492 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED,
1493 SDL_HomeLEDHintChanged, ctx);
1494
1495 CloseSteamController(device);
1496}
1497
1498static void HIDAPI_DriverSteam_FreeDevice(SDL_HIDAPI_Device *device)
1499{
1500 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1501
1502 if (IsDongle(device->product_id)) {
1503 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED,
1504 SDL_PairingEnabledHintChanged, ctx);
1505
1506 HIDAPI_DriverSteam_SetPairingState(ctx, false);
1507 }
1508}
1509
1510SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam = {
1511 SDL_HINT_JOYSTICK_HIDAPI_STEAM,
1512 true,
1513 HIDAPI_DriverSteam_RegisterHints,
1514 HIDAPI_DriverSteam_UnregisterHints,
1515 HIDAPI_DriverSteam_IsEnabled,
1516 HIDAPI_DriverSteam_IsSupportedDevice,
1517 HIDAPI_DriverSteam_InitDevice,
1518 HIDAPI_DriverSteam_GetDevicePlayerIndex,
1519 HIDAPI_DriverSteam_SetDevicePlayerIndex,
1520 HIDAPI_DriverSteam_UpdateDevice,
1521 HIDAPI_DriverSteam_OpenJoystick,
1522 HIDAPI_DriverSteam_RumbleJoystick,
1523 HIDAPI_DriverSteam_RumbleJoystickTriggers,
1524 HIDAPI_DriverSteam_GetJoystickCapabilities,
1525 HIDAPI_DriverSteam_SetJoystickLED,
1526 HIDAPI_DriverSteam_SendJoystickEffect,
1527 HIDAPI_DriverSteam_SetSensorsEnabled,
1528 HIDAPI_DriverSteam_CloseJoystick,
1529 HIDAPI_DriverSteam_FreeDevice,
1530};
1531
1532#endif // SDL_JOYSTICK_HIDAPI_STEAM
1533
1534#endif // SDL_JOYSTICK_HIDAPI
1535