From 2fd764f60f83e74377c54e513061f16ca8a592f8 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 13 Feb 2026 11:37:19 +0100 Subject: [PATCH] Add raw joystick fallback for non-standard controllers Open the joystick via SDL_JoystickOpen() when no GameController mapping exists, fixing d-pad and face button input on devices like the Anbernic retro handheld (GUID not in SDL's database). Handles JOYHATMOTION for d-pad navigation and JOYBUTTONDOWN button 0 for activation. Co-Authored-By: Claude Opus 4.6 --- docs/sdlamp2-fsd.md | 6 ++++++ src/sdlamp2.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/docs/sdlamp2-fsd.md b/docs/sdlamp2-fsd.md index 39abfa3..a02c677 100644 --- a/docs/sdlamp2-fsd.md +++ b/docs/sdlamp2-fsd.md @@ -43,6 +43,12 @@ This document specifies the functional requirements for an SDL2 based media play ## 6. Changelog +### 2026-02-13 — Raw joystick fallback for non-standard controllers + +- **Joystick fallback**: When no SDL GameController mapping exists for a connected device, the joystick is now opened directly via `SDL_JoystickOpen()` as a fallback. This fixes d-pad and face button input on devices like the Anbernic retro handheld whose GUID is not in SDL's GameController database. +- **Hat/button handling**: `SDL_JOYHATMOTION` events drive d-pad navigation (left/right to move focus, up/down for volume), and `SDL_JOYBUTTONDOWN` button 0 (BTN_SOUTH / A) activates the focused button. +- **Hot-unplug**: Raw joystick is properly closed on `SDL_JOYDEVICEREMOVED`. + ### 2026-02-13 — Embed controls spritesheet into binary - **Embedded asset**: `controls.png` is now compiled into the binary as a C byte array (`src/controls_png.h`), eliminating the requirement to run from the `build/` directory. diff --git a/src/sdlamp2.c b/src/sdlamp2.c index 8de3081..ec8f95c 100644 --- a/src/sdlamp2.c +++ b/src/sdlamp2.c @@ -67,6 +67,7 @@ static int current_file_index = 0; static float volume = 0.5f; static int focus_index = FOCUS_PLAY; static SDL_GameController* controller = NULL; +static SDL_Joystick* joystick = NULL; static int debug_mode = 0; /* --- Utility --- */ @@ -627,6 +628,16 @@ int main(int argc, char** argv) { } } + if (!controller) { + for (int i = 0; i < num_joy; i++) { + joystick = SDL_JoystickOpen(i); + if (joystick) { + printf("Joystick: %s\n", SDL_JoystickName(joystick)); + break; + } + } + } + SDL_Surface* controls_surface = IMG_Load("controls.png"); if (!controls_surface) { SDL_RWops* rw = SDL_RWFromConstMem(controls_png_data, controls_png_size); @@ -763,6 +774,26 @@ int main(int argc, char** argv) { } break; + case SDL_JOYHATMOTION: { + Uint8 hat = e.jhat.value; + if (hat & SDL_HAT_LEFT) { + focus_index = (focus_index - 1 + FOCUS_COUNT) % FOCUS_COUNT; + } else if (hat & SDL_HAT_RIGHT) { + focus_index = (focus_index + 1) % FOCUS_COUNT; + } else if (hat & SDL_HAT_UP) { + if (focus_index == FOCUS_VOLUME) adjust_volume(0.05f); + } else if (hat & SDL_HAT_DOWN) { + if (focus_index == FOCUS_VOLUME) adjust_volume(-0.05f); + } + break; + } + + case SDL_JOYBUTTONDOWN: + if (e.jbutton.button == 0) { + activate_focused_button(); + } + break; + case SDL_CONTROLLERDEVICEADDED: if (!controller && SDL_IsGameController(e.cdevice.which)) { controller = SDL_GameControllerOpen(e.cdevice.which); @@ -786,6 +817,13 @@ int main(int argc, char** argv) { printf("Controller removed\n"); } break; + + case SDL_JOYDEVICEREMOVED: + if (joystick && e.jdevice.which == SDL_JoystickInstanceID(joystick)) { + SDL_JoystickClose(joystick); + joystick = NULL; + } + break; } } @@ -898,6 +936,7 @@ int main(int argc, char** argv) { decoder_close(); SDL_FreeAudioStream(stream); SDL_DestroyTexture(controls_texture); + if (joystick) SDL_JoystickClose(joystick); if (controller) SDL_GameControllerClose(controller); SDL_CloseAudioDevice(audio_device); SDL_DestroyRenderer(renderer);