Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_switch.c
21520 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/* This driver supports the Nintendo Switch Pro controller.21Code and logic contributed by Valve Corporation under the SDL zlib license.22*/23#include "SDL_internal.h"2425#ifdef SDL_JOYSTICK_HIDAPI2627#include "../../SDL_hints_c.h"28#include "../SDL_sysjoystick.h"29#include "SDL_hidapijoystick_c.h"30#include "SDL_hidapi_rumble.h"31#include "SDL_hidapi_nintendo.h"3233#ifdef SDL_JOYSTICK_HIDAPI_SWITCH3435// Define this if you want to log all packets from the controller36// #define DEBUG_SWITCH_PROTOCOL3738// Define this to get log output for rumble logic39// #define DEBUG_RUMBLE4041/* The initialization sequence doesn't appear to work correctly on Windows unless42the reads and writes are on the same thread.4344... and now I can't reproduce this, so I'm leaving it in, but disabled for now.45*/46// #define SWITCH_SYNCHRONOUS_WRITES4748/* How often you can write rumble commands to the controller.49If you send commands more frequently than this, you can turn off the controller50in Bluetooth mode, or the motors can miss the command in USB mode.51*/52#define RUMBLE_WRITE_FREQUENCY_MS 305354// How often you have to refresh a long duration rumble to keep the motors running55#define RUMBLE_REFRESH_FREQUENCY_MS 505657#define SWITCH_GYRO_SCALE 14.2842f58#define SWITCH_ACCEL_SCALE 4096.f5960#define SWITCH_GYRO_SCALE_MULT 936.0f61#define SWITCH_ACCEL_SCALE_MULT 4.0f6263enum64{65SDL_GAMEPAD_BUTTON_SWITCH_SHARE = 11,66SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1,67SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1,68SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2,69SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2,70SDL_GAMEPAD_NUM_SWITCH_BUTTONS,71};7273typedef enum74{75k_eSwitchInputReportIDs_SubcommandReply = 0x21,76k_eSwitchInputReportIDs_FullControllerState = 0x30,77k_eSwitchInputReportIDs_FullControllerAndMcuState = 0x31,78k_eSwitchInputReportIDs_SimpleControllerState = 0x3F,79k_eSwitchInputReportIDs_CommandAck = 0x81,80} ESwitchInputReportIDs;8182typedef enum83{84k_eSwitchOutputReportIDs_RumbleAndSubcommand = 0x01,85k_eSwitchOutputReportIDs_Rumble = 0x10,86k_eSwitchOutputReportIDs_Proprietary = 0x80,87} ESwitchOutputReportIDs;8889typedef enum90{91k_eSwitchSubcommandIDs_BluetoothManualPair = 0x01,92k_eSwitchSubcommandIDs_RequestDeviceInfo = 0x02,93k_eSwitchSubcommandIDs_SetInputReportMode = 0x03,94k_eSwitchSubcommandIDs_SetHCIState = 0x06,95k_eSwitchSubcommandIDs_SPIFlashRead = 0x10,96k_eSwitchSubcommandIDs_SetPlayerLights = 0x30,97k_eSwitchSubcommandIDs_SetHomeLight = 0x38,98k_eSwitchSubcommandIDs_EnableIMU = 0x40,99k_eSwitchSubcommandIDs_SetIMUSensitivity = 0x41,100k_eSwitchSubcommandIDs_EnableVibration = 0x48,101} ESwitchSubcommandIDs;102103typedef enum104{105k_eSwitchProprietaryCommandIDs_Status = 0x01,106k_eSwitchProprietaryCommandIDs_Handshake = 0x02,107k_eSwitchProprietaryCommandIDs_HighSpeed = 0x03,108k_eSwitchProprietaryCommandIDs_ForceUSB = 0x04,109k_eSwitchProprietaryCommandIDs_ClearUSB = 0x05,110k_eSwitchProprietaryCommandIDs_ResetMCU = 0x06,111} ESwitchProprietaryCommandIDs;112113#define k_unSwitchOutputPacketDataLength 49114#define k_unSwitchMaxOutputPacketLength 64115#define k_unSwitchBluetoothPacketLength k_unSwitchOutputPacketDataLength116#define k_unSwitchUSBPacketLength k_unSwitchMaxOutputPacketLength117118#define k_unSPIStickFactoryCalibrationStartOffset 0x603D119#define k_unSPIStickFactoryCalibrationEndOffset 0x604E120#define k_unSPIStickFactoryCalibrationLength (k_unSPIStickFactoryCalibrationEndOffset - k_unSPIStickFactoryCalibrationStartOffset + 1)121122#define k_unSPIStickUserCalibrationStartOffset 0x8010123#define k_unSPIStickUserCalibrationEndOffset 0x8025124#define k_unSPIStickUserCalibrationLength (k_unSPIStickUserCalibrationEndOffset - k_unSPIStickUserCalibrationStartOffset + 1)125126#define k_unSPIIMUScaleStartOffset 0x6020127#define k_unSPIIMUScaleEndOffset 0x6037128#define k_unSPIIMUScaleLength (k_unSPIIMUScaleEndOffset - k_unSPIIMUScaleStartOffset + 1)129130#define k_unSPIIMUUserScaleStartOffset 0x8026131#define k_unSPIIMUUserScaleEndOffset 0x8039132#define k_unSPIIMUUserScaleLength (k_unSPIIMUUserScaleEndOffset - k_unSPIIMUUserScaleStartOffset + 1)133134#pragma pack(1)135typedef struct136{137Uint8 rgucButtons[2];138Uint8 ucStickHat;139Uint8 rgucJoystickLeft[2];140Uint8 rgucJoystickRight[2];141} SwitchInputOnlyControllerStatePacket_t;142143typedef struct144{145Uint8 rgucButtons[2];146Uint8 ucStickHat;147Sint16 sJoystickLeft[2];148Sint16 sJoystickRight[2];149} SwitchSimpleStatePacket_t;150151typedef struct152{153Uint8 ucCounter;154Uint8 ucBatteryAndConnection;155Uint8 rgucButtons[3];156Uint8 rgucJoystickLeft[3];157Uint8 rgucJoystickRight[3];158Uint8 ucVibrationCode;159} SwitchControllerStatePacket_t;160161typedef struct162{163Sint16 sAccelX;164Sint16 sAccelY;165Sint16 sAccelZ;166167Sint16 sGyroX;168Sint16 sGyroY;169Sint16 sGyroZ;170} SwitchControllerIMUState_t;171172typedef struct173{174SwitchControllerStatePacket_t controllerState;175SwitchControllerIMUState_t imuState[3];176} SwitchStatePacket_t;177178typedef struct179{180Uint32 unAddress;181Uint8 ucLength;182} SwitchSPIOpData_t;183184typedef struct185{186SwitchControllerStatePacket_t m_controllerState;187188Uint8 ucSubcommandAck;189Uint8 ucSubcommandID;190191#define k_unSubcommandDataBytes 35192union193{194Uint8 rgucSubcommandData[k_unSubcommandDataBytes];195196struct197{198SwitchSPIOpData_t opData;199Uint8 rgucReadData[k_unSubcommandDataBytes - sizeof(SwitchSPIOpData_t)];200} spiReadData;201202struct203{204Uint8 rgucFirmwareVersion[2];205Uint8 ucDeviceType;206Uint8 ucFiller1;207Uint8 rgucMACAddress[6];208Uint8 ucFiller2;209Uint8 ucColorLocation;210} deviceInfo;211212struct213{214SwitchSPIOpData_t opData;215Uint8 rgucLeftCalibration[9];216Uint8 rgucRightCalibration[9];217} stickFactoryCalibration;218219struct220{221SwitchSPIOpData_t opData;222Uint8 rgucLeftMagic[2];223Uint8 rgucLeftCalibration[9];224Uint8 rgucRightMagic[2];225Uint8 rgucRightCalibration[9];226} stickUserCalibration;227};228} SwitchSubcommandInputPacket_t;229230typedef struct231{232Uint8 ucPacketType;233Uint8 ucCommandID;234Uint8 ucFiller;235236Uint8 ucDeviceType;237Uint8 rgucMACAddress[6];238} SwitchProprietaryStatusPacket_t;239240typedef struct241{242Uint8 rgucData[4];243} SwitchRumbleData_t;244245typedef struct246{247Uint8 ucPacketType;248Uint8 ucPacketNumber;249SwitchRumbleData_t rumbleData[2];250} SwitchCommonOutputPacket_t;251252typedef struct253{254SwitchCommonOutputPacket_t commonData;255256Uint8 ucSubcommandID;257Uint8 rgucSubcommandData[k_unSwitchOutputPacketDataLength - sizeof(SwitchCommonOutputPacket_t) - 1];258} SwitchSubcommandOutputPacket_t;259260typedef struct261{262Uint8 ucPacketType;263Uint8 ucProprietaryID;264265Uint8 rgucProprietaryData[k_unSwitchOutputPacketDataLength - 1 - 1];266} SwitchProprietaryOutputPacket_t;267#pragma pack()268269/* Enhanced report hint mode:270* "0": enhanced features are never used271* "1": enhanced features are always used272* "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.273*/274typedef enum275{276SWITCH_ENHANCED_REPORT_HINT_OFF,277SWITCH_ENHANCED_REPORT_HINT_ON,278SWITCH_ENHANCED_REPORT_HINT_AUTO279} HIDAPI_Switch_EnhancedReportHint;280281typedef struct282{283SDL_HIDAPI_Device *device;284SDL_Joystick *joystick;285bool m_bInputOnly;286bool m_bUseButtonLabels;287bool m_bPlayerLights;288int m_nPlayerIndex;289bool m_bSyncWrite;290int m_nMaxWriteAttempts;291ESwitchDeviceInfoControllerType m_eControllerType;292Uint8 m_nInitialInputMode;293Uint8 m_nCurrentInputMode;294Uint8 m_rgucMACAddress[6];295Uint8 m_nCommandNumber;296HIDAPI_Switch_EnhancedReportHint m_eEnhancedReportHint;297bool m_bEnhancedMode;298bool m_bEnhancedModeAvailable;299SwitchCommonOutputPacket_t m_RumblePacket;300Uint8 m_rgucReadBuffer[k_unSwitchMaxOutputPacketLength];301bool m_bRumbleActive;302Uint64 m_ulRumbleSent;303bool m_bRumblePending;304bool m_bRumbleZeroPending;305Uint32 m_unRumblePending;306bool m_bSensorsSupported;307bool m_bReportSensors;308bool m_bHasSensorData;309Uint64 m_ulLastInput;310Uint64 m_ulLastIMUReset;311Uint64 m_ulIMUSampleTimestampNS;312Uint32 m_unIMUSamples;313Uint64 m_ulIMUUpdateIntervalNS;314Uint64 m_ulTimestampNS;315bool m_bVerticalMode;316317SwitchInputOnlyControllerStatePacket_t m_lastInputOnlyState;318SwitchSimpleStatePacket_t m_lastSimpleState;319SwitchStatePacket_t m_lastFullState;320321struct StickCalibrationData322{323struct324{325Sint16 sCenter;326Sint16 sMin;327Sint16 sMax;328} axis[2];329} m_StickCalData[2];330331struct StickExtents332{333struct334{335Sint16 sMin;336Sint16 sMax;337} axis[2];338} m_StickExtents[2], m_SimpleStickExtents[2];339340struct IMUScaleData341{342float fAccelScaleX;343float fAccelScaleY;344float fAccelScaleZ;345346float fGyroScaleX;347float fGyroScaleY;348float fGyroScaleZ;349} m_IMUScaleData;350} SDL_DriverSwitch_Context;351352static int ReadInput(SDL_DriverSwitch_Context *ctx)353{354int result;355356// Make sure we don't try to read at the same time a write is happening357if (SDL_GetAtomicInt(&ctx->device->rumble_pending) > 0) {358return 0;359}360361result = SDL_hid_read_timeout(ctx->device->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0);362363// See if we can guess the initial input mode364if (result > 0 && !ctx->m_bInputOnly && !ctx->m_nInitialInputMode) {365switch (ctx->m_rgucReadBuffer[0]) {366case k_eSwitchInputReportIDs_FullControllerState:367case k_eSwitchInputReportIDs_FullControllerAndMcuState:368case k_eSwitchInputReportIDs_SimpleControllerState:369ctx->m_nInitialInputMode = ctx->m_rgucReadBuffer[0];370break;371default:372break;373}374}375return result;376}377378static int WriteOutput(SDL_DriverSwitch_Context *ctx, const Uint8 *data, int size)379{380#ifdef SWITCH_SYNCHRONOUS_WRITES381return SDL_hid_write(ctx->device->dev, data, size);382#else383// Use the rumble thread for general asynchronous writes384if (!SDL_HIDAPI_LockRumble()) {385return -1;386}387return SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size);388#endif // SWITCH_SYNCHRONOUS_WRITES389}390391static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID, const Uint8 *pBuf, Uint8 ucLen)392{393// Average response time for messages is ~30ms394Uint64 endTicks = SDL_GetTicks() + 100;395396int nRead = 0;397while ((nRead = ReadInput(ctx)) != -1) {398if (nRead > 0) {399if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) {400SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[1];401if (reply->ucSubcommandID != expectedID || !(reply->ucSubcommandAck & 0x80)) {402continue;403}404if (reply->ucSubcommandID == k_eSwitchSubcommandIDs_SPIFlashRead) {405SDL_assert(ucLen == sizeof(reply->spiReadData.opData));406if (SDL_memcmp(&reply->spiReadData.opData, pBuf, ucLen) != 0) {407// This was a reply for another SPI read command408continue;409}410}411return reply;412}413} else {414SDL_Delay(1);415}416417if (SDL_GetTicks() >= endTicks) {418break;419}420}421return NULL;422}423424static bool ReadProprietaryReply(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs expectedID)425{426// Average response time for messages is ~30ms427Uint64 endTicks = SDL_GetTicks() + 100;428429int nRead = 0;430while ((nRead = ReadInput(ctx)) != -1) {431if (nRead > 0) {432if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_CommandAck && ctx->m_rgucReadBuffer[1] == expectedID) {433return true;434}435} else {436SDL_Delay(1);437}438439if (SDL_GetTicks() >= endTicks) {440break;441}442}443return false;444}445446static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, const Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandOutputPacket_t *outPacket)447{448SDL_memset(outPacket, 0, sizeof(*outPacket));449450outPacket->commonData.ucPacketType = k_eSwitchOutputReportIDs_RumbleAndSubcommand;451outPacket->commonData.ucPacketNumber = ctx->m_nCommandNumber;452453SDL_memcpy(outPacket->commonData.rumbleData, ctx->m_RumblePacket.rumbleData, sizeof(ctx->m_RumblePacket.rumbleData));454455outPacket->ucSubcommandID = ucCommandID;456if (pBuf) {457SDL_memcpy(outPacket->rgucSubcommandData, pBuf, ucLen);458}459460ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF;461}462463static bool WritePacket(SDL_DriverSwitch_Context *ctx, void *pBuf, Uint8 ucLen)464{465Uint8 rgucBuf[k_unSwitchMaxOutputPacketLength];466const size_t unWriteSize = ctx->device->is_bluetooth ? k_unSwitchBluetoothPacketLength : k_unSwitchUSBPacketLength;467468if (ucLen > k_unSwitchOutputPacketDataLength) {469return false;470}471472if (ucLen < unWriteSize) {473SDL_memcpy(rgucBuf, pBuf, ucLen);474SDL_memset(rgucBuf + ucLen, 0, unWriteSize - ucLen);475pBuf = rgucBuf;476ucLen = (Uint8)unWriteSize;477}478if (ctx->m_bSyncWrite) {479return SDL_hid_write(ctx->device->dev, (Uint8 *)pBuf, ucLen) >= 0;480} else {481return WriteOutput(ctx, (Uint8 *)pBuf, ucLen) >= 0;482}483}484485static bool WriteSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, const Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandInputPacket_t **ppReply)486{487SwitchSubcommandInputPacket_t *reply = NULL;488int nTries;489490for (nTries = 1; !reply && nTries <= ctx->m_nMaxWriteAttempts; ++nTries) {491SwitchSubcommandOutputPacket_t commandPacket;492ConstructSubcommand(ctx, ucCommandID, pBuf, ucLen, &commandPacket);493494if (!WritePacket(ctx, &commandPacket, sizeof(commandPacket))) {495continue;496}497498reply = ReadSubcommandReply(ctx, ucCommandID, pBuf, ucLen);499}500501if (ppReply) {502*ppReply = reply;503}504return reply != NULL;505}506507static bool WriteProprietary(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs ucCommand, Uint8 *pBuf, Uint8 ucLen, bool waitForReply)508{509int nTries;510511for (nTries = 1; nTries <= ctx->m_nMaxWriteAttempts; ++nTries) {512SwitchProprietaryOutputPacket_t packet;513514if ((!pBuf && ucLen > 0) || ucLen > sizeof(packet.rgucProprietaryData)) {515return false;516}517518SDL_zero(packet);519packet.ucPacketType = k_eSwitchOutputReportIDs_Proprietary;520packet.ucProprietaryID = ucCommand;521if (pBuf) {522SDL_memcpy(packet.rgucProprietaryData, pBuf, ucLen);523}524525if (!WritePacket(ctx, &packet, sizeof(packet))) {526continue;527}528529if (!waitForReply || ReadProprietaryReply(ctx, ucCommand)) {530// SDL_Log("Succeeded%s after %d tries", ctx->m_bSyncWrite ? " (sync)" : "", nTries);531return true;532}533}534// SDL_Log("Failed%s after %d tries", ctx->m_bSyncWrite ? " (sync)" : "", nTries);535return false;536}537538static Uint8 EncodeRumbleHighAmplitude(Uint16 amplitude)539{540/* More information about these values can be found here:541* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md542*/543Uint16 hfa[101][2] = { { 0, 0x0 }, { 514, 0x2 }, { 775, 0x4 }, { 921, 0x6 }, { 1096, 0x8 }, { 1303, 0x0a }, { 1550, 0x0c }, { 1843, 0x0e }, { 2192, 0x10 }, { 2606, 0x12 }, { 3100, 0x14 }, { 3686, 0x16 }, { 4383, 0x18 }, { 5213, 0x1a }, { 6199, 0x1c }, { 7372, 0x1e }, { 7698, 0x20 }, { 8039, 0x22 }, { 8395, 0x24 }, { 8767, 0x26 }, { 9155, 0x28 }, { 9560, 0x2a }, { 9984, 0x2c }, { 10426, 0x2e }, { 10887, 0x30 }, { 11369, 0x32 }, { 11873, 0x34 }, { 12398, 0x36 }, { 12947, 0x38 }, { 13520, 0x3a }, { 14119, 0x3c }, { 14744, 0x3e }, { 15067, 0x40 }, { 15397, 0x42 }, { 15734, 0x44 }, { 16079, 0x46 }, { 16431, 0x48 }, { 16790, 0x4a }, { 17158, 0x4c }, { 17534, 0x4e }, { 17918, 0x50 }, { 18310, 0x52 }, { 18711, 0x54 }, { 19121, 0x56 }, { 19540, 0x58 }, { 19967, 0x5a }, { 20405, 0x5c }, { 20851, 0x5e }, { 21308, 0x60 }, { 21775, 0x62 }, { 22251, 0x64 }, { 22739, 0x66 }, { 23236, 0x68 }, { 23745, 0x6a }, { 24265, 0x6c }, { 24797, 0x6e }, { 25340, 0x70 }, { 25894, 0x72 }, { 26462, 0x74 }, { 27041, 0x76 }, { 27633, 0x78 }, { 28238, 0x7a }, { 28856, 0x7c }, { 29488, 0x7e }, { 30134, 0x80 }, { 30794, 0x82 }, { 31468, 0x84 }, { 32157, 0x86 }, { 32861, 0x88 }, { 33581, 0x8a }, { 34316, 0x8c }, { 35068, 0x8e }, { 35836, 0x90 }, { 36620, 0x92 }, { 37422, 0x94 }, { 38242, 0x96 }, { 39079, 0x98 }, { 39935, 0x9a }, { 40809, 0x9c }, { 41703, 0x9e }, { 42616, 0xa0 }, { 43549, 0xa2 }, { 44503, 0xa4 }, { 45477, 0xa6 }, { 46473, 0xa8 }, { 47491, 0xaa }, { 48531, 0xac }, { 49593, 0xae }, { 50679, 0xb0 }, { 51789, 0xb2 }, { 52923, 0xb4 }, { 54082, 0xb6 }, { 55266, 0xb8 }, { 56476, 0xba }, { 57713, 0xbc }, { 58977, 0xbe }, { 60268, 0xc0 }, { 61588, 0xc2 }, { 62936, 0xc4 }, { 64315, 0xc6 }, { 65535, 0xc8 } };544int index = 0;545for (; index < 101; index++) {546if (amplitude <= hfa[index][0]) {547return (Uint8)hfa[index][1];548}549}550return (Uint8)hfa[100][1];551}552553static Uint16 EncodeRumbleLowAmplitude(Uint16 amplitude)554{555/* More information about these values can be found here:556* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md557*/558Uint16 lfa[101][2] = { { 0, 0x0040 }, { 514, 0x8040 }, { 775, 0x0041 }, { 921, 0x8041 }, { 1096, 0x0042 }, { 1303, 0x8042 }, { 1550, 0x0043 }, { 1843, 0x8043 }, { 2192, 0x0044 }, { 2606, 0x8044 }, { 3100, 0x0045 }, { 3686, 0x8045 }, { 4383, 0x0046 }, { 5213, 0x8046 }, { 6199, 0x0047 }, { 7372, 0x8047 }, { 7698, 0x0048 }, { 8039, 0x8048 }, { 8395, 0x0049 }, { 8767, 0x8049 }, { 9155, 0x004a }, { 9560, 0x804a }, { 9984, 0x004b }, { 10426, 0x804b }, { 10887, 0x004c }, { 11369, 0x804c }, { 11873, 0x004d }, { 12398, 0x804d }, { 12947, 0x004e }, { 13520, 0x804e }, { 14119, 0x004f }, { 14744, 0x804f }, { 15067, 0x0050 }, { 15397, 0x8050 }, { 15734, 0x0051 }, { 16079, 0x8051 }, { 16431, 0x0052 }, { 16790, 0x8052 }, { 17158, 0x0053 }, { 17534, 0x8053 }, { 17918, 0x0054 }, { 18310, 0x8054 }, { 18711, 0x0055 }, { 19121, 0x8055 }, { 19540, 0x0056 }, { 19967, 0x8056 }, { 20405, 0x0057 }, { 20851, 0x8057 }, { 21308, 0x0058 }, { 21775, 0x8058 }, { 22251, 0x0059 }, { 22739, 0x8059 }, { 23236, 0x005a }, { 23745, 0x805a }, { 24265, 0x005b }, { 24797, 0x805b }, { 25340, 0x005c }, { 25894, 0x805c }, { 26462, 0x005d }, { 27041, 0x805d }, { 27633, 0x005e }, { 28238, 0x805e }, { 28856, 0x005f }, { 29488, 0x805f }, { 30134, 0x0060 }, { 30794, 0x8060 }, { 31468, 0x0061 }, { 32157, 0x8061 }, { 32861, 0x0062 }, { 33581, 0x8062 }, { 34316, 0x0063 }, { 35068, 0x8063 }, { 35836, 0x0064 }, { 36620, 0x8064 }, { 37422, 0x0065 }, { 38242, 0x8065 }, { 39079, 0x0066 }, { 39935, 0x8066 }, { 40809, 0x0067 }, { 41703, 0x8067 }, { 42616, 0x0068 }, { 43549, 0x8068 }, { 44503, 0x0069 }, { 45477, 0x8069 }, { 46473, 0x006a }, { 47491, 0x806a }, { 48531, 0x006b }, { 49593, 0x806b }, { 50679, 0x006c }, { 51789, 0x806c }, { 52923, 0x006d }, { 54082, 0x806d }, { 55266, 0x006e }, { 56476, 0x806e }, { 57713, 0x006f }, { 58977, 0x806f }, { 60268, 0x0070 }, { 61588, 0x8070 }, { 62936, 0x0071 }, { 64315, 0x8071 }, { 65535, 0x0072 } };559int index = 0;560for (; index < 101; index++) {561if (amplitude <= lfa[index][0]) {562return lfa[index][1];563}564}565return lfa[100][1];566}567568static void SetNeutralRumble(SwitchRumbleData_t *pRumble)569{570pRumble->rgucData[0] = 0x00;571pRumble->rgucData[1] = 0x01;572pRumble->rgucData[2] = 0x40;573pRumble->rgucData[3] = 0x40;574}575576static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp)577{578if (ucHighFreqAmp > 0 || usLowFreqAmp > 0) {579// High-band frequency and low-band amplitude are actually nine-bits each so they580// take a bit from the high-band amplitude and low-band frequency bytes respectively581pRumble->rgucData[0] = usHighFreq & 0xFF;582pRumble->rgucData[1] = ucHighFreqAmp | ((usHighFreq >> 8) & 0x01);583584pRumble->rgucData[2] = ucLowFreq | ((usLowFreqAmp >> 8) & 0x80);585pRumble->rgucData[3] = usLowFreqAmp & 0xFF;586587#ifdef DEBUG_RUMBLE588SDL_Log("Freq: %.2X %.2X %.2X, Amp: %.2X %.2X %.2X",589usHighFreq & 0xFF, ((usHighFreq >> 8) & 0x01), ucLowFreq,590ucHighFreqAmp, ((usLowFreqAmp >> 8) & 0x80), usLowFreqAmp & 0xFF);591#endif592} else {593SetNeutralRumble(pRumble);594}595}596597static bool WriteRumble(SDL_DriverSwitch_Context *ctx)598{599/* Write into m_RumblePacket rather than a temporary buffer to allow the current rumble state600* to be retained for subsequent rumble or subcommand packets sent to the controller601*/602ctx->m_RumblePacket.ucPacketType = k_eSwitchOutputReportIDs_Rumble;603ctx->m_RumblePacket.ucPacketNumber = ctx->m_nCommandNumber;604ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF;605606// Refresh the rumble state periodically607ctx->m_ulRumbleSent = SDL_GetTicks();608609return WritePacket(ctx, (Uint8 *)&ctx->m_RumblePacket, sizeof(ctx->m_RumblePacket));610}611612static ESwitchDeviceInfoControllerType CalculateControllerType(SDL_DriverSwitch_Context *ctx, ESwitchDeviceInfoControllerType eControllerType)613{614SDL_HIDAPI_Device *device = ctx->device;615616// The N64 controller reports as a Pro controller over USB617if (eControllerType == k_eSwitchDeviceInfoControllerType_ProController &&618device->product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) {619eControllerType = k_eSwitchDeviceInfoControllerType_N64;620}621622if (eControllerType == k_eSwitchDeviceInfoControllerType_Unknown) {623// This might be a Joy-Con that's missing from a charging grip slot624if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) {625if (device->interface_number == 1) {626eControllerType = k_eSwitchDeviceInfoControllerType_JoyConLeft;627} else {628eControllerType = k_eSwitchDeviceInfoControllerType_JoyConRight;629}630}631}632return eControllerType;633}634635static bool BReadDeviceInfo(SDL_DriverSwitch_Context *ctx)636{637SwitchSubcommandInputPacket_t *reply = NULL;638639if (ctx->device->is_bluetooth) {640if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_RequestDeviceInfo, NULL, 0, &reply)) {641// Byte 2: Controller ID (1=LJC, 2=RJC, 3=Pro)642ctx->m_eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)reply->deviceInfo.ucDeviceType);643644// Bytes 4-9: MAC address (big-endian)645SDL_memcpy(ctx->m_rgucMACAddress, reply->deviceInfo.rgucMACAddress, sizeof(ctx->m_rgucMACAddress));646647return true;648}649} else {650if (WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Status, NULL, 0, true)) {651SwitchProprietaryStatusPacket_t *status = (SwitchProprietaryStatusPacket_t *)&ctx->m_rgucReadBuffer[0];652size_t i;653654ctx->m_eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)status->ucDeviceType);655656for (i = 0; i < sizeof(ctx->m_rgucMACAddress); ++i) {657ctx->m_rgucMACAddress[i] = status->rgucMACAddress[sizeof(ctx->m_rgucMACAddress) - i - 1];658}659660return true;661}662}663return false;664}665666static bool BTrySetupUSB(SDL_DriverSwitch_Context *ctx)667{668/* We have to send a connection handshake to the controller when communicating over USB669* before we're able to send it other commands. Luckily this command is not supported670* over Bluetooth, so we can use the controller's lack of response as a way to671* determine if the connection is over USB or Bluetooth672*/673if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, true)) {674return false;675}676if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_HighSpeed, NULL, 0, true)) {677// The 8BitDo M30 and SF30 Pro don't respond to this command, but otherwise work correctly678// return false;679}680if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, true)) {681// This fails on the right Joy-Con when plugged into the charging grip682// return false;683}684if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false)) {685return false;686}687return true;688}689690static bool SetVibrationEnabled(SDL_DriverSwitch_Context *ctx, Uint8 enabled)691{692return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_EnableVibration, &enabled, sizeof(enabled), NULL);693}694static bool SetInputMode(SDL_DriverSwitch_Context *ctx, Uint8 input_mode)695{696#ifdef FORCE_SIMPLE_REPORTS697input_mode = k_eSwitchInputReportIDs_SimpleControllerState;698#endif699#ifdef FORCE_FULL_REPORTS700input_mode = k_eSwitchInputReportIDs_FullControllerState;701#endif702703if (input_mode == ctx->m_nCurrentInputMode) {704return true;705} else {706ctx->m_nCurrentInputMode = input_mode;707708return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetInputReportMode, &input_mode, sizeof(input_mode), NULL);709}710}711712static bool SetHomeLED(SDL_DriverSwitch_Context *ctx, Uint8 brightness)713{714Uint8 ucLedIntensity = 0;715Uint8 rgucBuffer[4];716717if (brightness > 0) {718if (brightness < 65) {719ucLedIntensity = (brightness + 5) / 10;720} else {721ucLedIntensity = (Uint8)SDL_ceilf(0xF * SDL_powf((float)brightness / 100.f, 2.13f));722}723}724725rgucBuffer[0] = (0x0 << 4) | 0x1; // 0 mini cycles (besides first), cycle duration 8ms726rgucBuffer[1] = ((ucLedIntensity & 0xF) << 4) | 0x0; // LED start intensity (0x0-0xF), 0 cycles (LED stays on at start intensity after first cycle)727rgucBuffer[2] = ((ucLedIntensity & 0xF) << 4) | 0x0; // First cycle LED intensity, 0x0 intensity for second cycle728rgucBuffer[3] = (0x0 << 4) | 0x0; // 8ms fade transition to first cycle, 8ms first cycle LED duration729730return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetHomeLight, rgucBuffer, sizeof(rgucBuffer), NULL);731}732733static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)734{735SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata;736737if (hint && *hint) {738int value;739740if (SDL_strchr(hint, '.') != NULL) {741value = (int)(100.0f * SDL_atof(hint));742if (value > 255) {743value = 255;744}745} else if (SDL_GetStringBoolean(hint, true)) {746value = 100;747} else {748value = 0;749}750SetHomeLED(ctx, (Uint8)value);751}752}753754static void UpdateSlotLED(SDL_DriverSwitch_Context *ctx)755{756if (!ctx->m_bInputOnly) {757Uint8 led_data = 0;758759if (ctx->m_bPlayerLights && ctx->m_nPlayerIndex >= 0) {760led_data = (1 << (ctx->m_nPlayerIndex % 4));761}762WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetPlayerLights, &led_data, sizeof(led_data), NULL);763}764}765766static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)767{768SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata;769bool bPlayerLights = SDL_GetStringBoolean(hint, true);770771if (bPlayerLights != ctx->m_bPlayerLights) {772ctx->m_bPlayerLights = bPlayerLights;773774UpdateSlotLED(ctx);775HIDAPI_UpdateDeviceProperties(ctx->device);776}777}778779static void GetInitialInputMode(SDL_DriverSwitch_Context *ctx)780{781if (!ctx->m_nInitialInputMode) {782// This will set the initial input mode if it can783ReadInput(ctx);784}785}786787static Uint8 GetDefaultInputMode(SDL_DriverSwitch_Context *ctx)788{789Uint8 input_mode;790791// Determine the desired input mode792if (ctx->m_nInitialInputMode) {793input_mode = ctx->m_nInitialInputMode;794} else {795if (ctx->device->is_bluetooth) {796input_mode = k_eSwitchInputReportIDs_SimpleControllerState;797} else {798input_mode = k_eSwitchInputReportIDs_FullControllerState;799}800}801802switch (ctx->m_eEnhancedReportHint) {803case SWITCH_ENHANCED_REPORT_HINT_OFF:804input_mode = k_eSwitchInputReportIDs_SimpleControllerState;805break;806case SWITCH_ENHANCED_REPORT_HINT_ON:807if (input_mode == k_eSwitchInputReportIDs_SimpleControllerState) {808input_mode = k_eSwitchInputReportIDs_FullControllerState;809}810break;811case SWITCH_ENHANCED_REPORT_HINT_AUTO:812/* Joy-Con controllers switch their thumbsticks into D-pad mode in simple mode,813* so let's enable full controller state for them.814*/815if (ctx->device->vendor_id == USB_VENDOR_NINTENDO &&816(ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT ||817ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT)) {818input_mode = k_eSwitchInputReportIDs_FullControllerState;819}820break;821}822823// Wired controllers break if they are put into simple controller state824if (input_mode == k_eSwitchInputReportIDs_SimpleControllerState &&825!ctx->device->is_bluetooth) {826input_mode = k_eSwitchInputReportIDs_FullControllerState;827}828return input_mode;829}830831static Uint8 GetSensorInputMode(SDL_DriverSwitch_Context *ctx)832{833Uint8 input_mode;834835// Determine the desired input mode836if (!ctx->m_nInitialInputMode ||837ctx->m_nInitialInputMode == k_eSwitchInputReportIDs_SimpleControllerState) {838input_mode = k_eSwitchInputReportIDs_FullControllerState;839} else {840input_mode = ctx->m_nInitialInputMode;841}842return input_mode;843}844845static void UpdateInputMode(SDL_DriverSwitch_Context *ctx)846{847Uint8 input_mode;848849if (ctx->m_bReportSensors) {850input_mode = GetSensorInputMode(ctx);851} else {852input_mode = GetDefaultInputMode(ctx);853}854SetInputMode(ctx, input_mode);855}856857static void SetEnhancedModeAvailable(SDL_DriverSwitch_Context *ctx)858{859if (ctx->m_bEnhancedModeAvailable) {860return;861}862ctx->m_bEnhancedModeAvailable = true;863864if (ctx->m_bSensorsSupported) {865// Use the right sensor in the combined Joy-Con pair866if (!ctx->device->parent ||867ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {868SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 200.0f);869SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 200.0f);870}871if (ctx->device->parent &&872ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {873SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_L, 200.0f);874SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_L, 200.0f);875}876if (ctx->device->parent &&877ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {878SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_R, 200.0f);879SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_R, 200.0f);880}881}882}883884static void SetEnhancedReportHint(SDL_DriverSwitch_Context *ctx, HIDAPI_Switch_EnhancedReportHint eEnhancedReportHint)885{886ctx->m_eEnhancedReportHint = eEnhancedReportHint;887888switch (eEnhancedReportHint) {889case SWITCH_ENHANCED_REPORT_HINT_OFF:890ctx->m_bEnhancedMode = false;891break;892case SWITCH_ENHANCED_REPORT_HINT_ON:893SetEnhancedModeAvailable(ctx);894ctx->m_bEnhancedMode = true;895break;896case SWITCH_ENHANCED_REPORT_HINT_AUTO:897SetEnhancedModeAvailable(ctx);898break;899}900901UpdateInputMode(ctx);902}903904static void UpdateEnhancedModeOnEnhancedReport(SDL_DriverSwitch_Context *ctx)905{906if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) {907SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON);908}909}910911static void UpdateEnhancedModeOnApplicationUsage(SDL_DriverSwitch_Context *ctx)912{913if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) {914SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON);915}916}917918static void SDLCALL SDL_EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)919{920SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata;921922if (hint && SDL_strcasecmp(hint, "auto") == 0) {923SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_AUTO);924} else if (SDL_GetStringBoolean(hint, true)) {925SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON);926} else {927SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_OFF);928}929}930931static bool SetIMUEnabled(SDL_DriverSwitch_Context *ctx, bool enabled)932{933Uint8 imu_data = enabled ? 1 : 0;934return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_EnableIMU, &imu_data, sizeof(imu_data), NULL);935}936937static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx)938{939Uint8 *pLeftStickCal = NULL;940Uint8 *pRightStickCal = NULL;941size_t stick, axis;942SwitchSubcommandInputPacket_t *user_reply = NULL;943SwitchSubcommandInputPacket_t *factory_reply = NULL;944SwitchSPIOpData_t readUserParams;945SwitchSPIOpData_t readFactoryParams;946Uint8 userParamsReadSuccessCount = 0;947948// Read User Calibration Info949readUserParams.unAddress = k_unSPIStickUserCalibrationStartOffset;950readUserParams.ucLength = k_unSPIStickUserCalibrationLength;951952// This isn't readable on all controllers, so ignore failure953WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readUserParams, sizeof(readUserParams), &user_reply);954955// Read Factory Calibration Info956readFactoryParams.unAddress = k_unSPIStickFactoryCalibrationStartOffset;957readFactoryParams.ucLength = k_unSPIStickFactoryCalibrationLength;958959// Automatically select the user calibration if magic bytes are set960if (user_reply && user_reply->stickUserCalibration.rgucLeftMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucLeftMagic[1] == 0xA1) {961userParamsReadSuccessCount += 1;962pLeftStickCal = user_reply->stickUserCalibration.rgucLeftCalibration;963}964965if (user_reply && user_reply->stickUserCalibration.rgucRightMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucRightMagic[1] == 0xA1) {966userParamsReadSuccessCount += 1;967pRightStickCal = user_reply->stickUserCalibration.rgucRightCalibration;968}969970// Only read the factory calibration info if we failed to receive the correct magic bytes971if (userParamsReadSuccessCount < 2) {972// Read Factory Calibration Info973readFactoryParams.unAddress = k_unSPIStickFactoryCalibrationStartOffset;974readFactoryParams.ucLength = k_unSPIStickFactoryCalibrationLength;975976const int MAX_ATTEMPTS = 3;977for (int attempt = 0;; ++attempt) {978if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readFactoryParams, sizeof(readFactoryParams), &factory_reply)) {979return false;980}981982if (factory_reply->stickFactoryCalibration.opData.unAddress == k_unSPIStickFactoryCalibrationStartOffset) {983// We successfully read the calibration data984pLeftStickCal = factory_reply->stickFactoryCalibration.rgucLeftCalibration;985pRightStickCal = factory_reply->stickFactoryCalibration.rgucRightCalibration;986break;987}988989if (attempt == MAX_ATTEMPTS) {990return false;991}992}993}994995// If we still don't have calibration data, return false996if (pLeftStickCal == NULL || pRightStickCal == NULL)997{998return false;999}10001001/* Stick calibration values are 12-bits each and are packed by bit1002* For whatever reason the fields are in a different order for each stick1003* Left: X-Max, Y-Max, X-Center, Y-Center, X-Min, Y-Min1004* Right: X-Center, Y-Center, X-Min, Y-Min, X-Max, Y-Max1005*/10061007// Left stick1008ctx->m_StickCalData[0].axis[0].sMax = ((pLeftStickCal[1] << 8) & 0xF00) | pLeftStickCal[0]; // X Axis max above center1009ctx->m_StickCalData[0].axis[1].sMax = (pLeftStickCal[2] << 4) | (pLeftStickCal[1] >> 4); // Y Axis max above center1010ctx->m_StickCalData[0].axis[0].sCenter = ((pLeftStickCal[4] << 8) & 0xF00) | pLeftStickCal[3]; // X Axis center1011ctx->m_StickCalData[0].axis[1].sCenter = (pLeftStickCal[5] << 4) | (pLeftStickCal[4] >> 4); // Y Axis center1012ctx->m_StickCalData[0].axis[0].sMin = ((pLeftStickCal[7] << 8) & 0xF00) | pLeftStickCal[6]; // X Axis min below center1013ctx->m_StickCalData[0].axis[1].sMin = (pLeftStickCal[8] << 4) | (pLeftStickCal[7] >> 4); // Y Axis min below center10141015// Right stick1016ctx->m_StickCalData[1].axis[0].sCenter = ((pRightStickCal[1] << 8) & 0xF00) | pRightStickCal[0]; // X Axis center1017ctx->m_StickCalData[1].axis[1].sCenter = (pRightStickCal[2] << 4) | (pRightStickCal[1] >> 4); // Y Axis center1018ctx->m_StickCalData[1].axis[0].sMin = ((pRightStickCal[4] << 8) & 0xF00) | pRightStickCal[3]; // X Axis min below center1019ctx->m_StickCalData[1].axis[1].sMin = (pRightStickCal[5] << 4) | (pRightStickCal[4] >> 4); // Y Axis min below center1020ctx->m_StickCalData[1].axis[0].sMax = ((pRightStickCal[7] << 8) & 0xF00) | pRightStickCal[6]; // X Axis max above center1021ctx->m_StickCalData[1].axis[1].sMax = (pRightStickCal[8] << 4) | (pRightStickCal[7] >> 4); // Y Axis max above center10221023// Filter out any values that were uninitialized (0xFFF) in the SPI read1024for (stick = 0; stick < 2; ++stick) {1025for (axis = 0; axis < 2; ++axis) {1026if (ctx->m_StickCalData[stick].axis[axis].sCenter == 0xFFF) {1027ctx->m_StickCalData[stick].axis[axis].sCenter = 2048;1028}1029if (ctx->m_StickCalData[stick].axis[axis].sMax == 0xFFF) {1030ctx->m_StickCalData[stick].axis[axis].sMax = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sCenter * 0.7f);1031}1032if (ctx->m_StickCalData[stick].axis[axis].sMin == 0xFFF) {1033ctx->m_StickCalData[stick].axis[axis].sMin = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sCenter * 0.7f);1034}1035}1036}10371038for (stick = 0; stick < 2; ++stick) {1039for (axis = 0; axis < 2; ++axis) {1040ctx->m_StickExtents[stick].axis[axis].sMin = -(Sint16)(ctx->m_StickCalData[stick].axis[axis].sMin * 0.7f);1041ctx->m_StickExtents[stick].axis[axis].sMax = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sMax * 0.7f);1042}1043}10441045for (stick = 0; stick < 2; ++stick) {1046for (axis = 0; axis < 2; ++axis) {1047ctx->m_SimpleStickExtents[stick].axis[axis].sMin = (Sint16)(SDL_MIN_SINT16 * 0.5f);1048ctx->m_SimpleStickExtents[stick].axis[axis].sMax = (Sint16)(SDL_MAX_SINT16 * 0.5f);1049}1050}10511052return true;1053}10541055static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx)1056{1057SwitchSubcommandInputPacket_t *reply = NULL;10581059// Read Calibration Info1060SwitchSPIOpData_t readParams;1061readParams.unAddress = k_unSPIIMUScaleStartOffset;1062readParams.ucLength = k_unSPIIMUScaleLength;10631064if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply)) {1065Uint8 *pIMUScale;1066Sint16 sAccelRawX, sAccelRawY, sAccelRawZ, sGyroRawX, sGyroRawY, sGyroRawZ;1067Sint16 sAccelSensCoeffX, sAccelSensCoeffY, sAccelSensCoeffZ;1068Sint16 sGyroSensCoeffX, sGyroSensCoeffY, sGyroSensCoeffZ;10691070// IMU scale gives us multipliers for converting raw values to real world values1071pIMUScale = reply->spiReadData.rgucReadData;10721073sAccelRawX = (pIMUScale[1] << 8) | pIMUScale[0];1074sAccelRawY = (pIMUScale[3] << 8) | pIMUScale[2];1075sAccelRawZ = (pIMUScale[5] << 8) | pIMUScale[4];10761077sAccelSensCoeffX = (pIMUScale[7] << 8) | pIMUScale[6];1078sAccelSensCoeffY = (pIMUScale[9] << 8) | pIMUScale[8];1079sAccelSensCoeffZ = (pIMUScale[11] << 8) | pIMUScale[10];10801081sGyroRawX = (pIMUScale[13] << 8) | pIMUScale[12];1082sGyroRawY = (pIMUScale[15] << 8) | pIMUScale[14];1083sGyroRawZ = (pIMUScale[17] << 8) | pIMUScale[16];10841085sGyroSensCoeffX = (pIMUScale[19] << 8) | pIMUScale[18];1086sGyroSensCoeffY = (pIMUScale[21] << 8) | pIMUScale[20];1087sGyroSensCoeffZ = (pIMUScale[23] << 8) | pIMUScale[22];10881089// Check for user calibration data. If it's present and set, it'll override the factory settings1090readParams.unAddress = k_unSPIIMUUserScaleStartOffset;1091readParams.ucLength = k_unSPIIMUUserScaleLength;1092if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply) && (pIMUScale[0] | pIMUScale[1] << 8) == 0xA1B2) {1093pIMUScale = reply->spiReadData.rgucReadData;10941095sAccelRawX = (pIMUScale[3] << 8) | pIMUScale[2];1096sAccelRawY = (pIMUScale[5] << 8) | pIMUScale[4];1097sAccelRawZ = (pIMUScale[7] << 8) | pIMUScale[6];10981099sGyroRawX = (pIMUScale[15] << 8) | pIMUScale[14];1100sGyroRawY = (pIMUScale[17] << 8) | pIMUScale[16];1101sGyroRawZ = (pIMUScale[19] << 8) | pIMUScale[18];1102}11031104// Accelerometer scale1105ctx->m_IMUScaleData.fAccelScaleX = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffX - (float)sAccelRawX) * SDL_STANDARD_GRAVITY;1106ctx->m_IMUScaleData.fAccelScaleY = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffY - (float)sAccelRawY) * SDL_STANDARD_GRAVITY;1107ctx->m_IMUScaleData.fAccelScaleZ = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffZ - (float)sAccelRawZ) * SDL_STANDARD_GRAVITY;11081109// Gyro scale1110ctx->m_IMUScaleData.fGyroScaleX = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffX - (float)sGyroRawX) * SDL_PI_F / 180.0f;1111ctx->m_IMUScaleData.fGyroScaleY = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffY - (float)sGyroRawY) * SDL_PI_F / 180.0f;1112ctx->m_IMUScaleData.fGyroScaleZ = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffZ - (float)sGyroRawZ) * SDL_PI_F / 180.0f;11131114} else {1115// Use default values1116const float accelScale = SDL_STANDARD_GRAVITY / SWITCH_ACCEL_SCALE;1117const float gyroScale = SDL_PI_F / 180.0f / SWITCH_GYRO_SCALE;11181119ctx->m_IMUScaleData.fAccelScaleX = accelScale;1120ctx->m_IMUScaleData.fAccelScaleY = accelScale;1121ctx->m_IMUScaleData.fAccelScaleZ = accelScale;11221123ctx->m_IMUScaleData.fGyroScaleX = gyroScale;1124ctx->m_IMUScaleData.fGyroScaleY = gyroScale;1125ctx->m_IMUScaleData.fGyroScaleZ = gyroScale;1126}1127return true;1128}11291130static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue)1131{1132sRawValue -= ctx->m_StickCalData[nStick].axis[nAxis].sCenter;11331134if (sRawValue >= 0) {1135if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) {1136ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue;1137}1138return (Sint16)HIDAPI_RemapVal(sRawValue, 0, ctx->m_StickExtents[nStick].axis[nAxis].sMax, 0, SDL_MAX_SINT16);1139} else {1140if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) {1141ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue;1142}1143return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, 0, SDL_MIN_SINT16, 0);1144}1145}11461147static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue)1148{1149// 0x8000 is the neutral value for all joystick axes1150const Uint16 usJoystickCenter = 0x8000;11511152sRawValue -= usJoystickCenter;11531154if (sRawValue >= 0) {1155if (sRawValue > ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax) {1156ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax = sRawValue;1157}1158return (Sint16)HIDAPI_RemapVal(sRawValue, 0, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, 0, SDL_MAX_SINT16);1159} else {1160if (sRawValue < ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin) {1161ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue;1162}1163return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, 0, SDL_MIN_SINT16, 0);1164}1165}11661167static Uint8 RemapButton(SDL_DriverSwitch_Context *ctx, Uint8 button)1168{1169if (ctx->m_bUseButtonLabels) {1170// Use button labels instead of positions, e.g. Nintendo Online Classic controllers1171switch (button) {1172case SDL_GAMEPAD_BUTTON_SOUTH:1173return SDL_GAMEPAD_BUTTON_EAST;1174case SDL_GAMEPAD_BUTTON_EAST:1175return SDL_GAMEPAD_BUTTON_SOUTH;1176case SDL_GAMEPAD_BUTTON_WEST:1177return SDL_GAMEPAD_BUTTON_NORTH;1178case SDL_GAMEPAD_BUTTON_NORTH:1179return SDL_GAMEPAD_BUTTON_WEST;1180default:1181break;1182}1183}1184return button;1185}11861187static int GetMaxWriteAttempts(SDL_HIDAPI_Device *device)1188{1189if (device->vendor_id == USB_VENDOR_NINTENDO &&1190device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) {1191// This device is a little slow and we know we're always on USB1192return 20;1193} else {1194return 5;1195}1196}11971198static ESwitchDeviceInfoControllerType ReadJoyConControllerType(SDL_HIDAPI_Device *device)1199{1200ESwitchDeviceInfoControllerType eControllerType = k_eSwitchDeviceInfoControllerType_Unknown;1201const int MAX_ATTEMPTS = 1; // Don't try too long, in case this is a zombie Bluetooth controller1202int attempts = 0;12031204// Create enough of a context to read the controller type from the device1205SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx));1206if (ctx) {1207ctx->device = device;1208ctx->m_bSyncWrite = true;1209ctx->m_nMaxWriteAttempts = GetMaxWriteAttempts(device);12101211for ( ; ; ) {1212++attempts;1213if (device->is_bluetooth) {1214SwitchSubcommandInputPacket_t *reply = NULL;12151216if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_RequestDeviceInfo, NULL, 0, &reply)) {1217eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)reply->deviceInfo.ucDeviceType);1218}1219} else {1220if (WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Status, NULL, 0, true)) {1221SwitchProprietaryStatusPacket_t *status = (SwitchProprietaryStatusPacket_t *)&ctx->m_rgucReadBuffer[0];12221223eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)status->ucDeviceType);1224}1225}1226if (eControllerType == k_eSwitchDeviceInfoControllerType_Unknown && attempts < MAX_ATTEMPTS) {1227// Wait a bit and try again1228SDL_Delay(100);1229continue;1230}1231break;1232}1233SDL_free(ctx);1234}1235return eControllerType;1236}12371238static bool HasHomeLED(SDL_DriverSwitch_Context *ctx)1239{1240Uint16 vendor_id = ctx->device->vendor_id;1241Uint16 product_id = ctx->device->product_id;12421243// The Power A Nintendo Switch Pro controllers don't have a Home LED1244if (vendor_id == 0 && product_id == 0) {1245return false;1246}12471248// HORI Wireless Switch Pad1249if (vendor_id == 0x0f0d && product_id == 0x00f6) {1250return false;1251}12521253// Third party controllers don't have a home LED and will shut off if we try to set it1254if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_Unknown ||1255ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_LicProController) {1256return false;1257}12581259// The Nintendo Online classic controllers don't have a Home LED1260if (vendor_id == USB_VENDOR_NINTENDO &&1261ctx->m_eControllerType > k_eSwitchDeviceInfoControllerType_ProController) {1262return false;1263}12641265return true;1266}12671268static bool AlwaysUsesLabels(Uint16 vendor_id, Uint16 product_id, ESwitchDeviceInfoControllerType eControllerType)1269{1270// Some controllers don't have a diamond button configuration, so should always use labels1271if (SDL_IsJoystickGameCube(vendor_id, product_id)) {1272return true;1273}1274switch (eControllerType) {1275case k_eSwitchDeviceInfoControllerType_HVCLeft:1276case k_eSwitchDeviceInfoControllerType_HVCRight:1277case k_eSwitchDeviceInfoControllerType_NESLeft:1278case k_eSwitchDeviceInfoControllerType_NESRight:1279case k_eSwitchDeviceInfoControllerType_N64:1280case k_eSwitchDeviceInfoControllerType_SEGA_Genesis:1281return true;1282default:1283return false;1284}1285}12861287static void HIDAPI_DriverNintendoClassic_RegisterHints(SDL_HintCallback callback, void *userdata)1288{1289SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, callback, userdata);1290}12911292static void HIDAPI_DriverNintendoClassic_UnregisterHints(SDL_HintCallback callback, void *userdata)1293{1294SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, callback, userdata);1295}12961297static bool HIDAPI_DriverNintendoClassic_IsEnabled(void)1298{1299return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));1300}13011302static bool HIDAPI_DriverNintendoClassic_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)1303{1304if (vendor_id == USB_VENDOR_NINTENDO) {1305if (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) {1306if (SDL_strncmp(name, "NES Controller", 14) == 0 ||1307SDL_strncmp(name, "HVC Controller", 14) == 0) {1308return true;1309}1310}13111312if (product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) {1313return true;1314}13151316if (product_id == USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER) {1317return true;1318}13191320if (product_id == USB_PRODUCT_NINTENDO_SNES_CONTROLLER) {1321return true;1322}1323}13241325return false;1326}13271328static void HIDAPI_DriverJoyCons_RegisterHints(SDL_HintCallback callback, void *userdata)1329{1330SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, callback, userdata);1331}13321333static void HIDAPI_DriverJoyCons_UnregisterHints(SDL_HintCallback callback, void *userdata)1334{1335SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, callback, userdata);1336}13371338static bool HIDAPI_DriverJoyCons_IsEnabled(void)1339{1340return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));1341}13421343static bool HIDAPI_DriverJoyCons_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)1344{1345if (vendor_id == USB_VENDOR_NINTENDO) {1346if (product_id == USB_PRODUCT_NINTENDO_SWITCH_PRO && device && device->dev) {1347// This might be a Kinvoca Joy-Con that reports VID/PID as a Switch Pro controller1348ESwitchDeviceInfoControllerType eControllerType = ReadJoyConControllerType(device);1349if (eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft ||1350eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {1351return true;1352}1353}13541355if (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT ||1356product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT ||1357product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) {1358return true;1359}1360}1361return false;1362}13631364static void HIDAPI_DriverSwitch_RegisterHints(SDL_HintCallback callback, void *userdata)1365{1366SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, callback, userdata);1367}13681369static void HIDAPI_DriverSwitch_UnregisterHints(SDL_HintCallback callback, void *userdata)1370{1371SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, callback, userdata);1372}13731374static bool HIDAPI_DriverSwitch_IsEnabled(void)1375{1376return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));1377}13781379static bool HIDAPI_DriverSwitch_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)1380{1381/* The HORI Wireless Switch Pad enumerates as a HID device when connected via USB1382with the same VID/PID as when connected over Bluetooth but doesn't actually1383support communication over USB. The most reliable way to block this without allowing the1384controller to continually attempt to reconnect is to filter it out by manufacturer/product string.1385Note that the controller does have a different product string when connected over Bluetooth.1386*/1387if (SDL_strcmp(name, "HORI Wireless Switch Pad") == 0) {1388return false;1389}13901391// If it's handled by another driver, it's not handled here1392if (HIDAPI_DriverNintendoClassic_IsSupportedDevice(device, name, type, vendor_id, product_id, version, interface_number, interface_class, interface_subclass, interface_protocol) ||1393HIDAPI_DriverJoyCons_IsSupportedDevice(device, name, type, vendor_id, product_id, version, interface_number, interface_class, interface_subclass, interface_protocol)) {1394return false;1395}13961397return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO);1398}13991400static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)1401{1402SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;14031404if (ctx->m_bInputOnly) {1405if (SDL_IsJoystickGameCube(device->vendor_id, device->product_id)) {1406device->type = SDL_GAMEPAD_TYPE_STANDARD;1407}1408} else {1409char serial[18];14101411switch (ctx->m_eControllerType) {1412case k_eSwitchDeviceInfoControllerType_JoyConLeft:1413HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)");1414HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT);1415device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT;1416break;1417case k_eSwitchDeviceInfoControllerType_JoyConRight:1418HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)");1419HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT);1420device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT;1421break;1422case k_eSwitchDeviceInfoControllerType_ProController:1423case k_eSwitchDeviceInfoControllerType_LicProController:1424HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller");1425HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_PRO);1426device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;1427break;1428case k_eSwitchDeviceInfoControllerType_HVCLeft:1429HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (1)");1430device->type = SDL_GAMEPAD_TYPE_STANDARD;1431break;1432case k_eSwitchDeviceInfoControllerType_HVCRight:1433HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (2)");1434device->type = SDL_GAMEPAD_TYPE_STANDARD;1435break;1436case k_eSwitchDeviceInfoControllerType_NESLeft:1437HIDAPI_SetDeviceName(device, "Nintendo NES Controller (L)");1438device->type = SDL_GAMEPAD_TYPE_STANDARD;1439break;1440case k_eSwitchDeviceInfoControllerType_NESRight:1441HIDAPI_SetDeviceName(device, "Nintendo NES Controller (R)");1442device->type = SDL_GAMEPAD_TYPE_STANDARD;1443break;1444case k_eSwitchDeviceInfoControllerType_SNES:1445HIDAPI_SetDeviceName(device, "Nintendo SNES Controller");1446HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SNES_CONTROLLER);1447device->type = SDL_GAMEPAD_TYPE_STANDARD;1448break;1449case k_eSwitchDeviceInfoControllerType_N64:1450HIDAPI_SetDeviceName(device, "Nintendo N64 Controller");1451HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_N64_CONTROLLER);1452device->type = SDL_GAMEPAD_TYPE_STANDARD;1453break;1454case k_eSwitchDeviceInfoControllerType_SEGA_Genesis:1455HIDAPI_SetDeviceName(device, "Nintendo SEGA Genesis Controller");1456HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER);1457device->type = SDL_GAMEPAD_TYPE_STANDARD;1458break;1459case k_eSwitchDeviceInfoControllerType_Unknown:1460// We couldn't read the device info for this controller, might not be fully compliant1461if (device->vendor_id == USB_VENDOR_NINTENDO) {1462switch (device->product_id) {1463case USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT:1464ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_JoyConLeft;1465HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)");1466device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT;1467break;1468case USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT:1469ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_JoyConRight;1470HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)");1471device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT;1472break;1473case USB_PRODUCT_NINTENDO_SWITCH_PRO:1474ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_ProController;1475HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller");1476device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;1477break;1478default:1479break;1480}1481}1482return;1483default:1484device->type = SDL_GAMEPAD_TYPE_STANDARD;1485break;1486}1487device->guid.data[15] = ctx->m_eControllerType;14881489(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",1490ctx->m_rgucMACAddress[0],1491ctx->m_rgucMACAddress[1],1492ctx->m_rgucMACAddress[2],1493ctx->m_rgucMACAddress[3],1494ctx->m_rgucMACAddress[4],1495ctx->m_rgucMACAddress[5]);1496HIDAPI_SetDeviceSerial(device, serial);1497}1498}14991500static bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device)1501{1502SDL_DriverSwitch_Context *ctx;15031504ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx));1505if (!ctx) {1506return false;1507}1508ctx->device = device;1509device->context = ctx;15101511ctx->m_nMaxWriteAttempts = GetMaxWriteAttempts(device);1512ctx->m_bSyncWrite = true;15131514// Find out whether or not we can send output reports1515ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(device->vendor_id, device->product_id);1516if (!ctx->m_bInputOnly) {1517// Initialize rumble data, important for reading device info on the MOBAPAD M0731518SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);1519SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);15201521BReadDeviceInfo(ctx);1522}1523UpdateDeviceIdentity(device);15241525// Prefer the USB device over the Bluetooth device1526if (device->is_bluetooth) {1527if (HIDAPI_HasConnectedUSBDevice(device->serial)) {1528return true;1529}1530} else {1531HIDAPI_DisconnectBluetoothDevice(device->serial);1532}1533return HIDAPI_JoystickConnected(device, NULL);1534}15351536static int HIDAPI_DriverSwitch_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)1537{1538return -1;1539}15401541static void HIDAPI_DriverSwitch_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)1542{1543SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;15441545if (!ctx->joystick) {1546return;1547}15481549ctx->m_nPlayerIndex = player_index;15501551UpdateSlotLED(ctx);1552}15531554static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)1555{1556SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;15571558SDL_AssertJoysticksLocked();15591560ctx->joystick = joystick;15611562ctx->m_bSyncWrite = true;15631564if (!ctx->m_bInputOnly) {1565#ifdef SDL_PLATFORM_MACOS1566// Wait for the OS to finish its handshake with the controller1567SDL_Delay(250);1568#endif1569GetInitialInputMode(ctx);1570ctx->m_nCurrentInputMode = ctx->m_nInitialInputMode;15711572// Initialize rumble data1573SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);1574SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);15751576if (!device->is_bluetooth) {1577if (!BTrySetupUSB(ctx)) {1578SDL_SetError("Couldn't setup USB mode");1579return false;1580}1581}15821583if (!LoadStickCalibration(ctx)) {1584SDL_SetError("Couldn't load stick calibration");1585return false;1586}15871588if (ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_HVCLeft &&1589ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_HVCRight &&1590ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESLeft &&1591ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESRight &&1592ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SNES &&1593ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_N64 &&1594ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis) {1595if (LoadIMUCalibration(ctx)) {1596ctx->m_bSensorsSupported = true;1597}1598}15991600// Enable vibration1601SetVibrationEnabled(ctx, 1);16021603// Set desired input mode1604SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,1605SDL_EnhancedReportsChanged, ctx);16061607// Start sending USB reports1608if (!device->is_bluetooth) {1609// ForceUSB doesn't generate an ACK, so don't wait for a reply1610if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false)) {1611SDL_SetError("Couldn't start USB reports");1612return false;1613}1614}16151616// Set the LED state1617if (HasHomeLED(ctx)) {1618if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft ||1619ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {1620SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED,1621SDL_HomeLEDHintChanged, ctx);1622} else {1623SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED,1624SDL_HomeLEDHintChanged, ctx);1625}1626}1627}16281629if (AlwaysUsesLabels(device->vendor_id, device->product_id, ctx->m_eControllerType)) {1630ctx->m_bUseButtonLabels = true;1631}16321633// Initialize player index (needed for setting LEDs)1634ctx->m_nPlayerIndex = SDL_GetJoystickPlayerIndex(joystick);1635ctx->m_bPlayerLights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, true);1636UpdateSlotLED(ctx);16371638SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED,1639SDL_PlayerLEDHintChanged, ctx);16401641// Initialize the joystick capabilities1642joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH_BUTTONS;1643joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;1644joystick->nhats = 1;16451646// Set up for input1647ctx->m_bSyncWrite = false;1648ctx->m_ulLastIMUReset = ctx->m_ulLastInput = SDL_GetTicks();1649ctx->m_ulIMUUpdateIntervalNS = SDL_MS_TO_NS(5); // Start off at 5 ms update rate16501651// Set up for vertical mode1652ctx->m_bVerticalMode = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false);16531654return true;1655}16561657static bool HIDAPI_DriverSwitch_ActuallyRumbleJoystick(SDL_DriverSwitch_Context *ctx, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)1658{1659/* Experimentally determined rumble values. These will only matter on some controllers as tested ones1660* seem to disregard these and just use any non-zero rumble values as a binary flag for constant rumble1661*1662* More information about these values can be found here:1663* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md1664*/1665const Uint16 k_usHighFreq = 0x0074;1666const Uint8 k_ucHighFreqAmp = EncodeRumbleHighAmplitude(high_frequency_rumble);1667const Uint8 k_ucLowFreq = 0x3D;1668const Uint16 k_usLowFreqAmp = EncodeRumbleLowAmplitude(low_frequency_rumble);16691670if (low_frequency_rumble || high_frequency_rumble) {1671EncodeRumble(&ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);1672EncodeRumble(&ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);1673} else {1674SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);1675SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);1676}16771678ctx->m_bRumbleActive = (low_frequency_rumble || high_frequency_rumble);16791680if (!WriteRumble(ctx)) {1681return SDL_SetError("Couldn't send rumble packet");1682}1683return true;1684}16851686static bool HIDAPI_DriverSwitch_SendPendingRumble(SDL_DriverSwitch_Context *ctx)1687{1688if (SDL_GetTicks() < (ctx->m_ulRumbleSent + RUMBLE_WRITE_FREQUENCY_MS)) {1689return true;1690}16911692if (ctx->m_bRumblePending) {1693Uint16 low_frequency_rumble = (Uint16)(ctx->m_unRumblePending >> 16);1694Uint16 high_frequency_rumble = (Uint16)ctx->m_unRumblePending;16951696#ifdef DEBUG_RUMBLE1697SDL_Log("Sent pending rumble %d/%d, %d ms after previous rumble", low_frequency_rumble, high_frequency_rumble, SDL_GetTicks() - ctx->m_ulRumbleSent);1698#endif1699ctx->m_bRumblePending = false;1700ctx->m_unRumblePending = 0;17011702return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, low_frequency_rumble, high_frequency_rumble);1703}17041705if (ctx->m_bRumbleZeroPending) {1706ctx->m_bRumbleZeroPending = false;17071708#ifdef DEBUG_RUMBLE1709SDL_Log("Sent pending zero rumble, %d ms after previous rumble", SDL_GetTicks() - ctx->m_ulRumbleSent);1710#endif1711return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, 0, 0);1712}17131714return true;1715}17161717static bool HIDAPI_DriverSwitch_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)1718{1719SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;17201721if (ctx->m_bInputOnly) {1722return SDL_Unsupported();1723}17241725if (device->parent) {1726if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {1727// Just handle low frequency rumble1728high_frequency_rumble = 0;1729} else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {1730// Just handle high frequency rumble1731low_frequency_rumble = 0;1732}1733}17341735if (ctx->m_bRumblePending) {1736if (!HIDAPI_DriverSwitch_SendPendingRumble(ctx)) {1737return false;1738}1739}17401741if (SDL_GetTicks() < (ctx->m_ulRumbleSent + RUMBLE_WRITE_FREQUENCY_MS)) {1742if (low_frequency_rumble || high_frequency_rumble) {1743Uint32 unRumblePending = ((Uint32)low_frequency_rumble << 16) | high_frequency_rumble;17441745// Keep the highest rumble intensity in the given interval1746if (unRumblePending > ctx->m_unRumblePending) {1747ctx->m_unRumblePending = unRumblePending;1748}1749ctx->m_bRumblePending = true;1750ctx->m_bRumbleZeroPending = false;1751} else {1752// When rumble is complete, turn it off1753ctx->m_bRumbleZeroPending = true;1754}1755return true;1756}17571758#ifdef DEBUG_RUMBLE1759SDL_Log("Sent rumble %d/%d", low_frequency_rumble, high_frequency_rumble);1760#endif17611762return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, low_frequency_rumble, high_frequency_rumble);1763}17641765static bool HIDAPI_DriverSwitch_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)1766{1767return SDL_Unsupported();1768}17691770static Uint32 HIDAPI_DriverSwitch_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)1771{1772SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;1773Uint32 result = 0;17741775if (ctx->m_bPlayerLights && !ctx->m_bInputOnly) {1776result |= SDL_JOYSTICK_CAP_PLAYER_LED;1777}17781779if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_ProController && !ctx->m_bInputOnly) {1780// Doesn't have an RGB LED, so don't return SDL_JOYSTICK_CAP_RGB_LED here1781result |= SDL_JOYSTICK_CAP_RUMBLE;1782// But has the HOME LED, so1783result |= SDL_JOYSTICK_CAP_MONO_LED;1784} else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft ||1785ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {1786result |= SDL_JOYSTICK_CAP_RUMBLE;1787if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {1788result |= SDL_JOYSTICK_CAP_MONO_LED; // Those types of controllers also have the Home LED.1789}1790}17911792return result;1793}17941795static bool HIDAPI_DriverSwitch_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)1796{1797SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;1798int value = (int)((SDL_max(red, SDL_max(green, blue)) / 255.0f) * 100.0f); // The colors are received between 0-255 and we need them to be 0-100.1799return SetHomeLED(ctx, (Uint8)value);1800}18011802static bool HIDAPI_DriverSwitch_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)1803{1804SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;18051806if (size == sizeof(SwitchCommonOutputPacket_t)) {1807const SwitchCommonOutputPacket_t *packet = (SwitchCommonOutputPacket_t *)data;18081809if (packet->ucPacketType != k_eSwitchOutputReportIDs_Rumble) {1810return SDL_SetError("Unknown Nintendo Switch Pro effect type");1811}18121813SDL_copyp(&ctx->m_RumblePacket.rumbleData[0], &packet->rumbleData[0]);1814SDL_copyp(&ctx->m_RumblePacket.rumbleData[1], &packet->rumbleData[1]);1815if (!WriteRumble(ctx)) {1816return false;1817}18181819// This overwrites any internal rumble1820ctx->m_bRumblePending = false;1821ctx->m_bRumbleZeroPending = false;1822return true;1823} else if (size >= 2 && size <= 256) {1824const Uint8 *payload = (const Uint8 *)data;1825ESwitchSubcommandIDs cmd = (ESwitchSubcommandIDs)payload[0];18261827if (cmd == k_eSwitchSubcommandIDs_SetInputReportMode && !device->is_bluetooth) {1828// Going into simple mode over USB disables input reports, so don't do that1829return true;1830}1831if (cmd == k_eSwitchSubcommandIDs_SetHomeLight && !HasHomeLED(ctx)) {1832// Setting the home LED when it's not supported can cause the controller to reset1833return true;1834}18351836if (!WriteSubcommand(ctx, cmd, &payload[1], (Uint8)(size - 1), NULL)) {1837return false;1838}1839return true;1840}1841return SDL_Unsupported();1842}18431844static bool HIDAPI_DriverSwitch_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)1845{1846SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;18471848UpdateEnhancedModeOnApplicationUsage(ctx);18491850if (!ctx->m_bSensorsSupported || (enabled && !ctx->m_bEnhancedMode)) {1851return SDL_Unsupported();1852}18531854ctx->m_bReportSensors = enabled;1855ctx->m_unIMUSamples = 0;1856ctx->m_ulIMUSampleTimestampNS = SDL_GetTicksNS();18571858UpdateInputMode(ctx);1859SetIMUEnabled(ctx, enabled);18601861return true;1862}18631864static void HandleInputOnlyControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchInputOnlyControllerStatePacket_t *packet)1865{1866Sint16 axis;1867Uint64 timestamp = SDL_GetTicksNS();18681869if (packet->rgucButtons[0] != ctx->m_lastInputOnlyState.rgucButtons[0]) {1870Uint8 data = packet->rgucButtons[0];1871SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x02) != 0));1872SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x04) != 0));1873SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0));1874SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0));1875SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0));1876SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0));1877}18781879if (packet->rgucButtons[1] != ctx->m_lastInputOnlyState.rgucButtons[1]) {1880Uint8 data = packet->rgucButtons[1];1881SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0));1882SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));1883SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0));1884SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0));1885SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));1886SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));1887}18881889if (packet->ucStickHat != ctx->m_lastInputOnlyState.ucStickHat) {1890Uint8 hat;18911892switch (packet->ucStickHat) {1893case 0:1894hat = SDL_HAT_UP;1895break;1896case 1:1897hat = SDL_HAT_RIGHTUP;1898break;1899case 2:1900hat = SDL_HAT_RIGHT;1901break;1902case 3:1903hat = SDL_HAT_RIGHTDOWN;1904break;1905case 4:1906hat = SDL_HAT_DOWN;1907break;1908case 5:1909hat = SDL_HAT_LEFTDOWN;1910break;1911case 6:1912hat = SDL_HAT_LEFT;1913break;1914case 7:1915hat = SDL_HAT_LEFTUP;1916break;1917default:1918hat = SDL_HAT_CENTERED;1919break;1920}1921SDL_SendJoystickHat(timestamp, joystick, 0, hat);1922}19231924axis = (packet->rgucButtons[0] & 0x40) ? 32767 : -32768;1925SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);19261927axis = (packet->rgucButtons[0] & 0x80) ? 32767 : -32768;1928SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);19291930if (packet->rgucJoystickLeft[0] != ctx->m_lastInputOnlyState.rgucJoystickLeft[0]) {1931axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickLeft[0], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16);1932SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);1933}19341935if (packet->rgucJoystickLeft[1] != ctx->m_lastInputOnlyState.rgucJoystickLeft[1]) {1936axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickLeft[1], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16);1937SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);1938}19391940if (packet->rgucJoystickRight[0] != ctx->m_lastInputOnlyState.rgucJoystickRight[0]) {1941axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickRight[0], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16);1942SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);1943}19441945if (packet->rgucJoystickRight[1] != ctx->m_lastInputOnlyState.rgucJoystickRight[1]) {1946axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickRight[1], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16);1947SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);1948}19491950ctx->m_lastInputOnlyState = *packet;1951}19521953static void HandleCombinedSimpleControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)1954{1955if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {1956Uint8 data = packet->rgucButtons[0];1957Uint8 hat = 0;19581959if (data & 0x01) {1960hat |= SDL_HAT_LEFT;1961}1962if (data & 0x02) {1963hat |= SDL_HAT_DOWN;1964}1965if (data & 0x04) {1966hat |= SDL_HAT_UP;1967}1968if (data & 0x08) {1969hat |= SDL_HAT_RIGHT;1970}1971SDL_SendJoystickHat(timestamp, joystick, 0, hat);19721973SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x10) != 0));1974SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x20) != 0));1975}19761977if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {1978Uint8 data = packet->rgucButtons[1];1979SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0));1980SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0));1981SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));1982SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0));1983}19841985Sint16 axis = (packet->rgucButtons[1] & 0x80) ? 32767 : -32768;1986SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);19871988if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {1989switch (packet->ucStickHat) {1990case 0:1991SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);1992SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);1993break;1994case 1:1995SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);1996SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);1997break;1998case 2:1999SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);2000SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);2001break;2002case 3:2003SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);2004SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);2005break;2006case 4:2007SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);2008SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);2009break;2010case 5:2011SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);2012SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);2013break;2014case 6:2015SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);2016SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);2017break;2018case 7:2019SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);2020SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);2021break;2022default:2023SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);2024SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);2025break;2026}2027}2028}20292030static void HandleCombinedSimpleControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)2031{2032if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {2033Uint8 data = packet->rgucButtons[0];2034SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x01) != 0));2035SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0));2036SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0));2037SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x08) != 0));2038SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x10) != 0));2039SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x20) != 0));2040}20412042if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {2043Uint8 data = packet->rgucButtons[1];2044SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));2045SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0));2046SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));2047SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0));2048}20492050Sint16 axis = (packet->rgucButtons[1] & 0x80) ? 32767 : -32768;2051SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);20522053if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {2054switch (packet->ucStickHat) {2055case 0:2056SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN);2057SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0);2058break;2059case 1:2060SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN);2061SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN);2062break;2063case 2:2064SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0);2065SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN);2066break;2067case 3:2068SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX);2069SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN);2070break;2071case 4:2072SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX);2073SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0);2074break;2075case 5:2076SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX);2077SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX);2078break;2079case 6:2080SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0);2081SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX);2082break;2083case 7:2084SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN);2085SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX);2086break;2087default:2088SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0);2089SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0);2090break;2091}2092}2093}20942095static void HandleMiniSimpleControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)2096{2097if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {2098Uint8 data = packet->rgucButtons[0];2099SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0));2100SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0));2101SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0));2102SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0));2103SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0));2104SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0));2105}21062107if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {2108Uint8 data = packet->rgucButtons[1];2109SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x01) != 0));2110SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0));2111SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x20) != 0));2112SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x40) != 0));2113SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x80) != 0));2114}21152116if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {2117switch (packet->ucStickHat) {2118case 0:2119SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);2120SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);2121break;2122case 1:2123SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);2124SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);2125break;2126case 2:2127SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);2128SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);2129break;2130case 3:2131SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);2132SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);2133break;2134case 4:2135SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);2136SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);2137break;2138case 5:2139SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);2140SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);2141break;2142case 6:2143SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);2144SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);2145break;2146case 7:2147SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);2148SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);2149break;2150default:2151SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);2152SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);2153break;2154}2155}2156}21572158static void HandleMiniSimpleControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)2159{2160if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {2161Uint8 data = packet->rgucButtons[0];2162SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0));2163SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0));2164SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0));2165SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0));2166SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0));2167SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0));2168}21692170if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {2171Uint8 data = packet->rgucButtons[1];2172SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));2173SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0));2174SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));2175SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));2176SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x40) != 0));2177SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x80) != 0));2178}21792180if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {2181switch (packet->ucStickHat) {2182case 0:2183SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);2184SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);2185break;2186case 1:2187SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);2188SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);2189break;2190case 2:2191SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);2192SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);2193break;2194case 3:2195SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);2196SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);2197break;2198case 4:2199SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);2200SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);2201break;2202case 5:2203SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);2204SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);2205break;2206case 6:2207SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);2208SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);2209break;2210case 7:2211SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);2212SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);2213break;2214default:2215SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);2216SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);2217break;2218}2219}2220}22212222static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)2223{2224Uint64 timestamp = SDL_GetTicksNS();22252226if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {2227if (ctx->device->parent || ctx->m_bVerticalMode) {2228HandleCombinedSimpleControllerStateL(timestamp, joystick, ctx, packet);2229} else {2230HandleMiniSimpleControllerStateL(timestamp, joystick, ctx, packet);2231}2232} else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {2233if (ctx->device->parent || ctx->m_bVerticalMode) {2234HandleCombinedSimpleControllerStateR(timestamp, joystick, ctx, packet);2235} else {2236HandleMiniSimpleControllerStateR(timestamp, joystick, ctx, packet);2237}2238} else {2239Sint16 axis;22402241if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {2242Uint8 data = packet->rgucButtons[0];2243SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0));2244SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0));2245SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0));2246SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0));2247SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0));2248SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0));2249}22502251if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {2252Uint8 data = packet->rgucButtons[1];2253SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0));2254SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));2255SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0));2256SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0));2257SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));2258SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));2259}22602261if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {2262Uint8 hat;22632264switch (packet->ucStickHat) {2265case 0:2266hat = SDL_HAT_UP;2267break;2268case 1:2269hat = SDL_HAT_RIGHTUP;2270break;2271case 2:2272hat = SDL_HAT_RIGHT;2273break;2274case 3:2275hat = SDL_HAT_RIGHTDOWN;2276break;2277case 4:2278hat = SDL_HAT_DOWN;2279break;2280case 5:2281hat = SDL_HAT_LEFTDOWN;2282break;2283case 6:2284hat = SDL_HAT_LEFT;2285break;2286case 7:2287hat = SDL_HAT_LEFTUP;2288break;2289default:2290hat = SDL_HAT_CENTERED;2291break;2292}2293SDL_SendJoystickHat(timestamp, joystick, 0, hat);2294}22952296axis = (packet->rgucButtons[0] & 0x40) ? 32767 : -32768;2297SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);22982299axis = ((packet->rgucButtons[0] & 0x80) || (packet->rgucButtons[1] & 0x80)) ? 32767 : -32768;2300SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);23012302axis = ApplySimpleStickCalibration(ctx, 0, 0, packet->sJoystickLeft[0]);2303SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);23042305axis = ApplySimpleStickCalibration(ctx, 0, 1, packet->sJoystickLeft[1]);2306SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);23072308axis = ApplySimpleStickCalibration(ctx, 1, 0, packet->sJoystickRight[0]);2309SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);23102311axis = ApplySimpleStickCalibration(ctx, 1, 1, packet->sJoystickRight[1]);2312SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);2313}23142315ctx->m_lastSimpleState = *packet;2316}23172318static void SendSensorUpdate(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SDL_SensorType type, Uint64 sensor_timestamp, const Sint16 *values)2319{2320float data[3];23212322/* Note the order of components has been shuffled to match PlayStation controllers,2323* since that's our de facto standard from already supporting those controllers, and2324* users will want consistent axis mappings across devices.2325*/2326if (type == SDL_SENSOR_GYRO || type == SDL_SENSOR_GYRO_L || type == SDL_SENSOR_GYRO_R) {2327data[0] = -(ctx->m_IMUScaleData.fGyroScaleY * (float)values[1]);2328data[1] = ctx->m_IMUScaleData.fGyroScaleZ * (float)values[2];2329data[2] = -(ctx->m_IMUScaleData.fGyroScaleX * (float)values[0]);2330} else {2331data[0] = -(ctx->m_IMUScaleData.fAccelScaleY * (float)values[1]);2332data[1] = ctx->m_IMUScaleData.fAccelScaleZ * (float)values[2];2333data[2] = -(ctx->m_IMUScaleData.fAccelScaleX * (float)values[0]);2334}23352336// Right Joy-Con flips some axes, so let's flip them back for consistency2337if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {2338data[0] = -data[0];2339data[1] = -data[1];2340}23412342if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft &&2343!ctx->device->parent && !ctx->m_bVerticalMode) {2344// Mini-gamepad mode, swap some axes around2345float tmp = data[2];2346data[2] = -data[0];2347data[0] = tmp;2348}23492350if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight &&2351!ctx->device->parent && !ctx->m_bVerticalMode) {2352// Mini-gamepad mode, swap some axes around2353float tmp = data[2];2354data[2] = data[0];2355data[0] = -tmp;2356}23572358SDL_SendJoystickSensor(timestamp, joystick, type, sensor_timestamp, data, 3);2359}23602361static void HandleCombinedControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)2362{2363Sint16 axis;23642365if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {2366Uint8 data = packet->controllerState.rgucButtons[1];2367SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0));2368SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0));2369SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));2370}23712372if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) {2373Uint8 data = packet->controllerState.rgucButtons[2];2374Uint8 hat = 0;23752376if (data & 0x01) {2377hat |= SDL_HAT_DOWN;2378}2379if (data & 0x02) {2380hat |= SDL_HAT_UP;2381}2382if (data & 0x04) {2383hat |= SDL_HAT_RIGHT;2384}2385if (data & 0x08) {2386hat |= SDL_HAT_LEFT;2387}2388SDL_SendJoystickHat(timestamp, joystick, 0, hat);23892390SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x10) != 0));2391SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x20) != 0));2392SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0));2393axis = (data & 0x80) ? 32767 : -32768;2394SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);2395}23962397axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8);2398axis = ApplyStickCalibration(ctx, 0, 0, axis);2399SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);24002401axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4);2402axis = ApplyStickCalibration(ctx, 0, 1, axis);2403SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis);2404}24052406static void HandleCombinedControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)2407{2408Sint16 axis;24092410if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) {2411Uint8 data = packet->controllerState.rgucButtons[0];2412SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0));2413SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x08) != 0));2414SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0));2415SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0));2416SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x10) != 0));2417SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x20) != 0));2418SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0));2419axis = (data & 0x80) ? 32767 : -32768;2420SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);2421}24222423if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {2424Uint8 data = packet->controllerState.rgucButtons[1];2425SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));2426SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x04) != 0));2427SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));2428}24292430axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8);2431axis = ApplyStickCalibration(ctx, 1, 0, axis);2432SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);24332434axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4);2435axis = ApplyStickCalibration(ctx, 1, 1, axis);2436SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis);2437}24382439static void HandleMiniControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)2440{2441Sint16 axis;24422443if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {2444Uint8 data = packet->controllerState.rgucButtons[1];2445SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x01) != 0));2446SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0));2447SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x20) != 0));2448}24492450if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) {2451Uint8 data = packet->controllerState.rgucButtons[2];2452SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x08) != 0));2453SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x01) != 0));2454SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x02) != 0));2455SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x04) != 0));2456SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x10) != 0));2457SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x20) != 0));2458SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x40) != 0));2459SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x80) != 0));2460}24612462axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8);2463axis = ApplyStickCalibration(ctx, 0, 0, axis);2464SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis);24652466axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4);2467axis = ApplyStickCalibration(ctx, 0, 1, axis);2468SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, ~axis);2469}24702471static void HandleMiniControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)2472{2473Sint16 axis;24742475if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) {2476Uint8 data = packet->controllerState.rgucButtons[0];2477SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x08) != 0));2478SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0));2479SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0));2480SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x01) != 0));2481SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x10) != 0));2482SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x20) != 0));2483SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x40) != 0));2484SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x80) != 0));2485}24862487if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {2488Uint8 data = packet->controllerState.rgucButtons[1];2489SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));2490SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0));2491SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));2492}24932494axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8);2495axis = ApplyStickCalibration(ctx, 1, 0, axis);2496SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);24972498axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4);2499axis = ApplyStickCalibration(ctx, 1, 1, axis);2500SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);2501}25022503static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) SDL_NO_THREAD_SAFETY_ANALYSIS // We unlock and lock the device lock to be able to change IMU state2504{2505Uint64 timestamp = SDL_GetTicksNS();25062507if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {2508if (ctx->device->parent || ctx->m_bVerticalMode) {2509HandleCombinedControllerStateL(timestamp, joystick, ctx, packet);2510} else {2511HandleMiniControllerStateL(timestamp, joystick, ctx, packet);2512}2513} else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {2514if (ctx->device->parent || ctx->m_bVerticalMode) {2515HandleCombinedControllerStateR(timestamp, joystick, ctx, packet);2516} else {2517HandleMiniControllerStateR(timestamp, joystick, ctx, packet);2518}2519} else {2520Sint16 axis;25212522if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) {2523Uint8 data = packet->controllerState.rgucButtons[0];2524SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0));2525SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x08) != 0));2526SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0));2527SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0));2528SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0));2529}25302531if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {2532Uint8 data = packet->controllerState.rgucButtons[1];2533SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0));2534SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));2535SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x04) != 0));2536SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0));25372538SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));2539SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));2540}25412542if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) {2543Uint8 data = packet->controllerState.rgucButtons[2];2544Uint8 hat = 0;25452546if (data & 0x01) {2547hat |= SDL_HAT_DOWN;2548}2549if (data & 0x02) {2550hat |= SDL_HAT_UP;2551}2552if (data & 0x04) {2553hat |= SDL_HAT_RIGHT;2554}2555if (data & 0x08) {2556hat |= SDL_HAT_LEFT;2557}2558SDL_SendJoystickHat(timestamp, joystick, 0, hat);25592560SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0));2561}25622563axis = (packet->controllerState.rgucButtons[0] & 0x80) ? 32767 : -32768;2564SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);25652566axis = (packet->controllerState.rgucButtons[2] & 0x80) ? 32767 : -32768;2567SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);25682569axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8);2570axis = ApplyStickCalibration(ctx, 0, 0, axis);2571SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);25722573axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4);2574axis = ApplyStickCalibration(ctx, 0, 1, axis);2575SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis);25762577axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8);2578axis = ApplyStickCalibration(ctx, 1, 0, axis);2579SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);25802581axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4);2582axis = ApplyStickCalibration(ctx, 1, 1, axis);2583SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis);2584}25852586/* High nibble of battery/connection byte is battery level, low nibble is connection status (always 0 on 8BitDo Pro 2)2587* LSB of connection nibble is USB/Switch connection status2588* LSB of the battery nibble is used to report charging.2589* The battery level is reported from 0(empty)-8(full)2590*/2591SDL_PowerState state;2592int charging = (packet->controllerState.ucBatteryAndConnection & 0x10);2593int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4;2594int percent = (int)SDL_roundf((level / 8.0f) * 100.0f);25952596if (charging) {2597if (level == 8) {2598state = SDL_POWERSTATE_CHARGED;2599} else {2600state = SDL_POWERSTATE_CHARGING;2601}2602} else {2603state = SDL_POWERSTATE_ON_BATTERY;2604}2605SDL_SendJoystickPowerInfo(joystick, state, percent);26062607if (ctx->m_bReportSensors) {2608// Need to copy the imuState to an aligned variable2609SwitchControllerIMUState_t imuState[3];2610SDL_assert(sizeof(imuState) == sizeof(packet->imuState));2611SDL_memcpy(imuState, packet->imuState, sizeof(imuState));26122613bool bHasSensorData = (imuState[0].sAccelZ != 0 ||2614imuState[0].sAccelY != 0 ||2615imuState[0].sAccelX != 0);2616if (bHasSensorData) {2617const Uint32 IMU_UPDATE_RATE_SAMPLE_FREQUENCY = 1000;2618Uint64 sensor_timestamp[3];26192620ctx->m_bHasSensorData = true;26212622// We got three IMU samples, calculate the IMU update rate and timestamps2623ctx->m_unIMUSamples += 3;2624if (ctx->m_unIMUSamples >= IMU_UPDATE_RATE_SAMPLE_FREQUENCY) {2625Uint64 now = SDL_GetTicksNS();2626Uint64 elapsed = (now - ctx->m_ulIMUSampleTimestampNS);26272628if (elapsed > 0) {2629ctx->m_ulIMUUpdateIntervalNS = elapsed / ctx->m_unIMUSamples;2630}2631ctx->m_unIMUSamples = 0;2632ctx->m_ulIMUSampleTimestampNS = now;2633}26342635ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS;2636sensor_timestamp[0] = ctx->m_ulTimestampNS;2637ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS;2638sensor_timestamp[1] = ctx->m_ulTimestampNS;2639ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS;2640sensor_timestamp[2] = ctx->m_ulTimestampNS;26412642if (!ctx->device->parent ||2643ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {2644SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[0], &imuState[2].sGyroX);2645SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[0], &imuState[2].sAccelX);26462647SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[1], &imuState[1].sGyroX);2648SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[1], &imuState[1].sAccelX);26492650SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[2], &imuState[0].sGyroX);2651SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[2], &imuState[0].sAccelX);2652}26532654if (ctx->device->parent &&2655ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {2656SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[0], &imuState[2].sGyroX);2657SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[0], &imuState[2].sAccelX);26582659SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[1], &imuState[1].sGyroX);2660SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[1], &imuState[1].sAccelX);26612662SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[2], &imuState[0].sGyroX);2663SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[2], &imuState[0].sAccelX);2664}2665if (ctx->device->parent &&2666ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {2667SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[0], &imuState[2].sGyroX);2668SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[0], &imuState[2].sAccelX);26692670SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[1], &imuState[1].sGyroX);2671SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[1], &imuState[1].sAccelX);26722673SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[2], &imuState[0].sGyroX);2674SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[2], &imuState[0].sAccelX);2675}26762677} else if (ctx->m_bHasSensorData) {2678// Uh oh, someone turned off the IMU?2679const int IMU_RESET_DELAY_MS = 3000;2680Uint64 now = SDL_GetTicks();26812682if (now >= (ctx->m_ulLastIMUReset + IMU_RESET_DELAY_MS)) {2683SDL_HIDAPI_Device *device = ctx->device;26842685if (device->updating) {2686SDL_UnlockMutex(device->dev_lock);2687}26882689SetIMUEnabled(ctx, true);26902691if (device->updating) {2692SDL_LockMutex(device->dev_lock);2693}2694ctx->m_ulLastIMUReset = now;2695}26962697} else {2698// We have never gotten IMU data, probably not supported on this device2699}2700}27012702ctx->m_lastFullState = *packet;2703}27042705static bool HIDAPI_DriverSwitch_UpdateDevice(SDL_HIDAPI_Device *device)2706{2707SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;2708SDL_Joystick *joystick = NULL;2709int size;2710int packet_count = 0;2711Uint64 now = SDL_GetTicks();27122713if (device->num_joysticks > 0) {2714joystick = SDL_GetJoystickFromID(device->joysticks[0]);2715}27162717while ((size = ReadInput(ctx)) > 0) {2718#ifdef DEBUG_SWITCH_PROTOCOL2719HIDAPI_DumpPacket("Nintendo Switch packet: size = %d", ctx->m_rgucReadBuffer, size);2720#endif2721++packet_count;2722ctx->m_ulLastInput = now;27232724if (!joystick) {2725continue;2726}27272728if (ctx->m_bInputOnly) {2729HandleInputOnlyControllerState(joystick, ctx, (SwitchInputOnlyControllerStatePacket_t *)&ctx->m_rgucReadBuffer[0]);2730} else {2731if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) {2732continue;2733}27342735ctx->m_nCurrentInputMode = ctx->m_rgucReadBuffer[0];27362737switch (ctx->m_rgucReadBuffer[0]) {2738case k_eSwitchInputReportIDs_SimpleControllerState:2739HandleSimpleControllerState(joystick, ctx, (SwitchSimpleStatePacket_t *)&ctx->m_rgucReadBuffer[1]);2740break;2741case k_eSwitchInputReportIDs_FullControllerState:2742case k_eSwitchInputReportIDs_FullControllerAndMcuState:2743// This is the extended report, we can enable sensors now in auto mode2744UpdateEnhancedModeOnEnhancedReport(ctx);27452746HandleFullControllerState(joystick, ctx, (SwitchStatePacket_t *)&ctx->m_rgucReadBuffer[1]);2747break;2748default:2749break;2750}2751}2752}27532754if (joystick) {2755if (packet_count == 0) {2756if (!ctx->m_bInputOnly && !device->is_bluetooth &&2757ctx->device->product_id != USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) {2758const int INPUT_WAIT_TIMEOUT_MS = 100;2759if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) {2760// Steam may have put the controller back into non-reporting mode2761bool wasSyncWrite = ctx->m_bSyncWrite;27622763ctx->m_bSyncWrite = true;2764WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false);2765ctx->m_bSyncWrite = wasSyncWrite;2766}2767} else if (device->is_bluetooth &&2768ctx->m_nCurrentInputMode != k_eSwitchInputReportIDs_SimpleControllerState) {2769const int INPUT_WAIT_TIMEOUT_MS = 3000;2770if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) {2771// Bluetooth may have disconnected, try reopening the controller2772size = -1;2773}2774}2775}27762777if (ctx->m_bRumblePending || ctx->m_bRumbleZeroPending) {2778HIDAPI_DriverSwitch_SendPendingRumble(ctx);2779} else if (ctx->m_bRumbleActive &&2780now >= (ctx->m_ulRumbleSent + RUMBLE_REFRESH_FREQUENCY_MS)) {2781#ifdef DEBUG_RUMBLE2782SDL_Log("Sent continuing rumble, %d ms after previous rumble", now - ctx->m_ulRumbleSent);2783#endif2784WriteRumble(ctx);2785}2786}27872788// Reconnect the Bluetooth device once the USB device is gone2789if (device->num_joysticks == 0 && device->is_bluetooth && packet_count > 0 &&2790!device->parent &&2791!HIDAPI_HasConnectedUSBDevice(device->serial)) {2792HIDAPI_JoystickConnected(device, NULL);2793}27942795if (size < 0 && device->num_joysticks > 0) {2796// Read error, device is disconnected2797HIDAPI_JoystickDisconnected(device, device->joysticks[0]);2798}2799return (size >= 0);2800}28012802static void HIDAPI_DriverSwitch_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)2803{2804SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;28052806if (!ctx->m_bInputOnly) {2807// Restore simple input mode for other applications2808if (!ctx->m_nInitialInputMode ||2809ctx->m_nInitialInputMode == k_eSwitchInputReportIDs_SimpleControllerState) {2810SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState);2811}2812}28132814SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,2815SDL_EnhancedReportsChanged, ctx);28162817if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft ||2818ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {2819SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED,2820SDL_HomeLEDHintChanged, ctx);2821} else {2822SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED,2823SDL_HomeLEDHintChanged, ctx);2824}28252826SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED,2827SDL_PlayerLEDHintChanged, ctx);28282829ctx->joystick = NULL;28302831ctx->m_bReportSensors = false;2832ctx->m_bEnhancedMode = false;2833ctx->m_bEnhancedModeAvailable = false;2834}28352836static void HIDAPI_DriverSwitch_FreeDevice(SDL_HIDAPI_Device *device)2837{2838}28392840SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic = {2841SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC,2842true,2843HIDAPI_DriverNintendoClassic_RegisterHints,2844HIDAPI_DriverNintendoClassic_UnregisterHints,2845HIDAPI_DriverNintendoClassic_IsEnabled,2846HIDAPI_DriverNintendoClassic_IsSupportedDevice,2847HIDAPI_DriverSwitch_InitDevice,2848HIDAPI_DriverSwitch_GetDevicePlayerIndex,2849HIDAPI_DriverSwitch_SetDevicePlayerIndex,2850HIDAPI_DriverSwitch_UpdateDevice,2851HIDAPI_DriverSwitch_OpenJoystick,2852HIDAPI_DriverSwitch_RumbleJoystick,2853HIDAPI_DriverSwitch_RumbleJoystickTriggers,2854HIDAPI_DriverSwitch_GetJoystickCapabilities,2855HIDAPI_DriverSwitch_SetJoystickLED,2856HIDAPI_DriverSwitch_SendJoystickEffect,2857HIDAPI_DriverSwitch_SetJoystickSensorsEnabled,2858HIDAPI_DriverSwitch_CloseJoystick,2859HIDAPI_DriverSwitch_FreeDevice,2860};28612862SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons = {2863SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS,2864true,2865HIDAPI_DriverJoyCons_RegisterHints,2866HIDAPI_DriverJoyCons_UnregisterHints,2867HIDAPI_DriverJoyCons_IsEnabled,2868HIDAPI_DriverJoyCons_IsSupportedDevice,2869HIDAPI_DriverSwitch_InitDevice,2870HIDAPI_DriverSwitch_GetDevicePlayerIndex,2871HIDAPI_DriverSwitch_SetDevicePlayerIndex,2872HIDAPI_DriverSwitch_UpdateDevice,2873HIDAPI_DriverSwitch_OpenJoystick,2874HIDAPI_DriverSwitch_RumbleJoystick,2875HIDAPI_DriverSwitch_RumbleJoystickTriggers,2876HIDAPI_DriverSwitch_GetJoystickCapabilities,2877HIDAPI_DriverSwitch_SetJoystickLED,2878HIDAPI_DriverSwitch_SendJoystickEffect,2879HIDAPI_DriverSwitch_SetJoystickSensorsEnabled,2880HIDAPI_DriverSwitch_CloseJoystick,2881HIDAPI_DriverSwitch_FreeDevice,2882};28832884SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch = {2885SDL_HINT_JOYSTICK_HIDAPI_SWITCH,2886true,2887HIDAPI_DriverSwitch_RegisterHints,2888HIDAPI_DriverSwitch_UnregisterHints,2889HIDAPI_DriverSwitch_IsEnabled,2890HIDAPI_DriverSwitch_IsSupportedDevice,2891HIDAPI_DriverSwitch_InitDevice,2892HIDAPI_DriverSwitch_GetDevicePlayerIndex,2893HIDAPI_DriverSwitch_SetDevicePlayerIndex,2894HIDAPI_DriverSwitch_UpdateDevice,2895HIDAPI_DriverSwitch_OpenJoystick,2896HIDAPI_DriverSwitch_RumbleJoystick,2897HIDAPI_DriverSwitch_RumbleJoystickTriggers,2898HIDAPI_DriverSwitch_GetJoystickCapabilities,2899HIDAPI_DriverSwitch_SetJoystickLED,2900HIDAPI_DriverSwitch_SendJoystickEffect,2901HIDAPI_DriverSwitch_SetJoystickSensorsEnabled,2902HIDAPI_DriverSwitch_CloseJoystick,2903HIDAPI_DriverSwitch_FreeDevice,2904};29052906#endif // SDL_JOYSTICK_HIDAPI_SWITCH29072908#endif // SDL_JOYSTICK_HIDAPI290929102911