From 2c8f2ee4085172a7085903613a24d6ffa17048c8 Mon Sep 17 00:00:00 2001 From: Acaki Date: Sat, 24 May 2025 19:24:59 +0800 Subject: [PATCH 1/8] feat: hide home indicator without needing a controller connected --- VoidLink/ViewControllers/StreamFrameViewController.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index 6b471386..4d0de6fc 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -1156,8 +1156,7 @@ } - (BOOL)prefersHomeIndicatorAutoHidden { - if ([_controllerSupport getConnectedGamepadCount] > 0 && - [_streamView getCurrentOscState] == OnScreenControlsLevelOff && + if ( [_streamView getCurrentOscState] == OnScreenControlsLevelOff && _userIsInteracting == NO) { // Autohide the home bar when a gamepad is connected // and the on-screen controls are disabled. We can't From 1d6906e5e71e61d1fa3328a566c3fa594e160af4 Mon Sep 17 00:00:00 2001 From: Acaki Date: Tue, 8 Jul 2025 17:44:16 +0800 Subject: [PATCH 2/8] fix: virtual controller still appears when osc is disabled --- VoidLink/Input/ControllerSupport.m | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/VoidLink/Input/ControllerSupport.m b/VoidLink/Input/ControllerSupport.m index 71ef5af5..0f9c2fe4 100644 --- a/VoidLink/Input/ControllerSupport.m +++ b/VoidLink/Input/ControllerSupport.m @@ -1318,9 +1318,8 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; _oscController.playerIndex = 0; DataManager* dataMan = [[DataManager alloc] init]; - //_oscEnabled = (OnScreenControlsLevel)[[dataMan getSettings].onscreenControls integerValue] != OnScreenControlsLevelOff; TemporarySettings* currentSettings = [dataMan getSettings]; - _oscEnabled = true; + _oscEnabled = (OnScreenControlsLevel)[[dataMan getSettings].onscreenControls integerValue] != OnScreenControlsLevelOff; _gyroSensitivity = currentSettings.gyroSensitivity.floatValue; } @@ -1567,14 +1566,18 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; } -(void)connectionEstablished{ - [self updateTimerStateForController:self->_oscController]; - [self setButtonFlag:self->_oscController flags:A_FLAG]; - [self updateFinished:self->_oscController]; - [self clearButtonFlag:self->_oscController flags:A_FLAG]; - [self updateFinished:self->_oscController]; + if (_oscEnabled) { + [self updateTimerStateForController:self->_oscController]; + [self setButtonFlag:self->_oscController flags:A_FLAG]; + [self updateFinished:self->_oscController]; + [self clearButtonFlag:self->_oscController flags:A_FLAG]; + [self updateFinished:self->_oscController]; + } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self updateTimerStateForController:self->_oscController]; + if(self->_oscEnabled){ + [self updateTimerStateForController:self->_oscController]; + } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ From 994a60c8a4ae455af5d116675fd6b882bcbc8aca Mon Sep 17 00:00:00 2001 From: Acaki Date: Tue, 8 Jul 2025 21:11:36 +0800 Subject: [PATCH 3/8] fix: missing right brace and wrong device type for osc gyro --- VoidLink/Input/ControllerSupport.m | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/VoidLink/Input/ControllerSupport.m b/VoidLink/Input/ControllerSupport.m index 0f9c2fe4..e8ca8ade 100644 --- a/VoidLink/Input/ControllerSupport.m +++ b/VoidLink/Input/ControllerSupport.m @@ -769,16 +769,23 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; capabilities |= LI_CCAP_BATTERY_STATE; } } - else { - // This is a virtual controller corresponding to our OSC + } + else { + // This is a virtual controller corresponding to our OSC - // TODO: Support various layouts and button labels on the OSC + if (_streamConfig.gyroMode != GyroModeOff) { + type = LI_CTYPE_PS; + capabilities = LI_CCAP_GYRO | LI_CCAP_ACCEL; + } + else { type = LI_CTYPE_XBOX; capabilities = 0; - supportedButtonFlags = - PLAY_FLAG | BACK_FLAG | UP_FLAG | DOWN_FLAG | LEFT_FLAG | RIGHT_FLAG | - LB_FLAG | RB_FLAG | LS_CLK_FLAG | RS_CLK_FLAG | A_FLAG | B_FLAG | X_FLAG | Y_FLAG; } + + // Set the standard supported buttons for the virtual controller. + supportedButtonFlags = + PLAY_FLAG | BACK_FLAG | UP_FLAG | DOWN_FLAG | LEFT_FLAG | RIGHT_FLAG | + LB_FLAG | RB_FLAG | LS_CLK_FLAG | RS_CLK_FLAG | A_FLAG | B_FLAG | X_FLAG | Y_FLAG; } // Report the new controller to the host From 0047bfe51f0d31a865088c602b797239e11af183 Mon Sep 17 00:00:00 2001 From: Acaki Date: Wed, 9 Jul 2025 14:17:29 +0800 Subject: [PATCH 4/8] fix: built-in gyro not working when resuming stream --- VoidLink/Input/ControllerSupport.m | 34 ++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/VoidLink/Input/ControllerSupport.m b/VoidLink/Input/ControllerSupport.m index e8ca8ade..4dc49449 100644 --- a/VoidLink/Input/ControllerSupport.m +++ b/VoidLink/Input/ControllerSupport.m @@ -1588,25 +1588,33 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - self->_gyroMode = self->_streamConfig.gyroMode; + // Stop all existing timers to ensure a clean state. + [self stopTimerForAllControllers]; + // Read the user's final setting from the stream configuration. + self->_gyroMode = self->_streamConfig.gyroMode; + switch (self->_gyroMode) { case AlwaysController: - [self stopTimerForAllControllers]; - for(VoidController* voidController in self->_voidControllers.allValues) [self updateTimerStateForController:voidController]; - break; - case GyroModeAuto: - if(self->_voidControllers.count == 0) [self assignControllers]; - if(self->_voidControllers.count == 0 || ![self externalControllersHaveGyro]) nil; - else{ - [self stopTimerForAllControllers]; - for(VoidController* voidController in self->_voidControllers.allValues) [self updateTimerStateForController:voidController]; + for (VoidController* voidController in self->_voidControllers.allValues) { + [self updateTimerStateForController:voidController]; } break; - case GyroModeOff: - [self stopTimerForAllControllers]; + case GyroModeAuto: + if ([self externalControllersHaveGyro]) { + for (VoidController* voidController in self->_voidControllers.allValues) { + [self updateTimerStateForController:voidController]; + } + } else { + [self updateTimerStateForController:self->_oscController]; + } break; - default: + + case AlwaysDevice: + [self updateTimerStateForController:self->_oscController]; + break; + + case GyroModeOff: break; } }); From 0fef5a389da3cbd2ad39dd4dc9c549d0a27e0e8f Mon Sep 17 00:00:00 2001 From: Acaki Date: Wed, 9 Jul 2025 17:39:55 +0800 Subject: [PATCH 5/8] fix: auto gyro mode not working for controllers without gyro --- VoidLink/Input/ControllerSupport.m | 130 +++++++++++++---------------- 1 file changed, 56 insertions(+), 74 deletions(-) diff --git a/VoidLink/Input/ControllerSupport.m b/VoidLink/Input/ControllerSupport.m index 4dc49449..6bf4d96d 100644 --- a/VoidLink/Input/ControllerSupport.m +++ b/VoidLink/Input/ControllerSupport.m @@ -768,6 +768,13 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; if (controller.battery) { capabilities |= LI_CCAP_BATTERY_STATE; } + + bool controllerLacksGyro = (capabilities & LI_CCAP_GYRO) == 0; + if ((_gyroMode == AlwaysDevice) || (_gyroMode == GyroModeAuto && controllerLacksGyro)) + { + type = LI_CTYPE_PS; + capabilities |= LI_CCAP_GYRO | LI_CCAP_ACCEL; + } } } else { @@ -1172,9 +1179,11 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; voidController.reportRateHz = 120; } if (@available(iOS 14.0, *)) { - if(voidController.gamepad.motion.hasRotationRate) [voidController.motionTypes addObject:@(LI_MOTION_TYPE_GYRO)]; - voidController.hasGyroscope = YES; - voidController.reportRateHz = 120; + if(voidController.gamepad.motion.hasRotationRate) { + [voidController.motionTypes addObject:@(LI_MOTION_TYPE_GYRO)]; + voidController.hasGyroscope = YES; + voidController.reportRateHz = 120; + } } // If this is player 0, it shares state with the OSC @@ -1201,7 +1210,6 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; [self initializeControllerHaptics:voidController]; } - -(VoidController* )assignController:(GCController*)controller { NSLog(@"run assignController"); @@ -1355,46 +1363,17 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; Log(LOG_I, @"Number of supported controllers connected: %d", [ControllerSupport getGamepadCount]); Log(LOG_I, @"Multi-controller: %d", _multiController); - - if (@available(iOS 14.0, tvOS 14.0, *)) { - switch(_gyroMode){ - case GyroModeOff: - [self stopTimerForAllControllers]; - break; - case GyroModeAuto: - [self stopTimerForAllControllers]; - [_voidControllers removeAllObjects]; - [self assignControllers]; - //if(_voidControllers.count == 0) [self assignControllers]; - if(_voidControllers.count == 0 || ![self externalControllersHaveGyro]) [self updateTimerStateForController:_oscController]; - else for(VoidController* controller in _voidControllers.allValues) [self updateTimerStateForController:controller]; - // to be filled - break; - case AlwaysDevice: - //[self stopTimerForController:_oscController]; - for(VoidController* controller in _voidControllers.allValues){ - [self stopTimerForController:controller]; - } - [self updateTimerStateForController:_oscController]; - break; - case AlwaysController: - [self stopTimerForAllControllers]; - for(VoidController* controller in _voidControllers.allValues) [self updateTimerStateForController:controller]; - break; - } - } - - + [self applyGyroModeSetting]; [self assignControllers]; - - NSLog(@"controllerNumbers: %d", _controllerNumbers); for(VoidController* controller in _voidControllers.allValues){ NSLog(@"controller obj in dict: %@", controller); } + + [self updateFinished:_oscController]; } -(id)initWithConfig:(StreamConfiguration*)streamConfig delegate:(id)delegate @@ -1572,52 +1551,19 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; return false; } --(void)connectionEstablished{ +-(void)connectionEstablished { if (_oscEnabled) { - [self updateTimerStateForController:self->_oscController]; [self setButtonFlag:self->_oscController flags:A_FLAG]; [self updateFinished:self->_oscController]; [self clearButtonFlag:self->_oscController flags:A_FLAG]; [self updateFinished:self->_oscController]; } - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - if(self->_oscEnabled){ - [self updateTimerStateForController:self->_oscController]; - } + self->_gyroMode = self->_streamConfig.gyroMode; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - // Stop all existing timers to ensure a clean state. - [self stopTimerForAllControllers]; - - // Read the user's final setting from the stream configuration. - self->_gyroMode = self->_streamConfig.gyroMode; - - switch (self->_gyroMode) { - case AlwaysController: - for (VoidController* voidController in self->_voidControllers.allValues) { - [self updateTimerStateForController:voidController]; - } - break; - case GyroModeAuto: - if ([self externalControllersHaveGyro]) { - for (VoidController* voidController in self->_voidControllers.allValues) { - [self updateTimerStateForController:voidController]; - } - } else { - [self updateTimerStateForController:self->_oscController]; - } - break; - - case AlwaysDevice: - [self updateTimerStateForController:self->_oscController]; - break; - - case GyroModeOff: - break; - } - }); + [self applyGyroModeSetting]; }); } @@ -1636,6 +1582,42 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; } } +- (void)applyGyroModeSetting { + // Stop all timers to ensure a clean slate before applying the new setting. + [self stopTimerForAllControllers]; + + switch(_gyroMode) { + case AlwaysController: + // Activate timers only for physical controllers. + for (VoidController* voidController in _voidControllers.allValues) { + [self updateTimerStateForController:voidController]; + } + break; + + case GyroModeAuto: + // Prefer physical controller gyros if they exist. + if ([self externalControllersHaveGyro]) { + for (VoidController* voidController in _voidControllers.allValues) { + [self updateTimerStateForController:voidController]; + } + } else { + // Otherwise, fall back to the device gyro. + [self updateTimerStateForController:self->_oscController]; + } + break; + + case AlwaysDevice: + // Always start the device gyro in this mode. + [self updateTimerStateForController:self->_oscController]; + break; + + case GyroModeOff: + // Do nothing, all timers are already stopped. + break; + } +} + + -(void) cleanup { [[NSNotificationCenter defaultCenter] removeObserver:_controllerConnectObserver]; From c491132c09576c34b67d6f7a00b22e09af15d84e Mon Sep 17 00:00:00 2001 From: Acaki Date: Wed, 9 Jul 2025 20:28:13 +0800 Subject: [PATCH 6/8] fix: need to touch any buttons on physical controller for it to show up on host --- VoidLink/Input/ControllerSupport.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/VoidLink/Input/ControllerSupport.m b/VoidLink/Input/ControllerSupport.m index 6bf4d96d..4091a16a 100644 --- a/VoidLink/Input/ControllerSupport.m +++ b/VoidLink/Input/ControllerSupport.m @@ -1552,6 +1552,10 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; } -(void)connectionEstablished { + for (VoidController* voidController in _voidControllers.allValues) { + [self updateFinished:voidController]; + } + if (_oscEnabled) { [self setButtonFlag:self->_oscController flags:A_FLAG]; [self updateFinished:self->_oscController]; From e93d031f867ec1b5633efe582fcb82d8f8633895 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Thu, 10 Jul 2025 12:52:51 +0800 Subject: [PATCH 7/8] - make oscEnabled when gyroMode is not off - make it unable to hot switch oscEnabled to false in streaming --- VoidLink/Input/ControllerSupport.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VoidLink/Input/ControllerSupport.m b/VoidLink/Input/ControllerSupport.m index 4091a16a..47f1d2b2 100644 --- a/VoidLink/Input/ControllerSupport.m +++ b/VoidLink/Input/ControllerSupport.m @@ -1334,7 +1334,7 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; DataManager* dataMan = [[DataManager alloc] init]; TemporarySettings* currentSettings = [dataMan getSettings]; - _oscEnabled = (OnScreenControlsLevel)[[dataMan getSettings].onscreenControls integerValue] != OnScreenControlsLevelOff; + _oscEnabled = _oscEnabled || (OnScreenControlsLevel)[currentSettings.onscreenControls integerValue] != OnScreenControlsLevelOff || streamConfig.gyroMode != GyroModeOff; _gyroSensitivity = currentSettings.gyroSensitivity.floatValue; } From 09349ebdef377d3839fdf07a3a925e3a7a80bb00 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Thu, 10 Jul 2025 22:10:48 +0800 Subject: [PATCH 8/8] disable gyro mode selector when it's unable to perform in-stream hot switch --- VoidLink/Input/ControllerSupport.h | 2 ++ VoidLink/Input/ControllerSupport.m | 13 ++++++++++--- VoidLink/ViewControllers/MainFrameViewController.m | 2 ++ .../ViewControllers/StreamFrameViewController.h | 1 + .../ViewControllers/StreamFrameViewController.m | 4 ++++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/VoidLink/Input/ControllerSupport.h b/VoidLink/Input/ControllerSupport.h index 6a26b31f..bde2d8e2 100644 --- a/VoidLink/Input/ControllerSupport.h +++ b/VoidLink/Input/ControllerSupport.h @@ -21,6 +21,8 @@ @interface ControllerSupport : NSObject +@property (readonly) bool shallDisableGyroHotSwitch; + -(id) initWithConfig:(StreamConfiguration*)streamConfig delegate:(id)delegate; -(void) connectionEstablished; diff --git a/VoidLink/Input/ControllerSupport.m b/VoidLink/Input/ControllerSupport.m index 47f1d2b2..7a5bd8d6 100644 --- a/VoidLink/Input/ControllerSupport.m +++ b/VoidLink/Input/ControllerSupport.m @@ -25,6 +25,13 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; +@interface ControllerSupport() + +@property (assign,nonatomic) bool shallDisableGyroHotSwitch; + +@end + + @implementation ControllerSupport { id _controllerConnectObserver; id _controllerDisconnectObserver; @@ -1388,7 +1395,6 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; _activeGCControllers = [[NSMutableSet alloc] init]; _controllerNumbers = 0; - _captureMouse = (streamConfig.localMousePointerMode == 0); if (@available(iOS 14.0, tvOS 14.0, *)) { for (GCMouse* mouse in [GCMouse mice]) { @@ -1538,8 +1544,8 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; [self assignControllers]; - //[self updateConfig:streamConfig delegate:delegate]; - + _shallDisableGyroHotSwitch = streamConfig.gyroMode == GyroModeOff && _voidControllers.allValues.count == 0; + NSLog(@"shallDisableGyroHotSwitch %d", _shallDisableGyroHotSwitch); return self; } @@ -1551,6 +1557,7 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; return false; } + -(void)connectionEstablished { for (VoidController* voidController in _voidControllers.allValues) { [self updateFinished:voidController]; diff --git a/VoidLink/ViewControllers/MainFrameViewController.m b/VoidLink/ViewControllers/MainFrameViewController.m index 8d1fc166..b7fbc4dc 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.m +++ b/VoidLink/ViewControllers/MainFrameViewController.m @@ -1135,10 +1135,12 @@ static NSMutableSet* hostList; [settingsViewController.codecSelector setEnabled:!_settingsExpandedInStreamView]; [settingsViewController.yuv444Switch setEnabled:!_settingsExpandedInStreamView]; [settingsViewController.hdrSwitch setEnabled:!_settingsExpandedInStreamView]; + [settingsViewController.gyroModeSelector setEnabled:!_settingsExpandedInStreamView || ![streamFrameViewController shallDisableGyroHotSwitch]]; [settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.framepacingStack]; [settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.citrixX1MouseStack]; [settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.externalDisplayModeStack]; [settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.audioConfigStack]; + } - (void)revealController:(SWRevealViewController *)revealController didMoveToPosition:(FrontViewPosition)position { diff --git a/VoidLink/ViewControllers/StreamFrameViewController.h b/VoidLink/ViewControllers/StreamFrameViewController.h index a71c0d64..47c6c06c 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.h +++ b/VoidLink/ViewControllers/StreamFrameViewController.h @@ -32,5 +32,6 @@ -(void)updatePreferredDisplayMode:(BOOL)streamActive; -(void)reConfigStreamViewRealtime; - (void)setUserInteractionEnabledForStreamView:(bool)enabled; +- (bool)shallDisableGyroHotSwitch; @end diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index 4d0de6fc..85188a3c 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -670,6 +670,10 @@ } } +- (bool)shallDisableGyroHotSwitch{ + return _controllerSupport.shallDisableGyroHotSwitch; +} + - (BOOL) isAirPlaying{ return _extWindow != nil && _extWindow.hidden == NO; }