SDL 2.0
SDL_hidapi_switch.c
Go to the documentation of this file.
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2019 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/* This driver supports the Nintendo Switch Pro controller.
22 Code and logic contributed by Valve Corporation under the SDL zlib license.
23*/
24#include "../../SDL_internal.h"
25
26#ifdef SDL_JOYSTICK_HIDAPI
27
28#include "SDL_hints.h"
29#include "SDL_log.h"
30#include "SDL_events.h"
31#include "SDL_timer.h"
32#include "SDL_joystick.h"
33#include "SDL_gamecontroller.h"
34#include "../SDL_sysjoystick.h"
36
37
38#ifdef SDL_JOYSTICK_HIDAPI_SWITCH
39
40typedef enum {
41 k_eSwitchInputReportIDs_SubcommandReply = 0x21,
42 k_eSwitchInputReportIDs_FullControllerState = 0x30,
43 k_eSwitchInputReportIDs_SimpleControllerState = 0x3F,
44 k_eSwitchInputReportIDs_CommandAck = 0x81,
45} ESwitchInputReportIDs;
46
47typedef enum {
48 k_eSwitchOutputReportIDs_RumbleAndSubcommand = 0x01,
49 k_eSwitchOutputReportIDs_Rumble = 0x10,
50 k_eSwitchOutputReportIDs_Proprietary = 0x80,
51} ESwitchOutputReportIDs;
52
53typedef enum {
54 k_eSwitchSubcommandIDs_BluetoothManualPair = 0x01,
55 k_eSwitchSubcommandIDs_RequestDeviceInfo = 0x02,
56 k_eSwitchSubcommandIDs_SetInputReportMode = 0x03,
57 k_eSwitchSubcommandIDs_SetHCIState = 0x06,
58 k_eSwitchSubcommandIDs_SPIFlashRead = 0x10,
59 k_eSwitchSubcommandIDs_SetPlayerLights = 0x30,
60 k_eSwitchSubcommandIDs_SetHomeLight = 0x38,
61 k_eSwitchSubcommandIDs_EnableIMU = 0x40,
62 k_eSwitchSubcommandIDs_SetIMUSensitivity = 0x41,
63 k_eSwitchSubcommandIDs_EnableVibration = 0x48,
64} ESwitchSubcommandIDs;
65
66typedef enum {
67 k_eSwitchProprietaryCommandIDs_Handshake = 0x02,
68 k_eSwitchProprietaryCommandIDs_HighSpeed = 0x03,
69 k_eSwitchProprietaryCommandIDs_ForceUSB = 0x04,
70 k_eSwitchProprietaryCommandIDs_ClearUSB = 0x05,
71 k_eSwitchProprietaryCommandIDs_ResetMCU = 0x06,
72} ESwitchProprietaryCommandIDs;
73
74typedef enum {
75 k_eSwitchDeviceInfoControllerType_JoyConLeft = 0x1,
76 k_eSwitchDeviceInfoControllerType_JoyConRight = 0x2,
77 k_eSwitchDeviceInfoControllerType_ProController = 0x3,
78} ESwitchDeviceInfoControllerType;
79
80#define k_unSwitchOutputPacketDataLength 49
81#define k_unSwitchMaxOutputPacketLength 64
82#define k_unSwitchBluetoothPacketLength k_unSwitchOutputPacketDataLength
83#define k_unSwitchUSBPacketLength k_unSwitchMaxOutputPacketLength
84
85#define k_unSPIStickCalibrationStartOffset 0x603D
86#define k_unSPIStickCalibrationEndOffset 0x604E
87#define k_unSPIStickCalibrationLength (k_unSPIStickCalibrationEndOffset - k_unSPIStickCalibrationStartOffset + 1)
88
89#pragma pack(1)
90typedef struct
91{
92 Uint8 rgucButtons[2];
93 Uint8 ucStickHat;
94 Sint16 sJoystickLeft[2];
95 Sint16 sJoystickRight[2];
96} SwitchSimpleStatePacket_t;
97
98typedef struct
99{
100 Uint8 ucCounter;
101 Uint8 ucBatteryAndConnection;
102 Uint8 rgucButtons[3];
103 Uint8 rgucJoystickLeft[3];
104 Uint8 rgucJoystickRight[3];
105 Uint8 ucVibrationCode;
106} SwitchControllerStatePacket_t;
107
108typedef struct
109{
110 SwitchControllerStatePacket_t controllerState;
111
112 struct {
113 Sint16 sAccelX;
114 Sint16 sAccelY;
115 Sint16 sAccelZ;
116
117 Sint16 sGyroX;
118 Sint16 sGyroY;
119 Sint16 sGyroZ;
120 } imuState[3];
121} SwitchStatePacket_t;
122
123typedef struct
124{
125 Uint32 unAddress;
126 Uint8 ucLength;
127} SwitchSPIOpData_t;
128
129typedef struct
130{
131 SwitchControllerStatePacket_t m_controllerState;
132
133 Uint8 ucSubcommandAck;
134 Uint8 ucSubcommandID;
135
136 #define k_unSubcommandDataBytes 35
137 union {
138 Uint8 rgucSubcommandData[ k_unSubcommandDataBytes ];
139
140 struct {
141 SwitchSPIOpData_t opData;
142 Uint8 rgucReadData[ k_unSubcommandDataBytes - sizeof(SwitchSPIOpData_t) ];
143 } spiReadData;
144
145 struct {
146 Uint8 rgucFirmwareVersion[2];
147 Uint8 ucDeviceType;
148 Uint8 ucFiller1;
149 Uint8 rgucMACAddress[6];
150 Uint8 ucFiller2;
151 Uint8 ucColorLocation;
152 } deviceInfo;
153 };
154} SwitchSubcommandInputPacket_t;
155
156typedef struct
157{
158 Uint8 rgucData[4];
159} SwitchRumbleData_t;
160
161typedef struct
162{
163 Uint8 ucPacketType;
164 Uint8 ucPacketNumber;
165 SwitchRumbleData_t rumbleData[2];
166} SwitchCommonOutputPacket_t;
167
168typedef struct
169{
170 SwitchCommonOutputPacket_t commonData;
171
172 Uint8 ucSubcommandID;
173 Uint8 rgucSubcommandData[ k_unSwitchOutputPacketDataLength - sizeof(SwitchCommonOutputPacket_t) - 1 ];
174} SwitchSubcommandOutputPacket_t;
175
176typedef struct
177{
178 Uint8 ucPacketType;
179 Uint8 ucProprietaryID;
180
181 Uint8 rgucProprietaryData[ k_unSwitchOutputPacketDataLength - 1 - 1 ];
182} SwitchProprietaryOutputPacket_t;
183#pragma pack()
184
185typedef struct {
186 hid_device *dev;
187 SDL_bool m_bIsUsingBluetooth;
188 Uint8 m_nCommandNumber;
189 SwitchCommonOutputPacket_t m_RumblePacket;
190 Uint32 m_nRumbleExpiration;
191 Uint8 m_rgucReadBuffer[k_unSwitchMaxOutputPacketLength];
192 SwitchSimpleStatePacket_t m_lastSimpleState;
193 SwitchStatePacket_t m_lastFullState;
194
195 struct StickCalibrationData {
196 struct {
197 Sint16 sCenter;
198 Sint16 sMin;
199 Sint16 sMax;
200 } axis[2];
201 } m_StickCalData[2];
202
203 struct StickExtents {
204 struct {
205 Sint16 sMin;
206 Sint16 sMax;
207 } axis[2];
208 } m_StickExtents[2];
209} SDL_DriverSwitch_Context;
210
211
212static SDL_bool
213HIDAPI_DriverSwitch_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number)
214{
215 return SDL_IsJoystickNintendoSwitchPro(vendor_id, product_id);
216}
217
218static const char *
219HIDAPI_DriverSwitch_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
220{
221 /* Give a user friendly name for this controller */
222 if (SDL_IsJoystickNintendoSwitchPro(vendor_id, product_id)) {
223 return "Nintendo Switch Pro Controller";
224 }
225 return NULL;
226}
227
228static int ReadInput(SDL_DriverSwitch_Context *ctx)
229{
230 return hid_read_timeout(ctx->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0);
231}
232
233static int WriteOutput(SDL_DriverSwitch_Context *ctx, Uint8 *data, int size)
234{
235 return hid_write(ctx->dev, data, size);
236}
237
238static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID)
239{
240 /* Average response time for messages is ~30ms */
241 Uint32 TimeoutMs = 100;
242 Uint32 startTicks = SDL_GetTicks();
243
244 int nRead = 0;
245 while ((nRead = ReadInput(ctx)) != -1) {
246 if (nRead > 0) {
247 if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) {
248 SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[ 1 ];
249 if (reply->ucSubcommandID == expectedID && (reply->ucSubcommandAck & 0x80)) {
250 return reply;
251 }
252 }
253 } else {
254 SDL_Delay(1);
255 }
256
257 if (SDL_TICKS_PASSED(SDL_GetTicks(), startTicks + TimeoutMs)) {
258 break;
259 }
260 }
261 return NULL;
262}
263
264static SDL_bool ReadProprietaryReply(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs expectedID)
265{
266 /* Average response time for messages is ~30ms */
267 Uint32 TimeoutMs = 100;
268 Uint32 startTicks = SDL_GetTicks();
269
270 int nRead = 0;
271 while ((nRead = ReadInput(ctx)) != -1) {
272 if (nRead > 0) {
273 if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_CommandAck && ctx->m_rgucReadBuffer[ 1 ] == expectedID) {
274 return SDL_TRUE;
275 }
276 } else {
277 SDL_Delay(1);
278 }
279
280 if (SDL_TICKS_PASSED(SDL_GetTicks(), startTicks + TimeoutMs)) {
281 break;
282 }
283 }
284 return SDL_FALSE;
285}
286
287static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandOutputPacket_t *outPacket)
288{
289 SDL_memset(outPacket, 0, sizeof(*outPacket));
290
291 outPacket->commonData.ucPacketType = k_eSwitchOutputReportIDs_RumbleAndSubcommand;
292 outPacket->commonData.ucPacketNumber = ctx->m_nCommandNumber;
293
294 SDL_memcpy(&outPacket->commonData.rumbleData, &ctx->m_RumblePacket.rumbleData, sizeof(ctx->m_RumblePacket.rumbleData));
295
296 outPacket->ucSubcommandID = ucCommandID;
297 SDL_memcpy(outPacket->rgucSubcommandData, pBuf, ucLen);
298
299 ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF;
300}
301
302static SDL_bool WritePacket(SDL_DriverSwitch_Context *ctx, void *pBuf, Uint8 ucLen)
303{
304 Uint8 rgucBuf[k_unSwitchMaxOutputPacketLength];
305 const size_t unWriteSize = ctx->m_bIsUsingBluetooth ? k_unSwitchBluetoothPacketLength : k_unSwitchUSBPacketLength;
306
307 if (ucLen > k_unSwitchOutputPacketDataLength) {
308 return SDL_FALSE;
309 }
310
311 if (ucLen < unWriteSize) {
312 SDL_memcpy(rgucBuf, pBuf, ucLen);
313 SDL_memset(rgucBuf+ucLen, 0, unWriteSize-ucLen);
314 pBuf = rgucBuf;
315 ucLen = (Uint8)unWriteSize;
316 }
317 return (WriteOutput(ctx, (Uint8 *)pBuf, ucLen) >= 0);
318}
319
320static SDL_bool WriteSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandInputPacket_t **ppReply)
321{
322 int nRetries = 5;
323 SwitchSubcommandInputPacket_t *reply = NULL;
324
325 while (!reply && nRetries--) {
326 SwitchSubcommandOutputPacket_t commandPacket;
327 ConstructSubcommand(ctx, ucCommandID, pBuf, ucLen, &commandPacket);
328
329 if (!WritePacket(ctx, &commandPacket, sizeof(commandPacket))) {
330 continue;
331 }
332
333 reply = ReadSubcommandReply(ctx, ucCommandID);
334 }
335
336 if (ppReply) {
337 *ppReply = reply;
338 }
339 return reply != NULL;
340}
341
342static SDL_bool WriteProprietary(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs ucCommand, Uint8 *pBuf, Uint8 ucLen, SDL_bool waitForReply)
343{
344 int nRetries = 5;
345
346 while (nRetries--) {
347 SwitchProprietaryOutputPacket_t packet;
348
349 if ((!pBuf && ucLen > 0) || ucLen > sizeof(packet.rgucProprietaryData)) {
350 return SDL_FALSE;
351 }
352
353 packet.ucPacketType = k_eSwitchOutputReportIDs_Proprietary;
354 packet.ucProprietaryID = ucCommand;
355 SDL_memcpy(packet.rgucProprietaryData, pBuf, ucLen);
356
357 if (!WritePacket(ctx, &packet, sizeof(packet))) {
358 continue;
359 }
360
361 if (!waitForReply || ReadProprietaryReply(ctx, ucCommand)) {
362 return SDL_TRUE;
363 }
364 }
365 return SDL_FALSE;
366}
367
368static void SetNeutralRumble(SwitchRumbleData_t *pRumble)
369{
370 pRumble->rgucData[0] = 0x00;
371 pRumble->rgucData[1] = 0x01;
372 pRumble->rgucData[2] = 0x40;
373 pRumble->rgucData[3] = 0x40;
374}
375
376static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp)
377{
378 if (ucHighFreqAmp > 0 || usLowFreqAmp > 0) {
379 // High-band frequency and low-band amplitude are actually nine-bits each so they
380 // take a bit from the high-band amplitude and low-band frequency bytes respectively
381 pRumble->rgucData[0] = usHighFreq & 0xFF;
382 pRumble->rgucData[1] = ucHighFreqAmp | ((usHighFreq >> 8) & 0x01);
383
384 pRumble->rgucData[2] = ucLowFreq | ((usLowFreqAmp >> 8) & 0x80);
385 pRumble->rgucData[3] = usLowFreqAmp & 0xFF;
386
387#ifdef DEBUG_RUMBLE
388 SDL_Log("Freq: %.2X %.2X %.2X, Amp: %.2X %.2X %.2X\n",
389 usHighFreq & 0xFF, ((usHighFreq >> 8) & 0x01), ucLowFreq,
390 ucHighFreqAmp, ((usLowFreqAmp >> 8) & 0x80), usLowFreqAmp & 0xFF);
391#endif
392 } else {
393 SetNeutralRumble(pRumble);
394 }
395}
396
397static SDL_bool WriteRumble(SDL_DriverSwitch_Context *ctx)
398{
399 /* Write into m_RumblePacket rather than a temporary buffer to allow the current rumble state
400 * to be retained for subsequent rumble or subcommand packets sent to the controller
401 */
402 ctx->m_RumblePacket.ucPacketType = k_eSwitchOutputReportIDs_Rumble;
403 ctx->m_RumblePacket.ucPacketNumber = ctx->m_nCommandNumber;
404 ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF;
405
406 return WritePacket(ctx, (Uint8 *)&ctx->m_RumblePacket, sizeof(ctx->m_RumblePacket));
407}
408
409static SDL_bool BTrySetupUSB(SDL_DriverSwitch_Context *ctx)
410{
411 /* We have to send a connection handshake to the controller when communicating over USB
412 * before we're able to send it other commands. Luckily this command is not supported
413 * over Bluetooth, so we can use the controller's lack of response as a way to
414 * determine if the connection is over USB or Bluetooth
415 */
416 if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, SDL_TRUE)) {
417 return SDL_FALSE;
418 }
419 if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_HighSpeed, NULL, 0, SDL_TRUE)) {
420 return SDL_FALSE;
421 }
422 if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, SDL_TRUE)) {
423 return SDL_FALSE;
424 }
425 return SDL_TRUE;
426}
427
428static SDL_bool SetVibrationEnabled(SDL_DriverSwitch_Context *ctx, Uint8 enabled)
429{
430 return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_EnableVibration, &enabled, sizeof(enabled), NULL);
431
432}
433static SDL_bool SetInputMode(SDL_DriverSwitch_Context *ctx, Uint8 input_mode)
434{
435 return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetInputReportMode, &input_mode, 1, NULL);
436}
437
438static SDL_bool SetHomeLED(SDL_DriverSwitch_Context *ctx, Uint8 brightness)
439{
440 Uint8 ucLedIntensity = 0;
441 Uint8 rgucBuffer[4];
442
443 if (brightness > 0) {
444 if (brightness < 65) {
445 ucLedIntensity = (brightness + 5) / 10;
446 } else {
447 ucLedIntensity = (Uint8)SDL_ceilf(0xF * SDL_powf((float)brightness / 100.f, 2.13f));
448 }
449 }
450
451 rgucBuffer[0] = (0x0 << 4) | 0x1; /* 0 mini cycles (besides first), cycle duration 8ms */
452 rgucBuffer[1] = ((ucLedIntensity & 0xF) << 4) | 0x0; /* LED start intensity (0x0-0xF), 0 cycles (LED stays on at start intensity after first cycle) */
453 rgucBuffer[2] = ((ucLedIntensity & 0xF) << 4) | 0x0; /* First cycle LED intensity, 0x0 intensity for second cycle */
454 rgucBuffer[3] = (0x0 << 4) | 0x0; /* 8ms fade transition to first cycle, 8ms first cycle LED duration */
455
456 return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetHomeLight, rgucBuffer, sizeof(rgucBuffer), NULL);
457}
458
459static SDL_bool SetSlotLED(SDL_DriverSwitch_Context *ctx, Uint8 slot)
460{
461 Uint8 led_data = (1 << slot);
462 return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetPlayerLights, &led_data, sizeof(led_data), NULL);
463}
464
465static SDL_bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx)
466{
467 Uint8 *pStickCal;
468 size_t stick, axis;
469 SwitchSubcommandInputPacket_t *reply = NULL;
470
471 /* Read Calibration Info */
472 SwitchSPIOpData_t readParams;
473 readParams.unAddress = k_unSPIStickCalibrationStartOffset;
474 readParams.ucLength = k_unSPIStickCalibrationLength;
475
476 if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply)) {
477 return SDL_FALSE;
478 }
479
480 /* Stick calibration values are 12-bits each and are packed by bit
481 * For whatever reason the fields are in a different order for each stick
482 * Left: X-Max, Y-Max, X-Center, Y-Center, X-Min, Y-Min
483 * Right: X-Center, Y-Center, X-Min, Y-Min, X-Max, Y-Max
484 */
485 pStickCal = reply->spiReadData.rgucReadData;
486
487 /* Left stick */
488 ctx->m_StickCalData[0].axis[0].sMax = ((pStickCal[1] << 8) & 0xF00) | pStickCal[0]; /* X Axis max above center */
489 ctx->m_StickCalData[0].axis[1].sMax = (pStickCal[2] << 4) | (pStickCal[1] >> 4); /* Y Axis max above center */
490 ctx->m_StickCalData[0].axis[0].sCenter = ((pStickCal[4] << 8) & 0xF00) | pStickCal[3]; /* X Axis center */
491 ctx->m_StickCalData[0].axis[1].sCenter = (pStickCal[5] << 4) | (pStickCal[4] >> 4); /* Y Axis center */
492 ctx->m_StickCalData[0].axis[0].sMin = ((pStickCal[7] << 8) & 0xF00) | pStickCal[6]; /* X Axis min below center */
493 ctx->m_StickCalData[0].axis[1].sMin = (pStickCal[8] << 4) | (pStickCal[7] >> 4); /* Y Axis min below center */
494
495 /* Right stick */
496 ctx->m_StickCalData[1].axis[0].sCenter = ((pStickCal[10] << 8) & 0xF00) | pStickCal[9]; /* X Axis center */
497 ctx->m_StickCalData[1].axis[1].sCenter = (pStickCal[11] << 4) | (pStickCal[10] >> 4); /* Y Axis center */
498 ctx->m_StickCalData[1].axis[0].sMin = ((pStickCal[13] << 8) & 0xF00) | pStickCal[12]; /* X Axis min below center */
499 ctx->m_StickCalData[1].axis[1].sMin = (pStickCal[14] << 4) | (pStickCal[13] >> 4); /* Y Axis min below center */
500 ctx->m_StickCalData[1].axis[0].sMax = ((pStickCal[16] << 8) & 0xF00) | pStickCal[15]; /* X Axis max above center */
501 ctx->m_StickCalData[1].axis[1].sMax = (pStickCal[17] << 4) | (pStickCal[16] >> 4); /* Y Axis max above center */
502
503 /* Filter out any values that were uninitialized (0xFFF) in the SPI read */
504 for (stick = 0; stick < 2; ++stick) {
505 for (axis = 0; axis < 2; ++axis) {
506 if (ctx->m_StickCalData[stick].axis[axis].sCenter == 0xFFF) {
507 ctx->m_StickCalData[stick].axis[axis].sCenter = 0;
508 }
509 if (ctx->m_StickCalData[stick].axis[axis].sMax == 0xFFF) {
510 ctx->m_StickCalData[stick].axis[axis].sMax = 0;
511 }
512 if (ctx->m_StickCalData[stick].axis[axis].sMin == 0xFFF) {
513 ctx->m_StickCalData[stick].axis[axis].sMin = 0;
514 }
515 }
516 }
517
518 if (ctx->m_bIsUsingBluetooth) {
519 for (stick = 0; stick < 2; ++stick) {
520 for(axis = 0; axis < 2; ++axis) {
521 ctx->m_StickExtents[stick].axis[axis].sMin = (Sint16)(SDL_MIN_SINT16 * 0.5f);
522 ctx->m_StickExtents[stick].axis[axis].sMax = (Sint16)(SDL_MAX_SINT16 * 0.5f);
523 }
524 }
525 } else {
526 for (stick = 0; stick < 2; ++stick) {
527 for(axis = 0; axis < 2; ++axis) {
528 ctx->m_StickExtents[stick].axis[axis].sMin = -(Sint16)(ctx->m_StickCalData[stick].axis[axis].sMin * 0.7f);
529 ctx->m_StickExtents[stick].axis[axis].sMax = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sMax * 0.7f);
530 }
531 }
532 }
533 return SDL_TRUE;
534}
535
536static float fsel(float fComparand, float fValGE, float fLT)
537{
538 return fComparand >= 0 ? fValGE : fLT;
539}
540
541static float RemapVal(float val, float A, float B, float C, float D)
542{
543 if (A == B) {
544 return fsel(val - B , D , C);
545 }
546 return C + (D - C) * (val - A) / (B - A);
547}
548
549static Sint16 ApplyStickCalibrationCentered(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue, Sint16 sCenter)
550{
551 sRawValue -= sCenter;
552
553 if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) {
554 ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue;
555 }
556 if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) {
557 ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue;
558 }
559
560 if (sRawValue > 0) {
561 return (Sint16)(RemapVal(sRawValue, 0, ctx->m_StickExtents[nStick].axis[nAxis].sMax, 0, SDL_MAX_SINT16));
562 } else {
563 return (Sint16)(RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, 0, SDL_MIN_SINT16, 0));
564 }
565}
566
567static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue)
568{
569 return ApplyStickCalibrationCentered(ctx, nStick, nAxis, sRawValue, ctx->m_StickCalData[nStick].axis[nAxis].sCenter);
570}
571
572static SDL_bool
573HIDAPI_DriverSwitch_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context)
574{
575 SDL_DriverSwitch_Context *ctx;
576 Uint8 input_mode;
577
578 ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx));
579 if (!ctx) {
581 return SDL_FALSE;
582 }
583 ctx->dev = dev;
584
585 *context = ctx;
586
587 /* Initialize rumble data */
588 SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
589 SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
590
591 /* Try setting up USB mode, and if that fails we're using Bluetooth */
592 if (!BTrySetupUSB(ctx)) {
593 ctx->m_bIsUsingBluetooth = SDL_TRUE;
594 }
595
596 if (!LoadStickCalibration(ctx)) {
597 SDL_SetError("Couldn't load stick calibration");
598 SDL_free(ctx);
599 return SDL_FALSE;
600 }
601
602 if (!SetVibrationEnabled(ctx, 1)) {
603 SDL_SetError("Couldn't enable vibration");
604 SDL_free(ctx);
605 return SDL_FALSE;
606 }
607
608 /* Set the desired input mode */
609 if (ctx->m_bIsUsingBluetooth) {
610 input_mode = k_eSwitchInputReportIDs_SimpleControllerState;
611 } else {
612 input_mode = k_eSwitchInputReportIDs_FullControllerState;
613 }
614 if (!SetInputMode(ctx, input_mode)) {
615 SDL_SetError("Couldn't set input mode");
616 SDL_free(ctx);
617 return SDL_FALSE;
618 }
619
620 /* Start sending USB reports */
621 if (!ctx->m_bIsUsingBluetooth) {
622 /* ForceUSB doesn't generate an ACK, so don't wait for a reply */
623 if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, SDL_FALSE)) {
624 SDL_SetError("Couldn't start USB reports");
625 SDL_free(ctx);
626 return SDL_FALSE;
627 }
628 }
629
630 /* Set the LED state */
631 SetHomeLED(ctx, 100);
632 SetSlotLED(ctx, (joystick->instance_id % 4));
633
634 /* Initialize the joystick capabilities */
635 joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX;
636 joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
637 joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
638
639 return SDL_TRUE;
640}
641
642static int
643HIDAPI_DriverSwitch_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
644{
645 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context;
646
647 /* Experimentally determined rumble values. These will only matter on some controllers as tested ones
648 * seem to disregard these and just use any non-zero rumble values as a binary flag for constant rumble
649 *
650 * More information about these values can be found here:
651 * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
652 */
653 const Uint16 k_usHighFreq = 0x0074;
654 const Uint8 k_ucHighFreqAmp = 0xBE;
655 const Uint8 k_ucLowFreq = 0x3D;
656 const Uint16 k_usLowFreqAmp = 0x806F;
657
658 if (low_frequency_rumble) {
659 EncodeRumble(&ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);
660 } else {
661 SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
662 }
663
664 if (high_frequency_rumble) {
665 EncodeRumble(&ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);
666 } else {
667 SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
668 }
669
670 if (!WriteRumble(ctx)) {
671 SDL_SetError("Couldn't send rumble packet");
672 return -1;
673 }
674
675 if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) {
676 ctx->m_nRumbleExpiration = SDL_GetTicks() + duration_ms;
677 } else {
678 ctx->m_nRumbleExpiration = 0;
679 }
680 return 0;
681}
682
683static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)
684{
685 /* 0x8000 is the neutral value for all joystick axes */
686 const Uint16 usJoystickCenter = 0x8000;
687 Sint16 axis;
688
689 if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {
690 Uint8 data = packet->rgucButtons[0];
697
698 axis = (data & 0x40) ? 32767 : -32768;
700
701 axis = (data & 0x80) ? 32767 : -32768;
703 }
704
705 if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {
706 Uint8 data = packet->rgucButtons[1];
712 }
713
714 if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {
715 SDL_bool dpad_up = SDL_FALSE;
716 SDL_bool dpad_down = SDL_FALSE;
717 SDL_bool dpad_left = SDL_FALSE;
718 SDL_bool dpad_right = SDL_FALSE;
719
720 switch (packet->ucStickHat) {
721 case 0:
722 dpad_up = SDL_TRUE;
723 break;
724 case 1:
725 dpad_up = SDL_TRUE;
726 dpad_right = SDL_TRUE;
727 break;
728 case 2:
729 dpad_right = SDL_TRUE;
730 break;
731 case 3:
732 dpad_right = SDL_TRUE;
733 dpad_down = SDL_TRUE;
734 break;
735 case 4:
736 dpad_down = SDL_TRUE;
737 break;
738 case 5:
739 dpad_left = SDL_TRUE;
740 dpad_down = SDL_TRUE;
741 break;
742 case 6:
743 dpad_left = SDL_TRUE;
744 break;
745 case 7:
746 dpad_up = SDL_TRUE;
747 dpad_left = SDL_TRUE;
748 break;
749 default:
750 break;
751 }
756 }
757
758 axis = ApplyStickCalibrationCentered(ctx, 0, 0, packet->sJoystickLeft[0], (Sint16)usJoystickCenter);
760
761 axis = ApplyStickCalibrationCentered(ctx, 0, 1, packet->sJoystickLeft[1], (Sint16)usJoystickCenter);
763
764 axis = ApplyStickCalibrationCentered(ctx, 1, 0, packet->sJoystickRight[0], (Sint16)usJoystickCenter);
766
767 axis = ApplyStickCalibrationCentered(ctx, 1, 1, packet->sJoystickRight[1], (Sint16)usJoystickCenter);
769
770 ctx->m_lastSimpleState = *packet;
771}
772
773static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)
774{
775 Sint16 axis;
776
777 if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) {
778 Uint8 data = packet->controllerState.rgucButtons[0];
784 axis = (data & 0x80) ? 32767 : -32768;
786 }
787
788 if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {
789 Uint8 data = packet->controllerState.rgucButtons[1];
794
796 }
797
798 if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) {
799 Uint8 data = packet->controllerState.rgucButtons[2];
805 axis = (data & 0x80) ? 32767 : -32768;
807 }
808
809 axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8);
810 axis = ApplyStickCalibration(ctx, 0, 0, axis);
812
813 axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4);
814 axis = ApplyStickCalibration(ctx, 0, 1, axis);
816
817 axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8);
818 axis = ApplyStickCalibration(ctx, 1, 0, axis);
820
821 axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4);
822 axis = ApplyStickCalibration(ctx, 1, 1, axis);
824
825 /* High nibble of battery/connection byte is battery level, low nibble is connection status
826 * LSB of connection nibble is USB/Switch connection status
827 */
828 if (packet->controllerState.ucBatteryAndConnection & 0x1) {
829 joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
830 } else {
831 /* LSB of the battery nibble is used to report charging.
832 * The battery level is reported from 0(empty)-8(full)
833 */
834 int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4;
835 if (level == 0) {
836 joystick->epowerlevel = SDL_JOYSTICK_POWER_EMPTY;
837 } else if (level <= 2) {
838 joystick->epowerlevel = SDL_JOYSTICK_POWER_LOW;
839 } else if (level <= 6) {
840 joystick->epowerlevel = SDL_JOYSTICK_POWER_MEDIUM;
841 } else {
842 joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL;
843 }
844 }
845
846 ctx->m_lastFullState = *packet;
847}
848
849static SDL_bool
850HIDAPI_DriverSwitch_Update(SDL_Joystick *joystick, hid_device *dev, void *context)
851{
852 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context;
853 int size;
854
855 while ((size = ReadInput(ctx)) > 0) {
856 switch (ctx->m_rgucReadBuffer[0]) {
857 case k_eSwitchInputReportIDs_SimpleControllerState:
858 HandleSimpleControllerState(joystick, ctx, (SwitchSimpleStatePacket_t *)&ctx->m_rgucReadBuffer[1]);
859 break;
860 case k_eSwitchInputReportIDs_FullControllerState:
861 HandleFullControllerState(joystick, ctx, (SwitchStatePacket_t *)&ctx->m_rgucReadBuffer[1]);
862 break;
863 default:
864 break;
865 }
866 }
867
868 if (ctx->m_nRumbleExpiration) {
869 Uint32 now = SDL_GetTicks();
870 if (SDL_TICKS_PASSED(now, ctx->m_nRumbleExpiration)) {
871 HIDAPI_DriverSwitch_Rumble(joystick, dev, context, 0, 0, 0);
872 }
873 }
874
875 return (size >= 0);
876}
877
878static void
879HIDAPI_DriverSwitch_Quit(SDL_Joystick *joystick, hid_device *dev, void *context)
880{
881 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context;
882
883 /* Restore simple input mode for other applications */
884 SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState);
885
887}
888
890{
892 SDL_TRUE,
893 HIDAPI_DriverSwitch_IsSupportedDevice,
894 HIDAPI_DriverSwitch_GetDeviceName,
895 HIDAPI_DriverSwitch_Init,
896 HIDAPI_DriverSwitch_Rumble,
897 HIDAPI_DriverSwitch_Update,
898 HIDAPI_DriverSwitch_Quit
899};
900
901#endif /* SDL_JOYSTICK_HIDAPI_SWITCH */
902
903#endif /* SDL_JOYSTICK_HIDAPI */
904
905/* vi: set ts=4 sw=4 expandtab: */
unsigned char uint8_t
#define SDL_SetError
#define SDL_memset
#define SDL_powf
#define SDL_ceilf
#define SDL_free
#define SDL_Delay
#define SDL_memcpy
#define SDL_Log
#define SDL_calloc
#define SDL_OutOfMemory()
Definition: SDL_error.h:52
#define SDL_RELEASED
Definition: SDL_events.h:49
#define SDL_PRESSED
Definition: SDL_events.h:50
@ SDL_CONTROLLER_AXIS_LEFTX
@ SDL_CONTROLLER_AXIS_TRIGGERRIGHT
@ SDL_CONTROLLER_AXIS_RIGHTY
@ SDL_CONTROLLER_AXIS_RIGHTX
@ SDL_CONTROLLER_AXIS_MAX
@ SDL_CONTROLLER_AXIS_TRIGGERLEFT
@ SDL_CONTROLLER_AXIS_LEFTY
@ SDL_CONTROLLER_BUTTON_B
@ SDL_CONTROLLER_BUTTON_BACK
@ SDL_CONTROLLER_BUTTON_LEFTSTICK
@ SDL_CONTROLLER_BUTTON_START
@ SDL_CONTROLLER_BUTTON_DPAD_LEFT
@ SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
@ SDL_CONTROLLER_BUTTON_DPAD_DOWN
@ SDL_CONTROLLER_BUTTON_DPAD_UP
@ SDL_CONTROLLER_BUTTON_MAX
@ SDL_CONTROLLER_BUTTON_LEFTSHOULDER
@ SDL_CONTROLLER_BUTTON_GUIDE
@ SDL_CONTROLLER_BUTTON_DPAD_RIGHT
@ SDL_CONTROLLER_BUTTON_X
@ SDL_CONTROLLER_BUTTON_RIGHTSTICK
@ SDL_CONTROLLER_BUTTON_Y
@ SDL_CONTROLLER_BUTTON_A
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch
#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH
A variable controlling whether the HIDAPI driver for Nintendo Switch controllers should be used.
Definition: SDL_hints.h:555
int SDL_PrivateJoystickAxis(SDL_Joystick *joystick, Uint8 axis, Sint16 value)
Definition: SDL_joystick.c:833
SDL_bool SDL_IsJoystickNintendoSwitchPro(Uint16 vendor, Uint16 product)
int SDL_PrivateJoystickButton(SDL_Joystick *joystick, Uint8 button, Uint8 state)
Definition: SDL_joystick.c:966
@ SDL_JOYSTICK_POWER_FULL
Definition: SDL_joystick.h:103
@ SDL_JOYSTICK_POWER_MEDIUM
Definition: SDL_joystick.h:102
@ SDL_JOYSTICK_POWER_EMPTY
Definition: SDL_joystick.h:100
@ SDL_JOYSTICK_POWER_WIRED
Definition: SDL_joystick.h:104
@ SDL_JOYSTICK_POWER_LOW
Definition: SDL_joystick.h:101
GLint level
Definition: SDL_opengl.h:1572
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLuint GLfloat * val
GLsizeiptr size
SDL_bool
Definition: SDL_stdinc.h:162
@ SDL_TRUE
Definition: SDL_stdinc.h:164
@ SDL_FALSE
Definition: SDL_stdinc.h:163
#define SDL_MAX_SINT16
A signed 16-bit integer type.
Definition: SDL_stdinc.h:183
uint32_t Uint32
Definition: SDL_stdinc.h:203
int16_t Sint16
Definition: SDL_stdinc.h:185
#define SDL_MIN_SINT16
Definition: SDL_stdinc.h:184
uint16_t Uint16
Definition: SDL_stdinc.h:191
uint8_t Uint8
Definition: SDL_stdinc.h:179
Uint32 SDL_GetTicks(void)
Get the number of milliseconds since the SDL library initialization.
#define SDL_TICKS_PASSED(A, B)
Compare SDL ticks values, and return true if A has passed B.
Definition: SDL_timer.h:56
#define NULL
Definition: begin_code.h:167
EGLContext ctx
Definition: eglext.h:208
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *device, unsigned char *data, size_t length, int milliseconds)
Read an Input report from a HID device with timeout.
struct hid_device_ hid_device
Definition: hidapi.h:50
int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length)
Write an Output report to a HID device.
SDL_Texture * axis
static screen_context_t context
Definition: video.c:25