src/joystick/hidapi/SDL_hidapi_gamecube.c
author Ethan Lee <flibitijibibo@flibitijibibo.com>
Tue, 12 Mar 2019 20:27:54 -0400
changeset 12641 64597a7e8771
child 12704 704ec541957d
permissions -rw-r--r--
hidapi: Add support for Wii U/Switch USB GameCube controller adapter.

Note that a single USB device is responsible for all 4 joysticks, so a large
rewrite of the DeviceDriver functions was necessary to allow a single device to
produce multiple joysticks.
flibitijibibo@12641
     1
/*
flibitijibibo@12641
     2
  Simple DirectMedia Layer
flibitijibibo@12641
     3
  Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
flibitijibibo@12641
     4
flibitijibibo@12641
     5
  This software is provided 'as-is', without any express or implied
flibitijibibo@12641
     6
  warranty.  In no event will the authors be held liable for any damages
flibitijibibo@12641
     7
  arising from the use of this software.
flibitijibibo@12641
     8
flibitijibibo@12641
     9
  Permission is granted to anyone to use this software for any purpose,
flibitijibibo@12641
    10
  including commercial applications, and to alter it and redistribute it
flibitijibibo@12641
    11
  freely, subject to the following restrictions:
flibitijibibo@12641
    12
flibitijibibo@12641
    13
  1. The origin of this software must not be misrepresented; you must not
flibitijibibo@12641
    14
     claim that you wrote the original software. If you use this software
flibitijibibo@12641
    15
     in a product, an acknowledgment in the product documentation would be
flibitijibibo@12641
    16
     appreciated but is not required.
flibitijibibo@12641
    17
  2. Altered source versions must be plainly marked as such, and must not be
flibitijibibo@12641
    18
     misrepresented as being the original software.
flibitijibibo@12641
    19
  3. This notice may not be removed or altered from any source distribution.
flibitijibibo@12641
    20
*/
flibitijibibo@12641
    21
#include "../../SDL_internal.h"
flibitijibibo@12641
    22
flibitijibibo@12641
    23
#ifdef SDL_JOYSTICK_HIDAPI
flibitijibibo@12641
    24
flibitijibibo@12641
    25
#include "SDL_hints.h"
flibitijibibo@12641
    26
#include "SDL_log.h"
flibitijibibo@12641
    27
#include "SDL_events.h"
flibitijibibo@12641
    28
#include "SDL_timer.h"
flibitijibibo@12641
    29
#include "SDL_haptic.h"
flibitijibibo@12641
    30
#include "SDL_joystick.h"
flibitijibibo@12641
    31
#include "SDL_gamecontroller.h"
flibitijibibo@12641
    32
#include "../SDL_sysjoystick.h"
flibitijibibo@12641
    33
#include "SDL_hidapijoystick_c.h"
flibitijibibo@12641
    34
flibitijibibo@12641
    35
flibitijibibo@12641
    36
#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
flibitijibibo@12641
    37
flibitijibibo@12641
    38
typedef struct {
flibitijibibo@12641
    39
    SDL_JoystickID joysticks[4];
flibitijibibo@12641
    40
    Uint8 rumble[5];
flibitijibibo@12641
    41
    Uint32 rumbleExpiration[4];
flibitijibibo@12641
    42
    /* Without this variable, hid_write starts to lag a TON */
flibitijibibo@12641
    43
    Uint8 rumbleUpdate;
flibitijibibo@12641
    44
} SDL_DriverGameCube_Context;
flibitijibibo@12641
    45
flibitijibibo@12641
    46
static SDL_bool
flibitijibibo@12641
    47
HIDAPI_DriverGameCube_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number)
flibitijibibo@12641
    48
{
flibitijibibo@12641
    49
    return SDL_IsJoystickGameCube(vendor_id, product_id);
flibitijibibo@12641
    50
}
flibitijibibo@12641
    51
flibitijibibo@12641
    52
static const char *
flibitijibibo@12641
    53
