Path: blob/master/thirdparty/sdl/core/linux/SDL_udev.c
21382 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}278279bool SDL_UDEV_GetProductSerial(const char *device_path, const char **serial)280{281struct stat statbuf;282char type;283struct udev_device *dev;284const char *val;285286if (!_this) {287return false;288}289290if (stat(device_path, &statbuf) < 0) {291return false;292}293294if (S_ISBLK(statbuf.st_mode)) {295type = 'b';296} else if (S_ISCHR(statbuf.st_mode)) {297type = 'c';298} else {299return false;300}301302dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev);303if (!dev) {304return false;305}306307val = _this->syms.udev_device_get_property_value(dev, "ID_SERIAL_SHORT");308if (val) {309*serial = val;310return true;311}312313return false;314}315316void SDL_UDEV_UnloadLibrary(void)317{318if (!_this) {319return;320}321322if (_this->udev_handle) {323SDL_UnloadObject(_this->udev_handle);324_this->udev_handle = NULL;325}326}327328bool SDL_UDEV_LoadLibrary(void)329{330bool result = true;331332if (!_this) {333return SDL_SetError("UDEV not initialized");334}335336// See if there is a udev library already loaded337if (SDL_UDEV_load_syms()) {338return true;339}340341#ifdef SDL_UDEV_DYNAMIC342// Check for the build environment's libudev first343if (!_this->udev_handle) {344_this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC);345if (_this->udev_handle) {346result = SDL_UDEV_load_syms();347if (!result) {348SDL_UDEV_UnloadLibrary();349}350}351}352#endif353354if (!_this->udev_handle) {355for (int i = 0; i < SDL_arraysize(SDL_UDEV_LIBS); i++) {356_this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]);357if (_this->udev_handle) {358result = SDL_UDEV_load_syms();359if (!result) {360SDL_UDEV_UnloadLibrary();361} else {362break;363}364}365}366367if (!_this->udev_handle) {368result = false;369// Don't call SDL_SetError(): SDL_LoadObject already did.370}371}372373return result;374}375376static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len)377{378const char *value;379char text[4096];380char *word;381int i;382unsigned long v;383384SDL_memset(bitmask, 0, bitmask_len * sizeof(*bitmask));385value = _this->syms.udev_device_get_sysattr_value(pdev, attr);386if (!value) {387return;388}389390SDL_strlcpy(text, value, sizeof(text));391i = 0;392while ((word = SDL_strrchr(text, ' ')) != NULL) {393v = SDL_strtoul(word + 1, NULL, 16);394if (i < bitmask_len) {395bitmask[i] = v;396}397++i;398*word = '\0';399}400v = SDL_strtoul(text, NULL, 16);401if (i < bitmask_len) {402bitmask[i] = v;403}404}405406static int guess_device_class(struct udev_device *dev)407{408struct udev_device *pdev;409unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)];410unsigned long bitmask_ev[NBITS(EV_MAX)];411unsigned long bitmask_abs[NBITS(ABS_MAX)];412unsigned long bitmask_key[NBITS(KEY_MAX)];413unsigned long bitmask_rel[NBITS(REL_MAX)];414415/* walk up the parental chain until we find the real input device; the416* argument is very likely a subdevice of this, like eventN */417pdev = dev;418while (pdev && !_this->syms.udev_device_get_sysattr_value(pdev, "capabilities/ev")) {419pdev = _this->syms.udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL);420}421if (!pdev) {422return 0;423}424425get_caps(dev, pdev, "properties", bitmask_props, SDL_arraysize(bitmask_props));426get_caps(dev, pdev, "capabilities/ev", bitmask_ev, SDL_arraysize(bitmask_ev));427get_caps(dev, pdev, "capabilities/abs", bitmask_abs, SDL_arraysize(bitmask_abs));428get_caps(dev, pdev, "capabilities/rel", bitmask_rel, SDL_arraysize(bitmask_rel));429get_caps(dev, pdev, "capabilities/key", bitmask_key, SDL_arraysize(bitmask_key));430431return SDL_EVDEV_GuessDeviceClass(&bitmask_props[0],432&bitmask_ev[0],433&bitmask_abs[0],434&bitmask_key[0],435&bitmask_rel[0]);436}437438static int device_class(struct udev_device *dev)439{440const char *subsystem;441const char *val = NULL;442int devclass = 0;443444subsystem = _this->syms.udev_device_get_subsystem(dev);445if (!subsystem) {446return 0;447}448449if (SDL_strcmp(subsystem, "sound") == 0) {450devclass = SDL_UDEV_DEVICE_SOUND;451} else if (SDL_strcmp(subsystem, "video4linux") == 0) {452val = _this->syms.udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES");453if (val && SDL_strcasestr(val, "capture")) {454devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE;455}456} else if (SDL_strcmp(subsystem, "input") == 0) {457// udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c458459val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");460if (val && SDL_strcmp(val, "1") == 0) {461devclass |= SDL_UDEV_DEVICE_JOYSTICK;462}463464val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER");465if (val && SDL_strcmp(val, "1") == 0) {466devclass |= SDL_UDEV_DEVICE_ACCELEROMETER;467}468469val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_MOUSE");470if (val && SDL_strcmp(val, "1") == 0) {471devclass |= SDL_UDEV_DEVICE_MOUSE;472}473474val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");475if (val && SDL_strcmp(val, "1") == 0) {476devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN;477}478479/* The undocumented rule is:480- All devices with keys get ID_INPUT_KEY481- From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD482483Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183484*/485val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEY");486if (val && SDL_strcmp(val, "1") == 0) {487devclass |= SDL_UDEV_DEVICE_HAS_KEYS;488}489490val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD");491if (val && SDL_strcmp(val, "1") == 0) {492devclass |= SDL_UDEV_DEVICE_KEYBOARD;493}494495if (devclass == 0) {496// Fall back to old style input classes497val = _this->syms.udev_device_get_property_value(dev, "ID_CLASS");498if (val) {499if (SDL_strcmp(val, "joystick") == 0) {500devclass = SDL_UDEV_DEVICE_JOYSTICK;501} else if (SDL_strcmp(val, "mouse") == 0) {502devclass = SDL_UDEV_DEVICE_MOUSE;503} else if (SDL_strcmp(val, "kbd") == 0) {504devclass = SDL_UDEV_DEVICE_HAS_KEYS | SDL_UDEV_DEVICE_KEYBOARD;505}506} else {507// We could be linked with libudev on a system that doesn't have udev running508devclass = guess_device_class(dev);509}510}511}512513return devclass;514}515516static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)517{518int devclass = 0;519const char *path;520SDL_UDEV_CallbackList *item;521522path = _this->syms.udev_device_get_devnode(dev);523if (!path) {524return;525}526527if (type == SDL_UDEV_DEVICEADDED) {528devclass = device_class(dev);529if (!devclass) {530return;531}532} else {533// The device has been removed, the class isn't available534}535536// Process callbacks537for (item = _this->first; item; item = item->next) {538item->callback(type, devclass, path);539}540}541542void SDL_UDEV_Poll(void)543{544struct udev_device *dev = NULL;545const char *action = NULL;546547if (!_this) {548return;549}550551while (SDL_UDEV_hotplug_update_available()) {552dev = _this->syms.udev_monitor_receive_device(_this->udev_mon);553if (!dev) {554break;555}556action = _this->syms.udev_device_get_action(dev);557558if (action) {559if (SDL_strcmp(action, "add") == 0) {560device_event(SDL_UDEV_DEVICEADDED, dev);561} else if (SDL_strcmp(action, "remove") == 0) {562device_event(SDL_UDEV_DEVICEREMOVED, dev);563}564}565566_this->syms.udev_device_unref(dev);567}568}569570bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)571{572SDL_UDEV_CallbackList *item;573item = (SDL_UDEV_CallbackList *)SDL_calloc(1, sizeof(SDL_UDEV_CallbackList));574if (!item) {575return false;576}577578item->callback = cb;579580if (!_this->last) {581_this->first = _this->last = item;582} else {583_this->last->next = item;584_this->last = item;585}586587return true;588}589590void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)591{592SDL_UDEV_CallbackList *item;593SDL_UDEV_CallbackList *prev = NULL;594595if (!_this) {596return;597}598599for (item = _this->first; item; item = item->next) {600// found it, remove it.601if (item->callback == cb) {602if (prev) {603prev->next = item->next;604} else {605SDL_assert(_this->first == item);606_this->first = item->next;607}608if (item == _this->last) {609_this->last = prev;610}611SDL_free(item);612return;613}614prev = item;615}616}617618const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void)619{620if (!SDL_UDEV_Init()) {621SDL_SetError("Could not initialize UDEV");622return NULL;623}624625return &_this->syms;626}627628void SDL_UDEV_ReleaseUdevSyms(void)629{630SDL_UDEV_Quit();631}632633#endif // SDL_USE_LIBUDEV634635636