Atlas - SDL_x11xinput2.c

Home / ext / SDL / src / video / x11 Lines: 1 | Size: 40871 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 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; 37#endif 38#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH) 39static bool xinput2_scrolling_supported; 40static bool xinput2_multitouch_supported; 41static bool xinput2_grabbed_touch_raised; 42static int xinput2_active_touch_count; 43#endif 44#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 45static bool xinput2_gesture_supported; 46#endif 47 48/* Opcode returned X11_XQueryExtension 49 * It will be used in event processing 50 * to know that the event came from 51 * this extension */ 52static int xinput2_opcode; 53 54static Atom xinput2_rel_x_atom; 55static Atom xinput2_rel_y_atom; 56static Atom xinput2_abs_x_atom; 57static Atom xinput2_abs_y_atom; 58 59// Pointer button remapping table 60static unsigned char *xinput2_pointer_button_map; 61static int xinput2_pointer_button_map_size; 62 63#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 64typedef struct 65{ 66 int number; 67 int scroll_type; 68 double prev_value; 69 double increment; 70 bool prev_value_valid; 71} SDL_XInput2ScrollInfo; 72 73typedef struct 74{ 75 int device_id; 76 int scroll_info_count; 77 SDL_XInput2ScrollInfo *scroll_info; 78} SDL_XInput2ScrollableDevice; 79 80static SDL_XInput2ScrollableDevice *scrollable_devices; 81static int scrollable_device_count; 82#endif 83 84static void parse_relative_valuators(SDL_XInput2DeviceInfo *devinfo, const XIRawEvent *rawev) 85{ 86 SDL_Mouse *mouse = SDL_GetMouse(); 87 double processed_coords[2] = { 0.0, 0.0 }; 88 int values_i = 0, found = 0; 89 90 // Use the raw values if a custom transform function is set, or the relative system scale hint is unset. 91 const bool use_raw_vals = mouse->InputTransform || !mouse->enable_relative_system_scale; 92 93 for (int i = 0; i < rawev->valuators.mask_len * 8 && found < 2; ++i) { 94 if (!XIMaskIsSet(rawev->valuators.mask, i)) { 95 continue; 96 } 97 98 for (int j = 0; j < 2; ++j) { 99 if (devinfo->number[j] == i) { 100 const double current_val = use_raw_vals ? rawev->raw_values[values_i] : rawev->valuators.values[values_i]; 101 102 if (devinfo->relative[j]) { 103 processed_coords[j] = current_val; 104 } else { 105 processed_coords[j] = (current_val - devinfo->prev_coords[j]); // convert absolute to relative 106 devinfo->prev_coords[j] = current_val; 107 } 108 ++found; 109 110 break; 111 } 112 } 113 114 ++values_i; 115 } 116 117 // Relative mouse motion is delivered to the window with keyboard focus 118 if (mouse->relative_mode && SDL_GetKeyboardFocus()) { 119 SDL_SendMouseMotion(rawev->time, mouse->focus, (SDL_MouseID)rawev->sourceid, true, (float)processed_coords[0], (float)processed_coords[1]); 120 } 121} 122 123static int query_xinput2_version(Display *display, int major, int minor) 124{ 125 // We don't care if this fails, so long as it sets major/minor on it's way out the door. 126 X11_XIQueryVersion(display, &major, &minor); 127 return (major * 1000) + minor; 128} 129 130static bool xinput2_version_atleast(const int version, const int wantmajor, const int wantminor) 131{ 132 return version >= ((wantmajor * 1000) + wantminor); 133} 134 135static SDL_Window *xinput2_get_sdlwindow(SDL_VideoData *videodata, Window window) 136{ 137 const SDL_WindowData *windowdata = X11_FindWindow(videodata, window); 138 return windowdata ? windowdata->window : NULL; 139} 140 141#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 142static void xinput2_reset_scrollable_valuators() 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 xinput2_grabbed_touch_raised = false; 755 xinput2_active_touch_count = 0; 756} 757 758bool X11_Xinput2HandlesMotionForWindow(SDL_WindowData *window_data) 759{ 760 /* Send the active flag once more after the touch count is zero, to suppress the 761 * emulated motion event when the last touch is raised. 762 */ 763 const bool ret = window_data->xinput2_mouse_enabled && 764 (!window_data->mouse_grabbed || xinput2_active_touch_count || xinput2_grabbed_touch_raised); 765 xinput2_grabbed_touch_raised = false; 766 767 return ret; 768} 769 770void X11_Xinput2Select(SDL_VideoDevice *_this, SDL_Window *window) 771{ 772#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH) 773 SDL_VideoData *data = _this->internal; 774 SDL_WindowData *window_data = window->internal; 775 XIEventMask eventmask; 776 unsigned char mask[5] = { 0, 0, 0, 0, 0 }; 777 778 if (!xinput2_scrolling_supported && !xinput2_multitouch_supported) { 779 return; 780 } 781 782 eventmask.deviceid = XIAllMasterDevices; 783 eventmask.mask_len = sizeof(mask); 784 eventmask.mask = mask; 785 786 if (xinput2_scrolling_supported) { 787 /* Track enter events that inform us that we need to update 788 * the previous scroll coordinates since we cannot track 789 * them outside our window. 790 */ 791 XISetMask(mask, XI_Enter); 792 } 793 794 if (xinput2_multitouch_supported) { 795 XISetMask(mask, XI_TouchBegin); 796 XISetMask(mask, XI_TouchUpdate); 797 XISetMask(mask, XI_TouchEnd); 798 XISetMask(mask, XI_Motion); 799 } 800 801#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE 802 if (xinput2_gesture_supported) { 803 XISetMask(mask, XI_GesturePinchBegin); 804 XISetMask(mask, XI_GesturePinchUpdate); 805 XISetMask(mask, XI_GesturePinchEnd); 806 } 807#endif 808 809 X11_XISelectEvents(data->display, window_data->xwindow, &eventmask, 1); 810#endif 811} 812 813bool X11_Xinput2IsInitialized(void) 814{ 815#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 816 return xinput2_initialized; 817#else 818 return false; 819#endif 820} 821 822bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window) 823{ 824 SDL_WindowData *windowdata = window->internal; 825 826#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 827 const SDL_VideoData *data = _this->internal; 828 829 if (X11_Xinput2IsInitialized()) { 830 XIEventMask eventmask; 831 unsigned char mask[4] = { 0, 0, 0, 0 }; 832 833 eventmask.mask_len = sizeof(mask); 834 eventmask.mask = mask; 835 eventmask.deviceid = XIAllDevices; 836 837// This is not enabled by default because these events are only delivered to the window with mouse focus, not keyboard focus 838#ifdef USE_XINPUT2_KEYBOARD 839 XISetMask(mask, XI_KeyPress); 840 XISetMask(mask, XI_KeyRelease); 841 windowdata->xinput2_keyboard_enabled = true; 842#endif 843 844 XISetMask(mask, XI_ButtonPress); 845 XISetMask(mask, XI_ButtonRelease); 846 XISetMask(mask, XI_Motion); 847 windowdata->xinput2_mouse_enabled = true; 848 849 XISetMask(mask, XI_Enter); 850 XISetMask(mask, XI_Leave); 851 852 // Hotplugging: 853 XISetMask(mask, XI_DeviceChanged); 854 XISetMask(mask, XI_HierarchyChanged); 855 XISetMask(mask, XI_PropertyEvent); // E.g., when swapping tablet pens 856 857 if (X11_XISelectEvents(data->display, windowdata->xwindow, &eventmask, 1) != Success) { 858 SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Could not enable XInput2 event handling"); 859 windowdata->xinput2_keyboard_enabled = false; 860 windowdata->xinput2_mouse_enabled = false; 861 } 862 } 863#endif 864 865 if (windowdata->xinput2_keyboard_enabled || windowdata->xinput2_mouse_enabled) { 866 return true; 867 } 868 return false; 869} 870 871void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window) 872{ 873#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 874 SDL_WindowData *data = window->internal; 875 Display *display = data->videodata->display; 876 877 unsigned char mask[4] = { 0, 0, 0, 0 }; 878 XIGrabModifiers mods; 879 XIEventMask eventmask; 880 881 if (!xinput2_multitouch_supported) { 882 return; 883 } 884 885 mods.modifiers = XIAnyModifier; 886 mods.status = 0; 887 888 eventmask.deviceid = XIAllDevices; 889 eventmask.mask_len = sizeof(mask); 890 eventmask.mask = mask; 891 892 XISetMask(eventmask.mask, XI_TouchBegin); 893 XISetMask(eventmask.mask, XI_TouchUpdate); 894 XISetMask(eventmask.mask, XI_TouchEnd); 895 XISetMask(eventmask.mask, XI_Motion); 896 897 X11_XIGrabTouchBegin(display, XIAllDevices, data->xwindow, True, &eventmask, 1, &mods); 898#endif 899} 900 901void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window) 902{ 903#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 904 SDL_WindowData *data = window->internal; 905 Display *display = data->videodata->display; 906 907 XIGrabModifiers mods; 908 909 if (!xinput2_multitouch_supported) { 910 return; 911 } 912 913 xinput2_grabbed_touch_raised = false; 914 915 mods.modifiers = XIAnyModifier; 916 mods.status = 0; 917 918 X11_XIUngrabTouchBegin(display, XIAllDevices, data->xwindow, 1, &mods); 919#endif 920} 921 922#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 923 924static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count) 925{ 926 int new_count = (*count + 1); 927 Uint32 *new_list = (Uint32 *)SDL_realloc(*list, new_count * sizeof(*new_list)); 928 if (!new_list) { 929 // Oh well, we'll drop this one 930 return; 931 } 932 new_list[new_count - 1] = deviceID; 933 934 *count = new_count; 935 *list = new_list; 936} 937 938static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count) 939{ 940 for (int i = 0; i < count; ++i) { 941 if (deviceID == list[i]) { 942 return true; 943 } 944 } 945 return false; 946} 947 948static void AddDeviceID64(Uint64 deviceID, Uint64 **list, int *count) 949{ 950 int new_count = (*count + 1); 951 Uint64 *new_list = (Uint64 *)SDL_realloc(*list, new_count * sizeof(*new_list)); 952 if (!new_list) { 953 // Oh well, we'll drop this one 954 return; 955 } 956 new_list[new_count - 1] = deviceID; 957 958 *count = new_count; 959 *list = new_list; 960} 961 962static bool HasDeviceID64(Uint64 deviceID, const Uint64 *list, int count) 963{ 964 for (int i = 0; i < count; ++i) { 965 if (deviceID == list[i]) { 966 return true; 967 } 968 } 969 return false; 970} 971 972#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 973 974void X11_Xinput2UpdateDevices(SDL_VideoDevice *_this) 975{ 976#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 977 SDL_VideoData *data = _this->internal; 978 XIDeviceInfo *info; 979 int ndevices; 980 int old_keyboard_count = 0; 981 SDL_KeyboardID *old_keyboards = NULL; 982 int new_keyboard_count = 0; 983 SDL_KeyboardID *new_keyboards = NULL; 984 int old_mouse_count = 0; 985 SDL_MouseID *old_mice = NULL; 986 int new_mouse_count = 0; 987 SDL_MouseID *new_mice = NULL; 988 int old_touch_count = 0; 989 Uint64 *old_touch_devices = NULL; 990 int new_touch_count = 0; 991 Uint64 *new_touch_devices = NULL; 992 993 SDL_assert(X11_Xinput2IsInitialized()); 994 995 info = X11_XIQueryDevice(data->display, XIAllDevices, &ndevices); 996 997 old_keyboards = SDL_GetKeyboards(&old_keyboard_count); 998 old_mice = SDL_GetMice(&old_mouse_count); 999 old_touch_devices = SDL_GetTouchDevices(&old_touch_count); 1000 1001#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 1002 // Scroll devices don't get add/remove events, so just rebuild the list. 1003 for (int i = 0; i < scrollable_device_count; ++i) { 1004 SDL_free(scrollable_devices[i].scroll_info); 1005 } 1006 SDL_free(scrollable_devices); 1007 scrollable_devices = NULL; 1008 scrollable_device_count = 0; 1009#endif 1010 1011 for (int i = 0; i < ndevices; i++) { 1012 XIDeviceInfo *dev = &info[i]; 1013 1014 switch (dev->use) { 1015 case XIMasterKeyboard: 1016 case XISlaveKeyboard: 1017 { 1018 SDL_KeyboardID keyboardID = (SDL_KeyboardID)dev->deviceid; 1019 AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count); 1020 if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) { 1021 SDL_AddKeyboard(keyboardID, dev->name); 1022 } 1023 } 1024 break; 1025 case XIMasterPointer: 1026 data->xinput_master_pointer_device = dev->deviceid; 1027 SDL_FALLTHROUGH; 1028 case XISlavePointer: 1029 { 1030 SDL_MouseID mouseID = (SDL_MouseID)dev->deviceid; 1031 AddDeviceID(mouseID, &new_mice, &new_mouse_count); 1032 if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) { 1033 SDL_AddMouse(mouseID, dev->name); 1034 } 1035 } 1036 break; 1037 default: 1038 break; 1039 } 1040 1041#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO 1042 SDL_XInput2ScrollableDevice *sd = NULL; 1043 int allocated_scroll_info_count = 0; 1044 1045 for (int j = 0; j < dev->num_classes; j++) { 1046 const XIAnyClassInfo *class = dev->classes[j]; 1047 const XIScrollClassInfo *s = (XIScrollClassInfo *)class; 1048 1049 if (class->type != XIScrollClass) { 1050 continue; 1051 } 1052 1053 // Allocate a new scrollable device. 1054 if (!sd) { 1055 scrollable_devices = SDL_realloc(scrollable_devices, (scrollable_device_count + 1) * sizeof(SDL_XInput2ScrollableDevice)); 1056 if (!scrollable_devices) { 1057 // No memory; so just skip this. 1058 break; 1059 } 1060 1061 sd = &scrollable_devices[scrollable_device_count]; 1062 ++scrollable_device_count; 1063 1064 SDL_zerop(sd); 1065 sd->device_id = dev->deviceid; 1066 } 1067 1068 // Allocate new scroll info entries two at a time, as they typically come in a horizontal/vertical pair. 1069 if (sd->scroll_info_count == allocated_scroll_info_count) { 1070 sd->scroll_info = SDL_realloc(sd->scroll_info, (allocated_scroll_info_count + 2) * sizeof(SDL_XInput2ScrollInfo)); 1071 if (!sd->scroll_info) { 1072 // No memory; just skip this. 1073 break; 1074 } 1075 1076 allocated_scroll_info_count += 2; 1077 } 1078 1079 SDL_XInput2ScrollInfo *scroll_info = &sd->scroll_info[sd->scroll_info_count]; 1080 ++sd->scroll_info_count; 1081 1082 SDL_zerop(scroll_info); 1083 scroll_info->number = s->number; 1084 scroll_info->scroll_type = s->scroll_type; 1085 scroll_info->increment = s->increment; 1086 } 1087#endif 1088 1089#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1090 for (int j = 0; j < dev->num_classes; j++) { 1091 Uint64 touchID; 1092 SDL_TouchDeviceType touchType; 1093 XIAnyClassInfo *class = dev->classes[j]; 1094 XITouchClassInfo *t = (XITouchClassInfo *)class; 1095 1096 // Only touch devices 1097 if (class->type != XITouchClass) { 1098 continue; 1099 } 1100 1101 touchID = (Uint64)t->sourceid; 1102 AddDeviceID64(touchID, &new_touch_devices, &new_touch_count); 1103 if (!HasDeviceID64(touchID, old_touch_devices, old_touch_count)) { 1104 if (t->mode == XIDependentTouch) { 1105 touchType = SDL_TOUCH_DEVICE_INDIRECT_RELATIVE; 1106 } else { // XIDirectTouch 1107 touchType = SDL_TOUCH_DEVICE_DIRECT; 1108 } 1109 SDL_AddTouch(touchID, touchType, dev->name); 1110 } 1111 } 1112#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1113 } 1114 1115 for (int i = old_keyboard_count; i--;) { 1116 if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) { 1117 SDL_RemoveKeyboard(old_keyboards[i]); 1118 } 1119 } 1120 1121 for (int i = old_mouse_count; i--;) { 1122 if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) { 1123 SDL_RemoveMouse(old_mice[i]); 1124 } 1125 } 1126 1127 for (int i = old_touch_count; i--;) { 1128 if (!HasDeviceID64(old_touch_devices[i], new_touch_devices, new_touch_count)) { 1129 SDL_DelTouch(old_touch_devices[i]); 1130 } 1131 } 1132 1133 SDL_free(old_keyboards); 1134 SDL_free(new_keyboards); 1135 SDL_free(old_mice); 1136 SDL_free(new_mice); 1137 SDL_free(old_touch_devices); 1138 SDL_free(new_touch_devices); 1139 1140 X11_XIFreeDeviceInfo(info); 1141 1142#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 1143} 1144 1145#endif // SDL_VIDEO_DRIVER_X11 1146
[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.