n3ds: Treat the d-pad as a hat switch at the SDL_Joystick level.

Reference Issue #14830.
This commit is contained in:
Ryan C. Gordon
2026-06-22 09:27:23 -04:00
committed by Sam Lantinga
parent 82141a2439
commit fb3afd33e4
2 changed files with 79 additions and 38 deletions

View File

@@ -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
};

View File

@@ -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;
}