1035 lines
35 KiB
C
1035 lines
35 KiB
C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
|
|
|
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;
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO
|
|
typedef struct
|
|
{
|
|
int number;
|
|
int scroll_type;
|
|
double prev_value;
|
|
double increment;
|
|
bool prev_value_valid;
|
|
} SDL_XInput2ScrollInfo;
|
|
|
|
typedef struct
|
|
{
|
|
int device_id;
|
|
int scroll_info_count;
|
|
SDL_XInput2ScrollInfo *scroll_info;
|
|
} SDL_XInput2ScrollableDevice;
|
|
|
|
static SDL_XInput2ScrollableDevice *scrollable_devices;
|
|
static int scrollable_device_count;
|
|
static bool xinput2_scrolling_supported;
|
|
#endif
|
|
|
|
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_SCROLLINFO
|
|
static void xinput2_reset_scrollable_valuators(SDL_VideoData *videodata)
|
|
{
|
|
for (int i = 0; i < scrollable_device_count; ++i) {
|
|
for (int j = 0; j < scrollable_devices[i].scroll_info_count; ++j) {
|
|
scrollable_devices[i].scroll_info[j].prev_value_valid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void xinput2_parse_scrollable_valuators(const XIDeviceEvent *xev)
|
|
{
|
|
for (int i = 0; i < scrollable_device_count; ++i) {
|
|
const SDL_XInput2ScrollableDevice *sd = &scrollable_devices[i];
|
|
if (xev->sourceid == sd->device_id) {
|
|
int values_i = 0;
|
|
for (int j = 0; j < xev->valuators.mask_len * 8; ++j) {
|
|
if (!XIMaskIsSet(xev->valuators.mask, j)) {
|
|
continue;
|
|
}
|
|
|
|
for (int k = 0; k < sd->scroll_info_count; ++k) {
|
|
SDL_XInput2ScrollInfo *info = &sd->scroll_info[k];
|
|
if (info->number == j) {
|
|
const double current_val = xev->valuators.values[values_i];
|
|
const double delta = (info->prev_value - current_val) / info->increment;
|
|
/* Ignore very large jumps that can happen as a result of overflowing
|
|
* the maximum range, as the driver will reset the position to zero
|
|
* at "something that's close to 2^32".
|
|
*
|
|
* The first scroll event is meaningless by itself and must be discarded,
|
|
* as it is only useful for establishing a baseline for future deltas.
|
|
* This is a known deficiency of the XInput2 scroll protocol, and,
|
|
* unfortunately, there is nothing we can do about it.
|
|
*
|
|
* http://who-t.blogspot.com/2012/06/xi-21-protocol-design-issues.html
|
|
*/
|
|
if (info->prev_value_valid && SDL_fabs(delta) < (double)SDL_MAX_SINT32 * 0.95) {
|
|
const double x = info->scroll_type == XIScrollTypeHorizontal ? delta : 0;
|
|
const double y = info->scroll_type == XIScrollTypeVertical ? delta : 0;
|
|
|
|
SDL_Mouse *mouse = SDL_GetMouse();
|
|
SDL_SendMouseWheel(xev->time, mouse->focus, (SDL_MouseID)xev->sourceid, (float)x, (float)y, SDL_MOUSEWHEEL_NORMAL);
|
|
}
|
|
info->prev_value = current_val;
|
|
info->prev_value_valid = true;
|
|
}
|
|
}
|
|
|
|
++values_i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO
|
|
|
|
#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
|
|
|
|
static bool X11_Xinput2IsMultitouchSupported(void)
|
|
{
|
|
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
|
|
return xinput2_initialized && xinput2_multitouch_supported;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static bool X11_Xinput2IsScrollingSupported(void)
|
|
{
|
|
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO
|
|
return xinput2_initialized && xinput2_scrolling_supported;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
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[5] = { 0, 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_SCROLLINFO // Smooth scrolling needs XInput 2.1
|
|
xinput2_scrolling_supported = xinput2_version_atleast(version, 2, 1);
|
|
#endif
|
|
|
|
#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_SCROLLINFO
|
|
if (X11_Xinput2IsScrollingSupported()) {
|
|
XISetMask(mask, XI_Motion);
|
|
}
|
|
#endif
|
|
|
|
#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
|
|
}
|
|
|
|
void X11_QuitXinput2(SDL_VideoDevice *_this)
|
|
{
|
|
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO
|
|
for (int i = 0; i < scrollable_device_count; ++i) {
|
|
SDL_free(scrollable_devices[i].scroll_info);
|
|
}
|
|
SDL_free(scrollable_devices);
|
|
scrollable_devices = NULL;
|
|
scrollable_device_count = 0;
|
|
#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->sourceid);
|
|
const int button = xev->detail;
|
|
const bool down = (cookie->evtype == XI_ButtonPress);
|
|
#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH)
|
|
bool pointer_emulated = (xev->flags & XIPointerEmulated) != 0;
|
|
#else
|
|
bool pointer_emulated = false;
|
|
#endif
|
|
|
|
if (pen) {
|
|
if (xev->deviceid != xev->sourceid) {
|
|
// Discard events from "Master" devices to avoid duplicates.
|
|
break;
|
|
}
|
|
// 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);
|
|
int x_ticks = 0, y_ticks = 0;
|
|
|
|
/* Discard wheel events from "Master" devices to avoid duplicates,
|
|
* as coarse wheel events are stateless and can't be deduplicated.
|
|
*
|
|
* If the pointer emulation flag is set on a wheel event, it is being
|
|
* emulated from a scroll valuator, which will be handled natively.
|
|
*/
|
|
if ((pointer_emulated || xev->deviceid != xev->sourceid) &&
|
|
X11_IsWheelEvent(button, &x_ticks, &y_ticks)) {
|
|
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;
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO
|
|
case XI_Enter:
|
|
xinput2_reset_scrollable_valuators(videodata);
|
|
break;
|
|
#endif
|
|
|
|
/* 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;
|
|
#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(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->sourceid);
|
|
|
|
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) {
|
|
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO
|
|
if (xev->deviceid == xev->sourceid) {
|
|
xinput2_parse_scrollable_valuators(xev);
|
|
}
|
|
#endif
|
|
|
|
if (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_Xinput2Select(SDL_VideoDevice *_this, SDL_Window *window)
|
|
{
|
|
#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH)
|
|
SDL_VideoData *data = _this->internal;
|
|
SDL_WindowData *window_data = window->internal;
|
|
XIEventMask eventmask;
|
|
unsigned char mask[5] = { 0, 0, 0, 0, 0 };
|
|
|
|
if (!X11_Xinput2IsScrollingSupported() && !X11_Xinput2IsMultitouchSupported()) {
|
|
return;
|
|
}
|
|
|
|
eventmask.deviceid = XIAllMasterDevices;
|
|
eventmask.mask_len = sizeof(mask);
|
|
eventmask.mask = mask;
|
|
|
|
if (X11_Xinput2IsScrollingSupported()) {
|
|
/* Track enter events that inform us that we need to update
|
|
* the previous scroll coordinates since we cannot track
|
|
* them outside our window.
|
|
*/
|
|
XISetMask(mask, XI_Enter);
|
|
}
|
|
|
|
if (X11_Xinput2IsMultitouchSupported()) {
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO
|
|
// Scroll devices don't get add/remove events, so just rebuild the list.
|
|
for (int i = 0; i < scrollable_device_count; ++i) {
|
|
SDL_free(scrollable_devices[i].scroll_info);
|
|
}
|
|
SDL_free(scrollable_devices);
|
|
scrollable_devices = NULL;
|
|
scrollable_device_count = 0;
|
|
#endif
|
|
|
|
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_SCROLLINFO
|
|
SDL_XInput2ScrollableDevice *sd = NULL;
|
|
int allocated_scroll_info_count = 0;
|
|
|
|
for (int j = 0; j < dev->num_classes; j++) {
|
|
const XIAnyClassInfo *class = dev->classes[j];
|
|
const XIScrollClassInfo *s = (XIScrollClassInfo *)class;
|
|
|
|
if (class->type != XIScrollClass) {
|
|
continue;
|
|
}
|
|
|
|
// Allocate a new scrollable device.
|
|
if (!sd) {
|
|
scrollable_devices = SDL_realloc(scrollable_devices, (scrollable_device_count + 1) * sizeof(SDL_XInput2ScrollableDevice));
|
|
if (!scrollable_devices) {
|
|
// No memory; so just skip this.
|
|
break;
|
|
}
|
|
|
|
sd = &scrollable_devices[scrollable_device_count];
|
|
++scrollable_device_count;
|
|
|
|
SDL_zerop(sd);
|
|
sd->device_id = dev->deviceid;
|
|
}
|
|
|
|
// Allocate new scroll info entries two at a time, as they typically come in a horizontal/vertical pair.
|
|
if (sd->scroll_info_count == allocated_scroll_info_count) {
|
|
sd->scroll_info = SDL_realloc(sd->scroll_info, (allocated_scroll_info_count + 2) * sizeof(SDL_XInput2ScrollInfo));
|
|
if (!sd->scroll_info) {
|
|
// No memory; just skip this.
|
|
break;
|
|
}
|
|
|
|
allocated_scroll_info_count += 2;
|
|
}
|
|
|
|
SDL_XInput2ScrollInfo *scroll_info = &sd->scroll_info[sd->scroll_info_count];
|
|
++sd->scroll_info_count;
|
|
|
|
SDL_zerop(scroll_info);
|
|
scroll_info->number = s->number;
|
|
scroll_info->scroll_type = s->scroll_type;
|
|
scroll_info->increment = s->increment;
|
|
}
|
|
#endif
|
|
|
|
#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
|