Path: blob/master/thirdparty/sdl/core/linux/SDL_evdev.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#ifdef SDL_INPUT_LINUXEV2324// This is based on the linux joystick driver25/* References: https://www.kernel.org/doc/Documentation/input/input.txt26* https://www.kernel.org/doc/Documentation/input/event-codes.txt27* /usr/include/linux/input.h28* The evtest application is also useful to debug the protocol29*/3031#include "SDL_evdev.h"32#include "SDL_evdev_kbd.h"3334#include <sys/stat.h>35#include <unistd.h>36#include <fcntl.h>37#include <sys/ioctl.h>38#include <linux/input.h>3940#include "../../events/SDL_events_c.h"41#include "../../core/linux/SDL_evdev_capabilities.h"42#include "../../core/linux/SDL_udev.h"4344// These are not defined in older Linux kernel headers45#ifndef SYN_DROPPED46#define SYN_DROPPED 347#endif48#ifndef ABS_MT_SLOT49#define ABS_MT_SLOT 0x2f50#define ABS_MT_POSITION_X 0x3551#define ABS_MT_POSITION_Y 0x3652#define ABS_MT_TRACKING_ID 0x3953#define ABS_MT_PRESSURE 0x3a54#endif55#ifndef REL_WHEEL_HI_RES56#define REL_WHEEL_HI_RES 0x0b57#define REL_HWHEEL_HI_RES 0x0c58#endif5960// The field to look up in struct input_event for integer seconds61#ifndef input_event_sec62#define input_event_sec time.tv_sec63#endif6465// The field to look up in struct input_event for fractional seconds66#ifndef input_event_usec67#define input_event_usec time.tv_usec68#endif6970typedef struct SDL_evdevlist_item71{72char *path;73int fd;74int udev_class;7576// TODO: use this for every device, not just touchscreen77bool out_of_sync;7879/* TODO: expand on this to have data for every possible class (mouse,80keyboard, touchpad, etc.). Also there's probably some things in here we81can pull out to the SDL_evdevlist_item i.e. name */82bool is_touchscreen;83struct84{85char *name;8687int min_x, max_x, range_x;88int min_y, max_y, range_y;89int min_pressure, max_pressure, range_pressure;9091int max_slots;92int current_slot;93struct94{95enum96{97EVDEV_TOUCH_SLOTDELTA_NONE = 0,98EVDEV_TOUCH_SLOTDELTA_DOWN,99EVDEV_TOUCH_SLOTDELTA_UP,100EVDEV_TOUCH_SLOTDELTA_MOVE101} delta;102int tracking_id;103int x, y, pressure;104} *slots;105106} *touchscreen_data;107108// Mouse state109bool high_res_wheel;110bool high_res_hwheel;111bool relative_mouse;112int mouse_x, mouse_y;113int mouse_wheel, mouse_hwheel;114int min_x, max_x, range_x;115int min_y, max_y, range_y;116117struct SDL_evdevlist_item *next;118} SDL_evdevlist_item;119120typedef struct SDL_EVDEV_PrivateData121{122int ref_count;123int num_devices;124SDL_evdevlist_item *first;125SDL_evdevlist_item *last;126SDL_EVDEV_keyboard_state *kbd;127} SDL_EVDEV_PrivateData;128129static SDL_EVDEV_PrivateData *_this = NULL;130131static SDL_Scancode SDL_EVDEV_translate_keycode(int keycode);132static void SDL_EVDEV_sync_device(SDL_evdevlist_item *item);133static bool SDL_EVDEV_device_removed(const char *dev_path);134static bool SDL_EVDEV_device_added(const char *dev_path, int udev_class);135#ifdef SDL_USE_LIBUDEV136static void SDL_EVDEV_udev_callback(SDL_UDEV_deviceevent udev_event, int udev_class, const char *dev_path);137#endif // SDL_USE_LIBUDEV138139static Uint8 EVDEV_MouseButtons[] = {140SDL_BUTTON_LEFT, // BTN_LEFT 0x110141SDL_BUTTON_RIGHT, // BTN_RIGHT 0x111142SDL_BUTTON_MIDDLE, // BTN_MIDDLE 0x112143SDL_BUTTON_X1, // BTN_SIDE 0x113144SDL_BUTTON_X2, // BTN_EXTRA 0x114145SDL_BUTTON_X2 + 1, // BTN_FORWARD 0x115146SDL_BUTTON_X2 + 2, // BTN_BACK 0x116147SDL_BUTTON_X2 + 3 // BTN_TASK 0x117148};149150static bool SDL_EVDEV_SetRelativeMouseMode(bool enabled)151{152// Mice already send relative events through this interface153return true;154}155156static void SDL_EVDEV_UpdateKeyboardMute(void)157{158if (SDL_EVDEV_GetDeviceCount(SDL_UDEV_DEVICE_KEYBOARD) > 0) {159SDL_EVDEV_kbd_set_muted(_this->kbd, true);160} else {161SDL_EVDEV_kbd_set_muted(_this->kbd, false);162}163}164165bool SDL_EVDEV_Init(void)166{167if (!_this) {168_this = (SDL_EVDEV_PrivateData *)SDL_calloc(1, sizeof(*_this));169if (!_this) {170return false;171}172173#ifdef SDL_USE_LIBUDEV174if (!SDL_UDEV_Init()) {175SDL_free(_this);176_this = NULL;177return false;178}179180// Set up the udev callback181if (!SDL_UDEV_AddCallback(SDL_EVDEV_udev_callback)) {182SDL_UDEV_Quit();183SDL_free(_this);184_this = NULL;185return false;186}187188// Force a scan to build the initial device list189SDL_UDEV_Scan();190#else191{192/* Allow the user to specify a list of devices explicitly of193the form:194deviceclass:path[,deviceclass:path[,...]]195where device class is an integer representing the196SDL_UDEV_deviceclass and path is the full path to197the event device. */198const char *devices = SDL_GetHint(SDL_HINT_EVDEV_DEVICES);199if (devices) {200/* Assume this is the old use of the env var and it is not in201ROM. */202char *rest = (char *)devices;203char *spec;204while ((spec = SDL_strtok_r(rest, ",", &rest))) {205char *endofcls = 0;206long cls = SDL_strtol(spec, &endofcls, 0);207if (endofcls) {208SDL_EVDEV_device_added(endofcls + 1, cls);209}210}211} else {212// TODO: Scan the devices manually, like a caveman213}214}215#endif // SDL_USE_LIBUDEV216217_this->kbd = SDL_EVDEV_kbd_init();218219SDL_EVDEV_UpdateKeyboardMute();220}221222//SDL_GetMouse()->SetRelativeMouseMode = SDL_EVDEV_SetRelativeMouseMode;223224_this->ref_count += 1;225226return true;227}228229void SDL_EVDEV_Quit(void)230{231if (!_this) {232return;233}234235_this->ref_count -= 1;236237if (_this->ref_count < 1) {238#ifdef SDL_USE_LIBUDEV239SDL_UDEV_DelCallback(SDL_EVDEV_udev_callback);240SDL_UDEV_Quit();241#endif // SDL_USE_LIBUDEV242243// Remove existing devices244while (_this->first) {245SDL_EVDEV_device_removed(_this->first->path);246}247248SDL_EVDEV_kbd_quit(_this->kbd);249250SDL_assert(_this->first == NULL);251SDL_assert(_this->last == NULL);252SDL_assert(_this->num_devices == 0);253254SDL_free(_this);255_this = NULL;256}257}258259#ifdef SDL_USE_LIBUDEV260static void SDL_EVDEV_udev_callback(SDL_UDEV_deviceevent udev_event, int udev_class,261const char *dev_path)262{263if (!dev_path) {264return;265}266267switch (udev_event) {268case SDL_UDEV_DEVICEADDED:269if (!(udev_class & (SDL_UDEV_DEVICE_MOUSE | SDL_UDEV_DEVICE_HAS_KEYS | SDL_UDEV_DEVICE_TOUCHSCREEN | SDL_UDEV_DEVICE_TOUCHPAD))) {270return;271}272273if (udev_class & SDL_UDEV_DEVICE_JOYSTICK) {274return;275}276277SDL_EVDEV_device_added(dev_path, udev_class);278break;279case SDL_UDEV_DEVICEREMOVED:280SDL_EVDEV_device_removed(dev_path);281break;282default:283break;284}285}286#endif // SDL_USE_LIBUDEV287288void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data,289void (*acquire_callback)(void*), void *acquire_callback_data)290{291SDL_EVDEV_kbd_set_vt_switch_callbacks(_this->kbd,292release_callback, release_callback_data,293acquire_callback, acquire_callback_data);294}295296int SDL_EVDEV_GetDeviceCount(int device_class)297{298SDL_evdevlist_item *item;299int count = 0;300301for (item = _this->first; item; item = item->next) {302if ((item->udev_class & device_class) == device_class) {303++count;304}305}306return count;307}308309void SDL_EVDEV_Poll(void)310{311struct input_event events[32];312int i, j, len;313SDL_evdevlist_item *item;314SDL_Scancode scancode;315int mouse_button;316SDL_Mouse *mouse;317float norm_x, norm_y, norm_pressure;318319if (!_this) {320return;321}322323#ifdef SDL_USE_LIBUDEV324SDL_UDEV_Poll();325#endif326327SDL_EVDEV_kbd_update(_this->kbd);328329mouse = NULL; //SDL_GetMouse();330331for (item = _this->first; item; item = item->next) {332while ((len = read(item->fd, events, sizeof(events))) > 0) {333len /= sizeof(events[0]);334for (i = 0; i < len; ++i) {335struct input_event *event = &events[i];336337/* special handling for touchscreen, that should eventually be338used for all devices */339if (item->out_of_sync && item->is_touchscreen &&340event->type == EV_SYN && event->code != SYN_REPORT) {341break;342}343344switch (event->type) {345case EV_KEY:346break;347if (event->code >= BTN_MOUSE && event->code < BTN_MOUSE + SDL_arraysize(EVDEV_MouseButtons)) {348Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event);349mouse_button = event->code - BTN_MOUSE;350//SDL_SendMouseButton(timestamp, mouse->focus, (SDL_MouseID)item->fd, EVDEV_MouseButtons[mouse_button], (event->value != 0));351break;352}353354/* BTN_TOUCH event value 1 indicates there is contact with355a touchscreen or trackpad (earliest finger's current356position is sent in EV_ABS ABS_X/ABS_Y, switching to357next finger after earliest is released) */358if (item->is_touchscreen && event->code == BTN_TOUCH) {359if (item->touchscreen_data->max_slots == 1) {360if (event->value) {361item->touchscreen_data->slots[0].delta = EVDEV_TOUCH_SLOTDELTA_DOWN;362} else {363item->touchscreen_data->slots[0].delta = EVDEV_TOUCH_SLOTDELTA_UP;364}365}366break;367}368369// Probably keyboard370{371Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event);372scancode = SDL_EVDEV_translate_keycode(event->code);373// if (event->value == 0) {374// SDL_SendKeyboardKey(timestamp, (SDL_KeyboardID)item->fd, event->code, scancode, false);375// } else if (event->value == 1 || event->value == 2 /* key repeated */) {376// SDL_SendKeyboardKey(timestamp, (SDL_KeyboardID)item->fd, event->code, scancode, true);377// }378SDL_EVDEV_kbd_keycode(_this->kbd, event->code, event->value);379}380break;381case EV_ABS:382switch (event->code) {383case ABS_MT_SLOT:384if (!item->is_touchscreen) { // FIXME: temp hack385break;386}387item->touchscreen_data->current_slot = event->value;388break;389case ABS_MT_TRACKING_ID:390if (!item->is_touchscreen) { // FIXME: temp hack391break;392}393if (event->value >= 0) {394item->touchscreen_data->slots[item->touchscreen_data->current_slot].tracking_id = event->value + 1;395item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_DOWN;396} else {397item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_UP;398}399break;400case ABS_MT_POSITION_X:401if (!item->is_touchscreen) { // FIXME: temp hack402break;403}404item->touchscreen_data->slots[item->touchscreen_data->current_slot].x = event->value;405if (item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta == EVDEV_TOUCH_SLOTDELTA_NONE) {406item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_MOVE;407}408break;409case ABS_MT_POSITION_Y:410if (!item->is_touchscreen) { // FIXME: temp hack411break;412}413item->touchscreen_data->slots[item->touchscreen_data->current_slot].y = event->value;414if (item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta == EVDEV_TOUCH_SLOTDELTA_NONE) {415item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_MOVE;416}417break;418case ABS_MT_PRESSURE:419if (!item->is_touchscreen) { // FIXME: temp hack420break;421}422item->touchscreen_data->slots[item->touchscreen_data->current_slot].pressure = event->value;423if (item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta == EVDEV_TOUCH_SLOTDELTA_NONE) {424item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_MOVE;425}426break;427case ABS_X:428if (item->is_touchscreen) {429if (item->touchscreen_data->max_slots != 1) {430break;431}432item->touchscreen_data->slots[0].x = event->value;433} else if (!item->relative_mouse) {434item->mouse_x = event->value;435}436break;437case ABS_Y:438if (item->is_touchscreen) {439if (item->touchscreen_data->max_slots != 1) {440break;441}442item->touchscreen_data->slots[0].y = event->value;443} else if (!item->relative_mouse) {444item->mouse_y = event->value;445}446break;447default:448break;449}450break;451case EV_REL:452switch (event->code) {453case REL_X:454if (item->relative_mouse) {455item->mouse_x += event->value;456}457break;458case REL_Y:459if (item->relative_mouse) {460item->mouse_y += event->value;461}462break;463case REL_WHEEL:464if (!item->high_res_wheel) {465item->mouse_wheel += event->value;466}467break;468case REL_WHEEL_HI_RES:469SDL_assert(item->high_res_wheel);470item->mouse_wheel += event->value;471break;472case REL_HWHEEL:473if (!item->high_res_hwheel) {474item->mouse_hwheel += event->value;475}476break;477case REL_HWHEEL_HI_RES:478SDL_assert(item->high_res_hwheel);479item->mouse_hwheel += event->value;480break;481default:482break;483}484break;485case EV_SYN:486switch (event->code) {487case SYN_REPORT:488// Send mouse axis changes together to ensure consistency and reduce event processing overhead489if (item->relative_mouse) {490if (item->mouse_x != 0 || item->mouse_y != 0) {491Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event);492//SDL_SendMouseMotion(timestamp, mouse->focus, (SDL_MouseID)item->fd, item->relative_mouse, (float)item->mouse_x, (float)item->mouse_y);493item->mouse_x = item->mouse_y = 0;494}495} else if (item->range_x > 0 && item->range_y > 0) {496int screen_w = 0, screen_h = 0;497const SDL_DisplayMode *mode = NULL;498499if (mode) {500screen_w = mode->w;501screen_h = mode->h;502}503//SDL_SendMouseMotion(SDL_EVDEV_GetEventTimestamp(event), mouse->focus, (SDL_MouseID)item->fd, item->relative_mouse,504// (float)(item->mouse_x - item->min_x) * screen_w / item->range_x,505// (float)(item->mouse_y - item->min_y) * screen_h / item->range_y);506}507508if (item->mouse_wheel != 0 || item->mouse_hwheel != 0) {509Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event);510const float denom = (item->high_res_hwheel ? 120.0f : 1.0f);511//SDL_SendMouseWheel(timestamp,512// mouse->focus, (SDL_MouseID)item->fd,513// item->mouse_hwheel / denom,514// item->mouse_wheel / denom,515// SDL_MOUSEWHEEL_NORMAL);516item->mouse_wheel = item->mouse_hwheel = 0;517}518519if (!item->is_touchscreen) { // FIXME: temp hack520break;521}522523for (j = 0; j < item->touchscreen_data->max_slots; j++) {524norm_x = (float)(item->touchscreen_data->slots[j].x - item->touchscreen_data->min_x) /525(float)item->touchscreen_data->range_x;526norm_y = (float)(item->touchscreen_data->slots[j].y - item->touchscreen_data->min_y) /527(float)item->touchscreen_data->range_y;528529if (item->touchscreen_data->range_pressure > 0) {530norm_pressure = (float)(item->touchscreen_data->slots[j].pressure - item->touchscreen_data->min_pressure) /531(float)item->touchscreen_data->range_pressure;532} else {533// This touchscreen does not support pressure534norm_pressure = 1.0f;535}536537/* FIXME: the touch's window shouldn't be null, but538* the coordinate space of touch positions needs to539* be window-relative in that case. */540switch (item->touchscreen_data->slots[j].delta) {541case EVDEV_TOUCH_SLOTDELTA_DOWN:542//SDL_SendTouch(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, NULL, SDL_EVENT_FINGER_DOWN, norm_x, norm_y, norm_pressure);543item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE;544break;545case EVDEV_TOUCH_SLOTDELTA_UP:546//SDL_SendTouch(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, NULL, SDL_EVENT_FINGER_UP, norm_x, norm_y, norm_pressure);547item->touchscreen_data->slots[j].tracking_id = 0;548item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE;549break;550case EVDEV_TOUCH_SLOTDELTA_MOVE:551//SDL_SendTouchMotion(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, NULL, norm_x, norm_y, norm_pressure);552item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE;553break;554default:555break;556}557}558559if (item->out_of_sync) {560item->out_of_sync = false;561}562break;563case SYN_DROPPED:564if (item->is_touchscreen) {565item->out_of_sync = true;566}567SDL_EVDEV_sync_device(item);568break;569default:570break;571}572break;573}574}575}576}577}578579static SDL_Scancode SDL_EVDEV_translate_keycode(int keycode)580{581//SDL_Scancode scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_LINUX, keycode);582583#ifdef DEBUG_SCANCODES584if (scancode == SDL_SCANCODE_UNKNOWN) {585/* BTN_TOUCH is handled elsewhere, but we might still end up here if586you get an unexpected BTN_TOUCH from something SDL believes is not587a touch device. In this case, we'd rather not get a misleading588SDL_Log message about an unknown key. */589if (keycode != BTN_TOUCH) {590SDL_Log("The key you just pressed is not recognized by SDL. To help "591"get this fixed, please report this to the SDL forums/mailing list "592"<https://discourse.libsdl.org/> EVDEV KeyCode %d",593keycode);594}595}596#endif // DEBUG_SCANCODES597598return 0; //scancode;599}600601static bool SDL_EVDEV_init_keyboard(SDL_evdevlist_item *item, int udev_class)602{603char name[128];604605name[0] = '\0';606ioctl(item->fd, EVIOCGNAME(sizeof(name)), name);607608//SDL_AddKeyboard((SDL_KeyboardID)item->fd, name, true);609610return true;611}612613static void SDL_EVDEV_destroy_keyboard(SDL_evdevlist_item *item)614{615//SDL_RemoveKeyboard((SDL_KeyboardID)item->fd, true);616}617618static bool SDL_EVDEV_init_mouse(SDL_evdevlist_item *item, int udev_class)619{620char name[128];621int ret;622struct input_absinfo abs_info;623624name[0] = '\0';625ioctl(item->fd, EVIOCGNAME(sizeof(name)), name);626627//SDL_AddMouse((SDL_MouseID)item->fd, name, true);628629ret = ioctl(item->fd, EVIOCGABS(ABS_X), &abs_info);630if (ret < 0) {631// no absolute mode info, continue632return true;633}634item->min_x = abs_info.minimum;635item->max_x = abs_info.maximum;636item->range_x = abs_info.maximum - abs_info.minimum;637638ret = ioctl(item->fd, EVIOCGABS(ABS_Y), &abs_info);639if (ret < 0) {640// no absolute mode info, continue641return true;642}643item->min_y = abs_info.minimum;644item->max_y = abs_info.maximum;645item->range_y = abs_info.maximum - abs_info.minimum;646647return true;648}649650static void SDL_EVDEV_destroy_mouse(SDL_evdevlist_item *item)651{652//SDL_RemoveMouse((SDL_MouseID)item->fd, true);653}654655static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class)656{657int ret;658unsigned long xreq, yreq;659char name[64];660struct input_absinfo abs_info;661662if (!item->is_touchscreen) {663return true;664}665666item->touchscreen_data = SDL_calloc(1, sizeof(*item->touchscreen_data));667if (!item->touchscreen_data) {668return false;669}670671ret = ioctl(item->fd, EVIOCGNAME(sizeof(name)), name);672if (ret < 0) {673SDL_free(item->touchscreen_data);674return SDL_SetError("Failed to get evdev touchscreen name");675}676677item->touchscreen_data->name = SDL_strdup(name);678if (!item->touchscreen_data->name) {679SDL_free(item->touchscreen_data);680return false;681}682683ret = ioctl(item->fd, EVIOCGABS(ABS_MT_SLOT), &abs_info);684if (ret < 0) {685SDL_free(item->touchscreen_data->name);686SDL_free(item->touchscreen_data);687return SDL_SetError("Failed to get evdev touchscreen limits");688}689690if (abs_info.maximum == 0) {691item->touchscreen_data->max_slots = 1;692xreq = EVIOCGABS(ABS_X);693yreq = EVIOCGABS(ABS_Y);694} else {695item->touchscreen_data->max_slots = abs_info.maximum + 1;696xreq = EVIOCGABS(ABS_MT_POSITION_X);697yreq = EVIOCGABS(ABS_MT_POSITION_Y);698}699700ret = ioctl(item->fd, xreq, &abs_info);701if (ret < 0) {702SDL_free(item->touchscreen_data->name);703SDL_free(item->touchscreen_data);704return SDL_SetError("Failed to get evdev touchscreen limits");705}706item->touchscreen_data->min_x = abs_info.minimum;707item->touchscreen_data->max_x = abs_info.maximum;708item->touchscreen_data->range_x = abs_info.maximum - abs_info.minimum;709710ret = ioctl(item->fd, yreq, &abs_info);711if (ret < 0) {712SDL_free(item->touchscreen_data->name);713SDL_free(item->touchscreen_data);714return SDL_SetError("Failed to get evdev touchscreen limits");715}716item->touchscreen_data->min_y = abs_info.minimum;717item->touchscreen_data->max_y = abs_info.maximum;718item->touchscreen_data->range_y = abs_info.maximum - abs_info.minimum;719720ret = ioctl(item->fd, EVIOCGABS(ABS_MT_PRESSURE), &abs_info);721if (ret < 0) {722SDL_free(item->touchscreen_data->name);723SDL_free(item->touchscreen_data);724return SDL_SetError("Failed to get evdev touchscreen limits");725}726item->touchscreen_data->min_pressure = abs_info.minimum;727item->touchscreen_data->max_pressure = abs_info.maximum;728item->touchscreen_data->range_pressure = abs_info.maximum - abs_info.minimum;729730item->touchscreen_data->slots = SDL_calloc(731item->touchscreen_data->max_slots,732sizeof(*item->touchscreen_data->slots));733if (!item->touchscreen_data->slots) {734SDL_free(item->touchscreen_data->name);735SDL_free(item->touchscreen_data);736return false;737}738739//ret = SDL_AddTouch(item->fd, // I guess our fd is unique enough740// (udev_class & SDL_UDEV_DEVICE_TOUCHPAD) ? SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE : SDL_TOUCH_DEVICE_DIRECT,741// item->touchscreen_data->name);742ret = -1;743if (ret < 0) {744SDL_free(item->touchscreen_data->slots);745SDL_free(item->touchscreen_data->name);746SDL_free(item->touchscreen_data);747return false;748}749750return true;751}752753static void SDL_EVDEV_destroy_touchscreen(SDL_evdevlist_item *item)754{755if (!item->is_touchscreen) {756return;757}758759//SDL_DelTouch(item->fd);760SDL_free(item->touchscreen_data->slots);761SDL_free(item->touchscreen_data->name);762SDL_free(item->touchscreen_data);763}764765static void SDL_EVDEV_sync_device(SDL_evdevlist_item *item)766{767#ifdef EVIOCGMTSLOTS768int i, ret;769struct input_absinfo abs_info;770/*771* struct input_mt_request_layout {772* __u32 code;773* __s32 values[num_slots];774* };775*776* this is the structure we're trying to emulate777*/778Uint32 *mt_req_code;779Sint32 *mt_req_values;780size_t mt_req_size;781782// TODO: sync devices other than touchscreen783if (!item->is_touchscreen) {784return;785}786787mt_req_size = sizeof(*mt_req_code) +788sizeof(*mt_req_values) * item->touchscreen_data->max_slots;789790mt_req_code = SDL_calloc(1, mt_req_size);791if (!mt_req_code) {792return;793}794795mt_req_values = (Sint32 *)mt_req_code + 1;796797*mt_req_code = ABS_MT_TRACKING_ID;798ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);799if (ret < 0) {800SDL_free(mt_req_code);801return;802}803for (i = 0; i < item->touchscreen_data->max_slots; i++) {804/*805* This doesn't account for the very edge case of the user removing their806* finger and replacing it on the screen during the time we're out of sync,807* which'll mean that we're not going from down -> up or up -> down, we're808* going from down -> down but with a different tracking id, meaning we'd809* have to tell SDL of the two events, but since we wait till SYN_REPORT in810* SDL_EVDEV_Poll to tell SDL, the current structure of this code doesn't811* allow it. Lets just pray to God it doesn't happen.812*/813if (item->touchscreen_data->slots[i].tracking_id == 0 &&814mt_req_values[i] >= 0) {815item->touchscreen_data->slots[i].tracking_id = mt_req_values[i] + 1;816item->touchscreen_data->slots[i].delta = EVDEV_TOUCH_SLOTDELTA_DOWN;817} else if (item->touchscreen_data->slots[i].tracking_id != 0 &&818mt_req_values[i] < 0) {819item->touchscreen_data->slots[i].tracking_id = 0;820item->touchscreen_data->slots[i].delta = EVDEV_TOUCH_SLOTDELTA_UP;821}822}823824*mt_req_code = ABS_MT_POSITION_X;825ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);826if (ret < 0) {827SDL_free(mt_req_code);828return;829}830for (i = 0; i < item->touchscreen_data->max_slots; i++) {831if (item->touchscreen_data->slots[i].tracking_id != 0 &&832item->touchscreen_data->slots[i].x != mt_req_values[i]) {833item->touchscreen_data->slots[i].x = mt_req_values[i];834if (item->touchscreen_data->slots[i].delta ==835EVDEV_TOUCH_SLOTDELTA_NONE) {836item->touchscreen_data->slots[i].delta =837EVDEV_TOUCH_SLOTDELTA_MOVE;838}839}840}841842*mt_req_code = ABS_MT_POSITION_Y;843ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);844if (ret < 0) {845SDL_free(mt_req_code);846return;847}848for (i = 0; i < item->touchscreen_data->max_slots; i++) {849if (item->touchscreen_data->slots[i].tracking_id != 0 &&850item->touchscreen_data->slots[i].y != mt_req_values[i]) {851item->touchscreen_data->slots[i].y = mt_req_values[i];852if (item->touchscreen_data->slots[i].delta ==853EVDEV_TOUCH_SLOTDELTA_NONE) {854item->touchscreen_data->slots[i].delta =855EVDEV_TOUCH_SLOTDELTA_MOVE;856}857}858}859860*mt_req_code = ABS_MT_PRESSURE;861ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);862if (ret < 0) {863SDL_free(mt_req_code);864return;865}866for (i = 0; i < item->touchscreen_data->max_slots; i++) {867if (item->touchscreen_data->slots[i].tracking_id != 0 &&868item->touchscreen_data->slots[i].pressure != mt_req_values[i]) {869item->touchscreen_data->slots[i].pressure = mt_req_values[i];870if (item->touchscreen_data->slots[i].delta ==871EVDEV_TOUCH_SLOTDELTA_NONE) {872item->touchscreen_data->slots[i].delta =873EVDEV_TOUCH_SLOTDELTA_MOVE;874}875}876}877878ret = ioctl(item->fd, EVIOCGABS(ABS_MT_SLOT), &abs_info);879if (ret < 0) {880SDL_free(mt_req_code);881return;882}883item->touchscreen_data->current_slot = abs_info.value;884885SDL_free(mt_req_code);886887#endif // EVIOCGMTSLOTS888}889890static bool SDL_EVDEV_device_added(const char *dev_path, int udev_class)891{892SDL_evdevlist_item *item;893unsigned long relbit[NBITS(REL_MAX)] = { 0 };894895// Check to make sure it's not already in list.896for (item = _this->first; item; item = item->next) {897if (SDL_strcmp(dev_path, item->path) == 0) {898return false; // already have this one899}900}901902item = (SDL_evdevlist_item *)SDL_calloc(1, sizeof(SDL_evdevlist_item));903if (!item) {904return false;905}906907item->fd = open(dev_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);908if (item->fd < 0) {909SDL_free(item);910return SDL_SetError("Unable to open %s", dev_path);911}912913item->path = SDL_strdup(dev_path);914if (!item->path) {915close(item->fd);916SDL_free(item);917return false;918}919920item->udev_class = udev_class;921922if (ioctl(item->fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit) >= 0) {923item->relative_mouse = test_bit(REL_X, relbit) && test_bit(REL_Y, relbit);924item->high_res_wheel = test_bit(REL_WHEEL_HI_RES, relbit);925item->high_res_hwheel = test_bit(REL_HWHEEL_HI_RES, relbit);926}927928// For now, we just treat a touchpad like a touchscreen929if (udev_class & (SDL_UDEV_DEVICE_TOUCHSCREEN | SDL_UDEV_DEVICE_TOUCHPAD)) {930item->is_touchscreen = true;931if (!SDL_EVDEV_init_touchscreen(item, udev_class)) {932close(item->fd);933SDL_free(item->path);934SDL_free(item);935return false;936}937}938939if (udev_class & SDL_UDEV_DEVICE_MOUSE) {940if (!SDL_EVDEV_init_mouse(item, udev_class)) {941close(item->fd);942SDL_free(item->path);943SDL_free(item);944return false;945}946}947948if (udev_class & SDL_UDEV_DEVICE_KEYBOARD) {949if (!SDL_EVDEV_init_keyboard(item, udev_class)) {950close(item->fd);951SDL_free(item->path);952SDL_free(item);953return false;954}955}956957if (!_this->last) {958_this->first = _this->last = item;959} else {960_this->last->next = item;961_this->last = item;962}963964SDL_EVDEV_sync_device(item);965966SDL_EVDEV_UpdateKeyboardMute();967968++_this->num_devices;969return true;970}971972static bool SDL_EVDEV_device_removed(const char *dev_path)973{974SDL_evdevlist_item *item;975SDL_evdevlist_item *prev = NULL;976977for (item = _this->first; item; item = item->next) {978// found it, remove it.979if (SDL_strcmp(dev_path, item->path) == 0) {980if (prev) {981prev->next = item->next;982} else {983SDL_assert(_this->first == item);984_this->first = item->next;985}986if (item == _this->last) {987_this->last = prev;988}989990if (item->is_touchscreen) {991SDL_EVDEV_destroy_touchscreen(item);992}993if (item->udev_class & SDL_UDEV_DEVICE_MOUSE) {994SDL_EVDEV_destroy_mouse(item);995}996if (item->udev_class & SDL_UDEV_DEVICE_KEYBOARD) {997SDL_EVDEV_destroy_keyboard(item);998}999close(item->fd);1000SDL_free(item->path);1001SDL_free(item);1002SDL_EVDEV_UpdateKeyboardMute();1003_this->num_devices--;1004return true;1005}1006prev = item;1007}10081009return false;1010}10111012Uint64 SDL_EVDEV_GetEventTimestamp(struct input_event *event)1013{1014static Uint64 timestamp_offset;1015Uint64 timestamp;1016Uint64 now = SDL_GetTicksNS();10171018/* The kernel internally has nanosecond timestamps, but converts it1019to microseconds when delivering the events */1020timestamp = event->input_event_sec;1021timestamp *= SDL_NS_PER_SECOND;1022timestamp += SDL_US_TO_NS(event->input_event_usec);10231024if (!timestamp_offset) {1025timestamp_offset = (now - timestamp);1026}1027timestamp += timestamp_offset;10281029if (timestamp > now) {1030timestamp_offset -= (timestamp - now);1031timestamp = now;1032}1033return timestamp;1034}10351036#endif // SDL_INPUT_LINUXEV103710381039