Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_wii.c
9906 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#ifdef SDL_JOYSTICK_HIDAPI2324#include "../../SDL_hints_c.h"25#include "../SDL_sysjoystick.h"26#include "SDL_hidapijoystick_c.h"27#include "SDL_hidapi_rumble.h"28#include "SDL_hidapi_nintendo.h"2930#ifdef SDL_JOYSTICK_HIDAPI_WII3132// Define this if you want to log all packets from the controller33// #define DEBUG_WII_PROTOCOL3435#define ENABLE_CONTINUOUS_REPORTING true3637#define INPUT_WAIT_TIMEOUT_MS (3 * 1000)38#define MOTION_PLUS_UPDATE_TIME_MS (8 * 1000)39#define STATUS_UPDATE_TIME_MS (15 * 60 * 1000)4041#define WII_EXTENSION_NONE 0x2E2E42#define WII_EXTENSION_UNINITIALIZED 0xFFFF43#define WII_EXTENSION_NUNCHUK 0x000044#define WII_EXTENSION_GAMEPAD 0x010145#define WII_EXTENSION_WIIUPRO 0x012046#define WII_EXTENSION_MOTIONPLUS_MASK 0xF0FF47#define WII_EXTENSION_MOTIONPLUS_ID 0x00054849#define WII_MOTIONPLUS_MODE_NONE 0x0050#define WII_MOTIONPLUS_MODE_STANDARD 0x0451#define WII_MOTIONPLUS_MODE_NUNCHUK 0x0552#define WII_MOTIONPLUS_MODE_GAMEPAD 0x075354typedef enum55{56k_eWiiInputReportIDs_Status = 0x20,57k_eWiiInputReportIDs_ReadMemory = 0x21,58k_eWiiInputReportIDs_Acknowledge = 0x22,59k_eWiiInputReportIDs_ButtonData0 = 0x30,60k_eWiiInputReportIDs_ButtonData1 = 0x31,61k_eWiiInputReportIDs_ButtonData2 = 0x32,62k_eWiiInputReportIDs_ButtonData3 = 0x33,63k_eWiiInputReportIDs_ButtonData4 = 0x34,64k_eWiiInputReportIDs_ButtonData5 = 0x35,65k_eWiiInputReportIDs_ButtonData6 = 0x36,66k_eWiiInputReportIDs_ButtonData7 = 0x37,67k_eWiiInputReportIDs_ButtonDataD = 0x3D,68k_eWiiInputReportIDs_ButtonDataE = 0x3E,69k_eWiiInputReportIDs_ButtonDataF = 0x3F,70} EWiiInputReportIDs;7172typedef enum73{74k_eWiiOutputReportIDs_Rumble = 0x10,75k_eWiiOutputReportIDs_LEDs = 0x11,76k_eWiiOutputReportIDs_DataReportingMode = 0x12,77k_eWiiOutputReportIDs_IRCameraEnable = 0x13,78k_eWiiOutputReportIDs_SpeakerEnable = 0x14,79k_eWiiOutputReportIDs_StatusRequest = 0x15,80k_eWiiOutputReportIDs_WriteMemory = 0x16,81k_eWiiOutputReportIDs_ReadMemory = 0x17,82k_eWiiOutputReportIDs_SpeakerData = 0x18,83k_eWiiOutputReportIDs_SpeakerMute = 0x19,84k_eWiiOutputReportIDs_IRCameraEnable2 = 0x1a,85} EWiiOutputReportIDs;8687typedef enum88{89k_eWiiPlayerLEDs_P1 = 0x10,90k_eWiiPlayerLEDs_P2 = 0x20,91k_eWiiPlayerLEDs_P3 = 0x40,92k_eWiiPlayerLEDs_P4 = 0x80,93} EWiiPlayerLEDs;9495typedef enum96{97k_eWiiCommunicationState_None, // No special communications happening98k_eWiiCommunicationState_CheckMotionPlusStage1, // Sent standard extension identify request99k_eWiiCommunicationState_CheckMotionPlusStage2, // Sent Motion Plus extension identify request100} EWiiCommunicationState;101102typedef enum103{104k_eWiiButtons_A = SDL_GAMEPAD_BUTTON_MISC1,105k_eWiiButtons_B,106k_eWiiButtons_One,107k_eWiiButtons_Two,108k_eWiiButtons_Plus,109k_eWiiButtons_Minus,110k_eWiiButtons_Home,111k_eWiiButtons_DPad_Up,112k_eWiiButtons_DPad_Down,113k_eWiiButtons_DPad_Left,114k_eWiiButtons_DPad_Right,115k_eWiiButtons_Max116} EWiiButtons;117118#define k_unWiiPacketDataLength 22119120typedef struct121{122Uint8 rgucBaseButtons[2];123Uint8 rgucAccelerometer[3];124Uint8 rgucExtension[21];125bool hasBaseButtons;126bool hasAccelerometer;127Uint8 ucNExtensionBytes;128} WiiButtonData;129130typedef struct131{132Uint16 min;133Uint16 max;134Uint16 center;135Uint16 deadzone;136} StickCalibrationData;137138typedef struct139{140SDL_HIDAPI_Device *device;141SDL_Joystick *joystick;142Uint64 timestamp;143EWiiCommunicationState m_eCommState;144EWiiExtensionControllerType m_eExtensionControllerType;145bool m_bPlayerLights;146int m_nPlayerIndex;147bool m_bRumbleActive;148bool m_bMotionPlusPresent;149Uint8 m_ucMotionPlusMode;150bool m_bReportSensors;151Uint8 m_rgucReadBuffer[k_unWiiPacketDataLength];152Uint64 m_ulLastInput;153Uint64 m_ulLastStatus;154Uint64 m_ulNextMotionPlusCheck;155bool m_bDisconnected;156157StickCalibrationData m_StickCalibrationData[6];158} SDL_DriverWii_Context;159160static void HIDAPI_DriverWii_RegisterHints(SDL_HintCallback callback, void *userdata)161{162SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata);163}164165static void HIDAPI_DriverWii_UnregisterHints(SDL_HintCallback callback, void *userdata)166{167SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata);168}169170static bool HIDAPI_DriverWii_IsEnabled(void)171{172#if 1 // This doesn't work with the dolphinbar, so don't enable by default right now173return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII, false);174#else175return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII,176SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI,177SDL_HIDAPI_DEFAULT));178#endif179}180181static bool HIDAPI_DriverWii_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)182{183if (vendor_id == USB_VENDOR_NINTENDO &&184(product_id == USB_PRODUCT_NINTENDO_WII_REMOTE ||185product_id == USB_PRODUCT_NINTENDO_WII_REMOTE2)) {186return true;187}188return false;189}190191static int ReadInput(SDL_DriverWii_Context *ctx)192{193int size;194195// Make sure we don't try to read at the same time a write is happening196if (SDL_GetAtomicInt(&ctx->device->rumble_pending) > 0) {197return 0;198}199200size = SDL_hid_read_timeout(ctx->device->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0);201#ifdef DEBUG_WII_PROTOCOL202if (size > 0) {203HIDAPI_DumpPacket("Wii packet: size = %d", ctx->m_rgucReadBuffer, size);204}205#endif206return size;207}208209static bool WriteOutput(SDL_DriverWii_Context *ctx, const Uint8 *data, int size, bool sync)210{211#ifdef DEBUG_WII_PROTOCOL212if (size > 0) {213HIDAPI_DumpPacket("Wii write packet: size = %d", data, size);214}215#endif216if (sync) {217return SDL_hid_write(ctx->device->dev, data, size) >= 0;218} else {219// Use the rumble thread for general asynchronous writes220if (!SDL_HIDAPI_LockRumble()) {221return false;222}223return SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size) >= 0;224}225}226227static bool ReadInputSync(SDL_DriverWii_Context *ctx, EWiiInputReportIDs expectedID, bool (*isMine)(const Uint8 *))228{229Uint64 endTicks = SDL_GetTicks() + 250; // Seeing successful reads after about 200 ms230231int nRead = 0;232while ((nRead = ReadInput(ctx)) != -1) {233if (nRead > 0) {234if (ctx->m_rgucReadBuffer[0] == expectedID && (!isMine || isMine(ctx->m_rgucReadBuffer))) {235return true;236}237} else {238if (SDL_GetTicks() >= endTicks) {239break;240}241SDL_Delay(1);242}243}244SDL_SetError("Read timed out");245return false;246}247248static bool IsWriteMemoryResponse(const Uint8 *data)249{250return data[3] == k_eWiiOutputReportIDs_WriteMemory;251}252253static bool WriteRegister(SDL_DriverWii_Context *ctx, Uint32 address, const Uint8 *data, int size, bool sync)254{255Uint8 writeRequest[k_unWiiPacketDataLength];256257SDL_zeroa(writeRequest);258writeRequest[0] = k_eWiiOutputReportIDs_WriteMemory;259writeRequest[1] = (Uint8)(0x04 | (Uint8)ctx->m_bRumbleActive);260writeRequest[2] = (address >> 16) & 0xff;261writeRequest[3] = (address >> 8) & 0xff;262writeRequest[4] = address & 0xff;263writeRequest[5] = (Uint8)size;264SDL_assert(size > 0 && size <= 16);265SDL_memcpy(writeRequest + 6, data, size);266267if (!WriteOutput(ctx, writeRequest, sizeof(writeRequest), sync)) {268return false;269}270if (sync) {271// Wait for response272if (!ReadInputSync(ctx, k_eWiiInputReportIDs_Acknowledge, IsWriteMemoryResponse)) {273return false;274}275if (ctx->m_rgucReadBuffer[4]) {276SDL_SetError("Write memory failed: %u", ctx->m_rgucReadBuffer[4]);277return false;278}279}280return true;281}282283static bool ReadRegister(SDL_DriverWii_Context *ctx, Uint32 address, int size, bool sync)284{285Uint8 readRequest[7];286287readRequest[0] = k_eWiiOutputReportIDs_ReadMemory;288readRequest[1] = (Uint8)(0x04 | (Uint8)ctx->m_bRumbleActive);289readRequest[2] = (address >> 16) & 0xff;290readRequest[3] = (address >> 8) & 0xff;291readRequest[4] = address & 0xff;292readRequest[5] = (size >> 8) & 0xff;293readRequest[6] = size & 0xff;294295SDL_assert(size > 0 && size <= 0xffff);296297if (!WriteOutput(ctx, readRequest, sizeof(readRequest), sync)) {298return false;299}300if (sync) {301SDL_assert(size <= 16); // Only waiting for one packet is supported right now302// Wait for response303if (!ReadInputSync(ctx, k_eWiiInputReportIDs_ReadMemory, NULL)) {304return false;305}306}307return true;308}309310static bool SendExtensionIdentify(SDL_DriverWii_Context *ctx, bool sync)311{312return ReadRegister(ctx, 0xA400FE, 2, sync);313}314315static bool ParseExtensionIdentifyResponse(SDL_DriverWii_Context *ctx, Uint16 *extension)316{317int i;318319if (ctx->m_rgucReadBuffer[0] != k_eWiiInputReportIDs_ReadMemory) {320SDL_SetError("Unexpected extension response type");321return false;322}323324if (ctx->m_rgucReadBuffer[4] != 0x00 || ctx->m_rgucReadBuffer[5] != 0xFE) {325SDL_SetError("Unexpected extension response address");326return false;327}328329if (ctx->m_rgucReadBuffer[3] != 0x10) {330Uint8 error = (ctx->m_rgucReadBuffer[3] & 0xF);331332if (error == 7) {333// The extension memory isn't mapped334*extension = WII_EXTENSION_NONE;335return true;336}337338if (error) {339SDL_SetError("Failed to read extension type: %u", error);340} else {341SDL_SetError("Unexpected read length when reading extension type: %d", (ctx->m_rgucReadBuffer[3] >> 4) + 1);342}343return false;344}345346*extension = 0;347for (i = 6; i < 8; i++) {348*extension = *extension << 8 | ctx->m_rgucReadBuffer[i];349}350return true;351}352353static EWiiExtensionControllerType GetExtensionType(Uint16 extension_id)354{355switch (extension_id) {356case WII_EXTENSION_NONE:357return k_eWiiExtensionControllerType_None;358case WII_EXTENSION_NUNCHUK:359return k_eWiiExtensionControllerType_Nunchuk;360case WII_EXTENSION_GAMEPAD:361return k_eWiiExtensionControllerType_Gamepad;362case WII_EXTENSION_WIIUPRO:363return k_eWiiExtensionControllerType_WiiUPro;364default:365return k_eWiiExtensionControllerType_Unknown;366}367}368369static bool SendExtensionReset(SDL_DriverWii_Context *ctx, bool sync)370{371bool result = true;372{373Uint8 data = 0x55;374result = result && WriteRegister(ctx, 0xA400F0, &data, sizeof(data), sync);375}376// This write will fail if there is no extension connected, that's fine377{378Uint8 data = 0x00;379(void)WriteRegister(ctx, 0xA400FB, &data, sizeof(data), sync);380}381return result;382}383384static bool GetMotionPlusState(SDL_DriverWii_Context *ctx, bool *connected, Uint8 *mode)385{386Uint16 extension;387388if (connected) {389*connected = false;390}391if (mode) {392*mode = 0;393}394395if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {396// The Wii U Pro controller never has the Motion Plus extension397return true;398}399400if (SendExtensionIdentify(ctx, true) &&401ParseExtensionIdentifyResponse(ctx, &extension)) {402if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {403// Motion Plus is currently active404if (connected) {405*connected = true;406}407if (mode) {408*mode = (extension >> 8);409}410return true;411}412}413414if (ReadRegister(ctx, 0xA600FE, 2, true) &&415ParseExtensionIdentifyResponse(ctx, &extension)) {416if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {417// Motion Plus is currently connected418if (connected) {419*connected = true;420}421}422return true;423}424425// Failed to read the register or parse the response426return false;427}428429static bool NeedsPeriodicMotionPlusCheck(SDL_DriverWii_Context *ctx, bool status_update)430{431if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {432// The Wii U Pro controller never has the Motion Plus extension433return false;434}435436if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE && !status_update) {437// We'll get a status update when Motion Plus is disconnected438return false;439}440441return true;442}443444static void SchedulePeriodicMotionPlusCheck(SDL_DriverWii_Context *ctx)445{446ctx->m_ulNextMotionPlusCheck = SDL_GetTicks() + MOTION_PLUS_UPDATE_TIME_MS;447}448449static void CheckMotionPlusConnection(SDL_DriverWii_Context *ctx)450{451SendExtensionIdentify(ctx, false);452453ctx->m_eCommState = k_eWiiCommunicationState_CheckMotionPlusStage1;454}455456static void ActivateMotionPlusWithMode(SDL_DriverWii_Context *ctx, Uint8 mode)457{458#ifdef SDL_PLATFORM_LINUX459/* Linux drivers maintain a lot of state around the Motion Plus460* extension, so don't mess with it here.461*/462#else463WriteRegister(ctx, 0xA600FE, &mode, sizeof(mode), true);464465ctx->m_ucMotionPlusMode = mode;466#endif // LINUX467}468469static void ActivateMotionPlus(SDL_DriverWii_Context *ctx)470{471Uint8 mode = WII_MOTIONPLUS_MODE_STANDARD;472473// Pick the pass-through mode based on the connected controller474if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) {475mode = WII_MOTIONPLUS_MODE_NUNCHUK;476} else if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Gamepad) {477mode = WII_MOTIONPLUS_MODE_GAMEPAD;478}479ActivateMotionPlusWithMode(ctx, mode);480}481482static void DeactivateMotionPlus(SDL_DriverWii_Context *ctx)483{484Uint8 data = 0x55;485WriteRegister(ctx, 0xA400F0, &data, sizeof(data), true);486487// Wait for the deactivation status message488ReadInputSync(ctx, k_eWiiInputReportIDs_Status, NULL);489490ctx->m_ucMotionPlusMode = WII_MOTIONPLUS_MODE_NONE;491}492493static void UpdatePowerLevelWii(SDL_Joystick *joystick, Uint8 batteryLevelByte)494{495int percent;496if (batteryLevelByte > 178) {497percent = 100;498} else if (batteryLevelByte > 51) {499percent = 70;500} else if (batteryLevelByte > 13) {501percent = 20;502} else {503percent = 5;504}505SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent);506}507508static void UpdatePowerLevelWiiU(SDL_Joystick *joystick, Uint8 extensionBatteryByte)509{510bool charging = !(extensionBatteryByte & 0x08);511bool pluggedIn = !(extensionBatteryByte & 0x04);512Uint8 batteryLevel = extensionBatteryByte >> 4;513514if (pluggedIn) {515joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;516} else {517joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;518}519520/* Not sure if all Wii U Pro controllers act like this, but on mine521* 4, 3, and 2 are held for about 20 hours each522* 1 is held for about 6 hours523* 0 is held for about 2 hours524* No value above 4 has been observed.525*/526SDL_PowerState state;527int percent;528if (charging) {529state = SDL_POWERSTATE_CHARGING;530} else if (pluggedIn) {531state = SDL_POWERSTATE_CHARGED;532} else {533state = SDL_POWERSTATE_ON_BATTERY;534}535if (batteryLevel >= 4) {536percent = 100;537} else if (batteryLevel == 3) {538percent = 70;539} else if (batteryLevel == 2) {540percent = 40;541} else if (batteryLevel == 1) {542percent = 10;543} else {544percent = 3;545}546SDL_SendJoystickPowerInfo(joystick, state, percent);547}548549static EWiiInputReportIDs GetButtonPacketType(SDL_DriverWii_Context *ctx)550{551switch (ctx->m_eExtensionControllerType) {552case k_eWiiExtensionControllerType_WiiUPro:553return k_eWiiInputReportIDs_ButtonDataD;554case k_eWiiExtensionControllerType_Nunchuk:555case k_eWiiExtensionControllerType_Gamepad:556if (ctx->m_bReportSensors) {557return k_eWiiInputReportIDs_ButtonData5;558} else {559return k_eWiiInputReportIDs_ButtonData2;560}561default:562if (ctx->m_bReportSensors) {563return k_eWiiInputReportIDs_ButtonData5;564} else {565return k_eWiiInputReportIDs_ButtonData0;566}567}568}569570static bool RequestButtonPacketType(SDL_DriverWii_Context *ctx, EWiiInputReportIDs type)571{572Uint8 data[3];573Uint8 tt = (Uint8)ctx->m_bRumbleActive;574575// Continuous reporting off, tt & 4 == 0576if (ENABLE_CONTINUOUS_REPORTING) {577tt |= 4;578}579580data[0] = k_eWiiOutputReportIDs_DataReportingMode;581data[1] = tt;582data[2] = type;583return WriteOutput(ctx, data, sizeof(data), false);584}585586static void ResetButtonPacketType(SDL_DriverWii_Context *ctx)587{588RequestButtonPacketType(ctx, GetButtonPacketType(ctx));589}590591static void InitStickCalibrationData(SDL_DriverWii_Context *ctx)592{593int i;594switch (ctx->m_eExtensionControllerType) {595case k_eWiiExtensionControllerType_WiiUPro:596for (i = 0; i < 4; i++) {597ctx->m_StickCalibrationData[i].min = 1000;598ctx->m_StickCalibrationData[i].max = 3000;599ctx->m_StickCalibrationData[i].center = 0;600ctx->m_StickCalibrationData[i].deadzone = 100;601}602break;603case k_eWiiExtensionControllerType_Gamepad:604for (i = 0; i < 4; i++) {605ctx->m_StickCalibrationData[i].min = i < 2 ? 9 : 5;606ctx->m_StickCalibrationData[i].max = i < 2 ? 54 : 26;607ctx->m_StickCalibrationData[i].center = 0;608ctx->m_StickCalibrationData[i].deadzone = i < 2 ? 4 : 2;609}610break;611case k_eWiiExtensionControllerType_Nunchuk:612for (i = 0; i < 2; i++) {613ctx->m_StickCalibrationData[i].min = 40;614ctx->m_StickCalibrationData[i].max = 215;615ctx->m_StickCalibrationData[i].center = 0;616ctx->m_StickCalibrationData[i].deadzone = 10;617}618break;619default:620break;621}622}623624static void InitializeExtension(SDL_DriverWii_Context *ctx)625{626SendExtensionReset(ctx, true);627InitStickCalibrationData(ctx);628ResetButtonPacketType(ctx);629}630631static void UpdateSlotLED(SDL_DriverWii_Context *ctx)632{633Uint8 leds;634Uint8 data[2];635636// The lowest bit needs to have the rumble status637leds = (Uint8)ctx->m_bRumbleActive;638639if (ctx->m_bPlayerLights) {640// Use the same LED codes as Smash 8-player for 5-7641if (ctx->m_nPlayerIndex == 0 || ctx->m_nPlayerIndex > 3) {642leds |= k_eWiiPlayerLEDs_P1;643}644if (ctx->m_nPlayerIndex == 1 || ctx->m_nPlayerIndex == 4) {645leds |= k_eWiiPlayerLEDs_P2;646}647if (ctx->m_nPlayerIndex == 2 || ctx->m_nPlayerIndex == 5) {648leds |= k_eWiiPlayerLEDs_P3;649}650if (ctx->m_nPlayerIndex == 3 || ctx->m_nPlayerIndex == 6) {651leds |= k_eWiiPlayerLEDs_P4;652}653// Turn on all lights for other player indexes654if (ctx->m_nPlayerIndex < 0 || ctx->m_nPlayerIndex > 6) {655leds |= k_eWiiPlayerLEDs_P1 | k_eWiiPlayerLEDs_P2 | k_eWiiPlayerLEDs_P3 | k_eWiiPlayerLEDs_P4;656}657}658659data[0] = k_eWiiOutputReportIDs_LEDs;660data[1] = leds;661WriteOutput(ctx, data, sizeof(data), false);662}663664static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)665{666SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)userdata;667bool bPlayerLights = SDL_GetStringBoolean(hint, true);668669if (bPlayerLights != ctx->m_bPlayerLights) {670ctx->m_bPlayerLights = bPlayerLights;671672UpdateSlotLED(ctx);673}674}675676static EWiiExtensionControllerType ReadExtensionControllerType(SDL_HIDAPI_Device *device)677{678SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;679EWiiExtensionControllerType eExtensionControllerType = k_eWiiExtensionControllerType_Unknown;680const int MAX_ATTEMPTS = 20;681int attempts = 0;682683// Create enough of a context to read the controller type from the device684for (attempts = 0; attempts < MAX_ATTEMPTS; ++attempts) {685Uint16 extension;686if (SendExtensionIdentify(ctx, true) &&687ParseExtensionIdentifyResponse(ctx, &extension)) {688Uint8 motion_plus_mode = 0;689if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {690motion_plus_mode = (Uint8)(extension >> 8);691}692if (motion_plus_mode || extension == WII_EXTENSION_UNINITIALIZED) {693SendExtensionReset(ctx, true);694if (SendExtensionIdentify(ctx, true)) {695ParseExtensionIdentifyResponse(ctx, &extension);696}697}698699eExtensionControllerType = GetExtensionType(extension);700701// Reset the Motion Plus controller if needed702if (motion_plus_mode) {703ActivateMotionPlusWithMode(ctx, motion_plus_mode);704}705break;706}707}708return eExtensionControllerType;709}710711static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)712{713SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;714715switch (ctx->m_eExtensionControllerType) {716case k_eWiiExtensionControllerType_None:717HIDAPI_SetDeviceName(device, "Nintendo Wii Remote");718break;719case k_eWiiExtensionControllerType_Nunchuk:720HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Nunchuk");721break;722case k_eWiiExtensionControllerType_Gamepad:723HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Classic Controller");724break;725case k_eWiiExtensionControllerType_WiiUPro:726HIDAPI_SetDeviceName(device, "Nintendo Wii U Pro Controller");727break;728default:729HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Unknown Extension");730break;731}732device->guid.data[15] = ctx->m_eExtensionControllerType;733}734735static bool HIDAPI_DriverWii_InitDevice(SDL_HIDAPI_Device *device)736{737SDL_DriverWii_Context *ctx;738739ctx = (SDL_DriverWii_Context *)SDL_calloc(1, sizeof(*ctx));740if (!ctx) {741return false;742}743ctx->device = device;744device->context = ctx;745746if (device->vendor_id == USB_VENDOR_NINTENDO) {747ctx->m_eExtensionControllerType = ReadExtensionControllerType(device);748749UpdateDeviceIdentity(device);750}751return HIDAPI_JoystickConnected(device, NULL);752}753754static int HIDAPI_DriverWii_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)755{756return -1;757}758759static void HIDAPI_DriverWii_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)760{761SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;762763if (!ctx->joystick) {764return;765}766767ctx->m_nPlayerIndex = player_index;768769UpdateSlotLED(ctx);770}771772static bool HIDAPI_DriverWii_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)773{774SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;775776SDL_AssertJoysticksLocked();777778ctx->joystick = joystick;779780InitializeExtension(ctx);781782GetMotionPlusState(ctx, &ctx->m_bMotionPlusPresent, &ctx->m_ucMotionPlusMode);783784if (NeedsPeriodicMotionPlusCheck(ctx, false)) {785SchedulePeriodicMotionPlusCheck(ctx);786}787788if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_None ||789ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) {790SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f);791if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) {792SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 100.0f);793}794795if (ctx->m_bMotionPlusPresent) {796SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 100.0f);797}798}799800// Initialize player index (needed for setting LEDs)801ctx->m_nPlayerIndex = SDL_GetJoystickPlayerIndex(joystick);802ctx->m_bPlayerLights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED, true);803UpdateSlotLED(ctx);804805SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED,806SDL_PlayerLEDHintChanged, ctx);807808// Initialize the joystick capabilities809if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {810joystick->nbuttons = 15;811} else {812// Maximum is Classic Controller + Wiimote813joystick->nbuttons = k_eWiiButtons_Max;814}815joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;816817ctx->m_ulLastInput = SDL_GetTicks();818819return true;820}821822static bool HIDAPI_DriverWii_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)823{824SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;825bool active = (low_frequency_rumble || high_frequency_rumble);826827if (active != ctx->m_bRumbleActive) {828Uint8 data[2];829830data[0] = k_eWiiOutputReportIDs_Rumble;831data[1] = (Uint8)active;832WriteOutput(ctx, data, sizeof(data), false);833834ctx->m_bRumbleActive = active;835}836return true;837}838839static bool HIDAPI_DriverWii_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)840{841return SDL_Unsupported();842}843844static Uint32 HIDAPI_DriverWii_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)845{846return SDL_JOYSTICK_CAP_RUMBLE;847}848849static bool HIDAPI_DriverWii_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)850{851return SDL_Unsupported();852}853854static bool HIDAPI_DriverWii_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)855{856return SDL_Unsupported();857}858859static bool HIDAPI_DriverWii_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)860{861SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;862863if (enabled != ctx->m_bReportSensors) {864ctx->m_bReportSensors = enabled;865866if (ctx->m_bMotionPlusPresent) {867if (enabled) {868ActivateMotionPlus(ctx);869} else {870DeactivateMotionPlus(ctx);871}872}873874ResetButtonPacketType(ctx);875}876return true;877}878879static void PostStickCalibrated(Uint64 timestamp, SDL_Joystick *joystick, StickCalibrationData *calibration, Uint8 axis, Uint16 data)880{881Sint16 value = 0;882if (!calibration->center) {883// Center on first read884calibration->center = data;885return;886}887if (data < calibration->min) {888calibration->min = data;889}890if (data > calibration->max) {891calibration->max = data;892}893if (data < calibration->center - calibration->deadzone) {894Uint16 zero = calibration->center - calibration->deadzone;895Uint16 range = zero - calibration->min;896Uint16 distance = zero - data;897float fvalue = (float)distance / (float)range;898value = (Sint16)(fvalue * SDL_JOYSTICK_AXIS_MIN);899} else if (data > calibration->center + calibration->deadzone) {900Uint16 zero = calibration->center + calibration->deadzone;901Uint16 range = calibration->max - zero;902Uint16 distance = data - zero;903float fvalue = (float)distance / (float)range;904value = (Sint16)(fvalue * SDL_JOYSTICK_AXIS_MAX);905}906if (axis == SDL_GAMEPAD_AXIS_LEFTY || axis == SDL_GAMEPAD_AXIS_RIGHTY) {907if (value) {908value = ~value;909}910}911SDL_SendJoystickAxis(timestamp, joystick, axis, value);912}913914/* Send button data to SDL915*`defs` is a mapping for each bit to which button it represents. 0xFF indicates an unused bit916*`data` is the button data from the controller917*`size` is the number of bytes in `data` and the number of arrays of 8 mappings in `defs`918*`on` is the joystick value to be sent if a bit is on919*`off` is the joystick value to be sent if a bit is off920*/921static void PostPackedButtonData(Uint64 timestamp, SDL_Joystick *joystick, const Uint8 defs[][8], const Uint8 *data, int size, bool on, bool off)922{923int i, j;924925for (i = 0; i < size; i++) {926for (j = 0; j < 8; j++) {927Uint8 button = defs[i][j];928if (button != 0xFF) {929bool down = (data[i] >> j) & 1 ? on : off;930SDL_SendJoystickButton(timestamp, joystick, button, down);931}932}933}934}935936static const Uint8 GAMEPAD_BUTTON_DEFS[3][8] = {937{9380xFF /* Unused */,939SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,940SDL_GAMEPAD_BUTTON_START,941SDL_GAMEPAD_BUTTON_GUIDE,942SDL_GAMEPAD_BUTTON_BACK,943SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,944SDL_GAMEPAD_BUTTON_DPAD_DOWN,945SDL_GAMEPAD_BUTTON_DPAD_RIGHT,946},947{948SDL_GAMEPAD_BUTTON_DPAD_UP,949SDL_GAMEPAD_BUTTON_DPAD_LEFT,9500xFF /* ZR */,951SDL_GAMEPAD_BUTTON_NORTH,952SDL_GAMEPAD_BUTTON_EAST,953SDL_GAMEPAD_BUTTON_WEST,954SDL_GAMEPAD_BUTTON_SOUTH,9550xFF /*ZL*/,956},957{958SDL_GAMEPAD_BUTTON_RIGHT_STICK,959SDL_GAMEPAD_BUTTON_LEFT_STICK,9600xFF /* Charging */,9610xFF /* Plugged In */,9620xFF /* Unused */,9630xFF /* Unused */,9640xFF /* Unused */,9650xFF /* Unused */,966}967};968969static const Uint8 MP_GAMEPAD_BUTTON_DEFS[3][8] = {970{9710xFF /* Unused */,972SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,973SDL_GAMEPAD_BUTTON_START,974SDL_GAMEPAD_BUTTON_GUIDE,975SDL_GAMEPAD_BUTTON_BACK,976SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,977SDL_GAMEPAD_BUTTON_DPAD_DOWN,978SDL_GAMEPAD_BUTTON_DPAD_RIGHT,979},980{9810xFF /* Motion Plus data */,9820xFF /* Motion Plus data */,9830xFF /* ZR */,984SDL_GAMEPAD_BUTTON_NORTH,985SDL_GAMEPAD_BUTTON_EAST,986SDL_GAMEPAD_BUTTON_WEST,987SDL_GAMEPAD_BUTTON_SOUTH,9880xFF /*ZL*/,989},990{991SDL_GAMEPAD_BUTTON_RIGHT_STICK,992SDL_GAMEPAD_BUTTON_LEFT_STICK,9930xFF /* Charging */,9940xFF /* Plugged In */,9950xFF /* Unused */,9960xFF /* Unused */,9970xFF /* Unused */,9980xFF /* Unused */,999}1000};10011002static const Uint8 MP_FIXUP_DPAD_BUTTON_DEFS[2][8] = {1003{1004SDL_GAMEPAD_BUTTON_DPAD_UP,10050xFF,10060xFF,10070xFF,10080xFF,10090xFF,10100xFF,10110xFF,1012},1013{1014SDL_GAMEPAD_BUTTON_DPAD_LEFT,10150xFF,10160xFF,10170xFF,10180xFF,10190xFF,10200xFF,10210xFF,1022}1023};10241025static void HandleWiiUProButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)1026{1027static const Uint8 axes[] = { SDL_GAMEPAD_AXIS_LEFTX, SDL_GAMEPAD_AXIS_RIGHTX, SDL_GAMEPAD_AXIS_LEFTY, SDL_GAMEPAD_AXIS_RIGHTY };1028const Uint8(*buttons)[8] = GAMEPAD_BUTTON_DEFS;1029Uint8 zl, zr;1030int i;10311032if (data->ucNExtensionBytes < 11) {1033return;1034}10351036// Buttons1037PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucExtension + 8, 3, false, true);10381039// Triggers1040zl = data->rgucExtension[9] & 0x80;1041zr = data->rgucExtension[9] & 0x04;1042SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, zl ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);1043SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, zr ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);10441045// Sticks1046for (i = 0; i < 4; i++) {1047Uint16 value = data->rgucExtension[i * 2] | (data->rgucExtension[i * 2 + 1] << 8);1048PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[i], axes[i], value);1049}10501051// Power1052UpdatePowerLevelWiiU(joystick, data->rgucExtension[10]);1053}10541055static void HandleGamepadControllerButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)1056{1057const Uint8(*buttons)[8] = (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) ? MP_GAMEPAD_BUTTON_DEFS : GAMEPAD_BUTTON_DEFS;1058Uint8 lx, ly, rx, ry, zl, zr;10591060if (data->ucNExtensionBytes < 6) {1061return;1062}10631064// Buttons1065PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucExtension + 4, 2, false, true);1066if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) {1067PostPackedButtonData(ctx->timestamp, joystick, MP_FIXUP_DPAD_BUTTON_DEFS, data->rgucExtension, 2, false, true);1068}10691070// Triggers1071zl = data->rgucExtension[5] & 0x80;1072zr = data->rgucExtension[5] & 0x04;1073SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, zl ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);1074SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, zr ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);10751076// Sticks1077if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) {1078lx = data->rgucExtension[0] & 0x3E;1079ly = data->rgucExtension[1] & 0x3E;1080} else {1081lx = data->rgucExtension[0] & 0x3F;1082ly = data->rgucExtension[1] & 0x3F;1083}1084rx = (data->rgucExtension[2] >> 7) | ((data->rgucExtension[1] >> 5) & 0x06) | ((data->rgucExtension[0] >> 3) & 0x18);1085ry = data->rgucExtension[2] & 0x1F;1086PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[0], SDL_GAMEPAD_AXIS_LEFTX, lx);1087PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[1], SDL_GAMEPAD_AXIS_LEFTY, ly);1088PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[2], SDL_GAMEPAD_AXIS_RIGHTX, rx);1089PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[3], SDL_GAMEPAD_AXIS_RIGHTY, ry);1090}10911092static void HandleWiiRemoteButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)1093{1094static const Uint8 buttons[2][8] = {1095{1096k_eWiiButtons_DPad_Left,1097k_eWiiButtons_DPad_Right,1098k_eWiiButtons_DPad_Down,1099k_eWiiButtons_DPad_Up,1100k_eWiiButtons_Plus,11010xFF /* Unused */,11020xFF /* Unused */,11030xFF /* Unused */,1104},1105{1106k_eWiiButtons_Two,1107k_eWiiButtons_One,1108k_eWiiButtons_B,1109k_eWiiButtons_A,1110k_eWiiButtons_Minus,11110xFF /* Unused */,11120xFF /* Unused */,1113k_eWiiButtons_Home,1114}1115};1116if (data->hasBaseButtons) {1117PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucBaseButtons, 2, true, false);1118}1119}11201121static void HandleWiiRemoteButtonDataAsMainController(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)1122{1123/* Wii remote maps really badly to a normal controller1124* Mapped 1 and 2 as X and Y1125* Not going to attempt positional mapping1126*/1127static const Uint8 buttons[2][8] = {1128{1129SDL_GAMEPAD_BUTTON_DPAD_LEFT,1130SDL_GAMEPAD_BUTTON_DPAD_RIGHT,1131SDL_GAMEPAD_BUTTON_DPAD_DOWN,1132SDL_GAMEPAD_BUTTON_DPAD_UP,1133SDL_GAMEPAD_BUTTON_START,11340xFF /* Unused */,11350xFF /* Unused */,11360xFF /* Unused */,1137},1138{1139SDL_GAMEPAD_BUTTON_NORTH,1140SDL_GAMEPAD_BUTTON_WEST,1141SDL_GAMEPAD_BUTTON_SOUTH,1142SDL_GAMEPAD_BUTTON_EAST,1143SDL_GAMEPAD_BUTTON_BACK,11440xFF /* Unused */,11450xFF /* Unused */,1146SDL_GAMEPAD_BUTTON_GUIDE,1147}1148};1149if (data->hasBaseButtons) {1150PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucBaseButtons, 2, true, false);1151}1152}11531154static void HandleNunchuckButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)1155{1156bool c_button, z_button;11571158if (data->ucNExtensionBytes < 6) {1159return;1160}11611162if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_NUNCHUK) {1163c_button = (data->rgucExtension[5] & 0x08) ? false : true;1164z_button = (data->rgucExtension[5] & 0x04) ? false : true;1165} else {1166c_button = (data->rgucExtension[5] & 0x02) ? false : true;1167z_button = (data->rgucExtension[5] & 0x01) ? false : true;1168}1169SDL_SendJoystickButton(ctx->timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, c_button);1170SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, z_button ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN);1171PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[0], SDL_GAMEPAD_AXIS_LEFTX, data->rgucExtension[0]);1172PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[1], SDL_GAMEPAD_AXIS_LEFTY, data->rgucExtension[1]);11731174if (ctx->m_bReportSensors) {1175const float ACCEL_RES_PER_G = 200.0f;1176Sint16 x, y, z;1177float values[3];11781179x = (data->rgucExtension[2] << 2);1180y = (data->rgucExtension[3] << 2);1181z = (data->rgucExtension[4] << 2);11821183if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_NUNCHUK) {1184x |= ((data->rgucExtension[5] >> 3) & 0x02);1185y |= ((data->rgucExtension[5] >> 4) & 0x02);1186z &= ~0x04;1187z |= ((data->rgucExtension[5] >> 5) & 0x06);1188} else {1189x |= ((data->rgucExtension[5] >> 2) & 0x03);1190y |= ((data->rgucExtension[5] >> 4) & 0x03);1191z |= ((data->rgucExtension[5] >> 6) & 0x03);1192}11931194x -= 0x200;1195y -= 0x200;1196z -= 0x200;11971198values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;1199values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;1200values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;1201SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_ACCEL_L, ctx->timestamp, values, 3);1202}1203}12041205static void HandleMotionPlusData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)1206{1207if (ctx->m_bReportSensors) {1208const float GYRO_RES_PER_DEGREE = 8192.0f;1209int x, y, z;1210float values[3];12111212x = (data->rgucExtension[0] | ((data->rgucExtension[3] << 6) & 0xFF00)) - 8192;1213y = (data->rgucExtension[1] | ((data->rgucExtension[4] << 6) & 0xFF00)) - 8192;1214z = (data->rgucExtension[2] | ((data->rgucExtension[5] << 6) & 0xFF00)) - 8192;12151216if (data->rgucExtension[3] & 0x02) {1217// Slow rotation rate: 8192/440 units per deg/s1218x *= 440;1219} else {1220// Fast rotation rate: 8192/2000 units per deg/s1221x *= 2000;1222}1223if (data->rgucExtension[4] & 0x02) {1224// Slow rotation rate: 8192/440 units per deg/s1225y *= 440;1226} else {1227// Fast rotation rate: 8192/2000 units per deg/s1228y *= 2000;1229}1230if (data->rgucExtension[3] & 0x01) {1231// Slow rotation rate: 8192/440 units per deg/s1232z *= 440;1233} else {1234// Fast rotation rate: 8192/2000 units per deg/s1235z *= 2000;1236}12371238values[0] = -((float)z / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;1239values[1] = ((float)x / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;1240values[2] = ((float)y / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;1241SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_GYRO, ctx->timestamp, values, 3);1242}1243}12441245static void HandleWiiRemoteAccelData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)1246{1247const float ACCEL_RES_PER_G = 100.0f;1248Sint16 x, y, z;1249float values[3];12501251if (!ctx->m_bReportSensors) {1252return;1253}12541255x = ((data->rgucAccelerometer[0] << 2) | ((data->rgucBaseButtons[0] >> 5) & 0x03)) - 0x200;1256y = ((data->rgucAccelerometer[1] << 2) | ((data->rgucBaseButtons[1] >> 4) & 0x02)) - 0x200;1257z = ((data->rgucAccelerometer[2] << 2) | ((data->rgucBaseButtons[1] >> 5) & 0x02)) - 0x200;12581259values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;1260values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;1261values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;1262SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_ACCEL, ctx->timestamp, values, 3);1263}12641265static void HandleButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, WiiButtonData *data)1266{1267if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {1268HandleWiiUProButtonData(ctx, joystick, data);1269return;1270}12711272if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE &&1273data->ucNExtensionBytes > 5) {1274if (data->rgucExtension[5] & 0x01) {1275// The data is invalid, possibly during a hotplug1276return;1277}12781279if (data->rgucExtension[4] & 0x01) {1280if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_None) {1281// Something was plugged into the extension port, reinitialize to get new state1282ctx->m_bDisconnected = true;1283}1284} else {1285if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_None) {1286// Something was removed from the extension port, reinitialize to get new state1287ctx->m_bDisconnected = true;1288}1289}12901291if (data->rgucExtension[5] & 0x02) {1292HandleMotionPlusData(ctx, joystick, data);12931294// The extension data is consumed1295data->ucNExtensionBytes = 0;1296}1297}12981299HandleWiiRemoteButtonData(ctx, joystick, data);1300switch (ctx->m_eExtensionControllerType) {1301case k_eWiiExtensionControllerType_Nunchuk:1302HandleNunchuckButtonData(ctx, joystick, data);1303SDL_FALLTHROUGH;1304case k_eWiiExtensionControllerType_None:1305HandleWiiRemoteButtonDataAsMainController(ctx, joystick, data);1306break;1307case k_eWiiExtensionControllerType_Gamepad:1308HandleGamepadControllerButtonData(ctx, joystick, data);1309break;1310default:1311break;1312}1313HandleWiiRemoteAccelData(ctx, joystick, data);1314}13151316static void GetBaseButtons(WiiButtonData *dst, const Uint8 *src)1317{1318SDL_memcpy(dst->rgucBaseButtons, src, 2);1319dst->hasBaseButtons = true;1320}13211322static void GetAccelerometer(WiiButtonData *dst, const Uint8 *src)1323{1324SDL_memcpy(dst->rgucAccelerometer, src, 3);1325dst->hasAccelerometer = true;1326}13271328static void GetExtensionData(WiiButtonData *dst, const Uint8 *src, int size)1329{1330bool valid_data = false;1331int i;13321333if (size > sizeof(dst->rgucExtension)) {1334size = sizeof(dst->rgucExtension);1335}13361337for (i = 0; i < size; ++i) {1338if (src[i] != 0xFF) {1339valid_data = true;1340break;1341}1342}1343if (valid_data) {1344SDL_memcpy(dst->rgucExtension, src, size);1345dst->ucNExtensionBytes = (Uint8)size;1346}1347}13481349static void HandleStatus(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)1350{1351bool hadExtension = ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_None;1352bool hasExtension = (ctx->m_rgucReadBuffer[3] & 2) ? true : false;1353WiiButtonData data;1354SDL_zero(data);1355GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);1356HandleButtonData(ctx, joystick, &data);13571358if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_WiiUPro) {1359// Wii U has separate battery level tracking1360UpdatePowerLevelWii(joystick, ctx->m_rgucReadBuffer[6]);1361}13621363// The report data format has been reset, need to update it1364ResetButtonPacketType(ctx);13651366SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Status update, extension %s", hasExtension ? "CONNECTED" : "DISCONNECTED");13671368/* When Motion Plus is active, we get extension connect/disconnect status1369* through the Motion Plus packets. Otherwise we can use the status here.1370*/1371if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE) {1372/* Check to make sure the Motion Plus extension state hasn't changed,1373* otherwise we'll get extension connect/disconnect status through1374* Motion Plus packets.1375*/1376if (NeedsPeriodicMotionPlusCheck(ctx, true)) {1377ctx->m_ulNextMotionPlusCheck = SDL_GetTicks();1378}13791380} else if (hadExtension != hasExtension) {1381// Reinitialize to get new state1382ctx->m_bDisconnected = true;1383}1384}13851386static void HandleResponse(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)1387{1388EWiiInputReportIDs type = (EWiiInputReportIDs)ctx->m_rgucReadBuffer[0];1389WiiButtonData data;1390SDL_assert(type == k_eWiiInputReportIDs_Acknowledge || type == k_eWiiInputReportIDs_ReadMemory);1391SDL_zero(data);1392GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);1393HandleButtonData(ctx, joystick, &data);13941395switch (ctx->m_eCommState) {1396case k_eWiiCommunicationState_None:1397break;13981399case k_eWiiCommunicationState_CheckMotionPlusStage1:1400case k_eWiiCommunicationState_CheckMotionPlusStage2:1401{1402Uint16 extension = 0;1403if (ParseExtensionIdentifyResponse(ctx, &extension)) {1404if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {1405// Motion Plus is currently active1406SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Motion Plus CONNECTED (stage %d)", ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1 ? 1 : 2);14071408if (!ctx->m_bMotionPlusPresent) {1409// Reinitialize to get new sensor availability1410ctx->m_bDisconnected = true;1411}1412ctx->m_eCommState = k_eWiiCommunicationState_None;14131414} else if (ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1) {1415// Check to see if Motion Plus is present1416ReadRegister(ctx, 0xA600FE, 2, false);14171418ctx->m_eCommState = k_eWiiCommunicationState_CheckMotionPlusStage2;14191420} else {1421// Motion Plus is not present1422SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Motion Plus DISCONNECTED (stage %d)", ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1 ? 1 : 2);14231424if (ctx->m_bMotionPlusPresent) {1425// Reinitialize to get new sensor availability1426ctx->m_bDisconnected = true;1427}1428ctx->m_eCommState = k_eWiiCommunicationState_None;1429}1430}1431} break;1432default:1433// Should never happen1434break;1435}1436}14371438static void HandleButtonPacket(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)1439{1440EWiiInputReportIDs eExpectedReport = GetButtonPacketType(ctx);1441WiiButtonData data;14421443// FIXME: This should see if the data format is compatible rather than equal1444if (eExpectedReport != ctx->m_rgucReadBuffer[0]) {1445SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Resetting report mode to %d", eExpectedReport);1446RequestButtonPacketType(ctx, eExpectedReport);1447}14481449// IR camera data is not supported1450SDL_zero(data);1451switch (ctx->m_rgucReadBuffer[0]) {1452case k_eWiiInputReportIDs_ButtonData0: // 30 BB BB1453GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);1454break;1455case k_eWiiInputReportIDs_ButtonData1: // 31 BB BB AA AA AA1456case k_eWiiInputReportIDs_ButtonData3: // 33 BB BB AA AA AA II II II II II II II II II II II II1457GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);1458GetAccelerometer(&data, ctx->m_rgucReadBuffer + 3);1459break;1460case k_eWiiInputReportIDs_ButtonData2: // 32 BB BB EE EE EE EE EE EE EE EE1461GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);1462GetExtensionData(&data, ctx->m_rgucReadBuffer + 3, 8);1463break;1464case k_eWiiInputReportIDs_ButtonData4: // 34 BB BB EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE1465GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);1466GetExtensionData(&data, ctx->m_rgucReadBuffer + 3, 19);1467break;1468case k_eWiiInputReportIDs_ButtonData5: // 35 BB BB AA AA AA EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE1469GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);1470GetAccelerometer(&data, ctx->m_rgucReadBuffer + 3);1471GetExtensionData(&data, ctx->m_rgucReadBuffer + 6, 16);1472break;1473case k_eWiiInputReportIDs_ButtonData6: // 36 BB BB II II II II II II II II II II EE EE EE EE EE EE EE EE EE1474GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);1475GetExtensionData(&data, ctx->m_rgucReadBuffer + 13, 9);1476break;1477case k_eWiiInputReportIDs_ButtonData7: // 37 BB BB AA AA AA II II II II II II II II II II EE EE EE EE EE EE1478GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);1479GetExtensionData(&data, ctx->m_rgucReadBuffer + 16, 6);1480break;1481case k_eWiiInputReportIDs_ButtonDataD: // 3d EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE1482GetExtensionData(&data, ctx->m_rgucReadBuffer + 1, 21);1483break;1484case k_eWiiInputReportIDs_ButtonDataE:1485case k_eWiiInputReportIDs_ButtonDataF:1486default:1487SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Unsupported button data type %02x", ctx->m_rgucReadBuffer[0]);1488return;1489}1490HandleButtonData(ctx, joystick, &data);1491}14921493static void HandleInput(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)1494{1495EWiiInputReportIDs type = (EWiiInputReportIDs)ctx->m_rgucReadBuffer[0];14961497// Set up for handling input1498ctx->timestamp = SDL_GetTicksNS();14991500if (type == k_eWiiInputReportIDs_Status) {1501HandleStatus(ctx, joystick);1502} else if (type == k_eWiiInputReportIDs_Acknowledge || type == k_eWiiInputReportIDs_ReadMemory) {1503HandleResponse(ctx, joystick);1504} else if (type >= k_eWiiInputReportIDs_ButtonData0 && type <= k_eWiiInputReportIDs_ButtonDataF) {1505HandleButtonPacket(ctx, joystick);1506} else {1507SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Unexpected input packet of type %x", type);1508}1509}15101511static bool HIDAPI_DriverWii_UpdateDevice(SDL_HIDAPI_Device *device)1512{1513SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;1514SDL_Joystick *joystick = NULL;1515int size;1516Uint64 now;15171518if (device->num_joysticks > 0) {1519joystick = SDL_GetJoystickFromID(device->joysticks[0]);1520} else {1521return false;1522}15231524now = SDL_GetTicks();15251526while ((size = ReadInput(ctx)) > 0) {1527if (joystick) {1528HandleInput(ctx, joystick);1529}1530ctx->m_ulLastInput = now;1531}15321533/* Check to see if we've lost connection to the controller.1534* We have continuous reporting enabled, so this should be reliable now.1535*/1536{1537SDL_COMPILE_TIME_ASSERT(ENABLE_CONTINUOUS_REPORTING, ENABLE_CONTINUOUS_REPORTING);1538}1539if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) {1540// Bluetooth may have disconnected, try reopening the controller1541size = -1;1542}15431544if (joystick) {1545// These checks aren't needed on the Wii U Pro Controller1546if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_WiiUPro) {15471548// Check to see if the Motion Plus extension status has changed1549if (ctx->m_ulNextMotionPlusCheck && now >= ctx->m_ulNextMotionPlusCheck) {1550CheckMotionPlusConnection(ctx);1551if (NeedsPeriodicMotionPlusCheck(ctx, false)) {1552SchedulePeriodicMotionPlusCheck(ctx);1553} else {1554ctx->m_ulNextMotionPlusCheck = 0;1555}1556}15571558// Request a status update periodically to make sure our battery value is up to date1559if (!ctx->m_ulLastStatus || now >= (ctx->m_ulLastStatus + STATUS_UPDATE_TIME_MS)) {1560Uint8 data[2];15611562data[0] = k_eWiiOutputReportIDs_StatusRequest;1563data[1] = (Uint8)ctx->m_bRumbleActive;1564WriteOutput(ctx, data, sizeof(data), false);15651566ctx->m_ulLastStatus = now;1567}1568}1569}15701571if (size < 0 || ctx->m_bDisconnected) {1572// Read error, device is disconnected1573HIDAPI_JoystickDisconnected(device, device->joysticks[0]);1574}1575return (size >= 0);1576}15771578static void HIDAPI_DriverWii_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)1579{1580SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;15811582SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED,1583SDL_PlayerLEDHintChanged, ctx);15841585ctx->joystick = NULL;1586}15871588static void HIDAPI_DriverWii_FreeDevice(SDL_HIDAPI_Device *device)1589{1590}15911592SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii = {1593SDL_HINT_JOYSTICK_HIDAPI_WII,1594true,1595HIDAPI_DriverWii_RegisterHints,1596HIDAPI_DriverWii_UnregisterHints,1597HIDAPI_DriverWii_IsEnabled,1598HIDAPI_DriverWii_IsSupportedDevice,1599HIDAPI_DriverWii_InitDevice,1600HIDAPI_DriverWii_GetDevicePlayerIndex,1601HIDAPI_DriverWii_SetDevicePlayerIndex,1602HIDAPI_DriverWii_UpdateDevice,1603HIDAPI_DriverWii_OpenJoystick,1604HIDAPI_DriverWii_RumbleJoystick,1605HIDAPI_DriverWii_RumbleJoystickTriggers,1606HIDAPI_DriverWii_GetJoystickCapabilities,1607HIDAPI_DriverWii_SetJoystickLED,1608HIDAPI_DriverWii_SendJoystickEffect,1609HIDAPI_DriverWii_SetJoystickSensorsEnabled,1610HIDAPI_DriverWii_CloseJoystick,1611HIDAPI_DriverWii_FreeDevice,1612};16131614#endif // SDL_JOYSTICK_HIDAPI_WII16151616#endif // SDL_JOYSTICK_HIDAPI161716181619