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);