From d6fbbbe14dddfa09d6168267a0fcd39dea5a398a Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 30 Oct 2026 07:46:23 -0700
Subject: [PATCH] Cleaned up udev support
You can call SDL_UDEV_GetUdevSyms() independently of SDL_UDEV_Init(). The first loads the udev library and symbols, and the second creates a udev context and registers for hotplug events.
This also makes sure the each call to SDL_UDEV_GetUdevSyms() has a matching SDL_UDEV_ReleaseUdevSyms() and each call to SDL_UDEV_Init() has a matching SDL_UDEV_Quit().
---
src/camera/v4l2/SDL_camera_v4l2.c | 10 +-
src/core/linux/SDL_udev.c | 148 ++++++++++++++----------------
src/core/linux/SDL_udev.h | 15 ---
src/hidapi/SDL_hidapi.c | 21 ++++-
4 files changed, 94 insertions(+), 100 deletions(-)
diff --git a/src/camera/v4l2/SDL_camera_v4l2.c b/src/camera/v4l2/SDL_camera_v4l2.c
index 55e5d675adcf6..e440618ecc009 100644
--- a/src/camera/v4l2/SDL_camera_v4l2.c
+++ b/src/camera/v4l2/SDL_camera_v4l2.c
@@ -899,6 +899,8 @@ static void V4L2_FreeDeviceHandle(SDL_Camera *device)
}
#ifdef SDL_USE_LIBUDEV
+static bool V4L2_udev_initialized;
+
static bool FindV4L2CameraByPathCallback(void *userdata, SDL_Camera *device)
{
const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
@@ -927,8 +929,11 @@ static void CameraUdevCallback(SDL_UDEV_deviceevent udev_type, int udev_class, c
static void V4L2_Deinitialize(void)
{
#ifdef SDL_USE_LIBUDEV
- SDL_UDEV_DelCallback(CameraUdevCallback);
- SDL_UDEV_Quit();
+ if (V4L2_udev_initialized) {
+ SDL_UDEV_DelCallback(CameraUdevCallback);
+ SDL_UDEV_Quit();
+ V4L2_udev_initialized = false;
+ }
#endif // SDL_USE_LIBUDEV
}
@@ -936,6 +941,7 @@ static void V4L2_DetectDevices(void)
{
#ifdef SDL_USE_LIBUDEV
if (SDL_UDEV_Init()) {
+ V4L2_udev_initialized = true;
if (SDL_UDEV_AddCallback(CameraUdevCallback)) {
SDL_UDEV_Scan(); // Force a scan to build the initial device list
}
diff --git a/src/core/linux/SDL_udev.c b/src/core/linux/SDL_udev.c
index 941c93b73ac38..0fa321b0b651c 100644
--- a/src/core/linux/SDL_udev.c
+++ b/src/core/linux/SDL_udev.c
@@ -52,11 +52,25 @@ SDL_ELF_NOTE_DLOPEN(
)
#endif
-static SDL_UDEV_PrivateData *_this = NULL;
+typedef struct SDL_UDEV_Context
+{
+ SDL_SpinLock syms_lock;
+ int syms_ref_count;
+ SDL_SharedObject *udev_handle;
+ SDL_UDEV_Symbols syms;
+
+ // The udev context for the main thread
+ int udev_ref_count;
+ struct udev *udev;
+ struct udev_monitor *udev_mon;
+ SDL_UDEV_CallbackList *first, *last;
+
+} SDL_UDEV_Context;
+
+static SDL_UDEV_Context SDL_udev_context, *_this = &SDL_udev_context;
#ifdef SDL_UDEV_DYNAMIC
static bool SDL_UDEV_load_sym(const char *fn, void **addr);
-static bool SDL_UDEV_load_syms(void);
#endif
static bool SDL_UDEV_hotplug_update_available(void);
static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len);
@@ -79,7 +93,6 @@ static bool SDL_UDEV_load_sym(const char *fn, void **addr)
static bool SDL_UDEV_load_syms(void)
{
-
#ifdef SDL_UDEV_DYNAMIC
/* cast funcs to char* first, to please GCC's strict aliasing rules. */
#define SDL_UDEV_SYM(x) \
@@ -148,17 +161,14 @@ static bool SDL_UDEV_hotplug_update_available(void)
bool SDL_UDEV_Init(void)
{
- if (!_this) {
- _this = (SDL_UDEV_PrivateData *)SDL_calloc(1, sizeof(*_this));
- if (!_this) {
- return false;
- }
-
- if (!SDL_UDEV_LoadLibrary()) {
- SDL_UDEV_Quit();
+ if (_this->udev_ref_count == 0) {
+ if (!SDL_UDEV_GetUdevSyms()) {
return false;
}
+ }
+ ++_this->udev_ref_count;
+ if (_this->udev_ref_count == 1) {
/* Set up udev monitoring
* Listen for input devices (mouse, keyboard, joystick, etc) and sound devices
*/
@@ -179,26 +189,16 @@ bool SDL_UDEV_Init(void)
_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL);
_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "video4linux", NULL);
_this->syms.udev_monitor_enable_receiving(_this->udev_mon);
-
- // Do an initial scan of existing devices
- SDL_UDEV_Scan();
}
-
- _this->ref_count += 1;
-
return true;
}
void SDL_UDEV_Quit(void)
{
- if (!_this) {
- return;
- }
-
- _this->ref_count -= 1;
-
- if (_this->ref_count < 1) {
+ SDL_assert(_this->udev_ref_count > 0);
+ --_this->udev_ref_count;
+ if (_this->udev_ref_count == 0) {
if (_this->udev_mon) {
_this->syms.udev_monitor_unref(_this->udev_mon);
_this->udev_mon = NULL;
@@ -208,16 +208,15 @@ void SDL_UDEV_Quit(void)
_this->udev = NULL;
}
- // Remove existing devices
+ // Remove existing callbacks
while (_this->first) {
SDL_UDEV_CallbackList *item = _this->first;
_this->first = _this->first->next;
SDL_free(item);
}
+ _this->last = NULL;
- SDL_UDEV_UnloadLibrary();
- SDL_free(_this);
- _this = NULL;
+ SDL_UDEV_ReleaseUdevSyms();
}
}
@@ -227,13 +226,12 @@ bool SDL_UDEV_Scan(void)
struct udev_list_entry *devs = NULL;
struct udev_list_entry *item = NULL;
- if (!_this) {
+ if (!_this->udev) {
return true;
}
enumerate = _this->syms.udev_enumerate_new(_this->udev);
if (!enumerate) {
- SDL_UDEV_Quit();
return SDL_SetError("udev_enumerate_new() failed");
}
@@ -264,7 +262,7 @@ bool SDL_UDEV_GetProductInfo(const char *device_path, struct input_id *inpid, in
const char *val;
int class_temp;
- if (!_this) {
+ if (!_this->udev) {
return false;
}
@@ -328,7 +326,7 @@ char *SDL_UDEV_GetProductSerial(const char *device_path)
const char *val;
char *result = NULL;
- if (!_this) {
+ if (!_this->udev) {
return NULL;
}
@@ -359,64 +357,50 @@ char *SDL_UDEV_GetProductSerial(const char *device_path)
return result;
}
-void SDL_UDEV_UnloadLibrary(void)
+static void SDL_UDEV_UnloadLibrary(void)
{
- if (!_this) {
- return;
- }
-
if (_this->udev_handle) {
SDL_UnloadObject(_this->udev_handle);
_this->udev_handle = NULL;
}
+ SDL_zero(_this->syms);
}
-bool SDL_UDEV_LoadLibrary(void)
+static bool SDL_UDEV_LoadLibrary(void)
{
- bool result = true;
-
- if (!_this) {
- return SDL_SetError("UDEV not initialized");
+ if (_this->syms.udev_new) {
+ return true;
}
// See if there is a udev library already loaded
if (SDL_UDEV_load_syms()) {
return true;
}
+ SDL_UDEV_UnloadLibrary();
#ifdef SDL_UDEV_DYNAMIC
// Check for the build environment's libudev first
- if (!_this->udev_handle) {
- _this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC);
- if (_this->udev_handle) {
- result = SDL_UDEV_load_syms();
- if (!result) {
- SDL_UDEV_UnloadLibrary();
- }
+ _this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC);
+ if (_this->udev_handle) {
+ if (SDL_UDEV_load_syms()) {
+ return true;
}
+ SDL_UDEV_UnloadLibrary();
}
#endif
- if (!_this->udev_handle) {
- for (int i = 0; i < SDL_arraysize(SDL_UDEV_LIBS); i++) {
- _this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]);
- if (_this->udev_handle) {
- result = SDL_UDEV_load_syms();
- if (!result) {
- SDL_UDEV_UnloadLibrary();
- } else {
- break;
- }
+ for (int i = 0; i < SDL_arraysize(SDL_UDEV_LIBS); i++) {
+ _this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]);
+ if (_this->udev_handle) {
+ if (SDL_UDEV_load_syms()) {
+ return true;
}
- }
-
- if (!_this->udev_handle) {
- result = false;
- // Don't call SDL_SetError(): SDL_LoadObject already did.
+ SDL_UDEV_UnloadLibrary();
}
}
- return result;
+ // Don't call SDL_SetError(): SDL_LoadObject or SDL_LoadFunction already did.
+ return false;
}
static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len)
@@ -601,10 +585,6 @@ void SDL_UDEV_Poll(void)
struct udev_device *dev = NULL;
const char *action = NULL;
- if (!_this) {
- return;
- }
-
while (SDL_UDEV_hotplug_update_available()) {
dev = _this->syms.udev_monitor_receive_device(_this->udev_mon);
if (!dev) {
@@ -627,6 +607,11 @@ void SDL_UDEV_Poll(void)
bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
{
SDL_UDEV_CallbackList *item;
+
+ if (_this->udev_ref_count < 1) {
+ return SDL_SetError("UDEV not initialized");
+ }
+
item = (SDL_UDEV_CallbackList *)SDL_calloc(1, sizeof(SDL_UDEV_CallbackList));
if (!item) {
return false;
@@ -649,10 +634,6 @@ void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
SDL_UDEV_CallbackList *item;
SDL_UDEV_CallbackList *prev = NULL;
- if (!_this) {
- return;
- }
-
for (item = _this->first; item; item = item->next) {
// found it, remove it.
if (item->callback == cb) {
@@ -674,17 +655,28 @@ void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void)
{
- if (!SDL_UDEV_Init()) {
- SDL_SetError("Could not initialize UDEV");
- return NULL;
+ const SDL_UDEV_Symbols *syms = NULL;
+
+ SDL_LockSpinlock(&_this->syms_lock);
+ if (SDL_UDEV_LoadLibrary()) {
+ ++_this->syms_ref_count;
+ syms = &_this->syms;
}
+ SDL_UnlockSpinlock(&_this->syms_lock);
- return &_this->syms;
+ return syms;
}
void SDL_UDEV_ReleaseUdevSyms(void)
{
- SDL_UDEV_Quit();
+ SDL_LockSpinlock(&_this->syms_lock);
+ SDL_assert(_this->syms_ref_count > 0);
+
+ --_this->syms_ref_count;
+ if (_this->syms_ref_count == 0) {
+ SDL_UDEV_UnloadLibrary();
+ }
+ SDL_UnlockSpinlock(&_this->syms_lock);
}
#endif // SDL_USE_LIBUDEV
diff --git a/src/core/linux/SDL_udev.h b/src/core/linux/SDL_udev.h
index 8fe273d15d428..b62f2a5709396 100644
--- a/src/core/linux/SDL_udev.h
+++ b/src/core/linux/SDL_udev.h
@@ -91,23 +91,8 @@ typedef struct SDL_UDEV_Symbols
} SDL_UDEV_Symbols;
-typedef struct SDL_UDEV_PrivateData
-{
- const char *udev_library;
- SDL_SharedObject *udev_handle;
- struct udev *udev;
- struct udev_monitor *udev_mon;
- int ref_count;
- SDL_UDEV_CallbackList *first, *last;
-
- // Function pointers
- SDL_UDEV_Symbols syms;
-} SDL_UDEV_PrivateData;
-
extern bool SDL_UDEV_Init(void);
extern void SDL_UDEV_Quit(void);
-extern void SDL_UDEV_UnloadLibrary(void);
-extern bool SDL_UDEV_LoadLibrary(void);
extern void SDL_UDEV_Poll(void);
extern bool SDL_UDEV_Scan(void);
extern bool SDL_UDEV_GetProductInfo(const char *device_path, struct input_id *inpid, int *class, char **driver);
diff --git a/src/hidapi/SDL_hidapi.c b/src/hidapi/SDL_hidapi.c
index 5d4291f901e62..deeab48857846 100644
--- a/src/hidapi/SDL_hidapi.c
+++ b/src/hidapi/SDL_hidapi.c
@@ -281,6 +281,8 @@ static void HIDAPI_InitializeDiscovery(void)
if (SDL_HIDAPI_discovery.m_pUdev != NULL) {
SDL_HIDAPI_discovery.m_pUdevMonitor = usyms->udev_monitor_new_from_netlink(SDL_HIDAPI_discovery.m_pUdev, "udev");
if (SDL_HIDAPI_discovery.m_pUdevMonitor != NULL) {
+ usyms->udev_monitor_filter_add_match_subsystem_devtype(SDL_HIDAPI_discovery.m_pUdevMonitor, "hidraw", NULL);
+ usyms->udev_monitor_filter_add_match_subsystem_devtype(SDL_HIDAPI_discovery.m_pUdevMonitor, "usb", NULL);
usyms->udev_monitor_enable_receiving(SDL_HIDAPI_discovery.m_pUdevMonitor);
SDL_HIDAPI_discovery.m_nUdevFd = usyms->udev_monitor_get_fd(SDL_HIDAPI_discovery.m_pUdevMonitor);
SDL_HIDAPI_discovery.m_bCanGetNotifications = true;
@@ -1178,10 +1180,18 @@ int SDL_hid_init(void)
++attempts;
#ifdef SDL_PLATFORM_LINUX
udev_ctx = SDL_UDEV_GetUdevSyms();
-#endif // __LINUX __
- if (udev_ctx && PLATFORM_hid_init() == 0) {
+ if (udev_ctx && PLATFORM_hid_init() != 0) {
+ SDL_UDEV_ReleaseUdevSyms();
+ udev_ctx = NULL;
+ }
+ if (udev_ctx) {
+ ++success;
+ }
+#else
+ if (PLATFORM_hid_init() == 0) {
++success;
}
+#endif
#endif // HAVE_PLATFORM_BACKEND
if (attempts > 0 && success == 0) {
@@ -1216,10 +1226,11 @@ int SDL_hid_exit(void)
#ifdef HAVE_PLATFORM_BACKEND
if (udev_ctx) {
result |= PLATFORM_hid_exit();
- }
#ifdef SDL_PLATFORM_LINUX
- SDL_UDEV_ReleaseUdevSyms();
-#endif // __LINUX __
+ SDL_UDEV_ReleaseUdevSyms();
+ udev_ctx = NULL;
+#endif
+ }
#endif // HAVE_PLATFORM_BACKEND
#ifdef HAVE_LIBUSB