HIDAPI_DriverGameCube_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
flibitijibibo@12641
    54
{
flibitijibibo@12641
    55
    /* Give a user friendly name for this controller */
flibitijibibo@12641
    56
    if (SDL_IsJoystickGameCube(vendor_id, product_id)) {
flibitijibibo@12641
    57
        return "Nintendo GameCube Controller";
flibitijibibo@12641
    58
    }
flibitijibibo@12641
    59
    return NULL;
flibitijibibo@12641
    60
}
flibitijibibo@12641
    61
flibitijibibo@12641
    62
static SDL_bool
flibitijibibo@12641
    63
HIDAPI_DriverGameCube_InitDriver(SDL_HIDAPI_DriverData *context, Uint16 vendor_id, Uint16 product_id, int *num_joysticks)
flibitijibibo@12641
    64
{
flibitijibibo@12641
    65
    SDL_DriverGameCube_Context *ctx;
flibitijibibo@12641
    66
    Uint8 packet[37];
flibitijibibo@12641
    67
    Uint8 *curSlot;
flibitijibibo@12641
    68
    Uint8 i;
flibitijibibo@12641
    69
    int size;
flibitijibibo@12641
    70
    Uint8 initMagic = 0x13;
flibitijibibo@12641
    71
    Uint8 rumbleMagic = 0x11;
flibitijibibo@12641
    72
flibitijibibo@12641
    73
    ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
flibitijibibo@12641
    74
    if (!ctx) {
flibitijibibo@12641
    75
        SDL_OutOfMemory();
flibitijibibo@12641
    76
        return SDL_FALSE;
flibitijibibo@12641
    77
    }
flibitijibibo@12641
    78
    ctx->joysticks[0] = -1;
flibitijibibo@12641
    79
    ctx->joysticks[1] = -1;
flibitijibibo@12641
    80
    ctx->joysticks[2] = -1;
flibitijibibo@12641
    81
    ctx->joysticks[3] = -1;
flibitijibibo@12641
    82
    ctx->rumble[0] = rumbleMagic;
flibitijibibo@12641
    83
flibitijibibo@12641
    84
    context->context = ctx;
flibitijibibo@12641
    85
flibitijibibo@12641
    86
    /* This is all that's needed to initialize the device. Really! */
flibitijibibo@12641
    87
    if (hid_write(context->device, &initMagic, sizeof(initMagic)) <= 0) {
flibitijibibo@12641
    88
        SDL_SetError("Couldn't initialize WUP-028");
flibitijibibo@12641
    89
        SDL_free(ctx);
flibitijibibo@12641
    90
        return SDL_FALSE;
flibitijibibo@12641
    91
    }
flibitijibibo@12641
    92
flibitijibibo@12641
    93
    /* Add all the applicable joysticks */
flibitijibibo@12641
    94
    while ((size = hid_read_timeout(context->device, packet, sizeof(packet), 0)) > 0) {
flibitijibibo@12641
    95
        if (size < 37 || packet[0] != 0x21) {
flibitijibibo@12641
    96
            continue; /* Nothing to do yet...? */
flibitijibibo@12641
    97
        }
flibitijibibo@12641
    98
flibitijibibo@12641
    99
        /* Go through all 4 slots */
flibitijibibo@12641
   100
        curSlot = packet + 1;
flibitijibibo@12641
   101
        for (i = 0; i < 4; i += 1, curSlot += 9) {
flibitijibibo@12641
   102
            if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
flibitijibibo@12641
   103
                if (ctx->joysticks[i] == -1) {
flibitijibibo@12641
   104
                    ctx->joysticks[i] = SDL_GetNextJoystickInstanceID();
flibitijibibo@12641
   105
flibitijibibo@12641
   106
                    *num_joysticks += 1;
flibitijibibo@12641
   107
flibitijibibo@12641
   108
                    SDL_PrivateJoystickAdded(ctx->joysticks[i]);
flibitijibibo@12641
   109
                }
flibitijibibo@12641
   110
            } else {
flibitijibibo@12641
   111
                if (ctx->joysticks[i] != -1) {
flibitijibibo@12641
   112
                    SDL_PrivateJoystickRemoved(ctx->joysticks[i]);
flibitijibibo@12641
   113
flibitijibibo@12641
   114
                    *num_joysticks -= 1;
flibitijibibo@12641
   115
flibitijibibo@12641
   116
                    ctx->joysticks[i] = -1;
flibitijibibo@12641
   117
                }
flibitijibibo@12641
   118
                continue;
flibitijibibo@12641
   119
            }
flibitijibibo@12641
   120
        }
flibitijibibo@12641
   121
    }
