1 /* Copyright 2020 Samuel Mannehed for Cendio AB
2  *
3  * This is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This software is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this software; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
16  * USA.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 
23 #include <math.h>
24 
25 #define XK_MISCELLANY
26 #include <rfb/keysymdef.h>
27 #include <rfb/Exception.h>
28 #include <rfb/LogWriter.h>
29 
30 #include "i18n.h"
31 #include "Win32TouchHandler.h"
32 
33 static rfb::LogWriter vlog("Win32TouchHandler");
34 
35 static const DWORD MOUSEMOVE_FLAGS = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE |
36                                      MOUSEEVENTF_VIRTUALDESK;
37 
38 static const unsigned SINGLE_PAN_THRESHOLD = 50;
39 
Win32TouchHandler(HWND hWnd)40 Win32TouchHandler::Win32TouchHandler(HWND hWnd) :
41   hWnd(hWnd), gesturesConfigured(false), gestureActive(false),
42   ignoringGesture(false), fakeButtonMask(0)
43 {
44   // If window is registered as touch we can not receive gestures,
45   // this should not happen
46   if (IsTouchWindow(hWnd, NULL))
47     throw rfb::Exception(_("Window is registered for touch instead of gestures"));
48 
49   // We will not receive any touch/gesture events if this service
50   // isn't running - Logging is enough
51   if (!GetSystemMetrics(SM_DIGITIZER))
52     vlog.debug("The 'Tablet PC Input' service is required for touch");
53 
54   // When we have less than two touch points we won't receive all
55   // gesture events - Logging is enough
56   int supportedTouches = GetSystemMetrics(SM_MAXIMUMTOUCHES);
57   if (supportedTouches < 2)
58     vlog.debug("Two touch points required, system currently supports: %d",
59                supportedTouches);
60 }
61 
processEvent(UINT Msg,WPARAM wParam,LPARAM lParam)62 bool Win32TouchHandler::processEvent(UINT Msg, WPARAM wParam, LPARAM lParam)
63 {
64   GESTUREINFO gi;
65 
66   DWORD panWant = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY   |
67                   GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY |
68                   GC_PAN;
69   DWORD panBlock = GC_PAN_WITH_INERTIA | GC_PAN_WITH_GUTTER;
70 
71   GESTURECONFIG gc[] = {{GID_ZOOM, GC_ZOOM, 0},
72                         {GID_PAN, panWant, panBlock},
73                         {GID_TWOFINGERTAP, GC_TWOFINGERTAP, 0}};
74 
75   switch(Msg) {
76   case WM_GESTURENOTIFY:
77     if (gesturesConfigured)
78         return false;
79 
80     if (!SetGestureConfig(hWnd, 0, 3, gc, sizeof(GESTURECONFIG))) {
81       vlog.error(_("Failed to set gesture configuration (error 0x%x)"),
82                  (int)GetLastError());
83     }
84     gesturesConfigured = true;
85     // Windows expects all handler functions to always
86     // pass this message on, and not consume it
87     return false;
88   case WM_GESTURE:
89     ZeroMemory(&gi, sizeof(GESTUREINFO));
90     gi.cbSize = sizeof(GESTUREINFO);
91 
92     if (!GetGestureInfo((HGESTUREINFO)lParam, &gi)) {
93       vlog.error(_("Failed to get gesture information (error 0x%x)"),
94                  (int)GetLastError());
95       return true;
96     }
97 
98     handleWin32GestureEvent(gi);
99 
100     CloseGestureInfoHandle((HGESTUREINFO)lParam);
101     return true;
102   }
103 
104   return false;
105 }
106 
handleWin32GestureEvent(GESTUREINFO gi)107 void Win32TouchHandler::handleWin32GestureEvent(GESTUREINFO gi)
108 {
109   GestureEvent gev;
110   POINT pos;
111 
112   if (gi.dwID == GID_BEGIN) {
113     // FLTK gets very confused if the cursor position is outside
114     // of the window when getting mouse events, so we start by
115     // moving the cursor to something proper.
116     // FIXME: Only do this when necessary?
117     // FIXME: There is some odd delay before Windows fully updates
118     //        the state of the cursor position. By doing it here in
119     //        GID_BEGIN we hope to do it early enough that we don't
120     //        get any odd effects.
121     // FIXME: GF_BEGIN position can differ from GID_BEGIN pos.
122 
123     SetCursorPos(gi.ptsLocation.x, gi.ptsLocation.y);
124     return;
125   } else if (gi.dwID == GID_END) {
126     gestureActive = false;
127     ignoringGesture = false;
128     return;
129   }
130 
131   // The GID_BEGIN msg means that no fingers were previously touching,
132   // and a completely new set of gestures is beginning.
133   // The GF_BEGIN flag means a new type of gesture was detected. This
134   // flag can be set on a msg when changing between gestures within
135   // one set of touches.
136   //
137   // We don't support dynamically changing between gestures
138   // without lifting the finger(s).
139   if ((gi.dwFlags & GF_BEGIN) && gestureActive)
140     ignoringGesture = true;
141   if (ignoringGesture)
142     return;
143 
144   if (gi.dwFlags & GF_BEGIN) {
145     gev.type = GestureBegin;
146   } else if (gi.dwFlags & GF_END) {
147     gev.type = GestureEnd;
148   } else {
149     gev.type = GestureUpdate;
150   }
151 
152   // Convert to relative coordinates
153   pos.x = gi.ptsLocation.x;
154   pos.y = gi.ptsLocation.y;
155   ScreenToClient(gi.hwndTarget, &pos);
156   gev.eventX = pos.x;
157   gev.eventY = pos.y;
158 
159   switch(gi.dwID) {
160 
161   case GID_ZOOM:
162     gev.gesture = GesturePinch;
163     if (gi.dwFlags & GF_BEGIN) {
164       gestureStart.x = pos.x;
165       gestureStart.y = pos.y;
166     } else {
167       gev.eventX = gestureStart.x;
168       gev.eventY = gestureStart.y;
169     }
170     gev.magnitudeX = gi.ullArguments;
171     gev.magnitudeY = 0;
172     break;
173 
174   case GID_PAN:
175     if (isSinglePan(gi)) {
176       if (gi.dwFlags & GF_BEGIN) {
177         gestureStart.x = pos.x;
178         gestureStart.y = pos.y;
179         startedSinglePan = false;
180 
181       }
182 
183       // FIXME: Add support for sending a OneFingerTap gesture here.
184       //        When the movement was very small and we get a GF_END
185       //        within a short time we should consider it a tap.
186 
187       if (!startedSinglePan &&
188           ((unsigned)abs(gestureStart.x - pos.x) < SINGLE_PAN_THRESHOLD) &&
189           ((unsigned)abs(gestureStart.y - pos.y) < SINGLE_PAN_THRESHOLD))
190          return;
191 
192       // Here we know we got a single pan!
193 
194       // Change the first GestureUpdate to GestureBegin
195       // after we passed the threshold
196       if (!startedSinglePan) {
197         startedSinglePan = true;
198         gev.type = GestureBegin;
199         gev.eventX = gestureStart.x;
200         gev.eventY = gestureStart.y;
201       }
202 
203       gev.gesture = GestureDrag;
204 
205     } else {
206       if (gi.dwFlags & GF_BEGIN) {
207         gestureStart.x = pos.x;
208         gestureStart.y = pos.y;
209         gev.magnitudeX = 0;
210         gev.magnitudeY = 0;
211       } else {
212         gev.eventX = gestureStart.x;
213         gev.eventY = gestureStart.y;
214         gev.magnitudeX = pos.x - gestureStart.x;
215         gev.magnitudeY = pos.y - gestureStart.y;
216       }
217 
218       gev.gesture = GestureTwoDrag;
219     }
220     break;
221 
222   case GID_TWOFINGERTAP:
223     gev.gesture = GestureTwoTap;
224     break;
225 
226   }
227 
228   gestureActive = true;
229 
230   BaseTouchHandler::handleGestureEvent(gev);
231 
232   // Since we have a threshold for GestureDrag we need to generate
233   // a second event right away with the current position
234   if ((gev.type == GestureBegin) && (gev.gesture == GestureDrag)) {
235     gev.type = GestureUpdate;
236     gev.eventX = pos.x;
237     gev.eventY = pos.y;
238     BaseTouchHandler::handleGestureEvent(gev);
239   }
240 
241   // FLTK tends to reset the cursor to the real position so we
242   // need to make sure that we update that position
243   if (gev.type == GestureEnd) {
244     POINT expectedPos;
245     POINT currentPos;
246 
247     expectedPos = lastFakeMotionPos;
248     ClientToScreen(hWnd, &expectedPos);
249     GetCursorPos(&currentPos);
250 
251     if ((expectedPos.x != currentPos.x) ||
252         (expectedPos.y != currentPos.y))
253       SetCursorPos(expectedPos.x, expectedPos.y);
254   }
255 }
256 
isSinglePan(GESTUREINFO gi)257 bool Win32TouchHandler::isSinglePan(GESTUREINFO gi)
258 {
259   // To differentiate between a single and a double pan we can look
260   // at ullArguments. This shows the distance between the touch points,
261   // but in the case of single pan, it seems to show the monitor's
262   // origin value (this is not documented by microsoft). This origin
263   // value seems to be relative to the screen's position in a multi
264   // monitor setup. For example if the touch monitor is secondary and
265   // positioned to the left of the primary, the origin is negative.
266   //
267   // To use this we need to get the monitor's origin value and check
268   // if it is the same as ullArguments. If they match, we have a
269   // single pan.
270 
271   POINT coordinates;
272   HMONITOR monitorHandler;
273   MONITORINFO mi;
274   LONG lowestX;
275 
276   // Find the monitor with the touch event
277   coordinates.x = gi.ptsLocation.x;
278   coordinates.y = gi.ptsLocation.y;
279   monitorHandler = MonitorFromPoint(coordinates,
280                                     MONITOR_DEFAULTTOPRIMARY);
281 
282   // Find the monitor's origin
283   ZeroMemory(&mi, sizeof(MONITORINFO));
284   mi.cbSize = sizeof(MONITORINFO);
285   GetMonitorInfo(monitorHandler, &mi);
286   lowestX = mi.rcMonitor.left;
287 
288   return lowestX == (LONG)gi.ullArguments;
289 }
290 
fakeMotionEvent(const GestureEvent origEvent)291 void Win32TouchHandler::fakeMotionEvent(const GestureEvent origEvent)
292 {
293   UINT Msg = WM_MOUSEMOVE;
294   WPARAM wParam = MAKEWPARAM(fakeButtonMask, 0);
295   LPARAM lParam = MAKELPARAM(origEvent.eventX, origEvent.eventY);
296 
297   pushFakeEvent(Msg, wParam, lParam);
298   lastFakeMotionPos.x = origEvent.eventX;
299   lastFakeMotionPos.y = origEvent.eventY;
300 }
301 
fakeButtonEvent(bool press,int button,const GestureEvent origEvent)302 void Win32TouchHandler::fakeButtonEvent(bool press, int button,
303                                         const GestureEvent origEvent)
304 {
305   UINT Msg;
306   WPARAM wParam;
307   LPARAM lParam;
308   int delta;
309 
310   switch (button) {
311 
312   case 1: // left mousebutton
313     if (press) {
314       Msg = WM_LBUTTONDOWN;
315       fakeButtonMask |= MK_LBUTTON;
316     } else {
317       Msg = WM_LBUTTONUP;
318       fakeButtonMask &= ~MK_LBUTTON;
319     }
320     break;
321   case 2: // middle mousebutton
322     if (press) {
323       Msg = WM_MBUTTONDOWN;
324       fakeButtonMask |= MK_MBUTTON;
325     } else {
326       Msg = WM_MBUTTONUP;
327       fakeButtonMask &= ~MK_MBUTTON;
328     }
329     break;
330   case 3: // right mousebutton
331     if (press) {
332       Msg = WM_RBUTTONDOWN;
333       fakeButtonMask |= MK_RBUTTON;
334     } else {
335       Msg = WM_RBUTTONUP;
336       fakeButtonMask &= ~MK_RBUTTON;
337     }
338     break;
339 
340   case 4: // scroll up
341     Msg = WM_MOUSEWHEEL;
342     delta = WHEEL_DELTA;
343     break;
344   case 5: // scroll down
345     Msg = WM_MOUSEWHEEL;
346     delta = -WHEEL_DELTA;
347     break;
348   case 6: // scroll left
349     Msg = WM_MOUSEHWHEEL;
350     delta = -WHEEL_DELTA;
351     break;
352   case 7: // scroll right
353     Msg = WM_MOUSEHWHEEL;
354     delta = WHEEL_DELTA;
355     break;
356 
357   default:
358     vlog.error(_("Invalid mouse button %d, must be a number between 1 and 7."),
359                button);
360     return;
361   }
362 
363   if (1 <= button && button <= 3) {
364     wParam = MAKEWPARAM(fakeButtonMask, 0);
365 
366     // Regular mouse events expect client coordinates
367     lParam = MAKELPARAM(origEvent.eventX, origEvent.eventY);
368   } else {
369     POINT pos;
370 
371     // Only act on wheel press, not on release
372     if (!press)
373       return;
374 
375     wParam = MAKEWPARAM(fakeButtonMask, delta);
376 
377     // Wheel events require screen coordinates
378     pos.x = (LONG)origEvent.eventX;
379     pos.y = (LONG)origEvent.eventY;
380 
381     ClientToScreen(hWnd, &pos);
382     lParam = MAKELPARAM(pos.x, pos.y);
383   }
384 
385   pushFakeEvent(Msg, wParam, lParam);
386 }
387 
fakeKeyEvent(bool press,int keysym,const GestureEvent origEvent)388 void Win32TouchHandler::fakeKeyEvent(bool press, int keysym,
389                                      const GestureEvent origEvent)
390 {
391   UINT Msg = press ? WM_KEYDOWN : WM_KEYUP;
392   WPARAM wParam;
393   LPARAM lParam;
394   int vKey;
395   int scanCode;
396   int previousKeyState = press ? 0 : 1;
397   int transitionState = press ? 0 : 1;
398 
399   switch(keysym) {
400 
401   case XK_Control_L:
402     vKey = VK_CONTROL;
403     scanCode = 0x1d;
404     if (press)
405       fakeButtonMask |= MK_CONTROL;
406     else
407       fakeButtonMask &= ~MK_CONTROL;
408     break;
409 
410   // BaseTouchHandler will currently not send SHIFT but we keep it for
411   // completeness sake. This way we have coverage for all wParam's MK_-bits.
412   case XK_Shift_L:
413     vKey = VK_SHIFT;
414     scanCode = 0x2a;
415     if (press)
416       fakeButtonMask |= MK_SHIFT;
417     else
418       fakeButtonMask &= ~MK_SHIFT;
419     break;
420 
421   default:
422     //FIXME: consider adding generic handling
423     vlog.error(_("Unhandled key 0x%x - can't generate keyboard event."),
424                keysym);
425     return;
426   }
427 
428   wParam = MAKEWPARAM(vKey, 0);
429 
430   scanCode         <<= 0;
431   previousKeyState <<= 14;
432   transitionState  <<= 15;
433   lParam = MAKELPARAM(1, // RepeatCount
434                       (scanCode | previousKeyState | transitionState));
435 
436   pushFakeEvent(Msg, wParam, lParam);
437 }
438 
pushFakeEvent(UINT Msg,WPARAM wParam,LPARAM lParam)439 void Win32TouchHandler::pushFakeEvent(UINT Msg, WPARAM wParam, LPARAM lParam)
440 {
441   PostMessage(hWnd, Msg, wParam, lParam);
442 }
443