1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_INPUT_LINUXEV
24
25// This is based on the linux joystick driver
26/* References: https://www.kernel.org/doc/Documentation/input/input.txt
27 * https://www.kernel.org/doc/Documentation/input/event-codes.txt
28 * /usr/include/linux/input.h
29 * The evtest application is also useful to debug the protocol
30 */
31
32#include "SDL_evdev.h"
33#include "SDL_evdev_kbd.h"
34
35#include <sys/stat.h>
36#include <unistd.h>
37#include <fcntl.h>
38#include <sys/ioctl.h>
39#include <linux/input.h>
40
41#include "../../events/SDL_events_c.h"
42#include "../../events/SDL_scancode_tables_c.h"
43#include "../../core/linux/SDL_evdev_capabilities.h"
44#include "../../core/linux/SDL_udev.h"
45
46// These are not defined in older Linux kernel headers
47#ifndef SYN_DROPPED
48#define SYN_DROPPED 3
49#endif
50#ifndef ABS_MT_SLOT
51#define ABS_MT_SLOT 0x2f
52#define ABS_MT_POSITION_X 0x35
53#define ABS_MT_POSITION_Y 0x36
54#define ABS_MT_TRACKING_ID 0x39
55#define ABS_MT_PRESSURE 0x3a
56#endif
57#ifndef REL_WHEEL_HI_RES
58#define REL_WHEEL_HI_RES 0x0b
59#define REL_HWHEEL_HI_RES 0x0c
60#endif
61
62// The field to look up in struct input_event for integer seconds
63#ifndef input_event_sec
64#define input_event_sec time.tv_sec
65#endif
66
67// The field to look up in struct input_event for fractional seconds
68#ifndef input_event_usec
69#define input_event_usec time.tv_usec
70#endif
71
72typedef struct SDL_evdevlist_item
73{
74 char *path;
75 int fd;
76 int udev_class;
77
78 // TODO: use this for every device, not just touchscreen
79 bool out_of_sync;
80
81 /* TODO: expand on this to have data for every possible class (mouse,
82 keyboard, touchpad, etc.). Also there's probably some things in here we
83 can pull out to the SDL_evdevlist_item i.e. name */
84 bool is_touchscreen;
85 struct
86 {
87 char *name;
88
89 int min_x, max_x, range_x;
90 int min_y, max_y, range_y;
91 int min_pressure, max_pressure, range_pressure;
92
93 int max_slots;
94 int current_slot;
95 struct
96 {
97 enum
98 {
99 EVDEV_TOUCH_SLOTDELTA_NONE = 0,
100 EVDEV_TOUCH_SLOTDELTA_DOWN,
101 EVDEV_TOUCH_SLOTDELTA_UP,
102 EVDEV_TOUCH_SLOTDELTA_MOVE
103 } delta;
104 int tracking_id;
105 int x, y, pressure;
106 } *slots;
107
108 } *touchscreen_data;
109
110 // Mouse state
111 bool high_res_wheel;
112 bool high_res_hwheel;
113 bool relative_mouse;
114 int mouse_x, mouse_y;
115 int mouse_wheel, mouse_hwheel;
116 int min_x, max_x, range_x;
117 int min_y, max_y, range_y;
118
119 struct SDL_evdevlist_item *next;
120} SDL_evdevlist_item;
121
122typedef struct SDL_EVDEV_PrivateData
123{
124 int ref_count;
125 int num_devices;
126 SDL_evdevlist_item *first;
127 SDL_evdevlist_item *last;
128 SDL_EVDEV_keyboard_state *kbd;
129} SDL_EVDEV_PrivateData;
130
131static SDL_EVDEV_PrivateData *_this = NULL;
132
133static SDL_Scancode SDL_EVDEV_translate_keycode(int keycode);
134static void SDL_EVDEV_sync_device(SDL_evdevlist_item *item);
135static bool SDL_EVDEV_device_removed(const char *dev_path);
136static bool SDL_EVDEV_device_added(const char *dev_path, int udev_class);
137#ifdef SDL_USE_LIBUDEV
138static void SDL_EVDEV_udev_callback(SDL_UDEV_deviceevent udev_event, int udev_class, const char *dev_path);
139#endif // SDL_USE_LIBUDEV
140
141static Uint8 EVDEV_MouseButtons[] = {
142 SDL_BUTTON_LEFT, // BTN_LEFT 0x110
143 SDL_BUTTON_RIGHT, // BTN_RIGHT 0x111
144 SDL_BUTTON_MIDDLE, // BTN_MIDDLE 0x112
145 SDL_BUTTON_X1, // BTN_SIDE 0x113
146 SDL_BUTTON_X2, // BTN_EXTRA 0x114
147 SDL_BUTTON_X2 + 1, // BTN_FORWARD 0x115
148 SDL_BUTTON_X2 + 2, // BTN_BACK 0x116
149 SDL_BUTTON_X2 + 3 // BTN_TASK 0x117
150};
151
152static bool SDL_EVDEV_SetRelativeMouseMode(bool enabled)
153{
154 // Mice already send relative events through this interface
155 return true;
156}
157
158static void SDL_EVDEV_UpdateKeyboardMute(void)
159{
160 if (SDL_EVDEV_GetDeviceCount(SDL_UDEV_DEVICE_KEYBOARD) > 0) {
161 SDL_EVDEV_kbd_set_muted(_this->kbd, true);
162 } else {
163 SDL_EVDEV_kbd_set_muted(_this->kbd, false);
164 }
165}
166
167bool SDL_EVDEV_Init(void)
168{
169 if (!_this) {
170 _this = (SDL_EVDEV_PrivateData *)SDL_calloc(1, sizeof(*_this));
171 if (!_this) {
172 return false;
173 }
174
175#ifdef SDL_USE_LIBUDEV
176 if (!SDL_UDEV_Init()) {
177 SDL_free(_this);
178 _this = NULL;
179 return false;
180 }
181
182 // Set up the udev callback
183 if (!SDL_UDEV_AddCallback(SDL_EVDEV_udev_callback)) {
184 SDL_UDEV_Quit();
185 SDL_free(_this);
186 _this = NULL;
187 return false;
188 }
189
190 // Force a scan to build the initial device list
191 SDL_UDEV_Scan();
192#else
193 {
194 /* Allow the user to specify a list of devices explicitly of
195 the form:
196 deviceclass:path[,deviceclass:path[,...]]
197 where device class is an integer representing the
198 SDL_UDEV_deviceclass and path is the full path to
199 the event device. */
200 const char *devices = SDL_GetHint(SDL_HINT_EVDEV_DEVICES);
201 if (devices) {
202 /* Assume this is the old use of the env var and it is not in
203 ROM. */
204 char *rest = (char *)devices;
205 char *spec;
206 while ((spec = SDL_strtok_r(rest, ",", &rest))) {
207 char *endofcls = 0;
208 long cls = SDL_strtol(spec, &endofcls, 0);
209 if (endofcls) {
210 SDL_EVDEV_device_added(endofcls + 1, cls);
211 }
212 }
213 } else {
214 // TODO: Scan the devices manually, like a caveman
215 }
216 }
217#endif // SDL_USE_LIBUDEV
218
219 _this->kbd = SDL_EVDEV_kbd_init();
220
221 SDL_EVDEV_UpdateKeyboardMute();
222 }
223
224 SDL_GetMouse()->SetRelativeMouseMode = SDL_EVDEV_SetRelativeMouseMode;
225
226 _this->ref_count += 1;
227
228 return true;
229}
230
231void SDL_EVDEV_Quit(void)
232{
233 if (!_this) {
234 return;
235 }
236
237 _this->ref_count -= 1;
238
239 if (_this->ref_count < 1) {
240#ifdef SDL_USE_LIBUDEV
241 SDL_UDEV_DelCallback(SDL_EVDEV_udev_callback);
242 SDL_UDEV_Quit();
243#endif // SDL_USE_LIBUDEV
244
245 // Remove existing devices
246 while (_this->first) {
247 SDL_EVDEV_device_removed(_this->first->path);
248 }
249
250 SDL_EVDEV_kbd_quit(_this->kbd);
251
252 SDL_assert(_this->first == NULL);
253 SDL_assert(_this->last == NULL);
254 SDL_assert(_this->num_devices == 0);
255
256 SDL_free(_this);
257 _this = NULL;
258 }
259}
260
261#ifdef SDL_USE_LIBUDEV
262static void SDL_EVDEV_udev_callback(SDL_UDEV_deviceevent udev_event, int udev_class,
263 const char *dev_path)
264{
265 if (!dev_path) {
266 return;
267 }
268
269 switch (udev_event) {
270 case SDL_UDEV_DEVICEADDED:
271 if (!(udev_class & (SDL_UDEV_DEVICE_MOUSE | SDL_UDEV_DEVICE_HAS_KEYS | SDL_UDEV_DEVICE_TOUCHSCREEN | SDL_UDEV_DEVICE_TOUCHPAD))) {
272 return;
273 }
274
275 if (udev_class & SDL_UDEV_DEVICE_JOYSTICK) {
276 return;
277 }
278
279 SDL_EVDEV_device_added(dev_path, udev_class);
280 break;
281 case SDL_UDEV_DEVICEREMOVED:
282 SDL_EVDEV_device_removed(dev_path);
283 break;
284 default:
285 break;
286 }
287}
288#endif // SDL_USE_LIBUDEV
289
290void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data,
291 void (*acquire_callback)(void*), void *acquire_callback_data)
292{
293 SDL_EVDEV_kbd_set_vt_switch_callbacks(_this->kbd,
294 release_callback, release_callback_data,
295 acquire_callback, acquire_callback_data);
296}
297
298int SDL_EVDEV_GetDeviceCount(int device_class)
299{
300 SDL_evdevlist_item *item;
301 int count = 0;
302
303 for (item = _this->first; item; item = item->next) {
304 if ((item->udev_class & device_class) == device_class) {
305 ++count;
306 }
307 }
308 return count;
309}
310
311void SDL_EVDEV_Poll(void)
312{
313 struct input_event events[32];
314 int i, j, len;
315 SDL_evdevlist_item *item;
316 SDL_Scancode scancode;
317 int mouse_button;
318 SDL_Mouse *mouse;
319 float norm_x, norm_y, norm_pressure;
320
321 if (!_this) {
322 return;
323 }
324
325#ifdef SDL_USE_LIBUDEV
326 SDL_UDEV_Poll();
327#endif
328
329 SDL_EVDEV_kbd_update(_this->kbd);
330
331 mouse = SDL_GetMouse();
332
333 for (item = _this->first; item; item = item->next) {
334 while ((len = read(item->fd, events, sizeof(events))) > 0) {
335 len /= sizeof(events[0]);
336 for (i = 0; i < len; ++i) {
337 struct input_event *event = &events[i];
338
339 /* special handling for touchscreen, that should eventually be
340 used for all devices */
341 if (item->out_of_sync && item->is_touchscreen &&
342 event->type == EV_SYN && event->code != SYN_REPORT) {
343 break;
344 }
345
346 switch (event->type) {
347 case EV_KEY:
348 if (event->code >= BTN_MOUSE && event->code < BTN_MOUSE + SDL_arraysize(EVDEV_MouseButtons)) {
349 Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event);
350 mouse_button = event->code - BTN_MOUSE;
351 SDL_SendMouseButton(timestamp, mouse->focus, (SDL_MouseID)item->fd, EVDEV_MouseButtons[mouse_button], (event->value != 0));
352 break;
353 }
354
355 /* BTN_TOUCH event value 1 indicates there is contact with
356 a touchscreen or trackpad (earliest finger's current
357 position is sent in EV_ABS ABS_X/ABS_Y, switching to
358 next finger after earliest is released) */
359 if (item->is_touchscreen && event->code == BTN_TOUCH) {
360 if (item->touchscreen_data->max_slots == 1) {
361 if (event->value) {
362 item->touchscreen_data->slots[0].delta = EVDEV_TOUCH_SLOTDELTA_DOWN;
363 } else {
364 item->touchscreen_data->slots[0].delta = EVDEV_TOUCH_SLOTDELTA_UP;
365 }
366 }
367 break;
368 }
369
370 // Probably keyboard
371 {
372 Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event);
373 scancode = SDL_EVDEV_translate_keycode(event->code);
374 if (event->value == 0) {
375 SDL_SendKeyboardKey(timestamp, (SDL_KeyboardID)item->fd, event->code, scancode, false);
376 } else if (event->value == 1 || event->value == 2 /* key repeated */) {
377 SDL_SendKeyboardKey(timestamp, (SDL_KeyboardID)item->fd, event->code, scancode, true);
378 }
379 SDL_EVDEV_kbd_keycode(_this->kbd, event->code, event->value);
380 }
381 break;
382 case EV_ABS:
383 switch (event->code) {
384 case ABS_MT_SLOT:
385 if (!item->is_touchscreen) { // FIXME: temp hack
386 break;
387 }
388 item->touchscreen_data->current_slot = event->value;
389 break;
390 case ABS_MT_TRACKING_ID:
391 if (!item->is_touchscreen) { // FIXME: temp hack
392 break;
393 }
394 if (event->value >= 0) {
395 item->touchscreen_data->slots[item->touchscreen_data->current_slot].tracking_id = event->value + 1;
396 item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_DOWN;
397 } else {
398 item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_UP;
399 }
400 break;
401 case ABS_MT_POSITION_X:
402 if (!item->is_touchscreen) { // FIXME: temp hack
403 break;
404 }
405 item->touchscreen_data->slots[item->touchscreen_data->current_slot].x = event->value;
406 if (item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta == EVDEV_TOUCH_SLOTDELTA_NONE) {
407 item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_MOVE;
408 }
409 break;
410 case ABS_MT_POSITION_Y:
411 if (!item->is_touchscreen) { // FIXME: temp hack
412 break;
413 }
414 item->touchscreen_data->slots[item->touchscreen_data->current_slot].y = event->value;
415 if (item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta == EVDEV_TOUCH_SLOTDELTA_NONE) {
416 item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_MOVE;
417 }
418 break;
419 case ABS_MT_PRESSURE:
420 if (!item->is_touchscreen) { // FIXME: temp hack
421 break;
422 }
423 item->touchscreen_data->slots[item->touchscreen_data->current_slot].pressure = event->value;
424 if (item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta == EVDEV_TOUCH_SLOTDELTA_NONE) {
425 item->touchscreen_data->slots[item->touchscreen_data->current_slot].delta = EVDEV_TOUCH_SLOTDELTA_MOVE;
426 }
427 break;
428 case ABS_X:
429 if (item->is_touchscreen) {
430 if (item->touchscreen_data->max_slots != 1) {
431 break;
432 }
433 item->touchscreen_data->slots[0].x = event->value;
434 } else if (!item->relative_mouse) {
435 item->mouse_x = event->value;
436 }
437 break;
438 case ABS_Y:
439 if (item->is_touchscreen) {
440 if (item->touchscreen_data->max_slots != 1) {
441 break;
442 }
443 item->touchscreen_data->slots[0].y = event->value;
444 } else if (!item->relative_mouse) {
445 item->mouse_y = event->value;
446 }
447 break;
448 default:
449 break;
450 }
451 break;
452 case EV_REL:
453 switch (event->code) {
454 case REL_X:
455 if (item->relative_mouse) {
456 item->mouse_x += event->value;
457 }
458 break;
459 case REL_Y:
460 if (item->relative_mouse) {
461 item->mouse_y += event->value;
462 }
463 break;
464 case REL_WHEEL:
465 if (!item->high_res_wheel) {
466 item->mouse_wheel += event->value;
467 }
468 break;
469 case REL_WHEEL_HI_RES:
470 SDL_assert(item->high_res_wheel);
471 item->mouse_wheel += event->value;
472 break;
473 case REL_HWHEEL:
474 if (!item->high_res_hwheel) {
475 item->mouse_hwheel += event->value;
476 }
477 break;
478 case REL_HWHEEL_HI_RES:
479 SDL_assert(item->high_res_hwheel);
480 item->mouse_hwheel += event->value;
481 break;
482 default:
483 break;
484 }
485 break;
486 case EV_SYN:
487 switch (event->code) {
488 case SYN_REPORT:
489 // Send mouse axis changes together to ensure consistency and reduce event processing overhead
490 if (item->relative_mouse) {
491 if (item->mouse_x != 0 || item->mouse_y != 0) {
492 Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event);
493 SDL_SendMouseMotion(timestamp, mouse->focus, (SDL_MouseID)item->fd, item->relative_mouse, (float)item->mouse_x, (float)item->mouse_y);
494 item->mouse_x = item->mouse_y = 0;
495 }
496 } else if (item->range_x > 0 && item->range_y > 0) {
497 int screen_w = 0, screen_h = 0;
498 const SDL_DisplayMode *mode = NULL;
499
500 if (mouse->focus) {
501 mode = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(mouse->focus));
502 }
503 if (!mode) {
504 mode = SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay());
505 }
506 if (mode) {
507 screen_w = mode->w;
508 screen_h = mode->h;
509 }
510 SDL_SendMouseMotion(SDL_EVDEV_GetEventTimestamp(event), mouse->focus, (SDL_MouseID)item->fd, item->relative_mouse,
511 (float)(item->mouse_x - item->min_x) * screen_w / item->range_x,
512 (float)(item->mouse_y - item->min_y) * screen_h / item->range_y);
513 }
514
515 if (item->mouse_wheel != 0 || item->mouse_hwheel != 0) {
516 Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event);
517 const float denom = (item->high_res_hwheel ? 120.0f : 1.0f);
518 SDL_SendMouseWheel(timestamp,
519 mouse->focus, (SDL_MouseID)item->fd,
520 item->mouse_hwheel / denom,
521 item->mouse_wheel / denom,
522 SDL_MOUSEWHEEL_NORMAL);
523 item->mouse_wheel = item->mouse_hwheel = 0;
524 }
525
526 if (!item->is_touchscreen) { // FIXME: temp hack
527 break;
528 }
529
530 for (j = 0; j < item->touchscreen_data->max_slots; j++) {
531 norm_x = (float)(item->touchscreen_data->slots[j].x - item->touchscreen_data->min_x) /
532 (float)item->touchscreen_data->range_x;
533 norm_y = (float)(item->touchscreen_data->slots[j].y - item->touchscreen_data->min_y) /
534 (float)item->touchscreen_data->range_y;
535
536 if (item->touchscreen_data->range_pressure > 0) {
537 norm_pressure = (float)(item->touchscreen_data->slots[j].pressure - item->touchscreen_data->min_pressure) /
538 (float)item->touchscreen_data->range_pressure;
539 } else {
540 // This touchscreen does not support pressure
541 norm_pressure = 1.0f;
542 }
543
544 /* FIXME: the touch's window shouldn't be null, but
545 * the coordinate space of touch positions needs to
546 * be window-relative in that case. */
547 switch (item->touchscreen_data->slots[j].delta) {
548 case EVDEV_TOUCH_SLOTDELTA_DOWN:
549 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);
550 item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE;
551 break;
552 case EVDEV_TOUCH_SLOTDELTA_UP:
553 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);
554 item->touchscreen_data->slots[j].tracking_id = 0;
555 item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE;
556 break;
557 case EVDEV_TOUCH_SLOTDELTA_MOVE:
558 SDL_SendTouchMotion(SDL_EVDEV_GetEventTimestamp(event), item->fd, item->touchscreen_data->slots[j].tracking_id, NULL, norm_x, norm_y, norm_pressure);
559 item->touchscreen_data->slots[j].delta = EVDEV_TOUCH_SLOTDELTA_NONE;
560 break;
561 default:
562 break;
563 }
564 }
565
566 if (item->out_of_sync) {
567 item->out_of_sync = false;
568 }
569 break;
570 case SYN_DROPPED:
571 if (item->is_touchscreen) {
572 item->out_of_sync = true;
573 }
574 SDL_EVDEV_sync_device(item);
575 break;
576 default:
577 break;
578 }
579 break;
580 }
581 }
582 }
583 }
584}
585
586static SDL_Scancode SDL_EVDEV_translate_keycode(int keycode)
587{
588 SDL_Scancode scancode = SDL_GetScancodeFromTable(SDL_SCANCODE_TABLE_LINUX, keycode);
589
590#ifdef DEBUG_SCANCODES
591 if (scancode == SDL_SCANCODE_UNKNOWN) {
592 /* BTN_TOUCH is handled elsewhere, but we might still end up here if
593 you get an unexpected BTN_TOUCH from something SDL believes is not
594 a touch device. In this case, we'd rather not get a misleading
595 SDL_Log message about an unknown key. */
596 if (keycode != BTN_TOUCH) {
597 SDL_Log("The key you just pressed is not recognized by SDL. To help "
598 "get this fixed, please report this to the SDL forums/mailing list "
599 "<https://discourse.libsdl.org/> EVDEV KeyCode %d",
600 keycode);
601 }
602 }
603#endif // DEBUG_SCANCODES
604
605 return scancode;
606}
607
608static bool SDL_EVDEV_init_keyboard(SDL_evdevlist_item *item, int udev_class)
609{
610 char name[128];
611
612 name[0] = '\0';
613 ioctl(item->fd, EVIOCGNAME(sizeof(name)), name);
614
615 SDL_AddKeyboard((SDL_KeyboardID)item->fd, name, true);
616
617 return true;
618}
619
620static void SDL_EVDEV_destroy_keyboard(SDL_evdevlist_item *item)
621{
622 SDL_RemoveKeyboard((SDL_KeyboardID)item->fd, true);
623}
624
625static bool SDL_EVDEV_init_mouse(SDL_evdevlist_item *item, int udev_class)
626{
627 char name[128];
628 int ret;
629 struct input_absinfo abs_info;
630
631 name[0] = '\0';
632 ioctl(item->fd, EVIOCGNAME(sizeof(name)), name);
633
634 SDL_AddMouse((SDL_MouseID)item->fd, name, true);
635
636 ret = ioctl(item->fd, EVIOCGABS(ABS_X), &abs_info);
637 if (ret < 0) {
638 // no absolute mode info, continue
639 return true;
640 }
641 item->min_x = abs_info.minimum;
642 item->max_x = abs_info.maximum;
643 item->range_x = abs_info.maximum - abs_info.minimum;
644
645 ret = ioctl(item->fd, EVIOCGABS(ABS_Y), &abs_info);
646 if (ret < 0) {
647 // no absolute mode info, continue
648 return true;
649 }
650 item->min_y = abs_info.minimum;
651 item->max_y = abs_info.maximum;
652 item->range_y = abs_info.maximum - abs_info.minimum;
653
654 return true;
655}
656
657static void SDL_EVDEV_destroy_mouse(SDL_evdevlist_item *item)
658{
659 SDL_RemoveMouse((SDL_MouseID)item->fd, true);
660}
661
662static bool SDL_EVDEV_init_touchscreen(SDL_evdevlist_item *item, int udev_class)
663{
664 int ret;
665 unsigned long xreq, yreq;
666 char name[64];
667 struct input_absinfo abs_info;
668
669 if (!item->is_touchscreen) {
670 return true;
671 }
672
673 item->touchscreen_data = SDL_calloc(1, sizeof(*item->touchscreen_data));
674 if (!item->touchscreen_data) {
675 return false;
676 }
677
678 ret = ioctl(item->fd, EVIOCGNAME(sizeof(name)), name);
679 if (ret < 0) {
680 SDL_free(item->touchscreen_data);
681 return SDL_SetError("Failed to get evdev touchscreen name");
682 }
683
684 item->touchscreen_data->name = SDL_strdup(name);
685 if (!item->touchscreen_data->name) {
686 SDL_free(item->touchscreen_data);
687 return false;
688 }
689
690 ret = ioctl(item->fd, EVIOCGABS(ABS_MT_SLOT), &abs_info);
691 if (ret < 0) {
692 SDL_free(item->touchscreen_data->name);
693 SDL_free(item->touchscreen_data);
694 return SDL_SetError("Failed to get evdev touchscreen limits");
695 }
696
697 if (abs_info.maximum == 0) {
698 item->touchscreen_data->max_slots = 1;
699 xreq = EVIOCGABS(ABS_X);
700 yreq = EVIOCGABS(ABS_Y);
701 } else {
702 item->touchscreen_data->max_slots = abs_info.maximum + 1;
703 xreq = EVIOCGABS(ABS_MT_POSITION_X);
704 yreq = EVIOCGABS(ABS_MT_POSITION_Y);
705 }
706
707 ret = ioctl(item->fd, xreq, &abs_info);
708 if (ret < 0) {
709 SDL_free(item->touchscreen_data->name);
710 SDL_free(item->touchscreen_data);
711 return SDL_SetError("Failed to get evdev touchscreen limits");
712 }
713 item->touchscreen_data->min_x = abs_info.minimum;
714 item->touchscreen_data->max_x = abs_info.maximum;
715 item->touchscreen_data->range_x = abs_info.maximum - abs_info.minimum;
716
717 ret = ioctl(item->fd, yreq, &abs_info);
718 if (ret < 0) {
719 SDL_free(item->touchscreen_data->name);
720 SDL_free(item->touchscreen_data);
721 return SDL_SetError("Failed to get evdev touchscreen limits");
722 }
723 item->touchscreen_data->min_y = abs_info.minimum;
724 item->touchscreen_data->max_y = abs_info.maximum;
725 item->touchscreen_data->range_y = abs_info.maximum - abs_info.minimum;
726
727 ret = ioctl(item->fd, EVIOCGABS(ABS_MT_PRESSURE), &abs_info);
728 if (ret < 0) {
729 SDL_free(item->touchscreen_data->name);
730 SDL_free(item->touchscreen_data);
731 return SDL_SetError("Failed to get evdev touchscreen limits");
732 }
733 item->touchscreen_data->min_pressure = abs_info.minimum;
734 item->touchscreen_data->max_pressure = abs_info.maximum;
735 item->touchscreen_data->range_pressure = abs_info.maximum - abs_info.minimum;
736
737 item->touchscreen_data->slots = SDL_calloc(
738 item->touchscreen_data->max_slots,
739 sizeof(*item->touchscreen_data->slots));
740 if (!item->touchscreen_data->slots) {
741 SDL_free(item->touchscreen_data->name);
742 SDL_free(item->touchscreen_data);
743 return false;
744 }
745
746 ret = SDL_AddTouch(item->fd, // I guess our fd is unique enough
747 (udev_class & SDL_UDEV_DEVICE_TOUCHPAD) ? SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE : SDL_TOUCH_DEVICE_DIRECT,
748 item->touchscreen_data->name);
749 if (ret < 0) {
750 SDL_free(item->touchscreen_data->slots);
751 SDL_free(item->touchscreen_data->name);
752 SDL_free(item->touchscreen_data);
753 return false;
754 }
755
756 return true;
757}
758
759static void SDL_EVDEV_destroy_touchscreen(SDL_evdevlist_item *item)
760{
761 if (!item->is_touchscreen) {
762 return;
763 }
764
765 SDL_DelTouch(item->fd);
766 SDL_free(item->touchscreen_data->slots);
767 SDL_free(item->touchscreen_data->name);
768 SDL_free(item->touchscreen_data);
769}
770
771static void SDL_EVDEV_sync_device(SDL_evdevlist_item *item)
772{
773#ifdef EVIOCGMTSLOTS
774 int i, ret;
775 struct input_absinfo abs_info;
776 /*
777 * struct input_mt_request_layout {
778 * __u32 code;
779 * __s32 values[num_slots];
780 * };
781 *
782 * this is the structure we're trying to emulate
783 */
784 Uint32 *mt_req_code;
785 Sint32 *mt_req_values;
786 size_t mt_req_size;
787
788 // TODO: sync devices other than touchscreen
789 if (!item->is_touchscreen) {
790 return;
791 }
792
793 mt_req_size = sizeof(*mt_req_code) +
794 sizeof(*mt_req_values) * item->touchscreen_data->max_slots;
795
796 mt_req_code = SDL_calloc(1, mt_req_size);
797 if (!mt_req_code) {
798 return;
799 }
800
801 mt_req_values = (Sint32 *)mt_req_code + 1;
802
803 *mt_req_code = ABS_MT_TRACKING_ID;
804 ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);
805 if (ret < 0) {
806 SDL_free(mt_req_code);
807 return;
808 }
809 for (i = 0; i < item->touchscreen_data->max_slots; i++) {
810 /*
811 * This doesn't account for the very edge case of the user removing their
812 * finger and replacing it on the screen during the time we're out of sync,
813 * which'll mean that we're not going from down -> up or up -> down, we're
814 * going from down -> down but with a different tracking id, meaning we'd
815 * have to tell SDL of the two events, but since we wait till SYN_REPORT in
816 * SDL_EVDEV_Poll to tell SDL, the current structure of this code doesn't
817 * allow it. Lets just pray to God it doesn't happen.
818 */
819 if (item->touchscreen_data->slots[i].tracking_id == 0 &&
820 mt_req_values[i] >= 0) {
821 item->touchscreen_data->slots[i].tracking_id = mt_req_values[i] + 1;
822 item->touchscreen_data->slots[i].delta = EVDEV_TOUCH_SLOTDELTA_DOWN;
823 } else if (item->touchscreen_data->slots[i].tracking_id != 0 &&
824 mt_req_values[i] < 0) {
825 item->touchscreen_data->slots[i].tracking_id = 0;
826 item->touchscreen_data->slots[i].delta = EVDEV_TOUCH_SLOTDELTA_UP;
827 }
828 }
829
830 *mt_req_code = ABS_MT_POSITION_X;
831 ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);
832 if (ret < 0) {
833 SDL_free(mt_req_code);
834 return;
835 }
836 for (i = 0; i < item->touchscreen_data->max_slots; i++) {
837 if (item->touchscreen_data->slots[i].tracking_id != 0 &&
838 item->touchscreen_data->slots[i].x != mt_req_values[i]) {
839 item->touchscreen_data->slots[i].x = mt_req_values[i];
840 if (item->touchscreen_data->slots[i].delta ==
841 EVDEV_TOUCH_SLOTDELTA_NONE) {
842 item->touchscreen_data->slots[i].delta =
843 EVDEV_TOUCH_SLOTDELTA_MOVE;
844 }
845 }
846 }
847
848 *mt_req_code = ABS_MT_POSITION_Y;
849 ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);
850 if (ret < 0) {
851 SDL_free(mt_req_code);
852 return;
853 }
854 for (i = 0; i < item->touchscreen_data->max_slots; i++) {
855 if (item->touchscreen_data->slots[i].tracking_id != 0 &&
856 item->touchscreen_data->slots[i].y != mt_req_values[i]) {
857 item->touchscreen_data->slots[i].y = mt_req_values[i];
858 if (item->touchscreen_data->slots[i].delta ==
859 EVDEV_TOUCH_SLOTDELTA_NONE) {
860 item->touchscreen_data->slots[i].delta =
861 EVDEV_TOUCH_SLOTDELTA_MOVE;
862 }
863 }
864 }
865
866 *mt_req_code = ABS_MT_PRESSURE;
867 ret = ioctl(item->fd, EVIOCGMTSLOTS(mt_req_size), mt_req_code);
868 if (ret < 0) {
869 SDL_free(mt_req_code);
870 return;
871 }
872 for (i = 0; i < item->touchscreen_data->max_slots; i++) {
873 if (item->touchscreen_data->slots[i].tracking_id != 0 &&
874 item->touchscreen_data->slots[i].pressure != mt_req_values[i]) {
875 item->touchscreen_data->slots[i].pressure = mt_req_values[i];
876 if (item->touchscreen_data->slots[i].delta ==
877 EVDEV_TOUCH_SLOTDELTA_NONE) {
878 item->touchscreen_data->slots[i].delta =
879 EVDEV_TOUCH_SLOTDELTA_MOVE;
880 }
881 }
882 }
883
884 ret = ioctl(item->fd, EVIOCGABS(ABS_MT_SLOT), &abs_info);
885 if (ret < 0) {
886 SDL_free(mt_req_code);
887 return;
888 }
889 item->touchscreen_data->current_slot = abs_info.value;
890
891 SDL_free(mt_req_code);
892
893#endif // EVIOCGMTSLOTS
894}
895
896static bool SDL_EVDEV_device_added(const char *dev_path, int udev_class)
897{
898 SDL_evdevlist_item *item;
899 unsigned long relbit[NBITS(REL_MAX)] = { 0 };
900
901 // Check to make sure it's not already in list.
902 for (item = _this->first; item; item = item->next) {
903 if (SDL_strcmp(dev_path, item->path) == 0) {
904 return false; // already have this one
905 }
906 }
907
908 item = (SDL_evdevlist_item *)SDL_calloc(1, sizeof(SDL_evdevlist_item));
909 if (!item) {
910 return false;
911 }
912
913 item->fd = open(dev_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
914 if (item->fd < 0) {
915 SDL_free(item);
916 return SDL_SetError("Unable to open %s", dev_path);
917 }
918
919 item->path = SDL_strdup(dev_path);
920 if (!item->path) {
921 close(item->fd);
922 SDL_free(item);
923 return false;
924 }
925
926 item->udev_class = udev_class;
927
928 if (ioctl(item->fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit) >= 0) {
929 item->relative_mouse = test_bit(REL_X, relbit) && test_bit(REL_Y, relbit);
930 item->high_res_wheel = test_bit(REL_WHEEL_HI_RES, relbit);
931 item->high_res_hwheel = test_bit(REL_HWHEEL_HI_RES, relbit);
932 }
933
934 // For now, we just treat a touchpad like a touchscreen
935 if (udev_class & (SDL_UDEV_DEVICE_TOUCHSCREEN | SDL_UDEV_DEVICE_TOUCHPAD)) {
936 item->is_touchscreen = true;
937 if (!SDL_EVDEV_init_touchscreen(item, udev_class)) {
938 close(item->fd);
939 SDL_free(item->path);
940 SDL_free(item);
941 return false;
942 }
943 }
944
945 if (udev_class & SDL_UDEV_DEVICE_MOUSE) {
946 if (!SDL_EVDEV_init_mouse(item, udev_class)) {
947 close(item->fd);
948 SDL_free(item->path);
949 SDL_free(item);
950 return false;
951 }
952 }
953
954 if (udev_class & SDL_UDEV_DEVICE_KEYBOARD) {
955 if (!SDL_EVDEV_init_keyboard(item, udev_class)) {
956 close(item->fd);
957 SDL_free(item->path);
958 SDL_free(item);
959 return false;
960 }
961 }
962
963 if (!_this->last) {
964 _this->first = _this->last = item;
965 } else {
966 _this->last->next = item;
967 _this->last = item;
968 }
969
970 SDL_EVDEV_sync_device(item);
971
972 SDL_EVDEV_UpdateKeyboardMute();
973
974 ++_this->num_devices;
975 return true;
976}
977
978static bool SDL_EVDEV_device_removed(const char *dev_path)
979{
980 SDL_evdevlist_item *item;
981 SDL_evdevlist_item *prev = NULL;
982
983 for (item = _this->first; item; item = item->next) {
984 // found it, remove it.
985 if (SDL_strcmp(dev_path, item->path) == 0) {
986 if (prev) {
987 prev->next = item->next;
988 } else {
989 SDL_assert(_this->first == item);
990 _this->first = item->next;
991 }
992 if (item == _this->last) {
993 _this->last = prev;
994 }
995
996 if (item->is_touchscreen) {
997 SDL_EVDEV_destroy_touchscreen(item);
998 }
999 if (item->udev_class & SDL_UDEV_DEVICE_MOUSE) {
1000 SDL_EVDEV_destroy_mouse(item);
1001 }
1002 if (item->udev_class & SDL_UDEV_DEVICE_KEYBOARD) {
1003 SDL_EVDEV_destroy_keyboard(item);
1004 }
1005 close(item->fd);
1006 SDL_free(item->path);
1007 SDL_free(item);
1008 SDL_EVDEV_UpdateKeyboardMute();
1009 _this->num_devices--;
1010 return true;
1011 }
1012 prev = item;
1013 }
1014
1015 return false;
1016}
1017
1018Uint64 SDL_EVDEV_GetEventTimestamp(struct input_event *event)
1019{
1020 static Uint64 timestamp_offset;
1021 Uint64 timestamp;
1022 Uint64 now = SDL_GetTicksNS();
1023
1024 /* The kernel internally has nanosecond timestamps, but converts it
1025 to microseconds when delivering the events */
1026 timestamp = event->input_event_sec;
1027 timestamp *= SDL_NS_PER_SECOND;
1028 timestamp += SDL_US_TO_NS(event->input_event_usec);
1029
1030 if (!timestamp_offset) {
1031 timestamp_offset = (now - timestamp);
1032 }
1033 timestamp += timestamp_offset;
1034
1035 if (timestamp > now) {
1036 timestamp_offset -= (timestamp - now);
1037 timestamp = now;
1038 }
1039 return timestamp;
1040}
1041
1042#endif // SDL_INPUT_LINUXEV
1043