From fb3afd33e49c443cd8c5fd7e8ed0a20a07a6f8ac Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 22 Jun 2026 09:27:23 -0400 Subject: [PATCH] n3ds: Treat the d-pad as a hat switch at the SDL_Joystick level. Reference Issue #14830. --- src/joystick/SDL_gamepad_db.h | 2 +- src/joystick/n3ds/SDL_sysjoystick.c | 115 +++++++++++++++++++--------- 2 files changed, 79 insertions(+), 38 deletions(-) diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h index 1a834455ea..c184eefe23 100644 --- a/src/joystick/SDL_gamepad_db.h +++ b/src/joystick/SDL_gamepad_db.h @@ -877,7 +877,7 @@ static const char *s_GamepadMappings[] = { "0000000050535669746120436f6e7400,PSVita Controller,crc:d598,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftstick:b14,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", #endif #ifdef SDL_JOYSTICK_N3DS - "000000004e696e74656e646f20334400,Nintendo 3DS,crc:3210,a:b1,b:b0,back:b2,dpdown:b7,dpleft:b5,dpright:b4,dpup:b6,leftshoulder:b9,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b8,righttrigger:b15,rightx:a2,righty:a3,start:b3,x:b11,y:b10,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "000000004e696e74656e646f20334400,Nintendo 3DS,crc:3210,a:b1,b:b0,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b11,rightx:a2,righty:a3,start:b3,x:b7,y:b6,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", #endif NULL }; diff --git a/src/joystick/n3ds/SDL_sysjoystick.c b/src/joystick/n3ds/SDL_sysjoystick.c index fda19dd217..54bffa5c77 100644 --- a/src/joystick/n3ds/SDL_sysjoystick.c +++ b/src/joystick/n3ds/SDL_sysjoystick.c @@ -28,7 +28,8 @@ #include "../SDL_sysjoystick.h" -#define NB_BUTTONS 23 +#define NB_BUTTONS 23 // physical buttons on the device (although we treat four of these, the dpad, as a hat switch). +#define N3DS_HAT_MASK 0xF0 // 0xF0==d-pad bits, which we handle elsewhere, so mask them out here. /* N3DS sticks values are roughly within +/-160 @@ -54,8 +55,9 @@ static inline int Correct_Axis_Y(int Y) { return Correct_Axis_X(-Y); } -static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick); -static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick); +static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_state, u32 current_state); +static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_state, u32 current_state); +static void UpdateN3DSHat(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_down_state, u32 current_down_state, u32 previous_up_state, u32 current_up_state); static void UpdateN3DSCircle(Uint64 timestamp, SDL_Joystick *joystick); static void UpdateN3DSCStick(Uint64 timestamp, SDL_Joystick *joystick); @@ -89,9 +91,9 @@ static SDL_JoystickID N3DS_JoystickGetDeviceInstanceID(int device_index) static bool N3DS_JoystickOpen(SDL_Joystick *joystick, int device_index) { - joystick->nbuttons = NB_BUTTONS; + joystick->nbuttons = NB_BUTTONS - 4; // -4 for the dpad (which we treat as a hat). joystick->naxes = 4; - joystick->nhats = 0; + joystick->nhats = 1; // treat the dpad as a hat. return true; } @@ -103,46 +105,85 @@ static bool N3DS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) static void N3DS_JoystickUpdate(SDL_Joystick *joystick) { - Uint64 timestamp = SDL_GetTicksNS(); + static u32 previous_down_state = 0; + static u32 previous_up_state = 0; + const Uint64 timestamp = SDL_GetTicksNS(); + const u32 current_down_state = hidKeysDown(); + const u32 current_up_state = hidKeysUp(); - UpdateN3DSPressedButtons(timestamp, joystick); - UpdateN3DSReleasedButtons(timestamp, joystick); + UpdateN3DSPressedButtons(timestamp, joystick, previous_down_state, current_down_state); + UpdateN3DSReleasedButtons(timestamp, joystick, previous_up_state, current_up_state); + UpdateN3DSHat(timestamp, joystick, previous_down_state, current_down_state, previous_up_state, current_up_state); UpdateN3DSCircle(timestamp, joystick); UpdateN3DSCStick(timestamp, joystick); + + previous_down_state = current_down_state; + previous_up_state = current_up_state; } -static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick) +static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_state, u32 current_state) { - static u32 previous_state = 0; - u32 updated_down; - u32 current_state = hidKeysDown(); - updated_down = previous_state ^ current_state; + const u32 updated_down = (previous_state ^ current_state) & ~N3DS_HAT_MASK; if (updated_down) { - for (Uint8 i = 0; i < joystick->nbuttons; i++) { - if (current_state & BIT(i) & updated_down) { - SDL_SendJoystickButton(timestamp, joystick, i, true); + Uint8 buttonidx = 0; + Uint8 i = 0; + while (i < joystick->nbuttons) { + if ((buttonidx < 4) || (buttonidx > 7)) { // skip dpad (we treat it as a hat). + if (current_state & BIT(buttonidx) & updated_down) { + SDL_SendJoystickButton(timestamp, joystick, i, true); + } + i++; } + buttonidx++; } } - previous_state = current_state; } -static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick) +static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_state, u32 current_state) { - static u32 previous_state = 0; - u32 updated_up; - u32 current_state = hidKeysUp(); - updated_up = previous_state ^ current_state; + const u32 updated_up = (previous_state ^ current_state) & ~N3DS_HAT_MASK; if (updated_up) { - for (Uint8 i = 0; i < joystick->nbuttons; i++) { - if (current_state & BIT(i) & updated_up) { - SDL_SendJoystickButton(timestamp, joystick, i, false); - } + Uint8 buttonidx = 0; + Uint8 i = 0; + while (i < joystick->nbuttons) { + if ((buttonidx < 4) || (buttonidx > 7)) { // skip dpad (we treat it as a hat). + if (current_state & BIT(buttonidx) & updated_up) { + SDL_SendJoystickButton(timestamp, joystick, i, false); + } + i++; + } + buttonidx++; } } - previous_state = current_state; } +static void UpdateN3DSHat(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_down_state, u32 current_down_state, u32 previous_up_state, u32 current_up_state) +{ + // The 3DS dpad looks like 4 buttons at this level, but we treat it as a hat switch, so apps that are talking to SDL_Joystick can hope to do basic directional things without a configuration step. + // (but they should _really_ be using the gamepad API.) + const u32 updated_hat = (((previous_up_state ^ current_up_state) | (previous_down_state ^ current_down_state)) & N3DS_HAT_MASK); // did bits 4 through 7 change? + if (updated_hat) { + Uint8 hat = SDL_HAT_CENTERED; + + #define HATSTATE(n3dsbit, sdlenum) if (current_down_state & BIT(n3dsbit)) { hat |= SDL_HAT_##sdlenum; } + HATSTATE(4, RIGHT); + HATSTATE(5, LEFT); + HATSTATE(6, UP); + HATSTATE(7, DOWN); + #undef HATSTATE + + // this is a physical d-pad on the device, so it probably _can't_ send opposing buttons at the same time, but just in case, cancel them out. + if ((hat & (SDL_HAT_UP|SDL_HAT_DOWN)) == (SDL_HAT_UP|SDL_HAT_DOWN)) { + hat &= ~(SDL_HAT_UP|SDL_HAT_DOWN); + } + if ((hat & (SDL_HAT_LEFT|SDL_HAT_RIGHT)) == (SDL_HAT_LEFT|SDL_HAT_RIGHT)) { + hat &= ~(SDL_HAT_LEFT|SDL_HAT_RIGHT); + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } +} + + static void UpdateN3DSCircle(Uint64 timestamp, SDL_Joystick *joystick) { static circlePosition previous_state = { 0, 0 }; @@ -194,19 +235,19 @@ static bool N3DS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out = (SDL_GamepadMapping){ .a = { EMappingKind_Button, 0 }, .b = { EMappingKind_Button, 1 }, - .x = { EMappingKind_Button, 10 }, - .y = { EMappingKind_Button, 11 }, + .x = { EMappingKind_Button, 6 }, + .y = { EMappingKind_Button, 7 }, .back = { EMappingKind_Button, 2 }, .guide = { EMappingKind_None, 255 }, .start = { EMappingKind_Button, 3 }, .leftstick = { EMappingKind_None, 255 }, .rightstick = { EMappingKind_None, 255 }, - .leftshoulder = { EMappingKind_Button, 9 }, - .rightshoulder = { EMappingKind_Button, 8 }, - .dpup = { EMappingKind_Button, 6 }, - .dpdown = { EMappingKind_Button, 7 }, - .dpleft = { EMappingKind_Button, 5 }, - .dpright = { EMappingKind_Button, 4 }, + .leftshoulder = { EMappingKind_Button, 5 }, + .rightshoulder = { EMappingKind_Button, 4 }, + .dpup = { EMappingKind_Hat, SDL_HAT_UP }, + .dpdown = { EMappingKind_Hat, SDL_HAT_DOWN }, + .dpleft = { EMappingKind_Hat, SDL_HAT_LEFT }, + .dpright = { EMappingKind_Hat, SDL_HAT_RIGHT }, .misc1 = { EMappingKind_None, 255 }, .right_paddle1 = { EMappingKind_None, 255 }, .left_paddle1 = { EMappingKind_None, 255 }, @@ -216,8 +257,8 @@ static bool N3DS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping .lefty = { EMappingKind_Axis, 1 }, .rightx = { EMappingKind_Axis, 2 }, .righty = { EMappingKind_Axis, 3 }, - .lefttrigger = { EMappingKind_Button, 14 }, - .righttrigger = { EMappingKind_Button, 15 }, + .lefttrigger = { EMappingKind_Button, 10 }, + .righttrigger = { EMappingKind_Button, 11 }, }; return true; }