diff --git a/Makefile.common b/Makefile.common index 5de6926d9977..8ea83b302af7 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1678,7 +1678,14 @@ endif endif -ifeq ($(HAVE_SDL2), 1) +ifeq ($(HAVE_SDL3), 1) + SDL3_CFLAGS = $(shell pkg-config sdl3 --cflags) + SDL3_LIBS = $(shell pkg-config sdl3 --libs) + DEF_FLAGS += $(SDL3_CFLAGS) + LIBS += $(SDL3_LIBS) + DEFINES += -DHAVE_SDL3 + OBJ += input/drivers_joypad/sdl3_joypad.o +else ifeq ($(HAVE_SDL2), 1) HAVE_SDL_COMMON = 1 OBJ += gfx/drivers/sdl2_gfx.o \ gfx/common/sdl2_common.o diff --git a/Makefile.win b/Makefile.win index 67dd5ea79684..214bcf2b8dae 100644 --- a/Makefile.win +++ b/Makefile.win @@ -34,6 +34,7 @@ HAVE_WINMM = 1 HAVE_SDL := 0 HAVE_SDL2 := 0 +HAVE_SDL3 := 0 HAVE_RSOUND := 0 HAVE_STB_FONT := 1 @@ -64,6 +65,11 @@ SDL2_LIBS := -lSDL2 SDL2_CFLAGS := -ISDL2 -DHAVE_SDL2 endif +ifeq ($(HAVE_SDL3), 1) +SDL3_LIBS := -lSDL3 +SDL3_CFLAGS := -ISDL3 -DHAVE_SDL3 +endif + ifeq ($(HAVE_D3D8), 1) D3D8_LIBS := -ld3d8 endif diff --git a/config.features.h b/config.features.h index 50167c056bc0..f85edfa2c146 100644 --- a/config.features.h +++ b/config.features.h @@ -62,6 +62,12 @@ #define SUPPORTS_SDL2 false #endif +#ifdef HAVE_SDL3 +#define SUPPORTS_SDL3 true +#else +#define SUPPORTS_SDL3 false +#endif + #ifdef HAVE_THREADS #define SUPPORTS_THREAD true #else diff --git a/configuration.c b/configuration.c index e264246b2529..a4db13f072c4 100644 --- a/configuration.c +++ b/configuration.c @@ -1295,7 +1295,9 @@ const char *config_get_default_joypad(void) case JOYPAD_ANDROID: return "android"; case JOYPAD_SDL: -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 + return "sdl3"; +#elif defined(HAVE_SDL2) return "sdl2"; #else return "sdl"; diff --git a/griffin/griffin.c b/griffin/griffin.c index 4f301ac882ff..df0a95bb26ff 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -914,7 +914,9 @@ AUDIO #include "../audio/drivers/xaudio.c" #endif -#if defined(HAVE_SDL2) +#if defined(HAVE_SDL3) +#include "../input/drivers_joypad/sdl3_joypad.c" +#elif defined(HAVE_SDL2) #include "../audio/drivers/sdl_audio.c" #include "../input/drivers/sdl_input.c" #include "../input/drivers_joypad/sdl_joypad.c" diff --git a/input/drivers_joypad/sdl3_joypad.c b/input/drivers_joypad/sdl3_joypad.c new file mode 100644 index 000000000000..241c06d54e4c --- /dev/null +++ b/input/drivers_joypad/sdl3_joypad.c @@ -0,0 +1,663 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2017 - Daniel De Matteis + * Copyright (C) 2014-2017 - Higor Euripedes + * Copyright (C) 2023 - Carlo Refice + * Copyright (C) 2026 - Rob Loach + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include +#include + +#include + +#include +#include + +#include "../input_driver.h" +#include "../../configuration.h" +#include "../../tasks/tasks_internal.h" +#include "../../verbosity.h" + +typedef struct _sdl3_joypad +{ + SDL_Joystick *joypad; + SDL_Gamepad *gamepad; + SDL_JoystickID jid; /* 0 = Invalid ID */ + unsigned num_axes; + unsigned num_buttons; + unsigned num_hats; + uint16_t rumble_gain; /* 0-100 */ + uint16_t rumble[2]; /* raw magnitude per retro_rumble_effect (strong/weak) */ +} sdl3_joypad_t; + +/** + * The static global for the active joypads. + * + * @todo Move away from static globals. + */ +static sdl3_joypad_t sdl3_joypads[MAX_USERS]; + +static const char *sdl3_joypad_name(unsigned pad) +{ + if (pad >= MAX_USERS || !sdl3_joypads[pad].jid) + return NULL; + + if (sdl3_joypads[pad].gamepad) + return SDL_GetGamepadName(sdl3_joypads[pad].gamepad); + else if (sdl3_joypads[pad].joypad) + return SDL_GetJoystickName(sdl3_joypads[pad].joypad); + return NULL; +} + +static uint8_t sdl3_joypad_get_button(sdl3_joypad_t *pad, unsigned button) +{ + if (pad->gamepad) + return (uint8_t)SDL_GetGamepadButton(pad->gamepad, (SDL_GamepadButton)button); + else if (pad->joypad) + return (uint8_t)SDL_GetJoystickButton(pad->joypad, (int)button); + return 0; +} + +static uint8_t sdl3_joypad_get_hat(sdl3_joypad_t *pad, unsigned hat) +{ + /* Gamepads don't have hats, so we can pass this in directly for the Joystick. */ + return SDL_GetJoystickHat(pad->joypad, hat); +} + +static int16_t sdl3_joypad_get_axis(sdl3_joypad_t *pad, unsigned axis) +{ + if (pad->gamepad) + return SDL_GetGamepadAxis(pad->gamepad, (SDL_GamepadAxis)axis); + else if (pad->joypad) + return SDL_GetJoystickAxis(pad->joypad, (int)axis); + return 0; +} + +static bool sdl3_joypad_set_rumble_gain(unsigned pad, unsigned gain) +{ + if (pad >= MAX_USERS) + return false; + + sdl3_joypads[pad].rumble_gain = (gain > 100) ? 100 : gain; + return true; +} + +static void sdl3_joypad_connect(SDL_JoystickID jid) +{ + int i; + int slot = -1; + int32_t vendor = 0; + int32_t product = 0; + sdl3_joypad_t *pad = NULL; + SDL_Gamepad *gamepad = NULL; + SDL_Joystick *joypad = NULL; + + /* Protect against connecting an already connected device. */ + for (i = 0; i < MAX_USERS; i++) + if (sdl3_joypads[i].jid == jid) + return; + + /* Connect to the device. */ + if (SDL_IsGamepad(jid)) + { + gamepad = SDL_OpenGamepad(jid); + if (gamepad) + joypad = SDL_GetGamepadJoystick(gamepad); + + if (!gamepad || !joypad) + { + RARCH_ERR("[SDL3] Couldn't open gamepad %" SDL_PRIu32 ": %s.\n", jid, SDL_GetError()); + if (gamepad) + SDL_CloseGamepad(gamepad); + return; + } + } + else + { + joypad = SDL_OpenJoystick(jid); + if (!joypad) + { + RARCH_ERR("[SDL3] Couldn't open joystick %" SDL_PRIu32 ": %s.\n", jid, SDL_GetError()); + return; + } + } + + /* Gamepads allow restoring the player index, so re-use that for the slot if possible. */ + if (gamepad) + { + int player = SDL_GetGamepadPlayerIndex(gamepad); + if (player >= 0 && player < MAX_USERS && !sdl3_joypads[player].jid) + slot = player; + } + + /* Fallback to the first free slot. */ + if (slot < 0) + { + for (i = 0; i < MAX_USERS; i++) + { + if (!sdl3_joypads[i].jid) + { + slot = i; + break; + } + } + } + + /* Fail if a slot is still not found. */ + if (slot < 0) + { + RARCH_WARN("[SDL3] No free joypad slots for joystick %" SDL_PRIu32 ".\n", jid); + if (gamepad) + SDL_CloseGamepad(gamepad); + else + SDL_CloseJoystick(joypad); + return; + } + + pad = &sdl3_joypads[slot]; + pad->jid = jid; + pad->gamepad = gamepad; + pad->joypad = joypad; + + /* Seed the rumble gain from the saved setting so it applies on connect. */ + { + settings_t *settings = config_get_ptr(); + if (settings) + sdl3_joypad_set_rumble_gain((unsigned int)slot, settings->uints.input_rumble_gain); + } + + if (gamepad) + { + vendor = SDL_GetGamepadVendor(gamepad); + product = SDL_GetGamepadProduct(gamepad); + + /* Ensure the player index matches the slot. */ + if (SDL_GetGamepadPlayerIndex(gamepad) != slot) + SDL_SetGamepadPlayerIndex(gamepad, slot); + + /* Set the LED to match the player number. */ + switch (slot) { + case 0: SDL_SetGamepadLED(gamepad, 255, 0, 0); break; + case 1: SDL_SetGamepadLED(gamepad, 0, 0, 255); break; + case 2: SDL_SetGamepadLED(gamepad, 0, 255, 0); break; + case 3: SDL_SetGamepadLED(gamepad, 255, 255, 0); break; + case 4: SDL_SetGamepadLED(gamepad, 255, 0, 255); break; + case 5: SDL_SetGamepadLED(gamepad, 0, 255, 255); break; + case 6: SDL_SetGamepadLED(gamepad, 255, 128, 0); break; + case 7: SDL_SetGamepadLED(gamepad, 255, 255, 255); break; + case 8: SDL_SetGamepadLED(gamepad, 128, 0, 255); break; + case 9: SDL_SetGamepadLED(gamepad, 0, 128, 255); break; + case 10: SDL_SetGamepadLED(gamepad, 128, 255, 0); break; + case 11: SDL_SetGamepadLED(gamepad, 255, 0, 128); break; + case 12: SDL_SetGamepadLED(gamepad, 128, 0, 0); break; + case 13: SDL_SetGamepadLED(gamepad, 0, 128, 0); break; + case 14: SDL_SetGamepadLED(gamepad, 0, 0, 128); break; + case 15: SDL_SetGamepadLED(gamepad, 128, 128, 128); break; + default: SDL_SetGamepadLED(gamepad, 0, 0, 0); break; + } + } + else + { + vendor = SDL_GetJoystickVendor(joypad); + product = SDL_GetJoystickProduct(joypad); + } + + input_autoconfigure_connect_ex( + sdl3_joypad_name(slot), + NULL, + SDL_GetJoystickPath(joypad), + sdl_joypad.ident, + slot, + vendor, + product, + gamepad ? AUTOCONF_FLAG_HAS_STANDARD_MAPPING : 0); /* An open SDL_Gamepad always has a normalized mapping */ + + if (gamepad) + { + /* SDL_Gamepad internally supports all axis/button IDs, even if + * the controller's mapping does not have a binding for it. + * + * So, we can claim to support all axes/buttons, and when we try to poll + * an unbound ID, SDL simply returns the correct unpressed value. + * + * Note that, in addition to 0 trackballs, we also have 0 hats. This is + * because the d-pad is in the button list, as the last 4 enum entries. + * + * -flibit + */ + pad->num_axes = SDL_GAMEPAD_AXIS_COUNT; + pad->num_buttons = SDL_GAMEPAD_BUTTON_COUNT; + pad->num_hats = 0; + } + else + { + /* These can return -1 on error, so protect accordingly. */ + int num_axes = SDL_GetNumJoystickAxes(joypad); + int num_buttons = SDL_GetNumJoystickButtons(joypad); + int num_hats = SDL_GetNumJoystickHats(joypad); + pad->num_axes = (num_axes > 0) ? (unsigned)num_axes : 0; + pad->num_buttons = (num_buttons > 0) ? (unsigned)num_buttons : 0; + pad->num_hats = (num_hats > 0) ? (unsigned)num_hats : 0; + } +} + +static void sdl3_joypad_disconnect(SDL_JoystickID jid) +{ + for (int i = 0; i < MAX_USERS; i++) + { + if (sdl3_joypads[i].jid != jid) + continue; + + if (sdl3_joypads[i].gamepad) { + SDL_SetGamepadPlayerIndex(sdl3_joypads[i].gamepad, -1); + SDL_CloseGamepad(sdl3_joypads[i].gamepad); + } + else if (sdl3_joypads[i].joypad) + SDL_CloseJoystick(sdl3_joypads[i].joypad); + + input_autoconfigure_disconnect(i, sdl_joypad.ident); + + memset(&sdl3_joypads[i], 0, sizeof(sdl3_joypads[i])); + return; + } +} + +static void sdl3_joypad_destroy(void) +{ + + for (int i = 0; i < MAX_USERS; i++) + { + if (sdl3_joypads[i].gamepad) + SDL_CloseGamepad(sdl3_joypads[i].gamepad); + else if (sdl3_joypads[i].joypad) + SDL_CloseJoystick(sdl3_joypads[i].joypad); + } + + memset(sdl3_joypads, 0, sizeof(sdl3_joypads)); + SDL_QuitSubSystem(SDL_INIT_GAMEPAD); +} + +/** + * Attempts to load SDL_GameControllerDB from the autoconfig directory. + * + * @return The number of loaded configs. + * @see https://github.com/mdqinc/SDL_GameControllerDB + */ +static int sdl3_joypad_load_gamecontrollerdb(void) +{ + settings_t *settings = config_get_ptr(); + char path[PATH_MAX_LENGTH]; + void *buf = NULL; + int64_t len = 0; + int num_mappings = 0; + SDL_IOStream *io; + + if ( settings == NULL + || !settings->bools.input_autodetect_enable + || settings->paths.directory_autoconfig[0] == '\0') + return 0; + + fill_pathname_join_special(path, settings->paths.directory_autoconfig, "sdl3/gamecontrollerdb.cfg", sizeof(path)); + if (filestream_read_file(path, &buf, &len) == 0 || len == 0) + { + RARCH_WARN("[SDL3] Failed to load gamepad mappings from \"%s\".\n", path); + return 0; + } + + io = SDL_IOFromConstMem(buf, (size_t)len); + num_mappings = SDL_AddGamepadMappingsFromIO(io, true); + if (num_mappings >= 0) + RARCH_LOG("[SDL3] Loaded %d gamepad mappings from \"%s\".\n", num_mappings, path); + else + RARCH_WARN("[SDL3] Failed to load gamepad mappings from \"%s\": %s.\n", path, SDL_GetError()); + free(buf); + return num_mappings; +} + +/** + * Initializes the SDL3 Joypad system, and loads the GameController DB. + */ +static void *sdl3_joypad_init(void *data) +{ +#if SDL_VERSION_ATLEAST(3, 2, 0) + /* Gamepad driver hints. */ + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1"); +#endif + + SDL_InitFlags sdl_subsystem_flags = SDL_WasInit(0); + + if (sdl_subsystem_flags == 0) + { + if (!SDL_Init(SDL_INIT_GAMEPAD)) + return NULL; + } + else if ((sdl_subsystem_flags & SDL_INIT_GAMEPAD) == 0) + { + if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) + return NULL; + } + + /* Set the initial state of the joypad system. */ + memset(sdl3_joypads, 0, sizeof(sdl3_joypads)); + sdl3_joypad_load_gamecontrollerdb(); + + /* Ensure we know about any already connected devices. */ + { + int i; + int num_joysticks = 0; + SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks); + if (joysticks) + { + for (i = 0; i < num_joysticks; i++) + sdl3_joypad_connect(joysticks[i]); + SDL_free(joysticks); + } + } + + return (void*)-1; +} + +static int32_t sdl3_joypad_button_state(sdl3_joypad_t *pad, uint16_t joykey) +{ + unsigned hat_dir = GET_HAT_DIR(joykey); + + if (hat_dir) + { + uint8_t dir; + uint16_t hat = GET_HAT(joykey); + + if (hat >= pad->num_hats) + return 0; + + dir = sdl3_joypad_get_hat(pad, hat); + + switch (hat_dir) + { + case HAT_UP_MASK: + return (dir & SDL_HAT_UP); + case HAT_DOWN_MASK: + return (dir & SDL_HAT_DOWN); + case HAT_LEFT_MASK: + return (dir & SDL_HAT_LEFT); + case HAT_RIGHT_MASK: + return (dir & SDL_HAT_RIGHT); + default: + break; + } + /* hat requested and no hat button down */ + } + else if (joykey < pad->num_buttons) + return sdl3_joypad_get_button(pad, joykey); + + return 0; +} + +static int32_t sdl3_joypad_button(unsigned port, uint16_t joykey) +{ + if (port >= MAX_USERS) + return 0; + + if (!sdl3_joypads[port].joypad) + return 0; + + return sdl3_joypad_button_state(&sdl3_joypads[port], joykey); +} + +static int16_t sdl3_joypad_axis_state(sdl3_joypad_t *pad, uint32_t joyaxis) +{ + if (AXIS_NEG_GET(joyaxis) < pad->num_axes) + { + int16_t val = sdl3_joypad_get_axis(pad, AXIS_NEG_GET(joyaxis)); + if (val < 0) + { + /* Clamp - -0x8000 can cause trouble if we later abs() it. */ + if (val < -0x7fff) + return -0x7fff; + return val; + } + } + else if (AXIS_POS_GET(joyaxis) < pad->num_axes) + { + int16_t val = sdl3_joypad_get_axis(pad, AXIS_POS_GET(joyaxis)); + if (val > 0) + return val; + } + + return 0; +} + +static int16_t sdl3_joypad_axis(unsigned port, uint32_t joyaxis) +{ + if (port >= MAX_USERS) + return 0; + + if (!sdl3_joypads[port].joypad) + return 0; + + return sdl3_joypad_axis_state(&sdl3_joypads[port], joyaxis); +} + +static int16_t sdl3_joypad_state( + rarch_joypad_info_t *joypad_info, + const struct retro_keybind *binds, + unsigned port) +{ + int i; + int16_t ret = 0; + uint16_t port_idx = joypad_info->joy_idx; + + if (port_idx >= MAX_USERS) + return 0; + + if (!sdl3_joypads[port_idx].joypad) + return 0; + + for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++) + { + /* Auto-binds are per joypad, not per user. */ + const uint64_t joykey = (binds[i].joykey != NO_BTN) + ? binds[i].joykey : joypad_info->auto_binds[i].joykey; + const uint32_t joyaxis = (binds[i].joyaxis != AXIS_NONE) + ? binds[i].joyaxis : joypad_info->auto_binds[i].joyaxis; + + if ( + (uint16_t)joykey != NO_BTN + && sdl3_joypad_button_state(&sdl3_joypads[port_idx], (uint16_t)joykey) + ) + ret |= (1 << i); + else if (joyaxis != AXIS_NONE && + ((float)abs(sdl3_joypad_axis_state(&sdl3_joypads[port_idx], joyaxis)) + / 0x8000) > joypad_info->axis_threshold) + ret |= (1 << i); + } + + return ret; +} + +static void sdl3_joypad_poll(void) +{ + SDL_Event event; + + SDL_PumpEvents(); + + while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, + SDL_EVENT_JOYSTICK_ADDED, SDL_EVENT_JOYSTICK_REMOVED) > 0) + { + switch (event.type) + { + case SDL_EVENT_JOYSTICK_ADDED: + sdl3_joypad_connect(event.jdevice.which); + break; + case SDL_EVENT_JOYSTICK_REMOVED: + sdl3_joypad_disconnect(event.jdevice.which); + break; + } + } + + SDL_UpdateGamepads(); + + /* Flush all remaining gamepad/joystick input events, since we handle it directly. */ + SDL_FlushEvents(SDL_EVENT_JOYSTICK_AXIS_MOTION, SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED); +} + +static bool sdl3_joypad_set_rumble(unsigned pad, + enum retro_rumble_effect effect, uint16_t strength) +{ + uint16_t low, high; + + if (pad >= MAX_USERS) + return false; + + switch (effect) + { + case RETRO_RUMBLE_STRONG: + case RETRO_RUMBLE_WEAK: + break; + default: + return false; + } + + /* Send both effects, while not clobbering the other effect. */ + sdl3_joypads[pad].rumble[effect] = strength; + low = (uint16_t)((sdl3_joypads[pad].rumble[RETRO_RUMBLE_STRONG] * sdl3_joypads[pad].rumble_gain) / 100); + high = (uint16_t)((sdl3_joypads[pad].rumble[RETRO_RUMBLE_WEAK] * sdl3_joypads[pad].rumble_gain) / 100); + + /* The frontend re-issues rumble every frame, so just use an arbitrary duration. */ + if (sdl3_joypads[pad].gamepad) + return SDL_RumbleGamepad(sdl3_joypads[pad].gamepad, low, high, 5000); + else if (sdl3_joypads[pad].joypad) + return SDL_RumbleJoystick(sdl3_joypads[pad].joypad, low, high, 5000); + + return false; +} + +/** + * Enables or disables a sensor on the specified gamepad. + * + * @param pad Index of the gamepad. + * @param action Sensor action to perform (enable/disable gyroscope or accelerometer). + * @param rate Requested sensor update rate (unused). + * @return true if the sensor state was set successfully, false otherwise. + */ +static bool sdl3_joypad_set_sensor_state(unsigned pad, + enum retro_sensor_action action, unsigned rate) +{ + if (pad >= MAX_USERS) + return false; + + if (!sdl3_joypads[pad].gamepad) + return false; + + switch (action) + { + case RETRO_SENSOR_GYROSCOPE_ENABLE: + case RETRO_SENSOR_GYROSCOPE_DISABLE: + if (SDL_GamepadHasSensor(sdl3_joypads[pad].gamepad, SDL_SENSOR_GYRO)) + return SDL_SetGamepadSensorEnabled(sdl3_joypads[pad].gamepad, SDL_SENSOR_GYRO, + action == RETRO_SENSOR_GYROSCOPE_ENABLE); + return false; + + case RETRO_SENSOR_ACCELEROMETER_ENABLE: + case RETRO_SENSOR_ACCELEROMETER_DISABLE: + if (SDL_GamepadHasSensor(sdl3_joypads[pad].gamepad, SDL_SENSOR_ACCEL)) + return SDL_SetGamepadSensorEnabled(sdl3_joypads[pad].gamepad, SDL_SENSOR_ACCEL, + action == RETRO_SENSOR_ACCELEROMETER_ENABLE); + return false; + + default: + return false; + } +} + +/** + * Retrieves input data from a connected sensor device, such as a gyroscope or accelerometer. + * + * @return True if the sensor input was successfully handled by this function, false otherwise. + */ +static bool sdl3_joypad_get_sensor_input(unsigned pad, unsigned id, float *value) +{ + SDL_SensorType sensor_type; + float sensor_data[3]; + + if (pad >= MAX_USERS) + return false; + + if (!sdl3_joypads[pad].gamepad) + return false; + + if ((id >= RETRO_SENSOR_ACCELEROMETER_X) && (id <= RETRO_SENSOR_ACCELEROMETER_Z)) + sensor_type = SDL_SENSOR_ACCEL; + else if ((id >= RETRO_SENSOR_GYROSCOPE_X) && (id <= RETRO_SENSOR_GYROSCOPE_Z)) + sensor_type = SDL_SENSOR_GYRO; + else + return false; + + if (!SDL_GetGamepadSensorData(sdl3_joypads[pad].gamepad, sensor_type, sensor_data, 3)) + return false; + + switch (id) + { + case RETRO_SENSOR_ACCELEROMETER_X: + *value = sensor_data[0] / SDL_STANDARD_GRAVITY; + break; + case RETRO_SENSOR_ACCELEROMETER_Y: + *value = sensor_data[2] / SDL_STANDARD_GRAVITY; + break; + case RETRO_SENSOR_ACCELEROMETER_Z: + *value = sensor_data[1] / SDL_STANDARD_GRAVITY; + break; + case RETRO_SENSOR_GYROSCOPE_X: + *value = sensor_data[0]; + break; + case RETRO_SENSOR_GYROSCOPE_Y: + *value = -sensor_data[2]; + break; + case RETRO_SENSOR_GYROSCOPE_Z: + *value = sensor_data[1]; + break; + } + + return true; +} + +/** + * Queries whether or not the joypad is still connected. + * + * We check against the NULL state of the joypad, since that is managed + * by the event poll. SDL_GamepadConnected() or SDL_JoystickConnected() + * would be redundant. + */ +static bool sdl3_joypad_query_pad(unsigned pad) +{ + return pad < MAX_USERS && sdl3_joypads[pad].joypad != NULL; +} + +input_device_driver_t sdl_joypad = { + sdl3_joypad_init, + sdl3_joypad_query_pad, + sdl3_joypad_destroy, + sdl3_joypad_button, + sdl3_joypad_state, + NULL, /* get_buttons */ + sdl3_joypad_axis, + sdl3_joypad_poll, + sdl3_joypad_set_rumble, + sdl3_joypad_set_rumble_gain, + sdl3_joypad_set_sensor_state, + sdl3_joypad_get_sensor_input, + sdl3_joypad_name, + "sdl3", +}; diff --git a/input/input_autodetect_builtin.c b/input/input_autodetect_builtin.c index dc5ca1dea273..76fea748249d 100644 --- a/input/input_autodetect_builtin.c +++ b/input/input_autodetect_builtin.c @@ -37,6 +37,7 @@ #define DECL_AXIS(axis, bind) "input_" #axis "_axis = " #bind "\n" #define DECL_AXIS_EX(axis, bind, name) "input_" #axis "_axis = " #bind "\ninput_" #axis "_axis_label = \"" name "\"\n" #define DECL_MENU(btn) "input_menu_toggle_btn = " #btn "\n" +#define DECL_MENU_EX(btn, name) "input_menu_toggle_btn = " #btn "\ninput_menu_toggle_btn_label = \"" name "\"\n" #define DECL_AUTOCONF_DEVICE(device, driver, binds) "input_device = \"" device "\"\ninput_driver = \"" driver "\"\n" binds #define DECL_AUTOCONF_PID(pid, vid, driver, binds) "input_product_id = " #pid "\ninput_vendor_id = " #vid "\ninput_driver = \"" driver "\"\n" binds @@ -66,6 +67,33 @@ DECL_AXIS(r_x_minus, -2) \ DECL_AXIS(r_y_plus, -3) \ DECL_AXIS(r_y_minus, +3) +#define SDL3_DEFAULT_BINDS \ +DECL_BTN_EX(a, 1, "Right Face Button") /* SDL_GAMEPAD_BUTTON_EAST */ \ +DECL_BTN_EX(b, 0, "Bottom Face Button") /* SDL_GAMEPAD_BUTTON_SOUTH */ \ +DECL_BTN_EX(x, 3, "Top Face Button") /* SDL_GAMEPAD_BUTTON_NORTH */ \ +DECL_BTN_EX(y, 2, "Left Face Button") /* SDL_GAMEPAD_BUTTON_WEST */ \ +DECL_BTN_EX(select, 4, "Back") /* SDL_GAMEPAD_BUTTON_BACK */ \ +DECL_BTN_EX(start, 6, "Start") /* SDL_GAMEPAD_BUTTON_START */ \ +DECL_BTN_EX(up, 11, "D-Pad Up") /* SDL_GAMEPAD_BUTTON_DPAD_UP */ \ +DECL_BTN_EX(down, 12, "D-Pad Down") /* SDL_GAMEPAD_BUTTON_DPAD_DOWN */ \ +DECL_BTN_EX(left, 13, "D-Pad Left") /* SDL_GAMEPAD_BUTTON_DPAD_LEFT */ \ +DECL_BTN_EX(right, 14, "D-Pad Right") /* SDL_GAMEPAD_BUTTON_DPAD_RIGHT */ \ +DECL_BTN_EX(l, 9, "Left Shoulder") /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */ \ +DECL_BTN_EX(r, 10, "Right Shoulder") /* SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER */ \ +DECL_AXIS_EX(l2, +4, "Left Trigger") /* SDL_GAMEPAD_AXIS_LEFT_TRIGGER */ \ +DECL_AXIS_EX(r2, +5, "Right Trigger") /* SDL_GAMEPAD_AXIS_RIGHT_TRIGGER */ \ +DECL_BTN_EX(l3, 7, "Left Stick") /* SDL_GAMEPAD_BUTTON_LEFT_STICK */ \ +DECL_BTN_EX(r3, 8, "Right Stick") /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */ \ +DECL_AXIS_EX(l_x_plus, +0, "Left Thumbstick X+") /* SDL_GAMEPAD_AXIS_LEFTX */ \ +DECL_AXIS_EX(l_x_minus, -0, "Left Thumbstick X-") /* SDL_GAMEPAD_AXIS_LEFTX */ \ +DECL_AXIS_EX(l_y_plus, +1, "Left Thumbstick Y+") /* SDL_GAMEPAD_AXIS_LEFTY */ \ +DECL_AXIS_EX(l_y_minus, -1, "Left Thumbstick Y-") /* SDL_GAMEPAD_AXIS_LEFTY */ \ +DECL_AXIS_EX(r_x_plus, +2, "Right Thumbstick X+") /* SDL_GAMEPAD_AXIS_RIGHTX */ \ +DECL_AXIS_EX(r_x_minus, -2, "Right Thumbstick X-") /* SDL_GAMEPAD_AXIS_RIGHTX */ \ +DECL_AXIS_EX(r_y_plus, -3, "Right Thumbstick Y+") /* SDL_GAMEPAD_AXIS_RIGHTY */ \ +DECL_AXIS_EX(r_y_minus, +3, "Right Thumbstick Y-") /* SDL_GAMEPAD_AXIS_RIGHTY */ \ +DECL_MENU_EX(5, "Guide") /* SDL_GAMEPAD_BUTTON_GUIDE */ + #if defined(DINGUX) && defined(HAVE_SDL_DINGUX) #define DINGUX_SDL_DEFAULT_BINDS \ DECL_BTN_EX(a, 8, "A") \ @@ -720,6 +748,9 @@ const char* const input_builtin_autoconfs[] = #ifdef HAVE_SDL2 DECL_AUTOCONF_DEVICE("Standard Gamepad", "sdl2", SDL2_DEFAULT_BINDS), #endif +#ifdef HAVE_SDL3 + DECL_AUTOCONF_DEVICE("Gamepad", "sdl3", SDL3_DEFAULT_BINDS), +#endif #if defined(DINGUX) && defined(HAVE_SDL_DINGUX) DECL_AUTOCONF_DEVICE("Dingux Gamepad", "sdl_dingux", DINGUX_SDL_DEFAULT_BINDS), #endif diff --git a/input/input_driver.c b/input/input_driver.c index a98b81a9cdc0..5bc785684e25 100644 --- a/input/input_driver.c +++ b/input/input_driver.c @@ -298,7 +298,7 @@ input_device_driver_t *joypad_drivers[] = { #ifdef ANDROID &android_joypad, #endif -#if defined(HAVE_SDL) || defined(HAVE_SDL2) +#if defined(HAVE_SDL3) || defined(HAVE_SDL) || defined(HAVE_SDL2) &sdl_joypad, #endif #if defined(DINGUX) && defined(HAVE_SDL_DINGUX) @@ -622,7 +622,7 @@ const input_device_driver_t *input_joypad_init_driver( } } /* Fall back to first available driver */ - return input_joypad_init_first(data); + return input_joypad_init_first(data); } static bool input_driver_button_combo_hold( @@ -3401,7 +3401,7 @@ void input_overlay_load_active( /** * input_overlay_next_move_touch_masks * @ol : Overlay handle. - * + * * Finds similar descs in the next overlay (i.e. same location and type) * and moves touch masks from active overlay to next. */ @@ -6608,14 +6608,14 @@ static void input_keys_pressed( else input_st->flags |= INP_FLAG_BLOCK_HOTKEY; } - + #ifdef HAVE_MENU /* Prevent triggering menu actions after binding */ if ( !(input_st->flags & INP_FLAG_MENU_PRESS_PENDING) && menu_state_get_ptr()->input_driver_flushing_input) input_st->flags |= INP_FLAG_WAIT_INPUT_RELEASE; #endif - + /* Check libretro input if emulated device type is active, * except device type must be always active in menu. */ if ( !(input_st->flags & INP_FLAG_BLOCK_LIBRETRO_INPUT) diff --git a/input/input_driver.h b/input/input_driver.h index ca3992312be5..f0f68c453676 100644 --- a/input/input_driver.h +++ b/input/input_driver.h @@ -1249,7 +1249,7 @@ extern input_device_driver_t linuxraw_joypad; extern input_device_driver_t parport_joypad; extern input_device_driver_t udev_joypad; extern input_device_driver_t xinput_joypad; -extern input_device_driver_t sdl_joypad; +extern input_device_driver_t sdl_joypad; /* SDL2 or SDL3. @see sdl_joypad.c, sdl3_joypad.c. */ extern input_device_driver_t sdl_dingux_joypad; extern input_device_driver_t ps4_joypad; extern input_device_driver_t ps3_joypad; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 3c19bc340da9..f2ce7c44c59a 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -416,7 +416,7 @@ static int filebrowser_parse( ? FILE_TYPE_VIDEO_FONT : (enum msg_file_type)type_default; - if ( type == DISPLAYLIST_CORES_DETECTED + if ( type == DISPLAYLIST_CORES_DETECTED && path_is_compressed_file(file_path)) file_type = FILE_TYPE_CARCHIVE; break; @@ -2403,6 +2403,9 @@ static unsigned menu_displaylist_parse_system_info(file_list_t *list) #ifdef HAVE_SDL2 {SUPPORTS_SDL2, "SDL 2"}, #endif +#ifdef HAVE_SDL3 + {SUPPORTS_SDL3, "SDL 3"}, +#endif #ifdef HAVE_X11 {SUPPORTS_X11, "X11"}, #endif @@ -5112,7 +5115,7 @@ static unsigned menu_displaylist_parse_content_information( if (core_info_find(core_path, &core_info)) { - core_supports_no_game = (core_info->flags + core_supports_no_game = (core_info->flags & CORE_INFO_FLAG_SUPPORTS_NO_GAME); if (core_info->display_name && *core_info->display_name) strlcpy(core_name, core_info->display_name, sizeof(core_name)); diff --git a/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj b/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj index 8e08780cdf53..e7fb00e5bb79 100644 --- a/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj +++ b/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj @@ -422,6 +422,7 @@ 05C5D58220E3DD0900654EE4 /* input_autodetect_builtin.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = input_autodetect_builtin.c; sourceTree = ""; }; 05C5D58320E3DD0900654EE4 /* input_keymaps.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = input_keymaps.c; sourceTree = ""; }; 05C5D58720E3DD0900654EE4 /* sdl_joypad.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sdl_joypad.c; sourceTree = ""; }; + 05C5D58820E3DD0900654EE4 /* sdl3_joypad.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sdl3_joypad.c; sourceTree = ""; }; 05C5D58D20E3DD0900654EE4 /* mfi_joypad.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = mfi_joypad.m; sourceTree = ""; }; 05C5D59820E3DD0A00654EE4 /* hid_joypad.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = hid_joypad.c; sourceTree = ""; }; 05C5D5A320E3DD0A00654EE4 /* input_driver.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = input_driver.c; sourceTree = ""; }; @@ -1154,6 +1155,7 @@ 05C5D59820E3DD0A00654EE4 /* hid_joypad.c */, 05C5D58D20E3DD0900654EE4 /* mfi_joypad.m */, 05C5D58720E3DD0900654EE4 /* sdl_joypad.c */, + 05C5D58820E3DD0900654EE4 /* sdl3_joypad.c */, ); path = drivers_joypad; sourceTree = ""; diff --git a/qb/config.libs.sh b/qb/config.libs.sh index 38d049db0b1d..61f124415f3d 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -394,7 +394,13 @@ check_val '' PIPEWIRE -lpipewire-0.3 '' libpipewire-0.3 '' '' false check_val '' PIPEWIRE_STABLE -lpipewire-0.3 '' libpipewire-0.3 1.0.0 '' false check_val '' SDL -lSDL SDL sdl 1.2.10 '' true check_val '' SDL2 -lSDL2 SDL2 sdl2 2.0.0 '' true +check_val '' SDL3 -lSDL3 SDL3 sdl3 3.2.20 '' true +if [ "$HAVE_SDL3" = 'yes' ] && { [ "$HAVE_SDL2" = 'yes' ] || [ "$HAVE_SDL" = 'yes' ]; }; then + die : 'Notice: SDL drivers will be replaced by SDL3 ones.' + HAVE_SDL=no + HAVE_SDL2=no +fi if [ "$HAVE_SDL2" = 'yes' ] && [ "$HAVE_SDL" = 'yes' ]; then die : 'Notice: SDL drivers will be replaced by SDL2 ones.' HAVE_SDL=no diff --git a/qb/config.params.sh b/qb/config.params.sh index b00ae239a0de..8cc1e23944c1 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -49,6 +49,7 @@ HAVE_DYNAMIC=yes # Dynamic loading of libretro library HAVE_SDL=auto # SDL support C89_SDL=no HAVE_SDL2=auto # SDL2 support (disables SDL 1.x) +HAVE_SDL3=auto # SDL3 support (disables SDL 1.x and SDL 2.x) C89_SDL2=no HAVE_LIBUSB=auto # Libusb HID support C89_LIBUSB=no diff --git a/retroarch.c b/retroarch.c index b4b8a1da0ddc..ce756c16df0f 100644 --- a/retroarch.c +++ b/retroarch.c @@ -223,7 +223,9 @@ #include "ai/game_ai.h" #endif -#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX) +#if defined(HAVE_SDL3) +#include +#elif defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX) #include "SDL.h" #endif @@ -6033,7 +6035,7 @@ static void global_free(struct rarch_state *p_rarch) retroarch_override_setting_free_state(); } -#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX) +#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX) || defined(HAVE_SDL3) static void sdl_exit(void) { /* Quit any SDL subsystems, then quit @@ -6135,7 +6137,7 @@ void main_exit(void *args) CoUninitialize(); #endif -#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX) +#if defined(HAVE_SDL) || defined(HAVE_SDL2) || defined(HAVE_SDL_DINGUX) || defined(HAVE_SDL3) sdl_exit(); #endif } @@ -6627,6 +6629,9 @@ static void retroarch_print_features(void) #ifdef HAVE_SDL2 _len += _PSUPP_BUF(buf, _len, SUPPORTS_SDL2, "SDL2", "SDL2 input/audio/video drivers"); #endif +#ifdef HAVE_SDL3 + _len += _PSUPP_BUF(buf, _len, SUPPORTS_SDL3, "SDL3", "SDL3 joypad driver"); +#endif #ifdef HAVE_X11 _len += _PSUPP_BUF(buf, _len, SUPPORTS_X11, "X11", "X11 input/video drivers"); #endif diff --git a/tasks/task_autodetect.c b/tasks/task_autodetect.c index f52e0f1b6b02..b2b3879e211d 100644 --- a/tasks/task_autodetect.c +++ b/tasks/task_autodetect.c @@ -45,13 +45,6 @@ #include "../retroarch.h" #include "../runloop.h" -enum autoconfig_handle_flags -{ - AUTOCONF_FLAG_AUTOCONFIG_ENABLED = (1 << 0), - AUTOCONF_FLAG_SUPPRESS_NOTIFICATIONS = (1 << 1), - AUTOCONF_FLAG_SUPPRESS_FAILURE_NOTIF = (1 << 2) -}; - typedef struct { char *dir_autoconfig; @@ -747,6 +740,9 @@ static void input_autoconfigure_connect_handler(retro_task_t *task) else if (string_is_equal(autoconfig_handle->device_info.joypad_driver, "sdl2")) fallback_device_name = "Standard Gamepad"; + else if (string_is_equal(autoconfig_handle->device_info.joypad_driver, + "sdl3")) + fallback_device_name = "Gamepad"; #ifdef HAVE_TEST_DRIVERS else if (string_is_equal(autoconfig_handle->device_info.joypad_driver, "test")) @@ -765,6 +761,7 @@ static void input_autoconfigure_connect_handler(retro_task_t *task) * below). A stack buffer of matching size sidesteps both * issues. */ char name_backup[sizeof(autoconfig_handle->device_info.name)]; + bool fallback_matched; strlcpy(name_backup, autoconfig_handle->device_info.name, sizeof(name_backup)); @@ -773,15 +770,15 @@ static void input_autoconfigure_connect_handler(retro_task_t *task) fallback_device_name, sizeof(autoconfig_handle->device_info.name)); - /* This is not a genuine match - leave - * match_found set to 'false' regardless - * of the outcome */ - input_autoconfigure_scan_config_files_internal( - autoconfig_handle); + /* Apply the fallback built-in profile. */ + fallback_matched = input_autoconfigure_scan_config_files_internal(autoconfig_handle); strlcpy(autoconfig_handle->device_info.name, name_backup, sizeof(autoconfig_handle->device_info.name)); + + if (fallback_matched && (autoconfig_handle->flags & AUTOCONF_FLAG_HAS_STANDARD_MAPPING)) + match_found = true; } } @@ -866,6 +863,35 @@ bool input_autoconfigure_connect( unsigned port, unsigned vid, unsigned pid) +{ + return input_autoconfigure_connect_ex(name, display_name, phys, + driver, port, vid, pid, 0); +} + +/** + * Queues an asynchronous task to autoconfigure a newly connected device. + * + * @param name Device name reported by the driver, used for name-based profile matching. May be NULL. + * @param display_name Human-readable name, or NULL to use the matched profile. + * @param phys Physical location string (e.g. USB path), or NULL. + * @param driver Joypad driver identifier (e.g. "sdl3", "udev"). + * @param port Input port to configure (0 .. MAX_INPUT_DEVICES-1). + * @param vid USB vendor ID, or 0 if unknown. + * @param pid USB product ID, or 0 if unknown. + * @param flags An enum autoconfig_handle_flags bitmask seeding the initial state. + * + * @return true if the autoconfigure task was queued, false otherwise. + * @see input_autoconfigure_connect() + */ +bool input_autoconfigure_connect_ex( + const char *name, + const char *display_name, + const char *phys, + const char *driver, + unsigned port, + unsigned vid, + unsigned pid, + uint8_t flags) { task_finder_data_t find_data; retro_task_t *task = NULL; @@ -907,6 +933,7 @@ bool input_autoconfigure_connect( autoconfig_handle->device_info.joypad_driver[0] = '\0'; autoconfig_handle->device_info.autoconfigured = false; autoconfig_handle->device_info.name_index = 0; + autoconfig_handle->flags = flags; if (autoconfig_enabled) autoconfig_handle->flags |= AUTOCONF_FLAG_AUTOCONFIG_ENABLED; if (!notification_show_autoconfig) diff --git a/tasks/tasks_internal.h b/tasks/tasks_internal.h index 5876be816bb1..46761c25ca49 100644 --- a/tasks/tasks_internal.h +++ b/tasks/tasks_internal.h @@ -267,6 +267,25 @@ void *savefile_ptr_get(void); void path_init_savefile_new(void); /* Autoconfigure tasks */ + +/** + * Flags controlling how a device is autoconfigured. + * + * @see input_autoconfigure_connect_ex() + */ +enum autoconfig_handle_flags +{ + AUTOCONF_FLAG_AUTOCONFIG_ENABLED = (1 << 0), + AUTOCONF_FLAG_SUPPRESS_NOTIFICATIONS = (1 << 1), + AUTOCONF_FLAG_SUPPRESS_FAILURE_NOTIF = (1 << 2), + /** + * Device has a known, authoritative mapping, so fallback profiles are treated as matches. + * + * This is used in the SDL3 driver, for instance, where mappings are provided directly. + */ + AUTOCONF_FLAG_HAS_STANDARD_MAPPING = (1 << 3) +}; + void input_autoconfigure_blissbox_override_handler( int vid, int pid, char *device_name, size_t len); bool input_autoconfigure_connect( @@ -277,6 +296,15 @@ bool input_autoconfigure_connect( unsigned port, unsigned vid, unsigned pid); +bool input_autoconfigure_connect_ex( + const char *name, + const char *display_name, + const char *phys, + const char *driver, + unsigned port, + unsigned vid, + unsigned pid, + uint8_t flags); bool input_autoconfigure_disconnect( unsigned port, const char *name);