Skip to content

Commit

Permalink
iOS: support multiple GCMouse and iOS Simulator
Browse files Browse the repository at this point in the history
Multiple mice could be connected. We will always use the most "recent" mice.
Also ignore UITouchTypeIndirectPointer events when mouse is captured.

Fixes #2363
  • Loading branch information
osy committed Apr 26, 2021
1 parent 954ed16 commit 05a72f5
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 51 deletions.
115 changes: 65 additions & 50 deletions Platform/iOS/Display/VMDisplayMetalViewController+Pointer.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,74 @@
#import "UTMVirtualMachine+SPICE.h"
#import "UTMLogging.h"

@interface VMDisplayMetalViewController ()

- (BOOL)switchMouseType:(VMMouseType)type; // defined in VMDisplayMetalViewController+Touch.m

@end

NS_AVAILABLE_IOS(13.4)
@implementation VMDisplayMetalViewController (Pointer)

#pragma mark - GCMouse

- (void)initGCMouse {
if (@available(iOS 14.0, *)) { //if ios 14.0 above, use CGMouse instead
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(mouseDidBecomeCurrent:) name:GCMouseDidBecomeCurrentNotification object:nil];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(mouseDidStopBeingCurrent:) name:GCMouseDidStopBeingCurrentNotification object:nil];
}
}

- (BOOL)prefersPointerLocked {
return _mouseCaptured;
}

- (void)mouseDidBecomeCurrent:(NSNotification *)notification API_AVAILABLE(ios(14)) {
GCMouse *mouse = notification.object;
UTMLog(@"mouseDidBecomeCurrent: %p", mouse);
if (!mouse) {
UTMLog(@"invalid mouse object!");
return;
}
mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput * _Nonnull mouse, float deltaX, float deltaY) {
[self.vmInput sendMouseMotion:self.mouseButtonDown point:CGPointMake(deltaX, -deltaY)];
};
mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
self->_mouseLeftDown = pressed;
[self.vmInput sendMouseButton:kCSInputButtonLeft pressed:pressed point:CGPointZero];
};
mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
self->_mouseRightDown = pressed;
[self.vmInput sendMouseButton:kCSInputButtonRight pressed:pressed point:CGPointZero];

};
mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
self->_mouseMiddleDown = pressed;
[self.vmInput sendMouseButton:kCSInputButtonMiddle pressed:pressed point:CGPointZero];
};
// no handler to the gcmouse scroll event, gestureScroll works fine.
[self switchMouseType:VMMouseTypeRelative];
_mouseCaptured = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[self setNeedsUpdateOfPrefersPointerLocked];
});
}

- (void)mouseDidStopBeingCurrent:(NSNotification *)notification API_AVAILABLE(ios(14)) {
GCMouse *mouse = notification.object;
UTMLog(@"mouseDidStopBeingCurrent: %p", mouse);
mouse.mouseInput.mouseMovedHandler = nil;
mouse.mouseInput.leftButton.pressedChangedHandler = nil;
mouse.mouseInput.rightButton.pressedChangedHandler = nil;
mouse.mouseInput.middleButton.pressedChangedHandler = nil;
_mouseCaptured = NO;
dispatch_async(dispatch_get_main_queue(), ^{
[self setNeedsUpdateOfPrefersPointerLocked];
});
}

#pragma mark - UIPointerInteractionDelegate

// Add pointer interaction to VM view
-(void)initPointerInteraction {
[self.mtkView addInteraction:[[UIPointerInteraction alloc] initWithDelegate:self]];
Expand All @@ -42,60 +107,10 @@ -(void)initPointerInteraction {
}
}

- (BOOL)switchMouseType:(VMMouseType)type {
BOOL shouldUseServerMouse = YES;
self.vmInput.inhibitCursor = NO;
if (shouldUseServerMouse != self.vmInput.serverModeCursor) {
UTMLog(@"Switching mouse mode to server:%d for type:%ld", shouldUseServerMouse, type);
[self.vm requestInputTablet:!shouldUseServerMouse];
return YES;
}
return NO;
}

- (void) initGCMouse {
if (@available(iOS 14.0, *)) { //if ios 14.0 above, use CGMouse instead
GCMouse *mouse = GCMouse.current;
__weak typeof(self) _self = self;
VMDisplayMetalViewController *s = _self;
mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput * _Nonnull mouse, float deltaX, float deltaY) {
[self switchMouseType:VMMouseTypeRelative];
[self.vmInput sendMouseMotion:self.mouseButtonDown point:CGPointMake(deltaX, -deltaY)];
};
mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
s->_mouseLeftDown = pressed;
[s.vmInput sendMouseButton:kCSInputButtonLeft pressed:pressed point:CGPointZero];
};

mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
s->_mouseRightDown = pressed;
[s.vmInput sendMouseButton:kCSInputButtonRight pressed:pressed point:CGPointZero];

};

mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
s->_mouseMiddleDown = pressed;
[s.vmInput sendMouseButton:kCSInputButtonMiddle pressed:pressed point:CGPointZero];

};

// no handler to the gcmouse scroll event, gestureScroll works fine.
}
}

- (BOOL) prefersPointerLocked {
if (self.indirectMouseType == VMMouseTypeAbsolute || self.indirectMouseType == VMMouseTypeAbsoluteHideCursor) {
return YES;
} else {
return NO;
}
}

- (BOOL)hasTouchpadPointer {
return !self.vmConfiguration.inputLegacy && !self.vmInput.serverModeCursor && self.indirectMouseType != VMMouseTypeRelative;
}

#pragma mark - UIPointerInteractionDelegate
- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region {
// Hide cursor while hovering in VM view
if (interaction.view == self.mtkView && self.hasTouchpadPointer) {
Expand Down
7 changes: 6 additions & 1 deletion Platform/iOS/Display/VMDisplayMetalViewController+Touch.m
Original file line number Diff line number Diff line change
Expand Up @@ -583,8 +583,13 @@ - (BOOL)switchMouseType:(VMMouseType)type {
#pragma mark - Touch event handling

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (!self.vmConfiguration.inputLegacy) {
if (!_mouseCaptured && !self.vmConfiguration.inputLegacy) {
for (UITouch *touch in [event touchesForView:self.mtkView]) {
if (@available(iOS 14, *)) {
if (self.prefersPointerLocked && (touch.type == UITouchTypeIndirect || touch.type == UITouchTypeIndirectPointer)) {
continue; // skip indirect touches if we are capturing mouse input
}
}
VMMouseType type = [self touchTypeToMouseType:touch.type];
if ([self switchMouseType:type]) {
[self dragCursor:UIGestureRecognizerStateEnded primary:YES secondary:YES middle:YES]; // reset drag
Expand Down
1 change: 1 addition & 0 deletions Platform/iOS/Display/VMDisplayMetalViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
BOOL _pencilForceRightClickOnce;
VMCursor *_cursor;
VMScroll *_scroll;
BOOL _mouseCaptured;

// Gestures
UISwipeGestureRecognizer *_swipeUp;
Expand Down

0 comments on commit 05a72f5

Please sign in to comment.