VMDisplayMetalViewController+Touch.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. //
  2. // Copyright © 2020 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #import "VMDisplayMetalViewController.h"
  17. #import "VMDisplayMetalViewController+Private.h"
  18. #import "VMDisplayMetalViewController+Touch.h"
  19. #if !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
  20. #import "VMDisplayMetalViewController+Pencil.h"
  21. #endif
  22. #import "VMCursor.h"
  23. #import "VMScroll.h"
  24. #import "CSDisplay.h"
  25. #import "UTMSpiceIO.h"
  26. #import "UTMLogging.h"
  27. #import "UTM-Swift.h"
  28. const CGFloat kScrollSpeedReduction = 100.0f;
  29. const CGFloat kCursorResistance = 50.0f;
  30. const CGFloat kScrollResistance = 10.0f;
  31. @implementation VMDisplayMetalViewController (Gestures)
  32. - (void)initTouch {
  33. // mouse cursor
  34. self.cursor = [[VMCursor alloc] initWithVMViewController:self];
  35. self.scroll = [[VMScroll alloc] initWithVMViewController:self];
  36. #if defined(TARGET_OS_VISION) && TARGET_OS_VISION
  37. // we only support pan and tap on visionOS
  38. self.pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(gesturePan:)];
  39. self.pan.minimumNumberOfTouches = 1;
  40. self.pan.maximumNumberOfTouches = 1;
  41. self.pan.delegate = self;
  42. self.pan.cancelsTouchesInView = NO;
  43. self.tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(gestureTap:)];
  44. self.tap.delegate = self;
  45. self.tap.allowedTouchTypes = @[ @(UITouchTypeDirect) ];
  46. self.tap.cancelsTouchesInView = NO;
  47. [self.mtkView addGestureRecognizer:self.pan];
  48. [self.mtkView addGestureRecognizer:self.tap];
  49. #else
  50. // Set up gesture recognizers because Storyboards is BROKEN and doing it there crashes!
  51. self.swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(gestureSwipeUp:)];
  52. self.swipeUp.numberOfTouchesRequired = 3;
  53. self.swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
  54. self.swipeUp.delegate = self;
  55. self.swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(gestureSwipeDown:)];
  56. self.swipeDown.numberOfTouchesRequired = 3;
  57. self.swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
  58. self.swipeDown.delegate = self;
  59. self.swipeScrollUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(gestureSwipeScroll:)];
  60. self.swipeScrollUp.numberOfTouchesRequired = 2;
  61. self.swipeScrollUp.direction = UISwipeGestureRecognizerDirectionUp;
  62. self.swipeScrollUp.delegate = self;
  63. self.swipeScrollDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(gestureSwipeScroll:)];
  64. self.swipeScrollDown.numberOfTouchesRequired = 2;
  65. self.swipeScrollDown.direction = UISwipeGestureRecognizerDirectionDown;
  66. self.swipeScrollDown.delegate = self;
  67. self.pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(gesturePan:)];
  68. self.pan.minimumNumberOfTouches = 1;
  69. self.pan.maximumNumberOfTouches = 1;
  70. self.pan.delegate = self;
  71. self.pan.cancelsTouchesInView = NO;
  72. self.twoPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(gestureTwoPan:)];
  73. self.twoPan.minimumNumberOfTouches = 2;
  74. self.twoPan.maximumNumberOfTouches = 2;
  75. self.twoPan.delegate = self;
  76. self.threePan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(gestureThreePan:)];
  77. self.threePan.minimumNumberOfTouches = 3;
  78. self.threePan.maximumNumberOfTouches = 3;
  79. self.threePan.delegate = self;
  80. self.tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(gestureTap:)];
  81. self.tap.delegate = self;
  82. self.tap.allowedTouchTypes = @[ @(UITouchTypeDirect) ];
  83. self.tap.cancelsTouchesInView = NO;
  84. self.twoTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(gestureTwoTap:)];
  85. self.twoTap.numberOfTouchesRequired = 2;
  86. self.twoTap.delegate = self;
  87. self.twoTap.allowedTouchTypes = @[ @(UITouchTypeDirect) ];
  88. self.longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(gestureLongPress:)];
  89. self.longPress.delegate = self;
  90. self.longPress.allowedTouchTypes = @[ @(UITouchTypeDirect) ];
  91. self.pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(gesturePinch:)];
  92. self.pinch.delegate = self;
  93. [self.mtkView addGestureRecognizer:self.swipeUp];
  94. [self.mtkView addGestureRecognizer:self.swipeDown];
  95. [self.mtkView addGestureRecognizer:self.swipeScrollUp];
  96. [self.mtkView addGestureRecognizer:self.swipeScrollDown];
  97. [self.mtkView addGestureRecognizer:self.pan];
  98. [self.mtkView addGestureRecognizer:self.twoPan];
  99. [self.mtkView addGestureRecognizer:self.threePan];
  100. [self.mtkView addGestureRecognizer:self.tap];
  101. [self.mtkView addGestureRecognizer:self.twoTap];
  102. [self.mtkView addGestureRecognizer:self.longPress];
  103. [self.mtkView addGestureRecognizer:self.pinch];
  104. // Feedback generator for clicks
  105. self.clickFeedbackGenerator = [[UISelectionFeedbackGenerator alloc] init];
  106. #endif
  107. }
  108. #pragma mark - Properties from instance
  109. - (CSInputButton)mouseButtonDown {
  110. CSInputButton button = kCSInputButtonNone;
  111. if (self.mouseLeftDown) {
  112. button |= kCSInputButtonLeft;
  113. }
  114. if (self.mouseRightDown) {
  115. button |= kCSInputButtonRight;
  116. }
  117. if (self.mouseMiddleDown) {
  118. button |= kCSInputButtonMiddle;
  119. }
  120. if (self.mouseSideDown) {
  121. button |= kCSInputButtonSide;
  122. }
  123. if (self.mouseExtraDown) {
  124. button |= kCSInputButtonExtra;
  125. }
  126. return button;
  127. }
  128. #pragma mark - Properties from settings
  129. - (BOOL)isInvertScroll {
  130. return [self boolForSetting:@"InvertScroll"];
  131. }
  132. - (VMGestureType)gestureTypeForSetting:(NSString *)key {
  133. NSInteger integer = [self integerForSetting:key];
  134. if (integer < VMGestureTypeNone || integer >= VMGestureTypeMax) {
  135. return VMGestureTypeNone;
  136. } else {
  137. return (VMGestureType)integer;
  138. }
  139. }
  140. - (VMGestureType)longPressType {
  141. return [self gestureTypeForSetting:@"GestureLongPress"];
  142. }
  143. - (VMGestureType)twoFingerTapType {
  144. return [self gestureTypeForSetting:@"GestureTwoTap"];
  145. }
  146. - (VMGestureType)twoFingerPanType {
  147. return [self gestureTypeForSetting:@"GestureTwoPan"];
  148. }
  149. - (VMGestureType)twoFingerScrollType {
  150. return [self gestureTypeForSetting:@"GestureTwoScroll"];
  151. }
  152. - (VMGestureType)threeFingerPanType {
  153. return [self gestureTypeForSetting:@"GestureThreePan"];
  154. }
  155. - (VMMouseType)mouseTypeForSetting:(NSString *)key {
  156. NSInteger integer = [self integerForSetting:key];
  157. if (integer < VMMouseTypeRelative || integer >= VMMouseTypeMax) {
  158. return VMMouseTypeRelative;
  159. } else {
  160. return (VMMouseType)integer;
  161. }
  162. }
  163. - (VMMouseType)touchMouseType {
  164. return [self mouseTypeForSetting:@"MouseTouchType"];
  165. }
  166. - (VMMouseType)pencilMouseType {
  167. return [self mouseTypeForSetting:@"MousePencilType"];
  168. }
  169. - (VMMouseType)indirectMouseType {
  170. #if TARGET_OS_VISION
  171. return VMMouseTypeAbsolute;
  172. #else
  173. if (@available(iOS 14.0, *)) {
  174. return VMMouseTypeRelative;
  175. } else {
  176. return VMMouseTypeAbsolute; // legacy iOS 13.4 mouse handling requires absolute
  177. }
  178. #endif
  179. }
  180. #pragma mark - Converting view points to VM display points
  181. static CGRect CGRectClipToBounds(CGRect rect1, CGRect rect2) {
  182. if (rect2.origin.x < rect1.origin.x) {
  183. rect2.origin.x = rect1.origin.x;
  184. } else if (rect2.origin.x + rect2.size.width > rect1.origin.x + rect1.size.width) {
  185. rect2.origin.x = rect1.origin.x + rect1.size.width - rect2.size.width;
  186. }
  187. if (rect2.origin.y < rect1.origin.y) {
  188. rect2.origin.y = rect1.origin.y;
  189. } else if (rect2.origin.y + rect2.size.height > rect1.origin.y + rect1.size.height) {
  190. rect2.origin.y = rect1.origin.y + rect1.size.height - rect2.size.height;
  191. }
  192. return rect2;
  193. }
  194. - (CGPoint)clipCursorToDisplay:(CGPoint)pos {
  195. CGSize screenSize = self.mtkView.drawableSize;
  196. CGSize scaledSize = {
  197. self.vmDisplay.displaySize.width * self.vmDisplay.viewportScale,
  198. self.vmDisplay.displaySize.height * self.vmDisplay.viewportScale
  199. };
  200. CGRect drawRect = CGRectMake(
  201. self.vmDisplay.viewportOrigin.x + screenSize.width/2 - scaledSize.width/2,
  202. self.vmDisplay.viewportOrigin.y + screenSize.height/2 - scaledSize.height/2,
  203. scaledSize.width,
  204. scaledSize.height
  205. );
  206. pos.x -= drawRect.origin.x;
  207. pos.y -= drawRect.origin.y;
  208. if (pos.x < 0) {
  209. pos.x = 0;
  210. } else if (pos.x > scaledSize.width) {
  211. pos.x = scaledSize.width;
  212. }
  213. if (pos.y < 0) {
  214. pos.y = 0;
  215. } else if (pos.y > scaledSize.height) {
  216. pos.y = scaledSize.height;
  217. }
  218. pos.x /= self.vmDisplay.viewportScale;
  219. pos.y /= self.vmDisplay.viewportScale;
  220. return pos;
  221. }
  222. - (CGPoint)clipDisplayToView:(CGPoint)target {
  223. CGSize screenSize = self.mtkView.drawableSize;
  224. CGSize scaledSize = {
  225. self.vmDisplay.displaySize.width * self.vmDisplay.viewportScale,
  226. self.vmDisplay.displaySize.height * self.vmDisplay.viewportScale
  227. };
  228. CGRect drawRect = CGRectMake(
  229. target.x + screenSize.width/2 - scaledSize.width/2,
  230. target.y + screenSize.height/2 - scaledSize.height/2,
  231. scaledSize.width,
  232. scaledSize.height
  233. );
  234. CGRect boundRect = {
  235. {
  236. screenSize.width - MAX(screenSize.width, scaledSize.width),
  237. screenSize.height - MAX(screenSize.height, scaledSize.height)
  238. },
  239. {
  240. 2*MAX(screenSize.width, scaledSize.width) - screenSize.width,
  241. 2*MAX(screenSize.height, scaledSize.height) - screenSize.height
  242. }
  243. };
  244. CGRect clippedRect = CGRectClipToBounds(boundRect, drawRect);
  245. clippedRect.origin.x -= (screenSize.width/2 - scaledSize.width/2);
  246. clippedRect.origin.y -= (screenSize.height/2 - scaledSize.height/2);
  247. return CGPointMake(clippedRect.origin.x, clippedRect.origin.y);
  248. }
  249. #pragma mark - Gestures
  250. - (void)moveMouseWithInertia:(UIPanGestureRecognizer *)sender {
  251. CGPoint location = [sender locationInView:sender.view];
  252. CGPoint velocity = [sender velocityInView:sender.view];
  253. if (sender.state == UIGestureRecognizerStateBegan) {
  254. [self.cursor startMovement:location];
  255. }
  256. if (sender.state != UIGestureRecognizerStateCancelled) {
  257. [self.cursor updateMovement:location];
  258. }
  259. if (sender.state == UIGestureRecognizerStateEnded) {
  260. [self.cursor endMovementWithVelocity:velocity resistance:kCursorResistance];
  261. }
  262. }
  263. - (void)scrollWithInertia:(UIPanGestureRecognizer *)sender {
  264. CGPoint location = [sender locationInView:sender.view];
  265. CGPoint velocity = [sender velocityInView:sender.view];
  266. if (sender.state == UIGestureRecognizerStateBegan) {
  267. [self.scroll startMovement:location];
  268. }
  269. if (sender.state != UIGestureRecognizerStateCancelled) {
  270. [self.scroll updateMovement:location];
  271. }
  272. if (sender.state == UIGestureRecognizerStateEnded) {
  273. [self.scroll endMovementWithVelocity:velocity resistance:kScrollResistance];
  274. }
  275. }
  276. - (IBAction)gesturePan:(UIPanGestureRecognizer *)sender {
  277. if (self.serverModeCursor) { // otherwise we handle in touchesMoved
  278. [self moveMouseWithInertia:sender];
  279. }
  280. }
  281. - (void)moveScreen:(UIPanGestureRecognizer *)sender {
  282. if (sender.state == UIGestureRecognizerStateBegan) {
  283. self.lastTwoPanOrigin = self.vmDisplay.viewportOrigin;
  284. }
  285. if (sender.state != UIGestureRecognizerStateCancelled) {
  286. CGPoint translation = [sender translationInView:sender.view];
  287. CGPoint viewport = self.vmDisplay.viewportOrigin;
  288. viewport.x = CGPointToPixel(translation.x) + self.lastTwoPanOrigin.x;
  289. viewport.y = CGPointToPixel(translation.y) + self.lastTwoPanOrigin.y;
  290. self.vmDisplay.viewportOrigin = [self clipDisplayToView:viewport];
  291. // persist this change in viewState
  292. self.delegate.displayOrigin = self.vmDisplay.viewportOrigin;
  293. }
  294. if (sender.state == UIGestureRecognizerStateEnded) {
  295. // TODO: decelerate
  296. }
  297. }
  298. - (IBAction)gestureTwoPan:(UIPanGestureRecognizer *)sender {
  299. switch (self.twoFingerPanType) {
  300. case VMGestureTypeMoveScreen:
  301. [self moveScreen:sender];
  302. break;
  303. case VMGestureTypeDragCursor:
  304. [self dragCursor:sender.state primary:YES secondary:NO middle:NO];
  305. [self moveMouseWithInertia:sender];
  306. break;
  307. case VMGestureTypeMouseWheel:
  308. [self scrollWithInertia:sender];
  309. break;
  310. default:
  311. break;
  312. }
  313. }
  314. - (IBAction)gestureThreePan:(UIPanGestureRecognizer *)sender {
  315. switch (self.threeFingerPanType) {
  316. case VMGestureTypeMoveScreen:
  317. [self moveScreen:sender];
  318. break;
  319. case VMGestureTypeDragCursor:
  320. [self dragCursor:sender.state primary:YES secondary:NO middle:NO];
  321. [self moveMouseWithInertia:sender];
  322. break;
  323. case VMGestureTypeMouseWheel:
  324. [self scrollWithInertia:sender];
  325. break;
  326. default:
  327. break;
  328. }
  329. }
  330. - (CGPoint)moveMouseAbsolute:(CGPoint)location {
  331. CGPoint translated = location;
  332. translated.x = CGPointToPixel(translated.x);
  333. translated.y = CGPointToPixel(translated.y);
  334. translated = [self clipCursorToDisplay:translated];
  335. if (!self.vmInput.serverModeCursor) {
  336. [self.vmInput sendMousePosition:self.mouseButtonDown absolutePoint:translated];
  337. [self.vmDisplay.cursor moveTo:translated]; // required to show cursor on screen
  338. } else {
  339. UTMLog(@"Warning: ignored mouse set (%f, %f) while mouse is in server mode", translated.x, translated.y);
  340. }
  341. return translated;
  342. }
  343. - (CGPoint)moveMouseRelative:(CGPoint)translation {
  344. translation.x = CGPointToPixel(translation.x) / self.vmDisplay.viewportScale;
  345. translation.y = CGPointToPixel(translation.y) / self.vmDisplay.viewportScale;
  346. if (self.vmInput.serverModeCursor) {
  347. [self.vmInput sendMouseMotion:self.mouseButtonDown relativePoint:translation];
  348. } else {
  349. UTMLog(@"Warning: ignored mouse motion (%f, %f) while mouse is in client mode", translation.x, translation.y);
  350. }
  351. return translation;
  352. }
  353. - (CGPoint)moveMouseScroll:(CGPoint)translation {
  354. translation.y = CGPointToPixel(translation.y) / kScrollSpeedReduction;
  355. if (self.isInvertScroll) {
  356. translation.y = -translation.y;
  357. }
  358. [self.vmInput sendMouseScroll:kCSInputScrollSmooth buttonMask:self.mouseButtonDown dy:translation.y];
  359. return translation;
  360. }
  361. - (void)mouseClick:(CSInputButton)button location:(CGPoint)location {
  362. if (!self.serverModeCursor) {
  363. self.cursor.center = location;
  364. }
  365. [self.vmInput sendMouseButton:button mask:kCSInputButtonNone pressed:YES];
  366. [self onDelay:0.05f action:^{
  367. self.mouseLeftDown = NO;
  368. self.mouseRightDown = NO;
  369. self.mouseMiddleDown = NO;
  370. [self.vmInput sendMouseButton:button mask:kCSInputButtonNone pressed:NO];
  371. }];
  372. #if !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
  373. [self.clickFeedbackGenerator selectionChanged];
  374. #endif
  375. }
  376. - (void)dragCursor:(UIGestureRecognizerState)state primary:(BOOL)primary secondary:(BOOL)secondary middle:(BOOL)middle {
  377. CSInputButton button = kCSInputButtonNone;
  378. if (middle) {
  379. button = kCSInputButtonMiddle;
  380. }
  381. if (secondary) {
  382. button = kCSInputButtonRight;
  383. }
  384. if (primary) {
  385. button = kCSInputButtonLeft;
  386. }
  387. if (state == UIGestureRecognizerStateBegan) {
  388. #if !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
  389. [self.clickFeedbackGenerator selectionChanged];
  390. #endif
  391. if (primary) {
  392. self.mouseLeftDown = YES;
  393. }
  394. if (secondary) {
  395. self.mouseRightDown = YES;
  396. }
  397. if (middle) {
  398. self.mouseMiddleDown = YES;
  399. }
  400. [self.vmInput sendMouseButton:button mask:self.mouseButtonDown pressed:YES];
  401. } else if (state == UIGestureRecognizerStateEnded) {
  402. self.mouseLeftDown = NO;
  403. self.mouseRightDown = NO;
  404. self.mouseMiddleDown = NO;
  405. [self.vmInput sendMouseButton:button mask:self.mouseButtonDown pressed:NO];
  406. }
  407. }
  408. - (IBAction)gestureTap:(UITapGestureRecognizer *)sender {
  409. if (sender.state == UIGestureRecognizerStateEnded &&
  410. self.serverModeCursor) { // otherwise we handle in touchesBegan
  411. [self mouseClick:kCSInputButtonLeft location:[sender locationInView:sender.view]];
  412. }
  413. }
  414. - (IBAction)gestureTwoTap:(UITapGestureRecognizer *)sender {
  415. if (sender.state == UIGestureRecognizerStateEnded &&
  416. self.twoFingerTapType == VMGestureTypeRightClick) {
  417. [self mouseClick:kCSInputButtonRight location:[sender locationInView:sender.view]];
  418. }
  419. }
  420. - (IBAction)gestureLongPress:(UILongPressGestureRecognizer *)sender {
  421. if (sender.state == UIGestureRecognizerStateEnded &&
  422. self.longPressType == VMGestureTypeRightClick) {
  423. [self mouseClick:kCSInputButtonRight location:[sender locationInView:sender.view]];
  424. } else if (self.longPressType == VMGestureTypeDragCursor) {
  425. [self dragCursor:sender.state primary:YES secondary:NO middle:NO];
  426. }
  427. }
  428. - (IBAction)gesturePinch:(UIPinchGestureRecognizer *)sender {
  429. // disable pinch if move screen on pan is disabled
  430. if (!(self.twoFingerPanType == VMGestureTypeMoveScreen || self.threeFingerPanType == VMGestureTypeMoveScreen)) {
  431. return;
  432. }
  433. if (sender.state == UIGestureRecognizerStateBegan ||
  434. sender.state == UIGestureRecognizerStateChanged ||
  435. sender.state == UIGestureRecognizerStateEnded) {
  436. NSAssert(sender.scale > 0, @"sender.scale cannot be 0");
  437. CGFloat scaling;
  438. if (!self.delegate.qemuDisplayIsNativeResolution) {
  439. // will be undo in `-setDisplayScaling:origin:`
  440. scaling = CGPixelToPoint(CGPointToPixel(self.delegate.displayScale) * sender.scale);
  441. } else {
  442. scaling = self.delegate.displayScale * sender.scale;
  443. }
  444. self.delegate.displayScale = scaling;
  445. sender.scale = 1.0;
  446. }
  447. }
  448. - (IBAction)gestureSwipeUp:(UISwipeGestureRecognizer *)sender {
  449. if (sender.state == UIGestureRecognizerStateEnded) {
  450. [self showKeyboard];
  451. }
  452. }
  453. - (IBAction)gestureSwipeDown:(UISwipeGestureRecognizer *)sender {
  454. if (sender.state == UIGestureRecognizerStateEnded) {
  455. [self hideKeyboard];
  456. }
  457. }
  458. - (IBAction)gestureSwipeScroll:(UISwipeGestureRecognizer *)sender {
  459. if (sender.state == UIGestureRecognizerStateEnded &&
  460. self.twoFingerScrollType == VMGestureTypeMouseWheel) {
  461. if (sender == self.swipeScrollUp) {
  462. [self.vmInput sendMouseScroll:kCSInputScrollUp buttonMask:self.mouseButtonDown dy:0];
  463. } else if (sender == self.swipeScrollDown) {
  464. [self.vmInput sendMouseScroll:kCSInputScrollDown buttonMask:self.mouseButtonDown dy:0];
  465. } else {
  466. NSAssert(0, @"Invalid call to gestureSwipeScroll");
  467. }
  468. }
  469. }
  470. #pragma mark - UIGestureRecognizerDelegate
  471. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
  472. if (gestureRecognizer == self.twoPan && otherGestureRecognizer == self.swipeUp) {
  473. return YES;
  474. }
  475. if (gestureRecognizer == self.twoPan && otherGestureRecognizer == self.swipeDown) {
  476. return YES;
  477. }
  478. if (gestureRecognizer == self.twoTap && otherGestureRecognizer == self.swipeDown) {
  479. return YES;
  480. }
  481. if (gestureRecognizer == self.twoTap && otherGestureRecognizer == self.swipeUp) {
  482. return YES;
  483. }
  484. if (gestureRecognizer == self.tap && otherGestureRecognizer == self.twoTap) {
  485. return YES;
  486. }
  487. if (gestureRecognizer == self.longPress && otherGestureRecognizer == self.tap) {
  488. return YES;
  489. }
  490. if (gestureRecognizer == self.longPress && otherGestureRecognizer == self.twoTap) {
  491. return YES;
  492. }
  493. if (gestureRecognizer == self.pinch && otherGestureRecognizer == self.swipeDown) {
  494. return YES;
  495. }
  496. if (gestureRecognizer == self.pinch && otherGestureRecognizer == self.swipeUp) {
  497. return YES;
  498. }
  499. if (gestureRecognizer == self.pan && otherGestureRecognizer == self.swipeUp) {
  500. return YES;
  501. }
  502. if (gestureRecognizer == self.pan && otherGestureRecognizer == self.swipeDown) {
  503. return YES;
  504. }
  505. if (gestureRecognizer == self.threePan && otherGestureRecognizer == self.swipeUp) {
  506. return YES;
  507. }
  508. if (gestureRecognizer == self.threePan && otherGestureRecognizer == self.swipeDown) {
  509. return YES;
  510. }
  511. // only if we do not disable two finger swipe
  512. if (self.twoFingerScrollType != VMGestureTypeNone) {
  513. if (gestureRecognizer == self.twoPan && otherGestureRecognizer == self.swipeScrollUp) {
  514. return YES;
  515. }
  516. if (gestureRecognizer == self.twoPan && otherGestureRecognizer == self.swipeScrollDown) {
  517. return YES;
  518. }
  519. }
  520. #if !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
  521. return [self pencilGestureRecognizer:gestureRecognizer shouldRequireFailureOfGestureRecognizer:otherGestureRecognizer];
  522. #else
  523. return NO;
  524. #endif
  525. }
  526. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
  527. if (gestureRecognizer == self.twoPan && otherGestureRecognizer == self.pinch) {
  528. if (self.twoFingerPanType == VMGestureTypeMoveScreen) {
  529. return YES;
  530. } else {
  531. return NO;
  532. }
  533. } else if (gestureRecognizer == self.pan && otherGestureRecognizer == self.longPress) {
  534. return YES;
  535. } else if (self.twoFingerScrollType == VMGestureTypeNone && otherGestureRecognizer == self.twoPan) {
  536. // if two finger swipe is disabled, we can also recognize two finger pans
  537. if (gestureRecognizer == self.swipeScrollUp) {
  538. return YES;
  539. } else if (gestureRecognizer == self.swipeScrollDown) {
  540. return YES;
  541. } else {
  542. return NO;
  543. }
  544. } else {
  545. return NO;
  546. }
  547. }
  548. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveEvent:(UIEvent *)event API_AVAILABLE(ios(13.4)) {
  549. if (event.type == UIEventTypeTransform) {
  550. UTMLog(@"ignoring UIEventTypeTransform");
  551. return NO;
  552. } else {
  553. return YES;
  554. }
  555. }
  556. #pragma mark - Touch type
  557. - (VMMouseType)touchTypeToMouseType:(UITouchType)type {
  558. switch (type) {
  559. case UITouchTypeDirect: {
  560. return self.touchMouseType;
  561. }
  562. case UITouchTypePencil: {
  563. return self.pencilMouseType;
  564. }
  565. case UITouchTypeIndirect: {
  566. return self.indirectMouseType;
  567. }
  568. default: {
  569. if (@available(iOS 13.4, *)) {
  570. if (type == UITouchTypeIndirectPointer) {
  571. return self.indirectMouseType;
  572. }
  573. }
  574. return self.touchMouseType; // compatibility with future values
  575. }
  576. }
  577. }
  578. - (BOOL)switchMouseType:(VMMouseType)type {
  579. BOOL shouldHideCursor = (type == VMMouseTypeAbsoluteHideCursor);
  580. BOOL shouldUseServerMouse = (type == VMMouseTypeRelative);
  581. self.vmDisplay.cursor.isInhibited = shouldHideCursor;
  582. if (shouldUseServerMouse != self.vmInput.serverModeCursor) {
  583. UTMLog(@"Switching mouse mode to server:%d for type:%ld", shouldUseServerMouse, type);
  584. [self.delegate requestInputTablet:!shouldUseServerMouse];
  585. return YES;
  586. }
  587. return NO;
  588. }
  589. #if TARGET_OS_VISION
  590. - (BOOL)isTouchGazeGesture:(UITouch *)touch {
  591. id manipulator = [touch valueForKey:@"_manipulator"];
  592. SEL selector = NSSelectorFromString(@"_type");
  593. if ([manipulator respondsToSelector:selector]) {
  594. IMP imp = [manipulator methodForSelector:selector];
  595. if (imp) {
  596. return ((NSInteger (*)(id, SEL))imp)(manipulator, selector) == 2;
  597. }
  598. }
  599. return NO;
  600. }
  601. #endif
  602. #pragma mark - Touch event handling
  603. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  604. if (!self.delegate.qemuInputLegacy) {
  605. for (UITouch *touch in touches) {
  606. VMMouseType type = [self touchTypeToMouseType:touch.type];
  607. #if TARGET_OS_VISION
  608. if ([self isTouchGazeGesture:touch]) {
  609. type = VMMouseTypeRelative;
  610. }
  611. #endif
  612. if ([self switchMouseType:type]) {
  613. [self dragCursor:UIGestureRecognizerStateEnded primary:YES secondary:YES middle:YES]; // reset drag
  614. } else if (!self.vmInput.serverModeCursor) { // start click for client mode
  615. BOOL primary = YES;
  616. BOOL secondary = NO;
  617. BOOL middle = NO;
  618. CGPoint pos = [touch locationInView:self.mtkView];
  619. // iOS 13.4+ Pointing device support
  620. if (@available(iOS 13.4, *)) {
  621. if (touch.type == UITouchTypeIndirectPointer) {
  622. primary = (event.buttonMask & UIEventButtonMaskPrimary) != 0;
  623. secondary = (event.buttonMask & UIEventButtonMaskSecondary) != 0;
  624. middle = (event.buttonMask & 0x4) != 0; // undocumented mask
  625. }
  626. }
  627. #if !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
  628. // Apple Pencil 2 right click mode
  629. if (@available(iOS 12.1, *)) {
  630. if ([self pencilRightClickForTouch:touch]) {
  631. primary = NO;
  632. secondary = YES;
  633. }
  634. }
  635. #endif
  636. [self.cursor startMovement:pos];
  637. [self.cursor updateMovement:pos];
  638. [self dragCursor:UIGestureRecognizerStateBegan primary:primary secondary:secondary middle:middle];
  639. }
  640. break; // handle a single touch only
  641. }
  642. } else {
  643. [self switchMouseType:VMMouseTypeRelative];
  644. }
  645. [super touchesBegan:touches withEvent:event];
  646. }
  647. - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  648. // move cursor in client mode, in server mode we handle in gesturePan
  649. if (!self.delegate.qemuInputLegacy && !self.vmInput.serverModeCursor) {
  650. for (UITouch *touch in touches) {
  651. [self.cursor updateMovement:[touch locationInView:self.mtkView]];
  652. break; // handle single touch
  653. }
  654. }
  655. [super touchesMoved:touches withEvent:event];
  656. }
  657. - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  658. // release click in client mode, in server mode we handle in gesturePan
  659. if (!self.delegate.qemuInputLegacy && !self.vmInput.serverModeCursor) {
  660. [self dragCursor:UIGestureRecognizerStateEnded primary:YES secondary:YES middle:YES];
  661. }
  662. [super touchesCancelled:touches withEvent:event];
  663. }
  664. - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  665. // release click in client mode, in server mode we handle in gesturePan
  666. if (!self.delegate.qemuInputLegacy && !self.vmInput.serverModeCursor) {
  667. [self dragCursor:UIGestureRecognizerStateEnded primary:YES secondary:YES middle:YES];
  668. }
  669. [super touchesEnded:touches withEvent:event];
  670. }
  671. @end