Atlas - SDL_x11xinput2.c
Home / ext / SDL / src / video / x11 Lines: 1 | Size: 41112 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 XISetMask(mask, XI_HierarchyChanged); 307 X11_XISelectEvents(data->display, DefaultRootWindow(data->display), &eventmask, 1); 308 309 X11_Xinput2UpdateDevices(_this); 310 X11_Xinput2UpdatePointerMapping(_this); 311 312 return true; 313#else 314 return false; 315#endif 316} 317 318void X11_QuitXinput2(SDL_VideoDevice *_this) 319{ 320#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 321 SDL_free(xinput2_pointer_button_map); 322 xinput2_pointer_button_map = NULL; 323 xinput2_pointer_button_map_size = 0; 324 325#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 326 for (int i = 0; i < scrollable_device_count; ++i) { 327 SDL_free(scrollable_devices[i].scroll_info); 328 } 329 SDL_free(scrollable_devices); 330 scrollable_devices = NULL; 331 scrollable_device_count = 0; 332#endif 333#endif 334} 335 336void X11_Xinput2UpdatePointerMapping(SDL_VideoDevice *_this) 337{ 338#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 339 if (X11_Xinput2IsInitialized()) { 340 SDL_VideoData *vid = _this->internal; 341 342 SDL_free(xinput2_pointer_button_map); 343 xinput2_pointer_button_map = NULL; 344 xinput2_pointer_button_map_size = 0; 345 346 xinput2_pointer_button_map_size = X11_XGetPointerMapping(vid->display, NULL, 0); 347 if (xinput2_pointer_button_map_size) { 348 xinput2_pointer_button_map = SDL_calloc(xinput2_pointer_button_map_size, sizeof(unsigned char)); 349 if (xinput2_pointer_button_map) { 350 xinput2_pointer_button_map_size = X11_XGetPointerMapping(vid->display, xinput2_pointer_button_map, xinput2_pointer_button_map_size); 351 } else { 352 xinput2_pointer_button_map_size = 0; 353 } 354 } 355 } 356#endif 357} 358 359#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 360// xi2 device went away? take it out of the list. 361static void xinput2_remove_device_info(SDL_VideoData *videodata, const int device_id) 362{ 363 SDL_XInput2DeviceInfo *prev = NULL; 364 SDL_XInput2DeviceInfo *devinfo; 365 366 for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) { 367 if (devinfo->device_id == device_id) { 368 SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL)); 369 if (!prev) { 370 videodata->mouse_device_info = devinfo->next; 371 } else { 372 prev->next = devinfo->next; 373 } 374 SDL_free(devinfo); 375 return; 376 } 377 prev = devinfo; 378 } 379} 380 381static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, const int device_id) 382{ 383 // cache device info as we see new devices. 384 SDL_XInput2DeviceInfo *prev = NULL; 385 SDL_XInput2DeviceInfo *devinfo; 386 XIDeviceInfo *xidevinfo; 387 int i; 388 389 for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) { 390 if (devinfo->device_id == device_id) { 391 SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL)); 392 if (prev) { // move this to the front of the list, assuming we'll get more from this one. 393 prev->next = devinfo->next; 394 devinfo->next = videodata->mouse_device_info; 395 videodata->mouse_device_info = devinfo; 396 } 397 return devinfo; 398 } 399 prev = devinfo; 400 } 401 402 // don't know about this device yet, query and cache it. 403 devinfo = (SDL_XInput2DeviceInfo *)SDL_calloc(1, sizeof(SDL_XInput2DeviceInfo)); 404 if (!devinfo) { 405 return NULL; 406 } 407 408 xidevinfo = X11_XIQueryDevice(videodata->display, device_id, &i); 409 if (!xidevinfo) { 410 SDL_free(devinfo); 411 return NULL; 412 } 413 414 devinfo->device_id = device_id; 415 416 /* Search for relative axes with the following priority: 417 * - Labelled 'Rel X'/'Rel Y' 418 * - Labelled 'Abs X'/'Abs Y' 419 * - The first two axes found 420 */ 421 bool have_rel_x = false, have_rel_y = false; 422 bool have_abs_x = false, have_abs_y = false; 423 int axis_index = 0; 424 for (i = 0; i < xidevinfo->num_classes; i++) { 425 const XIValuatorClassInfo *v = (const XIValuatorClassInfo *)xidevinfo->classes[i]; 426 if (v->type == XIValuatorClass) { 427 if (v->label == xinput2_rel_x_atom || (v->label == xinput2_abs_x_atom && !have_rel_x) || 428 (axis_index == 0 && !have_rel_x && !have_abs_x)) { 429 devinfo->number[0] = v->number; 430 devinfo->relative[0] = (v->mode == XIModeRelative); 431 devinfo->minval[0] = v->min; 432 devinfo->maxval[0] = v->max; 433 434 if (v->label == xinput2_rel_x_atom) { 435 have_rel_x = true; 436 } else if (v->label == xinput2_abs_x_atom) { 437 have_abs_x = true; 438 } 439 } else if (v->label == xinput2_rel_y_atom || (v->label == xinput2_abs_y_atom && !have_rel_y) || 440 (axis_index == 1 && !have_rel_y && !have_abs_y)) { 441 devinfo->number[1] = v->number; 442 devinfo->relative[1] = (v->mode == XIModeRelative); 443 devinfo->minval[1] = v->min; 444 devinfo->maxval[1] = v->max; 445 446 if (v->label == xinput2_rel_y_atom) { 447 have_rel_y = true; 448 } else if (v->label == xinput2_abs_y_atom) { 449 have_abs_y = true; 450 } 451 } 452 453 // If two relative axes were found, nothing more to do. 454 if (have_rel_x && have_rel_y) { 455 break; 456 } 457 458 ++axis_index; 459 } 460 } 461 462 X11_XIFreeDeviceInfo(xidevinfo); 463 464 devinfo->next = videodata->mouse_device_info; 465 videodata->mouse_device_info = devinfo; 466 467 return devinfo; 468} 469#endif 470 471void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) 472{ 473#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 474 SDL_VideoData *videodata = _this->internal; 475 476 if (cookie->extension != xinput2_opcode) { 477 return; 478 } 479 480 switch (cookie->evtype) { 481 case XI_HierarchyChanged: 482 { 483 const XIHierarchyEvent *hierev = (const XIHierarchyEvent *)cookie->data; 484 int i; 485 for (i = 0; i < hierev->num_info; i++) { 486 // pen stuff... 487 if ((hierev->info[i].flags & (XISlaveRemoved | XIDeviceDisabled)) != 0) { 488 X11_RemovePenByDeviceID(hierev->info[i].deviceid); // it's okay if this thing isn't actually a pen, it'll handle it. 489 } else if ((hierev->info[i].flags & (XISlaveAdded | XIDeviceEnabled)) != 0) { 490 X11_MaybeAddPenByDeviceID(_this, hierev->info[i].deviceid); // this will do more checks to make sure this is valid. 491 } 492 493 // not pen stuff... 494 if (hierev->info[i].flags & XISlaveRemoved) { 495 xinput2_remove_device_info(videodata, hierev->info[i].deviceid); 496 } 497 } 498 videodata->xinput_hierarchy_changed = true; 499 } break; 500 501 // !!! 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? 502 //case XI_PropertyEvent: 503 //case XI_DeviceChanged: 504 505 case XI_PropertyEvent: 506 { 507 const XIPropertyEvent *proev = (const XIPropertyEvent *)cookie->data; 508 // Handle pen proximity enter/leave 509 if (proev->what == XIPropertyModified && proev->property == videodata->atoms.pen_atom_wacom_serial_ids) { 510 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 511 SDL_WindowData *windowdata = X11_FindWindow(videodata, xev->event); 512 X11_NotifyPenProximityChange(_this, windowdata ? windowdata->window : NULL, proev->deviceid); 513 } 514 } break; 515 516 case XI_RawMotion: 517 { 518 const XIRawEvent *rawev = (const XIRawEvent *)cookie->data; 519 const bool is_pen = X11_FindPenByDeviceID(rawev->sourceid) != NULL; 520 521 videodata->global_mouse_changed = true; 522 if (is_pen) { 523 break; // Pens check for XI_Motion instead 524 } 525 526 SDL_XInput2DeviceInfo *devinfo = xinput2_get_device_info(videodata, rawev->deviceid); 527 if (!devinfo) { 528 break; // oh well. 529 } 530 531 parse_relative_valuators(devinfo, rawev); 532 } break; 533 534 case XI_KeyPress: 535 case XI_KeyRelease: 536 { 537 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 538 SDL_WindowData *windowdata = X11_FindWindow(videodata, xev->event); 539 XEvent xevent; 540 541 if (xev->deviceid != xev->sourceid) { 542 // Discard events from "Master" devices to avoid duplicates. 543 break; 544 } 545 546 if (cookie->evtype == XI_KeyPress) { 547 xevent.type = KeyPress; 548 } else { 549 xevent.type = KeyRelease; 550 } 551 xevent.xkey.serial = xev->serial; 552 xevent.xkey.send_event = xev->send_event; 553 xevent.xkey.display = xev->display; 554 xevent.xkey.window = xev->event; 555 xevent.xkey.root = xev->root; 556 xevent.xkey.subwindow = xev->child; 557 xevent.xkey.time = xev->time; 558 xevent.xkey.x = (int)xev->event_x; 559 xevent.xkey.y = (int)xev->event_y; 560 xevent.xkey.x_root = (int)xev->root_x; 561 xevent.xkey.y_root = (int)xev->root_y; 562 xevent.xkey.state = xev->mods.effective; 563 xevent.xkey.keycode = xev->detail; 564 xevent.xkey.same_screen = 1; 565 566 X11_HandleKeyEvent(_this, windowdata, (SDL_KeyboardID)xev->sourceid, &xevent); 567 } break; 568 569 case XI_RawButtonPress: 570 case XI_RawButtonRelease: 571#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 572 case XI_RawTouchBegin: 573 case XI_RawTouchUpdate: 574 case XI_RawTouchEnd: 575#endif 576 { 577 videodata->global_mouse_changed = true; 578 } break; 579 580 case XI_ButtonPress: 581 case XI_ButtonRelease: 582 { 583 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 584 X11_PenHandle *pen = X11_FindPenByDeviceID(xev->sourceid); 585 int button = xev->detail; 586 const bool down = (cookie->evtype == XI_ButtonPress); 587#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH) 588 bool pointer_emulated = (xev->flags & XIPointerEmulated) != 0; 589#else 590 bool pointer_emulated = false; 591#endif 592 593 if (pen) { 594 if (xev->deviceid != xev->sourceid) { 595 // Discard events from "Master" devices to avoid duplicates. 596 break; 597 } 598 // Only report button event; if there was also pen movement / pressure changes, we expect an XI_Motion event first anyway. 599 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 600 if (button == 1) { // button 1 is the pen tip 601 SDL_SendPenTouch(0, pen->pen, window, pen->is_eraser, down); 602 } else { 603 SDL_SendPenButton(0, pen->pen, window, button - 1, down); 604 } 605 } else if (!pointer_emulated) { 606 // Otherwise assume a regular mouse 607 SDL_WindowData *windowdata = X11_FindWindow(videodata, xev->event); 608 int x_ticks = 0, y_ticks = 0; 609 610 // Slave pointer devices don't have button remapping applied automatically, so do it manually. 611 if (xev->deviceid != videodata->xinput_master_pointer_device) { 612 if (button <= xinput2_pointer_button_map_size) { 613 button = xinput2_pointer_button_map[button - 1]; 614 } 615 } 616 617 /* Discard wheel events from "Master" devices to avoid duplicates, 618 * as coarse wheel events are stateless and can't be deduplicated. 619 */ 620 if (xev->deviceid != xev->sourceid && X11_IsWheelEvent(button, &x_ticks, &y_ticks)) { 621 break; 622 } 623 624 if (down) { 625 X11_HandleButtonPress(_this, windowdata, (SDL_MouseID)xev->sourceid, button, 626 (float)xev->event_x, (float)xev->event_y, xev->time); 627 } else { 628 X11_HandleButtonRelease(_this, windowdata, (SDL_MouseID)xev->sourceid, button, xev->time); 629 } 630 } 631 } break; 632 633#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 634 case XI_Enter: 635 xinput2_reset_scrollable_valuators(); 636 break; 637#endif 638 639 /* Register to receive XI_Motion (which deactivates MotionNotify), so that we can distinguish 640 real mouse motions from synthetic ones, for multitouch and pen support. */ 641 case XI_Motion: 642 { 643 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 644#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH) 645 bool pointer_emulated = (xev->flags & XIPointerEmulated) != 0; 646#else 647 bool pointer_emulated = false; 648#endif 649 650 videodata->global_mouse_changed = true; 651 652 X11_PenHandle *pen = X11_FindPenByDeviceID(xev->sourceid); 653 654 if (pen) { 655 if (xev->deviceid != xev->sourceid) { 656 // Discard events from "Master" devices to avoid duplicates. 657 break; 658 } 659 660 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 661 SDL_SendPenMotion(0, pen->pen, window, (float) xev->event_x, (float) xev->event_y); 662 663 float axes[SDL_PEN_AXIS_COUNT]; 664 X11_PenAxesFromValuators(pen, xev->valuators.values, xev->valuators.mask, xev->valuators.mask_len, axes); 665 666 for (int i = 0; i < SDL_arraysize(axes); i++) { 667 if (pen->valuator_for_axis[i] != SDL_X11_PEN_AXIS_VALUATOR_MISSING) { 668 SDL_SendPenAxis(0, pen->pen, window, (SDL_PenAxis) i, axes[i]); 669 } 670 } 671 } else if (!pointer_emulated) { 672#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 673 if (xev->deviceid == xev->sourceid) { 674 xinput2_parse_scrollable_valuators(xev); 675 } 676#endif 677 678 /* Use the master device for non-relative motion, as the slave devices can seemingly lag behind, 679 * except when the mouse is grabbed and touches are active, as core input events are used for 680 * absolute motion while the mouse is grabbed, and core events don't have the XIPointerEmulated 681 * flag to filter out pointer events emulated from touch events. 682 */ 683 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 684 if (window && (xev->deviceid == videodata->xinput_master_pointer_device || (xinput2_active_touch_count && window->internal->mouse_grabbed))) { 685 SDL_Mouse *mouse = SDL_GetMouse(); 686 if (!mouse->relative_mode) { 687 X11_ProcessHitTest(_this, window->internal, (float)xev->event_x, (float)xev->event_y, false); 688 SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, (float)xev->event_x, (float)xev->event_y); 689 } 690 } 691 } 692 } break; 693 694#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 695 case XI_TouchBegin: 696 { 697 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 698 float x, y; 699 ++xinput2_active_touch_count; 700 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 701 xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); 702 SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_EVENT_FINGER_DOWN, x, y, 1.0); 703 } break; 704 705 case XI_TouchEnd: 706 { 707 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 708 float x, y; 709 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 710 if (!--xinput2_active_touch_count && window && window->internal->mouse_grabbed) { 711 xinput2_grabbed_touch_raised = true; 712 } 713 xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); 714 SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_EVENT_FINGER_UP, x, y, 1.0); 715 } break; 716 717 case XI_TouchUpdate: 718 { 719 const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; 720 float x, y; 721 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 722 xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); 723 SDL_SendTouchMotion(0, xev->sourceid, xev->detail, window, x, y, 1.0); 724 } break; 725#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 726 727#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 728 case XI_GesturePinchBegin: 729 case XI_GesturePinchUpdate: 730 case XI_GesturePinchEnd: 731 { 732 const XIGesturePinchEvent *xev = (const XIGesturePinchEvent *)cookie->data; 733 float x, y; 734 SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); 735 xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); 736 737 if (cookie->evtype == XI_GesturePinchBegin) { 738 SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, window, 0); 739 } else if (cookie->evtype == XI_GesturePinchUpdate) { 740 SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, window, (float)xev->scale); 741 } else { 742 SDL_SendPinch(SDL_EVENT_PINCH_END, 0, window, 0); 743 } 744 } break; 745 746#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 747 748 } 749#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 750} 751 752void X11_InitXinput2Multitouch(SDL_VideoDevice *_this) 753{ 754#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 755 xinput2_grabbed_touch_raised = false; 756 xinput2_active_touch_count = 0; 757#endif 758} 759 760bool X11_Xinput2HandlesMotionForWindow(SDL_WindowData *window_data) 761{ 762#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 763 /* Send the active flag once more after the touch count is zero, to suppress the 764 * emulated motion event when the last touch is raised. 765 */ 766 const bool ret = window_data->xinput2_mouse_enabled && 767 (!window_data->mouse_grabbed || xinput2_active_touch_count || xinput2_grabbed_touch_raised); 768 xinput2_grabbed_touch_raised = false; 769 770 return ret; 771#else 772 return false; 773#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 774} 775 776void X11_Xinput2Select(SDL_VideoDevice *_this, SDL_Window *window) 777{ 778#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH) 779 SDL_VideoData *data = _this->internal; 780 SDL_WindowData *window_data = window->internal; 781 XIEventMask eventmask; 782 unsigned char mask[5] = { 0, 0, 0, 0, 0 }; 783 784 if (!xinput2_scrolling_supported && !xinput2_multitouch_supported) { 785 return; 786 } 787 788 eventmask.deviceid = XIAllMasterDevices; 789 eventmask.mask_len = sizeof(mask); 790 eventmask.mask = mask; 791 792 if (xinput2_scrolling_supported) { 793 /* Track enter events that inform us that we need to update 794 * the previous scroll coordinates since we cannot track 795 * them outside our window. 796 */ 797 XISetMask(mask, XI_Enter); 798 } 799 800 if (xinput2_multitouch_supported) { 801 XISetMask(mask, XI_TouchBegin); 802 XISetMask(mask, XI_TouchUpdate); 803 XISetMask(mask, XI_TouchEnd); 804 XISetMask(mask, XI_Motion); 805 } 806 807#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 808 if (xinput2_gesture_supported) { 809 XISetMask(mask, XI_GesturePinchBegin); 810 XISetMask(mask, XI_GesturePinchUpdate); 811 XISetMask(mask, XI_GesturePinchEnd); 812 } 813#endif 814 815 X11_XISelectEvents(data->display, window_data->xwindow, &eventmask, 1); 816#endif 817} 818 819bool X11_Xinput2IsInitialized(void) 820{ 821#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 822 return xinput2_initialized; 823#else 824 return false; 825#endif 826} 827 828bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window) 829{ 830 SDL_WindowData *windowdata = window->internal; 831 832#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 833 const SDL_VideoData *data = _this->internal; 834 835 if (X11_Xinput2IsInitialized()) { 836 XIEventMask eventmask; 837 unsigned char mask[4] = { 0, 0, 0, 0 }; 838 839 eventmask.mask_len = sizeof(mask); 840 eventmask.mask = mask; 841 eventmask.deviceid = XIAllDevices; 842 843// This is not enabled by default because these events are only delivered to the window with mouse focus, not keyboard focus 844#ifdef USE_XINPUT2_KEYBOARD 845 XISetMask(mask, XI_KeyPress); 846 XISetMask(mask, XI_KeyRelease); 847 windowdata->xinput2_keyboard_enabled = true; 848#endif 849 850 XISetMask(mask, XI_ButtonPress); 851 XISetMask(mask, XI_ButtonRelease); 852 XISetMask(mask, XI_Motion); 853 windowdata->xinput2_mouse_enabled = true; 854 855 XISetMask(mask, XI_Enter); 856 XISetMask(mask, XI_Leave); 857 858 // Hotplugging: 859 XISetMask(mask, XI_DeviceChanged); 860 XISetMask(mask, XI_HierarchyChanged); 861 XISetMask(mask, XI_PropertyEvent); // E.g., when swapping tablet pens 862 863 if (X11_XISelectEvents(data->display, windowdata->xwindow, &eventmask, 1) != Success) { 864 SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Could not enable XInput2 event handling"); 865 windowdata->xinput2_keyboard_enabled = false; 866 windowdata->xinput2_mouse_enabled = false; 867 } 868 } 869#endif 870 871 if (windowdata->xinput2_keyboard_enabled || windowdata->xinput2_mouse_enabled) { 872 return true; 873 } 874 return false; 875} 876 877void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window) 878{ 879#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 880 SDL_WindowData *data = window->internal; 881 Display *display = data->videodata->display; 882 883 unsigned char mask[4] = { 0, 0, 0, 0 }; 884 XIGrabModifiers mods; 885 XIEventMask eventmask; 886 887 if (!xinput2_multitouch_supported) { 888 return; 889 } 890 891 mods.modifiers = XIAnyModifier; 892 mods.status = 0; 893 894 eventmask.deviceid = XIAllDevices; 895 eventmask.mask_len = sizeof(mask); 896 eventmask.mask = mask; 897 898 XISetMask(eventmask.mask, XI_TouchBegin); 899 XISetMask(eventmask.mask, XI_TouchUpdate); 900 XISetMask(eventmask.mask, XI_TouchEnd); 901 XISetMask(eventmask.mask, XI_Motion); 902 903 X11_XIGrabTouchBegin(display, XIAllDevices, data->xwindow, True, &eventmask, 1, &mods); 904#endif 905} 906 907void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window) 908{ 909#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 910 SDL_WindowData *data = window->internal; 911 Display *display = data->videodata->display; 912 913 XIGrabModifiers mods; 914 915 if (!xinput2_multitouch_supported) { 916 return; 917 } 918 919 xinput2_grabbed_touch_raised = false; 920 921 mods.modifiers = XIAnyModifier; 922 mods.status = 0; 923 924 X11_XIUngrabTouchBegin(display, XIAllDevices, data->xwindow, 1, &mods); 925#endif 926} 927 928#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 929 930static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count) 931{ 932 int new_count = (*count + 1); 933 Uint32 *new_list = (Uint32 *)SDL_realloc(*list, new_count * sizeof(*new_list)); 934 if (!new_list) { 935 // Oh well, we'll drop this one 936 return; 937 } 938 new_list[new_count - 1] = deviceID; 939 940 *count = new_count; 941 *list = new_list; 942} 943 944static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count) 945{ 946 for (int i = 0; i < count; ++i) { 947 if (deviceID == list[i]) { 948 return true; 949 } 950 } 951 return false; 952} 953 954#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 955static void AddDeviceID64(Uint64 deviceID, Uint64 **list, int *count) 956{ 957 int new_count = (*count + 1); 958 Uint64 *new_list = (Uint64 *)SDL_realloc(*list, new_count * sizeof(*new_list)); 959 if (!new_list) { 960 // Oh well, we'll drop this one 961 return; 962 } 963 new_list[new_count - 1] = deviceID; 964 965 *count = new_count; 966 *list = new_list; 967} 968#endif 969 970static bool HasDeviceID64(Uint64 deviceID, const Uint64 *list, int count) 971{ 972 for (int i = 0; i < count; ++i) { 973 if (deviceID == list[i]) { 974 return true; 975 } 976 } 977 return false; 978} 979 980#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 981 982void X11_Xinput2UpdateDevices(SDL_VideoDevice *_this) 983{ 984#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 985 SDL_VideoData *data = _this->internal; 986 XIDeviceInfo *info; 987 int ndevices; 988 int old_keyboard_count = 0; 989 SDL_KeyboardID *old_keyboards = NULL; 990 int new_keyboard_count = 0; 991 SDL_KeyboardID *new_keyboards = NULL; 992 int old_mouse_count = 0; 993 SDL_MouseID *old_mice = NULL; 994 int new_mouse_count = 0; 995 SDL_MouseID *new_mice = NULL; 996 int old_touch_count = 0; 997 Uint64 *old_touch_devices = NULL; 998 int new_touch_count = 0; 999 Uint64 *new_touch_devices = NULL; 1000 1001 SDL_assert(X11_Xinput2IsInitialized()); 1002 1003 info = X11_XIQueryDevice(data->display, XIAllDevices, &ndevices); 1004 1005 old_keyboards = SDL_GetKeyboards(&old_keyboard_count); 1006 old_mice = SDL_GetMice(&old_mouse_count); 1007 old_touch_devices = SDL_GetTouchDevices(&old_touch_count); 1008 1009#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 1010 // Scroll devices don't get add/remove events, so just rebuild the list. 1011 for (int i = 0; i < scrollable_device_count; ++i) { 1012 SDL_free(scrollable_devices[i].scroll_info); 1013 } 1014 SDL_free(scrollable_devices); 1015 scrollable_devices = NULL; 1016 scrollable_device_count = 0; 1017#endif 1018 1019 for (int i = 0; i < ndevices; i++) { 1020 XIDeviceInfo *dev = &info[i]; 1021 1022 switch (dev->use) { 1023 case XIMasterKeyboard: 1024 case XISlaveKeyboard: 1025 { 1026 SDL_KeyboardID keyboardID = (SDL_KeyboardID)dev->deviceid; 1027 AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count); 1028 if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) { 1029 SDL_AddKeyboard(keyboardID, dev->name); 1030 } 1031 } 1032 break; 1033 case XIMasterPointer: 1034 data->xinput_master_pointer_device = dev->deviceid; 1035 SDL_FALLTHROUGH; 1036 case XISlavePointer: 1037 { 1038 SDL_MouseID mouseID = (SDL_MouseID)dev->deviceid; 1039 AddDeviceID(mouseID, &new_mice, &new_mouse_count); 1040 if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) { 1041 SDL_AddMouse(mouseID, dev->name); 1042 } 1043 } 1044 break; 1045 default: 1046 break; 1047 } 1048 1049#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 1050 SDL_XInput2ScrollableDevice *sd = NULL; 1051 int allocated_scroll_info_count = 0; 1052 1053 for (int j = 0; j < dev->num_classes; j++) { 1054 const XIAnyClassInfo *class = dev->classes[j]; 1055 const XIScrollClassInfo *s = (XIScrollClassInfo *)class; 1056 1057 if (class->type != XIScrollClass) { 1058 continue; 1059 } 1060 1061 // Allocate a new scrollable device. 1062 if (!sd) { 1063 scrollable_devices = SDL_realloc(scrollable_devices, (scrollable_device_count + 1) * sizeof(SDL_XInput2ScrollableDevice)); 1064 if (!scrollable_devices) { 1065 // No memory; so just skip this. 1066 break; 1067 } 1068 1069 sd = &scrollable_devices[scrollable_device_count]; 1070 ++scrollable_device_count; 1071 1072 SDL_zerop(sd); 1073 sd->device_id = dev->deviceid; 1074 } 1075 1076 // Allocate new scroll info entries two at a time, as they typically come in a horizontal/vertical pair. 1077 if (sd->scroll_info_count == allocated_scroll_info_count) { 1078 sd->scroll_info = SDL_realloc(sd->scroll_info, (allocated_scroll_info_count + 2) * sizeof(SDL_XInput2ScrollInfo)); 1079 if (!sd->scroll_info) { 1080 // No memory; just skip this. 1081 break; 1082 } 1083 1084 allocated_scroll_info_count += 2; 1085 } 1086 1087 SDL_XInput2ScrollInfo *scroll_info = &sd->scroll_info[sd->scroll_info_count]; 1088 ++sd->scroll_info_count; 1089 1090 SDL_zerop(scroll_info); 1091 scroll_info->number = s->number; 1092 scroll_info->scroll_type = s->scroll_type; 1093 scroll_info->increment = s->increment; 1094 } 1095#endif 1096 1097#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1098 for (int j = 0; j < dev->num_classes; j++) { 1099 Uint64 touchID; 1100 SDL_TouchDeviceType touchType; 1101 XIAnyClassInfo *class = dev->classes[j]; 1102 XITouchClassInfo *t = (XITouchClassInfo *)class; 1103 1104 // Only touch devices 1105 if (class->type != XITouchClass) { 1106 continue; 1107 } 1108 1109 touchID = (Uint64)t->sourceid; 1110 AddDeviceID64(touchID, &new_touch_devices, &new_touch_count); 1111 if (!HasDeviceID64(touchID, old_touch_devices, old_touch_count)) { 1112 if (t->mode == XIDependentTouch) { 1113 touchType = SDL_TOUCH_DEVICE_INDIRECT_RELATIVE; 1114 } else { // XIDirectTouch 1115 touchType = SDL_TOUCH_DEVICE_DIRECT; 1116 } 1117 SDL_AddTouch(touchID, touchType, dev->name); 1118 } 1119 } 1120#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1121 } 1122 1123 for (int i = old_keyboard_count; i--;) { 1124 if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) { 1125 SDL_RemoveKeyboard(old_keyboards[i]); 1126 } 1127 } 1128 1129 for (int i = old_mouse_count; i--;) { 1130 if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) { 1131 SDL_RemoveMouse(old_mice[i]); 1132 } 1133 } 1134 1135 for (int i = old_touch_count; i--;) { 1136 if (!HasDeviceID64(old_touch_devices[i], new_touch_devices, new_touch_count)) { 1137 SDL_DelTouch(old_touch_devices[i]); 1138 } 1139 } 1140 1141 SDL_free(old_keyboards); 1142 SDL_free(new_keyboards); 1143 SDL_free(old_mice); 1144 SDL_free(new_mice); 1145 SDL_free(old_touch_devices); 1146 SDL_free(new_touch_devices); 1147 1148 X11_XIFreeDeviceInfo(info); 1149 1150#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 1151} 1152 1153#endif // SDL_VIDEO_DRIVER_X11 1154[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.