flibitijibibo@12641
   122
flibitijibibo@12641
   123
    return SDL_TRUE;
flibitijibibo@12641
   124
}
flibitijibibo@12641
   125
flibitijibibo@12641
   126
static void
flibitijibibo@12641
   127
HIDAPI_DriverGameCube_QuitDriver(SDL_HIDAPI_DriverData *context, SDL_bool send_event, int *num_joysticks)
flibitijibibo@12641
   128
{
flibitijibibo@12641
   129
    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
flibitijibibo@12641
   130
    Uint8 i;
flibitijibibo@12641
   131
flibitijibibo@12641
   132
    /* Stop all rumble activity */
flibitijibibo@12641
   133
    for (i = 1; i < 5; i += 1) {
flibitijibibo@12641
   134
        ctx->rumble[i] = 0;
flibitijibibo@12641
   135
    }
flibitijibibo@12641
   136
    hid_write(context->device, ctx->rumble, sizeof(ctx->rumble));
flibitijibibo@12641
   137
flibitijibibo@12641
   138
    /* Remove all joysticks! */
flibitijibibo@12641
   139
    for (i = 0; i < 4; i += 1) {
flibitijibibo@12641
   140
        if (ctx->joysticks[i] != -1) {
flibitijibibo@12641
   141
            *num_joysticks -= 1;
flibitijibibo@12641
   142
            if (send_event) {
flibitijibibo@12641
   143
                SDL_PrivateJoystickRemoved(ctx->joysticks[i]);
flibitijibibo@12641
   144
            }
flibitijibibo@12641
   145
        }
flibitijibibo@12641
   146
    }
flibitijibibo@12641
   147
flibitijibibo@12641
   148
    SDL_free(context->context);
flibitijibibo@12641
   149
}
flibitijibibo@12641
   150
flibitijibibo@12641
   151
static SDL_bool
flibitijibibo@12641
   152
