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

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