From 2fb854df302d94fd2e4de4b56ee83acda9379455 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 22 Jun 2026 09:27:23 -0400 Subject: [PATCH 1/4] 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 | 117 +++++++++++++++++++--------- 2 files changed, 80 insertions(+), 39 deletions(-) diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h index 3f20d8a76692a..81d569592ae8a 100644 --- a/src/joystick/SDL_gamepad_db.h +++ b/src/joystick/SDL_gamepad_db.h @@ -874,7 +874,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 fda19dd2179d5..54bffa5c77f48 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(); - - UpdateN3DSPressedButtons(timestamp, joystick); - UpdateN3DSReleasedButtons(timestamp, joystick); + 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, 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; } From a348571d466ed24daeff7f5b8f04668b5e4c4a64 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 22 Jun 2026 11:05:04 -0400 Subject: [PATCH 2/4] ps2: 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/ps2/SDL_sysjoystick.c | 57 +++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h index 81d569592ae8a..790d04a00e4f1 100644 --- a/src/joystick/SDL_gamepad_db.h +++ b/src/joystick/SDL_gamepad_db.h @@ -865,7 +865,7 @@ static const char *s_GamepadMappings[] = { "default,*,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,", #endif #ifdef SDL_JOYSTICK_PS2 - "0000000050533220436f6e74726f6c00,PS2 Controller,crc:ed87,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", + "0000000050533220436f6e74726f6c00,PS2 Controller,crc:ed87,a:b10,b:b9,back:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b1,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b2,righttrigger:b5,rightx:a2,righty:a3,start:b3,x:b11,y:b8,", #endif #ifdef SDL_JOYSTICK_PSP "00000000505350206275696c74696e00,PSP builtin joypad,crc:bb86,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", diff --git a/src/joystick/ps2/SDL_sysjoystick.c b/src/joystick/ps2/SDL_sysjoystick.c index 36a4dc09b00b1..598e66d1f4f1e 100644 --- a/src/joystick/ps2/SDL_sysjoystick.c +++ b/src/joystick/ps2/SDL_sysjoystick.c @@ -38,9 +38,11 @@ #define MAX_CONTROLLERS (PS2_MAX_PORT * PS2_MAX_SLOT) #define PS2_ANALOG_STICKS 2 #define PS2_ANALOG_AXIS 2 -#define PS2_BUTTONS 16 +#define PS2_BUTTONS 16 // this is total physical buttons, but we steal the 4 from the dpad for a hat switch. #define PS2_TOTAL_AXIS (PS2_ANALOG_STICKS * PS2_ANALOG_AXIS) +#define PS2_HAT_MASK 0xF0 // mask out bits 4-7 (that's the dpad, which we treat as a hat elsewhere). + struct JoyInfo { uint8_t padBuf[256]; @@ -269,9 +271,9 @@ static bool PS2_JoystickOpen(SDL_Joystick *joystick, int device_index) } PS2_InitializePad(info->port, info->slot); - joystick->nbuttons = PS2_BUTTONS; + joystick->nbuttons = PS2_BUTTONS - 4; // we steal 4 (the d-pad) for a hat switch. joystick->naxes = PS2_TOTAL_AXIS; - joystick->nhats = 0; + joystick->nhats = 1; // treat the dpad buttons as a hat. SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); @@ -347,19 +349,48 @@ static void PS2_JoystickUpdate(SDL_Joystick *joystick) int ret = padRead(info->port, info->slot, &buttons); // port, slot, buttons if (ret != 0) { // Buttons - int32_t pressed_buttons = 0xffff ^ buttons.btns; - ; - if (info->btns != pressed_buttons) { - for (i = 0; i < PS2_BUTTONS; i++) { - mask = (1 << i); - previous = info->btns & mask; - current = pressed_buttons & mask; - if (previous != current) { - SDL_SendJoystickButton(timestamp, joystick, i, (current != 0)); + const int32_t current_buttons = (0xffff ^ buttons.btns); + const int32_t previous_buttons = info->btns; + if (previous_buttons != current_buttons) { // did any buttons change? + if ((previous_buttons & ~PS2_HAT_MASK) != (current_buttons & ~PS2_HAT_MASK)) { // did non-dpad buttons change? + uint8_t buttonidx = 0; + i = 0; + while (i < PS2_BUTTONS-4) { + if ((buttonidx < 4) || (buttonidx > 7)) { // skip dpad (we treat it as a hat). + mask = (1 << buttonidx); + previous = previous_buttons & mask; + current = current_buttons & mask; + if (previous != current) { + SDL_SendJoystickButton(timestamp, joystick, i, (current != 0)); + } + i++; + } + buttonidx++; + } + } + + if ((previous_buttons & PS2_HAT_MASK) != (current_buttons & PS2_HAT_MASK)) { // did dpad buttons change? + // The PS2 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.) + Uint8 hat = SDL_HAT_CENTERED; + #define HATSTATE(ps2bit, sdlenum) if (current_buttons & (1 << ps2bit)) { hat |= SDL_HAT_##sdlenum; } + HATSTATE(4, UP); + HATSTATE(5, RIGHT); + HATSTATE(6, DOWN); + HATSTATE(7, LEFT); + #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); } + + info->btns = current_buttons; } - info->btns = pressed_buttons; // Analog all_axis[0] = buttons.ljoy_h; From 6c8565945ea51c2284e526b12085debe35e846f8 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 22 Jun 2026 13:09:42 -0400 Subject: [PATCH 3/4] psp: 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/psp/SDL_sysjoystick.c | 33 ++++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h index 790d04a00e4f1..7924a375605c4 100644 --- a/src/joystick/SDL_gamepad_db.h +++ b/src/joystick/SDL_gamepad_db.h @@ -868,7 +868,7 @@ static const char *s_GamepadMappings[] = { "0000000050533220436f6e74726f6c00,PS2 Controller,crc:ed87,a:b10,b:b9,back:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b1,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b2,righttrigger:b5,rightx:a2,righty:a3,start:b3,x:b11,y:b8,", #endif #ifdef SDL_JOYSTICK_PSP - "00000000505350206275696c74696e00,PSP builtin joypad,crc:bb86,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", + "00000000505350206275696c74696e00,PSP builtin joypad,crc:bb86,a:b2,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b7,x:b3,y:b0,", #endif #ifdef SDL_JOYSTICK_VITA "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,", diff --git a/src/joystick/psp/SDL_sysjoystick.c b/src/joystick/psp/SDL_sysjoystick.c index feeebe602512b..7528ffd496446 100644 --- a/src/joystick/psp/SDL_sysjoystick.c +++ b/src/joystick/psp/SDL_sysjoystick.c @@ -31,12 +31,13 @@ #include "../SDL_sysjoystick.h" #include "../SDL_joystick_c.h" +#define PSP_HAT_MASK (PSP_CTRL_DOWN | PSP_CTRL_LEFT | PSP_CTRL_UP | PSP_CTRL_RIGHT ) + // Current pad state static SceCtrlData pad = { .Lx = 0, .Ly = 0, .Buttons = 0 }; static const enum PspCtrlButtons button_map[] = { PSP_CTRL_TRIANGLE, PSP_CTRL_CIRCLE, PSP_CTRL_CROSS, PSP_CTRL_SQUARE, PSP_CTRL_LTRIGGER, PSP_CTRL_RTRIGGER, - PSP_CTRL_DOWN, PSP_CTRL_LEFT, PSP_CTRL_UP, PSP_CTRL_RIGHT, PSP_CTRL_SELECT, PSP_CTRL_START, PSP_CTRL_HOME, PSP_CTRL_HOLD }; static int analog_map[256]; // Map analog inputs to -32768 -> 32767 @@ -159,7 +160,7 @@ static bool PSP_JoystickOpen(SDL_Joystick *joystick, int device_index) { joystick->nbuttons = SDL_arraysize(button_map); joystick->naxes = 2; - joystick->nhats = 0; + joystick->nhats = 1; // we treat the d-pad as a hat. return true; } @@ -224,15 +225,35 @@ static void PSP_JoystickUpdate(SDL_Joystick *joystick) // Buttons changed = old_buttons ^ buttons; old_buttons = buttons; - if (changed) { + if (changed & ~PSP_HAT_MASK) { for (i = 0; i < SDL_arraysize(button_map); i++) { if (changed & button_map[i]) { - bool down = ((buttons & button_map[i]) != 0); - SDL_SendJoystickButton(timestamp, - joystick, i, down); + const bool down = ((buttons & button_map[i]) != 0); + SDL_SendJoystickButton(timestamp, joystick, i, down); } } } + + if (changed & PSP_HAT_MASK) { + // The PSP 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.) + Uint8 hat = SDL_HAT_CENTERED; + #define HATSTATE(name) if (buttons & PSP_CTRL_##name) { hat |= SDL_HAT_##name; } + HATSTATE(UP); + HATSTATE(RIGHT); + HATSTATE(DOWN); + HATSTATE(LEFT); + #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); + } } // Function to close a joystick after use From a28c7f2bb4ba5c1c080ecbb94b0ece1c2c8300d6 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 22 Jun 2026 13:45:00 -0400 Subject: [PATCH 4/4] vita: Treat the d-pad as a hat switch at the SDL_Joystick level. Reference Issue #14830. [sdl-ci-filter *] --- src/joystick/SDL_gamepad_db.h | 2 +- src/joystick/vita/SDL_sysjoystick.c | 33 +++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h index 7924a375605c4..9d0dfa4efad01 100644 --- a/src/joystick/SDL_gamepad_db.h +++ b/src/joystick/SDL_gamepad_db.h @@ -871,7 +871,7 @@ static const char *s_GamepadMappings[] = { "00000000505350206275696c74696e00,PSP builtin joypad,crc:bb86,a:b2,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b7,x:b3,y:b0,", #endif #ifdef SDL_JOYSTICK_VITA - "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,", + "0000000050535669746120436f6e7400,PSVita Controller,crc:d598,a:b2,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b3,y:b0,", #endif #ifdef SDL_JOYSTICK_N3DS "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,", diff --git a/src/joystick/vita/SDL_sysjoystick.c b/src/joystick/vita/SDL_sysjoystick.c index 59f553fb5328e..4a3f0d50b20fe 100644 --- a/src/joystick/vita/SDL_sysjoystick.c +++ b/src/joystick/vita/SDL_sysjoystick.c @@ -43,6 +43,8 @@ static int ext_port_map[4] = { 1, 2, 3, 4 }; // index: SDL joy number, entry: Vi static int SDL_numjoysticks = 1; +#define VITA_HAT_MASK (SCE_CTRL_DOWN | SCE_CTRL_LEFT | SCE_CTRL_UP | SCE_CTRL_RIGHT) // we offer the dpad as a hat switch. + static const unsigned int ext_button_map[] = { SCE_CTRL_TRIANGLE, SCE_CTRL_CIRCLE, @@ -50,10 +52,6 @@ static const unsigned int ext_button_map[] = { SCE_CTRL_SQUARE, SCE_CTRL_L1, SCE_CTRL_R1, - SCE_CTRL_DOWN, - SCE_CTRL_LEFT, - SCE_CTRL_UP, - SCE_CTRL_RIGHT, SCE_CTRL_SELECT, SCE_CTRL_START, SCE_CTRL_L2, @@ -205,7 +203,7 @@ static bool VITA_JoystickOpen(SDL_Joystick *joystick, int device_index) { joystick->nbuttons = SDL_arraysize(ext_button_map); joystick->naxes = 6; - joystick->nhats = 0; + joystick->nhats = 1; // we treat the dpad as a hat switch, even though its just buttons. SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true); SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); @@ -297,14 +295,35 @@ static void VITA_JoystickUpdate(SDL_Joystick *joystick) changed = old_buttons[index] ^ buttons; old_buttons[index] = buttons; - if (changed) { + if (changed & ~VITA_HAT_MASK) { for (i = 0; i < SDL_arraysize(ext_button_map); i++) { if (changed & ext_button_map[i]) { - bool down = ((buttons & ext_button_map[i]) != 0); + const bool down = ((buttons & ext_button_map[i]) != 0); SDL_SendJoystickButton(timestamp, joystick, i, down); } } } + + if (changed & VITA_HAT_MASK) { + // The Vita 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.) + Uint8 hat = SDL_HAT_CENTERED; + #define HATSTATE(name) if (buttons & SCE_CTRL_##name) { hat |= SDL_HAT_##name; } + HATSTATE(UP); + HATSTATE(RIGHT); + HATSTATE(DOWN); + HATSTATE(LEFT); + #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); + } } // Function to close a joystick after use