summaryrefslogtreecommitdiffstats
path: root/client/X11/xf_input.c
diff options
context:
space:
mode:
Diffstat (limited to 'client/X11/xf_input.c')
-rw-r--r--client/X11/xf_input.c946
1 files changed, 946 insertions, 0 deletions
diff --git a/client/X11/xf_input.c b/client/X11/xf_input.c
new file mode 100644
index 0000000..f1cdc83
--- /dev/null
+++ b/client/X11/xf_input.c
@@ -0,0 +1,946 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Input
+ *
+ * Copyright 2013 Corey Clayton <can.of.tuna@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#ifdef WITH_XCURSOR
+#include <X11/Xcursor/Xcursor.h>
+#endif
+
+#ifdef WITH_XI
+#include <X11/extensions/XInput2.h>
+#endif
+
+#include <math.h>
+#include <float.h>
+#include <limits.h>
+
+#include "xf_event.h"
+#include "xf_input.h"
+
+#include <winpr/assert.h>
+#include <winpr/wtypes.h>
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("x11")
+
+#ifdef WITH_XI
+
+#define PAN_THRESHOLD 50
+#define ZOOM_THRESHOLD 10
+
+#define MIN_FINGER_DIST 5
+
+static int xf_input_event(xfContext* xfc, const XEvent* xevent, XIDeviceEvent* event, int evtype);
+
+#ifdef DEBUG_XINPUT
+static const char* xf_input_get_class_string(int class)
+{
+ if (class == XIKeyClass)
+ return "XIKeyClass";
+ else if (class == XIButtonClass)
+ return "XIButtonClass";
+ else if (class == XIValuatorClass)
+ return "XIValuatorClass";
+ else if (class == XIScrollClass)
+ return "XIScrollClass";
+ else if (class == XITouchClass)
+ return "XITouchClass";
+
+ return "XIUnknownClass";
+}
+#endif
+
+static BOOL register_input_events(xfContext* xfc, Window window)
+{
+#define MAX_NR_MASKS 64
+ int ndevices = 0;
+ int nmasks = 0;
+ XIEventMask evmasks[MAX_NR_MASKS] = { 0 };
+ BYTE masks[MAX_NR_MASKS][XIMaskLen(XI_LASTEVENT)] = { 0 };
+
+ WINPR_ASSERT(xfc);
+
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ XIDeviceInfo* info = XIQueryDevice(xfc->display, XIAllDevices, &ndevices);
+
+ for (int i = 0; i < MIN(ndevices, MAX_NR_MASKS); i++)
+ {
+ BOOL used = FALSE;
+ XIDeviceInfo* dev = &info[i];
+
+ evmasks[nmasks].mask = masks[nmasks];
+ evmasks[nmasks].mask_len = sizeof(masks[0]);
+ evmasks[nmasks].deviceid = dev->deviceid;
+
+ /* Ignore virtual core pointer */
+ if (strcmp(dev->name, "Virtual core pointer") == 0)
+ continue;
+
+ for (int j = 0; j < dev->num_classes; j++)
+ {
+ const XIAnyClassInfo* class = dev->classes[j];
+
+ switch (class->type)
+ {
+ case XITouchClass:
+ if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchInput))
+ {
+ const XITouchClassInfo* t = (const XITouchClassInfo*)class;
+ if (t->mode == XIDirectTouch)
+ {
+ WLog_DBG(
+ TAG,
+ "%s %s touch device (id: %d, mode: %d), supporting %d touches.",
+ dev->name, (t->mode == XIDirectTouch) ? "direct" : "dependent",
+ dev->deviceid, t->mode, t->num_touches);
+ XISetMask(masks[nmasks], XI_TouchBegin);
+ XISetMask(masks[nmasks], XI_TouchUpdate);
+ XISetMask(masks[nmasks], XI_TouchEnd);
+ }
+ }
+ break;
+ case XIButtonClass:
+ {
+ const XIButtonClassInfo* t = (const XIButtonClassInfo*)class;
+ WLog_DBG(TAG, "%s button device (id: %d, mode: %d)", dev->name, dev->deviceid,
+ t->num_buttons);
+ XISetMask(masks[nmasks], XI_ButtonPress);
+ XISetMask(masks[nmasks], XI_ButtonRelease);
+ XISetMask(masks[nmasks], XI_Motion);
+ used = TRUE;
+ break;
+ }
+ case XIValuatorClass:
+ {
+ const XIValuatorClassInfo* t = (const XIValuatorClassInfo*)class;
+ char* name = t->label ? XGetAtomName(xfc->display, t->label) : NULL;
+
+ WLog_DBG(TAG, "%s device (id: %d) valuator %d label %s range %f - %f",
+ dev->name, dev->deviceid, t->number, name ? name : "None", t->min,
+ t->max);
+ free(name);
+
+ if (t->number == 2)
+ {
+ double max_pressure = t->max;
+
+ char devName[200] = { 0 };
+ strncpy(devName, dev->name, ARRAYSIZE(devName) - 1);
+ CharLowerBuffA(devName, ARRAYSIZE(devName));
+
+ if (strstr(devName, "eraser") != NULL)
+ {
+ if (freerdp_client_handle_pen(&xfc->common,
+ FREERDP_PEN_REGISTER |
+ FREERDP_PEN_IS_INVERTED |
+ FREERDP_PEN_HAS_PRESSURE,
+ dev->deviceid, max_pressure))
+ WLog_DBG(TAG, "registered eraser");
+ }
+ else if (strstr(devName, "stylus") != NULL ||
+ strstr(devName, "pen") != NULL)
+ {
+ if (freerdp_client_handle_pen(
+ &xfc->common, FREERDP_PEN_REGISTER | FREERDP_PEN_HAS_PRESSURE,
+ dev->deviceid, max_pressure))
+ WLog_DBG(TAG, "registered pen");
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ if (used)
+ nmasks++;
+ }
+
+ XIFreeDeviceInfo(info);
+
+ if (nmasks > 0)
+ {
+ Status xstatus = XISelectEvents(xfc->display, window, evmasks, nmasks);
+ if (xstatus != 0)
+ WLog_WARN(TAG, "XISelectEvents returned %d", xstatus);
+ }
+
+ return TRUE;
+}
+
+static BOOL register_raw_events(xfContext* xfc, Window window)
+{
+ XIEventMask mask;
+ unsigned char mask_bytes[XIMaskLen(XI_LASTEVENT)] = { 0 };
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (freerdp_client_use_relative_mouse_events(&xfc->common))
+ {
+ XISetMask(mask_bytes, XI_RawMotion);
+ XISetMask(mask_bytes, XI_RawButtonPress);
+ XISetMask(mask_bytes, XI_RawButtonRelease);
+
+ mask.deviceid = XIAllMasterDevices;
+ mask.mask_len = sizeof(mask_bytes);
+ mask.mask = mask_bytes;
+
+ XISelectEvents(xfc->display, window, &mask, 1);
+ }
+
+ return TRUE;
+}
+
+static BOOL register_device_events(xfContext* xfc, Window window)
+{
+ XIEventMask mask;
+ unsigned char mask_bytes[XIMaskLen(XI_LASTEVENT)] = { 0 };
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ XISetMask(mask_bytes, XI_DeviceChanged);
+ XISetMask(mask_bytes, XI_HierarchyChanged);
+
+ mask.deviceid = XIAllDevices;
+ mask.mask_len = sizeof(mask_bytes);
+ mask.mask = mask_bytes;
+
+ XISelectEvents(xfc->display, window, &mask, 1);
+
+ return TRUE;
+}
+
+int xf_input_init(xfContext* xfc, Window window)
+{
+ int major = XI_2_Major;
+ int minor = XI_2_Minor;
+ int opcode = 0;
+ int event = 0;
+ int error = 0;
+
+ WINPR_ASSERT(xfc);
+
+ rdpSettings* settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xfc->firstDist = -1.0;
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ xfc->active_contacts = 0;
+
+ if (!XQueryExtension(xfc->display, "XInputExtension", &opcode, &event, &error))
+ {
+ WLog_WARN(TAG, "XInput extension not available.");
+ return -1;
+ }
+
+ xfc->XInputOpcode = opcode;
+ XIQueryVersion(xfc->display, &major, &minor);
+
+ if ((major < XI_2_Major) || ((major == XI_2_Major) && (minor < 2)))
+ {
+ WLog_WARN(TAG, "Server does not support XI 2.2");
+ return -1;
+ }
+ else
+ {
+ int scr = DefaultScreen(xfc->display);
+ Window root = RootWindow(xfc->display, scr);
+
+ if (!register_raw_events(xfc, root))
+ return -1;
+ if (!register_input_events(xfc, window))
+ return -1;
+ if (!register_device_events(xfc, window))
+ return -1;
+ }
+
+ return 0;
+}
+
+static BOOL xf_input_is_duplicate(xfContext* xfc, const XGenericEventCookie* cookie)
+{
+ const XIDeviceEvent* event = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(cookie);
+
+ event = cookie->data;
+ WINPR_ASSERT(event);
+
+ if ((xfc->lastEvent.time == event->time) && (xfc->lastEvType == cookie->evtype) &&
+ (xfc->lastEvent.detail == event->detail) &&
+ (fabs(xfc->lastEvent.event_x - event->event_x) < DBL_EPSILON) &&
+ (fabs(xfc->lastEvent.event_y - event->event_y) < DBL_EPSILON))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void xf_input_save_last_event(xfContext* xfc, const XGenericEventCookie* cookie)
+{
+ const XIDeviceEvent* event = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(cookie);
+
+ event = cookie->data;
+ WINPR_ASSERT(event);
+
+ xfc->lastEvType = cookie->evtype;
+ xfc->lastEvent.time = event->time;
+ xfc->lastEvent.detail = event->detail;
+ xfc->lastEvent.event_x = event->event_x;
+ xfc->lastEvent.event_y = event->event_y;
+}
+
+static void xf_input_detect_pan(xfContext* xfc)
+{
+ double dx[2];
+ double dy[2];
+ double px = NAN;
+ double py = NAN;
+ double dist_x = NAN;
+ double dist_y = NAN;
+ rdpContext* ctx = NULL;
+
+ WINPR_ASSERT(xfc);
+ ctx = &xfc->common.context;
+ WINPR_ASSERT(ctx);
+
+ if (xfc->active_contacts != 2)
+ {
+ return;
+ }
+
+ dx[0] = xfc->contacts[0].pos_x - xfc->contacts[0].last_x;
+ dx[1] = xfc->contacts[1].pos_x - xfc->contacts[1].last_x;
+ dy[0] = xfc->contacts[0].pos_y - xfc->contacts[0].last_y;
+ dy[1] = xfc->contacts[1].pos_y - xfc->contacts[1].last_y;
+ px = fabs(dx[0]) < fabs(dx[1]) ? dx[0] : dx[1];
+ py = fabs(dy[0]) < fabs(dy[1]) ? dy[0] : dy[1];
+ xfc->px_vector += px;
+ xfc->py_vector += py;
+ dist_x = fabs(xfc->contacts[0].pos_x - xfc->contacts[1].pos_x);
+ dist_y = fabs(xfc->contacts[0].pos_y - xfc->contacts[1].pos_y);
+
+ if (dist_y > MIN_FINGER_DIST)
+ {
+ if (xfc->px_vector > PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = 5;
+ e.dy = 0;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ xfc->z_vector = 0;
+ }
+ else if (xfc->px_vector < -PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = -5;
+ e.dy = 0;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ xfc->z_vector = 0;
+ }
+ }
+
+ if (dist_x > MIN_FINGER_DIST)
+ {
+ if (xfc->py_vector > PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = 0;
+ e.dy = 5;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->py_vector = 0;
+ xfc->px_vector = 0;
+ xfc->z_vector = 0;
+ }
+ else if (xfc->py_vector < -PAN_THRESHOLD)
+ {
+ {
+ PanningChangeEventArgs e;
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = 0;
+ e.dy = -5;
+ PubSub_OnPanningChange(ctx->pubSub, xfc, &e);
+ }
+ xfc->py_vector = 0;
+ xfc->px_vector = 0;
+ xfc->z_vector = 0;
+ }
+ }
+}
+
+static void xf_input_detect_pinch(xfContext* xfc)
+{
+ double dist = NAN;
+ double delta = NAN;
+ ZoomingChangeEventArgs e;
+ rdpContext* ctx = NULL;
+
+ WINPR_ASSERT(xfc);
+ ctx = &xfc->common.context;
+ WINPR_ASSERT(ctx);
+
+ if (xfc->active_contacts != 2)
+ {
+ xfc->firstDist = -1.0;
+ return;
+ }
+
+ /* first calculate the distance */
+ dist = sqrt(pow(xfc->contacts[1].pos_x - xfc->contacts[0].last_x, 2.0) +
+ pow(xfc->contacts[1].pos_y - xfc->contacts[0].last_y, 2.0));
+
+ /* if this is the first 2pt touch */
+ if (xfc->firstDist <= 0)
+ {
+ xfc->firstDist = dist;
+ xfc->lastDist = xfc->firstDist;
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ }
+ else
+ {
+ delta = xfc->lastDist - dist;
+
+ if (delta > 1.0)
+ delta = 1.0;
+
+ if (delta < -1.0)
+ delta = -1.0;
+
+ /* compare the current distance to the first one */
+ xfc->z_vector += delta;
+ xfc->lastDist = dist;
+
+ if (xfc->z_vector > ZOOM_THRESHOLD)
+ {
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = e.dy = -10;
+ PubSub_OnZoomingChange(ctx->pubSub, xfc, &e);
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ }
+
+ if (xfc->z_vector < -ZOOM_THRESHOLD)
+ {
+ EventArgsInit(&e, "xfreerdp");
+ e.dx = e.dy = 10;
+ PubSub_OnZoomingChange(ctx->pubSub, xfc, &e);
+ xfc->z_vector = 0;
+ xfc->px_vector = 0;
+ xfc->py_vector = 0;
+ }
+ }
+}
+
+static void xf_input_touch_begin(xfContext* xfc, const XIDeviceEvent* event)
+{
+ WINPR_UNUSED(xfc);
+ for (int i = 0; i < MAX_CONTACTS; i++)
+ {
+ if (xfc->contacts[i].id == 0)
+ {
+ xfc->contacts[i].id = event->detail;
+ xfc->contacts[i].count = 1;
+ xfc->contacts[i].pos_x = event->event_x;
+ xfc->contacts[i].pos_y = event->event_y;
+ xfc->active_contacts++;
+ break;
+ }
+ }
+}
+
+static void xf_input_touch_update(xfContext* xfc, const XIDeviceEvent* event)
+{
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(event);
+
+ for (int i = 0; i < MAX_CONTACTS; i++)
+ {
+ if (xfc->contacts[i].id == event->detail)
+ {
+ xfc->contacts[i].count++;
+ xfc->contacts[i].last_x = xfc->contacts[i].pos_x;
+ xfc->contacts[i].last_y = xfc->contacts[i].pos_y;
+ xfc->contacts[i].pos_x = event->event_x;
+ xfc->contacts[i].pos_y = event->event_y;
+ xf_input_detect_pinch(xfc);
+ xf_input_detect_pan(xfc);
+ break;
+ }
+ }
+}
+
+static void xf_input_touch_end(xfContext* xfc, const XIDeviceEvent* event)
+{
+ WINPR_UNUSED(xfc);
+ for (int i = 0; i < MAX_CONTACTS; i++)
+ {
+ if (xfc->contacts[i].id == event->detail)
+ {
+ xfc->contacts[i].id = 0;
+ xfc->contacts[i].count = 0;
+ xfc->active_contacts--;
+ break;
+ }
+ }
+}
+
+static int xf_input_handle_event_local(xfContext* xfc, const XEvent* event)
+{
+ union
+ {
+ const XGenericEventCookie* cc;
+ XGenericEventCookie* vc;
+ } cookie;
+ cookie.cc = &event->xcookie;
+ XGetEventData(xfc->display, cookie.vc);
+
+ if ((cookie.cc->type == GenericEvent) && (cookie.cc->extension == xfc->XInputOpcode))
+ {
+ switch (cookie.cc->evtype)
+ {
+ case XI_TouchBegin:
+ if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE)
+ xf_input_touch_begin(xfc, cookie.cc->data);
+
+ xf_input_save_last_event(xfc, cookie.cc);
+ break;
+
+ case XI_TouchUpdate:
+ if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE)
+ xf_input_touch_update(xfc, cookie.cc->data);
+
+ xf_input_save_last_event(xfc, cookie.cc);
+ break;
+
+ case XI_TouchEnd:
+ if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE)
+ xf_input_touch_end(xfc, cookie.cc->data);
+
+ xf_input_save_last_event(xfc, cookie.cc);
+ break;
+
+ default:
+ xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype);
+ break;
+ }
+ }
+
+ XFreeEventData(xfc->display, cookie.vc);
+ return 0;
+}
+
+#ifdef WITH_DEBUG_X11
+static char* xf_input_touch_state_string(DWORD flags)
+{
+ if (flags & RDPINPUT_CONTACT_FLAG_DOWN)
+ return "RDPINPUT_CONTACT_FLAG_DOWN";
+ else if (flags & RDPINPUT_CONTACT_FLAG_UPDATE)
+ return "RDPINPUT_CONTACT_FLAG_UPDATE";
+ else if (flags & RDPINPUT_CONTACT_FLAG_UP)
+ return "RDPINPUT_CONTACT_FLAG_UP";
+ else if (flags & RDPINPUT_CONTACT_FLAG_INRANGE)
+ return "RDPINPUT_CONTACT_FLAG_INRANGE";
+ else if (flags & RDPINPUT_CONTACT_FLAG_INCONTACT)
+ return "RDPINPUT_CONTACT_FLAG_INCONTACT";
+ else if (flags & RDPINPUT_CONTACT_FLAG_CANCELED)
+ return "RDPINPUT_CONTACT_FLAG_CANCELED";
+ else
+ return "RDPINPUT_CONTACT_FLAG_UNKNOWN";
+}
+#endif
+
+static void xf_input_hide_cursor(xfContext* xfc)
+{
+#ifdef WITH_XCURSOR
+
+ if (!xfc->cursorHidden)
+ {
+ XcursorImage ci = { 0 };
+ XcursorPixel xp = 0;
+ static Cursor nullcursor = None;
+ xf_lock_x11(xfc);
+ ci.version = XCURSOR_IMAGE_VERSION;
+ ci.size = sizeof(ci);
+ ci.width = ci.height = 1;
+ ci.xhot = ci.yhot = 0;
+ ci.pixels = &xp;
+ nullcursor = XcursorImageLoadCursor(xfc->display, &ci);
+
+ if ((xfc->window) && (nullcursor != None))
+ XDefineCursor(xfc->display, xfc->window->handle, nullcursor);
+
+ xfc->cursorHidden = TRUE;
+ xf_unlock_x11(xfc);
+ }
+
+#endif
+}
+
+static void xf_input_show_cursor(xfContext* xfc)
+{
+#ifdef WITH_XCURSOR
+ xf_lock_x11(xfc);
+
+ if (xfc->cursorHidden)
+ {
+ if (xfc->window)
+ {
+ if (!xfc->pointer)
+ XUndefineCursor(xfc->display, xfc->window->handle);
+ else
+ XDefineCursor(xfc->display, xfc->window->handle, xfc->pointer->cursor);
+ }
+
+ xfc->cursorHidden = FALSE;
+ }
+
+ xf_unlock_x11(xfc);
+#endif
+}
+
+static int xf_input_touch_remote(xfContext* xfc, XIDeviceEvent* event, int evtype)
+{
+ int x = 0;
+ int y = 0;
+ int touchId = 0;
+ RdpeiClientContext* rdpei = xfc->common.rdpei;
+
+ if (!rdpei)
+ return 0;
+
+ xf_input_hide_cursor(xfc);
+ touchId = event->detail;
+ x = (int)event->event_x;
+ y = (int)event->event_y;
+ xf_event_adjust_coordinates(xfc, &x, &y);
+
+ switch (evtype)
+ {
+ case XI_TouchBegin:
+ freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_DOWN, touchId, 0, x, y);
+ break;
+ case XI_TouchUpdate:
+ freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_MOTION, touchId, 0, x, y);
+ break;
+ case XI_TouchEnd:
+ freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_UP, touchId, 0, x, y);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static BOOL xf_input_pen_remote(xfContext* xfc, XIDeviceEvent* event, int evtype, int deviceid)
+{
+ int x = 0;
+ int y = 0;
+ RdpeiClientContext* rdpei = xfc->common.rdpei;
+
+ if (!rdpei)
+ return FALSE;
+
+ xf_input_hide_cursor(xfc);
+ x = (int)event->event_x;
+ y = (int)event->event_y;
+ xf_event_adjust_coordinates(xfc, &x, &y);
+
+ double pressure = 0.0;
+ double* val = event->valuators.values;
+ for (int i = 0; i < MIN(event->valuators.mask_len * 8, 3); i++)
+ {
+ if (XIMaskIsSet(event->valuators.mask, i))
+ {
+ double value = *val++;
+ if (i == 2)
+ pressure = value;
+ }
+ }
+
+ UINT32 flags = FREERDP_PEN_HAS_PRESSURE;
+ if ((evtype == XI_ButtonPress) || (evtype == XI_ButtonRelease))
+ {
+ WLog_DBG(TAG, "pen button %d", event->detail);
+ switch (event->detail)
+ {
+ case 1:
+ break;
+ case 3:
+ flags |= FREERDP_PEN_BARREL_PRESSED;
+ break;
+ default:
+ return FALSE;
+ }
+ }
+
+ switch (evtype)
+ {
+ case XI_ButtonPress:
+ flags |= FREERDP_PEN_PRESS;
+ if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure))
+ return FALSE;
+ break;
+ case XI_Motion:
+ flags |= FREERDP_PEN_MOTION;
+ if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure))
+ return FALSE;
+ break;
+ case XI_ButtonRelease:
+ flags |= FREERDP_PEN_RELEASE;
+ if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure))
+ return FALSE;
+ break;
+ default:
+ break;
+ }
+ return TRUE;
+}
+
+static int xf_input_pens_unhover(xfContext* xfc)
+{
+ WINPR_ASSERT(xfc);
+
+ freerdp_client_pen_cancel_all(&xfc->common);
+ return 0;
+}
+
+int xf_input_event(xfContext* xfc, const XEvent* xevent, XIDeviceEvent* event, int evtype)
+{
+ const rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(xfc);
+ WINPR_ASSERT(xevent);
+ WINPR_ASSERT(event);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ xfWindow* window = xfc->window;
+ if (window)
+ {
+ if (xf_floatbar_is_locked(window->floatbar))
+ return 0;
+ }
+
+ xf_input_show_cursor(xfc);
+
+ switch (evtype)
+ {
+ case XI_ButtonPress:
+ xfc->xi_event = TRUE;
+ xf_generic_ButtonEvent(xfc, (int)event->event_x, (int)event->event_y, event->detail,
+ event->event, xfc->remote_app, TRUE);
+ break;
+
+ case XI_ButtonRelease:
+ xfc->xi_event = TRUE;
+ xf_generic_ButtonEvent(xfc, (int)event->event_x, (int)event->event_y, event->detail,
+ event->event, xfc->remote_app, FALSE);
+ break;
+
+ case XI_Motion:
+ xfc->xi_event = TRUE;
+ xf_generic_MotionNotify(xfc, (int)event->event_x, (int)event->event_y, event->detail,
+ event->event, xfc->remote_app);
+ break;
+ case XI_RawButtonPress:
+ case XI_RawButtonRelease:
+ xfc->xi_rawevent =
+ xfc->common.mouse_grabbed && freerdp_client_use_relative_mouse_events(&xfc->common);
+
+ if (xfc->xi_rawevent)
+ {
+ const XIRawEvent* ev = (const XIRawEvent*)event;
+ xf_generic_RawButtonEvent(xfc, ev->detail, xfc->remote_app,
+ evtype == XI_RawButtonPress);
+ }
+ break;
+ case XI_RawMotion:
+ xfc->xi_rawevent =
+ xfc->common.mouse_grabbed && freerdp_client_use_relative_mouse_events(&xfc->common);
+
+ if (xfc->xi_rawevent)
+ {
+ const XIRawEvent* ev = (const XIRawEvent*)event;
+ double x = 0.0;
+ double y = 0.0;
+ if (XIMaskIsSet(ev->valuators.mask, 0))
+ x = ev->raw_values[0];
+ if (XIMaskIsSet(ev->valuators.mask, 1))
+ y = ev->raw_values[1];
+
+ xf_generic_RawMotionNotify(xfc, (int)x, (int)y, event->event, xfc->remote_app);
+ }
+ break;
+ case XI_DeviceChanged:
+ {
+ const XIDeviceChangedEvent* ev = (const XIDeviceChangedEvent*)event;
+ if (ev->reason != XIDeviceChange)
+ break;
+
+ /*
+ * TODO:
+ * 1. Register only changed devices.
+ * 2. Both `XIDeviceChangedEvent` and `XIHierarchyEvent` have no target
+ * `Window` which is used to register xinput events. So assume
+ * `xfc->window` created by `xf_CreateDesktopWindow` is the same
+ * `Window` we registered.
+ */
+ if (xfc->window)
+ register_input_events(xfc, xfc->window->handle);
+ }
+ break;
+ case XI_HierarchyChanged:
+ if (xfc->window)
+ register_input_events(xfc, xfc->window->handle);
+ break;
+
+ default:
+ WLog_WARN(TAG, "Unhandled event %d: Event was registered but is not handled!", evtype);
+ break;
+ }
+
+ return 0;
+}
+
+static int xf_input_handle_event_remote(xfContext* xfc, const XEvent* event)
+{
+ union
+ {
+ const XGenericEventCookie* cc;
+ XGenericEventCookie* vc;
+ } cookie;
+ cookie.cc = &event->xcookie;
+ XGetEventData(xfc->display, cookie.vc);
+
+ if ((cookie.cc->type == GenericEvent) && (cookie.cc->extension == xfc->XInputOpcode))
+ {
+ switch (cookie.cc->evtype)
+ {
+ case XI_TouchBegin:
+ xf_input_pens_unhover(xfc);
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ case XI_TouchUpdate:
+ case XI_TouchEnd:
+ xf_input_touch_remote(xfc, cookie.cc->data, cookie.cc->evtype);
+ break;
+ case XI_ButtonPress:
+ case XI_Motion:
+ case XI_ButtonRelease:
+ {
+ WLog_DBG(TAG, "checking for pen");
+ XIDeviceEvent* deviceEvent = (XIDeviceEvent*)cookie.cc->data;
+ int deviceid = deviceEvent->deviceid;
+
+ if (freerdp_client_is_pen(&xfc->common, deviceid))
+ {
+ if (!xf_input_pen_remote(xfc, cookie.cc->data, cookie.cc->evtype, deviceid))
+ {
+ // XXX: don't show cursor
+ xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype);
+ }
+ break;
+ }
+ }
+ /* fallthrough */
+ WINPR_FALLTHROUGH
+ default:
+ xf_input_pens_unhover(xfc);
+ xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype);
+ break;
+ }
+ }
+
+ XFreeEventData(xfc->display, cookie.vc);
+ return 0;
+}
+
+#else
+
+int xf_input_init(xfContext* xfc, Window window)
+{
+ return 0;
+}
+
+#endif
+
+int xf_input_handle_event(xfContext* xfc, const XEvent* event)
+{
+#ifdef WITH_XI
+ const rdpSettings* settings = NULL;
+ WINPR_ASSERT(xfc);
+
+ settings = xfc->common.context.settings;
+ WINPR_ASSERT(settings);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchInput))
+ {
+ return xf_input_handle_event_remote(xfc, event);
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))
+ {
+ return xf_input_handle_event_local(xfc, event);
+ }
+ else
+ {
+ return xf_input_handle_event_local(xfc, event);
+ }
+
+#else
+ return 0;
+#endif
+}