1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 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.h"
26 #include "SDL_events.h"
27 #include "SDL_timer.h"
28 #include "SDL_haptic.h"
29 #include "SDL_joystick.h"
30 #include "SDL_gamecontroller.h"
31 #include "../../SDL_hints_c.h"
32 #include "../SDL_sysjoystick.h"
33 #include "SDL_hidapijoystick_c.h"
34 #include "SDL_hidapi_rumble.h"
35
36
37 #ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
38
39 #define MAX_CONTROLLERS 4
40
41 typedef struct {
42 SDL_JoystickID joysticks[MAX_CONTROLLERS];
43 Uint8 wireless[MAX_CONTROLLERS];
44 Uint8 min_axis[MAX_CONTROLLERS*SDL_CONTROLLER_AXIS_MAX];
45 Uint8 max_axis[MAX_CONTROLLERS*SDL_CONTROLLER_AXIS_MAX];
46 Uint8 rumbleAllowed[MAX_CONTROLLERS];
47 Uint8 rumble[1+MAX_CONTROLLERS];
48 /* Without this variable, hid_write starts to lag a TON */
49 SDL_bool rumbleUpdate;
50 SDL_bool m_bUseButtonLabels;
51 } SDL_DriverGameCube_Context;
52
53 static SDL_bool
HIDAPI_DriverGameCube_IsSupportedDevice(const char * name,SDL_GameControllerType type,Uint16 vendor_id,Uint16 product_id,Uint16 version,int interface_number,int interface_class,int interface_subclass,int interface_protocol)54 HIDAPI_DriverGameCube_IsSupportedDevice(const char *name, SDL_GameControllerType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
55 {
56 if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) {
57 /* Nintendo Co., Ltd. Wii U GameCube Controller Adapter */
58 return SDL_TRUE;
59 }
60 return SDL_FALSE;
61 }
62
63 static const char *
HIDAPI_DriverGameCube_GetDeviceName(Uint16 vendor_id,Uint16 product_id)64 HIDAPI_DriverGameCube_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
65 {
66 return "Nintendo GameCube Controller";
67 }
68
69 static void
ResetAxisRange(SDL_DriverGameCube_Context * ctx,int joystick_index)70 ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index)
71 {
72 SDL_memset(&ctx->min_axis[joystick_index*SDL_CONTROLLER_AXIS_MAX], 128-88, SDL_CONTROLLER_AXIS_MAX);
73 SDL_memset(&ctx->max_axis[joystick_index*SDL_CONTROLLER_AXIS_MAX], 128+88, SDL_CONTROLLER_AXIS_MAX);
74
75 /* Trigger axes may have a higher resting value */
76 ctx->min_axis[joystick_index*SDL_CONTROLLER_AXIS_MAX+SDL_CONTROLLER_AXIS_TRIGGERLEFT] = 40;
77 ctx->min_axis[joystick_index*SDL_CONTROLLER_AXIS_MAX+SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = 40;
78 }
79
fsel(float fComparand,float fValGE,float fLT)80 static float fsel(float fComparand, float fValGE, float fLT)
81 {
82 return fComparand >= 0 ? fValGE : fLT;
83 }
84
RemapVal(float val,float A,float B,float C,float D)85 static float RemapVal(float val, float A, float B, float C, float D)
86 {
87 if (A == B) {
88 return fsel(val - B , D , C);
89 }
90 if (val < A) {
91 val = A;
92 }
93 if (val > B) {
94 val = B;
95 }
96 return C + (D - C) * (val - A) / (B - A);
97 }
98
SDL_GameControllerButtonReportingHintChanged(void * userdata,const char * name,const char * oldValue,const char * hint)99 static void SDLCALL SDL_GameControllerButtonReportingHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
100 {
101 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata;
102 ctx->m_bUseButtonLabels = SDL_GetStringBoolean(hint, SDL_TRUE);
103 }
104
RemapButton(SDL_DriverGameCube_Context * ctx,Uint8 button)105 static Uint8 RemapButton(SDL_DriverGameCube_Context *ctx, Uint8 button)
106 {
107 if (!ctx->m_bUseButtonLabels) {
108 /* Use button positions */
109 switch (button) {
110 case SDL_CONTROLLER_BUTTON_B:
111 return SDL_CONTROLLER_BUTTON_X;
112 case SDL_CONTROLLER_BUTTON_X:
113 return SDL_CONTROLLER_BUTTON_B;
114 default:
115 break;
116 }
117 }
118 return button;
119 }
120
121 static SDL_bool
HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device * device)122 HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
123 {
124 SDL_DriverGameCube_Context *ctx;
125 Uint8 packet[37];
126 Uint8 *curSlot;
127 Uint8 i;
128 int size;
129 Uint8 initMagic = 0x13;
130 Uint8 rumbleMagic = 0x11;
131
132 ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
133 if (!ctx) {
134 SDL_OutOfMemory();
135 return SDL_FALSE;
136 }
137
138 device->dev = hid_open_path(device->path, 0);
139 if (!device->dev) {
140 SDL_free(ctx);
141 SDL_SetError("Couldn't open %s", device->path);
142 return SDL_FALSE;
143 }
144 device->context = ctx;
145
146 ctx->joysticks[0] = -1;
147 ctx->joysticks[1] = -1;
148 ctx->joysticks[2] = -1;
149 ctx->joysticks[3] = -1;
150 ctx->rumble[0] = rumbleMagic;
151
152 /* This is all that's needed to initialize the device. Really! */
153 if (hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) {
154 SDL_SetError("Couldn't initialize WUP-028");
155 goto error;
156 }
157
158 /* Wait for the adapter to initialize */
159 SDL_Delay(10);
160
161 /* Add all the applicable joysticks */
162 while ((size = hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
163 if (size < 37 || packet[0] != 0x21) {
164 continue; /* Nothing to do yet...? */
165 }
166
167 /* Go through all 4 slots */
168 curSlot = packet + 1;
169 for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
170 ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
171
172 /* Only allow rumble if the adapter's second USB cable is connected */
173 ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) != 0 && !ctx->wireless[i];
174
175 if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
176 if (ctx->joysticks[i] == -1) {
177 ResetAxisRange(ctx, i);
178 HIDAPI_JoystickConnected(device, &ctx->joysticks[i], SDL_FALSE);
179 }
180 } else {
181 if (ctx->joysticks[i] != -1) {
182 HIDAPI_JoystickDisconnected(device, ctx->joysticks[i], SDL_FALSE);
183 ctx->joysticks[i] = -1;
184 }
185 continue;
186 }
187 }
188 }
189
190 SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
191 SDL_GameControllerButtonReportingHintChanged, ctx);
192
193 return SDL_TRUE;
194
195 error:
196 if (device->dev) {
197 hid_close(device->dev);
198 device->dev = NULL;
199 }
200 if (device->context) {
201 SDL_free(device->context);
202 device->context = NULL;
203 }
204 return SDL_FALSE;
205 }
206
207 static int
HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device * device,SDL_JoystickID instance_id)208 HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
209 {
210 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
211 Uint8 i;
212
213 for (i = 0; i < 4; ++i) {
214 if (instance_id == ctx->joysticks[i]) {
215 return i;
216 }
217 }
218 return -1;
219 }
220
221 static void
HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device * device,SDL_JoystickID instance_id,int player_index)222 HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
223 {
224 }
225
226 static SDL_bool
HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device * device)227 HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device)
228 {
229 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
230 SDL_Joystick *joystick;
231 Uint8 packet[37];
232 Uint8 *curSlot;
233 Uint8 i;
234 Sint16 axis_value;
235 int size;
236
237 /* Read input packet */
238 while ((size = hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
239 if (size < 37 || packet[0] != 0x21) {
240 continue; /* Nothing to do right now...? */
241 }
242
243 /* Go through all 4 slots */
244 curSlot = packet + 1;
245 for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
246 ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
247
248 /* Only allow rumble if the adapter's second USB cable is connected */
249 ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) != 0 && !ctx->wireless[i];
250
251 if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
252 if (ctx->joysticks[i] == -1) {
253 ResetAxisRange(ctx, i);
254 HIDAPI_JoystickConnected(device, &ctx->joysticks[i], SDL_FALSE);
255 }
256 joystick = SDL_JoystickFromInstanceID(ctx->joysticks[i]);
257
258 /* Hasn't been opened yet, skip */
259 if (joystick == NULL) {
260 continue;
261 }
262 } else {
263 if (ctx->joysticks[i] != -1) {
264 HIDAPI_JoystickDisconnected(device, ctx->joysticks[i], SDL_FALSE);
265 ctx->joysticks[i] = -1;
266 }
267 continue;
268 }
269
270 #define READ_BUTTON(off, flag, button) \
271 SDL_PrivateJoystickButton( \
272 joystick, \
273 RemapButton(ctx, button), \
274 (curSlot[off] & flag) ? SDL_PRESSED : SDL_RELEASED \
275 );
276 READ_BUTTON(1, 0x01, 0) /* A */
277 READ_BUTTON(1, 0x04, 1) /* B */
278 READ_BUTTON(1, 0x02, 2) /* X */
279 READ_BUTTON(1, 0x08, 3) /* Y */
280 READ_BUTTON(1, 0x10, 4) /* DPAD_LEFT */
281 READ_BUTTON(1, 0x20, 5) /* DPAD_RIGHT */
282 READ_BUTTON(1, 0x40, 6) /* DPAD_DOWN */
283 READ_BUTTON(1, 0x80, 7) /* DPAD_UP */
284 READ_BUTTON(2, 0x01, 8) /* START */
285 READ_BUTTON(2, 0x02, 9) /* RIGHTSHOULDER */
286 /* These two buttons are for the bottoms of the analog triggers.
287 * More than likely, you're going to want to read the axes instead!
288 * -flibit
289 */
290 READ_BUTTON(2, 0x04, 10) /* TRIGGERRIGHT */
291 READ_BUTTON(2, 0x08, 11) /* TRIGGERLEFT */
292 #undef READ_BUTTON
293
294 #define READ_AXIS(off, axis) \
295 if (axis < SDL_CONTROLLER_AXIS_TRIGGERLEFT) \
296 if (curSlot[off] < ctx->min_axis[i*SDL_CONTROLLER_AXIS_MAX+axis]) ctx->min_axis[i*SDL_CONTROLLER_AXIS_MAX+axis] = curSlot[off]; \
297 if (curSlot[off] > ctx->max_axis[i*SDL_CONTROLLER_AXIS_MAX+axis]) ctx->max_axis[i*SDL_CONTROLLER_AXIS_MAX+axis] = curSlot[off]; \
298 axis_value = (Sint16)(RemapVal(curSlot[off], ctx->min_axis[i*SDL_CONTROLLER_AXIS_MAX+axis], ctx->max_axis[i*SDL_CONTROLLER_AXIS_MAX+axis], SDL_MIN_SINT16, SDL_MAX_SINT16)); \
299 SDL_PrivateJoystickAxis( \
300 joystick, \
301 axis, axis_value \
302 );
303 READ_AXIS(3, SDL_CONTROLLER_AXIS_LEFTX)
304 READ_AXIS(4, SDL_CONTROLLER_AXIS_LEFTY)
305 READ_AXIS(5, SDL_CONTROLLER_AXIS_RIGHTX)
306 READ_AXIS(6, SDL_CONTROLLER_AXIS_RIGHTY)
307 READ_AXIS(7, SDL_CONTROLLER_AXIS_TRIGGERLEFT)
308 READ_AXIS(8, SDL_CONTROLLER_AXIS_TRIGGERRIGHT)
309 #undef READ_AXIS
310 }
311 }
312
313 /* Write rumble packet */
314 if (ctx->rumbleUpdate) {
315 SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
316 ctx->rumbleUpdate = SDL_FALSE;
317 }
318
319 /* If we got here, nothing bad happened! */
320 return SDL_TRUE;
321 }
322
323 static SDL_bool
HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device * device,SDL_Joystick * joystick)324 HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
325 {
326 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
327 Uint8 i;
328 for (i = 0; i < MAX_CONTROLLERS; i += 1) {
329 if (joystick->instance_id == ctx->joysticks[i]) {
330 joystick->nbuttons = 12;
331 joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
332 joystick->epowerlevel = ctx->wireless[i] ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED;
333 return SDL_TRUE;
334 }
335 }
336 return SDL_FALSE; /* Should never get here! */
337 }
338
339 static int
HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device * device,SDL_Joystick * joystick,Uint16 low_frequency_rumble,Uint16 high_frequency_rumble)340 HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
341 {
342 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
343 Uint8 i, val;
344 for (i = 0; i < MAX_CONTROLLERS; i += 1) {
345 if (joystick->instance_id == ctx->joysticks[i]) {
346 if (ctx->wireless[i]) {
347 return SDL_SetError("Ninteno GameCube WaveBird controllers do not support rumble");
348 }
349 if (!ctx->rumbleAllowed[i]) {
350 return SDL_SetError("Second USB cable for WUP-028 not connected");
351 }
352 val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
353 if (val != ctx->rumble[i + 1]) {
354 ctx->rumble[i + 1] = val;
355 ctx->rumbleUpdate = SDL_TRUE;
356 }
357 return 0;
358 }
359 }
360
361 /* Should never get here! */
362 SDL_SetError("Couldn't find joystick");
363 return -1;
364 }
365
366 static void
HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device * device,SDL_Joystick * joystick)367 HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
368 {
369 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
370
371 /* Stop rumble activity */
372 if (ctx->rumbleUpdate) {
373 SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
374 ctx->rumbleUpdate = SDL_FALSE;
375 }
376 }
377
378 static void
HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device * device)379 HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device)
380 {
381 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
382
383 hid_close(device->dev);
384 device->dev = NULL;
385
386 SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
387 SDL_GameControllerButtonReportingHintChanged, ctx);
388
389 SDL_free(device->context);
390 device->context = NULL;
391 }
392
393 SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube =
394 {
395 SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
396 SDL_TRUE,
397 HIDAPI_DriverGameCube_IsSupportedDevice,
398 HIDAPI_DriverGameCube_GetDeviceName,
399 HIDAPI_DriverGameCube_InitDevice,
400 HIDAPI_DriverGameCube_GetDevicePlayerIndex,
401 HIDAPI_DriverGameCube_SetDevicePlayerIndex,
402 HIDAPI_DriverGameCube_UpdateDevice,
403 HIDAPI_DriverGameCube_OpenJoystick,
404 HIDAPI_DriverGameCube_RumbleJoystick,
405 HIDAPI_DriverGameCube_CloseJoystick,
406 HIDAPI_DriverGameCube_FreeDevice,
407 NULL,
408 };
409
410 #endif /* SDL_JOYSTICK_HIDAPI_GAMECUBE */
411
412 #endif /* SDL_JOYSTICK_HIDAPI */
413
414 /* vi: set ts=4 sw=4 expandtab: */
415