Added mapping for the Wii Nunchuk extension

This commit is contained in:
Sam Lantinga 2022-09-01 20:27:34 -07:00
parent e19b36d871
commit 396411c090
4 changed files with 164 additions and 98 deletions

View File

@ -30,6 +30,7 @@
#include "SDL_gamecontrollerdb.h" #include "SDL_gamecontrollerdb.h"
#include "controller_type.h" #include "controller_type.h"
#include "usb_ids.h" #include "usb_ids.h"
#include "hidapi/SDL_hidapi_nintendo.h"
#if !SDL_EVENTS_DISABLED #if !SDL_EVENTS_DISABLED
#include "../events/SDL_events_c.h" #include "../events/SDL_events_c.h"
@ -570,25 +571,39 @@ static ControllerMapping_t *SDL_CreateMappingForHIDAPIController(SDL_JoystickGUI
(vendor == USB_VENDOR_SHENZHEN && product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER)) { (vendor == USB_VENDOR_SHENZHEN && product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER)) {
/* GameCube driver has 12 buttons and 6 axes */ /* GameCube driver has 12 buttons and 6 axes */
SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3,start:b8,x:b2,y:b3,", sizeof(mapping_string)); SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3,start:b8,x:b2,y:b3,", sizeof(mapping_string));
} else if (vendor == USB_VENDOR_NINTENDO && guid.data[15] != 0 && guid.data[15] != 3) { } else if (vendor == USB_VENDOR_NINTENDO &&
guid.data[15] != k_eSwitchDeviceInfoControllerType_Unknown &&
guid.data[15] != k_eSwitchDeviceInfoControllerType_ProController &&
guid.data[15] != k_eWiiExtensionControllerType_WiiUPro) {
switch (guid.data[15]) { switch (guid.data[15]) {
case 9: case k_eSwitchDeviceInfoControllerType_NESLeft:
case 10: case k_eSwitchDeviceInfoControllerType_NESRight:
/* NES Controller */
SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string)); SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string));
break; break;
case 11: case k_eSwitchDeviceInfoControllerType_SNES:
/* SNES Controller */
SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,", sizeof(mapping_string)); SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,", sizeof(mapping_string));
break; break;
case 12: case k_eSwitchDeviceInfoControllerType_N64:
/* N64 Controller */
SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b15,", sizeof(mapping_string)); SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b15,", sizeof(mapping_string));
break; break;
case 13: case k_eSwitchDeviceInfoControllerType_SEGA_Genesis:
/* SEGA Genesis Controller */
SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,rightshoulder:b10,righttrigger:a5,start:b6,misc1:b15,", sizeof(mapping_string)); SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,rightshoulder:b10,righttrigger:a5,start:b6,misc1:b15,", sizeof(mapping_string));
break; break;
case k_eWiiExtensionControllerType_None:
SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,start:b6,x:b2,y:b3,", sizeof(mapping_string));
break;
case k_eWiiExtensionControllerType_Nunchuck:
{
/* FIXME: Should we map this to the left or right side? */
const SDL_bool map_nunchuck_left_side = SDL_TRUE;
if (map_nunchuck_left_side) {
SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,start:b6,x:b2,y:b3,", sizeof(mapping_string));
} else {
SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,rightshoulder:b9,righttrigger:a4,rightx:a0,righty:a1,start:b6,x:b2,y:b3,", sizeof(mapping_string));
}
}
break;
default: default:
/* Mini gamepad mode */ /* Mini gamepad mode */
SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,", sizeof(mapping_string)); SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,", sizeof(mapping_string));

View File

@ -0,0 +1,47 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2022 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.
*/
/* These are values used in the controller type byte of the controller GUID */
/* These values come directly out of the hardware, so don't change them */
typedef enum {
k_eSwitchDeviceInfoControllerType_Unknown = 0,
k_eSwitchDeviceInfoControllerType_JoyConLeft = 1,
k_eSwitchDeviceInfoControllerType_JoyConRight = 2,
k_eSwitchDeviceInfoControllerType_ProController = 3,
k_eSwitchDeviceInfoControllerType_NESLeft = 9,
k_eSwitchDeviceInfoControllerType_NESRight = 10,
k_eSwitchDeviceInfoControllerType_SNES = 11,
k_eSwitchDeviceInfoControllerType_N64 = 12,
k_eSwitchDeviceInfoControllerType_SEGA_Genesis = 13,
} ESwitchDeviceInfoControllerType;
/* These values are used internally but can be updated as needed */
typedef enum {
k_eWiiExtensionControllerType_Unknown = 0,
k_eWiiExtensionControllerType_None = 128,
k_eWiiExtensionControllerType_Nunchuck = 129,
k_eWiiExtensionControllerType_ClassicController = 130,
k_eWiiExtensionControllerType_ClassicControllerPro = 131,
k_eWiiExtensionControllerType_WiiUPro = 132,
} EWiiExtensionControllerType;
/* vi: set ts=4 sw=4 expandtab: */

