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 |
36 | static bool xinput2_initialized; |
37 | |
38 | #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH |
39 | static 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 */ |
46 | static int xinput2_opcode; |
47 | |
48 | static 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 | |
68 | static 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 | |
75 | static bool xinput2_version_atleast(const int version, const int wantmajor, const int wantminor) |
76 | { |
77 | return version >= ((wantmajor * 1000) + wantminor); |
78 | } |
79 | |
80 | static 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 | |
92 | static 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 |
99 | static 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 | |
122 | bool 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. |
204 | static 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 | |
224 | static 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 | |
284 | void 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 | |
515 | void X11_InitXinput2Multitouch(SDL_VideoDevice *_this) |
516 | { |
517 | } |
518 | |
519 | void 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 | |
547 | bool X11_Xinput2IsInitialized(void) |
548 | { |
549 | #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 |
550 | return xinput2_initialized; |
551 | #else |
552 | return false; |
553 | #endif |
554 | } |
555 | |
556 | bool 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 | |
605 | bool 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 | |
614 | void 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 | |
644 | void 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 | |
665 | static 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 | |
679 | static 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 | |
689 | static 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 | |
703 | static 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 | |
715 | void 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 | |