HIDAPI_DriverGameCube_UpdateDriver(SDL_HIDAPI_DriverData *context, int *num_joysticks)
flibitijibibo@12641
   153
{
flibitijibibo@12641
   154
    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
flibitijibibo@12641
   155
    SDL_Joystick *joystick;
flibitijibibo@12641
   156
    Uint8 packet[37];
flibitijibibo@12641
   157
    Uint8 *curSlot;
flibitijibibo@12641
   158
    Uint32 now;
flibitijibibo@12641
   159
    Uint8 i;
flibitijibibo@12641
   160
    int size;
flibitijibibo@12641
   161
flibitijibibo@12641
   162
    /* Read input packet */
flibitijibibo@12641
   163
    while ((size = hid_read_timeout(context->device, packet, sizeof(packet), 0)) > 0) {
flibitijibibo@12641
   164
        if (size < 37 || packet[0] != 0x21) {
flibitijibibo@12641
   165
            continue; /* Nothing to do right now...? */
flibitijibibo@12641
   166
        }
flibitijibibo@12641
   167
flibitijibibo@12641
   168
        /* Go through all 4 slots */
flibitijibibo@12641
   169
        curSlot = packet + 1;
flibitijibibo@12641
   170
        for (i = 0; i < 4; i += 1, curSlot += 9) {
flibitijibibo@12641
   171
            if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
flibitijibibo@12641
   172
                if (ctx->joysticks[i] == -1) {
flibitijibibo@12641
   173
                    ctx->joysticks[i] = SDL_GetNextJoystickInstanceID();
flibitijibibo@12641
   174
flibitijibibo@12641
   175
                    *num_joysticks += 1;
flibitijibibo@12641
   176
flibitijibibo@12641
   177
                    SDL_PrivateJoystickAdded(ctx->joysticks[i]);
flibitijibibo@12641
   178
                }
flibitijibibo@12641
   179
                joystick = SDL_JoystickFromInstanceID(ctx->joysticks[i]);
flibitijibibo@12641
   180
flibitijibibo@12641
   181
                /* Hasn't been opened yet, skip */
flibitijibibo@12641
   182
                if (joystick == NULL) {
flibitijibibo@12641
   183
                    continue;
flibitijibibo@12641
   184
                }
flibitijibibo@12641
   185
            } else {
flibitijibibo@12641
   186
                if (ctx->joysticks[i] != -1) {
flibitijibibo@12641
   187
                    SDL_PrivateJoystickRemoved(ctx->joysticks[i]);
flibitijibibo@12641
   188
flibitijibibo@12641
   189
                    *num_joysticks -= 1;
flibitijibibo@12641
   190
flibitijibibo@12641
   191
                    ctx->joysticks[i] = -1;
flibitijibibo@12641
   192
                }
flibitijibibo@12641
   193
                continue;
flibitijibibo@12641
   194
            }
flibitijibibo@12641
   195
flibitijibibo@12641
   196
            #define READ_BUTTON(off, flag, button) \
flibitijibibo@12641
   197
                SDL_PrivateJoystickButton( \
flibitijibibo@12641
   198
                    joystick, \
flibitijibibo@12641
   199
                    button, \
flibitijibibo@12641
   200
                    (curSlot[off] & flag) ? SDL_PRESSED : SDL_RELEASED \
flibitijibibo@12641
   201
                );
flibitijibibo@12641
   202
            READ_BUTTON(1, 0x01, 0) /* A */
flibitijibibo@12641
   203
            READ_BUTTON(1, 0x02, 1) /* B */
flibitijibibo@12641
   204
            READ_BUTTON(1, 0x04, 2) /* X */
flibitijibibo@12641
   205
            READ_BUTTON(1, 0x08, 3) /* Y */
flibitijibibo@12641
   206
            READ_BUTTON(1, 0x10, 4) /* DPAD_LEFT */
flibitijibibo@12641
   207
            READ_BUTTON(1, 0x20, 5) /* DPAD_RIGHT */
flibitijibibo@12641
   208
            READ_BUTTON(1, 0x40, 6) /* DPAD_DOWN */
flibitijibibo@12641
   209
            READ_BUTTON(1, 0x80, 7) /* DPAD_UP */
flibitijibibo@12641
   210
            READ_BUTTON(2, 0x01, 8) /* START */
flibitijibibo@12641
   211
            READ_BUTTON(2, 0x02, 9) /* RIGHTSHOULDER */
flibitijibibo@12641
   212
            /* [2] 0x04 - R, [2] 0x08 - L */
flibitijibibo@12641
   213
            #undef READ_BUTTON
flibitijibibo@12641
   214
flibitijibibo@12641
   215
            /* Axis math taken from SDL_xinputjoystick.c */
flibitijibibo@12641
   216
            #define READ_AXIS(off, axis) \
flibitijibibo@12641
   217
                SDL_PrivateJoystickAxis( \
flibitijibibo@12641
   218
                    joystick, \
flibitijibibo@12641
   219
                    axis, \
flibitijibibo@12641
   220
                    (Sint16)(((int)curSlot[off] * 257) - 32768) \
flibitijibibo@12641
   221
                );
flibitijibibo@12641
   222
            READ_AXIS(3, 0) /* LEFTX */
flibitijibibo@12641
   223
            READ_AXIS(4, 1) /* LEFTY */
flibitijibibo@12641
   224
            READ_AXIS(5, 2) /* RIGHTX */
flibitijibibo@12641
   225
            READ_AXIS(6, 3) /* RIGHTY */
flibitijibibo@12641
   226
            READ_AXIS(7, 4) /* TRIGGERLEFT */
flibitijibibo@12641
   227
            READ_AXIS(8, 5) /* TRIGGERRIGHT */
flibitijibibo@12641
   228
            #undef READ_AXIS
flibitijibibo@12641
   229
        }
flibitijibibo@12641
   230
    }
