Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_wii.c
9906 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
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
#include "SDL_hidapi_rumble.h"
29
#include "SDL_hidapi_nintendo.h"
30
31
#ifdef SDL_JOYSTICK_HIDAPI_WII
32
33
// Define this if you want to log all packets from the controller
34
// #define DEBUG_WII_PROTOCOL
35
36
#define ENABLE_CONTINUOUS_REPORTING true
37
38
#define INPUT_WAIT_TIMEOUT_MS (3 * 1000)
39
#define MOTION_PLUS_UPDATE_TIME_MS (8 * 1000)
40
#define STATUS_UPDATE_TIME_MS (15 * 60 * 1000)
41
42
#define WII_EXTENSION_NONE 0x2E2E
43
#define WII_EXTENSION_UNINITIALIZED 0xFFFF
44
#define WII_EXTENSION_NUNCHUK 0x0000
45
#define WII_EXTENSION_GAMEPAD 0x0101
46
#define WII_EXTENSION_WIIUPRO 0x0120
47
#define WII_EXTENSION_MOTIONPLUS_MASK 0xF0FF
48
#define WII_EXTENSION_MOTIONPLUS_ID 0x0005
49
50
#define WII_MOTIONPLUS_MODE_NONE 0x00
51
#define WII_MOTIONPLUS_MODE_STANDARD 0x04
52
#define WII_MOTIONPLUS_MODE_NUNCHUK 0x05
53
#define WII_MOTIONPLUS_MODE_GAMEPAD 0x07
54
55
typedef enum
56
{
57
k_eWiiInputReportIDs_Status = 0x20,
58
k_eWiiInputReportIDs_ReadMemory = 0x21,
59
k_eWiiInputReportIDs_Acknowledge = 0x22,
60
k_eWiiInputReportIDs_ButtonData0 = 0x30,
61
k_eWiiInputReportIDs_ButtonData1 = 0x31,
62
k_eWiiInputReportIDs_ButtonData2 = 0x32,
63
k_eWiiInputReportIDs_ButtonData3 = 0x33,
64
k_eWiiInputReportIDs_ButtonData4 = 0x34,
65
k_eWiiInputReportIDs_ButtonData5 = 0x35,
66
k_eWiiInputReportIDs_ButtonData6 = 0x36,
67
k_eWiiInputReportIDs_ButtonData7 = 0x37,
68
k_eWiiInputReportIDs_ButtonDataD = 0x3D,
69
k_eWiiInputReportIDs_ButtonDataE = 0x3E,
70
k_eWiiInputReportIDs_ButtonDataF = 0x3F,
71
} EWiiInputReportIDs;
72
73
typedef enum
74
{
75
k_eWiiOutputReportIDs_Rumble = 0x10,
76
k_eWiiOutputReportIDs_LEDs = 0x11,
77
k_eWiiOutputReportIDs_DataReportingMode = 0x12,
78
k_eWiiOutputReportIDs_IRCameraEnable = 0x13,
79
k_eWiiOutputReportIDs_SpeakerEnable = 0x14,
80
k_eWiiOutputReportIDs_StatusRequest = 0x15,
81
k_eWiiOutputReportIDs_WriteMemory = 0x16,
82
k_eWiiOutputReportIDs_ReadMemory = 0x17,
83
k_eWiiOutputReportIDs_SpeakerData = 0x18,
84
k_eWiiOutputReportIDs_SpeakerMute = 0x19,
85
k_eWiiOutputReportIDs_IRCameraEnable2 = 0x1a,
86
} EWiiOutputReportIDs;
87
88
typedef enum
89
{
90
k_eWiiPlayerLEDs_P1 = 0x10,
91
k_eWiiPlayerLEDs_P2 = 0x20,
92
k_eWiiPlayerLEDs_P3 = 0x40,
93
k_eWiiPlayerLEDs_P4 = 0x80,
94
} EWiiPlayerLEDs;
95
96
typedef enum
97
{
98
k_eWiiCommunicationState_None, // No special communications happening
99
k_eWiiCommunicationState_CheckMotionPlusStage1, // Sent standard extension identify request
100
k_eWiiCommunicationState_CheckMotionPlusStage2, // Sent Motion Plus extension identify request
101
} EWiiCommunicationState;
102
103
typedef enum
104
{
105
k_eWiiButtons_A = SDL_GAMEPAD_BUTTON_MISC1,
106
k_eWiiButtons_B,
107
k_eWiiButtons_One,
108
k_eWiiButtons_Two,
109
k_eWiiButtons_Plus,
110
k_eWiiButtons_Minus,
111
k_eWiiButtons_Home,
112
k_eWiiButtons_DPad_Up,
113
k_eWiiButtons_DPad_Down,
114
k_eWiiButtons_DPad_Left,
115
k_eWiiButtons_DPad_Right,
116
k_eWiiButtons_Max
117
} EWiiButtons;
118
119
#define k_unWiiPacketDataLength 22
120
121
typedef struct
122
{
123
Uint8 rgucBaseButtons[2];
124
Uint8 rgucAccelerometer[3];
125
Uint8 rgucExtension[21];
126
bool hasBaseButtons;
127
bool hasAccelerometer;
128
Uint8 ucNExtensionBytes;
129
} WiiButtonData;
130
131
typedef struct
132
{
133
Uint16 min;
134
Uint16 max;
135
Uint16 center;
136
Uint16 deadzone;
137
} StickCalibrationData;
138
139
typedef struct
140
{
141
SDL_HIDAPI_Device *device;
142
SDL_Joystick *joystick;
143
Uint64 timestamp;
144
EWiiCommunicationState m_eCommState;
145
EWiiExtensionControllerType m_eExtensionControllerType;
146
bool m_bPlayerLights;
147
int m_nPlayerIndex;
148
bool m_bRumbleActive;
149
bool m_bMotionPlusPresent;
150
Uint8 m_ucMotionPlusMode;
151
bool m_bReportSensors;
152
Uint8 m_rgucReadBuffer[k_unWiiPacketDataLength];
153
Uint64 m_ulLastInput;
154
Uint64 m_ulLastStatus;
155
Uint64 m_ulNextMotionPlusCheck;
156
bool m_bDisconnected;
157
158
StickCalibrationData m_StickCalibrationData[6];
159
} SDL_DriverWii_Context;
160
161
static void HIDAPI_DriverWii_RegisterHints(SDL_HintCallback callback, void *userdata)
162
{
163
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata);
164
}
165
166
static void HIDAPI_DriverWii_UnregisterHints(SDL_HintCallback callback, void *userdata)
167
{
168
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata);
169
}
170
171
static bool HIDAPI_DriverWii_IsEnabled(void)
172
{
173
#if 1 // This doesn't work with the dolphinbar, so don't enable by default right now
174
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII, false);
175
#else
176
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII,
177
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI,
178
SDL_HIDAPI_DEFAULT));
179
#endif
180
}
181
182
static bool HIDAPI_DriverWii_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)
183
{
184
if (vendor_id == USB_VENDOR_NINTENDO &&
185
(product_id == USB_PRODUCT_NINTENDO_WII_REMOTE ||
186
product_id == USB_PRODUCT_NINTENDO_WII_REMOTE2)) {
187
return true;
188
}
189
return false;
190
}
191
192
static int ReadInput(SDL_DriverWii_Context *ctx)
193
{
194
int size;
195
196
// Make sure we don't try to read at the same time a write is happening
197
if (SDL_GetAtomicInt(&ctx->device->rumble_pending) > 0) {
198
return 0;
199
}
200
201
size = SDL_hid_read_timeout(ctx->device->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0);
202
#ifdef DEBUG_WII_PROTOCOL
203
if (size > 0) {
204
HIDAPI_DumpPacket("Wii packet: size = %d", ctx->m_rgucReadBuffer, size);
205
}
206
#endif
207
return size;
208
}
209
210
static bool WriteOutput(SDL_DriverWii_Context *ctx, const Uint8 *data, int size, bool sync)
211
{
212
#ifdef DEBUG_WII_PROTOCOL
213
if (size > 0) {
214
HIDAPI_DumpPacket("Wii write packet: size = %d", data, size);
215
}
216
#endif
217
if (sync) {
218
return SDL_hid_write(ctx->device->dev, data, size) >= 0;
219
} else {
220
// Use the rumble thread for general asynchronous writes
221
if (!SDL_HIDAPI_LockRumble()) {
222
return false;
223
}
224
return SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size) >= 0;
225
}
226
}
227
228
static bool ReadInputSync(SDL_DriverWii_Context *ctx, EWiiInputReportIDs expectedID, bool (*isMine)(const Uint8 *))
229
{
230
Uint64 endTicks = SDL_GetTicks() + 250; // Seeing successful reads after about 200 ms
231
232
int nRead = 0;
233
while ((nRead = ReadInput(ctx)) != -1) {
234
if (nRead > 0) {
235
if (ctx->m_rgucReadBuffer[0] == expectedID && (!isMine || isMine(ctx->m_rgucReadBuffer))) {
236
return true;
237
}
238
} else {
239
if (SDL_GetTicks() >= endTicks) {
240
break;
241
}
242
SDL_Delay(1);
243
}
244
}
245
SDL_SetError("Read timed out");
246
return false;
247
}
248
249
static bool IsWriteMemoryResponse(const Uint8 *data)
250
{
251
return data[3] == k_eWiiOutputReportIDs_WriteMemory;
252
}
253
254
static bool WriteRegister(SDL_DriverWii_Context *ctx, Uint32 address, const Uint8 *data, int size, bool sync)
255
{
256
Uint8 writeRequest[k_unWiiPacketDataLength];
257
258
SDL_zeroa(writeRequest);
259
writeRequest[0] = k_eWiiOutputReportIDs_WriteMemory;
260
writeRequest[1] = (Uint8)(0x04 | (Uint8)ctx->m_bRumbleActive);
261
writeRequest[2] = (address >> 16) & 0xff;
262
writeRequest[3] = (address >> 8) & 0xff;
263
writeRequest[4] = address & 0xff;
264
writeRequest[5] = (Uint8)size;
265
SDL_assert(size > 0 && size <= 16);
266
SDL_memcpy(writeRequest + 6, data, size);
267
268
if (!WriteOutput(ctx, writeRequest, sizeof(writeRequest), sync)) {
269
return false;
270
}
271
if (sync) {
272
// Wait for response
273
if (!ReadInputSync(ctx, k_eWiiInputReportIDs_Acknowledge, IsWriteMemoryResponse)) {
274
return false;
275
}
276
if (ctx->m_rgucReadBuffer[4]) {
277
SDL_SetError("Write memory failed: %u", ctx->m_rgucReadBuffer[4]);
278
return false;
279
}
280
}
281
return true;
282
}
283
284
static bool ReadRegister(SDL_DriverWii_Context *ctx, Uint32 address, int size, bool sync)
285
{
286
Uint8 readRequest[7];
287
288
readRequest[0] = k_eWiiOutputReportIDs_ReadMemory;
289
readRequest[1] = (Uint8)(0x04 | (Uint8)ctx->m_bRumbleActive);
290
readRequest[2] = (address >> 16) & 0xff;
291
readRequest[3] = (address >> 8) & 0xff;
292
readRequest[4] = address & 0xff;
293
readRequest[5] = (size >> 8) & 0xff;
294
readRequest[6] = size & 0xff;
295
296
SDL_assert(size > 0 && size <= 0xffff);
297
298
if (!WriteOutput(ctx, readRequest, sizeof(readRequest), sync)) {
299
return false;
300
}
301
if (sync) {
302
SDL_assert(size <= 16); // Only waiting for one packet is supported right now
303
// Wait for response
304
if (!ReadInputSync(ctx, k_eWiiInputReportIDs_ReadMemory, NULL)) {
305
return false;
306
}
307
}
308
return true;
309
}
310
311
static bool SendExtensionIdentify(SDL_DriverWii_Context *ctx, bool sync)
312
{
313
return ReadRegister(ctx, 0xA400FE, 2, sync);
314
}
315
316
static bool ParseExtensionIdentifyResponse(SDL_DriverWii_Context *ctx, Uint16 *extension)
317
{
318
int i;
319
320
if (ctx->m_rgucReadBuffer[0] != k_eWiiInputReportIDs_ReadMemory) {
321
SDL_SetError("Unexpected extension response type");
322
return false;
323
}
324
325
if (ctx->m_rgucReadBuffer[4] != 0x00 || ctx->m_rgucReadBuffer[5] != 0xFE) {
326
SDL_SetError("Unexpected extension response address");
327
return false;
328
}
329
330
if (ctx->m_rgucReadBuffer[3] != 0x10) {
331
Uint8 error = (ctx->m_rgucReadBuffer[3] & 0xF);
332
333
if (error == 7) {
334
// The extension memory isn't mapped
335
*extension = WII_EXTENSION_NONE;
336
return true;
337
}
338
339
if (error) {
340
SDL_SetError("Failed to read extension type: %u", error);
341
} else {
342
SDL_SetError("Unexpected read length when reading extension type: %d", (ctx->m_rgucReadBuffer[3] >> 4) + 1);
343
}
344
return false;
345
}
346
347
*extension = 0;
348
for (i = 6; i < 8; i++) {
349
*extension = *extension << 8 | ctx->m_rgucReadBuffer[i];
350
}
351
return true;
352
}
353
354
static EWiiExtensionControllerType GetExtensionType(Uint16 extension_id)
355
{
356
switch (extension_id) {
357
case WII_EXTENSION_NONE:
358
return k_eWiiExtensionControllerType_None;
359
case WII_EXTENSION_NUNCHUK:
360
return k_eWiiExtensionControllerType_Nunchuk;
361
case WII_EXTENSION_GAMEPAD:
362
return k_eWiiExtensionControllerType_Gamepad;
363
case WII_EXTENSION_WIIUPRO:
364
return k_eWiiExtensionControllerType_WiiUPro;
365
default:
366
return k_eWiiExtensionControllerType_Unknown;
367
}
368
}
369
370
static bool SendExtensionReset(SDL_DriverWii_Context *ctx, bool sync)
371
{
372
bool result = true;
373
{
374
Uint8 data = 0x55;
375
result = result && WriteRegister(ctx, 0xA400F0, &data, sizeof(data), sync);
376
}
377
// This write will fail if there is no extension connected, that's fine
378
{
379
Uint8 data = 0x00;
380
(void)WriteRegister(ctx, 0xA400FB, &data, sizeof(data), sync);
381
}
382
return result;
383
}
384
385
static bool GetMotionPlusState(SDL_DriverWii_Context *ctx, bool *connected, Uint8 *mode)
386
{
387
Uint16 extension;
388
389
if (connected) {
390
*connected = false;
391
}
392
if (mode) {
393
*mode = 0;
394
}
395
396
if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {
397
// The Wii U Pro controller never has the Motion Plus extension
398
return true;
399
}
400
401
if (SendExtensionIdentify(ctx, true) &&
402
ParseExtensionIdentifyResponse(ctx, &extension)) {
403
if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {
404
// Motion Plus is currently active
405
if (connected) {
406
*connected = true;
407
}
408
if (mode) {
409
*mode = (extension >> 8);
410
}
411
return true;
412
}
413
}
414
415
if (ReadRegister(ctx, 0xA600FE, 2, true) &&
416
ParseExtensionIdentifyResponse(ctx, &extension)) {
417
if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {
418
// Motion Plus is currently connected
419
if (connected) {
420
*connected = true;
421
}
422
}
423
return true;
424
}
425
426
// Failed to read the register or parse the response
427
return false;
428
}
429
430
static bool NeedsPeriodicMotionPlusCheck(SDL_DriverWii_Context *ctx, bool status_update)
431
{
432
if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {
433
// The Wii U Pro controller never has the Motion Plus extension
434
return false;
435
}
436
437
if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE && !status_update) {
438
// We'll get a status update when Motion Plus is disconnected
439
return false;
440
}
441
442
return true;
443
}
444
445
static void SchedulePeriodicMotionPlusCheck(SDL_DriverWii_Context *ctx)
446
{
447
ctx->m_ulNextMotionPlusCheck = SDL_GetTicks() + MOTION_PLUS_UPDATE_TIME_MS;
448
}
449
450
static void CheckMotionPlusConnection(SDL_DriverWii_Context *ctx)
451
{
452
SendExtensionIdentify(ctx, false);
453
454
ctx->m_eCommState = k_eWiiCommunicationState_CheckMotionPlusStage1;
455
}
456
457
static void ActivateMotionPlusWithMode(SDL_DriverWii_Context *ctx, Uint8 mode)
458
{
459
#ifdef SDL_PLATFORM_LINUX
460
/* Linux drivers maintain a lot of state around the Motion Plus
461
* extension, so don't mess with it here.
462
*/
463
#else
464
WriteRegister(ctx, 0xA600FE, &mode, sizeof(mode), true);
465
466
ctx->m_ucMotionPlusMode = mode;
467
#endif // LINUX
468
}
469
470
static void ActivateMotionPlus(SDL_DriverWii_Context *ctx)
471
{
472
Uint8 mode = WII_MOTIONPLUS_MODE_STANDARD;
473
474
// Pick the pass-through mode based on the connected controller
475
if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) {
476
mode = WII_MOTIONPLUS_MODE_NUNCHUK;
477
} else if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Gamepad) {
478
mode = WII_MOTIONPLUS_MODE_GAMEPAD;
479
}
480
ActivateMotionPlusWithMode(ctx, mode);
481
}
482
483
static void DeactivateMotionPlus(SDL_DriverWii_Context *ctx)
484
{
485
Uint8 data = 0x55;
486
WriteRegister(ctx, 0xA400F0, &data, sizeof(data), true);
487
488
// Wait for the deactivation status message
489
ReadInputSync(ctx, k_eWiiInputReportIDs_Status, NULL);
490
491
ctx->m_ucMotionPlusMode = WII_MOTIONPLUS_MODE_NONE;
492
}
493
494
static void UpdatePowerLevelWii(SDL_Joystick *joystick, Uint8 batteryLevelByte)
495
{
496
int percent;
497
if (batteryLevelByte > 178) {
498
percent = 100;
499
} else if (batteryLevelByte > 51) {
500
percent = 70;
501
} else if (batteryLevelByte > 13) {
502
percent = 20;
503
} else {
504
percent = 5;
505
}
506
SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent);
507
}
508
509
static void UpdatePowerLevelWiiU(SDL_Joystick *joystick, Uint8 extensionBatteryByte)
510
{
511
bool charging = !(extensionBatteryByte & 0x08);
512
bool pluggedIn = !(extensionBatteryByte & 0x04);
513
Uint8 batteryLevel = extensionBatteryByte >> 4;
514
515
if (pluggedIn) {
516
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
517
} else {
518
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
519
}
520
521
/* Not sure if all Wii U Pro controllers act like this, but on mine
522
* 4, 3, and 2 are held for about 20 hours each
523
* 1 is held for about 6 hours
524
* 0 is held for about 2 hours
525
* No value above 4 has been observed.
526
*/
527
SDL_PowerState state;
528
int percent;
529
if (charging) {
530
state = SDL_POWERSTATE_CHARGING;
531
} else if (pluggedIn) {
532
state = SDL_POWERSTATE_CHARGED;
533
} else {
534
state = SDL_POWERSTATE_ON_BATTERY;
535
}
536
if (batteryLevel >= 4) {
537
percent = 100;
538
} else if (batteryLevel == 3) {
539
percent = 70;
540
} else if (batteryLevel == 2) {
541
percent = 40;
542
} else if (batteryLevel == 1) {
543
percent = 10;
544
} else {
545
percent = 3;
546
}
547
SDL_SendJoystickPowerInfo(joystick, state, percent);
548
}
549
550
static EWiiInputReportIDs GetButtonPacketType(SDL_DriverWii_Context *ctx)
551
{
552
switch (ctx->m_eExtensionControllerType) {
553
case k_eWiiExtensionControllerType_WiiUPro:
554
return k_eWiiInputReportIDs_ButtonDataD;
555
case k_eWiiExtensionControllerType_Nunchuk:
556
case k_eWiiExtensionControllerType_Gamepad:
557
if (ctx->m_bReportSensors) {
558
return k_eWiiInputReportIDs_ButtonData5;
559
} else {
560
return k_eWiiInputReportIDs_ButtonData2;
561
}
562
default:
563
if (ctx->m_bReportSensors) {
564
return k_eWiiInputReportIDs_ButtonData5;
565
} else {
566
return k_eWiiInputReportIDs_ButtonData0;
567
}
568
}
569
}
570
571
static bool RequestButtonPacketType(SDL_DriverWii_Context *ctx, EWiiInputReportIDs type)
572
{
573
Uint8 data[3];
574
Uint8 tt = (Uint8)ctx->m_bRumbleActive;
575
576
// Continuous reporting off, tt & 4 == 0
577
if (ENABLE_CONTINUOUS_REPORTING) {
578
tt |= 4;
579
}
580
581
data[0] = k_eWiiOutputReportIDs_DataReportingMode;
582
data[1] = tt;
583
data[2] = type;
584
return WriteOutput(ctx, data, sizeof(data), false);
585
}
586
587
static void ResetButtonPacketType(SDL_DriverWii_Context *ctx)
588
{
589
RequestButtonPacketType(ctx, GetButtonPacketType(ctx));
590
}
591
592
static void InitStickCalibrationData(SDL_DriverWii_Context *ctx)
593
{
594
int i;
595
switch (ctx->m_eExtensionControllerType) {
596
case k_eWiiExtensionControllerType_WiiUPro:
597
for (i = 0; i < 4; i++) {
598
ctx->m_StickCalibrationData[i].min = 1000;
599
ctx->m_StickCalibrationData[i].max = 3000;
600
ctx->m_StickCalibrationData[i].center = 0;
601
ctx->m_StickCalibrationData[i].deadzone = 100;
602
}
603
break;
604
case k_eWiiExtensionControllerType_Gamepad:
605
for (i = 0; i < 4; i++) {
606
ctx->m_StickCalibrationData[i].min = i < 2 ? 9 : 5;
607
ctx->m_StickCalibrationData[i].max = i < 2 ? 54 : 26;
608
ctx->m_StickCalibrationData[i].center = 0;
609
ctx->m_StickCalibrationData[i].deadzone = i < 2 ? 4 : 2;
610
}
611
break;
612
case k_eWiiExtensionControllerType_Nunchuk:
613
for (i = 0; i < 2; i++) {
614
ctx->m_StickCalibrationData[i].min = 40;
615
ctx->m_StickCalibrationData[i].max = 215;
616
ctx->m_StickCalibrationData[i].center = 0;
617
ctx->m_StickCalibrationData[i].deadzone = 10;
618
}
619
break;
620
default:
621
break;
622
}
623
}
624
625
static void InitializeExtension(SDL_DriverWii_Context *ctx)
626
{
627
SendExtensionReset(ctx, true);
628
InitStickCalibrationData(ctx);
629
ResetButtonPacketType(ctx);
630
}
631
632
static void UpdateSlotLED(SDL_DriverWii_Context *ctx)
633
{
634
Uint8 leds;
635
Uint8 data[2];
636
637
// The lowest bit needs to have the rumble status
638
leds = (Uint8)ctx->m_bRumbleActive;
639
640
if (ctx->m_bPlayerLights) {
641
// Use the same LED codes as Smash 8-player for 5-7
642
if (ctx->m_nPlayerIndex == 0 || ctx->m_nPlayerIndex > 3) {
643
leds |= k_eWiiPlayerLEDs_P1;
644
}
645
if (ctx->m_nPlayerIndex == 1 || ctx->m_nPlayerIndex == 4) {
646
leds |= k_eWiiPlayerLEDs_P2;
647
}
648
if (ctx->m_nPlayerIndex == 2 || ctx->m_nPlayerIndex == 5) {
649
leds |= k_eWiiPlayerLEDs_P3;
650
}
651
if (ctx->m_nPlayerIndex == 3 || ctx->m_nPlayerIndex == 6) {
652
leds |= k_eWiiPlayerLEDs_P4;
653
}
654
// Turn on all lights for other player indexes
655
if (ctx->m_nPlayerIndex < 0 || ctx->m_nPlayerIndex > 6) {
656
leds |= k_eWiiPlayerLEDs_P1 | k_eWiiPlayerLEDs_P2 | k_eWiiPlayerLEDs_P3 | k_eWiiPlayerLEDs_P4;
657
}
658
}
659
660
data[0] = k_eWiiOutputReportIDs_LEDs;
661
data[1] = leds;
662
WriteOutput(ctx, data, sizeof(data), false);
663
}
664
665
static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
666
{
667
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)userdata;
668
bool bPlayerLights = SDL_GetStringBoolean(hint, true);
669
670
if (bPlayerLights != ctx->m_bPlayerLights) {
671
ctx->m_bPlayerLights = bPlayerLights;
672
673
UpdateSlotLED(ctx);
674
}
675
}
676
677
static EWiiExtensionControllerType ReadExtensionControllerType(SDL_HIDAPI_Device *device)
678
{
679
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
680
EWiiExtensionControllerType eExtensionControllerType = k_eWiiExtensionControllerType_Unknown;
681
const int MAX_ATTEMPTS = 20;
682
int attempts = 0;
683
684
// Create enough of a context to read the controller type from the device
685
for (attempts = 0; attempts < MAX_ATTEMPTS; ++attempts) {
686
Uint16 extension;
687
if (SendExtensionIdentify(ctx, true) &&
688
ParseExtensionIdentifyResponse(ctx, &extension)) {
689
Uint8 motion_plus_mode = 0;
690
if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {
691
motion_plus_mode = (Uint8)(extension >> 8);
692
}
693
if (motion_plus_mode || extension == WII_EXTENSION_UNINITIALIZED) {
694
SendExtensionReset(ctx, true);
695
if (SendExtensionIdentify(ctx, true)) {
696
ParseExtensionIdentifyResponse(ctx, &extension);
697
}
698
}
699
700
eExtensionControllerType = GetExtensionType(extension);
701
702
// Reset the Motion Plus controller if needed
703
if (motion_plus_mode) {
704
ActivateMotionPlusWithMode(ctx, motion_plus_mode);
705
}
706
break;
707
}
708
}
709
return eExtensionControllerType;
710
}
711
712
static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
713
{
714
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
715
716
switch (ctx->m_eExtensionControllerType) {
717
case k_eWiiExtensionControllerType_None:
718
HIDAPI_SetDeviceName(device, "Nintendo Wii Remote");
719
break;
720
case k_eWiiExtensionControllerType_Nunchuk:
721
HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Nunchuk");
722
break;
723
case k_eWiiExtensionControllerType_Gamepad:
724
HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Classic Controller");
725
break;
726
case k_eWiiExtensionControllerType_WiiUPro:
727
HIDAPI_SetDeviceName(device, "Nintendo Wii U Pro Controller");
728
break;
729
default:
730
HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Unknown Extension");
731
break;
732
}
733
device->guid.data[15] = ctx->m_eExtensionControllerType;
734
}
735
736
static bool HIDAPI_DriverWii_InitDevice(SDL_HIDAPI_Device *device)
737
{
738
SDL_DriverWii_Context *ctx;
739
740
ctx = (SDL_DriverWii_Context *)SDL_calloc(1, sizeof(*ctx));
741
if (!ctx) {
742
return false;
743
}
744
ctx->device = device;
745
device->context = ctx;
746
747
if (device->vendor_id == USB_VENDOR_NINTENDO) {
748
ctx->m_eExtensionControllerType = ReadExtensionControllerType(device);
749
750
UpdateDeviceIdentity(device);
751
}
752
return HIDAPI_JoystickConnected(device, NULL);
753
}
754
755
static int HIDAPI_DriverWii_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
756
{
757
return -1;
758
}
759
760
static void HIDAPI_DriverWii_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
761
{
762
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
763
764
if (!ctx->joystick) {
765
return;
766
}
767
768
ctx->m_nPlayerIndex = player_index;
769
770
UpdateSlotLED(ctx);
771
}
772
773
static bool HIDAPI_DriverWii_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
774
{
775
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
776
777
SDL_AssertJoysticksLocked();
778
779
ctx->joystick = joystick;
780
781
InitializeExtension(ctx);
782
783
GetMotionPlusState(ctx, &ctx->m_bMotionPlusPresent, &ctx->m_ucMotionPlusMode);
784
785
if (NeedsPeriodicMotionPlusCheck(ctx, false)) {
786
SchedulePeriodicMotionPlusCheck(ctx);
787
}
788
789
if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_None ||
790
ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) {
791
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f);
792
if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) {
793
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 100.0f);
794
}
795
796
if (ctx->m_bMotionPlusPresent) {
797
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 100.0f);
798
}
799
}
800
801
// Initialize player index (needed for setting LEDs)
802
ctx->m_nPlayerIndex = SDL_GetJoystickPlayerIndex(joystick);
803
ctx->m_bPlayerLights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED, true);
804
UpdateSlotLED(ctx);
805
806
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED,
807
SDL_PlayerLEDHintChanged, ctx);
808
809
// Initialize the joystick capabilities
810
if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {
811
joystick->nbuttons = 15;
812
} else {
813
// Maximum is Classic Controller + Wiimote
814
joystick->nbuttons = k_eWiiButtons_Max;
815
}
816
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
817
818
ctx->m_ulLastInput = SDL_GetTicks();
819
820
return true;
821
}
822
823
static bool HIDAPI_DriverWii_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
824
{
825
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
826
bool active = (low_frequency_rumble || high_frequency_rumble);
827
828
if (active != ctx->m_bRumbleActive) {
829
Uint8 data[2];
830
831
data[0] = k_eWiiOutputReportIDs_Rumble;
832
data[1] = (Uint8)active;
833
WriteOutput(ctx, data, sizeof(data), false);
834
835
ctx->m_bRumbleActive = active;
836
}
837
return true;
838
}
839
840
static bool HIDAPI_DriverWii_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
841
{
842
return SDL_Unsupported();
843
}
844
845
static Uint32 HIDAPI_DriverWii_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
846
{
847
return SDL_JOYSTICK_CAP_RUMBLE;
848
}
849
850
static bool HIDAPI_DriverWii_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
851
{
852
return SDL_Unsupported();
853
}
854
855
static bool HIDAPI_DriverWii_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
856
{
857
return SDL_Unsupported();
858
}
859
860
static bool HIDAPI_DriverWii_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
861
{
862
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
863
864
if (enabled != ctx->m_bReportSensors) {
865
ctx->m_bReportSensors = enabled;
866
867
if (ctx->m_bMotionPlusPresent) {
868
if (enabled) {
869
ActivateMotionPlus(ctx);
870
} else {
871
DeactivateMotionPlus(ctx);
872
}
873
}
874
875
ResetButtonPacketType(ctx);
876
}
877
return true;
878
}
879
880
static void PostStickCalibrated(Uint64 timestamp, SDL_Joystick *joystick, StickCalibrationData *calibration, Uint8 axis, Uint16 data)
881
{
882
Sint16 value = 0;
883
if (!calibration->center) {
884
// Center on first read
885
calibration->center = data;
886
return;
887
}
888
if (data < calibration->min) {
889
calibration->min = data;
890
}
891
if (data > calibration->max) {
892
calibration->max = data;
893
}
894
if (data < calibration->center - calibration->deadzone) {
895
Uint16 zero = calibration->center - calibration->deadzone;
896
Uint16 range = zero - calibration->min;
897
Uint16 distance = zero - data;
898
float fvalue = (float)distance / (float)range;
899
value = (Sint16)(fvalue * SDL_JOYSTICK_AXIS_MIN);
900
} else if (data > calibration->center + calibration->deadzone) {
901
Uint16 zero = calibration->center + calibration->deadzone;
902
Uint16 range = calibration->max - zero;
903
Uint16 distance = data - zero;
904
float fvalue = (float)distance / (float)range;
905
value = (Sint16)(fvalue * SDL_JOYSTICK_AXIS_MAX);
906
}
907
if (axis == SDL_GAMEPAD_AXIS_LEFTY || axis == SDL_GAMEPAD_AXIS_RIGHTY) {
908
if (value) {
909
value = ~value;
910
}
911
}
912
SDL_SendJoystickAxis(timestamp, joystick, axis, value);
913
}
914
915
/* Send button data to SDL
916
*`defs` is a mapping for each bit to which button it represents. 0xFF indicates an unused bit
917
*`data` is the button data from the controller
918
*`size` is the number of bytes in `data` and the number of arrays of 8 mappings in `defs`
919
*`on` is the joystick value to be sent if a bit is on
920
*`off` is the joystick value to be sent if a bit is off
921
*/
922
static void PostPackedButtonData(Uint64 timestamp, SDL_Joystick *joystick, const Uint8 defs[][8], const Uint8 *data, int size, bool on, bool off)
923
{
924
int i, j;
925
926
for (i = 0; i < size; i++) {
927
for (j = 0; j < 8; j++) {
928
Uint8 button = defs[i][j];
929
if (button != 0xFF) {
930
bool down = (data[i] >> j) & 1 ? on : off;
931
SDL_SendJoystickButton(timestamp, joystick, button, down);
932
}
933
}
934
}
935
}
936
937
static const Uint8 GAMEPAD_BUTTON_DEFS[3][8] = {
938
{
939
0xFF /* Unused */,
940
SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
941
SDL_GAMEPAD_BUTTON_START,
942
SDL_GAMEPAD_BUTTON_GUIDE,
943
SDL_GAMEPAD_BUTTON_BACK,
944
SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
945
SDL_GAMEPAD_BUTTON_DPAD_DOWN,
946
SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
947
},
948
{
949
SDL_GAMEPAD_BUTTON_DPAD_UP,
950
SDL_GAMEPAD_BUTTON_DPAD_LEFT,
951
0xFF /* ZR */,
952
SDL_GAMEPAD_BUTTON_NORTH,
953
SDL_GAMEPAD_BUTTON_EAST,
954
SDL_GAMEPAD_BUTTON_WEST,
955
SDL_GAMEPAD_BUTTON_SOUTH,
956
0xFF /*ZL*/,
957
},
958
{
959
SDL_GAMEPAD_BUTTON_RIGHT_STICK,
960
SDL_GAMEPAD_BUTTON_LEFT_STICK,
961
0xFF /* Charging */,
962
0xFF /* Plugged In */,
963
0xFF /* Unused */,
964
0xFF /* Unused */,
965
0xFF /* Unused */,
966
0xFF /* Unused */,
967
}
968
};
969
970
static const Uint8 MP_GAMEPAD_BUTTON_DEFS[3][8] = {
971
{
972
0xFF /* Unused */,
973
SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
974
SDL_GAMEPAD_BUTTON_START,
975
SDL_GAMEPAD_BUTTON_GUIDE,
976
SDL_GAMEPAD_BUTTON_BACK,
977
SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
978
SDL_GAMEPAD_BUTTON_DPAD_DOWN,
979
SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
980
},
981
{
982
0xFF /* Motion Plus data */,
983
0xFF /* Motion Plus data */,
984
0xFF /* ZR */,
985
SDL_GAMEPAD_BUTTON_NORTH,
986
SDL_GAMEPAD_BUTTON_EAST,
987
SDL_GAMEPAD_BUTTON_WEST,
988
SDL_GAMEPAD_BUTTON_SOUTH,
989
0xFF /*ZL*/,
990
},
991
{
992
SDL_GAMEPAD_BUTTON_RIGHT_STICK,
993
SDL_GAMEPAD_BUTTON_LEFT_STICK,
994
0xFF /* Charging */,
995
0xFF /* Plugged In */,
996
0xFF /* Unused */,
997
0xFF /* Unused */,
998
0xFF /* Unused */,
999
0xFF /* Unused */,
1000
}
1001
};
1002
1003
static const Uint8 MP_FIXUP_DPAD_BUTTON_DEFS[2][8] = {
1004
{
1005
SDL_GAMEPAD_BUTTON_DPAD_UP,
1006
0xFF,
1007
0xFF,
1008
0xFF,
1009
0xFF,
1010
0xFF,
1011
0xFF,
1012
0xFF,
1013
},
1014
{
1015
SDL_GAMEPAD_BUTTON_DPAD_LEFT,
1016
0xFF,
1017
0xFF,
1018
0xFF,
1019
0xFF,
1020
0xFF,
1021
0xFF,
1022
0xFF,
1023
}
1024
};
1025
1026
static void HandleWiiUProButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1027
{
1028
static const Uint8 axes[] = { SDL_GAMEPAD_AXIS_LEFTX, SDL_GAMEPAD_AXIS_RIGHTX, SDL_GAMEPAD_AXIS_LEFTY, SDL_GAMEPAD_AXIS_RIGHTY };
1029
const Uint8(*buttons)[8] = GAMEPAD_BUTTON_DEFS;
1030
Uint8 zl, zr;
1031
int i;
1032
1033
if (data->ucNExtensionBytes < 11) {
1034
return;
1035
}
1036
1037
// Buttons
1038
PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucExtension + 8, 3, false, true);
1039
1040
// Triggers
1041
zl = data->rgucExtension[9] & 0x80;
1042
zr = data->rgucExtension[9] & 0x04;
1043
SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, zl ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);
1044
SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, zr ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);
1045
1046
// Sticks
1047
for (i = 0; i < 4; i++) {
1048
Uint16 value = data->rgucExtension[i * 2] | (data->rgucExtension[i * 2 + 1] << 8);
1049
PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[i], axes[i], value);
1050
}
1051
1052
// Power
1053
UpdatePowerLevelWiiU(joystick, data->rgucExtension[10]);
1054
}
1055
1056
static void HandleGamepadControllerButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1057
{
1058
const Uint8(*buttons)[8] = (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) ? MP_GAMEPAD_BUTTON_DEFS : GAMEPAD_BUTTON_DEFS;
1059
Uint8 lx, ly, rx, ry, zl, zr;
1060
1061
if (data->ucNExtensionBytes < 6) {
1062
return;
1063
}
1064
1065
// Buttons
1066
PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucExtension + 4, 2, false, true);
1067
if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) {
1068
PostPackedButtonData(ctx->timestamp, joystick, MP_FIXUP_DPAD_BUTTON_DEFS, data->rgucExtension, 2, false, true);
1069
}
1070
1071
// Triggers
1072
zl = data->rgucExtension[5] & 0x80;
1073
zr = data->rgucExtension[5] & 0x04;
1074
SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, zl ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);
1075
SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, zr ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);
1076
1077
// Sticks
1078
if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) {
1079
lx = data->rgucExtension[0] & 0x3E;
1080
ly = data->rgucExtension[1] & 0x3E;
1081
} else {
1082
lx = data->rgucExtension[0] & 0x3F;
1083
ly = data->rgucExtension[1] & 0x3F;
1084
}
1085
rx = (data->rgucExtension[2] >> 7) | ((data->rgucExtension[1] >> 5) & 0x06) | ((data->rgucExtension[0] >> 3) & 0x18);
1086
ry = data->rgucExtension[2] & 0x1F;
1087
PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[0], SDL_GAMEPAD_AXIS_LEFTX, lx);
1088
PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[1], SDL_GAMEPAD_AXIS_LEFTY, ly);
1089
PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[2], SDL_GAMEPAD_AXIS_RIGHTX, rx);
1090
PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[3], SDL_GAMEPAD_AXIS_RIGHTY, ry);
1091
}
1092
1093
static void HandleWiiRemoteButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1094
{
1095
static const Uint8 buttons[2][8] = {
1096
{
1097
k_eWiiButtons_DPad_Left,
1098
k_eWiiButtons_DPad_Right,
1099
k_eWiiButtons_DPad_Down,
1100
k_eWiiButtons_DPad_Up,
1101
k_eWiiButtons_Plus,
1102
0xFF /* Unused */,
1103
0xFF /* Unused */,
1104
0xFF /* Unused */,
1105
},
1106
{
1107
k_eWiiButtons_Two,
1108
k_eWiiButtons_One,
1109
k_eWiiButtons_B,
1110
k_eWiiButtons_A,
1111
k_eWiiButtons_Minus,
1112
0xFF /* Unused */,
1113
0xFF /* Unused */,
1114
k_eWiiButtons_Home,
1115
}
1116
};
1117
if (data->hasBaseButtons) {
1118
PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucBaseButtons, 2, true, false);
1119
}
1120
}
1121
1122
static void HandleWiiRemoteButtonDataAsMainController(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1123
{
1124
/* Wii remote maps really badly to a normal controller
1125
* Mapped 1 and 2 as X and Y
1126
* Not going to attempt positional mapping
1127
*/
1128
static const Uint8 buttons[2][8] = {
1129
{
1130
SDL_GAMEPAD_BUTTON_DPAD_LEFT,
1131
SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
1132
SDL_GAMEPAD_BUTTON_DPAD_DOWN,
1133
SDL_GAMEPAD_BUTTON_DPAD_UP,
1134
SDL_GAMEPAD_BUTTON_START,
1135
0xFF /* Unused */,
1136
0xFF /* Unused */,
1137
0xFF /* Unused */,
1138
},
1139
{
1140
SDL_GAMEPAD_BUTTON_NORTH,
1141
SDL_GAMEPAD_BUTTON_WEST,
1142
SDL_GAMEPAD_BUTTON_SOUTH,
1143
SDL_GAMEPAD_BUTTON_EAST,
1144
SDL_GAMEPAD_BUTTON_BACK,
1145
0xFF /* Unused */,
1146
0xFF /* Unused */,
1147
SDL_GAMEPAD_BUTTON_GUIDE,
1148
}
1149
};
1150
if (data->hasBaseButtons) {
1151
PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucBaseButtons, 2, true, false);
1152
}
1153
}
1154
1155
static void HandleNunchuckButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1156
{
1157
bool c_button, z_button;
1158
1159
if (data->ucNExtensionBytes < 6) {
1160
return;
1161
}
1162
1163
if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_NUNCHUK) {
1164
c_button = (data->rgucExtension[5] & 0x08) ? false : true;
1165
z_button = (data->rgucExtension[5] & 0x04) ? false : true;
1166
} else {
1167
c_button = (data->rgucExtension[5] & 0x02) ? false : true;
1168
z_button = (data->rgucExtension[5] & 0x01) ? false : true;
1169
}
1170
SDL_SendJoystickButton(ctx->timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, c_button);
1171
SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, z_button ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN);
1172
PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[0], SDL_GAMEPAD_AXIS_LEFTX, data->rgucExtension[0]);
1173
PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[1], SDL_GAMEPAD_AXIS_LEFTY, data->rgucExtension[1]);
1174
1175
if (ctx->m_bReportSensors) {
1176
const float ACCEL_RES_PER_G = 200.0f;
1177
Sint16 x, y, z;
1178
float values[3];
1179
1180
x = (data->rgucExtension[2] << 2);
1181
y = (data->rgucExtension[3] << 2);
1182
z = (data->rgucExtension[4] << 2);
1183
1184
if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_NUNCHUK) {
1185
x |= ((data->rgucExtension[5] >> 3) & 0x02);
1186
y |= ((data->rgucExtension[5] >> 4) & 0x02);
1187
z &= ~0x04;
1188
z |= ((data->rgucExtension[5] >> 5) & 0x06);
1189
} else {
1190
x |= ((data->rgucExtension[5] >> 2) & 0x03);
1191
y |= ((data->rgucExtension[5] >> 4) & 0x03);
1192
z |= ((data->rgucExtension[5] >> 6) & 0x03);
1193
}
1194
1195
x -= 0x200;
1196
y -= 0x200;
1197
z -= 0x200;
1198
1199
values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1200
values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1201
values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1202
SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_ACCEL_L, ctx->timestamp, values, 3);
1203
}
1204
}
1205
1206
static void HandleMotionPlusData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1207
{
1208
if (ctx->m_bReportSensors) {
1209
const float GYRO_RES_PER_DEGREE = 8192.0f;
1210
int x, y, z;
1211
float values[3];
1212
1213
x = (data->rgucExtension[0] | ((data->rgucExtension[3] << 6) & 0xFF00)) - 8192;
1214
y = (data->rgucExtension[1] | ((data->rgucExtension[4] << 6) & 0xFF00)) - 8192;
1215
z = (data->rgucExtension[2] | ((data->rgucExtension[5] << 6) & 0xFF00)) - 8192;
1216
1217
if (data->rgucExtension[3] & 0x02) {
1218
// Slow rotation rate: 8192/440 units per deg/s
1219
x *= 440;
1220
} else {
1221
// Fast rotation rate: 8192/2000 units per deg/s
1222
x *= 2000;
1223
}
1224
if (data->rgucExtension[4] & 0x02) {
1225
// Slow rotation rate: 8192/440 units per deg/s
1226
y *= 440;
1227
} else {
1228
// Fast rotation rate: 8192/2000 units per deg/s
1229
y *= 2000;
1230
}
1231
if (data->rgucExtension[3] & 0x01) {
1232
// Slow rotation rate: 8192/440 units per deg/s
1233
z *= 440;
1234
} else {
1235
// Fast rotation rate: 8192/2000 units per deg/s
1236
z *= 2000;
1237
}
1238
1239
values[0] = -((float)z / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;
1240
values[1] = ((float)x / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;
1241
values[2] = ((float)y / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;
1242
SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_GYRO, ctx->timestamp, values, 3);
1243
}
1244
}
1245
1246
static void HandleWiiRemoteAccelData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1247
{
1248
const float ACCEL_RES_PER_G = 100.0f;
1249
Sint16 x, y, z;
1250
float values[3];
1251
1252
if (!ctx->m_bReportSensors) {
1253
return;
1254
}
1255
1256
x = ((data->rgucAccelerometer[0] << 2) | ((data->rgucBaseButtons[0] >> 5) & 0x03)) - 0x200;
1257
y = ((data->rgucAccelerometer[1] << 2) | ((data->rgucBaseButtons[1] >> 4) & 0x02)) - 0x200;
1258
z = ((data->rgucAccelerometer[2] << 2) | ((data->rgucBaseButtons[1] >> 5) & 0x02)) - 0x200;
1259
1260
values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1261
values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1262
values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1263
SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_ACCEL, ctx->timestamp, values, 3);
1264
}
1265
1266
static void HandleButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, WiiButtonData *data)
1267
{
1268
if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {
1269
HandleWiiUProButtonData(ctx, joystick, data);
1270
return;
1271
}
1272
1273
if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE &&
1274
data->ucNExtensionBytes > 5) {
1275
if (data->rgucExtension[5] & 0x01) {
1276
// The data is invalid, possibly during a hotplug
1277
return;
1278
}
1279
1280
if (data->rgucExtension[4] & 0x01) {
1281
if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_None) {
1282
// Something was plugged into the extension port, reinitialize to get new state
1283
ctx->m_bDisconnected = true;
1284
}
1285
} else {
1286
if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_None) {
1287
// Something was removed from the extension port, reinitialize to get new state
1288
ctx->m_bDisconnected = true;
1289
}
1290
}
1291
1292
if (data->rgucExtension[5] & 0x02) {
1293
HandleMotionPlusData(ctx, joystick, data);
1294
1295
// The extension data is consumed
1296
data->ucNExtensionBytes = 0;
1297
}
1298
}
1299
1300
HandleWiiRemoteButtonData(ctx, joystick, data);
1301
switch (ctx->m_eExtensionControllerType) {
1302
case k_eWiiExtensionControllerType_Nunchuk:
1303
HandleNunchuckButtonData(ctx, joystick, data);
1304
SDL_FALLTHROUGH;
1305
case k_eWiiExtensionControllerType_None:
1306
HandleWiiRemoteButtonDataAsMainController(ctx, joystick, data);
1307
break;
1308
case k_eWiiExtensionControllerType_Gamepad:
1309
HandleGamepadControllerButtonData(ctx, joystick, data);
1310
break;
1311
default:
1312
break;
1313
}
1314
HandleWiiRemoteAccelData(ctx, joystick, data);
1315
}
1316
1317
static void GetBaseButtons(WiiButtonData *dst, const Uint8 *src)
1318
{
1319
SDL_memcpy(dst->rgucBaseButtons, src, 2);
1320
dst->hasBaseButtons = true;
1321
}
1322
1323
static void GetAccelerometer(WiiButtonData *dst, const Uint8 *src)
1324
{
1325
SDL_memcpy(dst->rgucAccelerometer, src, 3);
1326
dst->hasAccelerometer = true;
1327
}
1328
1329
static void GetExtensionData(WiiButtonData *dst, const Uint8 *src, int size)
1330
{
1331
bool valid_data = false;
1332
int i;
1333
1334
if (size > sizeof(dst->rgucExtension)) {
1335
size = sizeof(dst->rgucExtension);
1336
}
1337
1338
for (i = 0; i < size; ++i) {
1339
if (src[i] != 0xFF) {
1340
valid_data = true;
1341
break;
1342
}
1343
}
1344
if (valid_data) {
1345
SDL_memcpy(dst->rgucExtension, src, size);
1346
dst->ucNExtensionBytes = (Uint8)size;
1347
}
1348
}
1349
1350
static void HandleStatus(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
1351
{
1352
bool hadExtension = ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_None;
1353
bool hasExtension = (ctx->m_rgucReadBuffer[3] & 2) ? true : false;
1354
WiiButtonData data;
1355
SDL_zero(data);
1356
GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1357
HandleButtonData(ctx, joystick, &data);
1358
1359
if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_WiiUPro) {
1360
// Wii U has separate battery level tracking
1361
UpdatePowerLevelWii(joystick, ctx->m_rgucReadBuffer[6]);
1362
}
1363
1364
// The report data format has been reset, need to update it
1365
ResetButtonPacketType(ctx);
1366
1367
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Status update, extension %s", hasExtension ? "CONNECTED" : "DISCONNECTED");
1368
1369
/* When Motion Plus is active, we get extension connect/disconnect status
1370
* through the Motion Plus packets. Otherwise we can use the status here.
1371
*/
1372
if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE) {
1373
/* Check to make sure the Motion Plus extension state hasn't changed,
1374
* otherwise we'll get extension connect/disconnect status through
1375
* Motion Plus packets.
1376
*/
1377
if (NeedsPeriodicMotionPlusCheck(ctx, true)) {
1378
ctx->m_ulNextMotionPlusCheck = SDL_GetTicks();
1379
}
1380
1381
} else if (hadExtension != hasExtension) {
1382
// Reinitialize to get new state
1383
ctx->m_bDisconnected = true;
1384
}
1385
}
1386
1387
static void HandleResponse(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
1388
{
1389
EWiiInputReportIDs type = (EWiiInputReportIDs)ctx->m_rgucReadBuffer[0];
1390
WiiButtonData data;
1391
SDL_assert(type == k_eWiiInputReportIDs_Acknowledge || type == k_eWiiInputReportIDs_ReadMemory);
1392
SDL_zero(data);
1393
GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1394
HandleButtonData(ctx, joystick, &data);
1395
1396
switch (ctx->m_eCommState) {
1397
case k_eWiiCommunicationState_None:
1398
break;
1399
1400
case k_eWiiCommunicationState_CheckMotionPlusStage1:
1401
case k_eWiiCommunicationState_CheckMotionPlusStage2:
1402
{
1403
Uint16 extension = 0;
1404
if (ParseExtensionIdentifyResponse(ctx, &extension)) {
1405
if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {
1406
// Motion Plus is currently active
1407
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Motion Plus CONNECTED (stage %d)", ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1 ? 1 : 2);
1408
1409
if (!ctx->m_bMotionPlusPresent) {
1410
// Reinitialize to get new sensor availability
1411
ctx->m_bDisconnected = true;
1412
}
1413
ctx->m_eCommState = k_eWiiCommunicationState_None;
1414
1415
} else if (ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1) {
1416
// Check to see if Motion Plus is present
1417
ReadRegister(ctx, 0xA600FE, 2, false);
1418
1419
ctx->m_eCommState = k_eWiiCommunicationState_CheckMotionPlusStage2;
1420
1421
} else {
1422
// Motion Plus is not present
1423
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Motion Plus DISCONNECTED (stage %d)", ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1 ? 1 : 2);
1424
1425
if (ctx->m_bMotionPlusPresent) {
1426
// Reinitialize to get new sensor availability
1427
ctx->m_bDisconnected = true;
1428
}
1429
ctx->m_eCommState = k_eWiiCommunicationState_None;
1430
}
1431
}
1432
} break;
1433
default:
1434
// Should never happen
1435
break;
1436
}
1437
}
1438
1439
static void HandleButtonPacket(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
1440
{
1441
EWiiInputReportIDs eExpectedReport = GetButtonPacketType(ctx);
1442
WiiButtonData data;
1443
1444
// FIXME: This should see if the data format is compatible rather than equal
1445
if (eExpectedReport != ctx->m_rgucReadBuffer[0]) {
1446
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Resetting report mode to %d", eExpectedReport);
1447
RequestButtonPacketType(ctx, eExpectedReport);
1448
}
1449
1450
// IR camera data is not supported
1451
SDL_zero(data);
1452
switch (ctx->m_rgucReadBuffer[0]) {
1453
case k_eWiiInputReportIDs_ButtonData0: // 30 BB BB
1454
GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1455
break;
1456
case k_eWiiInputReportIDs_ButtonData1: // 31 BB BB AA AA AA
1457
case k_eWiiInputReportIDs_ButtonData3: // 33 BB BB AA AA AA II II II II II II II II II II II II
1458
GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1459
GetAccelerometer(&data, ctx->m_rgucReadBuffer + 3);
1460
break;
1461
case k_eWiiInputReportIDs_ButtonData2: // 32 BB BB EE EE EE EE EE EE EE EE
1462
GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1463
GetExtensionData(&data, ctx->m_rgucReadBuffer + 3, 8);
1464
break;
1465
case k_eWiiInputReportIDs_ButtonData4: // 34 BB BB EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE
1466
GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1467
GetExtensionData(&data, ctx->m_rgucReadBuffer + 3, 19);
1468
break;
1469
case k_eWiiInputReportIDs_ButtonData5: // 35 BB BB AA AA AA EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE
1470
GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1471
GetAccelerometer(&data, ctx->m_rgucReadBuffer + 3);
1472
GetExtensionData(&data, ctx->m_rgucReadBuffer + 6, 16);
1473
break;
1474
case k_eWiiInputReportIDs_ButtonData6: // 36 BB BB II II II II II II II II II II EE EE EE EE EE EE EE EE EE
1475
GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1476
GetExtensionData(&data, ctx->m_rgucReadBuffer + 13, 9);
1477
break;
1478
case k_eWiiInputReportIDs_ButtonData7: // 37 BB BB AA AA AA II II II II II II II II II II EE EE EE EE EE EE
1479
GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1480
GetExtensionData(&data, ctx->m_rgucReadBuffer + 16, 6);
1481
break;
1482
case k_eWiiInputReportIDs_ButtonDataD: // 3d EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE
1483
GetExtensionData(&data, ctx->m_rgucReadBuffer + 1, 21);
1484
break;
1485
case k_eWiiInputReportIDs_ButtonDataE:
1486
case k_eWiiInputReportIDs_ButtonDataF:
1487
default:
1488
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Unsupported button data type %02x", ctx->m_rgucReadBuffer[0]);
1489
return;
1490
}
1491
HandleButtonData(ctx, joystick, &data);
1492
}
1493
1494
static void HandleInput(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
1495
{
1496
EWiiInputReportIDs type = (EWiiInputReportIDs)ctx->m_rgucReadBuffer[0];
1497
1498
// Set up for handling input
1499
ctx->timestamp = SDL_GetTicksNS();
1500
1501
if (type == k_eWiiInputReportIDs_Status) {
1502
HandleStatus(ctx, joystick);
1503
} else if (type == k_eWiiInputReportIDs_Acknowledge || type == k_eWiiInputReportIDs_ReadMemory) {
1504
HandleResponse(ctx, joystick);
1505
} else if (type >= k_eWiiInputReportIDs_ButtonData0 && type <= k_eWiiInputReportIDs_ButtonDataF) {
1506
HandleButtonPacket(ctx, joystick);
1507
} else {
1508
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Unexpected input packet of type %x", type);
1509
}
1510
}
1511
1512
static bool HIDAPI_DriverWii_UpdateDevice(SDL_HIDAPI_Device *device)
1513
{
1514
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
1515
SDL_Joystick *joystick = NULL;
1516
int size;
1517
Uint64 now;
1518
1519
if (device->num_joysticks > 0) {
1520
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1521
} else {
1522
return false;
1523
}
1524
1525
now = SDL_GetTicks();
1526
1527
while ((size = ReadInput(ctx)) > 0) {
1528
if (joystick) {
1529
HandleInput(ctx, joystick);
1530
}
1531
ctx->m_ulLastInput = now;
1532
}
1533
1534
/* Check to see if we've lost connection to the controller.
1535
* We have continuous reporting enabled, so this should be reliable now.
1536
*/
1537
{
1538
SDL_COMPILE_TIME_ASSERT(ENABLE_CONTINUOUS_REPORTING, ENABLE_CONTINUOUS_REPORTING);
1539
}
1540
if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) {
1541
// Bluetooth may have disconnected, try reopening the controller
1542
size = -1;
1543
}
1544
1545
if (joystick) {
1546
// These checks aren't needed on the Wii U Pro Controller
1547
if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_WiiUPro) {
1548
1549
// Check to see if the Motion Plus extension status has changed
1550
if (ctx->m_ulNextMotionPlusCheck && now >= ctx->m_ulNextMotionPlusCheck) {
1551
CheckMotionPlusConnection(ctx);
1552
if (NeedsPeriodicMotionPlusCheck(ctx, false)) {
1553
SchedulePeriodicMotionPlusCheck(ctx);
1554
} else {
1555
ctx->m_ulNextMotionPlusCheck = 0;
1556
}
1557
}
1558
1559
// Request a status update periodically to make sure our battery value is up to date
1560
if (!ctx->m_ulLastStatus || now >= (ctx->m_ulLastStatus + STATUS_UPDATE_TIME_MS)) {
1561
Uint8 data[2];
1562
1563
data[0] = k_eWiiOutputReportIDs_StatusRequest;
1564
data[1] = (Uint8)ctx->m_bRumbleActive;
1565
WriteOutput(ctx, data, sizeof(data), false);
1566
1567
ctx->m_ulLastStatus = now;
1568
}
1569
}
1570
}
1571
1572
if (size < 0 || ctx->m_bDisconnected) {
1573
// Read error, device is disconnected
1574
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1575
}
1576
return (size >= 0);
1577
}
1578
1579
static void HIDAPI_DriverWii_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1580
{
1581
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
1582
1583
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED,
1584
SDL_PlayerLEDHintChanged, ctx);
1585
1586
ctx->joystick = NULL;
1587
}
1588
1589
static void HIDAPI_DriverWii_FreeDevice(SDL_HIDAPI_Device *device)
1590
{
1591
}
1592
1593
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii = {
1594
SDL_HINT_JOYSTICK_HIDAPI_WII,
1595
true,
1596
HIDAPI_DriverWii_RegisterHints,
1597
HIDAPI_DriverWii_UnregisterHints,
1598
HIDAPI_DriverWii_IsEnabled,
1599
HIDAPI_DriverWii_IsSupportedDevice,
1600
HIDAPI_DriverWii_InitDevice,
1601
HIDAPI_DriverWii_GetDevicePlayerIndex,
1602
HIDAPI_DriverWii_SetDevicePlayerIndex,
1603
HIDAPI_DriverWii_UpdateDevice,
1604
HIDAPI_DriverWii_OpenJoystick,
1605
HIDAPI_DriverWii_RumbleJoystick,
1606
HIDAPI_DriverWii_RumbleJoystickTriggers,
1607
HIDAPI_DriverWii_GetJoystickCapabilities,
1608
HIDAPI_DriverWii_SetJoystickLED,
1609
HIDAPI_DriverWii_SendJoystickEffect,
1610
HIDAPI_DriverWii_SetJoystickSensorsEnabled,
1611
HIDAPI_DriverWii_CloseJoystick,
1612
HIDAPI_DriverWii_FreeDevice,
1613
};
1614
1615
#endif // SDL_JOYSTICK_HIDAPI_WII
1616
1617
#endif // SDL_JOYSTICK_HIDAPI
1618
1619