diff options
Diffstat (limited to 'client/X11/xf_input.c')
-rw-r--r-- | client/X11/xf_input.c | 946 |
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 +} |