mirror of
https://github.com/libsdl-org/SDL.git
synced 2025-10-06 18:06:26 +00:00
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.
This commit is contained in:
339
src/joystick/hidapi/SDL_hidapi_gamecube.c
Normal file
339
src/joystick/hidapi/SDL_hidapi_gamecube.c
Normal file
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "../../SDL_internal.h"
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI
|
||||
|
||||
#include "SDL_hints.h"
|
||||
#include "SDL_log.h"
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_timer.h"
|
||||
#include "SDL_haptic.h"
|
||||
#include "SDL_joystick.h"
|
||||
#include "SDL_gamecontroller.h"
|
||||
#include "../SDL_sysjoystick.h"
|
||||
#include "SDL_hidapijoystick_c.h"
|
||||
|
||||
|
||||
#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
|
||||
|
||||
typedef struct {
|
||||
SDL_JoystickID joysticks[4];
|
||||
Uint8 rumble[5];
|
||||
Uint32 rumbleExpiration[4];
|
||||
/* Without this variable, hid_write starts to lag a TON */
|
||||
Uint8 rumbleUpdate;
|
||||
} SDL_DriverGameCube_Context;
|
||||
|
||||
static SDL_bool
|
||||
HIDAPI_DriverGameCube_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number)
|
||||
{
|
||||
return SDL_IsJoystickGameCube(vendor_id, product_id);
|
||||
}
|
||||
|
||||
static const char *
|
||||
HIDAPI_DriverGameCube_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
|
||||
{
|
||||
/* Give a user friendly name for this controller */
|
||||
if (SDL_IsJoystickGameCube(vendor_id, product_id)) {
|
||||
return "Nintendo GameCube Controller";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static SDL_bool
|
||||
HIDAPI_DriverGameCube_InitDriver(SDL_HIDAPI_DriverData *context, Uint16 vendor_id, Uint16 product_id, int *num_joysticks)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx;
|
||||
Uint8 packet[37];
|
||||
Uint8 *curSlot;
|
||||
Uint8 i;
|
||||
int size;
|
||||
Uint8 initMagic = 0x13;
|
||||
Uint8 rumbleMagic = 0x11;
|
||||
|
||||
ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
SDL_OutOfMemory();
|
||||
return SDL_FALSE;
|
||||
}
|
||||
ctx->joysticks[0] = -1;
|
||||
ctx->joysticks[1] = -1;
|
||||
ctx->joysticks[2] = -1;
|
||||
ctx->joysticks[3] = -1;
|
||||
ctx->rumble[0] = rumbleMagic;
|
||||
|
||||
context->context = ctx;
|
||||
|
||||
/* This is all that's needed to initialize the device. Really! */
|
||||
if (hid_write(context->device, &initMagic, sizeof(initMagic)) <= 0) {
|
||||
SDL_SetError("Couldn't initialize WUP-028");
|
||||
SDL_free(ctx);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
/* Add all the applicable joysticks */
|
||||
while ((size = hid_read_timeout(context->device, packet, sizeof(packet), 0)) > 0) {
|
||||
if (size < 37 || packet[0] != 0x21) {
|
||||
continue; /* Nothing to do yet...? */
|
||||
}
|
||||
|
||||
/* Go through all 4 slots */
|
||||
curSlot = packet + 1;
|
||||
for (i = 0; i < 4; i += 1, curSlot += 9) {
|
||||
if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
|
||||
if (ctx->joysticks[i] == -1) {
|
||||
ctx->joysticks[i] = SDL_GetNextJoystickInstanceID();
|
||||
|
||||
*num_joysticks += 1;
|
||||
|
||||
SDL_PrivateJoystickAdded(ctx->joysticks[i]);
|
||||
}
|
||||
} else {
|
||||
if (ctx->joysticks[i] != -1) {
|
||||
SDL_PrivateJoystickRemoved(ctx->joysticks[i]);
|
||||
|
||||
*num_joysticks -= 1;
|
||||
|
||||
ctx->joysticks[i] = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
HIDAPI_DriverGameCube_QuitDriver(SDL_HIDAPI_DriverData *context, SDL_bool send_event, int *num_joysticks)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
|
||||
Uint8 i;
|
||||
|
||||
/* Stop all rumble activity */
|
||||
for (i = 1; i < 5; i += 1) {
|
||||
ctx->rumble[i] = 0;
|
||||
}
|
||||
hid_write(context->device, ctx->rumble, sizeof(ctx->rumble));
|
||||
|
||||
/* Remove all joysticks! */
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
if (ctx->joysticks[i] != -1) {
|
||||
*num_joysticks -= 1;
|
||||
if (send_event) {
|
||||
SDL_PrivateJoystickRemoved(ctx->joysticks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(context->context);
|
||||
}
|
||||
|
||||
static SDL_bool
|
||||
HIDAPI_DriverGameCube_UpdateDriver(SDL_HIDAPI_DriverData *context, int *num_joysticks)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
|
||||
SDL_Joystick *joystick;
|
||||
Uint8 packet[37];
|
||||
Uint8 *curSlot;
|
||||
Uint32 now;
|
||||
Uint8 i;
|
||||
int size;
|
||||
|
||||
/* Read input packet */
|
||||
while ((size = hid_read_timeout(context->device, packet, sizeof(packet), 0)) > 0) {
|
||||
if (size < 37 || packet[0] != 0x21) {
|
||||
continue; /* Nothing to do right now...? */
|
||||
}
|
||||
|
||||
/* Go through all 4 slots */
|
||||
curSlot = packet + 1;
|
||||
for (i = 0; i < 4; i += 1, curSlot += 9) {
|
||||
if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
|
||||
if (ctx->joysticks[i] == -1) {
|
||||
ctx->joysticks[i] = SDL_GetNextJoystickInstanceID();
|
||||
|
||||
*num_joysticks += 1;
|
||||
|
||||
SDL_PrivateJoystickAdded(ctx->joysticks[i]);
|
||||
}
|
||||
joystick = SDL_JoystickFromInstanceID(ctx->joysticks[i]);
|
||||
|
||||
/* Hasn't been opened yet, skip */
|
||||
if (joystick == NULL) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (ctx->joysticks[i] != -1) {
|
||||
SDL_PrivateJoystickRemoved(ctx->joysticks[i]);
|
||||
|
||||
*num_joysticks -= 1;
|
||||
|
||||
ctx->joysticks[i] = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
#define READ_BUTTON(off, flag, button) \
|
||||
SDL_PrivateJoystickButton( \
|
||||
joystick, \
|
||||
button, \
|
||||
(curSlot[off] & flag) ? SDL_PRESSED : SDL_RELEASED \
|
||||
);
|
||||
READ_BUTTON(1, 0x01, 0) /* A */
|
||||
READ_BUTTON(1, 0x02, 1) /* B */
|
||||
READ_BUTTON(1, 0x04, 2) /* X */
|
||||
READ_BUTTON(1, 0x08, 3) /* Y */
|
||||
READ_BUTTON(1, 0x10, 4) /* DPAD_LEFT */
|
||||
READ_BUTTON(1, 0x20, 5) /* DPAD_RIGHT */
|
||||
READ_BUTTON(1, 0x40, 6) /* DPAD_DOWN */
|
||||
READ_BUTTON(1, 0x80, 7) /* DPAD_UP */
|
||||
READ_BUTTON(2, 0x01, 8) /* START */
|
||||
READ_BUTTON(2, 0x02, 9) /* RIGHTSHOULDER */
|
||||
/* [2] 0x04 - R, [2] 0x08 - L */
|
||||
#undef READ_BUTTON
|
||||
|
||||
/* Axis math taken from SDL_xinputjoystick.c */
|
||||
#define READ_AXIS(off, axis) \
|
||||
SDL_PrivateJoystickAxis( \
|
||||
joystick, \
|
||||
axis, \
|
||||
(Sint16)(((int)curSlot[off] * 257) - 32768) \
|
||||
);
|
||||
READ_AXIS(3, 0) /* LEFTX */
|
||||
READ_AXIS(4, 1) /* LEFTY */
|
||||
READ_AXIS(5, 2) /* RIGHTX */
|
||||
READ_AXIS(6, 3) /* RIGHTY */
|
||||
READ_AXIS(7, 4) /* TRIGGERLEFT */
|
||||
READ_AXIS(8, 5) /* TRIGGERRIGHT */
|
||||
#undef READ_AXIS
|
||||
}
|
||||
}
|
||||
|
||||
/* Write rumble packet */
|
||||
now = SDL_GetTicks();
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
if (ctx->rumbleExpiration[i] > 0) {
|
||||
if (SDL_TICKS_PASSED(now, ctx->rumbleExpiration[i])) {
|
||||
ctx->rumble[i + 1] = 0;
|
||||
ctx->rumbleExpiration[i] = 0;
|
||||
ctx->rumbleUpdate = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx->rumbleUpdate) {
|
||||
hid_write(context->device, ctx->rumble, sizeof(ctx->rumble));
|
||||
ctx->rumbleUpdate = 0;
|
||||
}
|
||||
|
||||
/* If we got here, nothing bad happened! */
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
static int
|
||||
HIDAPI_DriverGameCube_NumJoysticks(SDL_HIDAPI_DriverData *context)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
|
||||
int i, joysticks = 0;
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
if (ctx->joysticks[i] != -1) {
|
||||
joysticks += 1;
|
||||
}
|
||||
}
|
||||
return joysticks;
|
||||
}
|
||||
|
||||
static SDL_JoystickID
|
||||
HIDAPI_DriverGameCube_InstanceIDForIndex(SDL_HIDAPI_DriverData *context, int index)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
|
||||
Uint8 i;
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
if (ctx->joysticks[i] != -1) {
|
||||
if (index == 0) {
|
||||
return ctx->joysticks[i];
|
||||
}
|
||||
index -= 1;
|
||||
}
|
||||
}
|
||||
return -1; /* Should never get here! */
|
||||
}
|
||||
|
||||
static SDL_bool
|
||||
HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
|
||||
SDL_JoystickID instance = SDL_JoystickInstanceID(joystick);
|
||||
Uint8 i;
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
if (instance == ctx->joysticks[i]) {
|
||||
joystick->nbuttons = 10;
|
||||
joystick->naxes = 6;
|
||||
joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
}
|
||||
return SDL_FALSE; /* Should never get here! */
|
||||
}
|
||||
|
||||
static int
|
||||
HIDAPI_DriverGameCube_Rumble(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
|
||||
{
|
||||
SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
|
||||
SDL_JoystickID instance = SDL_JoystickInstanceID(joystick);
|
||||
Uint8 i, val;
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
if (instance == ctx->joysticks[i]) {
|
||||
val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
|
||||
if (val != ctx->rumble[i + 1]) {
|
||||
ctx->rumble[i + 1] = val;
|
||||
ctx->rumbleUpdate = 1;
|
||||
}
|
||||
if (val && duration_ms < SDL_HAPTIC_INFINITY) {
|
||||
ctx->rumbleExpiration[i] = SDL_GetTicks() + duration_ms;
|
||||
} else {
|
||||
ctx->rumbleExpiration[i] = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube =
|
||||
{
|
||||
SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
|
||||
SDL_TRUE,
|
||||
HIDAPI_DriverGameCube_IsSupportedDevice,
|
||||
HIDAPI_DriverGameCube_GetDeviceName,
|
||||
HIDAPI_DriverGameCube_InitDriver,
|
||||
HIDAPI_DriverGameCube_QuitDriver,
|
||||
HIDAPI_DriverGameCube_UpdateDriver,
|
||||
HIDAPI_DriverGameCube_NumJoysticks,
|
||||
HIDAPI_DriverGameCube_InstanceIDForIndex,
|
||||
HIDAPI_DriverGameCube_OpenJoystick,
|
||||
HIDAPI_DriverGameCube_Rumble
|
||||
};
|
||||
|
||||
#endif /* SDL_JOYSTICK_HIDAPI_GAMECUBE */
|
||||
|
||||
#endif /* SDL_JOYSTICK_HIDAPI */
|
Reference in New Issue
Block a user