View File

@ -33,6 +33,7 @@
#include "../SDL_sysjoystick.h" #include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h" #include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h" #include "SDL_hidapi_rumble.h"
#include "SDL_hidapi_nintendo.h"
#ifdef SDL_JOYSTICK_HIDAPI_SWITCH #ifdef SDL_JOYSTICK_HIDAPI_SWITCH
@ -102,18 +103,6 @@ typedef enum {
k_eSwitchProprietaryCommandIDs_ResetMCU = 0x06, k_eSwitchProprietaryCommandIDs_ResetMCU = 0x06,
} ESwitchProprietaryCommandIDs; } ESwitchProprietaryCommandIDs;
typedef enum {
k_eSwitchDeviceInfoControllerType_Unknown = 0,
k_eSwitchDeviceInfoControllerType_JoyConLeft = 1,
k_eSwitchDeviceInfoControllerType_JoyConRight = 2,
k_eSwitchDeviceInfoControllerType_ProController = 3,
k_eSwitchDeviceInfoControllerType_NESLeft = 9,
k_eSwitchDeviceInfoControllerType_NESRight = 10,
k_eSwitchDeviceInfoControllerType_SNES = 11,
k_eSwitchDeviceInfoControllerType_N64 = 12,
k_eSwitchDeviceInfoControllerType_SEGA_Genesis = 13,
} ESwitchDeviceInfoControllerType;
#define k_unSwitchOutputPacketDataLength 49 #define k_unSwitchOutputPacketDataLength 49
#define k_unSwitchMaxOutputPacketLength 64 #define k_unSwitchMaxOutputPacketLength 64
#define k_unSwitchBluetoothPacketLength k_unSwitchOutputPacketDataLength #define k_unSwitchBluetoothPacketLength k_unSwitchOutputPacketDataLength
@ -1244,6 +1233,7 @@ UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
if (name && (!name || SDL_strcmp(name, device->name) != 0)) { if (name && (!name || SDL_strcmp(name, device->name) != 0)) {
SDL_free(device->name); SDL_free(device->name);
device->name = SDL_strdup(name); device->name = SDL_strdup(name);
SDL_SetJoystickGUIDCRC(&device->guid, SDL_crc16(0, name, SDL_strlen(name)));
} }
} }

View File

