]> source.dussan.org Git - tigervnc.git/commitdiff
Implement X Input pointer handling for Unix
authorAaron Sowry <aaron@cendio.se>
Thu, 25 Apr 2019 00:31:23 +0000 (12:31 +1200)
committerNiko Lehto <nikle@cendio.se>
Fri, 29 May 2020 13:26:32 +0000 (15:26 +0200)
Switch from using Core events to using X Input events for pointer
devices in order to differentiate between mouse events and touch events.

Because FLTK doesn't understand X Input 2, we intercept these events and
translate them to core events where possible.

cmake/StaticBuild.cmake
vncviewer/CMakeLists.txt
vncviewer/DesktopWindow.cxx
vncviewer/XInputTouchHandler.cxx [new file with mode: 0644]
vncviewer/XInputTouchHandler.h [new file with mode: 0644]
vncviewer/touch.cxx [new file with mode: 0644]
vncviewer/touch.h [new file with mode: 0644]
vncviewer/vncviewer.cxx

index 43a0786ee0d5df6d7cccc6b184506eb3311e8959..9129f9dbf2e85a005d2a34cb6137bacc33c85a1d 100644 (file)
@@ -112,6 +112,9 @@ if(BUILD_STATIC)
     if(X11_Xrandr_LIB)
       set(X11_Xrandr_LIB "-Wl,-Bstatic -lXrandr -lXrender -Wl,-Bdynamic")
     endif()
+    if(X11_Xi_LIB)
+      set(X11_Xi_LIB "-Wl,-Bstatic -lXi -Wl,-Bdynamic")
+    endif()
   endif()
 endif()
 
index a2048f29851304586dc62f0ae5727fa7ff074bcb..92b516cba18f1c7a59ea9c82e057965e7c7afff3 100644 (file)
@@ -15,6 +15,7 @@ set(VNCVIEWER_SOURCES
   Viewport.cxx
   parameters.cxx
   keysym2ucs.c
+  touch.cxx
   vncviewer.cxx)
 
 if(WIN32)
@@ -32,7 +33,7 @@ if(WIN32)
 elseif(APPLE)
   set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} cocoa.mm osx_to_qnum.c)
 else()
-  set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} xkb_to_qnum.c)
+  set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} XInputTouchHandler.cxx xkb_to_qnum.c)
 endif()
 
 if(WIN32)
