|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- /* Copyright 2020 Samuel Mannehed 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 <math.h>
-
- #define XK_MISCELLANY
- #include <rfb/keysymdef.h>
- #include <rfb/Exception.h>
- #include <rfb/LogWriter.h>
-
- #include "i18n.h"
- #include "Win32TouchHandler.h"
-
- static rfb::LogWriter vlog("Win32TouchHandler");
-
- static const DWORD MOUSEMOVE_FLAGS = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE |
- MOUSEEVENTF_VIRTUALDESK;
-
- static const unsigned SINGLE_PAN_THRESHOLD = 50;
-
- Win32TouchHandler::Win32TouchHandler(HWND hWnd) :
- hWnd(hWnd), gesturesConfigured(false), gestureActive(false),
- ignoringGesture(false), fakeButtonMask(0)
- {
- // If window is registered as touch we can not receive gestures,
- // this should not happen
- if (IsTouchWindow(hWnd, NULL))
- throw rfb::Exception(_("Window is registered for touch instead of gestures"));
-
- // We will not receive any touch/gesture events if this service
- // isn't running - Logging is enough
- if (!GetSystemMetrics(SM_DIGITIZER))
- vlog.debug("The 'Tablet PC Input' service is required for touch");
-
- // When we have less than two touch points we won't receive all
- // gesture events - Logging is enough
- int supportedTouches = GetSystemMetrics(SM_MAXIMUMTOUCHES);
- if (supportedTouches < 2)
- vlog.debug("Two touch points required, system currently supports: %d",
- supportedTouches);
- }
-
- bool Win32TouchHandler::processEvent(UINT Msg, WPARAM wParam, LPARAM lParam)
- {
- GESTUREINFO gi;
-
- DWORD panWant = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY |
- GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY |
- GC_PAN;
- DWORD panBlock = GC_PAN_WITH_INERTIA | GC_PAN_WITH_GUTTER;
-
- GESTURECONFIG gc[] = {{GID_ZOOM, GC_ZOOM, 0},
- {GID_PAN, panWant, panBlock},
- {GID_TWOFINGERTAP, GC_TWOFINGERTAP, 0}};
-
- switch(Msg) {
- case WM_GESTURENOTIFY:
- if (gesturesConfigured)
- return false;
-
- if (!SetGestureConfig(hWnd, 0, 3, gc, sizeof(GESTURECONFIG))) {
- vlog.error(_("Failed to set gesture configuration (error 0x%x)"),
- (int)GetLastError());
- }
- gesturesConfigured = true;
- // Windows expects all handler functions to always
- // pass this message on, and not consume it
- return false;
- case WM_GESTURE:
- ZeroMemory(&gi, sizeof(GESTUREINFO));
- gi.cbSize = sizeof(GESTUREINFO);
-
- if (!GetGestureInfo((HGESTUREINFO)lParam, &gi)) {
- vlog.error(_("Failed to get gesture information (error 0x%x)"),
- (int)GetLastError());
- return true;
- }
-
- handleWin32GestureEvent(gi);
-
- CloseGestureInfoHandle((HGESTUREINFO)lParam);
- return true;
- }
-
- return false;
- }
-
- void Win32TouchHandler::handleWin32GestureEvent(GESTUREINFO gi)
- {
- GestureEvent gev;
- POINT pos;
-
- if (gi.dwID == GID_BEGIN) {
- // FLTK gets very confused if the cursor position is outside
- // of the window when getting mouse events, so we start by
- // moving the cursor to something proper.
- // FIXME: Only do this when necessary?
- // FIXME: There is some odd delay before Windows fully updates
- // the state of the cursor position. By doing it here in
- // GID_BEGIN we hope to do it early enough that we don't
- // get any odd effects.
- // FIXME: GF_BEGIN position can differ from GID_BEGIN pos.
-
- SetCursorPos(gi.ptsLocation.x, gi.ptsLocation.y);
- return;
- } else if (gi.dwID == GID_END) {
- gestureActive = false;
- ignoringGesture = false;
- return;
- }
-
- // The GID_BEGIN msg means that no fingers were previously touching,
- // and a completely new set of gestures is beginning.
- // The GF_BEGIN flag means a new type of gesture was detected. This
- // flag can be set on a msg when changing between gestures within
- // one set of touches.
- //
- // We don't support dynamically changing between gestures
- // without lifting the finger(s).
- if ((gi.dwFlags & GF_BEGIN) && gestureActive)
- ignoringGesture = true;
- if (ignoringGesture)
- return;
-
- if (gi.dwFlags & GF_BEGIN) {
- gev.type = GestureBegin;
- } else if (gi.dwFlags & GF_END) {
- gev.type = GestureEnd;
- } else {
- gev.type = GestureUpdate;
- }
-
- // Convert to relative coordinates
- pos.x = gi.ptsLocation.x;
- pos.y = gi.ptsLocation.y;
- ScreenToClient(gi.hwndTarget, &pos);
- gev.eventX = pos.x;
- gev.eventY = pos.y;
-
- switch(gi.dwID) {
-
- case GID_ZOOM:
- gev.gesture = GesturePinch;
- if (gi.dwFlags & GF_BEGIN) {
- gestureStart.x = pos.x;
- gestureStart.y = pos.y;
- } else {
- gev.eventX = gestureStart.x;
- gev.eventY = gestureStart.y;
- }
- gev.magnitudeX = gi.ullArguments;
- gev.magnitudeY = 0;
- break;
-
- case GID_PAN:
- if (isSinglePan(gi)) {
- if (gi.dwFlags & GF_BEGIN) {
- gestureStart.x = pos.x;
- gestureStart.y = pos.y;
- startedSinglePan = false;
-
- }
-
- // FIXME: Add support for sending a OneFingerTap gesture here.
- // When the movement was very small and we get a GF_END
- // within a short time we should consider it a tap.
-
- if (!startedSinglePan &&
- ((unsigned)abs(gestureStart.x - pos.x) < SINGLE_PAN_THRESHOLD) &&
- ((unsigned)abs(gestureStart.y - pos.y) < SINGLE_PAN_THRESHOLD))
- return;
-
- // Here we know we got a single pan!
-
- // Change the first GestureUpdate to GestureBegin
- // after we passed the threshold
- if (!startedSinglePan) {
- startedSinglePan = true;
- gev.type = GestureBegin;
- gev.eventX = gestureStart.x;
- gev.eventY = gestureStart.y;
- }
-
- gev.gesture = GestureDrag;
-
- } else {
- if (gi.dwFlags & GF_BEGIN) {
- gestureStart.x = pos.x;
- gestureStart.y = pos.y;
- gev.magnitudeX = 0;
- gev.magnitudeY = 0;
- } else {
- gev.eventX = gestureStart.x;
- gev.eventY = gestureStart.y;
- gev.magnitudeX = pos.x - gestureStart.x;
- gev.magnitudeY = pos.y - gestureStart.y;
- }
-
- gev.gesture = GestureTwoDrag;
- }
- break;
-
- case GID_TWOFINGERTAP:
- gev.gesture = GestureTwoTap;
- break;
-
- }
-
- gestureActive = true;
-
- BaseTouchHandler::handleGestureEvent(gev);
-
- // Since we have a threshold for GestureDrag we need to generate
- // a second event right away with the current position
- if ((gev.type == GestureBegin) && (gev.gesture == GestureDrag)) {
- gev.type = GestureUpdate;
- gev.eventX = pos.x;
- gev.eventY = pos.y;
- BaseTouchHandler::handleGestureEvent(gev);
- }
-
- // FLTK tends to reset the cursor to the real position so we
- // need to make sure that we update that position
- if (gev.type == GestureEnd) {
- POINT expectedPos;
- POINT currentPos;
-
- expectedPos = lastFakeMotionPos;
- ClientToScreen(hWnd, &expectedPos);
- GetCursorPos(¤tPos);
-
- if ((expectedPos.x != currentPos.x) ||
- (expectedPos.y != currentPos.y))
- SetCursorPos(expectedPos.x, expectedPos.y);
- }
- }
-
- bool Win32TouchHandler::isSinglePan(GESTUREINFO gi)
- {
- // To differentiate between a single and a double pan we can look
- // at ullArguments. This shows the distance between the touch points,
- // but in the case of single pan, it seems to show the monitor's
- // origin value (this is not documented by microsoft). This origin
- // value seems to be relative to the screen's position in a multi
- // monitor setup. For example if the touch monitor is secondary and
- // positioned to the left of the primary, the origin is negative.
- //
- // To use this we need to get the monitor's origin value and check
- // if it is the same as ullArguments. If they match, we have a
- // single pan.
-
- POINT coordinates;
- HMONITOR monitorHandler;
- MONITORINFO mi;
- LONG lowestX;
-
- // Find the monitor with the touch event
- coordinates.x = gi.ptsLocation.x;
- coordinates.y = gi.ptsLocation.y;
- monitorHandler = MonitorFromPoint(coordinates,
- MONITOR_DEFAULTTOPRIMARY);
-
- // Find the monitor's origin
- ZeroMemory(&mi, sizeof(MONITORINFO));
- mi.cbSize = sizeof(MONITORINFO);
- GetMonitorInfo(monitorHandler, &mi);
- lowestX = mi.rcMonitor.left;
-
- return lowestX == (LONG)gi.ullArguments;
- }
-
- void Win32TouchHandler::fakeMotionEvent(const GestureEvent origEvent)
- {
- UINT Msg = WM_MOUSEMOVE;
- WPARAM wParam = MAKEWPARAM(fakeButtonMask, 0);
- LPARAM lParam = MAKELPARAM(origEvent.eventX, origEvent.eventY);
-
- pushFakeEvent(Msg, wParam, lParam);
- lastFakeMotionPos.x = origEvent.eventX;
- lastFakeMotionPos.y = origEvent.eventY;
- }
-
- void Win32TouchHandler::fakeButtonEvent(bool press, int button,
- const GestureEvent origEvent)
- {
- UINT Msg;
- WPARAM wParam;
- LPARAM lParam;
- int delta;
-
- switch (button) {
-
- case 1: // left mousebutton
- if (press) {
- Msg = WM_LBUTTONDOWN;
- fakeButtonMask |= MK_LBUTTON;
- } else {
- Msg = WM_LBUTTONUP;
- fakeButtonMask &= ~MK_LBUTTON;
- }
- break;
- case 2: // middle mousebutton
- if (press) {
- Msg = WM_MBUTTONDOWN;
- fakeButtonMask |= MK_MBUTTON;
- } else {
- Msg = WM_MBUTTONUP;
- fakeButtonMask &= ~MK_MBUTTON;
- }
- break;
- case 3: // right mousebutton
- if (press) {
- Msg = WM_RBUTTONDOWN;
- fakeButtonMask |= MK_RBUTTON;
- } else {
- Msg = WM_RBUTTONUP;
- fakeButtonMask &= ~MK_RBUTTON;
- }
- break;
-
- case 4: // scroll up
- Msg = WM_MOUSEWHEEL;
- delta = WHEEL_DELTA;
- break;
- case 5: // scroll down
- Msg = WM_MOUSEWHEEL;
- delta = -WHEEL_DELTA;
- break;
- case 6: // scroll left
- Msg = WM_MOUSEHWHEEL;
- delta = -WHEEL_DELTA;
- break;
- case 7: // scroll right
- Msg = WM_MOUSEHWHEEL;
- delta = WHEEL_DELTA;
- break;
-
- default:
- vlog.error(_("Invalid mouse button %d, must be a number between 1 and 7."),
- button);
- return;
- }
-
- if (1 <= button && button <= 3) {
- wParam = MAKEWPARAM(fakeButtonMask, 0);
-
- // Regular mouse events expect client coordinates
- lParam = MAKELPARAM(origEvent.eventX, origEvent.eventY);
- } else {
- POINT pos;
-
- // Only act on wheel press, not on release
- if (!press)
- return;
-
- wParam = MAKEWPARAM(fakeButtonMask, delta);
-
- // Wheel events require screen coordinates
- pos.x = (LONG)origEvent.eventX;
- pos.y = (LONG)origEvent.eventY;
-
- ClientToScreen(hWnd, &pos);
- lParam = MAKELPARAM(pos.x, pos.y);
- }
-
- pushFakeEvent(Msg, wParam, lParam);
- }
-
- void Win32TouchHandler::fakeKeyEvent(bool press, int keysym,
- const GestureEvent origEvent)
- {
- UINT Msg = press ? WM_KEYDOWN : WM_KEYUP;
- WPARAM wParam;
- LPARAM lParam;
- int vKey;
- int scanCode;
- int previousKeyState = press ? 0 : 1;
- int transitionState = press ? 0 : 1;
-
- switch(keysym) {
-
- case XK_Control_L:
- vKey = VK_CONTROL;
- scanCode = 0x1d;
- if (press)
- fakeButtonMask |= MK_CONTROL;
- else
- fakeButtonMask &= ~MK_CONTROL;
- break;
-
- // BaseTouchHandler will currently not send SHIFT but we keep it for
- // completeness sake. This way we have coverage for all wParam's MK_-bits.
- case XK_Shift_L:
- vKey = VK_SHIFT;
- scanCode = 0x2a;
- if (press)
- fakeButtonMask |= MK_SHIFT;
- else
- fakeButtonMask &= ~MK_SHIFT;
- break;
-
- default:
- //FIXME: consider adding generic handling
- vlog.error(_("Unhandled key 0x%x - can't generate keyboard event."),
- keysym);
- return;
- }
-
- wParam = MAKEWPARAM(vKey, 0);
-
- scanCode <<= 0;
- previousKeyState <<= 14;
- transitionState <<= 15;
- lParam = MAKELPARAM(1, // RepeatCount
- (scanCode | previousKeyState | transitionState));
-
- pushFakeEvent(Msg, wParam, lParam);
- }
-
- void Win32TouchHandler::pushFakeEvent(UINT Msg, WPARAM wParam, LPARAM lParam)
- {
- PostMessage(hWnd, Msg, wParam, lParam);
- }
|