/** * FreeRDP: A Remote Desktop Protocol Implementation * X11 Input * * Copyright 2013 Corey Clayton * * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #ifdef WITH_XCURSOR #include #endif #ifdef WITH_XI #include #endif #include #include "xf_event.h" #include "xf_input.h" #include #define TAG CLIENT_TAG("x11") #ifdef WITH_XI #define MAX_CONTACTS 2 #define PAN_THRESHOLD 50 #define ZOOM_THRESHOLD 10 #define MIN_FINGER_DIST 5 typedef struct touch_contact { int id; int count; double pos_x; double pos_y; double last_x; double last_y; } touchContact; static touchContact contacts[MAX_CONTACTS]; static int active_contacts; static int lastEvType; static XIDeviceEvent lastEvent; static double firstDist = -1.0; static double lastDist; static double z_vector; static double px_vector; static double py_vector; 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"; } int xf_input_init(xfContext* xfc, Window window) { int i, j; int nmasks; int ndevices; int major = 2; int minor = 2; Status xstatus; XIDeviceInfo* info; XIEventMask evmasks[64]; int opcode, event, error; BYTE masks[8][XIMaskLen(XI_LASTEVENT)]; z_vector = 0; px_vector = 0; py_vector = 0; nmasks = 0; ndevices = 0; active_contacts = 0; ZeroMemory(contacts, sizeof(touchContact) * MAX_CONTACTS); 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 * 1000 + minor < 2002) { WLog_WARN(TAG, "Server does not support XI 2.2"); return -1; } if (xfc->context.settings->MultiTouchInput) xfc->use_xinput = TRUE; info = XIQueryDevice(xfc->display, XIAllDevices, &ndevices); for (i = 0; i < ndevices; i++) { BOOL touch = FALSE; XIDeviceInfo* dev = &info[i]; for (j = 0; j < dev->num_classes; j++) { XIAnyClassInfo* class = dev->classes[j]; XITouchClassInfo* t = (XITouchClassInfo*)class; if ((class->type == XITouchClass) && (t->mode == XIDirectTouch) && (strcmp(dev->name, "Virtual core pointer") != 0)) { touch = TRUE; } } for (j = 0; j < dev->num_classes; j++) { XIAnyClassInfo* class = dev->classes[j]; XITouchClassInfo* t = (XITouchClassInfo*)class; if (xfc->context.settings->MultiTouchInput) { WLog_DBG(TAG, "%s (%d) \"%s\" id: %d", xf_input_get_class_string(class->type), class->type, dev->name, dev->deviceid); } evmasks[nmasks].mask = masks[nmasks]; evmasks[nmasks].mask_len = sizeof(masks[0]); ZeroMemory(masks[nmasks], sizeof(masks[0])); evmasks[nmasks].deviceid = dev->deviceid; if ((class->type == XITouchClass) && (t->mode == XIDirectTouch) && (strcmp(dev->name, "Virtual core pointer") != 0)) { if (xfc->context.settings->MultiTouchInput) { 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); nmasks++; } if (xfc->use_xinput) { if (!touch && (class->type == XIButtonClass) && strcmp(dev->name, "Virtual core pointer")) { WLog_DBG(TAG, "%s button device (id: %d, mode: %d)", dev->name, dev->deviceid, t->mode); XISetMask(masks[nmasks], XI_ButtonPress); XISetMask(masks[nmasks], XI_ButtonRelease); XISetMask(masks[nmasks], XI_Motion); nmasks++; } } } } XIFreeDeviceInfo(info); if (nmasks > 0) xstatus = XISelectEvents(xfc->display, window, evmasks, nmasks); return 0; } static BOOL xf_input_is_duplicate(const XGenericEventCookie* cookie) { const XIDeviceEvent* event; event = cookie->data; if ((lastEvent.time == event->time) && (lastEvType == cookie->evtype) && (lastEvent.detail == event->detail) && (lastEvent.event_x == event->event_x) && (lastEvent.event_y == event->event_y)) { return TRUE; } return FALSE; } static void xf_input_save_last_event(const XGenericEventCookie* cookie) { const XIDeviceEvent* event; event = cookie->data; lastEvType = cookie->evtype; lastEvent.time = event->time; lastEvent.detail = event->detail; lastEvent.event_x = event->event_x; lastEvent.event_y = event->event_y; } static void xf_input_detect_pan(xfContext* xfc) { double dx[2]; double dy[2]; double px; double py; double dist_x; double dist_y; rdpContext* ctx = &xfc->context; if (active_contacts != 2) { return; } dx[0] = contacts[0].pos_x - contacts[0].last_x; dx[1] = contacts[1].pos_x - contacts[1].last_x; dy[0] = contacts[0].pos_y - contacts[0].last_y; dy[1] = contacts[1].pos_y - 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]; px_vector += px; py_vector += py; dist_x = fabs(contacts[0].pos_x - contacts[1].pos_x); dist_y = fabs(contacts[0].pos_y - contacts[1].pos_y); if (dist_y > MIN_FINGER_DIST) { if (px_vector > PAN_THRESHOLD) { { PanningChangeEventArgs e; EventArgsInit(&e, "xfreerdp"); e.dx = 5; e.dy = 0; PubSub_OnPanningChange(ctx->pubSub, xfc, &e); } px_vector = 0; py_vector = 0; z_vector = 0; } else if (px_vector < -PAN_THRESHOLD) { { PanningChangeEventArgs e; EventArgsInit(&e, "xfreerdp"); e.dx = -5; e.dy = 0; PubSub_OnPanningChange(ctx->pubSub, xfc, &e); } px_vector = 0; py_vector = 0; z_vector = 0; } } if (dist_x > MIN_FINGER_DIST) { if (py_vector > PAN_THRESHOLD) { { PanningChangeEventArgs e; EventArgsInit(&e, "xfreerdp"); e.dx = 0; e.dy = 5; PubSub_OnPanningChange(ctx->pubSub, xfc, &e); } py_vector = 0; px_vector = 0; z_vector = 0; } else if (py_vector < -PAN_THRESHOLD) { { PanningChangeEventArgs e; EventArgsInit(&e, "xfreerdp"); e.dx = 0; e.dy = -5; PubSub_OnPanningChange(ctx->pubSub, xfc, &e); } py_vector = 0; px_vector = 0; z_vector = 0; } } } static void xf_input_detect_pinch(xfContext* xfc) { double dist; double delta; ZoomingChangeEventArgs e; rdpContext* ctx = &xfc->context; if (active_contacts != 2) { firstDist = -1.0; return; } /* first calculate the distance */ dist = sqrt(pow(contacts[1].pos_x - contacts[0].last_x, 2.0) + pow(contacts[1].pos_y - contacts[0].last_y, 2.0)); /* if this is the first 2pt touch */ if (firstDist <= 0) { firstDist = dist; lastDist = firstDist; z_vector = 0; px_vector = 0; py_vector = 0; } else { delta = lastDist - dist; if (delta > 1.0) delta = 1.0; if (delta < -1.0) delta = -1.0; /* compare the current distance to the first one */ z_vector += delta; lastDist = dist; if (z_vector > ZOOM_THRESHOLD) { EventArgsInit(&e, "xfreerdp"); e.dx = e.dy = -10; PubSub_OnZoomingChange(ctx->pubSub, xfc, &e); z_vector = 0; px_vector = 0; py_vector = 0; } if (z_vector < -ZOOM_THRESHOLD) { EventArgsInit(&e, "xfreerdp"); e.dx = e.dy = 10; PubSub_OnZoomingChange(ctx->pubSub, xfc, &e); z_vector = 0; px_vector = 0; py_vector = 0; } } } static void xf_input_touch_begin(xfContext* xfc, XIDeviceEvent* event) { int i; WINPR_UNUSED(xfc); for (i = 0; i < MAX_CONTACTS; i++) { if (contacts[i].id == 0) { contacts[i].id = event->detail; contacts[i].count = 1; contacts[i].pos_x = event->event_x; contacts[i].pos_y = event->event_y; active_contacts++; break; } } } static void xf_input_touch_update(xfContext* xfc, XIDeviceEvent* event) { int i; for (i = 0; i < MAX_CONTACTS; i++) { if (contacts[i].id == event->detail) { contacts[i].count++; contacts[i].last_x = contacts[i].pos_x; contacts[i].last_y = contacts[i].pos_y; contacts[i].pos_x = event->event_x; 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, XIDeviceEvent* event) { int i; WINPR_UNUSED(xfc); for (i = 0; i < MAX_CONTACTS; i++) { if (contacts[i].id == event->detail) { contacts[i].id = 0; contacts[i].count = 0; 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(cookie.cc) == FALSE) xf_input_touch_begin(xfc, cookie.cc->data); xf_input_save_last_event(cookie.cc); break; case XI_TouchUpdate: if (xf_input_is_duplicate(cookie.cc) == FALSE) xf_input_touch_update(xfc, cookie.cc->data); xf_input_save_last_event(cookie.cc); break; case XI_TouchEnd: if (xf_input_is_duplicate(cookie.cc) == FALSE) xf_input_touch_end(xfc, cookie.cc->data); xf_input_save_last_event(cookie.cc); break; default: WLog_ERR(TAG, "unhandled xi type= %d", 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 & CONTACT_FLAG_DOWN) return "RDPINPUT::CONTACT_FLAG_DOWN"; else if (flags & CONTACT_FLAG_UPDATE) return "RDPINPUT::CONTACT_FLAG_UPDATE"; else if (flags & CONTACT_FLAG_UP) return "RDPINPUT::CONTACT_FLAG_UP"; else if (flags & CONTACT_FLAG_INRANGE) return "RDPINPUT::CONTACT_FLAG_INRANGE"; else if (flags & CONTACT_FLAG_INCONTACT) return "RDPINPUT::CONTACT_FLAG_INCONTACT"; else if (flags & 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; XcursorPixel xp = 0; static Cursor nullcursor = None; xf_lock_x11(xfc); ZeroMemory(&ci, sizeof(ci)); 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, y; int touchId; int contactId; RdpeiClientContext* rdpei = xfc->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); if (evtype == XI_TouchBegin) { WLog_DBG(TAG, "TouchBegin: %d", touchId); rdpei->TouchBegin(rdpei, touchId, x, y, &contactId); } else if (evtype == XI_TouchUpdate) { WLog_DBG(TAG, "TouchUpdate: %d", touchId); rdpei->TouchUpdate(rdpei, touchId, x, y, &contactId); } else if (evtype == XI_TouchEnd) { WLog_DBG(TAG, "TouchEnd: %d", touchId); rdpei->TouchEnd(rdpei, touchId, x, y, &contactId); } return 0; } static int xf_input_event(xfContext* xfc, XIDeviceEvent* event, int evtype) { xf_input_show_cursor(xfc); switch (evtype) { case XI_ButtonPress: xf_generic_ButtonEvent(xfc, (int)event->event_x, (int)event->event_y, event->detail, event->event, xfc->remote_app, TRUE); break; case XI_ButtonRelease: xf_generic_ButtonEvent(xfc, (int)event->event_x, (int)event->event_y, event->detail, event->event, xfc->remote_app, FALSE); break; case XI_Motion: xf_generic_MotionNotify(xfc, (int)event->event_x, (int)event->event_y, event->detail, event->event, xfc->remote_app); 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_touch_remote(xfc, cookie.cc->data, XI_TouchBegin); break; case XI_TouchUpdate: xf_input_touch_remote(xfc, cookie.cc->data, XI_TouchUpdate); break; case XI_TouchEnd: xf_input_touch_remote(xfc, cookie.cc->data, XI_TouchEnd); break; default: xf_input_event(xfc, 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 if (xfc->context.settings->MultiTouchInput) { return xf_input_handle_event_remote(xfc, event); } if (xfc->context.settings->MultiTouchGestures) { return xf_input_handle_event_local(xfc, event); } #endif return 0; }