@@ -53,12 +54,12 @@ target_link_libraries(vncviewer rfb network rdr os ${FLTK_LIBRARIES} ${GETTEXT_L
 
 if(WIN32)
   target_link_libraries(vncviewer msimg32)
-endif()
-
-if(APPLE)
+elseif(APPLE)
   target_link_libraries(vncviewer "-framework Cocoa")
   target_link_libraries(vncviewer "-framework Carbon")
   target_link_libraries(vncviewer "-framework IOKit")
+else()
+  target_link_libraries(vncviewer ${X11_Xi_LIB})
 endif()
 
 install(TARGETS vncviewer DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})
index f616f8c8a81667bff882884b4f748ee54346c46c..6dc85f4a9ec407cd441b999146534d1b91c095a2 100644 (file)
@@ -37,6 +37,7 @@
 #include "CConn.h"
 #include "Surface.h"
 #include "Viewport.h"
+#include "touch.h"
 
 #include <FL/Fl.H>
 #include <FL/Fl_Image_Surface.H>
@@ -967,23 +968,13 @@ void DesktopWindow::ungrabKeyboard()
 void DesktopWindow::grabPointer()
 {
 #if !defined(WIN32) && !defined(__APPLE__)
-  int ret;
-
   // We also need to grab the pointer as some WMs like to grab buttons
   // combined with modifies (e.g. Alt+Button0 in metacity).
-  ret = XGrabPointer(fl_display, fl_xid(this), True,
-                     ButtonPressMask|ButtonReleaseMask|
-                     ButtonMotionMask|PointerMotionMask,
-                     GrabModeAsync, GrabModeAsync,
-                     None, None, CurrentTime);
-  if (ret) {
-    // Having a button pressed prevents us from grabbing, we make
-    // a new attempt in fltkHandle()
-    if (ret == AlreadyGrabbed)
-      return;
-    vlog.error(_("Failure grabbing mouse"));
+
+  // Having a button pressed prevents us from grabbing, we make
+  // a new attempt in fltkHandle()
+  if (!x11_grab_pointer(fl_xid(this)))
     return;
-  }
 #endif
 
   mouseGrabbed = true;
@@ -993,8 +984,9 @@ void DesktopWindow::grabPointer()
 void DesktopWindow::ungrabPointer()
 {
   mouseGrabbed = false;
+
 #if !defined(WIN32) && !defined(__APPLE__)
-  XUngrabPointer(fl_display, CurrentTime);
+  x11_ungrab_pointer(fl_xid(this));
 #endif
 }
 
diff --git a/vncviewer/XInputTouchHandler.cxx b/vncviewer/XInputTouchHandler.cxx
new file mode 100644 (file)
index 0000000..f64b06a
--- /dev/null
@@ -0,0 +1,332 @@
+/* Copyright 2019 Aaron Sowry for Cendio AB
+ * Copyright 2019-2020 Pierre Ossman for Cendio AB
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <string.h>
+
+#include <X11/extensions/XInput2.h>
+#include <X11/extensions/XI2.h>
+
+#include <FL/x.H>
+
+#include <rfb/LogWriter.h>
+
+#include "i18n.h"
+#include "XInputTouchHandler.h"
+
+static rfb::LogWriter vlog("XInputTouchHandler");
+
+static bool grabbed = false;
+
+XInputTouchHandler::XInputTouchHandler(Window wnd)
+  : wnd(wnd), fakeStateMask(0), trackingTouch(false)
+{
+  XIEventMask eventmask;
+  unsigned char flags[XIMaskLen(XI_LASTEVENT)] = { 0 };
+
+  // Event delivery is broken when somebody else does a pointer grab,
+  // so we need to listen to all devices and do filtering of master
+  // devices manually
+  eventmask.deviceid = XIAllDevices;
+  eventmask.mask_len = sizeof(flags);
+  eventmask.mask = flags;
+
+  XISetMask(flags, XI_ButtonPress);
+  XISetMask(flags, XI_Motion);
+  XISetMask(flags, XI_ButtonRelease);
+
+  XISetMask(flags, XI_TouchBegin);
+  XISetMask(flags, XI_TouchUpdate);
+  XISetMask(flags, XI_TouchEnd);
+
+  // If something has a passive grab of touches (e.g. the window
+  // manager wants to have its own gestures) then we won't get the
+  // touch events until everyone who has a grab has indicated they
+  // don't want these touches (via XIAllowTouchEvents()).
+  // Unfortunately the touches are then replayed one touch point at
+  // a time, meaning things will be delayed and out of order,
+  // completely screwing up our gesture detection. Listening for
+  // XI_TouchOwnership has the effect of giving us the touch events
+  // right away, even if grabbing clients are also getting them.
+  //
+  // FIXME: We should really wait for the XI_TouchOwnership event
+  //        before it is safe to react to the gesture, otherwise we
+  //        might react to something that the window manager will
+  //        also react to.
+  //
+  if (!grabbed)
+    XISetMask(flags, XI_TouchOwnership);
+
+  XISelectEvents(fl_display, wnd, &eventmask, 1);
+}
+
+bool XInputTouchHandler::grabPointer()
+{
+  XIEventMask *curmasks;
+  int num_masks;
+
+  int ret, ndevices;
+
+  XIDeviceInfo *devices, *device;
+  bool gotGrab;
+
+  // We grab for the same events as the window is currently interested in
+  curmasks = XIGetSelectedEvents(fl_display, wnd, &num_masks);
+  if (curmasks == NULL) {
+    if (num_masks == -1)
+      vlog.error(_("Unable to get X Input 2 event mask for window 0x%08lu"), wnd);
+    else
+      vlog.error(_("Window 0x%08lu has no X Input 2 event mask"), wnd);
+
+    return false;
+  }
+
+  // Our windows should only have a single mask, which allows us to
+  // simplify all the code handling the masks
+  if (num_masks > 1) {
+    vlog.error(_("Window 0x%08lu has more than one X Input 2 event mask"), wnd);
+    return false;
+  }
+
+  devices = XIQueryDevice(fl_display, XIAllMasterDevices, &ndevices);
+
+  // Iterate through available devices to find those which
+  // provide pointer input, and attempt to grab all such devices.
+  gotGrab = false;
+  for (int i = 0; i < ndevices; i++) {
+    device = &devices[i];
+
+    if (device->use != XIMasterPointer)
+      continue;
+
+    curmasks[0].deviceid = device->deviceid;
+
+    ret = XIGrabDevice(fl_display,
+                       device->deviceid,
+                       wnd,
+                       CurrentTime,
+                       None,
+                       XIGrabModeAsync,
+                       XIGrabModeAsync,
+                       True,
+                       &(curmasks[0]));
+
+    if (ret) {
+      if (ret == XIAlreadyGrabbed) {
+        continue;
+      } else {
+        vlog.error(_("Failure grabbing device %i"), device->deviceid);
+        continue;
+      }
+    }
+
+    gotGrab = true;
+  }
+
+  XIFreeDeviceInfo(devices);
+
+  // Did we not even grab a single device?
+  if (!gotGrab)
+    return false;
+
+  grabbed = true;
+
+  return true;
+}
+
+void XInputTouchHandler::ungrabPointer()
+{
+  int ndevices;
+  XIDeviceInfo *devices, *device;
+
+  devices = XIQueryDevice(fl_display, XIAllMasterDevices, &ndevices);
+
+  // Release all devices, hoping they are the same as when we
+  // grabbed things
+  for (int i = 0; i < ndevices; i++) {
+    device = &devices[i];
+
+    if (device->use != XIMasterPointer)
+      continue;
+
+    XIUngrabDevice(fl_display, device->deviceid, CurrentTime);
+  }
+
+  XIFreeDeviceInfo(devices);
+
+  grabbed = false;
+}
+
+void XInputTouchHandler::processEvent(const XIDeviceEvent* devev)
+{
+  // FLTK doesn't understand X Input events, and we've stopped
+  // delivery of Core events by enabling the X Input ones. Make
+  // FLTK happy by faking Core events based on the X Input ones.
+
+  bool isMaster = devev->deviceid != devev->sourceid;
+
+  // We're only interested in events from master devices
+  if (!isMaster) {
+    // However we need to accept TouchEnd from slave devices as they
+    // might not get delivered if there is a pointer grab, see:
+    // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1016
+    if (devev->evtype != XI_TouchEnd)
+      return;
+  }
+
+  // Avoid duplicate TouchEnd events, see above
+  // FIXME: Doesn't handle floating slave devices
+  if (isMaster && devev->evtype == XI_TouchEnd)
+    return;
+
+  if (devev->flags & XIPointerEmulated) {
+    // We still want the server to do the scroll wheel to button thing
+    // though, so keep those
+    if (((devev->evtype == XI_ButtonPress) ||
+         (devev->evtype == XI_ButtonRelease)) &&
+        (devev->detail >= 4) && (devev->detail <= 7)) {
+      ;
+    } else {
+      // Sometimes the server incorrectly sends us various events with
+      // this flag set, see:
+      // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1027
+      return;
+    }
+  }
+
+  switch (devev->evtype) {
+  case XI_Enter:
+  case XI_Leave:
+    // We get these when the mouse is grabbed implicitly, so just ignore them
+    // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1026
+    break;
+  case XI_Motion:
+    // FIXME: We also get XI_Motion for scroll wheel events, which
+    //        we might want to ignore
+    fakeMotionEvent(devev);
+    break;
+  case XI_ButtonPress:
+    fakeButtonEvent(true, devev->detail, devev);
+    break;
+  case XI_ButtonRelease:
+    fakeButtonEvent(false, devev->detail, devev);
+    break;
+  case XI_TouchBegin:
+    if (trackingTouch)
+      break;
+
+    // XInput2 wants us to explicitly accept touch sequences
+    // for grabbed devices before it will pass events
+    if (grabbed) {
+      XIAllowTouchEvents(fl_display,
+                         devev->deviceid,
+                         devev->detail,
+                         devev->event,
+                         XIAcceptTouch);
+    }
+
+    trackingTouch = true;
+    trackedTouchPoint = devev->detail;
+
+    fakeMotionEvent(devev);
+    fakeButtonEvent(true, Button1, devev);
+    break;
+  case XI_TouchUpdate:
+    if (!trackingTouch || (devev->detail != trackedTouchPoint))
+      break;
+    fakeMotionEvent(devev);
+    break;
+  case XI_TouchEnd:
+    if (!trackingTouch || (devev->detail != trackedTouchPoint))
+      break;
+    fakeButtonEvent(false, Button1, devev);
+    trackingTouch = false;
+    break;
+  case XI_TouchOwnership:
+    // FIXME: Currently ignored, see constructor
+    break;
+  }
+}
+
+void XInputTouchHandler::preparePointerEvent(XEvent* dst, const XIDeviceEvent* src)
+{
+  // XButtonEvent and XMotionEvent are almost identical, so we
+  // don't have to care which it is for these fields
+  dst->xbutton.serial = src->serial;
+  dst->xbutton.display = src->display;
+  dst->xbutton.window = src->event;
+  dst->xbutton.root = src->root;
+  dst->xbutton.subwindow = src->child;
+  dst->xbutton.time = src->time;
+  dst->xbutton.x = src->event_x;
+  dst->xbutton.y = src->event_y;
+  dst->xbutton.x_root = src->root_x;
+  dst->xbutton.y_root = src->root_y;
+  dst->xbutton.state = src->mods.effective;
+  dst->xbutton.state |= ((src->buttons.mask[0] >> 1) & 0x1f) << 8;
+  dst->xbutton.same_screen = True;
+}
+
+void XInputTouchHandler::fakeMotionEvent(const XIDeviceEvent* origEvent)
+{
+  XEvent fakeEvent;
+
+  memset(&fakeEvent, 0, sizeof(XEvent));
+
+  fakeEvent.type = MotionNotify;
+  fakeEvent.xmotion.is_hint = False;
+  preparePointerEvent(&fakeEvent, origEvent);
+
+  fakeEvent.xbutton.state |= fakeStateMask;
+
+  pushFakeEvent(&fakeEvent);
+}
+
+void XInputTouchHandler::fakeButtonEvent(bool press, int button,
+                                         const XIDeviceEvent* origEvent)
+{
+  XEvent fakeEvent;
+
+  memset(&fakeEvent, 0, sizeof(XEvent));
+
+  fakeEvent.type = press ? ButtonPress : ButtonRelease;
+  fakeEvent.xbutton.button = button;
+  preparePointerEvent(&fakeEvent, origEvent);
+
+  fakeEvent.xbutton.state |= fakeStateMask;
+
+  pushFakeEvent(&fakeEvent);
+
+  // Set/unset the bit for the correct button
+  if (press) {
+    fakeStateMask |= (1<<(7+button));
+  } else {
+    fakeStateMask &= ~(1<<(7+button));
+  }
+}
+
+void XInputTouchHandler::pushFakeEvent(XEvent* event)
+{
+  // Perhaps use XPutBackEvent() to avoid round trip latency?
+  XSendEvent(fl_display, event->xany.window, true, 0, event);
+}
diff --git a/vncviewer/XInputTouchHandler.h b/vncviewer/XInputTouchHandler.h
new file mode 100644 (file)
index 0000000..0657288
--- /dev/null
@@ -0,0 +1,48 @@
+/* Copyright 2019 Aaron Sowry for Cendio AB
+ * Copyright 2019-2020 Pierre Ossman for Cendio AB
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifndef __XINPUTTOUCHHANDLER_H__
+#define __XINPUTTOUCHHANDLER_H__
+
+class XInputTouchHandler {
+  public:
+    XInputTouchHandler(Window wnd);
+
+    bool grabPointer();
+    void ungrabPointer();
+
+    void processEvent(const XIDeviceEvent* devev);
+
+  protected:
+    void preparePointerEvent(XEvent* dst, const XIDeviceEvent* src);
+    void fakeMotionEvent(const XIDeviceEvent* origEvent);
+    void fakeButtonEvent(bool press, int button,
+                         const XIDeviceEvent* origEvent);
+
+  private:
+    void pushFakeEvent(XEvent* event);
+
+  private:
+    Window wnd;
+    int fakeStateMask;
+    bool trackingTouch;
+    int trackedTouchPoint;
+};
+
+#endif
diff --git a/vncviewer/touch.cxx b/vncviewer/touch.cxx
new file mode 100644 (file)
index 0000000..4ad9a0a
--- /dev/null
@@ -0,0 +1,220 @@
+/* Copyright 2019-2020 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ * Copyright 2019 Aaron Sowry for Cendio AB
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+
+#include <map>
+
+#if !defined(WIN32) && !defined(__APPLE__)
+#include <X11/extensions/XInput2.h>
+#include <X11/extensions/XI2.h>
+#endif
+
+#include <FL/Fl.H>
+#include <FL/x.H>
+
+#include <rfb/LogWriter.h>
+
+#include "i18n.h"
+#include "vncviewer.h"
+#if !defined(WIN32) && !defined(__APPLE__)
+#include "XInputTouchHandler.h"
+#endif
+
+#include "touch.h"
+
+static rfb::LogWriter vlog("Touch");
+
+#if !defined(WIN32) && !defined(__APPLE__)
+static int xi_major;
+
+typedef std::map<Window, class XInputTouchHandler*> HandlerMap;
+static HandlerMap handlers;
+#endif
+
+#if !defined(WIN32) && !defined(__APPLE__)
+static void x11_change_touch_ownership(bool enable)
+{
+  HandlerMap::const_iterator iter;
+
+  XIEventMask *curmasks;
+  int num_masks;
+
+  XIEventMask newmask;
+  unsigned char mask[XIMaskLen(XI_LASTEVENT)] = { 0 };
+
+  newmask.mask = mask;
+  newmask.mask_len = sizeof(mask);
+
+  for (iter = handlers.begin(); iter != handlers.end(); ++iter) {
+    curmasks = XIGetSelectedEvents(fl_display, iter->first, &num_masks);
+    if (curmasks == NULL) {
+      if (num_masks == -1)
+        vlog.error(_("Unable to get X Input 2 event mask for window 0x%08lu"), iter->first);
+      continue;
+    }
+
+    // Our windows should only have a single mask, which allows us to
+    // simplify all the code handling the masks
+    if (num_masks > 1) {
+      vlog.error(_("Window 0x%08lu has more than one X Input 2 event mask"), iter->first);
+      continue;
+    }
+
+    newmask.deviceid = curmasks[0].deviceid;
+
+    assert(newmask.mask_len >= curmasks[0].mask_len);
+    memcpy(newmask.mask, curmasks[0].mask, curmasks[0].mask_len);
+    if (enable)
+      XISetMask(newmask.mask, XI_TouchOwnership);
+    else
+      XIClearMask(newmask.mask, XI_TouchOwnership);
+
+    XISelectEvents(fl_display, iter->first, &newmask, 1);
+
+    XFree(curmasks);
+  }
+}
+
+bool x11_grab_pointer(Window window)
+{
+  bool ret;
+
+  if (handlers.count(window) == 0) {
+    vlog.error(_("Invalid window 0x%08lu specified for pointer grab"), window);
+    return BadWindow;
+  }
+
+  // We need to remove XI_TouchOwnership from our event masks while
+  // grabbing as otherwise we will get double events (one for the grab,
+  // and one because of XI_TouchOwnership) with no way of telling them
+  // apart. See XInputTouchHandler constructor for why we use this
+  // event.
+  x11_change_touch_ownership(false);
+
+  ret = handlers[window]->grabPointer();
+
+  if (!ret)
+    x11_change_touch_ownership(true);
+
+  return ret;
+}
+
+void x11_ungrab_pointer(Window window)
+{
+  if (handlers.count(window) == 0) {
+    vlog.error(_("Invalid window 0x%08lu specified for pointer grab"), window);
+    return;
+  }
+
+  handlers[window]->ungrabPointer();
+
+  // Restore XI_TouchOwnership now that the grab is gone
+  x11_change_touch_ownership(true);
+}
+#endif
+
+static int handleTouchEvent(void *event, void *data)
+{
+#if !defined(WIN32) && !defined(__APPLE__)
+  XEvent *xevent = (XEvent*)event;
+
+  if (xevent->type == MapNotify) {
+    handlers[xevent->xmap.window] = new XInputTouchHandler(xevent->xmap.window);
+
+    // Fall through as we don't want to interfere with whatever someone
+    // else might want to do with this event
+
+  } else if (xevent->type == UnmapNotify) {
+    delete handlers[xevent->xunmap.window];
+    handlers.erase(xevent->xunmap.window);
+  } else if (xevent->type == DestroyNotify) {
+    delete handlers[xevent->xdestroywindow.window];
+    handlers.erase(xevent->xdestroywindow.window);
+  } else if (xevent->type == GenericEvent) {
+    if (xevent->xgeneric.extension == xi_major) {
+      XIDeviceEvent *devev;
+
+      if (!XGetEventData(fl_display, &xevent->xcookie)) {
+        vlog.error(_("Failed to get event data for X Input event"));
+        return 1;
+      }
+
+      devev = (XIDeviceEvent*)xevent->xcookie.data;
+
+      if (handlers.count(devev->event) == 0) {
+        // We get these when the mouse is grabbed implicitly, so just
+        // ignore them
+        // https://gitlab.freedesktop.org/xorg/xserver/-/issues/1026
+        if ((devev->evtype == XI_Enter) || (devev->evtype == XI_Leave))
+          ;
+        else
+          vlog.error(_("X Input event for unknown window"));
+        XFreeEventData(fl_display, &xevent->xcookie);
+        return 1;
+      }
+
+      handlers[devev->event]->processEvent(devev);
+
+      XFreeEventData(fl_display, &xevent->xcookie);
+
+      return 1;
+    }
+  }
+#endif
+
+  return 0;
+}
+
+void enable_touch()
+{
+#if !defined(WIN32) && !defined(__APPLE__)
+  int ev, err;
+  int major_ver, minor_ver;
+
+  fl_open_display();
+
+  if (!XQueryExtension(fl_display, "XInputExtension", &xi_major, &ev, &err)) {
+    exit_vncviewer(_("X Input extension not available."));
+    return; // Not reached
+  }
+
+  major_ver = 2;
+  minor_ver = 2;
+  if (XIQueryVersion(fl_display, &major_ver, &minor_ver) != Success) {
+    exit_vncviewer(_("X Input 2 (or newer) is not available."));
+    return; // Not reached
+  }
+
+  if ((major_ver == 2) && (minor_ver < 2))
+    vlog.error(_("X Input 2.2 (or newer) is not available. Touch gestures will not be supported."));
+#endif
+
+  Fl::add_system_handler(handleTouchEvent, NULL);
+}
+
+void disable_touch()
+{
+  Fl::remove_system_handler(handleTouchEvent);
+}
+
diff --git a/vncviewer/touch.h b/vncviewer/touch.h
new file mode 100644 (file)
index 0000000..dad79b4
--- /dev/null
@@ -0,0 +1,30 @@
+/* Copyright 2019-2020 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifndef __TOUCH_H__
+#define __TOUCH_H__
+
+void enable_touch();
+void disable_touch();
+
+#if !defined(WIN32) && !defined(__APPLE__)
+bool x11_grab_pointer(Window window);
+void x11_ungrab_pointer(Window window);
+#endif
+
+#endif
index 7b1ac92ebdc861084b35121ac52f49fd168f3e6c..d4dd3063cb55861b59bd4be586437903986c1a59 100644 (file)
@@ -68,6 +68,7 @@
 #include "CConn.h"
 #include "ServerDialog.h"
 #include "UserDialog.h"
+#include "touch.h"
 #include "vncviewer.h"
 #include "fltk_layout.h"
 
@@ -585,6 +586,7 @@ int main(int argc, char** argv)
 #endif
 
   init_fltk();
+  enable_touch();
 
   // Check if the server name in reality is a configuration file
   potentiallyLoadConfigurationFile(vncServerName);