@ -31,13 +31,13 @@
#include "../SDL_sysjoystick.h" #include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h" #include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h" #include "SDL_hidapi_rumble.h"
#include "SDL_hidapi_nintendo.h"
#ifdef SDL_JOYSTICK_HIDAPI_WII #ifdef SDL_JOYSTICK_HIDAPI_WII
/* Define this if you want to log all packets from the controller */ /* Define this if you want to log all packets from the controller */
/*#define DEBUG_WII_PROTOCOL*/ /*#define DEBUG_WII_PROTOCOL*/
#define DEBUG_WII_PROTOCOL
#undef clamp #undef clamp
#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) #define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))
@ -80,15 +80,6 @@ typedef enum {
k_eWiiPlayerLEDs_P4 = 0x80, k_eWiiPlayerLEDs_P4 = 0x80,
} EWiiPlayerLEDs; } EWiiPlayerLEDs;
typedef enum {
k_eWiiExtensionControllerType_None,
k_eWiiExtensionControllerType_Unknown,
k_eWiiExtensionControllerType_Nunchuck,
k_eWiiExtensionControllerType_ClassicController,
k_eWiiExtensionControllerType_ClassicControllerPro,
k_eWiiExtensionControllerType_WiiUPro,
} EWiiExtensionControllerType;
#define k_unWiiPacketDataLength 22 #define k_unWiiPacketDataLength 22
typedef struct { typedef struct {
@ -312,13 +303,13 @@ static SDL_bool ParseExtensionResponse(SDL_DriverWii_Context *ctx)
static void UpdatePowerLevelWii(SDL_Joystick *joystick, Uint8 batteryLevelByte) static void UpdatePowerLevelWii(SDL_Joystick *joystick, Uint8 batteryLevelByte)
{ {
if (batteryLevelByte > 178) { if (batteryLevelByte > 178) {
joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL; SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_FULL);
} else if (batteryLevelByte > 51) { } else if (batteryLevelByte > 51) {
joystick->epowerlevel = SDL_JOYSTICK_POWER_MEDIUM; SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_MEDIUM);
} else if (batteryLevelByte > 13) { } else if (batteryLevelByte > 13) {
joystick->epowerlevel = SDL_JOYSTICK_POWER_LOW; SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_LOW);
} else { } else {
joystick->epowerlevel = SDL_JOYSTICK_POWER_EMPTY; SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_EMPTY);
} }
} }
@ -335,45 +326,18 @@ static void UpdatePowerLevelWiiU(SDL_Joystick *joystick, Uint8 extensionBatteryB
* No value above 4 has been observed. * No value above 4 has been observed.
*/ */
if (pluggedIn && !charging) { if (pluggedIn && !charging) {
joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_WIRED);
} else if (batteryLevel >= 4) { } else if (batteryLevel >= 4) {
joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL; SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_FULL);
} else if (batteryLevel > 1) { } else if (batteryLevel > 1) {
joystick->epowerlevel = SDL_JOYSTICK_POWER_MEDIUM; SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_MEDIUM);
} else if (batteryLevel == 1) { } else if (batteryLevel == 1) {
joystick->epowerlevel = SDL_JOYSTICK_POWER_LOW; SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_LOW);
} else { } else {
joystick->epowerlevel = SDL_JOYSTICK_POWER_EMPTY; SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_EMPTY);
} }
} }
static SDL_bool IdentifyController(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
{
const Uint8 statusRequest[2] = { k_eWiiOutputReportIDs_StatusRequest, 0 };
SDL_bool hasExtension;
WriteOutput(ctx, statusRequest, sizeof(statusRequest), SDL_TRUE);
if (!ReadInputSync(ctx, k_eWiiInputReportIDs_Status, NULL)) {
return SDL_FALSE;
}
UpdatePowerLevelWii(joystick, ctx->m_rgucReadBuffer[6]);
hasExtension = ctx->m_rgucReadBuffer[3] & 2 ? SDL_TRUE : SDL_FALSE;
if (hasExtension) {
/* http://wiibrew.org/wiki/Wiimote/Extension_Controllers#The_New_Way */
Uint8 data_0x55 = 0x55;
Uint8 data_0x00 = 0x00;
SDL_bool ok = WriteRegister(ctx, 0xA400F0, &data_0x55, sizeof(data_0x55), SDL_TRUE)
&& WriteRegister(ctx, 0xA400FB, &data_0x00, sizeof(data_0x00), SDL_TRUE)
&& ReadRegister(ctx, 0xA400FA, 6, SDL_TRUE)
&& ParseExtensionResponse(ctx);
if (!ok) {
return SDL_FALSE;
}
} else {
ctx->m_eExtensionControllerType = k_eWiiExtensionControllerType_None;
}
return SDL_TRUE;
}
static EWiiInputReportIDs GetButtonPacketType(SDL_DriverWii_Context *ctx) static EWiiInputReportIDs GetButtonPacketType(SDL_DriverWii_Context *ctx)
{ {
switch (ctx->m_eExtensionControllerType) { switch (ctx->m_eExtensionControllerType) {
@ -434,24 +398,6 @@ static void InitStickCalibrationData(SDL_DriverWii_Context *ctx)
} }
} }
static const char* GetNameFromExtensionInfo(SDL_DriverWii_Context *ctx)
{
switch (ctx->m_eExtensionControllerType) {
case k_eWiiExtensionControllerType_None:
return "Nintendo Wii Remote";
case k_eWiiExtensionControllerType_Nunchuck:
return "Nintendo Wii Remote with Nunchuck";
case k_eWiiExtensionControllerType_ClassicController:
return "Nintendo Wii Remote with Classic Controller";
case k_eWiiExtensionControllerType_ClassicControllerPro:
return "Nintendo Wii Remote with Classic Controller Pro";
case k_eWiiExtensionControllerType_WiiUPro:
return "Nintendo Wii U Pro Controller";
default:
return "Nintendo Wii Remote with Unknown Extension";
}
}
static void SDLCALL SDL_GameControllerButtonReportingHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) static void SDLCALL SDL_GameControllerButtonReportingHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{ {
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)userdata; SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)userdata;
@ -523,9 +469,84 @@ static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, c
} }
} }
static EWiiExtensionControllerType
ReadControllerType(SDL_HIDAPI_Device *device)
{
EWiiExtensionControllerType eControllerType = k_eWiiExtensionControllerType_Unknown;
/* Create enough of a context to read the controller type from the device */
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)SDL_calloc(1, sizeof(*ctx));
if (ctx) {
ctx->device = device;
device->dev = SDL_hid_open_path(device->path, 0);
if (device->dev) {
const Uint8 statusRequest[2] = { k_eWiiOutputReportIDs_StatusRequest, 0 };
WriteOutput(ctx, statusRequest, sizeof(statusRequest), SDL_TRUE);
if (ReadInputSync(ctx, k_eWiiInputReportIDs_Status, NULL)) {
SDL_bool hasExtension = (ctx->m_rgucReadBuffer[3] & 2) ? SDL_TRUE : SDL_FALSE;
if (hasExtension) {
/* http://wiibrew.org/wiki/Wiimote/Extension_Controllers#The_New_Way */
Uint8 data_0x55 = 0x55;
Uint8 data_0x00 = 0x00;
if (WriteRegister(ctx, 0xA400F0, &data_0x55, sizeof(data_0x55), SDL_TRUE) &&
WriteRegister(ctx, 0xA400FB, &data_0x00, sizeof(data_0x00), SDL_TRUE) &&
ReadRegister(ctx, 0xA400FA, 6, SDL_TRUE) && ParseExtensionResponse(ctx)) {
eControllerType = ctx->m_eExtensionControllerType;
}
} else {
eControllerType = k_eWiiExtensionControllerType_None;
}
}
SDL_hid_close(device->dev);
device->dev = NULL;
}
SDL_free(ctx);
}
return eControllerType;
}
static void
UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
{
EWiiExtensionControllerType eControllerType = (EWiiExtensionControllerType)device->guid.data[15];
const char *name = NULL;
switch (eControllerType) {
case k_eWiiExtensionControllerType_None:
name = "Nintendo Wii Remote";
break;
case k_eWiiExtensionControllerType_Nunchuck:
name = "Nintendo Wii Remote with Nunchuck";
break;
case k_eWiiExtensionControllerType_ClassicController:
name = "Nintendo Wii Remote with Classic Controller";
break;
case k_eWiiExtensionControllerType_ClassicControllerPro:
name = "Nintendo Wii Remote with Classic Controller Pro";
break;
case k_eWiiExtensionControllerType_WiiUPro:
name = "Nintendo Wii U Pro Controller";
break;
default:
name = "Nintendo Wii Remote with Unknown Extension";
break;
}
if (name && (!name || SDL_strcmp(name, device->name) != 0)) {
SDL_free(device->name);
device->name = SDL_strdup(name);
SDL_SetJoystickGUIDCRC(&device->guid, SDL_crc16(0, name, SDL_strlen(name)));
}
}
static SDL_bool static SDL_bool
HIDAPI_DriverWii_InitDevice(SDL_HIDAPI_Device *device) HIDAPI_DriverWii_InitDevice(SDL_HIDAPI_Device *device)
{ {
if (device->vendor_id == USB_VENDOR_NINTENDO) {
EWiiExtensionControllerType eControllerType = ReadControllerType(device);
device->guid.data[15] = eControllerType;
UpdateDeviceIdentity(device);
}
return HIDAPI_JoystickConnected(device, NULL); return HIDAPI_JoystickConnected(device, NULL);
} }
@ -568,19 +589,12 @@ HIDAPI_DriverWii_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
goto error; goto error;
} }
SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, ctx->m_eExtensionControllerType = (EWiiExtensionControllerType)device->guid.data[15];
SDL_GameControllerButtonReportingHintChanged, ctx);
if (!IdentifyController(ctx, joystick)) {
char msg[512];
SDL_GetErrorMsg(msg, sizeof(msg) - 1);
SDL_SetError("Couldn't read device info: %s", msg);
goto error;
}
InitStickCalibrationData(ctx); InitStickCalibrationData(ctx);
SDL_free(device->name);
device->name = SDL_strdup(GetNameFromExtensionInfo(ctx)); SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
SDL_GameControllerButtonReportingHintChanged, ctx);
/* Initialize player index (needed for setting LEDs) */ /* Initialize player index (needed for setting LEDs) */
ctx->m_nPlayerIndex = SDL_JoystickGetPlayerIndex(joystick); ctx->m_nPlayerIndex = SDL_JoystickGetPlayerIndex(joystick);
@ -953,7 +967,7 @@ HIDAPI_DriverWii_UpdateDevice(SDL_HIDAPI_Device *device)
} }
/* Request a status update periodically to make sure our battery value is up to date */ /* Request a status update periodically to make sure our battery value is up to date */
if (SDL_TICKS_PASSED(now, ctx->m_unLastStatus + FIFTEEN_MINUTES_IN_MS)) { if (!ctx->m_unLastStatus || SDL_TICKS_PASSED(now, ctx->m_unLastStatus + FIFTEEN_MINUTES_IN_MS)) {
Uint8 data[2]; Uint8 data[2];
data[0] = k_eWiiOutputReportIDs_StatusRequest; data[0] = k_eWiiOutputReportIDs_StatusRequest;