#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;
static bool xinput2_grabbed_touch_raised;
static int xinput2_active_touch_count;
#if defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO) || defined(SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH)
static bool xinput2_scrolling_supported;
static bool xinput2_multitouch_supported;
#endif
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE
static bool xinput2_gesture_supported;
#endif
static int xinput2_opcode;
static Atom xinput2_rel_x_atom;
static Atom xinput2_rel_y_atom;
static Atom xinput2_abs_x_atom;
static Atom xinput2_abs_y_atom;
static unsigned char *xinput2_pointer_button_map;
static int xinput2_pointer_button_map_size;
#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;
#endif
static void parse_relative_valuators(SDL_XInput2DeviceInfo *devinfo, const XIRawEvent *rawev)
{
SDL_Mouse *mouse = SDL_GetMouse();
double processed_coords[2] = { 0.0, 0.0 };
int values_i = 0, found = 0;
const bool use_raw_vals = mouse->InputTransform || !mouse->enable_relative_system_scale;
for (int i = 0; i < rawev->valuators.mask_len * 8 && found < 2; ++i) {
if (!XIMaskIsSet(rawev->valuators.mask, i)) {
continue;
}
for (int j = 0; j < 2; ++j) {
if (devinfo->number[j] == i) {
const double current_val = use_raw_vals ? rawev->raw_values[values_i] : rawev->valuators.values[values_i];
if (devinfo->relative[j]) {
processed_coords[j] = current_val;
} else {
processed_coords[j] = (current_val - devinfo->prev_coords[j]); devinfo->prev_coords[j] = current_val;
}
++found;
break;
}
}
++values_i;
}
if (mouse->relative_mode && SDL_GetKeyboardFocus()) {
SDL_SendMouseMotion(rawev->time, mouse->focus, (SDL_MouseID)rawev->sourceid, true, (float)processed_coords[0], (float)processed_coords[1]);
}
}
static int query_xinput2_version(Display *display, int major, int minor)
{
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_Window *xinput2_get_sdlwindow(SDL_VideoData *videodata, Window window)
{
const SDL_WindowData *windowdata = X11_FindWindow(videodata, window);
return windowdata ? windowdata->window : NULL;
}
#endif
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO
static void xinput2_reset_scrollable_valuators()
{
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;
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
#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 {
*out_x = (float)in_x;
*out_y = (float)in_y;
}
}
#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;
if (!SDL_GetHintBoolean("SDL_VIDEO_X11_XINPUT2", true)) {
return false;
}
if (!SDL_X11_HAVE_XINPUT2 ||
!X11_XQueryExtension(data->display, "XInputExtension", &xinput2_opcode, &event, &err)) {
return false; }
version = query_xinput2_version(data->display, 2, 4);
if (!xinput2_version_atleast(version, 2, 0)) {
return false; }
xinput2_initialized = true;
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO
xinput2_scrolling_supported = xinput2_version_atleast(version, 2, 1);
#endif
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
xinput2_multitouch_supported = xinput2_version_atleast(version, 2, 2);
#endif
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE
xinput2_gesture_supported = xinput2_version_atleast(version, 2, 4);
#endif
xinput2_rel_x_atom = X11_XInternAtom(data->display, "Rel X", False);
xinput2_rel_y_atom = X11_XInternAtom(data->display, "Rel Y", False);
xinput2_abs_x_atom = X11_XInternAtom(data->display, "Abs X", False);
xinput2_abs_y_atom = X11_XInternAtom(data->display, "Abs Y", False);
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 (xinput2_scrolling_supported) {
XISetMask(mask, XI_Motion);
}
#endif
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
if (xinput2_multitouch_supported) {
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);
X11_Xinput2UpdatePointerMapping(_this);
return true;
#else
return false;
#endif
}
void X11_QuitXinput2(SDL_VideoDevice *_this)
{
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
SDL_free(xinput2_pointer_button_map);
xinput2_pointer_button_map = NULL;
xinput2_pointer_button_map_size = 0;
#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
#endif
}
void X11_Xinput2UpdatePointerMapping(SDL_VideoDevice *_this)
{
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
if (X11_Xinput2IsInitialized()) {
SDL_VideoData *vid = _this->internal;
SDL_free(xinput2_pointer_button_map);
xinput2_pointer_button_map = NULL;
xinput2_pointer_button_map_size = 0;
xinput2_pointer_button_map_size = X11_XGetPointerMapping(vid->display, NULL, 0);
if (xinput2_pointer_button_map_size) {
xinput2_pointer_button_map = SDL_calloc(xinput2_pointer_button_map_size, sizeof(unsigned char));
if (xinput2_pointer_button_map) {
xinput2_pointer_button_map_size = X11_XGetPointerMapping(vid->display, xinput2_pointer_button_map, xinput2_pointer_button_map_size);
} else {
xinput2_pointer_button_map_size = 0;
}
}
}
#endif
}
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
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)
{
SDL_XInput2DeviceInfo *prev = NULL;
SDL_XInput2DeviceInfo *devinfo;
XIDeviceInfo *xidevinfo;
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) { prev->next = devinfo->next;
devinfo->next = videodata->mouse_device_info;
videodata->mouse_device_info = devinfo;
}
return devinfo;
}
prev = devinfo;
}
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;
bool have_rel_x = false, have_rel_y = false;
bool have_abs_x = false, have_abs_y = false;
int axis_index = 0;
for (i = 0; i < xidevinfo->num_classes; i++) {
const XIValuatorClassInfo *v = (const XIValuatorClassInfo *)xidevinfo->classes[i];
if (v->type == XIValuatorClass) {
if (v->label == xinput2_rel_x_atom || (v->label == xinput2_abs_x_atom && !have_rel_x) ||
(axis_index == 0 && !have_rel_x && !have_abs_x)) {
devinfo->number[0] = v->number;
devinfo->relative[0] = (v->mode == XIModeRelative);
devinfo->minval[0] = v->min;
devinfo->maxval[0] = v->max;
if (v->label == xinput2_rel_x_atom) {
have_rel_x = true;
} else if (v->label == xinput2_abs_x_atom) {
have_abs_x = true;
}
} else if (v->label == xinput2_rel_y_atom || (v->label == xinput2_abs_y_atom && !have_rel_y) ||
(axis_index == 1 && !have_rel_y && !have_abs_y)) {
devinfo->number[1] = v->number;
devinfo->relative[1] = (v->mode == XIModeRelative);
devinfo->minval[1] = v->min;
devinfo->maxval[1] = v->max;
if (v->label == xinput2_rel_y_atom) {
have_rel_y = true;
} else if (v->label == xinput2_abs_y_atom) {
have_abs_y = true;
}
}
if (have_rel_x && have_rel_y) {
break;
}
++axis_index;
}
}
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++) {
if ((hierev->info[i].flags & (XISlaveRemoved | XIDeviceDisabled)) != 0) {
X11_RemovePenByDeviceID(hierev->info[i].deviceid); } else if ((hierev->info[i].flags & (XISlaveAdded | XIDeviceEnabled)) != 0) {
X11_MaybeAddPenByDeviceID(_this, hierev->info[i].deviceid); }
if (hierev->info[i].flags & XISlaveRemoved) {
xinput2_remove_device_info(videodata, hierev->info[i].deviceid);
}
}
videodata->xinput_hierarchy_changed = true;
} break;
case XI_PropertyEvent:
{
const XIPropertyEvent *proev = (const XIPropertyEvent *)cookie->data;
if (proev->what == XIPropertyModified && proev->property == videodata->atoms.pen_atom_wacom_serial_ids) {
const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
SDL_WindowData *windowdata = X11_FindWindow(videodata, xev->event);
X11_NotifyPenProximityChange(_this, windowdata ? windowdata->window : NULL, proev->deviceid);
}
} break;
case XI_RawMotion:
{
const XIRawEvent *rawev = (const XIRawEvent *)cookie->data;
const bool is_pen = X11_FindPenByDeviceID(rawev->sourceid) != NULL;
videodata->global_mouse_changed = true;
if (is_pen) {
break; }
SDL_XInput2DeviceInfo *devinfo = xinput2_get_device_info(videodata, rawev->deviceid);
if (!devinfo) {
break; }
parse_relative_valuators(devinfo, rawev);
} break;
case XI_KeyPress:
case XI_KeyRelease:
{
const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
SDL_WindowData *windowdata = X11_FindWindow(videodata, xev->event);
XEvent xevent;
if (xev->deviceid != xev->sourceid) {
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);
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) {
break;
}
SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
if (button == 1) { SDL_SendPenTouch(0, pen->pen, window, pen->is_eraser, down);
} else {
SDL_SendPenButton(0, pen->pen, window, button - 1, down);
}
} else if (!pointer_emulated) {
SDL_WindowData *windowdata = X11_FindWindow(videodata, xev->event);
int x_ticks = 0, y_ticks = 0;
if (xev->deviceid != videodata->xinput_master_pointer_device) {
if (button <= xinput2_pointer_button_map_size) {
button = xinput2_pointer_button_map[button - 1];
}
}
if (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();
break;
#endif
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) {
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
SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
if (window && (xev->deviceid == videodata->xinput_master_pointer_device || (xinput2_active_touch_count && window->internal->mouse_grabbed))) {
SDL_Mouse *mouse = SDL_GetMouse();
if (!mouse->relative_mode) {
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;
++xinput2_active_touch_count;
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);
if (!--xinput2_active_touch_count && window && window->internal->mouse_grabbed) {
xinput2_grabbed_touch_raised = true;
}
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
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE
case XI_GesturePinchBegin:
case XI_GesturePinchUpdate:
case XI_GesturePinchEnd:
{
const XIGesturePinchEvent *xev = (const XIGesturePinchEvent *)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);
if (cookie->evtype == XI_GesturePinchBegin) {
SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, 0, window, 0);
} else if (cookie->evtype == XI_GesturePinchUpdate) {
SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, 0, window, (float)xev->scale);
} else {
SDL_SendPinch(SDL_EVENT_PINCH_END, 0, window, 0);
}
} break;
#endif
}
#endif }
void X11_InitXinput2Multitouch(SDL_VideoDevice *_this)
{
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
xinput2_grabbed_touch_raised = false;
xinput2_active_touch_count = 0;
#endif
}
bool X11_Xinput2HandlesMotionForWindow(SDL_WindowData *window_data)
{
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
const bool ret = window_data->xinput2_mouse_enabled &&
(!window_data->mouse_grabbed || xinput2_active_touch_count || xinput2_grabbed_touch_raised);
xinput2_grabbed_touch_raised = false;
return ret;
#else
return false;
#endif }
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 (!xinput2_scrolling_supported && !xinput2_multitouch_supported) {
return;
}
eventmask.deviceid = XIAllMasterDevices;
eventmask.mask_len = sizeof(mask);
eventmask.mask = mask;
if (xinput2_scrolling_supported) {
XISetMask(mask, XI_Enter);
}
if (xinput2_multitouch_supported) {
XISetMask(mask, XI_TouchBegin);
XISetMask(mask, XI_TouchUpdate);
XISetMask(mask, XI_TouchEnd);
XISetMask(mask, XI_Motion);
}
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_GESTURE
if (xinput2_gesture_supported) {
XISetMask(mask, XI_GesturePinchBegin);
XISetMask(mask, XI_GesturePinchUpdate);
XISetMask(mask, XI_GesturePinchEnd);
}
#endif
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;
#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);
XISetMask(mask, XI_DeviceChanged);
XISetMask(mask, XI_HierarchyChanged);
XISetMask(mask, XI_PropertyEvent);
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 (!xinput2_multitouch_supported) {
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 (!xinput2_multitouch_supported) {
return;
}
xinput2_grabbed_touch_raised = false;
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) {
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;
}
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
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) {
return;
}
new_list[new_count - 1] = deviceID;
*count = new_count;
*list = new_list;
}
#endif
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
void X11_Xinput2UpdateDevices(SDL_VideoDevice *_this)
{
#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;
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
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);
}
}
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);
}
}
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;
}
if (!sd) {
scrollable_devices = SDL_realloc(scrollable_devices, (scrollable_device_count + 1) * sizeof(SDL_XInput2ScrollableDevice));
if (!scrollable_devices) {
break;
}
sd = &scrollable_devices[scrollable_device_count];
++scrollable_device_count;
SDL_zerop(sd);
sd->device_id = dev->deviceid;
}
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) {
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;
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 { touchType = SDL_TOUCH_DEVICE_DIRECT;
}
SDL_AddTouch(touchID, touchType, dev->name);
}
}
#endif }
for (int i = old_keyboard_count; i--;) {
if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) {
SDL_RemoveKeyboard(old_keyboards[i]);
}
}
for (int i = old_mouse_count; i--;) {
if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) {
SDL_RemoveMouse(old_mice[i]);
}
}
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 }
#endif