flibitijibibo@12641
   231
flibitijibibo@12641
   232
    /* Write rumble packet */
flibitijibibo@12641
   233
    now = SDL_GetTicks();
flibitijibibo@12641
   234
    for (i = 0; i < 4; i += 1) {
flibitijibibo@12641
   235
        if (ctx->rumbleExpiration[i] > 0) {
flibitijibibo@12641
   236
            if (SDL_TICKS_PASSED(now, ctx->rumbleExpiration[i])) {
flibitijibibo@12641
   237
                ctx->rumble[i + 1] = 0;
flibitijibibo@12641
   238
                ctx->rumbleExpiration[i] = 0;
flibitijibibo@12641
   239
                ctx->rumbleUpdate = 1;
flibitijibibo@12641
   240
            }
flibitijibibo@12641
   241
        }
flibitijibibo@12641
   242
    }
flibitijibibo@12641
   243
    if (ctx->rumbleUpdate) {
flibitijibibo@12641
   244
        hid_write(context->device, ctx->rumble, sizeof(ctx->rumble));
flibitijibibo@12641
   245
        ctx->rumbleUpdate = 0;
flibitijibibo@12641
   246
    }
flibitijibibo@12641
   247
flibitijibibo@12641
   248
    /* If we got here, nothing bad happened! */
flibitijibibo@12641
   249
    return SDL_TRUE;
flibitijibibo@12641
   250
}
flibitijibibo@12641
   251
flibitijibibo@12641
   252
static int
flibitijibibo@12641
   253
HIDAPI_DriverGameCube_NumJoysticks(SDL_HIDAPI_DriverData *context)
flibitijibibo@12641
   254
{
flibitijibibo@12641
   255
    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
flibitijibibo@12641
   256
    int i, joysticks = 0;
flibitijibibo@12641
   257
    for (i = 0; i < 4; i += 1) {
flibitijibibo@12641
   258
        if (ctx->joysticks[i] != -1) {
flibitijibibo@12641
   259
            joysticks += 1;
flibitijibibo@12641
   260
        }
flibitijibibo@12641
   261
    }
flibitijibibo@12641
   262
    return joysticks;
flibitijibibo@12641
   263
}
flibitijibibo@12641
   264
flibitijibibo@12641
   265
static SDL_JoystickID
flibitijibibo@12641
   266
HIDAPI_DriverGameCube_InstanceIDForIndex(SDL_HIDAPI_DriverData *context, int index)
flibitijibibo@12641
   267
{
flibitijibibo@12641
   268
    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
flibitijibibo@12641
   269
    Uint8 i;
flibitijibibo@12641
   270
    for (i = 0; i < 4; i += 1) {
flibitijibibo@12641
   271
        if (ctx->joysticks[i] != -1) {
flibitijibibo@12641
   272
            if (index == 0) {
flibitijibibo@12641
   273
                return ctx->joysticks[i];
flibitijibibo@12641
   274
            }
flibitijibibo@12641
   275
            index -= 1;
flibitijibibo@12641
   276
        }
flibitijibibo@12641
   277
    }
flibitijibibo@12641
   278
    return -1; /* Should never get here! */
flibitijibibo@12641
   279
}
flibitijibibo@12641
   280
flibitijibibo@12641
   281
static SDL_bool
flibitijibibo@12641
   282
HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick)
flibitijibibo@12641
   283
{
flibitijibibo@12641
   284
    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
flibitijibibo@12641
   285
    SDL_JoystickID instance = SDL_JoystickInstanceID(joystick);
flibitijibibo@12641
   286
    Uint8 i;
flibitijibibo@12641
   287
    for (i = 0; i < 4; i += 1) {
flibitijibibo@12641
   288
        if (instance == ctx->joysticks[i]) {
flibitijibibo@12641
   289
            joystick->nbuttons = 10;
flibitijibibo@12641
   290
            joystick->naxes = 6;
flibitijibibo@12641
   291
            joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
flibitijibibo@12641
   292
            return SDL_TRUE;
flibitijibibo@12641
   293
        }
flibitijibibo@12641
   294
    }
flibitijibibo@12641
   295
    return SDL_FALSE; /* Should never get here! */
flibitijibibo@12641
   296
}
flibitijibibo@12641
   297
flibitijibibo@12641
   298
static int
flibitijibibo@12641
   299
HIDAPI_DriverGameCube_Rumble(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
flibitijibibo@12641
   300
{
flibitijibibo@12641
   301
    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
flibitijibibo@12641
   302
    SDL_JoystickID instance = SDL_JoystickInstanceID(joystick);
flibitijibibo@12641
   303
    Uint8 i, val;
flibitijibibo@12641
   304
    for (i = 0; i < 4; i += 1) {
flibitijibibo@12641
   305
        if (instance == ctx->joysticks[i]) {
flibitijibibo@12641
   306
            val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
flibitijibibo@12641
   307
            if (val != ctx->rumble[i + 1]) {
flibitijibibo@12641
   308
                ctx->rumble[i + 1] = val;
flibitijibibo@12641
   309
                ctx->rumbleUpdate = 1;
flibitijibibo@12641
   310
            }
flibitijibibo@12641
   311
            if (val && duration_ms < SDL_HAPTIC_INFINITY) {
flibitijibibo@12641
   312
                ctx->rumbleExpiration[i] = SDL_GetTicks() + duration_ms;
flibitijibibo@12641
   313
            } else {
flibitijibibo@12641
   314
                ctx->rumbleExpiration[i] = 0;
flibitijibibo@12641
   315
            }
flibitijibibo@12641
   316
            return 0;
flibitijibibo@12641
   317
        }
flibitijibibo@12641
   318
    }
flibitijibibo@12641
   319
    return -1;
flibitijibibo@12641
   320
}
flibitijibibo@12641
   321
flibitijibibo@12641
   322
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube =
flibitijibibo@12641
   323
{
flibitijibibo@12641
   324
    SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
flibitijibibo@12641
   325
    SDL_TRUE,
flibitijibibo@12641
   326
    HIDAPI_DriverGameCube_IsSupportedDevice,
flibitijibibo@12641
   327
    HIDAPI_DriverGameCube_GetDeviceName,
flibitijibibo@12641
   328
    HIDAPI_DriverGameCube_InitDriver,
flibitijibibo@12641
   329
    HIDAPI_DriverGameCube_QuitDriver,
flibitijibibo@12641
   330
    HIDAPI_DriverGameCube_UpdateDriver,
flibitijibibo@12641
   331
    HIDAPI_DriverGameCube_NumJoysticks,
flibitijibibo@12641
   332
    HIDAPI_DriverGameCube_InstanceIDForIndex,
flibitijibibo@12641
   333
    HIDAPI_DriverGameCube_OpenJoystick,
flibitijibibo@12641
   334
    HIDAPI_DriverGameCube_Rumble
flibitijibibo@12641
   335
};
flibitijibibo@12641
   336
flibitijibibo@12641
   337
#endif /* SDL_JOYSTICK_HIDAPI_GAMECUBE */
flibitijibibo@12641
   338
flibitijibibo@12641
   339
#endif /* SDL_JOYSTICK_HIDAPI */