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.

GestureHandler.cxx 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. /* Copyright 2019 Aaron Sowry for Cendio AB
  2. * Copyright 2020 Pierre Ossman for Cendio AB
  3. *
  4. * This is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This software is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this software; if not, write to the Free Software
  16. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
  17. * USA.
  18. */
  19. #ifdef HAVE_CONFIG_H
  20. #include <config.h>
  21. #endif
  22. #include <assert.h>
  23. #include <math.h>
  24. #include <rfb/util.h>
  25. #include <rfb/LogWriter.h>
  26. #include "GestureHandler.h"
  27. static rfb::LogWriter vlog("GestureHandler");
  28. static const unsigned char GH_NOGESTURE = 0;
  29. static const unsigned char GH_ONETAP = 1;
  30. static const unsigned char GH_TWOTAP = 2;
  31. static const unsigned char GH_THREETAP = 4;
  32. static const unsigned char GH_DRAG = 8;
  33. static const unsigned char GH_LONGPRESS = 16;
  34. static const unsigned char GH_TWODRAG = 32;
  35. static const unsigned char GH_PINCH = 64;
  36. static const unsigned char GH_INITSTATE = 127;
  37. const unsigned GH_MOVE_THRESHOLD = 50;
  38. const unsigned GH_ANGLE_THRESHOLD = 90; // Degrees
  39. // Timeout when waiting for gestures (ms)
  40. const unsigned GH_MULTITOUCH_TIMEOUT = 250;
  41. // Maximum time between press and release for a tap (ms)
  42. const unsigned GH_TAP_TIMEOUT = 1000;
  43. // Timeout when waiting for longpress (ms)
  44. const unsigned GH_LONGPRESS_TIMEOUT = 1000;
  45. // Timeout when waiting to decide between PINCH and TWODRAG (ms)
  46. const unsigned GH_TWOTOUCH_TIMEOUT = 50;
  47. GestureHandler::GestureHandler() :
  48. state(GH_INITSTATE), waitingRelease(false),
  49. longpressTimer(this), twoTouchTimer(this)
  50. {
  51. }
  52. GestureHandler::~GestureHandler()
  53. {
  54. }
  55. void GestureHandler::handleTouchBegin(int id, double x, double y)
  56. {
  57. GHTouch ght;
  58. // Ignore any new touches if there is already an active gesture,
  59. // or we're in a cleanup state
  60. if (hasDetectedGesture() || (state == GH_NOGESTURE)) {
  61. ignored.insert(id);
  62. return;
  63. }
  64. // Did it take too long between touches that we should no longer
  65. // consider this a single gesture?
  66. if ((tracked.size() > 0) &&
  67. (rfb::msSince(&tracked.begin()->second.started) > GH_MULTITOUCH_TIMEOUT)) {
  68. state = GH_NOGESTURE;
  69. ignored.insert(id);
  70. return;
  71. }
  72. // If we're waiting for fingers to release then we should no longer
  73. // recognize new touches
  74. if (waitingRelease) {
  75. state = GH_NOGESTURE;
  76. ignored.insert(id);
  77. return;
  78. }
  79. gettimeofday(&ght.started, NULL);
  80. ght.active = true;
  81. ght.lastX = ght.firstX = x;
  82. ght.lastY = ght.firstY = y;
  83. ght.angle = 0;
  84. tracked[id] = ght;
  85. switch (tracked.size()) {
  86. case 1:
  87. longpressTimer.start(GH_LONGPRESS_TIMEOUT);
  88. break;
  89. case 2:
  90. state &= ~(GH_ONETAP | GH_DRAG | GH_LONGPRESS);
  91. longpressTimer.stop();
  92. break;
  93. case 3:
  94. state &= ~(GH_TWOTAP | GH_TWODRAG | GH_PINCH);
  95. break;
  96. default:
  97. state = GH_NOGESTURE;
  98. }
  99. }
  100. void GestureHandler::handleTouchUpdate(int id, double x, double y)
  101. {
  102. GHTouch *touch, *prevTouch;
  103. double deltaX, deltaY, prevDeltaMove;
  104. unsigned deltaAngle;
  105. // If this is an update for a touch we're not tracking, ignore it
  106. if (tracked.count(id) == 0)
  107. return;
  108. touch = &tracked[id];
  109. // Update the touches last position with the event coordinates
  110. touch->lastX = x;
  111. touch->lastY = y;
  112. deltaX = x - touch->firstX;
  113. deltaY = y - touch->firstY;
  114. // Update angle when the touch has moved
  115. if ((touch->firstX != touch->lastX) ||
  116. (touch->firstY != touch->lastY))
  117. touch->angle = atan2(deltaY, deltaX) * 180 / M_PI;
  118. if (!hasDetectedGesture()) {
  119. // Ignore moves smaller than the minimum threshold
  120. if (hypot(deltaX, deltaY) < GH_MOVE_THRESHOLD)
  121. return;
  122. // Can't be a tap or long press as we've seen movement
  123. state &= ~(GH_ONETAP | GH_TWOTAP | GH_THREETAP | GH_LONGPRESS);
  124. longpressTimer.stop();
  125. if (tracked.size() != 1)
  126. state &= ~(GH_DRAG);
  127. if (tracked.size() != 2)
  128. state &= ~(GH_TWODRAG | GH_PINCH);
  129. // We need to figure out which of our different two touch gestures
  130. // this might be
  131. if (tracked.size() == 2) {
  132. // The other touch can be first or last in tracked
  133. // depending on which event came first
  134. prevTouch = &tracked.rbegin()->second;
  135. if (prevTouch == touch)
  136. prevTouch = &tracked.begin()->second;
  137. // How far the previous touch point has moved since start
  138. prevDeltaMove = hypot(prevTouch->firstX - prevTouch->lastX,
  139. prevTouch->firstY - prevTouch->lastY);
  140. // We know that the current touch moved far enough,
  141. // but unless both touches moved further than their
  142. // threshold we don't want to disqualify any gestures
  143. if (prevDeltaMove > GH_MOVE_THRESHOLD) {
  144. // The angle difference between the direction of the touch points
  145. deltaAngle = fabs(touch->angle - prevTouch->angle);
  146. deltaAngle = fabs(((deltaAngle + 180) % 360) - 180);
  147. // PINCH or TWODRAG can be eliminated depending on the angle
  148. if (deltaAngle > GH_ANGLE_THRESHOLD)
  149. state &= ~GH_TWODRAG;
  150. else
  151. state &= ~GH_PINCH;
  152. if (twoTouchTimer.isStarted())
  153. twoTouchTimer.stop();
  154. } else if (!twoTouchTimer.isStarted()) {
  155. // We can't determine the gesture right now, let's
  156. // wait and see if more events are on their way
  157. twoTouchTimer.start(GH_TWOTOUCH_TIMEOUT);
  158. }
  159. }
  160. if (!hasDetectedGesture())
  161. return;
  162. pushEvent(GestureBegin);
  163. }
  164. pushEvent(GestureUpdate);
  165. }
  166. void GestureHandler::handleTouchEnd(int id)
  167. {
  168. std::map<int, GHTouch>::const_iterator iter;
  169. // Check if this is an ignored touch
  170. if (ignored.count(id)) {
  171. ignored.erase(id);
  172. if (ignored.empty() && tracked.empty()) {
  173. state = GH_INITSTATE;
  174. waitingRelease = false;
  175. }
  176. return;
  177. }
  178. // We got a TouchEnd before the timer triggered,
  179. // this cannot result in a gesture anymore.
  180. if (!hasDetectedGesture() && twoTouchTimer.isStarted()) {
  181. twoTouchTimer.stop();
  182. state = GH_NOGESTURE;
  183. }
  184. // Some gestures don't trigger until a touch is released
  185. if (!hasDetectedGesture()) {
  186. // Can't be a gesture that relies on movement
  187. state &= ~(GH_DRAG | GH_TWODRAG | GH_PINCH);
  188. // Or something that relies on more time
  189. state &= ~GH_LONGPRESS;
  190. longpressTimer.stop();
  191. if (!waitingRelease) {
  192. gettimeofday(&releaseStart, NULL);
  193. waitingRelease = true;
  194. // Can't be a tap that requires more touches than we current have
  195. switch (tracked.size()) {
  196. case 1:
  197. state &= ~(GH_TWOTAP | GH_THREETAP);
  198. break;
  199. case 2:
  200. state &= ~(GH_ONETAP | GH_THREETAP);
  201. break;
  202. }
  203. }
  204. }
  205. // Waiting for all touches to release? (i.e. some tap)
  206. if (waitingRelease) {
  207. // Were all touches released at roughly the same time?
  208. if (rfb::msSince(&releaseStart) > GH_MULTITOUCH_TIMEOUT)
  209. state = GH_NOGESTURE;
  210. // Did too long time pass between press and release?
  211. for (iter = tracked.begin(); iter != tracked.end(); ++iter) {
  212. if (rfb::msSince(&iter->second.started) > GH_TAP_TIMEOUT) {
  213. state = GH_NOGESTURE;
  214. break;
  215. }
  216. }
  217. tracked[id].active = false;
  218. // Are we still waiting for more releases?
  219. if (hasDetectedGesture()) {
  220. pushEvent(GestureBegin);
  221. } else {
  222. // Have we reached a dead end?
  223. if (state != GH_NOGESTURE)
  224. return;
  225. }
  226. }
  227. if (hasDetectedGesture())
  228. pushEvent(GestureEnd);
  229. // Ignore any remaining touches until they are ended
  230. for (iter = tracked.begin(); iter != tracked.end(); ++iter) {
  231. if (iter->second.active)
  232. ignored.insert(iter->first);
  233. }
  234. tracked.clear();
  235. state = GH_NOGESTURE;
  236. ignored.erase(id);
  237. if (ignored.empty()) {
  238. state = GH_INITSTATE;
  239. waitingRelease = false;
  240. }
  241. }
  242. bool GestureHandler::hasDetectedGesture()
  243. {
  244. if (state == GH_NOGESTURE)
  245. return false;
  246. // Check to see if the bitmask value is a power of 2
  247. // (i.e. only one bit set). If it is, we have a state.
  248. if (state & (state - 1))
  249. return false;
  250. // For taps we also need to have all touches released
  251. // before we've fully detected the gesture
  252. if (state & (GH_ONETAP | GH_TWOTAP | GH_THREETAP)) {
  253. std::map<int, GHTouch>::const_iterator iter;
  254. // Any touch still active/pressed?
  255. for (iter = tracked.begin(); iter != tracked.end(); ++iter) {
  256. if (iter->second.active)
  257. return false;
  258. }
  259. }
  260. return true;
  261. }
  262. bool GestureHandler::handleTimeout(rfb::Timer* t)
  263. {
  264. if (t == &longpressTimer)
  265. longpressTimeout();
  266. else if (t == &twoTouchTimer)
  267. twoTouchTimeout();
  268. return false;
  269. }
  270. void GestureHandler::longpressTimeout()
  271. {
  272. assert(!hasDetectedGesture());
  273. state = GH_LONGPRESS;
  274. pushEvent(GestureBegin);
  275. }
  276. void GestureHandler::twoTouchTimeout()
  277. {
  278. double avgMoveH, avgMoveV, fdx, fdy, ldx, ldy, deltaTouchDistance;
  279. assert(!tracked.empty());
  280. // How far each touch point has moved since start
  281. getAverageMovement(&avgMoveH, &avgMoveV);
  282. avgMoveH = fabs(avgMoveH);
  283. avgMoveV = fabs(avgMoveV);
  284. // The difference in the distance between where
  285. // the touch points started and where they are now
  286. getAverageDistance(&fdx, &fdy, &ldx, &ldy);
  287. deltaTouchDistance = fabs(hypot(fdx, fdy) - hypot(ldx, ldy));
  288. if ((avgMoveV < deltaTouchDistance) &&
  289. (avgMoveH < deltaTouchDistance))
  290. state = GH_PINCH;
  291. else
  292. state = GH_TWODRAG;
  293. pushEvent(GestureBegin);
  294. pushEvent(GestureUpdate);
  295. }
  296. void GestureHandler::pushEvent(GestureEventType t)
  297. {
  298. GestureEvent gev;
  299. double avgX, avgY;
  300. gev.type = t;
  301. gev.gesture = stateToGesture(state);
  302. // For most gesture events the current (average) position is the
  303. // most useful
  304. getPosition(NULL, NULL, &avgX, &avgY);
  305. // However we have a slight distance to detect gestures, so for the
  306. // first gesture event we want to use the first positions we saw
  307. if (t == GestureBegin)
  308. getPosition(&avgX, &avgY, NULL, NULL);
  309. // For these gestures, we always want the event coordinates
  310. // to be where the gesture began, not the current touch location.
  311. switch (state) {
  312. case GH_TWODRAG:
  313. case GH_PINCH:
  314. getPosition(&avgX, &avgY, NULL, NULL);
  315. break;
  316. }
  317. gev.eventX = avgX;
  318. gev.eventY = avgY;
  319. // Some gestures also have a magnitude
  320. if (state == GH_PINCH) {
  321. if (t == GestureBegin)
  322. getAverageDistance(&gev.magnitudeX, &gev.magnitudeY,
  323. NULL, NULL);
  324. else
  325. getAverageDistance(NULL, NULL,
  326. &gev.magnitudeX, &gev.magnitudeY);
  327. } else if (state == GH_TWODRAG) {
  328. if (t == GestureBegin)
  329. gev.magnitudeX = gev.magnitudeY = 0;
  330. else
  331. getAverageMovement(&gev.magnitudeX, &gev.magnitudeY);
  332. }
  333. handleGestureEvent(gev);
  334. }
  335. GestureEventGesture GestureHandler::stateToGesture(unsigned char state)
  336. {
  337. switch (state) {
  338. case GH_ONETAP:
  339. return GestureOneTap;
  340. case GH_TWOTAP:
  341. return GestureTwoTap;
  342. case GH_THREETAP:
  343. return GestureThreeTap;
  344. case GH_DRAG:
  345. return GestureDrag;
  346. case GH_LONGPRESS:
  347. return GestureLongPress;
  348. case GH_TWODRAG:
  349. return GestureTwoDrag;
  350. case GH_PINCH:
  351. return GesturePinch;
  352. }
  353. assert(false);
  354. return (GestureEventGesture)0;
  355. }
  356. void GestureHandler::getPosition(double *firstX, double *firstY,
  357. double *lastX, double *lastY)
  358. {
  359. size_t size;
  360. double fx = 0, fy = 0, lx = 0, ly = 0;
  361. assert(!tracked.empty());
  362. size = tracked.size();
  363. std::map<int, GHTouch>::const_iterator iter;
  364. for (iter = tracked.begin(); iter != tracked.end(); ++iter) {
  365. fx += iter->second.firstX;
  366. fy += iter->second.firstY;
  367. lx += iter->second.lastX;
  368. ly += iter->second.lastY;
  369. }
  370. if (firstX)
  371. *firstX = fx / size;
  372. if (firstY)
  373. *firstY = fy / size;
  374. if (lastX)
  375. *lastX = lx / size;
  376. if (lastY)
  377. *lastY = ly / size;
  378. }
  379. void GestureHandler::getAverageMovement(double *h, double *v)
  380. {
  381. double totalH, totalV;
  382. size_t size;
  383. assert(!tracked.empty());
  384. totalH = totalV = 0;
  385. size = tracked.size();
  386. std::map<int, GHTouch>::const_iterator iter;
  387. for (iter = tracked.begin(); iter != tracked.end(); ++iter) {
  388. totalH += iter->second.lastX - iter->second.firstX;
  389. totalV += iter->second.lastY - iter->second.firstY;
  390. }
  391. if (h)
  392. *h = totalH / size;
  393. if (v)
  394. *v = totalV / size;
  395. }
  396. void GestureHandler::getAverageDistance(double *firstX, double *firstY,
  397. double *lastX, double *lastY)
  398. {
  399. double dx, dy;
  400. assert(!tracked.empty());
  401. // Distance between the first and last tracked touches
  402. dx = fabs(tracked.rbegin()->second.firstX - tracked.begin()->second.firstX);
  403. dy = fabs(tracked.rbegin()->second.firstY - tracked.begin()->second.firstY);
  404. if (firstX)
  405. *firstX = dx;
  406. if (firstY)
  407. *firstY = dy;
  408. dx = fabs(tracked.rbegin()->second.lastX - tracked.begin()->second.lastX);
  409. dy = fabs(tracked.rbegin()->second.lastY - tracked.begin()->second.lastY);
  410. if (lastX)
  411. *lastX = dx;
  412. if (lastY)
  413. *lastY = dy;
  414. }