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_VIDEO_DRIVER_X11
24
25#include "SDL_x11pen.h"
26#include "SDL_x11video.h"
27#include "SDL_x11xinput2.h"
28#include "../../events/SDL_events_c.h"
29#include "../../events/SDL_mouse_c.h"
30#include "../../events/SDL_pen_c.h"
31#include "../../events/SDL_touch_c.h"
32
33#define MAX_AXIS 16
34
35#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
36static bool xinput2_initialized;
37
38#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
39static bool xinput2_multitouch_supported;
40#endif
41
42/* Opcode returned X11_XQueryExtension
43 * It will be used in event processing
44 * to know that the event came from
45 * this extension */
46static int xinput2_opcode;
47
48static void parse_valuators(const double *input_values, const unsigned char *mask, int mask_len,
49 double *output_values, int output_values_len)
50{
51 int i = 0, z = 0;
52 int top = mask_len * 8;
53 if (top > MAX_AXIS) {
54 top = MAX_AXIS;
55 }
56
57 SDL_memset(output_values, 0, output_values_len * sizeof(double));
58 for (; i < top && z < output_values_len; i++) {
59 if (XIMaskIsSet(mask, i)) {
60 const int value = (int)*input_values;
61 output_values[z] = value;
62 input_values++;
63 }
64 z++;
65 }
66}
67
68static int query_xinput2_version(Display *display, int major, int minor)
69{
70 // We don't care if this fails, so long as it sets major/minor on it's way out the door.
71 X11_XIQueryVersion(display, &major, &minor);
72 return (major * 1000) + minor;
73}
74
75static bool xinput2_version_atleast(const int version, const int wantmajor, const int wantminor)
76{
77 return version >= ((wantmajor * 1000) + wantminor);
78}
79
80static SDL_WindowData *xinput2_get_sdlwindowdata(SDL_VideoData *videodata, Window window)
81{
82 int i;
83 for (i = 0; i < videodata->numwindows; i++) {
84 SDL_WindowData *d = videodata->windowlist[i];
85 if (d->xwindow == window) {
86 return d;
87 }
88 }
89 return NULL;
90}
91
92static SDL_Window *xinput2_get_sdlwindow(SDL_VideoData *videodata, Window window)
93{
94 const SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, window);
95 return windowdata ? windowdata->window : NULL;
96}
97
98#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
99static void xinput2_normalize_touch_coordinates(SDL_Window *window, double in_x, double in_y, float *out_x, float *out_y)
100{
101 if (window) {
102 if (window->w == 1) {
103 *out_x = 0.5f;
104 } else {
105 *out_x = (float)in_x / (window->w - 1);
106 }
107 if (window->h == 1) {
108 *out_y = 0.5f;
109 } else {
110 *out_y = (float)in_y / (window->h - 1);
111 }
112 } else {
113 // couldn't find the window...
114 *out_x = (float)in_x;
115 *out_y = (float)in_y;
116 }
117}
118#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
119
120#endif // SDL_VIDEO_DRIVER_X11_XINPUT2
121
122bool X11_InitXinput2(SDL_VideoDevice *_this)
123{
124#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
125 SDL_VideoData *data = _this->internal;
126
127 int version = 0;
128 XIEventMask eventmask;
129 unsigned char mask[4] = { 0, 0, 0, 0 };
130 int event, err;
131
132 /* XInput2 is required for relative mouse mode, so you probably want to leave this enabled */
133 if (!SDL_GetHintBoolean("SDL_VIDEO_X11_XINPUT2", true)) {
134 return false;
135 }
136
137 /*
138 * Initialize XInput 2
139 * According to http://who-t.blogspot.com/2009/05/xi2-recipes-part-1.html its better
140 * to inform Xserver what version of Xinput we support.The server will store the version we support.
141 * "As XI2 progresses it becomes important that you use this call as the server may treat the client
142 * differently depending on the supported version".
143 *
144 * FIXME:event and err are not needed but if not passed X11_XQueryExtension returns SegmentationFault
145 */
146 if (!SDL_X11_HAVE_XINPUT2 ||
147 !X11_XQueryExtension(data->display, "XInputExtension", &xinput2_opcode, &event, &err)) {
148 return false; // X server does not have XInput at all
149 }
150
151 // We need at least 2.2 for Multitouch, 2.0 otherwise.
152 version = query_xinput2_version(data->display, 2, 2);
153 if (!xinput2_version_atleast(version, 2, 0)) {
154 return false; // X server does not support the version we want at all.
155 }
156
157 xinput2_initialized = true;
158
159#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH // Multitouch needs XInput 2.2
160 xinput2_multitouch_supported = xinput2_version_atleast(version, 2, 2);
161#endif
162
163 // Enable raw motion events for this display
164 SDL_zero(eventmask);
165 SDL_zeroa(mask);
166 eventmask.deviceid = XIAllMasterDevices;
167 eventmask.mask_len = sizeof(mask);
168 eventmask.mask = mask;
169
170 XISetMask(mask, XI_RawMotion);
171 XISetMask(mask, XI_RawButtonPress);
172 XISetMask(mask, XI_RawButtonRelease);
173
174#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
175 // Enable raw touch events if supported
176 if (X11_Xinput2IsMultitouchSupported()) {
177 XISetMask(mask, XI_RawTouchBegin);
178 XISetMask(mask, XI_RawTouchUpdate);
179 XISetMask(mask, XI_RawTouchEnd);
180 }
181#endif
182
183 X11_XISelectEvents(data->display, DefaultRootWindow(data->display), &eventmask, 1);
184
185 SDL_zero(eventmask);
186 SDL_zeroa(mask);
187 eventmask.deviceid = XIAllDevices;
188 eventmask.mask_len = sizeof(mask);
189 eventmask.mask = mask;
190
191 XISetMask(mask, XI_HierarchyChanged);
192 X11_XISelectEvents(data->display, DefaultRootWindow(data->display), &eventmask, 1);
193
194 X11_Xinput2UpdateDevices(_this, true);
195
196 return true;
197#else
198 return false;
199#endif
200}
201
202#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
203// xi2 device went away? take it out of the list.
204static void xinput2_remove_device_info(SDL_VideoData *videodata, const int device_id)
205{
206 SDL_XInput2DeviceInfo *prev = NULL;
207 SDL_XInput2DeviceInfo *devinfo;
208
209 for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) {
210 if (devinfo->device_id == device_id) {
211 SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL));
212 if (!prev) {
213 videodata->mouse_device_info = devinfo->next;
214 } else {
215 prev->next = devinfo->next;
216 }
217 SDL_free(devinfo);
218 return;
219 }
220 prev = devinfo;
221 }
222}
223
224static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, const int device_id)
225{
226 // cache device info as we see new devices.
227 SDL_XInput2DeviceInfo *prev = NULL;
228 SDL_XInput2DeviceInfo *devinfo;
229 XIDeviceInfo *xidevinfo;
230 int axis = 0;
231 int i;
232
233 for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) {
234 if (devinfo->device_id == device_id) {
235 SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL));
236 if (prev) { // move this to the front of the list, assuming we'll get more from this one.
237 prev->next = devinfo->next;
238 devinfo->next = videodata->mouse_device_info;
239 videodata->mouse_device_info = devinfo;
240 }
241 return devinfo;
242 }
243 prev = devinfo;
244 }
245
246 // don't know about this device yet, query and cache it.
247 devinfo = (SDL_XInput2DeviceInfo *)SDL_calloc(1, sizeof(SDL_XInput2DeviceInfo));
248 if (!devinfo) {
249 return NULL;
250 }
251
252 xidevinfo = X11_XIQueryDevice(videodata->display, device_id, &i);
253 if (!xidevinfo) {
254 SDL_free(devinfo);
255 return NULL;
256 }
257
258 devinfo->device_id = device_id;
259
260 /* !!! FIXME: this is sort of hacky because we only care about the first two axes we see, but any given
261 !!! FIXME: axis could be relative or absolute, and they might not even be the X and Y axes!
262 !!! FIXME: But we go on, for now. Maybe we need a more robust mouse API in SDL3... */
263 for (i = 0; i < xidevinfo->num_classes; i++) {
264 const XIValuatorClassInfo *v = (const XIValuatorClassInfo *)xidevinfo->classes[i];
265 if (v->type == XIValuatorClass) {
266 devinfo->relative[axis] = (v->mode == XIModeRelative);
267 devinfo->minval[axis] = v->min;
268 devinfo->maxval[axis] = v->max;
269 if (++axis >= 2) {
270 break;
271 }
272 }
273 }
274
275 X11_XIFreeDeviceInfo(xidevinfo);
276
277 devinfo->next = videodata->mouse_device_info;
278 videodata->mouse_device_info = devinfo;
279
280 return devinfo;
281}
282#endif
283
284void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
285{
286#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
287 SDL_VideoData *videodata = _this->internal;
288
289 if (cookie->extension != xinput2_opcode) {
290 return;
291 }
292
293 switch (cookie->evtype) {
294 case XI_HierarchyChanged:
295 {
296 const XIHierarchyEvent *hierev = (const XIHierarchyEvent *)cookie->data;
297 int i;
298 for (i = 0; i < hierev->num_info; i++) {
299 // pen stuff...
300 if ((hierev->info[i].flags & (XISlaveRemoved | XIDeviceDisabled)) != 0) {
301 X11_RemovePenByDeviceID(hierev->info[i].deviceid); // it's okay if this thing isn't actually a pen, it'll handle it.
302 } else if ((hierev->info[i].flags & (XISlaveAdded | XIDeviceEnabled)) != 0) {
303 X11_MaybeAddPenByDeviceID(_this, hierev->info[i].deviceid); // this will do more checks to make sure this is valid.
304 }
305
306 // not pen stuff...
307 if (hierev->info[i].flags & XISlaveRemoved) {
308 xinput2_remove_device_info(videodata, hierev->info[i].deviceid);
309 }
310 }
311 videodata->xinput_hierarchy_changed = true;
312 } break;
313
314 // !!! FIXME: the pen code used to rescan all devices here, but we can do this device-by-device with XI_HierarchyChanged. When do these events fire and why?
315 //case XI_PropertyEvent:
316 //case XI_DeviceChanged:
317
318 case XI_RawMotion:
319 {
320 const XIRawEvent *rawev = (const XIRawEvent *)cookie->data;
321 const bool is_pen = X11_FindPenByDeviceID(rawev->sourceid) != NULL;
322 SDL_Mouse *mouse = SDL_GetMouse();
323 SDL_XInput2DeviceInfo *devinfo;
324 double coords[2];
325 double processed_coords[2];
326 int i;
327 Uint64 timestamp = X11_GetEventTimestamp(rawev->time);
328
329 videodata->global_mouse_changed = true;
330 if (is_pen) {
331 break; // Pens check for XI_Motion instead
332 }
333
334 devinfo = xinput2_get_device_info(videodata, rawev->deviceid);
335 if (!devinfo) {
336 break; // oh well.
337 }
338
339 parse_valuators(rawev->raw_values, rawev->valuators.mask,
340 rawev->valuators.mask_len, coords, 2);
341
342 for (i = 0; i < 2; i++) {
343 if (devinfo->relative[i]) {
344 processed_coords[i] = coords[i];
345 } else {
346 processed_coords[i] = devinfo->prev_coords[i] - coords[i]; // convert absolute to relative
347 }
348 }
349
350 // Relative mouse motion is delivered to the window with keyboard focus
351 if (mouse->relative_mode && SDL_GetKeyboardFocus()) {
352 SDL_SendMouseMotion(timestamp, mouse->focus, (SDL_MouseID)rawev->sourceid, true, (float)processed_coords[0], (float)processed_coords[1]);
353 }
354
355 devinfo->prev_coords[0] = coords[0];
356 devinfo->prev_coords[1] = coords[1];
357 } break;
358
359 case XI_KeyPress:
360 case XI_KeyRelease:
361 {
362 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
363 SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event);
364 XEvent xevent;
365
366 if (xev->deviceid != xev->sourceid) {
367 // Discard events from "Master" devices to avoid duplicates.
368 break;
369 }
370
371 if (cookie->evtype == XI_KeyPress) {
372 xevent.type = KeyPress;
373 } else {
374 xevent.type = KeyRelease;
375 }
376 xevent.xkey.serial = xev->serial;
377 xevent.xkey.send_event = xev->send_event;
378 xevent.xkey.display = xev->display;
379 xevent.xkey.window = xev->event;
380 xevent.xkey.root = xev->root;
381 xevent.xkey.subwindow = xev->child;
382 xevent.xkey.time = xev->time;
383 xevent.xkey.x = (int)xev->event_x;
384 xevent.xkey.y = (int)xev->event_y;
385 xevent.xkey.x_root = (int)xev->root_x;
386 xevent.xkey.y_root = (int)xev->root_y;
387 xevent.xkey.state = xev->mods.effective;
388 xevent.xkey.keycode = xev->detail;
389 xevent.xkey.same_screen = 1;
390
391 X11_HandleKeyEvent(_this, windowdata, (SDL_KeyboardID)xev->sourceid, &xevent);
392 } break;
393
394 case XI_RawButtonPress:
395 case XI_RawButtonRelease:
396#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
397 case XI_RawTouchBegin:
398 case XI_RawTouchUpdate:
399 case XI_RawTouchEnd:
400#endif
401 {
402 videodata->global_mouse_changed = true;
403 } break;
404
405 case XI_ButtonPress:
406 case XI_ButtonRelease:
407 {
408 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
409 X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid);
410 const int button = xev->detail;
411 const bool down = (cookie->evtype == XI_ButtonPress);
412
413 if (pen) {
414 // Only report button event; if there was also pen movement / pressure changes, we expect an XI_Motion event first anyway.
415 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
416 if (button == 1) { // button 1 is the pen tip
417 SDL_SendPenTouch(0, pen->pen, window, pen->is_eraser, down);
418 } else {
419 SDL_SendPenButton(0, pen->pen, window, button - 1, down);
420 }
421 } else {
422 // Otherwise assume a regular mouse
423 SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, xev->event);
424
425 if (xev->deviceid != xev->sourceid) {
426 // Discard events from "Master" devices to avoid duplicates.
427 break;
428 }
429
430 if (down) {
431 X11_HandleButtonPress(_this, windowdata, (SDL_MouseID)xev->sourceid, button,
432 (float)xev->event_x, (float)xev->event_y, xev->time);
433 } else {
434 X11_HandleButtonRelease(_this, windowdata, (SDL_MouseID)xev->sourceid, button, xev->time);
435 }
436 }
437 } break;
438
439 /* Register to receive XI_Motion (which deactivates MotionNotify), so that we can distinguish
440 real mouse motions from synthetic ones, for multitouch and pen support. */
441 case XI_Motion:
442 {
443 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
444#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
445 bool pointer_emulated = ((xev->flags & XIPointerEmulated) != 0);
446#else
447 bool pointer_emulated = false;
448#endif
449
450 videodata->global_mouse_changed = true;
451
452 X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid);
453 if (pen) {
454 if (xev->deviceid != xev->sourceid) {
455 // Discard events from "Master" devices to avoid duplicates.
456 break;
457 }
458
459 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
460 SDL_SendPenMotion(0, pen->pen, window, (float) xev->event_x, (float) xev->event_y);
461
462 float axes[SDL_PEN_AXIS_COUNT];
463 X11_PenAxesFromValuators(pen, xev->valuators.values, xev->valuators.mask, xev->valuators.mask_len, axes);
464
465 for (int i = 0; i < SDL_arraysize(axes); i++) {
466 if (pen->valuator_for_axis[i] != SDL_X11_PEN_AXIS_VALUATOR_MISSING) {
467 SDL_SendPenAxis(0, pen->pen, window, (SDL_PenAxis) i, axes[i]);
468 }
469 }
470 } else if (!pointer_emulated && xev->deviceid == videodata->xinput_master_pointer_device) {
471 // Use the master device for non-relative motion, as the slave devices can seemingly lag behind.
472 SDL_Mouse *mouse = SDL_GetMouse();
473 if (!mouse->relative_mode) {
474 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
475 if (window) {
476 X11_ProcessHitTest(_this, window->internal, (float)xev->event_x, (float)xev->event_y, false);
477 SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, (float)xev->event_x, (float)xev->event_y);
478 }
479 }
480 }
481 } break;
482
483#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
484 case XI_TouchBegin:
485 {
486 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
487 float x, y;
488 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
489 xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y);
490 SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_EVENT_FINGER_DOWN, x, y, 1.0);
491 } break;
492
493 case XI_TouchEnd:
494 {
495 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
496 float x, y;
497 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
498 xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y);
499 SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_EVENT_FINGER_UP, x, y, 1.0);
500 } break;
501
502 case XI_TouchUpdate:
503 {
504 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
505 float x, y;
506 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
507 xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y);
508 SDL_SendTouchMotion(0, xev->sourceid, xev->detail, window, x, y, 1.0);
509 } break;
510#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
511 }
512#endif // SDL_VIDEO_DRIVER_X11_XINPUT2
513}
514
515void X11_InitXinput2Multitouch(SDL_VideoDevice *_this)
516{
517}
518
519void X11_Xinput2SelectTouch(SDL_VideoDevice *_this, SDL_Window *window)
520{
521#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
522 SDL_VideoData *data = NULL;
523 XIEventMask eventmask;
524 unsigned char mask[4] = { 0, 0, 0, 0 };
525 SDL_WindowData *window_data = NULL;
526
527 if (!X11_Xinput2IsMultitouchSupported()) {
528 return;
529 }
530
531 data = _this->internal;
532 window_data = window->internal;
533
534 eventmask.deviceid = XIAllMasterDevices;
535 eventmask.mask_len = sizeof(mask);
536 eventmask.mask = mask;
537
538 XISetMask(mask, XI_TouchBegin);
539 XISetMask(mask, XI_TouchUpdate);
540 XISetMask(mask, XI_TouchEnd);
541 XISetMask(mask, XI_Motion);
542
543 X11_XISelectEvents(data->display, window_data->xwindow, &eventmask, 1);
544#endif
545}
546
547bool X11_Xinput2IsInitialized(void)
548{
549#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
550 return xinput2_initialized;
551#else
552 return false;
553#endif
554}
555
556bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
557{
558 SDL_WindowData *windowdata = window->internal;
559
560#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
561 const SDL_VideoData *data = _this->internal;
562
563 if (X11_Xinput2IsInitialized()) {
564 XIEventMask eventmask;
565 unsigned char mask[4] = { 0, 0, 0, 0 };
566
567 eventmask.mask_len = sizeof(mask);
568 eventmask.mask = mask;
569 eventmask.deviceid = XIAllDevices;
570
571// This is not enabled by default because these events are only delivered to the window with mouse focus, not keyboard focus
572#ifdef USE_XINPUT2_KEYBOARD
573 XISetMask(mask, XI_KeyPress);
574 XISetMask(mask, XI_KeyRelease);
575 windowdata->xinput2_keyboard_enabled = true;
576#endif
577
578 XISetMask(mask, XI_ButtonPress);
579 XISetMask(mask, XI_ButtonRelease);
580 XISetMask(mask, XI_Motion);
581 windowdata->xinput2_mouse_enabled = true;
582
583 XISetMask(mask, XI_Enter);
584 XISetMask(mask, XI_Leave);
585
586 // Hotplugging:
587 XISetMask(mask, XI_DeviceChanged);
588 XISetMask(mask, XI_HierarchyChanged);
589 XISetMask(mask, XI_PropertyEvent); // E.g., when swapping tablet pens
590
591 if (X11_XISelectEvents(data->display, windowdata->xwindow, &eventmask, 1) != Success) {
592 SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Could not enable XInput2 event handling");
593 windowdata->xinput2_keyboard_enabled = false;
594 windowdata->xinput2_mouse_enabled = false;
595 }
596 }
597#endif
598
599 if (windowdata->xinput2_keyboard_enabled || windowdata->xinput2_mouse_enabled) {
600 return true;
601 }
602 return false;
603}
604
605bool X11_Xinput2IsMultitouchSupported(void)
606{
607#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
608 return xinput2_initialized && xinput2_multitouch_supported;
609#else
610 return true;
611#endif
612}
613
614void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window)
615{
616#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
617 SDL_WindowData *data = window->internal;
618 Display *display = data->videodata->display;
619
620 unsigned char mask[4] = { 0, 0, 0, 0 };
621 XIGrabModifiers mods;
622 XIEventMask eventmask;
623
624 if (!X11_Xinput2IsMultitouchSupported()) {
625 return;
626 }
627
628 mods.modifiers = XIAnyModifier;
629 mods.status = 0;
630
631 eventmask.deviceid = XIAllDevices;
632 eventmask.mask_len = sizeof(mask);
633 eventmask.mask = mask;
634
635 XISetMask(eventmask.mask, XI_TouchBegin);
636 XISetMask(eventmask.mask, XI_TouchUpdate);
637 XISetMask(eventmask.mask, XI_TouchEnd);
638 XISetMask(eventmask.mask, XI_Motion);
639
640 X11_XIGrabTouchBegin(display, XIAllDevices, data->xwindow, True, &eventmask, 1, &mods);
641#endif
642}
643
644void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window)
645{
646#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
647 SDL_WindowData *data = window->internal;
648 Display *display = data->videodata->display;
649
650 XIGrabModifiers mods;
651
652 if (!X11_Xinput2IsMultitouchSupported()) {
653 return;
654 }
655
656 mods.modifiers = XIAnyModifier;
657 mods.status = 0;
658
659 X11_XIUngrabTouchBegin(display, XIAllDevices, data->xwindow, 1, &mods);
660#endif
661}
662
663#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
664
665static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count)
666{
667 int new_count = (*count + 1);
668 Uint32 *new_list = (Uint32 *)SDL_realloc(*list, new_count * sizeof(*new_list));
669 if (!new_list) {
670 // Oh well, we'll drop this one
671 return;
672 }
673 new_list[new_count - 1] = deviceID;
674
675 *count = new_count;
676 *list = new_list;
677}
678
679static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count)
680{
681 for (int i = 0; i < count; ++i) {
682 if (deviceID == list[i]) {
683 return true;
684 }
685 }
686 return false;
687}
688
689static void AddDeviceID64(Uint64 deviceID, Uint64 **list, int *count)
690{
691 int new_count = (*count + 1);
692 Uint64 *new_list = (Uint64 *)SDL_realloc(*list, new_count * sizeof(*new_list));
693 if (!new_list) {
694 // Oh well, we'll drop this one
695 return;
696 }
697 new_list[new_count - 1] = deviceID;
698
699 *count = new_count;
700 *list = new_list;
701}
702
703static bool HasDeviceID64(Uint64 deviceID, const Uint64 *list, int count)
704{
705 for (int i = 0; i < count; ++i) {
706 if (deviceID == list[i]) {
707 return true;
708 }
709 }
710 return false;
711}
712
713#endif // SDL_VIDEO_DRIVER_X11_XINPUT2
714
715void X11_Xinput2UpdateDevices(SDL_VideoDevice *_this, bool initial_check)
716{
717#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
718 SDL_VideoData *data = _this->internal;
719 XIDeviceInfo *info;
720 int ndevices;
721 int old_keyboard_count = 0;
722 SDL_KeyboardID *old_keyboards = NULL;
723 int new_keyboard_count = 0;
724 SDL_KeyboardID *new_keyboards = NULL;
725 int old_mouse_count = 0;
726 SDL_MouseID *old_mice = NULL;
727 int new_mouse_count = 0;
728 SDL_MouseID *new_mice = NULL;
729 int old_touch_count = 0;
730 Uint64 *old_touch_devices = NULL;
731 int new_touch_count = 0;
732 Uint64 *new_touch_devices = NULL;
733 bool send_event = !initial_check;
734
735 SDL_assert(X11_Xinput2IsInitialized());
736
737 info = X11_XIQueryDevice(data->display, XIAllDevices, &ndevices);
738
739 old_keyboards = SDL_GetKeyboards(&old_keyboard_count);
740 old_mice = SDL_GetMice(&old_mouse_count);
741 old_touch_devices = SDL_GetTouchDevices(&old_touch_count);
742
743 for (int i = 0; i < ndevices; i++) {
744 XIDeviceInfo *dev = &info[i];
745
746 switch (dev->use) {
747 case XIMasterKeyboard:
748 case XISlaveKeyboard:
749 {
750 SDL_KeyboardID keyboardID = (SDL_KeyboardID)dev->deviceid;
751 AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count);
752 if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) {
753 SDL_AddKeyboard(keyboardID, dev->name, send_event);
754 }
755 }
756 break;
757 case XIMasterPointer:
758 data->xinput_master_pointer_device = dev->deviceid;
759 SDL_FALLTHROUGH;
760 case XISlavePointer:
761 {
762 SDL_MouseID mouseID = (SDL_MouseID)dev->deviceid;
763 AddDeviceID(mouseID, &new_mice, &new_mouse_count);
764 if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) {
765 SDL_AddMouse(mouseID, dev->name, send_event);
766 }
767 }
768 break;
769 default:
770 break;
771 }
772
773#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
774 for (int j = 0; j < dev->num_classes; j++) {
775 Uint64 touchID;
776 SDL_TouchDeviceType touchType;
777 XIAnyClassInfo *class = dev->classes[j];
778 XITouchClassInfo *t = (XITouchClassInfo *)class;
779
780 // Only touch devices
781 if (class->type != XITouchClass) {
782 continue;
783 }
784
785 touchID = (Uint64)t->sourceid;
786 AddDeviceID64(touchID, &new_touch_devices, &new_touch_count);
787 if (!HasDeviceID64(touchID, old_touch_devices, old_touch_count)) {
788 if (t->mode == XIDependentTouch) {
789 touchType = SDL_TOUCH_DEVICE_INDIRECT_RELATIVE;
790 } else { // XIDirectTouch
791 touchType = SDL_TOUCH_DEVICE_DIRECT;
792 }
793 SDL_AddTouch(touchID, touchType, dev->name);
794 }
795 }
796#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
797 }
798
799 for (int i = old_keyboard_count; i--;) {
800 if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) {
801 SDL_RemoveKeyboard(old_keyboards[i], send_event);
802 }
803 }
804
805 for (int i = old_mouse_count; i--;) {
806 if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) {
807 SDL_RemoveMouse(old_mice[i], send_event);
808 }
809 }
810
811 for (int i = old_touch_count; i--;) {
812 if (!HasDeviceID64(old_touch_devices[i], new_touch_devices, new_touch_count)) {
813 SDL_DelTouch(old_touch_devices[i]);
814 }
815 }
816
817 SDL_free(old_keyboards);
818 SDL_free(new_keyboards);
819 SDL_free(old_mice);
820 SDL_free(new_mice);
821 SDL_free(old_touch_devices);
822 SDL_free(new_touch_devices);
823
824 X11_XIFreeDeviceInfo(info);
825
826#endif // SDL_VIDEO_DRIVER_X11_XINPUT2
827}
828
829#endif // SDL_VIDEO_DRIVER_X11
830