Path: blob/master/thirdparty/sdl/core/linux/SDL_udev.c
10279 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122/*23* To list the properties of a device, try something like:24* udevadm info -a -n snd/hwC0D0 (for a sound card)25* udevadm info --query=all -n input/event3 (for a keyboard, mouse, etc)26* udevadm info --query=property -n input/event227*/28#include "SDL_udev.h"2930#ifdef SDL_USE_LIBUDEV3132#include <linux/input.h>33#include <sys/stat.h>3435#include "SDL_evdev_capabilities.h"36#include "../unix/SDL_poll.h"3738static const char *SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" };3940SDL_UDEV_PrivateData *SDL_UDEV_PrivateData_this = NULL;41#define _this SDL_UDEV_PrivateData_this4243static bool SDL_UDEV_load_sym(const char *fn, void **addr);44static bool SDL_UDEV_load_syms(void);45static bool SDL_UDEV_hotplug_update_available(void);46static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len);47static int guess_device_class(struct udev_device *dev);48static int device_class(struct udev_device *dev);49static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev);5051static bool SDL_UDEV_load_sym(const char *fn, void **addr)52{53*addr = SDL_LoadFunction(_this->udev_handle, fn);54if (!*addr) {55// Don't call SDL_SetError(): SDL_LoadFunction already did.56return false;57}5859return true;60}6162static bool SDL_UDEV_load_syms(void)63{64/* cast funcs to char* first, to please GCC's strict aliasing rules. */65#define SDL_UDEV_SYM(x) \66if (!SDL_UDEV_load_sym(#x, (void **)(char *)&_this->syms.x)) \67return false6869SDL_UDEV_SYM(udev_device_get_action);70SDL_UDEV_SYM(udev_device_get_devnode);71SDL_UDEV_SYM(udev_device_get_syspath);72SDL_UDEV_SYM(udev_device_get_subsystem);73SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype);74SDL_UDEV_SYM(udev_device_get_property_value);75SDL_UDEV_SYM(udev_device_get_sysattr_value);76SDL_UDEV_SYM(udev_device_new_from_syspath);77SDL_UDEV_SYM(udev_device_unref);78SDL_UDEV_SYM(udev_enumerate_add_match_property);79SDL_UDEV_SYM(udev_enumerate_add_match_subsystem);80SDL_UDEV_SYM(udev_enumerate_get_list_entry);81SDL_UDEV_SYM(udev_enumerate_new);82SDL_UDEV_SYM(udev_enumerate_scan_devices);83SDL_UDEV_SYM(udev_enumerate_unref);84SDL_UDEV_SYM(udev_list_entry_get_name);85SDL_UDEV_SYM(udev_list_entry_get_next);86SDL_UDEV_SYM(udev_monitor_enable_receiving);87SDL_UDEV_SYM(udev_monitor_filter_add_match_subsystem_devtype);88SDL_UDEV_SYM(udev_monitor_get_fd);89SDL_UDEV_SYM(udev_monitor_new_from_netlink);90SDL_UDEV_SYM(udev_monitor_receive_device);91SDL_UDEV_SYM(udev_monitor_unref);92SDL_UDEV_SYM(udev_new);93SDL_UDEV_SYM(udev_unref);94SDL_UDEV_SYM(udev_device_new_from_devnum);95SDL_UDEV_SYM(udev_device_get_devnum);96#undef SDL_UDEV_SYM9798return true;99}100101static bool SDL_UDEV_hotplug_update_available(void)102{103if (_this->udev_mon) {104const int fd = _this->syms.udev_monitor_get_fd(_this->udev_mon);105if (SDL_IOReady(fd, SDL_IOR_READ, 0)) {106return true;107}108}109return false;110}111112bool SDL_UDEV_Init(void)113{114if (!_this) {115_this = (SDL_UDEV_PrivateData *)SDL_calloc(1, sizeof(*_this));116if (!_this) {117return false;118}119120if (!SDL_UDEV_LoadLibrary()) {121SDL_UDEV_Quit();122return false;123}124125/* Set up udev monitoring126* Listen for input devices (mouse, keyboard, joystick, etc) and sound devices127*/128129_this->udev = _this->syms.udev_new();130if (!_this->udev) {131SDL_UDEV_Quit();132return SDL_SetError("udev_new() failed");133}134135_this->udev_mon = _this->syms.udev_monitor_new_from_netlink(_this->udev, "udev");136if (!_this->udev_mon) {137SDL_UDEV_Quit();138return SDL_SetError("udev_monitor_new_from_netlink() failed");139}140141_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL);142_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL);143_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "video4linux", NULL);144_this->syms.udev_monitor_enable_receiving(_this->udev_mon);145146// Do an initial scan of existing devices147SDL_UDEV_Scan();148}149150_this->ref_count += 1;151152return true;153}154155void SDL_UDEV_Quit(void)156{157if (!_this) {158return;159}160161_this->ref_count -= 1;162163if (_this->ref_count < 1) {164165if (_this->udev_mon) {166_this->syms.udev_monitor_unref(_this->udev_mon);167_this->udev_mon = NULL;168}169if (_this->udev) {170_this->syms.udev_unref(_this->udev);171_this->udev = NULL;172}173174// Remove existing devices175while (_this->first) {176SDL_UDEV_CallbackList *item = _this->first;177_this->first = _this->first->next;178SDL_free(item);179}180181SDL_UDEV_UnloadLibrary();182SDL_free(_this);183_this = NULL;184}185}186187bool SDL_UDEV_Scan(void)188{189struct udev_enumerate *enumerate = NULL;190struct udev_list_entry *devs = NULL;191struct udev_list_entry *item = NULL;192193if (!_this) {194return true;195}196197enumerate = _this->syms.udev_enumerate_new(_this->udev);198if (!enumerate) {199SDL_UDEV_Quit();200return SDL_SetError("udev_enumerate_new() failed");201}202203_this->syms.udev_enumerate_add_match_subsystem(enumerate, "input");204_this->syms.udev_enumerate_add_match_subsystem(enumerate, "sound");205_this->syms.udev_enumerate_add_match_subsystem(enumerate, "video4linux");206207_this->syms.udev_enumerate_scan_devices(enumerate);208devs = _this->syms.udev_enumerate_get_list_entry(enumerate);209for (item = devs; item; item = _this->syms.udev_list_entry_get_next(item)) {210const char *path = _this->syms.udev_list_entry_get_name(item);211struct udev_device *dev = _this->syms.udev_device_new_from_syspath(_this->udev, path);212if (dev) {213device_event(SDL_UDEV_DEVICEADDED, dev);214_this->syms.udev_device_unref(dev);215}216}217218_this->syms.udev_enumerate_unref(enumerate);219return true;220}221222bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class)223{224struct stat statbuf;225char type;226struct udev_device *dev;227const char* val;228int class_temp;229230if (!_this) {231return false;232}233234if (stat(device_path, &statbuf) == -1) {235return false;236}237238if (S_ISBLK(statbuf.st_mode)) {239type = 'b';240}241else if (S_ISCHR(statbuf.st_mode)) {242type = 'c';243}244else {245return false;246}247248dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev);249250if (!dev) {251return false;252}253254val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID");255if (val) {256*vendor = (Uint16)SDL_strtol(val, NULL, 16);257}258259val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID");260if (val) {261*product = (Uint16)SDL_strtol(val, NULL, 16);262}263264val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION");265if (val) {266*version = (Uint16)SDL_strtol(val, NULL, 16);267}268269class_temp = device_class(dev);270if (class_temp) {271*class = class_temp;272}273274_this->syms.udev_device_unref(dev);275276return true;277}278279void SDL_UDEV_UnloadLibrary(void)280{281if (!_this) {282return;283}284285if (_this->udev_handle) {286SDL_UnloadObject(_this->udev_handle);287_this->udev_handle = NULL;288}289}290291bool SDL_UDEV_LoadLibrary(void)292{293bool result = true;294295if (!_this) {296return SDL_SetError("UDEV not initialized");297}298299// See if there is a udev library already loaded300if (SDL_UDEV_load_syms()) {301return true;302}303304#ifdef SDL_UDEV_DYNAMIC305// Check for the build environment's libudev first306if (!_this->udev_handle) {307_this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC);308if (_this->udev_handle) {309result = SDL_UDEV_load_syms();310if (!result) {311SDL_UDEV_UnloadLibrary();312}313}314}315#endif316317if (!_this->udev_handle) {318for (int i = 0; i < SDL_arraysize(SDL_UDEV_LIBS); i++) {319_this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]);320if (_this->udev_handle) {321result = SDL_UDEV_load_syms();322if (!result) {323SDL_UDEV_UnloadLibrary();324} else {325break;326}327}328}329330if (!_this->udev_handle) {331result = false;332// Don't call SDL_SetError(): SDL_LoadObject already did.333}334}335336return result;337}338339static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len)340{341const char *value;342char text[4096];343char *word;344int i;345unsigned long v;346347SDL_memset(bitmask, 0, bitmask_len * sizeof(*bitmask));348value = _this->syms.udev_device_get_sysattr_value(pdev, attr);349if (!value) {350return;351}352353SDL_strlcpy(text, value, sizeof(text));354i = 0;355while ((word = SDL_strrchr(text, ' ')) != NULL) {356v = SDL_strtoul(word + 1, NULL, 16);357if (i < bitmask_len) {358bitmask[i] = v;359}360++i;361*word = '\0';362}363v = SDL_strtoul(text, NULL, 16);364if (i < bitmask_len) {365bitmask[i] = v;366}367}368369static int guess_device_class(struct udev_device *dev)370{371struct udev_device *pdev;372unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)];373unsigned long bitmask_ev[NBITS(EV_MAX)];374unsigned long bitmask_abs[NBITS(ABS_MAX)];375unsigned long bitmask_key[NBITS(KEY_MAX)];376unsigned long bitmask_rel[NBITS(REL_MAX)];377378/* walk up the parental chain until we find the real input device; the379* argument is very likely a subdevice of this, like eventN */380pdev = dev;381while (pdev && !_this->syms.udev_device_get_sysattr_value(pdev, "capabilities/ev")) {382pdev = _this->syms.udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL);383}384if (!pdev) {385return 0;386}387388get_caps(dev, pdev, "properties", bitmask_props, SDL_arraysize(bitmask_props));389get_caps(dev, pdev, "capabilities/ev", bitmask_ev, SDL_arraysize(bitmask_ev));390get_caps(dev, pdev, "capabilities/abs", bitmask_abs, SDL_arraysize(bitmask_abs));391get_caps(dev, pdev, "capabilities/rel", bitmask_rel, SDL_arraysize(bitmask_rel));392get_caps(dev, pdev, "capabilities/key", bitmask_key, SDL_arraysize(bitmask_key));393394return SDL_EVDEV_GuessDeviceClass(&bitmask_props[0],395&bitmask_ev[0],396&bitmask_abs[0],397&bitmask_key[0],398&bitmask_rel[0]);399}400401static int device_class(struct udev_device *dev)402{403const char *subsystem;404const char *val = NULL;405int devclass = 0;406407subsystem = _this->syms.udev_device_get_subsystem(dev);408if (!subsystem) {409return 0;410}411412if (SDL_strcmp(subsystem, "sound") == 0) {413devclass = SDL_UDEV_DEVICE_SOUND;414} else if (SDL_strcmp(subsystem, "video4linux") == 0) {415val = _this->syms.udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES");416if (val && SDL_strcasestr(val, "capture")) {417devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE;418}419} else if (SDL_strcmp(subsystem, "input") == 0) {420// udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c421422val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");423if (val && SDL_strcmp(val, "1") == 0) {424devclass |= SDL_UDEV_DEVICE_JOYSTICK;425}426427val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER");428if (val && SDL_strcmp(val, "1") == 0) {429devclass |= SDL_UDEV_DEVICE_ACCELEROMETER;430}431432val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_MOUSE");433if (val && SDL_strcmp(val, "1") == 0) {434devclass |= SDL_UDEV_DEVICE_MOUSE;435}436437val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");438if (val && SDL_strcmp(val, "1") == 0) {439devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN;440}441442/* The undocumented rule is:443- All devices with keys get ID_INPUT_KEY444- From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD445446Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183447*/448val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEY");449if (val && SDL_strcmp(val, "1") == 0) {450devclass |= SDL_UDEV_DEVICE_HAS_KEYS;451}452453val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD");454if (val && SDL_strcmp(val, "1") == 0) {455devclass |= SDL_UDEV_DEVICE_KEYBOARD;456}457458if (devclass == 0) {459// Fall back to old style input classes460val = _this->syms.udev_device_get_property_value(dev, "ID_CLASS");461if (val) {462if (SDL_strcmp(val, "joystick") == 0) {463devclass = SDL_UDEV_DEVICE_JOYSTICK;464} else if (SDL_strcmp(val, "mouse") == 0) {465devclass = SDL_UDEV_DEVICE_MOUSE;466} else if (SDL_strcmp(val, "kbd") == 0) {467devclass = SDL_UDEV_DEVICE_HAS_KEYS | SDL_UDEV_DEVICE_KEYBOARD;468}469} else {470// We could be linked with libudev on a system that doesn't have udev running471devclass = guess_device_class(dev);472}473}474}475476return devclass;477}478479static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)480{481int devclass = 0;482const char *path;483SDL_UDEV_CallbackList *item;484485path = _this->syms.udev_device_get_devnode(dev);486if (!path) {487return;488}489490if (type == SDL_UDEV_DEVICEADDED) {491devclass = device_class(dev);492if (!devclass) {493return;494}495} else {496// The device has been removed, the class isn't available497}498499// Process callbacks500for (item = _this->first; item; item = item->next) {501item->callback(type, devclass, path);502}503}504505void SDL_UDEV_Poll(void)506{507struct udev_device *dev = NULL;508const char *action = NULL;509510if (!_this) {511return;512}513514while (SDL_UDEV_hotplug_update_available()) {515dev = _this->syms.udev_monitor_receive_device(_this->udev_mon);516if (!dev) {517break;518}519action = _this->syms.udev_device_get_action(dev);520521if (action) {522if (SDL_strcmp(action, "add") == 0) {523device_event(SDL_UDEV_DEVICEADDED, dev);524} else if (SDL_strcmp(action, "remove") == 0) {525device_event(SDL_UDEV_DEVICEREMOVED, dev);526}527}528529_this->syms.udev_device_unref(dev);530}531}532533bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)534{535SDL_UDEV_CallbackList *item;536item = (SDL_UDEV_CallbackList *)SDL_calloc(1, sizeof(SDL_UDEV_CallbackList));537if (!item) {538return false;539}540541item->callback = cb;542543if (!_this->last) {544_this->first = _this->last = item;545} else {546_this->last->next = item;547_this->last = item;548}549550return true;551}552553void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)554{555SDL_UDEV_CallbackList *item;556SDL_UDEV_CallbackList *prev = NULL;557558if (!_this) {559return;560}561562for (item = _this->first; item; item = item->next) {563// found it, remove it.564if (item->callback == cb) {565if (prev) {566prev->next = item->next;567} else {568SDL_assert(_this->first == item);569_this->first = item->next;570}571if (item == _this->last) {572_this->last = prev;573}574SDL_free(item);575return;576}577prev = item;578}579}580581const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void)582{583if (!SDL_UDEV_Init()) {584SDL_SetError("Could not initialize UDEV");585return NULL;586}587588return &_this->syms;589}590591void SDL_UDEV_ReleaseUdevSyms(void)592{593SDL_UDEV_Quit();594}595596#endif // SDL_USE_LIBUDEV597598599