From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.c | 829 +++++++++++++++++++++++ 1 file changed, 829 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.c (limited to 'contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.c') diff --git a/contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.c b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.c new file mode 100644 index 0000000..afe4a7c --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/x11/SDL_x11xinput2.c @@ -0,0 +1,829 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_X11 + +#include "SDL_x11pen.h" +#include "SDL_x11video.h" +#include "SDL_x11xinput2.h" +#include "../../events/SDL_events_c.h" +#include "../../events/SDL_mouse_c.h" +#include "../../events/SDL_pen_c.h" +#include "../../events/SDL_touch_c.h" + +#define MAX_AXIS 16 + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 +static bool xinput2_initialized; + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH +static bool xinput2_multitouch_supported; +#endif + +/* Opcode returned X11_XQueryExtension + * It will be used in event processing + * to know that the event came from + * this extension */ +static int xinput2_opcode; + +static void parse_valuators(const double *input_values, const unsigned char *mask, int mask_len, + double *output_values, int output_values_len) +{ + int i = 0, z = 0; + int top = mask_len * 8; + if (top > MAX_AXIS) { + top = MAX_AXIS; + } + + SDL_memset(output_values, 0, output_values_len * sizeof(double)); + for (; i < top && z < output_values_len; i++) { + if (XIMaskIsSet(mask, i)) { + const int value = (int)*input_values; + output_values[z] = value; + input_values++; + } + z++; + } +} + +static int query_xinput2_version(Display *display, int major, int minor) +{ + // We don't care if this fails, so long as it sets major/minor on it's way out the door. + X11_XIQueryVersion(display, &major, &minor); + return (major * 1000) + minor; +} + +static bool xinput2_version_atleast(const int version, const int wantmajor, const int wantminor) +{ + return version >= ((wantmajor * 1000) + wantminor); +} + +static SDL_WindowData *xinput2_get_sdlwindowdata(SDL_VideoData *videodata, Window window) +{ + int i; + for (i = 0; i < videodata->numwindows; i++) { + SDL_WindowData *d = videodata->windowlist[i]; + if (d->xwindow == window) { + return d; + } + } + return NULL; +} + +static SDL_Window *xinput2_get_sdlwindow(SDL_VideoData *videodata, Window window) +{ + const SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, window); + return windowdata ? windowdata->window : NULL; +} + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH +static void xinput2_normalize_touch_coordinates(SDL_Window *window, double in_x, double in_y, float *out_x, float *out_y) +{ + if (window) { + if (window->w == 1) { + *out_x = 0.5f; + } else { + *out_x = (float)in_x / (window->w - 1); + } + if (window->h == 1) { + *out_y = 0.5f; + } else { + *out_y = (float)in_y / (window->h - 1); + } + } else { + // couldn't find the window... + *out_x = (float)in_x; + *out_y = (float)in_y; + } +} +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 + +bool X11_InitXinput2(SDL_VideoDevice *_this) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + SDL_VideoData *data = _this->internal; + + int version = 0; + XIEventMask eventmask; + unsigned char mask[4] = { 0, 0, 0, 0 }; + int event, err; + + /* XInput2 is required for relative mouse mode, so you probably want to leave this enabled */ + if (!SDL_GetHintBoolean("SDL_VIDEO_X11_XINPUT2", true)) { + return false; + } + + /* + * Initialize XInput 2 + * According to http://who-t.blogspot.com/2009/05/xi2-recipes-part-1.html its better + * to inform Xserver what version of Xinput we support.The server will store the version we support. + * "As XI2 progresses it becomes important that you use this call as the server may treat the client + * differently depending on the supported version". + * + * FIXME:event and err are not needed but if not passed X11_XQueryExtension returns SegmentationFault + */ + if (!SDL_X11_HAVE_XINPUT2 || + !X11_XQueryExtension(data->display, "XInputExtension", &xinput2_opcode, &event, &err)) { + return false; // X server does not have XInput at all + } + + // We need at least 2.2 for Multitouch, 2.0 otherwise. + version = query_xinput2_version(data->display, 2, 2); + if (!xinput2_version_atleast(version, 2, 0)) { + return false; // X server does not support the version we want at all. + } + + xinput2_initialized = true; + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH // Multitouch needs XInput 2.2 + xinput2_multitouch_supported = xinput2_version_atleast(version, 2, 2); +#endif + + // Enable raw motion events for this display + SDL_zero(eventmask); + SDL_zeroa(mask); + eventmask.deviceid = XIAllMasterDevices; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + + XISetMask(mask, XI_RawMotion); + XISetMask(mask, XI_RawButtonPress); + XISetMask(mask, XI_RawButtonRelease); + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + // Enable raw touch events if supported + if (X11_Xinput2IsMultitouchSupported()) { + XISetMask(mask, XI_RawTouchBegin); + XISetMask(mask, XI_RawTouchUpdate); + XISetMask(mask, XI_RawTouchEnd); + } +#endif + + X11_XISelectEvents(data->display, DefaultRootWindow(data->display), &eventmask, 1); + + SDL_zero(eventmask); + SDL_zeroa(mask); + eventmask.deviceid = XIAllDevices; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + + XISetMask(mask, XI_HierarchyChanged); + X11_XISelectEvents(data->display, DefaultRootWindow(data->display), &eventmask, 1); + + X11_Xinput2UpdateDevices(_this, true); + + return true; +#else + return false; +#endif +} + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 +// xi2 device went away? take it out of the list. +static void xinput2_remove_device_info(SDL_VideoData *videodata, const int device_id) +{ + SDL_XInput2DeviceInfo *prev = NULL; + SDL_XInput2DeviceInfo *devinfo; + + for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) { + if (devinfo->device_id == device_id) { + SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL)); + if (!prev) { + videodata->mouse_device_info = devinfo->next; + } else { + prev->next = devinfo->next; + } + SDL_free(devinfo); + return; + } + prev = devinfo; + } +} + +static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, const int device_id) +{ + // cache device info as we see new devices. + SDL_XInput2DeviceInfo *prev = NULL; + SDL_XInput2DeviceInfo *devinfo; + XIDeviceInfo *xidevinfo; + int axis = 0; + int i; + + for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) { + if (devinfo->device_id == device_id) { + SDL_assert((devinfo == videodata->mouse_device_info) == (prev == NULL)); + if (prev) { // move this to the front of the list, assuming we'll get more from this one. + prev->next = devinfo->next; + devinfo->next = videodata->mouse_device_info; + videodata->mouse_device_info = devinfo; + } + return devinfo; + } + prev = devinfo; + } + + // don't know about this device yet, query and cache it. + devinfo = (SDL_XInput2DeviceInfo *)SDL_calloc(1, sizeof(SDL_XInput2DeviceInfo)); + if (!devinfo) { + return NULL; + } + + xidevinfo = X11_XIQueryDevice(videodata->display, device_id, &i); + if (!xidevinfo) { + SDL_free(devinfo); + return NULL; + } + + devinfo->device_id = device_id; + + /* !!! FIXME: this is sort of hacky because we only care about the first two axes we see, but any given + !!! FIXME: axis could be relative or absolute, and they might not even be the X and Y axes! + !!! FIXME: But we go on, for now. Maybe we need a more robust mouse API in SDL3... */ + for (i = 0; i < xidevinfo->num_classes; i++) { + const XIValuatorClassInfo *v = (const XIValuatorClassInfo *)xidevinfo->classes[i]; + if (v->type == XIValuatorClass) { + devinfo->relative[axis] = (v->mode == XIModeRelative); + devinfo->minval[axis] = v->min; + devinfo->maxval[axis] = v->max; + if (++axis >= 2) { + break; + } + } + } + + X11_XIFreeDeviceInfo(xidevinfo); + + devinfo->next = videodata->mouse_device_info; + videodata->mouse_device_info = devinfo; + + return devinfo; +} +#endif + +void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + SDL_VideoData *videodata = _this->internal; + + if (cookie->extension != xinput2_opcode) { + return; + } + + switch (cookie->evtype) { + case XI_HierarchyChanged: + { + const XIHierarchyEvent *hierev = (const XIHierarchyEvent *)cookie->data; + int i; + for (i = 0; i < hierev->num_info; i++) { + // pen stuff... + if ((hierev->info[i].flags & (XISlaveRemoved | XIDeviceDisabled)) != 0) { + X11_RemovePenByDeviceID(hierev->info[i].deviceid); // it's okay if this thing isn't actually a pen, it'll handle it. + } else if ((hierev->info[i].flags & (XISlaveAdded | XIDeviceEnabled)) != 0) { + X11_MaybeAddPenByDeviceID(_this, hierev->info[i].deviceid); // this will do more checks to make sure this is valid. + } + + // not pen stuff... + if (hierev->info[i].flags & XISlaveRemoved) { + xinput2_remove_device_info(videodata, hierev->info[i].deviceid); + } + } + videodata->xinput_hierarchy_changed = true; + } break; + + // !!! 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? + //case XI_PropertyEvent: + //case XI_DeviceChanged: + + case XI_RawMotion: + { + const XIRawEvent *rawev = (const XIRawEvent *)cookie->data; + const bool is_pen = X11_FindPenByDeviceID(rawev->sourceid) != NULL; + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_XInput2DeviceInfo *devinfo; + double coords[2]; + double processed_coords[2]; + int i; + Uint64 timestamp = X11_GetEventTimestamp(rawev->time); + + videodata->global_mouse_changed = true; + if (is_pen) { + break; // Pens check for XI_Motion instead + } + + devinfo = xinput2_get_device_info(videodata, rawev->deviceid); + if (!devinfo) { + break; // oh well. + } + + parse_valuators(rawev->raw_values, rawev->valuators.mask, + rawev->valuators.mask_len, coords, 2); + + for (i = 0; i < 2; i++) { + if (devinfo->relative[i]) { + processed_coords[i] = coords[i]; + } else { + processed_coords[i] = devinfo->prev_coords[i] - coords[i]; // convert absolute to relative + } + } + + // Relative mouse motion is delivered to the window with keyboard focus + if (mouse->relative_mode && SDL_GetKeyboardFocus()) { + SDL_SendMouseMotion(timestamp, mouse->focus, (SDL_MouseID)rawev->sourceid, true, (float)processed_coords[0], (float)processed_coords[1]); + } + + devinfo->prev_coords[0] = coords[0]; + devinfo->prev_coords[1] = coords[1]; + } break; + + case XI_KeyPress: + case XI_KeyRelease: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + SDL_WindowData *windowdata = X11_FindWindow(_this, xev->event); + XEvent xevent; + + if (xev->deviceid != xev->sourceid) { + // Discard events from "Master" devices to avoid duplicates. + break; + } + + if (cookie->evtype == XI_KeyPress) { + xevent.type = KeyPress; + } else { + xevent.type = KeyRelease; + } + xevent.xkey.serial = xev->serial; + xevent.xkey.send_event = xev->send_event; + xevent.xkey.display = xev->display; + xevent.xkey.window = xev->event; + xevent.xkey.root = xev->root; + xevent.xkey.subwindow = xev->child; + xevent.xkey.time = xev->time; + xevent.xkey.x = (int)xev->event_x; + xevent.xkey.y = (int)xev->event_y; + xevent.xkey.x_root = (int)xev->root_x; + xevent.xkey.y_root = (int)xev->root_y; + xevent.xkey.state = xev->mods.effective; + xevent.xkey.keycode = xev->detail; + xevent.xkey.same_screen = 1; + + X11_HandleKeyEvent(_this, windowdata, (SDL_KeyboardID)xev->sourceid, &xevent); + } break; + + case XI_RawButtonPress: + case XI_RawButtonRelease: +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + case XI_RawTouchBegin: + case XI_RawTouchUpdate: + case XI_RawTouchEnd: +#endif + { + videodata->global_mouse_changed = true; + } break; + + case XI_ButtonPress: + case XI_ButtonRelease: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid); + const int button = xev->detail; + const bool down = (cookie->evtype == XI_ButtonPress); + + if (pen) { + // Only report button event; if there was also pen movement / pressure changes, we expect an XI_Motion event first anyway. + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + if (button == 1) { // button 1 is the pen tip + SDL_SendPenTouch(0, pen->pen, window, pen->is_eraser, down); + } else { + SDL_SendPenButton(0, pen->pen, window, button - 1, down); + } + } else { + // Otherwise assume a regular mouse + SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, xev->event); + + if (xev->deviceid != xev->sourceid) { + // Discard events from "Master" devices to avoid duplicates. + break; + } + + if (down) { + X11_HandleButtonPress(_this, windowdata, (SDL_MouseID)xev->sourceid, button, + (float)xev->event_x, (float)xev->event_y, xev->time); + } else { + X11_HandleButtonRelease(_this, windowdata, (SDL_MouseID)xev->sourceid, button, xev->time); + } + } + } break; + + /* Register to receive XI_Motion (which deactivates MotionNotify), so that we can distinguish + real mouse motions from synthetic ones, for multitouch and pen support. */ + case XI_Motion: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + bool pointer_emulated = ((xev->flags & XIPointerEmulated) != 0); +#else + bool pointer_emulated = false; +#endif + + videodata->global_mouse_changed = true; + + X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid); + if (pen) { + if (xev->deviceid != xev->sourceid) { + // Discard events from "Master" devices to avoid duplicates. + break; + } + + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + SDL_SendPenMotion(0, pen->pen, window, (float) xev->event_x, (float) xev->event_y); + + float axes[SDL_PEN_AXIS_COUNT]; + X11_PenAxesFromValuators(pen, xev->valuators.values, xev->valuators.mask, xev->valuators.mask_len, axes); + + for (int i = 0; i < SDL_arraysize(axes); i++) { + if (pen->valuator_for_axis[i] != SDL_X11_PEN_AXIS_VALUATOR_MISSING) { + SDL_SendPenAxis(0, pen->pen, window, (SDL_PenAxis) i, axes[i]); + } + } + } else if (!pointer_emulated && xev->deviceid == videodata->xinput_master_pointer_device) { + // Use the master device for non-relative motion, as the slave devices can seemingly lag behind. + SDL_Mouse *mouse = SDL_GetMouse(); + if (!mouse->relative_mode) { + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + if (window) { + X11_ProcessHitTest(_this, window->internal, (float)xev->event_x, (float)xev->event_y, false); + SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, (float)xev->event_x, (float)xev->event_y); + } + } + } + } break; + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + case XI_TouchBegin: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + float x, y; + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); + SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_EVENT_FINGER_DOWN, x, y, 1.0); + } break; + + case XI_TouchEnd: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + float x, y; + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); + SDL_SendTouch(0, xev->sourceid, xev->detail, window, SDL_EVENT_FINGER_UP, x, y, 1.0); + } break; + + case XI_TouchUpdate: + { + const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data; + float x, y; + SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event); + xinput2_normalize_touch_coordinates(window, xev->event_x, xev->event_y, &x, &y); + SDL_SendTouchMotion(0, xev->sourceid, xev->detail, window, x, y, 1.0); + } break; +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + } +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 +} + +void X11_InitXinput2Multitouch(SDL_VideoDevice *_this) +{ +} + +void X11_Xinput2SelectTouch(SDL_VideoDevice *_this, SDL_Window *window) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + SDL_VideoData *data = NULL; + XIEventMask eventmask; + unsigned char mask[4] = { 0, 0, 0, 0 }; + SDL_WindowData *window_data = NULL; + + if (!X11_Xinput2IsMultitouchSupported()) { + return; + } + + data = _this->internal; + window_data = window->internal; + + eventmask.deviceid = XIAllMasterDevices; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + + XISetMask(mask, XI_TouchBegin); + XISetMask(mask, XI_TouchUpdate); + XISetMask(mask, XI_TouchEnd); + XISetMask(mask, XI_Motion); + + X11_XISelectEvents(data->display, window_data->xwindow, &eventmask, 1); +#endif +} + +bool X11_Xinput2IsInitialized(void) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + return xinput2_initialized; +#else + return false; +#endif +} + +bool X11_Xinput2SelectMouseAndKeyboard(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *windowdata = window->internal; + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + const SDL_VideoData *data = _this->internal; + + if (X11_Xinput2IsInitialized()) { + XIEventMask eventmask; + unsigned char mask[4] = { 0, 0, 0, 0 }; + + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + eventmask.deviceid = XIAllDevices; + +// This is not enabled by default because these events are only delivered to the window with mouse focus, not keyboard focus +#ifdef USE_XINPUT2_KEYBOARD + XISetMask(mask, XI_KeyPress); + XISetMask(mask, XI_KeyRelease); + windowdata->xinput2_keyboard_enabled = true; +#endif + + XISetMask(mask, XI_ButtonPress); + XISetMask(mask, XI_ButtonRelease); + XISetMask(mask, XI_Motion); + windowdata->xinput2_mouse_enabled = true; + + XISetMask(mask, XI_Enter); + XISetMask(mask, XI_Leave); + + // Hotplugging: + XISetMask(mask, XI_DeviceChanged); + XISetMask(mask, XI_HierarchyChanged); + XISetMask(mask, XI_PropertyEvent); // E.g., when swapping tablet pens + + if (X11_XISelectEvents(data->display, windowdata->xwindow, &eventmask, 1) != Success) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Could not enable XInput2 event handling"); + windowdata->xinput2_keyboard_enabled = false; + windowdata->xinput2_mouse_enabled = false; + } + } +#endif + + if (windowdata->xinput2_keyboard_enabled || windowdata->xinput2_mouse_enabled) { + return true; + } + return false; +} + +bool X11_Xinput2IsMultitouchSupported(void) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + return xinput2_initialized && xinput2_multitouch_supported; +#else + return true; +#endif +} + +void X11_Xinput2GrabTouch(SDL_VideoDevice *_this, SDL_Window *window) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + + unsigned char mask[4] = { 0, 0, 0, 0 }; + XIGrabModifiers mods; + XIEventMask eventmask; + + if (!X11_Xinput2IsMultitouchSupported()) { + return; + } + + mods.modifiers = XIAnyModifier; + mods.status = 0; + + eventmask.deviceid = XIAllDevices; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + + XISetMask(eventmask.mask, XI_TouchBegin); + XISetMask(eventmask.mask, XI_TouchUpdate); + XISetMask(eventmask.mask, XI_TouchEnd); + XISetMask(eventmask.mask, XI_Motion); + + X11_XIGrabTouchBegin(display, XIAllDevices, data->xwindow, True, &eventmask, 1, &mods); +#endif +} + +void X11_Xinput2UngrabTouch(SDL_VideoDevice *_this, SDL_Window *window) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + SDL_WindowData *data = window->internal; + Display *display = data->videodata->display; + + XIGrabModifiers mods; + + if (!X11_Xinput2IsMultitouchSupported()) { + return; + } + + mods.modifiers = XIAnyModifier; + mods.status = 0; + + X11_XIUngrabTouchBegin(display, XIAllDevices, data->xwindow, 1, &mods); +#endif +} + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + +static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count) +{ + int new_count = (*count + 1); + Uint32 *new_list = (Uint32 *)SDL_realloc(*list, new_count * sizeof(*new_list)); + if (!new_list) { + // Oh well, we'll drop this one + return; + } + new_list[new_count - 1] = deviceID; + + *count = new_count; + *list = new_list; +} + +static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count) +{ + for (int i = 0; i < count; ++i) { + if (deviceID == list[i]) { + return true; + } + } + return false; +} + +static void AddDeviceID64(Uint64 deviceID, Uint64 **list, int *count) +{ + int new_count = (*count + 1); + Uint64 *new_list = (Uint64 *)SDL_realloc(*list, new_count * sizeof(*new_list)); + if (!new_list) { + // Oh well, we'll drop this one + return; + } + new_list[new_count - 1] = deviceID; + + *count = new_count; + *list = new_list; +} + +static bool HasDeviceID64(Uint64 deviceID, const Uint64 *list, int count) +{ + for (int i = 0; i < count; ++i) { + if (deviceID == list[i]) { + return true; + } + } + return false; +} + +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 + +void X11_Xinput2UpdateDevices(SDL_VideoDevice *_this, bool initial_check) +{ +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 + SDL_VideoData *data = _this->internal; + XIDeviceInfo *info; + int ndevices; + int old_keyboard_count = 0; + SDL_KeyboardID *old_keyboards = NULL; + int new_keyboard_count = 0; + SDL_KeyboardID *new_keyboards = NULL; + int old_mouse_count = 0; + SDL_MouseID *old_mice = NULL; + int new_mouse_count = 0; + SDL_MouseID *new_mice = NULL; + int old_touch_count = 0; + Uint64 *old_touch_devices = NULL; + int new_touch_count = 0; + Uint64 *new_touch_devices = NULL; + bool send_event = !initial_check; + + SDL_assert(X11_Xinput2IsInitialized()); + + info = X11_XIQueryDevice(data->display, XIAllDevices, &ndevices); + + old_keyboards = SDL_GetKeyboards(&old_keyboard_count); + old_mice = SDL_GetMice(&old_mouse_count); + old_touch_devices = SDL_GetTouchDevices(&old_touch_count); + + for (int i = 0; i < ndevices; i++) { + XIDeviceInfo *dev = &info[i]; + + switch (dev->use) { + case XIMasterKeyboard: + case XISlaveKeyboard: + { + SDL_KeyboardID keyboardID = (SDL_KeyboardID)dev->deviceid; + AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count); + if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) { + SDL_AddKeyboard(keyboardID, dev->name, send_event); + } + } + break; + case XIMasterPointer: + data->xinput_master_pointer_device = dev->deviceid; + SDL_FALLTHROUGH; + case XISlavePointer: + { + SDL_MouseID mouseID = (SDL_MouseID)dev->deviceid; + AddDeviceID(mouseID, &new_mice, &new_mouse_count); + if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) { + SDL_AddMouse(mouseID, dev->name, send_event); + } + } + break; + default: + break; + } + +#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + for (int j = 0; j < dev->num_classes; j++) { + Uint64 touchID; + SDL_TouchDeviceType touchType; + XIAnyClassInfo *class = dev->classes[j]; + XITouchClassInfo *t = (XITouchClassInfo *)class; + + // Only touch devices + if (class->type != XITouchClass) { + continue; + } + + touchID = (Uint64)t->sourceid; + AddDeviceID64(touchID, &new_touch_devices, &new_touch_count); + if (!HasDeviceID64(touchID, old_touch_devices, old_touch_count)) { + if (t->mode == XIDependentTouch) { + touchType = SDL_TOUCH_DEVICE_INDIRECT_RELATIVE; + } else { // XIDirectTouch + touchType = SDL_TOUCH_DEVICE_DIRECT; + } + SDL_AddTouch(touchID, touchType, dev->name); + } + } +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH + } + + for (int i = old_keyboard_count; i--;) { + if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) { + SDL_RemoveKeyboard(old_keyboards[i], send_event); + } + } + + for (int i = old_mouse_count; i--;) { + if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) { + SDL_RemoveMouse(old_mice[i], send_event); + } + } + + for (int i = old_touch_count; i--;) { + if (!HasDeviceID64(old_touch_devices[i], new_touch_devices, new_touch_count)) { + SDL_DelTouch(old_touch_devices[i]); + } + } + + SDL_free(old_keyboards); + SDL_free(new_keyboards); + SDL_free(old_mice); + SDL_free(new_mice); + SDL_free(old_touch_devices); + SDL_free(new_touch_devices); + + X11_XIFreeDeviceInfo(info); + +#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 +} + +#endif // SDL_VIDEO_DRIVER_X11 -- cgit v1.2.3