You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Win32TouchHandler.cxx 12KB

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