From 882b61ac63a84da4417d212dbefda7e6ea5193d8 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 17 Sep 2020 13:56:09 -0500 Subject: [PATCH] Implement GCMouse API for relative mouse support This seems to be quite broken in iOS 14.0. The Microsoft Surface Precision mouse is detected as a GCKeyboard instead of a GCMouse. --- Limelight/Input/ControllerSupport.h | 3 - Limelight/Input/ControllerSupport.m | 167 ++++++++++++++++++++++++++-- 2 files changed, 159 insertions(+), 11 deletions(-) diff --git a/Limelight/Input/ControllerSupport.h b/Limelight/Input/ControllerSupport.h index 3b7fc31..f416c34 100644 --- a/Limelight/Input/ControllerSupport.h +++ b/Limelight/Input/ControllerSupport.h @@ -44,7 +44,4 @@ -(NSUInteger) getConnectedGamepadCount; -@property (nonatomic, strong) id connectObserver; -@property (nonatomic, strong) id disconnectObserver; - @end diff --git a/Limelight/Input/ControllerSupport.m b/Limelight/Input/ControllerSupport.m index 0327f42..0c39067 100644 --- a/Limelight/Input/ControllerSupport.m +++ b/Limelight/Input/ControllerSupport.m @@ -17,12 +17,25 @@ @import GameController; @import AudioToolbox; +static const double MOUSE_SPEED_DIVISOR = 2.5; + @implementation ControllerSupport { + id _controllerConnectObserver; + id _controllerDisconnectObserver; + id _mouseConnectObserver; + id _mouseDisconnectObserver; + id _keyboardConnectObserver; + id _keyboardDisconnectObserver; + NSLock *_controllerStreamLock; NSMutableDictionary *_controllers; NSTimer *_rumbleTimer; id _presenceDelegate; + float accumulatedDeltaX; + float accumulatedDeltaY; + float accumulatedScrollY; + OnScreenControls *_osc; // This controller object is shared between on-screen controls @@ -195,6 +208,15 @@ [_controllerStreamLock unlock]; } ++(BOOL) hasKeyboardOrMouse { + if (@available(iOS 14.0, tvOS 14.0, *)) { + return GCMouse.mice.count > 0 || GCKeyboard.coalescedKeyboard != nil; + } + else { + return NO; + } +} + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -327,6 +349,71 @@ } } +-(void) unregisterMouseCallbacks:(GCMouse*)mouse API_AVAILABLE(ios(14.0)) { + mouse.mouseInput.mouseMovedHandler = nil; + + mouse.mouseInput.leftButton.pressedChangedHandler = nil; + mouse.mouseInput.middleButton.pressedChangedHandler = nil; + mouse.mouseInput.rightButton.pressedChangedHandler = nil; + + for (GCControllerButtonInput* auxButton in mouse.mouseInput.auxiliaryButtons) { + auxButton.pressedChangedHandler = nil; + } +} + +-(void) registerMouseCallbacks:(GCMouse*) mouse API_AVAILABLE(ios(14.0)) { + mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput * _Nonnull mouse, float deltaX, float deltaY) { + self->accumulatedDeltaX += deltaX / MOUSE_SPEED_DIVISOR; + self->accumulatedDeltaY += -deltaY / MOUSE_SPEED_DIVISOR; + + short truncatedDeltaX = (short)self->accumulatedDeltaX; + short truncatedDeltaY = (short)self->accumulatedDeltaY; + + if (truncatedDeltaX != 0 || truncatedDeltaY != 0) { + LiSendMouseMoveEvent(truncatedDeltaX, truncatedDeltaY); + + self->accumulatedDeltaX -= truncatedDeltaX; + self->accumulatedDeltaY -= truncatedDeltaY; + } + }; + + mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) { + LiSendMouseButtonEvent(pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_LEFT); + }; + mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) { + LiSendMouseButtonEvent(pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_MIDDLE); + }; + mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) { + LiSendMouseButtonEvent(pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_RIGHT); + }; + + if (mouse.mouseInput.auxiliaryButtons != nil) { + if (mouse.mouseInput.auxiliaryButtons.count >= 1) { + mouse.mouseInput.auxiliaryButtons[0].pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) { + LiSendMouseButtonEvent(pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_X1); + }; + } + if (mouse.mouseInput.auxiliaryButtons.count >= 2) { + mouse.mouseInput.auxiliaryButtons[1].pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) { + LiSendMouseButtonEvent(pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_X2); + }; + } + } + + // TODO: Confirm scroll direction + mouse.mouseInput.scroll.yAxis.valueChangedHandler = ^(GCControllerAxisInput * _Nonnull axis, float value) { + self->accumulatedScrollY += -value; + + short truncatedScrollY = (short)self->accumulatedScrollY; + + if (truncatedScrollY != 0) { + LiSendHighResScrollEvent(truncatedScrollY); + + self->accumulatedScrollY -= truncatedScrollY; + } + }; +} + -(void) updateAutoOnScreenControlMode { // Auto on-screen control support may not be enabled @@ -365,6 +452,17 @@ } } + // If we didn't find a gamepad present and we have a keyboard or mouse, turn + // the on-screen controls off to get the overlays out of the way. + if (level == OnScreenControlsLevelFull && [ControllerSupport hasKeyboardOrMouse]) { + level = OnScreenControlsLevelOff; + + // Ensure the virtual gamepad disappears to avoid confusing some games. + // If the mouse and keyboard disconnect later, it will reappear when the + // first OSC input is received. + LiSendMultiControllerEvent(0, 0, 0, 0, 0, 0, 0, 0, 0); + } + [_osc setLevel:level]; } @@ -452,9 +550,9 @@ DataManager* dataMan = [[DataManager alloc] init]; OnScreenControlsLevel level = (OnScreenControlsLevel)[[dataMan getSettings].onscreenControls integerValue]; - // Even if no gamepads are present, we will always count one - // if OSC is enabled. - if (level != OnScreenControlsLevelOff) { + // Even if no gamepads are present, we will always count one if OSC is enabled, + // or it's set to auto and no keyboard or mouse is present. + if (level != OnScreenControlsLevelOff && (![ControllerSupport hasKeyboardOrMouse] || level != OnScreenControlsLevelAuto)) { mask |= 0x1; } @@ -512,11 +610,16 @@ if ([ControllerSupport isSupportedGamepad:controller]) { [self assignController:controller]; [self registerControllerCallbacks:controller]; - [self updateAutoOnScreenControlMode]; } } - self.connectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + if (@available(iOS 14.0, tvOS 14.0, *)) { + for (GCMouse* mouse in [GCMouse mice]) { + [self registerMouseCallbacks:mouse]; + } + } + + _controllerConnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Log(LOG_I, @"Controller connected!"); GCController* controller = note.object; @@ -537,7 +640,7 @@ // Notify the delegate [self->_presenceDelegate gamepadPresenceChanged]; }]; - self.disconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + _controllerDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Log(LOG_I, @"Controller disconnected!"); GCController* controller = note.object; @@ -565,6 +668,44 @@ // Notify the delegate [self->_presenceDelegate gamepadPresenceChanged]; }]; + + if (@available(iOS 14.0, tvOS 14.0, *)) { + _mouseConnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCMouseDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Log(LOG_I, @"Mouse connected!"); + + GCMouse* mouse = note.object; + + // Register for mouse events + [self registerMouseCallbacks: mouse]; + + // Re-evaluate the on-screen control mode + [self updateAutoOnScreenControlMode]; + }]; + _mouseDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCMouseDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Log(LOG_I, @"Mouse disconnected!"); + + GCMouse* mouse = note.object; + + // Unregister for mouse events + [self unregisterMouseCallbacks: mouse]; + + // Re-evaluate the on-screen control mode + [self updateAutoOnScreenControlMode]; + }]; + _keyboardConnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCKeyboardDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Log(LOG_I, @"Keyboard connected!"); + + // Re-evaluate the on-screen control mode + [self updateAutoOnScreenControlMode]; + }]; + _keyboardDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCKeyboardDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Log(LOG_I, @"Keyboard disconnected!"); + + // Re-evaluate the on-screen control mode + [self updateAutoOnScreenControlMode]; + }]; + } + return self; } @@ -572,8 +713,12 @@ { [_rumbleTimer invalidate]; _rumbleTimer = nil; - [[NSNotificationCenter defaultCenter] removeObserver:self.connectObserver]; - [[NSNotificationCenter defaultCenter] removeObserver:self.disconnectObserver]; + [[NSNotificationCenter defaultCenter] removeObserver:_controllerConnectObserver]; + [[NSNotificationCenter defaultCenter] removeObserver:_controllerDisconnectObserver]; + [[NSNotificationCenter defaultCenter] removeObserver:_mouseConnectObserver]; + [[NSNotificationCenter defaultCenter] removeObserver:_mouseDisconnectObserver]; + [[NSNotificationCenter defaultCenter] removeObserver:_keyboardConnectObserver]; + [[NSNotificationCenter defaultCenter] removeObserver:_keyboardDisconnectObserver]; [_controllers removeAllObjects]; _controllerNumbers = 0; for (GCController* controller in [GCController controllers]) { @@ -581,6 +726,12 @@ [self unregisterControllerCallbacks:controller]; } } + + if (@available(iOS 14.0, tvOS 14.0, *)) { + for (GCMouse* mouse in [GCMouse mice]) { + [self unregisterMouseCallbacks:mouse]; + } + } } @end