Atlas - SDL_x11xinput2.c

Home / ext / SDL / src / video / x11 Lines: 1 | Size: 42054 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 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; 37static bool xinput2_grabbed_touch_raised; 38static int xinput2_active_touch_count; 39#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH) 40static bool xinput2_scrolling_supported; 41static bool xinput2_multitouch_supported; 42#endif 43#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 44static bool xinput2_gesture_supported; 45#endif 46 47/* Opcode returned X11_XQueryExtension 48 * It will be used in event processing 49 * to know that the event came from 50 * this extension */ 51static int xinput2_opcode; 52 53static Atom xinput2_rel_x_atom; 54static Atom xinput2_rel_y_atom; 55static Atom xinput2_abs_x_atom; 56static Atom xinput2_abs_y_atom; 57 58// Pointer button remapping table 59static unsigned char *xinput2_pointer_button_map; 60static int xinput2_pointer_button_map_size; 61 62#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 63typedef struct 64{ 65 int number; 66 int scroll_type; 67 double prev_value; 68 double increment; 69 bool prev_value_valid; 70} SDL_XInput2ScrollInfo; 71 72typedef struct 73{ 74 int device_id; 75 int scroll_info_count; 76 SDL_XInput2ScrollInfo *scroll_info; 77} SDL_XInput2ScrollableDevice; 78 79static SDL_XInput2ScrollableDevice *scrollable_devices; 80static int scrollable_device_count; 81#endif 82 83static void parse_relative_valuators(SDL_XInput2DeviceInfo *devinfo, const XIRawEvent *rawev) 84{ 85 SDL_Mouse *mouse = SDL_GetMouse(); 86 double processed_coords[2] = { 0.0, 0.0 }; 87 int values_i = 0, found = 0; 88 89 // Use the raw values if a custom transform function is set, or the relative system scale hint is unset. 90 const bool use_raw_vals = mouse->InputTransform || !mouse->enable_relative_system_scale; 91 92 for (int i = 0; i < rawev->valuators.mask_len * 8 && found < 2; ++i) { 93 if (!XIMaskIsSet(rawev->valuators.mask, i)) { 94 continue; 95 } 96 97 for (int j = 0; j < 2; ++j) { 98 if (devinfo->number[j] == i) { 99 const double current_val = use_raw_vals ? rawev->raw_values[values_i] : rawev->valuators.values[values_i]; 100 101 if (devinfo->relative[j]) { 102 processed_coords[j] = current_val; 103 } else { 104 processed_coords[j] = (current_val - devinfo->prev_coords[j]); // convert absolute to relative 105 devinfo->prev_coords[j] = current_val; 106 } 107 ++found; 108 109 break; 110 } 111 } 112 113 ++values_i; 114 } 115 116 // Relative mouse motion is delivered to the window with keyboard focus 117 if (mouse->relative_mode && SDL_GetKeyboardFocus()) { 118 SDL_SendMouseMotion(rawev->time, mouse->focus, (SDL_MouseID)rawev->sourceid, true, (float)processed_coords[0], (float)processed_coords[1]); 119 } 120} 121 122static int query_xinput2_version(Display *display, int major, int minor) 123{ 124 // We don't care if this fails, so long as it sets major/minor on it's way out the door. 125 X11_XIQueryVersion(display, &major, &minor); 126 return (major * 1000) + minor; 127} 128 129static bool xinput2_version_atleast(const int version, const int wantmajor, const int wantminor) 130{ 131 return version >= ((wantmajor * 1000) + wantminor); 132} 133 134static SDL_Window *xinput2_get_sdlwindow(SDL_VideoData *videodata, Window window) 135{ 136 const SDL_WindowData *windowdata = X11_FindWindow(videodata, window); 137 return windowdata ? windowdata->window : NULL; 138} 139#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 140 141#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 142static void xinput2_reset_scrollable_valuators(void) 143{ 144 for (int i = 0; i < scrollable_device_count; ++i) { 145 for (int j = 0; j < scrollable_devices[i].scroll_info_count; ++j) { 146 scrollable_devices[i].scroll_info[j].prev_value_valid = false; 147 } 148 } 149} 150 151static void xinput2_parse_scrollable_valuators(const XIDeviceEvent *xev) 152{ 153 for (int i = 0; i < scrollable_device_count; ++i) { 154 const SDL_XInput2ScrollableDevice *sd = &scrollable_devices[i]; 155 if (xev->sourceid == sd->device_id) { 156 int values_i = 0; 157 for (int j = 0; j < xev->valuators.mask_len * 8; ++j) { 158 if (!XIMaskIsSet(xev->valuators.mask, j)) { 159 continue; 160 } 161 162 for (int k = 0; k < sd->scroll_info_count; ++k) { 163 SDL_XInput2ScrollInfo *info = &sd->scroll_info[k]; 164 if (info->number == j) { 165 const double current_val = xev->valuators.values[values_i]; 166 const double delta = (info->prev_value - current_val) / info->increment; 167 /* Ignore very large jumps that can happen as a result of overflowing 168 * the maximum range, as the driver will reset the position to zero 169 * at "something that's close to 2^32". 170 * 171 * The first scroll event is meaningless by itself and must be discarded, 172 * as it is only useful for establishing a baseline for future deltas. 173 * This is a known deficiency of the XInput2 scroll protocol, and, 174 * unfortunately, there is nothing we can do about it. 175 * 176 * http://who-t.blogspot.com/2012/06/xi-21-protocol-design-issues.html 177 */ 178 if (info->prev_value_valid && SDL_fabs(delta) < (double)SDL_MAX_SINT32 * 0.95) { 179 const double x = info->scroll_type == XIScrollTypeHorizontal ? delta : 0; 180 const double y = info->scroll_type == XIScrollTypeVertical ? delta : 0; 181 182 SDL_Mouse *mouse = SDL_GetMouse(); 183 SDL_SendMouseWheel(xev->time, mouse->focus, (SDL_MouseID)xev->sourceid, (float)-x, (float)y, SDL_MOUSEWHEEL_NORMAL); 184 } 185 info->prev_value = current_val; 186 info->prev_value_valid = true; 187 } 188 } 189 190 ++values_i; 191 } 192 } 193 } 194} 195#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 196 197#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 198static void xinput2_normalize_touch_coordinates(SDL_Window *window, double in_x, double in_y, float *out_x, float *out_y) 199{ 200 if (window) { 201 if (window->w == 1) { 202 *out_x = 0.5f; 203 } else { 204 *out_x = (float)in_x / (window->w - 1); 205 } 206 if (window->h == 1) { 207 *out_y = 0.5f; 208 } else { 209 *out_y = (float)in_y / (window->h - 1); 210 } 211 } else { 212 // couldn't find the window... 213 *out_x = (float)in_x; 214 *out_y = (float)in_y; 215 } 216} 217#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 218 219bool X11_InitXinput2(SDL_VideoDevice *_this) 220{ 221#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 222 SDL_VideoData *data = _this->internal; 223 224 int version = 0; 225 XIEventMask eventmask; 226 unsigned char mask[5] = { 0, 0, 0, 0, 0 }; 227 int event, err; 228 229 /* XInput2 is required for relative mouse mode, so you probably want to leave this enabled */ 230 if (!SDL_GetHintBoolean("SDL_VIDEO_X11_XINPUT2", true)) { 231 return false; 232 } 233 234 /* 235 * Initialize XInput 2 236 * According to http://who-t.blogspot.com/2009/05/xi2-recipes-part-1.html its better 237 * to inform Xserver what version of Xinput we support.The server will store the version we support. 238 * "As XI2 progresses it becomes important that you use this call as the server may treat the client 239 * differently depending on the supported version". 240 * 241 * FIXME:event and err are not needed but if not passed X11_XQueryExtension returns SegmentationFault 242 */ 243 if (!SDL_X11_HAVE_XINPUT2 || 244 !X11_XQueryExtension(data->display, "XInputExtension", &xinput2_opcode, &event, &err)) { 245 return false; // X server does not have XInput at all 246 } 247 248 // We need at least 2.4 for Gesture, 2.2 for Multitouch, 2.0 otherwise. 249 version = query_xinput2_version(data->display, 2, 4); 250 if (!xinput2_version_atleast(version, 2, 0)) { 251 return false; // X server does not support the version we want at all. 252 } 253 254 xinput2_initialized = true; 255 256#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO // Smooth scrolling needs XInput 2.1 257 xinput2_scrolling_supported = xinput2_version_atleast(version, 2, 1); 258#endif 259#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH // Multitouch needs XInput 2.2 260 xinput2_multitouch_supported = xinput2_version_atleast(version, 2, 2); 261#endif 262#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE // Gesture needs XInput 2.4 263 xinput2_gesture_supported = xinput2_version_atleast(version, 2, 4); 264#endif 265 266 // Populate the atoms for finding relative axes 267 xinput2_rel_x_atom = X11_XInternAtom(data->display, "Rel X", False); 268 xinput2_rel_y_atom = X11_XInternAtom(data->display, "Rel Y", False); 269 xinput2_abs_x_atom = X11_XInternAtom(data->display, "Abs X", False); 270 xinput2_abs_y_atom = X11_XInternAtom(data->display, "Abs Y", False); 271 272 // Enable raw motion events for this display 273 SDL_zero(eventmask); 274 SDL_zeroa(mask); 275 eventmask.deviceid = XIAllMasterDevices; 276 eventmask.mask_len = sizeof(mask); 277 eventmask.mask = mask; 278 279 XISetMask(mask, XI_RawMotion); 280 XISetMask(mask, XI_RawButtonPress); 281 XISetMask(mask, XI_RawButtonRelease); 282 283#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 284 if (xinput2_scrolling_supported) { 285 XISetMask(mask, XI_Motion); 286 } 287#endif 288 289#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 290 // Enable raw touch events if supported 291 if (xinput2_multitouch_supported) { 292 XISetMask(mask, XI_RawTouchBegin); 293 XISetMask(mask, XI_RawTouchUpdate); 294 XISetMask(mask, XI_RawTouchEnd); 295 } 296#endif 297 298 X11_XISelectEvents(data->display, DefaultRootWindow(data->display), &eventmask, 1); 299 300 SDL_zero(eventmask); 301 SDL_zeroa(mask); 302 eventmask.deviceid = XIAllDevices; 303 eventmask.mask_len = sizeof(mask); 304 eventmask.mask = mask; 305 306#ifndef USE_XINPUT2_KEYBOARD 307 // If not using the full keyboard handling, register for keypresses to get the event source devices. 308 XISetMask(mask, XI_KeyPress); 309 XISetMask(mask, XI_KeyRelease); 310#endif 311 312 XISetMask(mask, XI_HierarchyChanged); 313 X11_XISelectEvents(data->display, DefaultRootWindow(data->display), &eventmask, 1); 314 315 X11_Xinput2UpdateDevices(_this); 316 X11_Xinput2UpdatePointerMapping(_this); 317 318 return true; 319#else 320 return false; 321#endif 322} 323 324void X11_QuitXinput2(SDL_VideoDevice *_this) 325{ 326#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 327 SDL_free(xinput2_pointer_button_map); 328 xinput2_pointer_button_map = NULL; 329 xinput2_pointer_button_map_size = 0; 330 331#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 332 for (int i = 0; i < scrollable_device_count; ++i) { 333 SDL_free(scrollable_devices[i].scroll_info); 334 } 335 SDL_free(scrollable_devices); 336 scrollable_devices = NULL; 337 scrollable_device_count = 0; 338#endif 339#endif 340} 341 342void X11_Xinput2UpdatePointerMapping(SDL_VideoDevice *_this) 343{ 344#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 345 if (X11_Xinput2IsInitialized()) { 346 SDL_VideoData *vid = _this->internal; 347 348 SDL_free(xinput2_pointer_button_map); 349 xinput2_pointer_button_map = NULL; 350 xinput2_pointer_button_map_size = 0; 351 352 xinput2_pointer_button_map_size = X11_XGetPointerMapping(vid->display, NULL, 0); 353 if (xinput2_pointer_button_map_size) { 354 xinput2_pointer_button_map = SDL_calloc(xinput2_pointer_button_map_size, sizeof(unsigned char)); 355 if (xinput2_pointer_button_map) { 356 xinput2_pointer_button_map_size = X11_XGetPointerMapping(vid->display, xinput2_pointer_button_map, xinput2_pointer_button_map_size); 357 } else { 358 xinput2_pointer_button_map_size = 0; 359 } 360 } 361 } 362#endif 363} 364 365#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 366// xi2 device went away? take it out of the list. 367static void xinput2_remove_device_info(SDL_VideoData *videodata, const int device_id) 368{ 369 SDL_XInput2DeviceInfo *prev = NULL; 370 SDL_XInput2DeviceInfo *devinfo; 371 372 for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) { 373 if (devinfo->device_id == device_id) { 374 SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL)); 375 if (!prev) { 376 videodata->mouse_device_info = devinfo->next; 377 } else { 378 prev->next = devinfo->next; 379 } 380 SDL_free(devinfo); 381 return; 382 } 383 prev = devinfo; 384 } 385} 386 387static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, const int device_id) 388{ 389 // cache device info as we see new devices. 390 SDL_XInput2DeviceInfo *prev = NULL; 391 SDL_XInput2DeviceInfo *devinfo; 392 XIDeviceInfo *xidevinfo; 393 int i; 394 395 for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) { 396 if (devinfo->device_id == device_id) { 397 SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL)); 398 if (prev) { // move this to the front of the list, assuming we'll get more from this one. 399 prev->next = devinfo->next; 400 devinfo->next = videodata->mouse_device_info; 401 videodata->mouse_device_info = devinfo; 402 } 403 return devinfo; 404 } 405 prev = devinfo; 406 } 407 408 // don't know about this device yet, query and cache it. 409 devinfo = (SDL_XInput2DeviceInfo *)SDL_calloc(1, sizeof(SDL_XInput2DeviceInfo)); 410 if (!devinfo) { 411 return NULL; 412 } 413 414 xidevinfo = X11_XIQueryDevice(videodata->display, device_id, &i); 415 if (!xidevinfo) { 416 SDL_free(devinfo); 417 return NULL; 418 } 419 420 devinfo->device_id = device_id; 421 422 /* Search for relative axes with the following priority: 423 * - Labelled 'Rel X'/'Rel Y' 424 * - Labelled 'Abs X'/'Abs Y' 425 * - The first two axes found 426 */ 427 bool have_rel_x = false, have_rel_y = false; 428 bool have_abs_x = false, have_abs_y = false; 429 int axis_index = 0; 430 for (i = 0; i < xidevinfo->num_classes; i++) { 431 const XIValuatorClassInfo *v = (const XIValuatorClassInfo *)xidevinfo->classes[i]; 432 if (v->type == XIValuatorClass) { 433 if (v->label == xinput2_rel_x_atom || (v->label == xinput2_abs_x_atom && !have_rel_x) || 434 (axis_index == 0 && !have_rel_x && !have_abs_x)) { 435 devinfo->number[0] = v->number; 436 devinfo->relative[0] = (v->mode == XIModeRelative); 437 devinfo->minval[0] = v->min; 438 devinfo->maxval[0] = v->max; 439 440 if (v->label == xinput2_rel_x_atom) { 441 have_rel_x = true; 442 } else if (v->label == xinput2_abs_x_atom) { 443 have_abs_x = true; 444 } 445 } else if (v->label == xinput2_rel_y_atom || (v->label == xinput2_abs_y_atom && !have_rel_y) || 446 (axis_index == 1 && !have_rel_y && !have_abs_y)) { 447 devinfo->number[1] = v->number; 448 devinfo->relative[1] = (v->mode == XIModeRelative); 449 devinfo->minval[1] = v->min; 450 devinfo->maxval[1] = v->max; 451 452 if (v->label == xinput2_rel_y_atom) { 453 have_rel_y = true; 454 } else if (v->label == xinput2_abs_y_atom) { 455 have_abs_y = true; 456 } 457 } 458 459 // If two relative axes were found, nothing more to do. 460 if (have_rel_x && have_rel_y) { 461 break; 462 } 463 464 ++axis_index; 465 } 466 } 467 468 X11_XIFreeDeviceInfo(xidevinfo); 469 470 devinfo->next = videodata->mouse_device_info; 471 videodata->mouse_device_info = devinfo; 472 473 return devinfo; 474} 475#endif 476 477void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) 478{ 479#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 480 SDL_VideoData *videodata = _this->internal; 481 482 if (cookie->extension != xinput2_opcode) { 483 return; 484 } 485 486 switch (cookie->evtype) { 487 case XI_HierarchyChanged: 488 { 489 const XIHierarchyEvent *hierev = (const XIHierarchyEvent *)cookie->data; 490 int i; 491 for (i = 0; i < hierev->num_info; i++) { 492 // pen stuff... 493 if ((hierev->info[i].flags & (XISlaveRemoved | XIDeviceDisabled)) != 0) { 494 X11_RemovePenByDeviceID(hierev->info[i].deviceid); // it's okay if this thing isn't actually a pen, it'll handle it. 495 } else if ((hierev->info[i].flags & (XISlaveAdded | XIDeviceEnabled)) != 0) { 496 X11_MaybeAddPenByDeviceID(_this, hierev->info[i].deviceid); // this will do more checks to make sure this is valid. 497 } 498 499 // not pen stuff... 500 if (hierev->info[i].flags & XISlaveRemoved) { 501 xinput2_remove_device_info(videodata, hierev->info[i].deviceid); 502 } 503 } 504 videodata->xinput_hierarchy_changed = true; 505 } break; 506 507 // !!! 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? 508 //case XI_PropertyEvent: 509 //case XI_DeviceChanged: 510 511 case XI_PropertyEvent: 512 { 513 const XIPropertyEvent *proev = (const XIPropertyEvent *)cookie->data; 514 // Handle pen proximity enter/leave 515 if (proev->what == XIPropertyModified && proev->property == videodata->atoms.pen_atom_wacom_serial_ids) { 516 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 517 SDL_WindowData *windowdata = X11_FindWindow(videodata, xev->event); 518 X11_NotifyPenProximityChange(_this, windowdata ? windowdata->window : NULL, proev->deviceid); 519 } 520 } break; 521 522 case XI_RawMotion: 523 { 524 const XIRawEvent *rawev = (const XIRawEvent *)cookie->data; 525 const bool is_pen = X11_FindPenByDeviceID(rawev->sourceid) != NULL; 526 527 videodata->global_mouse_changed = true; 528 if (is_pen) { 529 break; // Pens check for XI_Motion instead 530 } 531 532 SDL_XInput2DeviceInfo *devinfo = xinput2_get_device_info(videodata, rawev->deviceid); 533 if (!devinfo) { 534 break; // oh well. 535 } 536 537 parse_relative_valuators(devinfo, rawev); 538 } break; 539 540 case XI_KeyPress: 541 case XI_KeyRelease: 542 { 543 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 544 545#ifdef USE_XINPUT2_KEYBOARD 546 SDL_WindowData *windowdata = X11_FindWindow(videodata, xev->event); 547 XEvent xevent; 548 549 if (xev->deviceid != xev->sourceid) { 550 // Discard events from "Master" devices to avoid duplicates. 551 break; 552 } 553 554 if (cookie->evtype == XI_KeyPress) { 555 xevent.type = KeyPress; 556 } else { 557 xevent.type = KeyRelease; 558 } 559 xevent.xkey.serial = xev->serial; 560 xevent.xkey.send_event = xev->send_event; 561 xevent.xkey.display = xev->display; 562 xevent.xkey.window = xev->event; 563 xevent.xkey.root = xev->root; 564 xevent.xkey.subwindow = xev->child; 565 xevent.xkey.time = xev->time; 566 xevent.xkey.x = (int)xev->event_x; 567 xevent.xkey.y = (int)xev->event_y; 568 xevent.xkey.x_root = (int)xev->root_x; 569 xevent.xkey.y_root = (int)xev->root_y; 570 xevent.xkey.state = xev->mods.effective; 571 xevent.xkey.keycode = xev->detail; 572 xevent.xkey.same_screen = 1; 573 574 X11_HandleKeyEvent(_this, windowdata, (SDL_KeyboardID)xev->sourceid, &xevent); 575#else 576 /* Keys are handled through core X events, however, note the device ID and 577 * associated serial, so that the source device ID can be passed through. 578 */ 579 videodata->xinput_last_key_serial = xev->serial; 580 videodata->xinput_last_keyboard_device = xev->sourceid; 581#endif 582 } break; 583 584 case XI_RawButtonPress: 585 case XI_RawButtonRelease: 586#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 587 case XI_RawTouchBegin: 588 case XI_RawTouchUpdate: 589 case XI_RawTouchEnd: 590#endif 591 { 592 videodata->global_mouse_changed = true; 593 } break; 594 595 case XI_ButtonPress: 596 case XI_ButtonRelease: 597 { 598 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 599 X11_PenHandle *pen = X11_FindPenByDeviceID(xev->sourceid); 600 int button = xev->detail; 601 const bool down = (cookie->evtype == XI_ButtonPress); 602#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH) 603 bool pointer_emulated = (xev->flags & XIPointerEmulated) != 0; 604#else 605 bool pointer_emulated = false; 606#endif 607 608 if (pen) { 609 if (xev->deviceid != xev->sourceid) { 610 // Discard events from "Master" devices to avoid duplicates. 611 break; 612 } 613 // Only report button event; if there was also pen movement / pressure changes, we expect an XI_Motion event first anyway. 614 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 615 if (button == 1) { // button 1 is the pen tip 616 SDL_SendPenTouch(0, pen->pen, window, pen->is_eraser, down); 617 } else { 618 SDL_SendPenButton(0, pen->pen, window, button - 1, down); 619 } 620 } else if (!pointer_emulated) { 621 // Otherwise assume a regular mouse 622 SDL_WindowData *windowdata = X11_FindWindow(videodata, xev->event); 623 int x_ticks = 0, y_ticks = 0; 624 625 // Store the button serial to filter out redundant core button events. 626 videodata->xinput_last_button_serial = xev->serial; 627 628 if (xev->deviceid != videodata->xinput_master_pointer_device) { 629 // Ignore slave button events on non-focused windows, or focus can be incorrectly set while a grab is active. 630 if (SDL_GetMouseFocus() != windowdata->window) { 631 break; 632 } 633 634 // Slave pointer devices don't have button remapping applied automatically, so do it manually. 635 if (button <= xinput2_pointer_button_map_size) { 636 button = xinput2_pointer_button_map[button - 1]; 637 } 638 } 639 640 /* Discard wheel events from "Master" devices to avoid duplicates, 641 * as coarse wheel events are stateless and can't be deduplicated. 642 */ 643 if (xev->deviceid != xev->sourceid && X11_IsWheelEvent(button, &x_ticks, &y_ticks)) { 644 break; 645 } 646 647 if (down) { 648 X11_HandleButtonPress(_this, windowdata, (SDL_MouseID)xev->sourceid, button, 649 (float)xev->event_x, (float)xev->event_y, xev->time); 650 } else { 651 X11_HandleButtonRelease(_this, windowdata, (SDL_MouseID)xev->sourceid, button, xev->time); 652 } 653 } 654 } break; 655 656#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 657 case XI_Enter: 658 xinput2_reset_scrollable_valuators(); 659 break; 660#endif 661 662 /* Register to receive XI_Motion (which deactivates MotionNotify), so that we can distinguish 663 real mouse motions from synthetic ones, for multitouch and pen support. */ 664 case XI_Motion: 665 { 666 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 667#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH) 668 bool pointer_emulated = (xev->flags & XIPointerEmulated) != 0; 669#else 670 bool pointer_emulated = false; 671#endif 672 673 videodata->global_mouse_changed = true; 674 675 X11_PenHandle *pen = X11_FindPenByDeviceID(xev->sourceid); 676 677 if (pen) { 678 if (xev->deviceid != xev->sourceid) { 679 // Discard events from "Master" devices to avoid duplicates. 680 break; 681 } 682 683 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 684 SDL_SendPenMotion(0, pen->pen, window, (float) xev->event_x, (float) xev->event_y); 685 686 float axes[SDL_PEN_AXIS_COUNT]; 687 X11_PenAxesFromValuators(pen, xev->valuators.values, xev->valuators.mask, xev->valuators.mask_len, axes); 688 689 for (int i = 0; i < SDL_arraysize(axes); i++) { 690 if (pen->valuator_for_axis[i] != SDL_X11_PEN_AXIS_VALUATOR_MISSING) { 691 SDL_SendPenAxis(0, pen->pen, window, (SDL_PenAxis) i, axes[i]); 692 } 693 } 694 } else if (!pointer_emulated) { 695#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 696 if (xev->deviceid == xev->sourceid) { 697 xinput2_parse_scrollable_valuators(xev); 698 } 699#endif 700 701 /* Use the master device for non-relative motion, as the slave devices can seemingly lag behind, 702 * except when the mouse is grabbed and touches are active, as core input events are used for 703 * absolute motion while the mouse is grabbed, and core events don't have the XIPointerEmulated 704 * flag to filter out pointer events emulated from touch events. 705 */ 706 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 707 if (window && (xev->deviceid == videodata->xinput_master_pointer_device || (xinput2_active_touch_count && window->internal->mouse_grabbed))) { 708 SDL_Mouse *mouse = SDL_GetMouse(); 709 if (!mouse->relative_mode) { 710 X11_ProcessHitTest(_this, window->internal, (float)xev->event_x, (float)xev->event_y, false); 711 SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, (float)xev->event_x, (float)xev->event_y); 712 } 713 } 714 } 715 } break; 716 717#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 718 case XI_TouchBegin: 719 { 720 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 721 float x, y; 722 ++xinput2_active_touch_count; 723 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 724 xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); 725 SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_EVENT_FINGER_DOWN, x, y, 1.0); 726 } break; 727 728 case XI_TouchEnd: 729 { 730 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 731 float x, y; 732 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 733 if (!--xinput2_active_touch_count && window && window->internal->mouse_grabbed) { 734 xinput2_grabbed_touch_raised = true; 735 } 736 xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); 737 SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_EVENT_FINGER_UP, x, y, 1.0); 738 } break; 739 740 case XI_TouchUpdate: 741 { 742 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 743 float x, y; 744 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 745 xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); 746 SDL_SendTouchMotion(0, xev->sourceid, xev->detail, window, x, y, 1.0); 747 } break; 748#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 749 750#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 751 case XI_GesturePinchBegin: 752 case XI_GesturePinchUpdate: 753 case XI_GesturePinchEnd: 754 { 755 const XIGesturePinchEvent *xev = (const XIGesturePinchEvent *)cookie->data; 756 float x, y; 757 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 758 xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); 759 760 if (cookie->evtype == XI_GesturePinchBegin) { 761 SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, window, 0); 762 } else if (cookie->evtype == XI_GesturePinchUpdate) { 763 SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, window, (float)xev->scale); 764 } else { 765 SDL_SendPinch(SDL_EVENT_PINCH_END, 0, window, 0); 766 } 767 } break; 768 769#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 770 771 } 772#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 773} 774 775void X11_InitXinput2Multitouch(SDL_VideoDevice *_this) 776{ 777#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 778 xinput2_grabbed_touch_raised = false; 779 xinput2_active_touch_count = 0; 780#endif 781} 782 783bool X11_Xinput2HandlesMotionForWindow(SDL_WindowData *window_data) 784{ 785#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 786 /* Send the active flag once more after the touch count is zero, to suppress the 787 * emulated motion event when the last touch is raised. 788 */ 789 const bool ret = window_data->xinput2_mouse_enabled && 790 (!window_data->mouse_grabbed || xinput2_active_touch_count || xinput2_grabbed_touch_raised); 791 xinput2_grabbed_touch_raised = false; 792 793 return ret; 794#else 795 return false; 796#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 797} 798 799void X11_Xinput2Select(SDL_VideoDevice *_this, SDL_Window *window) 800{ 801#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH) 802 SDL_VideoData *data = _this->internal; 803 SDL_WindowData *window_data = window->internal; 804 XIEventMask eventmask; 805 unsigned char mask[5] = { 0, 0, 0, 0, 0 }; 806 807 if (!xinput2_scrolling_supported && !xinput2_multitouch_supported) { 808 return; 809 } 810 811 eventmask.deviceid = XIAllMasterDevices; 812 eventmask.mask_len = sizeof(mask); 813 eventmask.mask = mask; 814 815 if (xinput2_scrolling_supported) { 816 /* Track enter events that inform us that we need to update 817 * the previous scroll coordinates since we cannot track 818 * them outside our window. 819 */ 820 XISetMask(mask, XI_Enter); 821 } 822 823 if (xinput2_multitouch_supported) { 824 XISetMask(mask, XI_TouchBegin); 825 XISetMask(mask, XI_TouchUpdate); 826 XISetMask(mask, XI_TouchEnd); 827 XISetMask(mask, XI_Motion); 828 } 829 830#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 831 if (xinput2_gesture_supported) { 832 XISetMask(mask, XI_GesturePinchBegin); 833 XISetMask(mask, XI_GesturePinchUpdate); 834 XISetMask(mask, XI_GesturePinchEnd); 835 } 836#endif 837 838 X11_XISelectEvents(data->display, window_data->xwindow, &eventmask, 1); 839#endif 840} 841 842bool X11_Xinput2IsInitialized(void) 843{ 844#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 845 return xinput2_initialized; 846#else 847 return false; 848#endif 849} 850 851bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window) 852{ 853 SDL_WindowData *windowdata = window->internal; 854 855#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 856 const SDL_VideoData *data = _this->internal; 857 858 if (X11_Xinput2IsInitialized()) { 859 XIEventMask eventmask; 860 unsigned char mask[4] = { 0, 0, 0, 0 }; 861 862 eventmask.mask_len = sizeof(mask); 863 eventmask.mask = mask; 864 eventmask.deviceid = XIAllDevices; 865 866// This is not enabled by default because these events are only delivered to the window with mouse focus, not keyboard focus 867#ifdef USE_XINPUT2_KEYBOARD 868 XISetMask(mask, XI_KeyPress); 869 XISetMask(mask, XI_KeyRelease); 870 windowdata->xinput2_keyboard_enabled = true; 871#endif 872 873 XISetMask(mask, XI_ButtonPress); 874 XISetMask(mask, XI_ButtonRelease); 875 XISetMask(mask, XI_Motion); 876 windowdata->xinput2_mouse_enabled = true; 877 878 XISetMask(mask, XI_Enter); 879 XISetMask(mask, XI_Leave); 880 881 // Hotplugging: 882 XISetMask(mask, XI_DeviceChanged); 883 XISetMask(mask, XI_HierarchyChanged); 884 XISetMask(mask, XI_PropertyEvent); // E.g., when swapping tablet pens 885 886 if (X11_XISelectEvents(data->display, windowdata->xwindow, &eventmask, 1) != Success) { 887 SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Could not enable XInput2 event handling"); 888 windowdata->xinput2_keyboard_enabled = false; 889 windowdata->xinput2_mouse_enabled = false; 890 } 891 } 892#endif 893 894 if (windowdata->xinput2_keyboard_enabled || windowdata->xinput2_mouse_enabled) { 895 return true; 896 } 897 return false; 898} 899 900void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window) 901{ 902#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 903 SDL_WindowData *data = window->internal; 904 Display *display = data->videodata->display; 905 906 unsigned char mask[4] = { 0, 0, 0, 0 }; 907 XIGrabModifiers mods; 908 XIEventMask eventmask; 909 910 if (!xinput2_multitouch_supported) { 911 return; 912 } 913 914 mods.modifiers = XIAnyModifier; 915 mods.status = 0; 916 917 eventmask.deviceid = XIAllDevices; 918 eventmask.mask_len = sizeof(mask); 919 eventmask.mask = mask; 920 921 XISetMask(eventmask.mask, XI_TouchBegin); 922 XISetMask(eventmask.mask, XI_TouchUpdate); 923 XISetMask(eventmask.mask, XI_TouchEnd); 924 XISetMask(eventmask.mask, XI_Motion); 925 926 X11_XIGrabTouchBegin(display, XIAllDevices, data->xwindow, True, &eventmask, 1, &mods); 927#endif 928} 929 930void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window) 931{ 932#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 933 SDL_WindowData *data = window->internal; 934 Display *display = data->videodata->display; 935 936 XIGrabModifiers mods; 937 938 if (!xinput2_multitouch_supported) { 939 return; 940 } 941 942 xinput2_grabbed_touch_raised = false; 943 944 mods.modifiers = XIAnyModifier; 945 mods.status = 0; 946 947 X11_XIUngrabTouchBegin(display, XIAllDevices, data->xwindow, 1, &mods); 948#endif 949} 950 951#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 952 953static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count) 954{ 955 int new_count = (*count + 1); 956 Uint32 *new_list = (Uint32 *)SDL_realloc(*list, new_count * sizeof(*new_list)); 957 if (!new_list) { 958 // Oh well, we'll drop this one 959 return; 960 } 961 new_list[new_count - 1] = deviceID; 962 963 *count = new_count; 964 *list = new_list; 965} 966 967static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count) 968{ 969 for (int i = 0; i < count; ++i) { 970 if (deviceID == list[i]) { 971 return true; 972 } 973 } 974 return false; 975} 976 977#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 978static void AddDeviceID64(Uint64 deviceID, Uint64 **list, int *count) 979{ 980 int new_count = (*count + 1); 981 Uint64 *new_list = (Uint64 *)SDL_realloc(*list, new_count * sizeof(*new_list)); 982 if (!new_list) { 983 // Oh well, we'll drop this one 984 return; 985 } 986 new_list[new_count - 1] = deviceID; 987 988 *count = new_count; 989 *list = new_list; 990} 991#endif 992 993static bool HasDeviceID64(Uint64 deviceID, const Uint64 *list, int count) 994{ 995 for (int i = 0; i < count; ++i) { 996 if (deviceID == list[i]) { 997 return true; 998 } 999 } 1000 return false; 1001} 1002 1003#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 1004 1005void X11_Xinput2UpdateDevices(SDL_VideoDevice *_this) 1006{ 1007#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 1008 SDL_VideoData *data = _this->internal; 1009 XIDeviceInfo *info; 1010 int ndevices; 1011 int old_keyboard_count = 0; 1012 SDL_KeyboardID *old_keyboards = NULL; 1013 int new_keyboard_count = 0; 1014 SDL_KeyboardID *new_keyboards = NULL; 1015 int old_mouse_count = 0; 1016 SDL_MouseID *old_mice = NULL; 1017 int new_mouse_count = 0; 1018 SDL_MouseID *new_mice = NULL; 1019 int old_touch_count = 0; 1020 Uint64 *old_touch_devices = NULL; 1021 int new_touch_count = 0; 1022 Uint64 *new_touch_devices = NULL; 1023 1024 SDL_assert(X11_Xinput2IsInitialized()); 1025 1026 info = X11_XIQueryDevice(data->display, XIAllDevices, &ndevices); 1027 1028 old_keyboards = SDL_GetKeyboards(&old_keyboard_count); 1029 old_mice = SDL_GetMice(&old_mouse_count); 1030 old_touch_devices = SDL_GetTouchDevices(&old_touch_count); 1031 1032#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 1033 // Scroll devices don't get add/remove events, so just rebuild the list. 1034 for (int i = 0; i < scrollable_device_count; ++i) { 1035 SDL_free(scrollable_devices[i].scroll_info); 1036 } 1037 SDL_free(scrollable_devices); 1038 scrollable_devices = NULL; 1039 scrollable_device_count = 0; 1040#endif 1041 1042 for (int i = 0; i < ndevices; i++) { 1043 XIDeviceInfo *dev = &info[i]; 1044 1045 switch (dev->use) { 1046 case XIMasterKeyboard: 1047 case XISlaveKeyboard: 1048 { 1049 SDL_KeyboardID keyboardID = (SDL_KeyboardID)dev->deviceid; 1050 AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count); 1051 if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) { 1052 SDL_AddKeyboard(keyboardID, dev->name); 1053 } 1054 } 1055 break; 1056 case XIMasterPointer: 1057 data->xinput_master_pointer_device = dev->deviceid; 1058 SDL_FALLTHROUGH; 1059 case XISlavePointer: 1060 { 1061 SDL_MouseID mouseID = (SDL_MouseID)dev->deviceid; 1062 AddDeviceID(mouseID, &new_mice, &new_mouse_count); 1063 if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) { 1064 SDL_AddMouse(mouseID, dev->name); 1065 } 1066 } 1067 break; 1068 default: 1069 break; 1070 } 1071 1072#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 1073 SDL_XInput2ScrollableDevice *sd = NULL; 1074 int allocated_scroll_info_count = 0; 1075 1076 for (int j = 0; j < dev->num_classes; j++) { 1077 const XIAnyClassInfo *class = dev->classes[j]; 1078 const XIScrollClassInfo *s = (XIScrollClassInfo *)class; 1079 1080 if (class->type != XIScrollClass) { 1081 continue; 1082 } 1083 1084 // Allocate a new scrollable device. 1085 if (!sd) { 1086 scrollable_devices = SDL_realloc(scrollable_devices, (scrollable_device_count + 1) * sizeof(SDL_XInput2ScrollableDevice)); 1087 if (!scrollable_devices) { 1088 // No memory; so just skip this. 1089 break; 1090 } 1091 1092 sd = &scrollable_devices[scrollable_device_count]; 1093 ++scrollable_device_count; 1094 1095 SDL_zerop(sd); 1096 sd->device_id = dev->deviceid; 1097 } 1098 1099 // Allocate new scroll info entries two at a time, as they typically come in a horizontal/vertical pair. 1100 if (sd->scroll_info_count == allocated_scroll_info_count) { 1101 sd->scroll_info = SDL_realloc(sd->scroll_info, (allocated_scroll_info_count + 2) * sizeof(SDL_XInput2ScrollInfo)); 1102 if (!sd->scroll_info) { 1103 // No memory; just skip this. 1104 break; 1105 } 1106 1107 allocated_scroll_info_count += 2; 1108 } 1109 1110 SDL_XInput2ScrollInfo *scroll_info = &sd->scroll_info[sd->scroll_info_count]; 1111 ++sd->scroll_info_count; 1112 1113 SDL_zerop(scroll_info); 1114 scroll_info->number = s->number; 1115 scroll_info->scroll_type = s->scroll_type; 1116 scroll_info->increment = s->increment; 1117 } 1118#endif 1119 1120#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1121 for (int j = 0; j < dev->num_classes; j++) { 1122 Uint64 touchID; 1123 SDL_TouchDeviceType touchType; 1124 XIAnyClassInfo *class = dev->classes[j]; 1125 XITouchClassInfo *t = (XITouchClassInfo *)class; 1126 1127 // Only touch devices 1128 if (class->type != XITouchClass) { 1129 continue; 1130 } 1131 1132 touchID = (Uint64)t->sourceid; 1133 AddDeviceID64(touchID, &new_touch_devices, &new_touch_count); 1134 if (!HasDeviceID64(touchID, old_touch_devices, old_touch_count)) { 1135 if (t->mode == XIDependentTouch) { 1136 touchType = SDL_TOUCH_DEVICE_INDIRECT_RELATIVE; 1137 } else { // XIDirectTouch 1138 touchType = SDL_TOUCH_DEVICE_DIRECT; 1139 } 1140 SDL_AddTouch(touchID, touchType, dev->name); 1141 } 1142 } 1143#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1144 } 1145 1146 for (int i = old_keyboard_count; i--;) { 1147 if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) { 1148 SDL_RemoveKeyboard(old_keyboards[i]); 1149 } 1150 } 1151 1152 for (int i = old_mouse_count; i--;) { 1153 if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) { 1154 SDL_RemoveMouse(old_mice[i]); 1155 } 1156 } 1157 1158 for (int i = old_touch_count; i--;) { 1159 if (!HasDeviceID64(old_touch_devices[i], new_touch_devices, new_touch_count)) { 1160 SDL_DelTouch(old_touch_devices[i]); 1161 } 1162 } 1163 1164 SDL_free(old_keyboards); 1165 SDL_free(new_keyboards); 1166 SDL_free(old_mice); 1167 SDL_free(new_mice); 1168 SDL_free(old_touch_devices); 1169 SDL_free(new_touch_devices); 1170 1171 X11_XIFreeDeviceInfo(info); 1172 1173#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 1174} 1175 1176#endif // SDL_VIDEO_DRIVER_X11 1177
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.