From a4255f4cad5a5bac5493e2a33b75055927ba8bfd Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 18 Apr 2020 13:11:24 -0700 Subject: [PATCH] Implement absolute mouse support on iOS 13.4 Fixes #402 --- Limelight/Input/StreamView.h | 4 ++ Limelight/Input/StreamView.m | 113 +++++++++++++++++++++++++++++++++ Limelight/Limelight-Info.plist | 10 +-- 3 files changed, 123 insertions(+), 4 deletions(-) diff --git a/Limelight/Input/StreamView.h b/Limelight/Input/StreamView.h index 5aef4eb..7dc1c4d 100644 --- a/Limelight/Input/StreamView.h +++ b/Limelight/Input/StreamView.h @@ -23,7 +23,11 @@ @end +#if TARGET_OS_TV @interface StreamView : OSView +#else +@interface StreamView : OSView +#endif @property (nonatomic, retain) IBOutlet UITextField* keyInputField; diff --git a/Limelight/Input/StreamView.m b/Limelight/Input/StreamView.m index 2cf26d4..c2b376e 100644 --- a/Limelight/Input/StreamView.m +++ b/Limelight/Input/StreamView.m @@ -29,6 +29,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; float yDeltaFactor; float screenFactor; + NSInteger lastMouseButtonMask; double mouseX; double mouseY; @@ -81,6 +82,15 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; Log(LOG_I, @"Setting manual on-screen controls level: %d", (int)level); [onScreenControls setLevel:level]; } + + if (@available(iOS 13.4, *)) { + [self addInteraction:[[UIPointerInteraction alloc] initWithDelegate:self]]; + + UIPanGestureRecognizer *mouseWheelRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(mouseWheelMoved:)]; + mouseWheelRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete; + mouseWheelRecognizer.allowedTouchTypes = @[@(UITouchTypeIndirectPointer)]; + [self addGestureRecognizer:mouseWheelRecognizer]; + } #endif textFieldDelegate = [[TextFieldKeyboardDelegate alloc] initWithTextField:_keyInputField]; @@ -147,6 +157,13 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + if ([self handleMouseButtonEvent:BUTTON_ACTION_PRESS + forTouches:touches + withEvent:event]) { + // If it's a mouse event, we're done + return; + } + Log(LOG_D, @"Touch down"); // Notify of user interaction and start expiration timer @@ -173,7 +190,57 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; } } +- (BOOL)handleMouseButtonEvent:(int)buttonAction forTouches:(NSSet *)touches withEvent:(UIEvent *)event { +#if !TARGET_OS_TV + if (@available(iOS 13.4, *)) { + UITouch* touch = [touches anyObject]; + if (touch.type == UITouchTypeIndirectPointer) { + UIEventButtonMask changedButtons = lastMouseButtonMask ^ event.buttonMask; + + for (int i = BUTTON_LEFT; i <= BUTTON_X2; i++) { + UIEventButtonMask buttonFlag; + + switch (i) { + // Right and Middle are reversed from what iOS uses + case BUTTON_RIGHT: + buttonFlag = UIEventButtonMaskForButtonNumber(2); + break; + case BUTTON_MIDDLE: + buttonFlag = UIEventButtonMaskForButtonNumber(3); + break; + + default: + buttonFlag = UIEventButtonMaskForButtonNumber(i); + break; + } + + if (changedButtons & buttonFlag) { + LiSendMouseButtonEvent(buttonAction, i); + } + } + + lastMouseButtonMask = event.buttonMask; + return YES; + } + } +#endif + + return NO; +} + - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { +#if !TARGET_OS_TV + if (@available(iOS 13.4, *)) { + UITouch *touch = [touches anyObject]; + if (touch.type == UITouchTypeIndirectPointer) { + // Ignore move events from mice. These only happen while the + // mouse button is pressed and conflict with our positional + // mouse input handling. + return; + } + } +#endif + hasUserInteracted = YES; if (![onScreenControls handleTouchMovedEvent:touches]) { @@ -261,6 +328,13 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + if ([self handleMouseButtonEvent:BUTTON_ACTION_RELEASE + forTouches:touches + withEvent:event]) { + // If it's a mouse event, we're done + return; + } + Log(LOG_D, @"Touch up"); hasUserInteracted = YES; @@ -338,6 +412,9 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; isDragging = false; LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT); } + [self handleMouseButtonEvent:BUTTON_ACTION_RELEASE + forTouches:touches + withEvent:event]; } #if TARGET_OS_TV @@ -362,6 +439,42 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; isDragging = true; LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT); } +#else +- (UIPointerRegion *)pointerInteraction:(UIPointerInteraction *)interaction + regionForRequest:(UIPointerRegionRequest *)request + defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4)) { + // Send coordinates normalized to our view + LiSendMousePositionEvent(request.location.x - self.bounds.origin.x, + request.location.y - self.bounds.origin.y, + self.bounds.size.width, self.bounds.size.height); + + // The pointer interaction should cover the entire view + return [UIPointerRegion regionWithRect:self.bounds identifier:nil]; +} + +- (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4)) { + // Always hide the mouse cursor over our stream view + return [UIPointerStyle hiddenPointerStyle]; +} + +- (void)mouseWheelMoved:(UIPanGestureRecognizer *)gesture { + switch (gesture.state) { + case UIGestureRecognizerStateBegan: + case UIGestureRecognizerStateChanged: + case UIGestureRecognizerStateEnded: + break; + + default: + // Ignore recognition failure and other states + return; + } + + CGPoint velocity = [gesture velocityInView:self]; + if ((short)velocity.y != 0) { + LiSendHighResScrollEvent((short)velocity.y); + } +} + #endif - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { diff --git a/Limelight/Limelight-Info.plist b/Limelight/Limelight-Info.plist index abc5a10..86a0fe3 100644 --- a/Limelight/Limelight-Info.plist +++ b/Limelight/Limelight-Info.plist @@ -2,10 +2,6 @@ - NSBluetoothPeripheralUsageDescription - Bluetooth access allows Moonlight to connect to Citrix X1 mice. - NSBluetoothAlwaysUsageDescription - Bluetooth access allows Moonlight to connect to Citrix X1 mice. CADisableMinimumFrameDuration CFBundleDevelopmentRegion @@ -50,6 +46,12 @@ NSAllowsArbitraryLoads + NSBluetoothAlwaysUsageDescription + Bluetooth access allows Moonlight to connect to Citrix X1 mice. + NSBluetoothPeripheralUsageDescription + Bluetooth access allows Moonlight to connect to Citrix X1 mice. + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName Launch Screen UIMainStoryboardFile