VMDisplayMetalViewController+Pointer.m 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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. #import "VMDisplayMetalViewController+Pointer.h"
  20. #import "VMCursor.h"
  21. #import "CSDisplay.h"
  22. #import "VMScroll.h"
  23. #import "UTMLogging.h"
  24. #import "UTM-Swift.h"
  25. @interface VMDisplayMetalViewController ()
  26. - (BOOL)switchMouseType:(VMMouseType)type; // defined in VMDisplayMetalViewController+Touch.m
  27. @end
  28. NS_AVAILABLE_IOS(13.4)
  29. @implementation VMDisplayMetalViewController (Pointer)
  30. #pragma mark - GCMouse
  31. - (void)startGCMouse {
  32. if (@available(iOS 14.0, *)) { //if ios 14.0 above, use CGMouse instead
  33. [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(mouseDidBecomeCurrent:) name:GCMouseDidBecomeCurrentNotification object:nil];
  34. [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(mouseDidStopBeingCurrent:) name:GCMouseDidStopBeingCurrentNotification object:nil];
  35. GCMouse *current = GCMouse.current;
  36. if (current) {
  37. // send the current mouse if already connected
  38. [NSNotificationCenter.defaultCenter postNotificationName:GCMouseDidBecomeCurrentNotification object:current];
  39. }
  40. }
  41. }
  42. - (void)stopGCMouse {
  43. GCMouse *current = GCMouse.current;
  44. [NSNotificationCenter.defaultCenter removeObserver:self name:GCMouseDidBecomeCurrentNotification object:nil];
  45. if (current) {
  46. // send the current mouse if already connected
  47. [NSNotificationCenter.defaultCenter postNotificationName:GCMouseDidStopBeingCurrentNotification object:current];
  48. }
  49. [NSNotificationCenter.defaultCenter removeObserver:self name:GCMouseDidStopBeingCurrentNotification object:nil];
  50. }
  51. - (void)mouseDidBecomeCurrent:(NSNotification *)notification API_AVAILABLE(ios(14)) {
  52. GCMouse *mouse = notification.object;
  53. UTMLog(@"mouseDidBecomeCurrent: %p", mouse);
  54. if (!mouse) {
  55. UTMLog(@"invalid mouse object!");
  56. return;
  57. }
  58. mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput * _Nonnull mouse, float deltaX, float deltaY) {
  59. [self switchMouseType:VMMouseTypeRelative];
  60. [self.vmInput sendMouseMotion:self.mouseButtonDown relativePoint:CGPointMake(deltaX, -deltaY)];
  61. };
  62. mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
  63. self.mouseLeftDown = pressed;
  64. [self.vmInput sendMouseButton:kCSInputButtonLeft mask:self.mouseButtonDown pressed:pressed];
  65. };
  66. mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
  67. self.mouseRightDown = pressed;
  68. [self.vmInput sendMouseButton:kCSInputButtonRight mask:self.mouseButtonDown pressed:pressed];
  69. };
  70. mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
  71. self.mouseMiddleDown = pressed;
  72. [self.vmInput sendMouseButton:kCSInputButtonMiddle mask:self.mouseButtonDown pressed:pressed];
  73. };
  74. for (int i = 0; i < MIN(2, mouse.mouseInput.auxiliaryButtons.count); i++) {
  75. mouse.mouseInput.auxiliaryButtons[i].pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
  76. switch (i) {
  77. case 0: self.mouseSideDown = pressed; [self.vmInput sendMouseButton:kCSInputButtonSide mask:self.mouseButtonDown pressed:pressed]; break;
  78. case 1: self.mouseExtraDown = pressed; [self.vmInput sendMouseButton:kCSInputButtonExtra mask:self.mouseButtonDown pressed:pressed]; break;
  79. default: break;
  80. }
  81. };
  82. }
  83. // no handler to the gcmouse scroll event, gestureScroll works fine.
  84. }
  85. - (void)mouseDidStopBeingCurrent:(NSNotification *)notification API_AVAILABLE(ios(14)) {
  86. GCMouse *mouse = notification.object;
  87. UTMLog(@"mouseDidStopBeingCurrent: %p", mouse);
  88. mouse.mouseInput.mouseMovedHandler = nil;
  89. mouse.mouseInput.leftButton.pressedChangedHandler = nil;
  90. mouse.mouseInput.rightButton.pressedChangedHandler = nil;
  91. mouse.mouseInput.middleButton.pressedChangedHandler = nil;
  92. for (int i = 0; i < MIN(4, mouse.mouseInput.auxiliaryButtons.count); i++) {
  93. mouse.mouseInput.auxiliaryButtons[i].pressedChangedHandler = nil;
  94. }
  95. }
  96. #pragma mark - UIPointerInteractionDelegate
  97. // Add pointer interaction to VM view
  98. -(void)initPointerInteraction {
  99. [self.mtkView addInteraction:[[UIPointerInteraction alloc] initWithDelegate:self]];
  100. if (@available(iOS 13.4, *)) {
  101. UIPanGestureRecognizer *scroll = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(gestureScroll:)];
  102. scroll.allowedScrollTypesMask = UIScrollTypeMaskAll;
  103. scroll.minimumNumberOfTouches = 0;
  104. scroll.maximumNumberOfTouches = 0;
  105. [self.mtkView addGestureRecognizer:scroll];
  106. }
  107. }
  108. - (BOOL)hasTouchpadPointer {
  109. return !self.delegate.qemuInputLegacy && !self.vmInput.serverModeCursor && self.indirectMouseType != VMMouseTypeRelative;
  110. }
  111. - (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region {
  112. // Hide cursor while hovering in VM view
  113. if (interaction.view == self.mtkView && self.hasTouchpadPointer) {
  114. #if TARGET_OS_VISION
  115. return nil; // FIXME: hidden pointer seems to jump around due to following gaze
  116. #else
  117. return [UIPointerStyle hiddenPointerStyle];
  118. #endif
  119. }
  120. return nil;
  121. }
  122. - (bool)isPointOnVMDisplay:(CGPoint)pos {
  123. CGSize screenSize = self.mtkView.drawableSize;
  124. CGSize scaledSize = {
  125. self.vmDisplay.displaySize.width * self.vmDisplay.viewportScale,
  126. self.vmDisplay.displaySize.height * self.vmDisplay.viewportScale
  127. };
  128. CGRect drawRect = CGRectMake(
  129. self.vmDisplay.viewportOrigin.x + screenSize.width/2 - scaledSize.width/2,
  130. self.vmDisplay.viewportOrigin.y + screenSize.height/2 - scaledSize.height/2,
  131. scaledSize.width,
  132. scaledSize.height
  133. );
  134. pos.x -= drawRect.origin.x;
  135. pos.y -= drawRect.origin.y;
  136. return 0 <= pos.x && pos.x <= scaledSize.width && 0 <= pos.y && pos.y <= scaledSize.height;
  137. }
  138. - (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion {
  139. #if !TARGET_OS_VISION
  140. if (@available(iOS 14.0, *)) {
  141. if (self.prefersPointerLocked) {
  142. return nil;
  143. }
  144. }
  145. #endif
  146. // Requesting region for the VM display?
  147. if (interaction.view == self.mtkView && self.hasTouchpadPointer) {
  148. // Then we need to find out if the pointer is in the actual display area or outside
  149. CGPoint location = [self.mtkView convertPoint:[request location] fromView:nil];
  150. CGPoint translated = location;
  151. translated.x = CGPointToPixel(translated.x);
  152. translated.y = CGPointToPixel(translated.y);
  153. if ([self isPointOnVMDisplay:translated]) {
  154. // move vm cursor, hide iOS cursor
  155. [self.cursor updateMovement:location];
  156. return [UIPointerRegion regionWithRect:[self.mtkView bounds] identifier:@"vm view"];
  157. } else {
  158. // don't move vm cursor, show iOS cursor
  159. return nil;
  160. }
  161. } else {
  162. return nil;
  163. }
  164. }
  165. #pragma mark - Scroll Gesture
  166. - (IBAction)gestureScroll:(UIPanGestureRecognizer *)sender API_AVAILABLE(ios(13.4)) {
  167. [self scrollWithInertia:sender];
  168. }
  169. @end