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#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
55typedef 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
73typedef 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
88typedef 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
96typedef 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
103typedef 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
121typedef 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
131typedef struct
132{
133 Uint16 min;
134 Uint16 max;
135 Uint16 center;
136 Uint16 deadzone;
137} StickCalibrationData;
138
139typedef 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
161static void HIDAPI_DriverWii_RegisterHints(SDL_HintCallback callback, void *userdata)
162{
163 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata);
164}
165
166static void HIDAPI_DriverWii_UnregisterHints(SDL_HintCallback callback, void *userdata)
167{
168 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata);
169}
170
171static 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
182static 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
192static 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
210static 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
228static 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
249static bool IsWriteMemoryResponse(const Uint8 *data)
250{
251 return data[3] == k_eWiiOutputReportIDs_WriteMemory;
252}
253
254static 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
284static 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
311static bool SendExtensionIdentify(SDL_DriverWii_Context *ctx, bool sync)
312{
313 return ReadRegister(ctx, 0xA400FE, 2, sync);
314}
315
316static 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
354static 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
370static 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
385static 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
430static 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
445static void SchedulePeriodicMotionPlusCheck(SDL_DriverWii_Context *ctx)
446{
447 ctx->m_ulNextMotionPlusCheck = SDL_GetTicks() + MOTION_PLUS_UPDATE_TIME_MS;
448}
449
450static void CheckMotionPlusConnection(SDL_DriverWii_Context *ctx)
451{
452 SendExtensionIdentify(ctx, false);
453
454 ctx->m_eCommState = k_eWiiCommunicationState_CheckMotionPlusStage1;
455}
456
457static 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
470static 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
483static 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
494static 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
509static 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
550static 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
571static 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
587static void ResetButtonPacketType(SDL_DriverWii_Context *ctx)
588{
589 RequestButtonPacketType(ctx, GetButtonPacketType(ctx));
590}
591
592static 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
625static void InitializeExtension(SDL_DriverWii_Context *ctx)
626{
627 SendExtensionReset(ctx, true);
628 InitStickCalibrationData(ctx);
629 ResetButtonPacketType(ctx);
630}
631
632static 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
665static 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
677static 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
712static 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
736static 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
755static int HIDAPI_DriverWii_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
756{
757 return -1;
758}
759
760static 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
773static 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
823static 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
840static bool HIDAPI_DriverWii_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
841{
842 return SDL_Unsupported();
843}
844
845static Uint32 HIDAPI_DriverWii_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
846{
847 return SDL_JOYSTICK_CAP_RUMBLE;
848}
849
850static bool HIDAPI_DriverWii_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
851{
852 return SDL_Unsupported();
853}
854
855static bool HIDAPI_DriverWii_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
856{
857 return SDL_Unsupported();
858}
859
860static 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
880static 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 */
922static 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
937static 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
970static 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
1003static 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
1026static 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
1056static 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
1093static 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
1122static 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
1155static 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
1206static 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
1246static 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
1266static 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
1317static void GetBaseButtons(WiiButtonData *dst, const Uint8 *src)
1318{
1319 SDL_memcpy(dst->rgucBaseButtons, src, 2);
1320 dst->hasBaseButtons = true;
1321}
1322
1323static void GetAccelerometer(WiiButtonData *dst, const Uint8 *src)
1324{
1325 SDL_memcpy(dst->rgucAccelerometer, src, 3);
1326 dst->hasAccelerometer = true;
1327}
1328
1329static 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
1350static 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
1387static 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
1439static 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
1494static 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
1512static 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
1579static 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
1589static void HIDAPI_DriverWii_FreeDevice(SDL_HIDAPI_Device *device)
1590{
1591}
1592
1593SDL_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