2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
21 #include "../../SDL_internal.h"
23 #ifdef SDL_JOYSTICK_HIDAPI
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"
37 #ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
39 #define MAX_CONTROLLERS 4
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;
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)
56 if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) {
57 /* Nintendo Co., Ltd. Wii U GameCube Controller Adapter */
64 HIDAPI_DriverGameCube_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
66 return "Nintendo GameCube Controller";
70 ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index)
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);
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;
80 static float fsel(float fComparand, float fValGE, float fLT)
82 return fComparand >= 0 ? fValGE : fLT;
85 static float RemapVal(float val, float A, float B, float C, float D)
88 return fsel(val - B , D , C);
96 return C + (D - C) * (val - A) / (B - A);
99 static void SDLCALL SDL_GameControllerButtonReportingHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
101 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata;
102 ctx->m_bUseButtonLabels = SDL_GetStringBoolean(hint, SDL_TRUE);
105 static Uint8 RemapButton(SDL_DriverGameCube_Context *ctx, Uint8 button)
107 if (!ctx->m_bUseButtonLabels) {
108 /* Use button positions */
110 case SDL_CONTROLLER_BUTTON_B:
111 return SDL_CONTROLLER_BUTTON_X;
112 case SDL_CONTROLLER_BUTTON_X:
113 return SDL_CONTROLLER_BUTTON_B;
122 HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
124 SDL_DriverGameCube_Context *ctx;
129 Uint8 initMagic = 0x13;
130 Uint8 rumbleMagic = 0x11;
132 ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
138 device->dev = hid_open_path(device->path, 0);
141 SDL_SetError("Couldn't open %s", device->path);
144 device->context = ctx;
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;
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");
158 /* Wait for the adapter to initialize */
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...? */
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;
172 /* Only allow rumble if the adapter's second USB cable is connected */
173 ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) != 0 && !ctx->wireless[i];
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]);
181 if (ctx->joysticks[i] != -1) {
182 HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
183 ctx->joysticks[i] = -1;
190 SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
191 SDL_GameControllerButtonReportingHintChanged, ctx);
197 hid_close(device->dev);
200 if (device->context) {
201 SDL_free(device->context);
202 device->context = NULL;
208 HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
210 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
213 for (i = 0; i < 4; ++i) {
214 if (instance_id == ctx->joysticks[i]) {
222 HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
227 HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device)
229 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
230 SDL_Joystick *joystick;
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...? */
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;
248 /* Only allow rumble if the adapter's second USB cable is connected */
249 ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) != 0 && !ctx->wireless[i];
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]);
256 joystick = SDL_JoystickFromInstanceID(ctx->joysticks[i]);
258 /* Hasn't been opened yet, skip */
259 if (joystick == NULL) {
263 if (ctx->joysticks[i] != -1) {
264 HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
265 ctx->joysticks[i] = -1;
270 #define READ_BUTTON(off, flag, button) \
271 SDL_PrivateJoystickButton( \
273 RemapButton(ctx, button), \
274 (curSlot[off] & flag) ? SDL_PRESSED : SDL_RELEASED \
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!
290 READ_BUTTON(2, 0x04, 10) /* TRIGGERRIGHT */
291 READ_BUTTON(2, 0x08, 11) /* TRIGGERLEFT */
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( \
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)
313 /* Write rumble packet */
314 if (ctx->rumbleUpdate) {
315 SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
316 ctx->rumbleUpdate = SDL_FALSE;
319 /* If we got here, nothing bad happened! */
324 HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
326 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
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;
336 return SDL_FALSE; /* Should never get here! */
340 HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
342 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
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");
349 if (!ctx->rumbleAllowed[i]) {
350 return SDL_SetError("Second USB cable for WUP-028 not connected");
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;
361 /* Should never get here! */
362 SDL_SetError("Couldn't find joystick");
367 HIDAPI_DriverGameCube_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
369 return SDL_Unsupported();
373 HIDAPI_DriverGameCube_HasJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
379 HIDAPI_DriverGameCube_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
381 return SDL_Unsupported();
385 HIDAPI_DriverGameCube_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
387 return SDL_Unsupported();
391 HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
393 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
395 /* Stop rumble activity */
396 if (ctx->rumbleUpdate) {
397 SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
398 ctx->rumbleUpdate = SDL_FALSE;
403 HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device)
405 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
407 hid_close(device->dev);
410 SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
411 SDL_GameControllerButtonReportingHintChanged, ctx);
413 SDL_free(device->context);
414 device->context = NULL;
417 SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube =
419 SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
421 HIDAPI_DriverGameCube_IsSupportedDevice,
422 HIDAPI_DriverGameCube_GetDeviceName,
423 HIDAPI_DriverGameCube_InitDevice,
424 HIDAPI_DriverGameCube_GetDevicePlayerIndex,
425 HIDAPI_DriverGameCube_SetDevicePlayerIndex,
426 HIDAPI_DriverGameCube_UpdateDevice,
427 HIDAPI_DriverGameCube_OpenJoystick,
428 HIDAPI_DriverGameCube_RumbleJoystick,
429 HIDAPI_DriverGameCube_RumbleJoystickTriggers,
430 HIDAPI_DriverGameCube_HasJoystickLED,
431 HIDAPI_DriverGameCube_SetJoystickLED,
432 HIDAPI_DriverGameCube_SetJoystickSensorsEnabled,
433 HIDAPI_DriverGameCube_CloseJoystick,
434 HIDAPI_DriverGameCube_FreeDevice,
437 #endif /* SDL_JOYSTICK_HIDAPI_GAMECUBE */
439 #endif /* SDL_JOYSTICK_HIDAPI */
441 /* vi: set ts=4 sw=4 expandtab: */