diff --git a/VoidLink.xcodeproj/project.pbxproj b/VoidLink.xcodeproj/project.pbxproj index 28712f87..c008dd6d 100644 --- a/VoidLink.xcodeproj/project.pbxproj +++ b/VoidLink.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 180345E12E819877004AF36F /* libopus-sim.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 180345E02E819877004AF36F /* libopus-sim.a */; }; - 180E6E372E93BFDB0067C73A /* CountdownAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180E6E362E93BFDB0067C73A /* CountdownAlertController.swift */; }; + 180E6E372E93BFDB0067C73A /* AlertControllerUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180E6E362E93BFDB0067C73A /* AlertControllerUtil.swift */; }; 1817B61E2E6FD04D000D938D /* SafeTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1817B61D2E6FD04D000D938D /* SafeTimer.swift */; }; 182760F72E83E2EA00EC1C5C /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = 182760F62E83E2EA00EC1C5C /* DequeModule */; }; 182760FA2E83E57D00EC1C5C /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = 182760F92E83E57D00EC1C5C /* DequeModule */; }; @@ -164,7 +164,7 @@ /* Begin PBXFileReference section */ 180345E02E819877004AF36F /* libopus-sim.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libopus-sim.a"; sourceTree = ""; }; - 180E6E362E93BFDB0067C73A /* CountdownAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountdownAlertController.swift; sourceTree = ""; }; + 180E6E362E93BFDB0067C73A /* AlertControllerUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertControllerUtil.swift; sourceTree = ""; }; 1817B61D2E6FD04D000D938D /* SafeTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeTimer.swift; sourceTree = ""; }; 1829CE3D2E2154DF0074A99A /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; 1829CE3E2E2154DF0074A99A /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; @@ -643,7 +643,7 @@ 847D2FDA2DDA015F00937A62 /* MenuSectionView.m */, FBDE86E419F82297001C18A8 /* UIAppView.h */, FBDE86E519F82297001C18A8 /* UIAppView.m */, - 180E6E362E93BFDB0067C73A /* CountdownAlertController.swift */, + 180E6E362E93BFDB0067C73A /* AlertControllerUtil.swift */, 1888C1902E24C2C800108730 /* AboutView.swift */, 1888C1912E24C33100108730 /* AboutViewController.swift */, FB89463719F6473800339C8A /* Launch Screen.xib */, @@ -1068,7 +1068,7 @@ 840751B32BF49C050096B657 /* CustomTapGestureRecognizer.m in Sources */, 187F4B282E04658A00EECC59 /* WidgetPanelStackView.m in Sources */, FB4678FA1A55FFAD00377732 /* DiscoveryManager.m in Sources */, - 180E6E372E93BFDB0067C73A /* CountdownAlertController.swift in Sources */, + 180E6E372E93BFDB0067C73A /* AlertControllerUtil.swift in Sources */, 841BAC922DE35D6B00DA639E /* ThemeManager.m in Sources */, 98D5856D1C0EA79600F6CC00 /* TemporaryHost.m in Sources */, FB89463519F646E200339C8A /* MainFrameViewController.m in Sources */, diff --git a/VoidLink/CountdownAlertController.swift b/VoidLink/AlertControllerUtil.swift similarity index 78% rename from VoidLink/CountdownAlertController.swift rename to VoidLink/AlertControllerUtil.swift index 74f262fd..0fc2452b 100644 --- a/VoidLink/CountdownAlertController.swift +++ b/VoidLink/AlertControllerUtil.swift @@ -1,5 +1,5 @@ // -// CountdownAlertController.swift +// AlertControllerUtil.swift // VoidLink // // Created by Weimin on 2025/10/6. @@ -7,7 +7,7 @@ // import UIKit -@objc class CountdownAlertController: NSObject { +@objc class AlertControllerUtil: NSObject { /// 类方法,直接在任何 UIViewController 上显示倒计时弹窗 /// - Parameters: @@ -19,7 +19,8 @@ import UIKit /// - completion: 点击确认或倒计时结束的回调 @objc public static var actionCancelled:Bool = false - + @objc public static var alertController:UIAlertController = UIAlertController() + @objc class func showAlert( in viewController: UIViewController, title: String?, @@ -27,11 +28,12 @@ import UIKit withCancel: Bool, buttonTitle: String, countdown: Int, + action: (() -> Void)? = nil, completion: (() -> Void)? = nil ) { var remainingSeconds = countdown - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) let confirmAction = UIAlertAction(title: "\(remainingSeconds)", style: .default) { _ in actionCancelled = false @@ -45,15 +47,15 @@ import UIKit confirmAction.isEnabled = false - if withCancel {alert.addAction(cancelAction)} - alert.addAction(confirmAction) + if withCancel {alertController.addAction(cancelAction)} + if buttonTitle != "" {alertController.addAction(confirmAction)} if(countdown == 0) { confirmAction.setValue(buttonTitle, forKey: "title") - confirmAction.isEnabled = true + confirmAction.isEnabled = buttonTitle != "" } - viewController.present(alert, animated: true, completion: nil) + viewController.present(alertController, animated: true, completion: action) if(countdown == 0) {return} diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard index eb7af15e..957fd9b1 100644 --- a/VoidLink/Base.lproj/iPad.storyboard +++ b/VoidLink/Base.lproj/iPad.storyboard @@ -1697,6 +1697,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1721,6 +1781,12 @@ + + + + + + @@ -2667,7 +2733,7 @@ - + @@ -2676,13 +2742,13 @@ - + - + - + diff --git a/VoidLink/Base.lproj/iPhone.storyboard b/VoidLink/Base.lproj/iPhone.storyboard index fcb9069f..15ca7782 100644 --- a/VoidLink/Base.lproj/iPhone.storyboard +++ b/VoidLink/Base.lproj/iPhone.storyboard @@ -1,9 +1,9 @@ - + - + @@ -14,7 +14,7 @@ - + @@ -81,7 +81,7 @@ - + @@ -387,7 +387,7 @@ - + @@ -409,7 +409,7 @@ - + @@ -431,7 +431,7 @@ - + @@ -448,7 +448,7 @@ - + @@ -470,7 +470,7 @@ - + @@ -492,7 +492,7 @@ - + @@ -514,7 +514,7 @@ - + @@ -536,7 +536,7 @@ - + @@ -558,7 +558,7 @@ - + @@ -580,7 +580,7 @@ - + @@ -602,7 +602,7 @@ - + @@ -624,7 +624,7 @@ - + @@ -911,7 +911,7 @@ - + @@ -925,10 +925,10 @@ - + - + @@ -976,15 +976,15 @@ - @@ -1122,15 +1122,15 @@ - @@ -1142,15 +1142,15 @@ - @@ -1162,15 +1162,15 @@ - @@ -1182,15 +1182,15 @@ - @@ -1202,15 +1202,15 @@ - @@ -1350,15 +1350,15 @@ - @@ -1717,15 +1717,15 @@ - @@ -1782,17 +1782,17 @@ - - + - + @@ -2410,15 +2410,15 @@ - @@ -2430,15 +2430,15 @@ - @@ -2491,17 +2491,17 @@ - + - @@ -2530,6 +2530,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2554,6 +2614,12 @@ + + + + + + diff --git a/VoidLink/Database/DataManager.h b/VoidLink/Database/DataManager.h index f3d956ee..d49d42c8 100644 --- a/VoidLink/Database/DataManager.h +++ b/VoidLink/Database/DataManager.h @@ -55,7 +55,7 @@ typedef NS_ENUM(NSInteger, HapticEnginePreference) { RumbleOff }; -typedef NS_ENUM(NSInteger, ControllerToMouseStick) { +typedef NS_ENUM(NSInteger, ControllerMouseStick) { LeftStickToMouse, RightStickToMouse, }; @@ -142,6 +142,9 @@ typedef NS_ENUM(NSInteger, WidgetSizeTransition) { leftClickDelayMs:(CGFloat)leftClickDelayMs settingsMenuOffset:(CGFloat)settingsMenuOffset passthroughGestures:(BOOL)passthroughGestures + mapControllerToMouse:(BOOL)mapControllerToMouse + controllerMousePointerVelocity:(CGFloat)controllerMousePointerVelocity + controllerMouseExpo:(CGFloat)controllerMouseExpo backgroundSessionTimer:(NSInteger)backgroundSessionTimer; - (NSArray*) getHosts; diff --git a/VoidLink/Database/DataManager.m b/VoidLink/Database/DataManager.m index d66a713f..29345d19 100644 --- a/VoidLink/Database/DataManager.m +++ b/VoidLink/Database/DataManager.m @@ -120,6 +120,9 @@ leftClickDelayMs:(CGFloat)leftClickDelayMs settingsMenuOffset:(CGFloat)settingsMenuOffset passthroughGestures:(BOOL)passthroughGestures + mapControllerToMouse:(BOOL)mapControllerToMouse + controllerMousePointerVelocity:(CGFloat)controllerMousePointerVelocity + controllerMouseExpo:(CGFloat)controllerMouseExpo backgroundSessionTimer:(NSInteger)backgroundSessionTimer{ [_managedObjectContext performBlockAndWait:^{ @@ -190,6 +193,9 @@ settingsToSave.leftClickDelayMs = [NSNumber numberWithFloat:leftClickDelayMs]; settingsToSave.settingsMenuOffset = [NSNumber numberWithFloat:settingsMenuOffset]; settingsToSave.passthroughGestures = passthroughGestures; + settingsToSave.mapControllerToMouse = mapControllerToMouse; + settingsToSave.controllerMousePointerVelocity = [NSNumber numberWithFloat:controllerMousePointerVelocity]; + settingsToSave.controllerMouseExpo = [NSNumber numberWithFloat:controllerMouseExpo]; settingsToSave.rememberFoldState = rememberFoldState; [self saveData]; }]; diff --git a/VoidLink/Database/TemporarySettings.h b/VoidLink/Database/TemporarySettings.h index 5cb31203..3cb49b8e 100644 --- a/VoidLink/Database/TemporarySettings.h +++ b/VoidLink/Database/TemporarySettings.h @@ -95,6 +95,13 @@ @property (nonatomic, retain) NSNumber * relativeTouchSlideThreshold; @property (nonatomic, retain) NSNumber * leftClickDelayMs; @property (nonatomic, retain) NSNumber * settingsMenuOffset; +@property (nonatomic, retain) NSNumber * controllerMouseLeftButton; +@property (nonatomic, retain) NSNumber * controllerMouseRightButton; +@property (nonatomic, retain) NSNumber * controllerMouseStick; +@property (nonatomic, retain) NSNumber * controllerMouseSwitch; +@property (nonatomic) BOOL mapControllerToMouse; +@property (nonatomic, retain) NSNumber * controllerMousePointerVelocity; +@property (nonatomic, retain) NSNumber * controllerMouseExpo; - (id) initFromSettings:(Settings*)settings; diff --git a/VoidLink/Database/TemporarySettings.m b/VoidLink/Database/TemporarySettings.m index b177e540..2ba9712d 100644 --- a/VoidLink/Database/TemporarySettings.m +++ b/VoidLink/Database/TemporarySettings.m @@ -151,6 +151,14 @@ self.ctrlDownForPinch = settings.ctrlDownForPinch; self.settingsMenuOffset = settings.settingsMenuOffset; self.passthroughGestures = settings.passthroughGestures; + self.mapControllerToMouse = settings.mapControllerToMouse; + self.controllerMouseLeftButton = settings.controllerMouseLeftButton; + self.controllerMouseRightButton = settings.controllerMouseRightButton; + self.controllerMouseSwitch = settings.controllerMouseSwitch; + self.controllerMouseStick = settings.controllerMouseStick; + self.controllerMousePointerVelocity = settings.controllerMousePointerVelocity; + self.controllerMouseExpo = settings.controllerMouseExpo; + #endif return self; diff --git a/VoidLink/Input/ControllerSupport.m b/VoidLink/Input/ControllerSupport.m index c35556e7..3b14b192 100644 --- a/VoidLink/Input/ControllerSupport.m +++ b/VoidLink/Input/ControllerSupport.m @@ -50,12 +50,21 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; float accumulatedScrollX; float accumulatedScrollY; - ControllerToMouseStick controllerToMouseStick; - bool _stickToMouseEnabled; + int _controllerMouseSwitch; + bool _mouseSwitchButtonPressed; + bool _mouseSwitchButtonBeingClicked; + NSTimeInterval mouseSwitchDownTimestamp; + int _controllerMouseLeftButton; + int _controllerMouseRightButton; + ControllerMouseStick _controllerMouseStick; + bool _mapControllerToMouse; + bool _controllerMouseEnabledFlag; float stickToMouseInputX; float stickToMouseInputY; - float stickToMouseExpo; - float stickToMouseVelocity; + float stickToWheelInputX; + float stickToWheelInputY; + float _stickToMouseExpo; + float _stickToMouseVelocity; CADisplayLink *_displayLink; @@ -939,19 +948,19 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; } } -double rc_expo(double x, double gamma) { +double rc_expo(double x, double expo) { if (x == 0.0) return 0.0; double ax = fabs(x); - double y = pow(ax, gamma); + double y = pow(ax, expo); return x > 0 ? y : -y; } - (void)sendStickToMouseMoveEventWithStickX:(float)stickX stickY:(float)stickY expo:(float)expo { - CGFloat mouseDeltaX = stickToMouseVelocity*rc_expo(stickX, expo); - CGFloat mouseDeltaY = stickToMouseVelocity*rc_expo(stickY, expo); - // NSLog(@"mouseDeltaX %f, mouseDeltaY %f, %f, %ld", mouseDeltaX, mouseDeltaY, CACurrentMediaTime(), (long)_displayLink.preferredFramesPerSecond); + CGFloat mouseDeltaX = _stickToMouseVelocity*rc_expo(stickX, expo); + CGFloat mouseDeltaY = _stickToMouseVelocity*rc_expo(stickY, expo); + NSLog(@"mouseDeltaX %f, mouseDeltaY %f, %f, %ld", mouseDeltaX, mouseDeltaY, CACurrentMediaTime(), (long)_displayLink.preferredFramesPerSecond); LiSendMouseMoveEvent(mouseDeltaX, -mouseDeltaY); } @@ -961,8 +970,26 @@ double rc_expo(double x, double gamma) { } - (void)displayLinkCallBack { - if(!_stickToMouseEnabled) return; - [self sendStickToMouseMoveEventWithStickX:stickToMouseInputX stickY:stickToMouseInputY expo:stickToMouseExpo]; + if(!_controllerMouseEnabledFlag){ + return; + } + NSTimeInterval delay = 0.5/_displayLink.preferredFramesPerSecond*NSEC_PER_SEC; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self sendStickToMouseMoveEventWithStickX:self->stickToMouseInputX stickY:self->stickToMouseInputY expo:self->_stickToMouseExpo]; + LiSendHighResScrollEvent(15*self->stickToWheelInputY); + LiSendHighResHScrollEvent(15*self->stickToWheelInputX); + }); +} + +- (void)sendControllerMouseSwitchClick:(VoidController*) voidController{ + [self setButtonFlag:voidController flags:self->_controllerMouseSwitch]; + _mouseSwitchButtonBeingClicked = true; + [self updateFinished:voidController]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.03*NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self clearButtonFlag:voidController flags:self->_controllerMouseSwitch]; + self->_mouseSwitchButtonBeingClicked = false; + [self updateFinished:voidController]; + }); } -(void) registerControllerCallbacks:(GCController*) controller @@ -1009,6 +1036,42 @@ double rc_expo(double x, double gamma) { } } + if(self->_swapABXYButtons){ + switch (self->_controllerMouseLeftButton) { + case ControllerButtonA: + self->_controllerMouseLeftButton = ControllerButtonB; + break; + case ControllerButtonB: + self->_controllerMouseLeftButton = ControllerButtonA; + break; + case ControllerButtonX: + self->_controllerMouseLeftButton = ControllerButtonY; + break; + case ControllerButtonY: + self->_controllerMouseLeftButton = ControllerButtonX; + break; + default: + break; + } + switch (self->_controllerMouseRightButton) { + case ControllerButtonA: + self->_controllerMouseRightButton = ControllerButtonB; + break; + case ControllerButtonB: + self->_controllerMouseRightButton = ControllerButtonA; + break; + case ControllerButtonX: + self->_controllerMouseRightButton = ControllerButtonY; + break; + case ControllerButtonY: + self->_controllerMouseRightButton = ControllerButtonX; + break; + default: + break; + } + } + + [ControllerUtil listenWithController:controller swapABXY:self->_swapABXYButtons handler:^(NSDictionary * buttonDict, GCExtendedGamepad * gamepad, GCControllerElement * element) { VoidController* voidController = [self->_voidControllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]]; short leftStickX, leftStickY; @@ -1017,18 +1080,75 @@ double rc_expo(double x, double gamma) { for(NSNumber* buttonFlagId in buttonDict){ GCControllerButtonInput * button = (GCControllerButtonInput *)buttonDict[buttonFlagId]; - UPDATE_BUTTON_FLAG(voidController, buttonFlagId.intValue, button.pressed); + if(self->_mapControllerToMouse){ + + if(button.pressed){ + if(buttonFlagId.intValue == self->_controllerMouseSwitch){ + self->_mouseSwitchButtonPressed = true; + self->mouseSwitchDownTimestamp = CACurrentMediaTime(); + } + } + + else{ + if(buttonFlagId.intValue == self->_controllerMouseSwitch && self->_mouseSwitchButtonPressed){ + if(CACurrentMediaTime()-self->mouseSwitchDownTimestamp>1){ + self->_controllerMouseEnabledFlag = !self->_controllerMouseEnabledFlag; + } + else [self sendControllerMouseSwitchClick:voidController]; + self->_mouseSwitchButtonPressed = false; + self->mouseSwitchDownTimestamp = 0; + } + } + + + + /* if(buttonFlagId.intValue == self->_controllerMouseSwitch){ + if(button.pressed){ + self->_mouseSwitchButtonPressed = true; + self->mouseSwitchDownTimestamp = CACurrentMediaTime(); + } + else if(self->_mouseSwitchButtonPressed){ + if(CACurrentMediaTime()-self->mouseSwitchDownTimestamp>1){ + self->_controllerMouseEnabledFlag = !self->_controllerMouseEnabledFlag; + self->_mouseSwitchButtonPressed = false; + self->mouseSwitchDownTimestamp = 0; + } + else [self sendControllerMouseSwitchClick:voidController]; + } + else self->_mouseSwitchButtonPressed = false; + } */ + } + else self->_controllerMouseEnabledFlag = false; + + if(self->_controllerMouseEnabledFlag){ + if(buttonFlagId.intValue == self->_controllerMouseLeftButton || buttonFlagId.intValue == self->_controllerMouseRightButton){ + if(buttonFlagId.intValue == self->_controllerMouseLeftButton) LiSendMouseButtonEvent(button.pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_LEFT); + if(buttonFlagId.intValue == self->_controllerMouseRightButton) LiSendMouseButtonEvent(button.pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_RIGHT); + UPDATE_BUTTON_FLAG(voidController, buttonFlagId.intValue, NO); + } + else if(buttonFlagId.intValue!=self->_controllerMouseSwitch) UPDATE_BUTTON_FLAG(voidController, buttonFlagId.intValue, button.pressed); + } + else if(buttonFlagId.intValue!=self->_controllerMouseSwitch) UPDATE_BUTTON_FLAG(voidController, buttonFlagId.intValue, button.pressed); } - leftStickX = self->controllerToMouseStick != LeftStickToMouse ? gamepad.leftThumbstick.xAxis.value * 0x7FFE : 0; - leftStickY = self->controllerToMouseStick != LeftStickToMouse ? gamepad.leftThumbstick.yAxis.value * 0x7FFE : 0;; + leftStickX = self->_controllerMouseEnabledFlag ? 0 :gamepad.leftThumbstick.xAxis.value * 0x7FFE; + leftStickY = self->_controllerMouseEnabledFlag ? 0 : gamepad.leftThumbstick.yAxis.value * 0x7FFE; - rightStickX = self->controllerToMouseStick != RightStickToMouse ? gamepad.rightThumbstick.xAxis.value * 0x7FFE : 0; - rightStickY = self->controllerToMouseStick != RightStickToMouse ? gamepad.rightThumbstick.yAxis.value * 0x7FFE : 0; - - self->stickToMouseInputX = self->controllerToMouseStick == LeftStickToMouse ? gamepad.leftThumbstick.xAxis.value : gamepad.rightThumbstick.xAxis.value; - self->stickToMouseInputY = self->controllerToMouseStick == LeftStickToMouse ? gamepad.leftThumbstick.yAxis.value : gamepad.rightThumbstick.yAxis.value; + rightStickX = self->_controllerMouseEnabledFlag ? 0 : gamepad.rightThumbstick.xAxis.value * 0x7FFE; + rightStickY = self->_controllerMouseEnabledFlag ? 0 : gamepad.rightThumbstick.yAxis.value * 0x7FFE; + if(self->_controllerMouseEnabledFlag){ + self->stickToMouseInputX = self->_controllerMouseStick == LeftStickToMouse ? gamepad.leftThumbstick.xAxis.value : gamepad.rightThumbstick.xAxis.value; + self->stickToMouseInputY = self->_controllerMouseStick == LeftStickToMouse ? gamepad.leftThumbstick.yAxis.value : gamepad.rightThumbstick.yAxis.value; + + self->stickToWheelInputX = self->_controllerMouseStick == LeftStickToMouse ? gamepad.rightThumbstick.xAxis.value: gamepad.leftThumbstick.xAxis.value; + self->stickToWheelInputY = self->_controllerMouseStick == LeftStickToMouse ? gamepad.rightThumbstick.yAxis.value: gamepad.leftThumbstick.yAxis.value; + } + else{ + self->stickToMouseInputX = 0; + self->stickToMouseInputY = 0; + } + leftTrigger = gamepad.leftTrigger.value * 0xFF; rightTrigger = gamepad.rightTrigger.value * 0xFF; @@ -1036,6 +1156,19 @@ double rc_expo(double x, double gamma) { [self updateRightStick:voidController x:rightStickX y:rightStickY]; [self updateTriggers:voidController left:leftTrigger right:rightTrigger]; [self updateFinished:voidController]; + + if (@available(iOS 14.0, *)) { + if (gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]) { + [self handleControllerTouchpad:voidController + touch:gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne] + index:0]; + } + if (gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]) { + [self handleControllerTouchpad:voidController + touch:gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo] + index:1]; + } + } }]; /* @@ -1485,15 +1618,28 @@ double rc_expo(double x, double gamma) { _oscEnabled = _oscEnabled || (OnScreenControlsLevel)[tempSettings.onscreenControls integerValue] != OnScreenControlsLevelOff || streamConfig.gyroMode != GyroModeOff; _gyroSensitivity = tempSettings.gyroSensitivity.floatValue; - _stickToMouseEnabled = true; - controllerToMouseStick = LeftStickToMouse; - stickToMouseExpo = 1.1; - stickToMouseVelocity = 21*60/tempSettings.framerate.intValue; + _mapControllerToMouse = tempSettings.mapControllerToMouse; + _controllerMouseSwitch = tempSettings.controllerMouseSwitch.intValue; + mouseSwitchDownTimestamp = 0; + _mouseSwitchButtonPressed = false; + _mouseSwitchButtonBeingClicked = false; + _controllerMouseStick = tempSettings.controllerMouseStick.intValue; + _controllerMouseLeftButton = tempSettings.controllerMouseLeftButton.intValue; + _controllerMouseRightButton = tempSettings.controllerMouseRightButton.intValue; + _stickToMouseExpo = tempSettings.controllerMouseExpo.floatValue; + _stickToMouseVelocity = tempSettings.controllerMousePointerVelocity.floatValue*60/tempSettings.framerate.intValue; stickToMouseInputX = 0; stickToMouseInputY = 0; + stickToWheelInputY = 0; [self stopDisplayLink]; - if(_stickToMouseEnabled){ + if(_mapControllerToMouse){ _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkCallBack)]; + if (@available(iOS 15.0, tvOS 15.0, *)) { + [_displayLink setPreferredFrameRateRange:CAFrameRateRangeMake(tempSettings.framerate.intValue,tempSettings.framerate.intValue, tempSettings.framerate.intValue)]; + } + else { + _displayLink.preferredFramesPerSecond = tempSettings.framerate.intValue; + } [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } } @@ -1688,6 +1834,7 @@ double rc_expo(double x, double gamma) { [self initializeControllerHaptics:_oscController]; _gyroMode = AlwaysDevice; + _controllerMouseEnabledFlag = false; [self updateCommonConfig:streamConfig]; for(VoidController* voidController in _voidControllers.allValues){ diff --git a/VoidLink/Input/ControllerUtil.swift b/VoidLink/Input/ControllerUtil.swift index c633d011..ef219438 100644 --- a/VoidLink/Input/ControllerUtil.swift +++ b/VoidLink/Input/ControllerUtil.swift @@ -43,7 +43,8 @@ import GameController case touchpad = 0x100000 case misc = 0x200000 - case leftTrigger, rightTrigger + case leftTrigger = 0x400000 + case rightTrigger = 0x800000 } diff --git a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents index c2253de9..038aaa24 100644 --- a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents +++ b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -29,6 +29,12 @@ + + + + + + @@ -57,6 +63,7 @@ + diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index 52ee70e5..39a22432 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -1399,6 +1399,29 @@ } } }, + "Continue" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "继续" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "繼續" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "繼續" + } + } + } + }, "Controller" : { "extractionState" : "manual", "localizations" : { @@ -1422,6 +1445,35 @@ } } }, + "controllerMouseExpoStackTip" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The larger this value is, the flatter the curve becomes near the zero point (center). When set to 1, it is completely linear." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "该值越大,接近零点(中心点)的曲线越平缓。为 1 时则为完全线性。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "該值越大,接近零點(中心點)的曲線越平緩。為 1 時則為完全線性。" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "該值越大,接近零點(中心點)的曲線越平緩。為 1 時則為完全線性。" + } + } + } + }, "Create New" : { "extractionState" : "manual", "localizations" : { @@ -2223,6 +2275,29 @@ } } }, + "Finished" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "完成" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "完成" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "完成" + } + } + } + }, "Finished!" : { "extractionState" : "manual", "localizations" : { @@ -3151,6 +3226,29 @@ } } }, + "Map controller to mouse" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "手柄映射为鼠标" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "手柄映射為滑鼠" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "手柄映射為滑鼠" + } + } + } + }, "micPermissionTip" : { "extractionState" : "manual", "localizations" : { @@ -3347,6 +3445,29 @@ } } }, + "Move the stick you wish to use for mouse control. Another stick will be used for vertical & horizontal scroll." : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "移动用于控制鼠标指针的摇杆。另一侧的摇杆将用于控制垂直、水平滚动。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "移動用於控制滑鼠指標的搖桿。另一側的搖桿將用於控制垂直、水平滾動。" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "移動用於控制滑鼠指標的搖桿。另一側的搖桿將用於控制垂直、水平滾動。" + } + } + } + }, "name" : { "extractionState" : "manual", "localizations" : { @@ -4136,19 +4257,19 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "- App切后台时自动开启画中画, 需要打开 iOS 设置-> 多任务与手势 -> 自动开启画中画。\n\n- 画中画可在工具箱菜单中手动开启。\n\n- 画中画时长由 “其他 -> 后台会话” 设置项控制\n\n- 仅 iOS15+ 支持画中画。" } }, "zh-Hant" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "- App 切到背景時會自動啟用子母畫面,需開啟 iOS 「設定」→「多工與手勢」→「自動啟用子母畫面」。\n\n- 子母畫面也可透過工具箱選單手動啟用。\n\n- 子母畫面顯示時長由「其他」→「背景執行」設定項目控制。\n\n- 子母畫面功能自 iOS 15 起支援。" } }, "zh-HK" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "- App 切到背景時會自動啟用子母畫面,需開啟 iOS 「設定」→「多工與手勢」→「自動啟用子母畫面」。\n\n- 子母畫面也可透過工具箱選單手動啟用。\n\n- 子母畫面顯示時長由「其他」→「背景執行」設定項目控制。\n\n- 子母畫面功能自 iOS 15 起支援。" } } @@ -4327,7 +4448,7 @@ }, "zh-Hant" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "觸控靈敏度:調整螢幕右半邊觸控點的移動速度,讓您可以控制遊戲內攝影機的靈敏度(適用於《原神》和《崩壞:星穹鐵道》)。詳情請參閱線上文件。" } }, @@ -4362,6 +4483,75 @@ } } }, + "Press a button for switching to controller mouse mode (long press for 1 second during streaming)" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按下用于切换手柄鼠标模式的按键(串流时长按1秒切换)" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "按下用於切換手柄滑鼠模式的按鍵(串流時長按 1 秒切換)" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "按下用於切換手柄滑鼠模式的按鍵(串流時長按 1 秒切換)" + } + } + } + }, + "Press the button for mouse left button" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按下用于操作鼠标左键的按键" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "按下用於操作滑鼠左鍵的按鍵" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "按下用於操作滑鼠左鍵的按鍵" + } + } + } + }, + "Press the button for mouse right button" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按下用于操作鼠标右键的按键" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "按下用於操作滑鼠右鍵的按鍵" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "按下用於操作滑鼠右鍵的按鍵" + } + } + } + }, "Profile %@ duplicated from current layout" : { "extractionState" : "manual", "localizations" : { diff --git a/VoidLink/ViewControllers/MainFrameViewController.m b/VoidLink/ViewControllers/MainFrameViewController.m index e1e87060..16742432 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.m +++ b/VoidLink/ViewControllers/MainFrameViewController.m @@ -78,6 +78,10 @@ TemporaryApp * launchedApp; NSTimer *_foregroundHostUpdateTimer; + + id _controllerConnectObserver; + id _controllerDisconnectObserver; + #if TARGET_OS_TV UITapGestureRecognizer* _menuRecognizer; @@ -1626,6 +1630,25 @@ static NSMutableSet* hostList; __unused UIView *view = viewController.view; snapshot = nil; + + _controllerConnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Log(LOG_I, @"Controller connected!"); + GCController* controller = note.object; + if(controller){ + if (@available(iOS 14.0, tvOS 14.0, *)) { + for (GCControllerElement* element in controller.physicalInputProfile.allElements) { + element.preferredSystemGestureState = GCSystemGestureStateDisabled; + } + } + } + }]; + + _controllerDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + Log(LOG_I, @"Controller disconnected!"); + + GCController* controller = note.object; + [self unregisterControllerCallbacks:controller]; + }]; } -(void)viewDidLayoutSubviews{ @@ -1837,7 +1860,6 @@ static NSMutableSet* hostList; if([self isFirstLaunch])[self helpButtonTapped]; } - - (void)viewWillDisappear:(BOOL)animated{ NSLog(@"willDisappear"); [super viewWillDisappear:animated]; @@ -1893,6 +1915,8 @@ static NSMutableSet* hostList; // Remove our lifetime observers to avoid triggering them // while streaming + [[NSNotificationCenter defaultCenter] removeObserver:_controllerConnectObserver]; + [[NSNotificationCenter defaultCenter] removeObserver:_controllerDisconnectObserver]; [[NSNotificationCenter defaultCenter] removeObserver:self]; } @@ -2296,4 +2320,14 @@ static NSMutableSet* hostList; } +-(void) unregisterControllerCallbacks:(GCController*) controller +{ + if (controller != NULL) { + controller.controllerPausedHandler = NULL; + if (controller.extendedGamepad != NULL) { + controller.extendedGamepad.valueChangedHandler = NULL; + } + } +} + @end diff --git a/VoidLink/ViewControllers/SettingsViewController.h b/VoidLink/ViewControllers/SettingsViewController.h index 3a3805fd..59a41af0 100644 --- a/VoidLink/ViewControllers/SettingsViewController.h +++ b/VoidLink/ViewControllers/SettingsViewController.h @@ -214,6 +214,15 @@ @property (strong, nonatomic) IBOutlet UIStackView *passthroughGesturesStack; @property (strong, nonatomic) IBOutlet UISwitch *passthroughGesturesSwitch; +@property (strong, nonatomic) IBOutlet UIStackView *controllerToMouseStack; +@property (strong, nonatomic) IBOutlet UISwitch *controllerToMouseSwitch; + +@property (strong, nonatomic) IBOutlet UIStackView *controllerMouseVelocityStack; +@property (strong, nonatomic) IBOutlet UISlider *controllerMouseVelocitySlider; + +@property (strong, nonatomic) IBOutlet UIStackView *controllerMouseExpoStack; +@property (strong, nonatomic) IBOutlet UISlider *controllerMouseExpoSlider; + @property (strong, nonatomic) IBOutlet UIStackView *testStack; @property (nonatomic, strong) MicHandler *micHandler; diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 2829f8eb..620dea33 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -55,6 +55,8 @@ MenuSectionView *otherSection; MenuSectionView *experimentalSection; NSMutableSet* hiddenStacks; + + GCController *capturedController; } @dynamic overrideUserInterfaceStyle; @@ -450,14 +452,15 @@ BOOL isCustomResolution(int resolutionSelected) { [self->motionControlSection setExpanded:self->motionControlSection.expandable]; __weak typeof(self) weakSelf = self; self->motionControlSection.lockedSectionHandler = ^{ - [CountdownAlertController showAlertIn:weakSelf + [AlertControllerUtil showAlertIn:weakSelf title:[LocalizationHelper localizedStringForKey:@"Tips"] message:[LocalizationHelper localizedStringForKey:@"Tap 'OK' to set on-screen widget to 'Custom' and enable motion control."] withCancel:YES buttonTitle:[LocalizationHelper localizedStringForKey:@"OK"] countdown:0 + action:^{} completion:^{ - if(!CountdownAlertController.actionCancelled){ + if(!AlertControllerUtil.actionCancelled){ [weakSelf.onScreenWidgetSelector setSelectedSegmentIndex:OnScreenControlsLevelCustom]; if(weakSelf.touchModeSelector1.selectedSegmentIndex == NativeTouch){ [weakSelf.enableOswForNativeTouchSwitch setOn:true]; @@ -760,6 +763,9 @@ BOOL isCustomResolution(int resolutionSelected) { [self addSetting:self.emulatedControllerTypeStack ofId:@"emulatedControllerTypeStack" withInfoTag:YES withDynamicLabel:NO to:controllerSection]; [self addSetting:self.gyroModeStack ofId:@"gyroModeStack" withInfoTag:YES withDynamicLabel:YES to:controllerSection]; [self addSetting:self.gyroSensitivityStack ofId:@"gyroSensitivityStack" withInfoTag:NO withDynamicLabel:YES to:controllerSection]; + [self addSetting:self.controllerToMouseStack ofId:@"controllerToMouseStack" withInfoTag:NO withDynamicLabel:YES to:controllerSection]; + [self addSetting:self.controllerMouseVelocityStack ofId:@"controllerMouseVelocityStack" withInfoTag:NO withDynamicLabel:YES to:controllerSection]; + [self addSetting:self.controllerMouseExpoStack ofId:@"controllerMouseExpoStack" withInfoTag:YES withDynamicLabel:YES to:controllerSection]; [controllerSection addToParentStack:_parentStack]; motionControlSection = [[MenuSectionView alloc] init]; @@ -1339,7 +1345,10 @@ BOOL isCustomResolution(int resolutionSelected) { tipText = [LocalizationHelper localizedStringForKey:@"ctrlDownForPinchStackTip"]; showOnlineDocAction = false; } - + if([sender.superview.accessibilityIdentifier isEqualToString: @"controllerMouseExpoStack"]){ + tipText = [LocalizationHelper localizedStringForKey:@"controllerMouseExpoStackTip"]; + showOnlineDocAction = false; + } UIAlertController *tipsAlertController = [UIAlertController alertControllerWithTitle: [LocalizationHelper localizedStringForKey:@"Tips"] message:tipText preferredStyle:UIAlertControllerStyleAlert]; @@ -1965,6 +1974,18 @@ BOOL isCustomResolution(int resolutionSelected) { [self.sendDummyEventSwitch setOn:self->tempSettings.sendDummyEvent];// Load old setting + [self.controllerToMouseSwitch setOn:self->tempSettings.mapControllerToMouse]; + [self.controllerToMouseSwitch addTarget:self action:@selector(controllerToMouseSwitchFlipped:) forControlEvents:UIControlEventValueChanged]; + [self controllerToMouseSwitchFlipped:self.controllerToMouseSwitch]; + + [self.controllerMouseVelocitySlider setValue:self->tempSettings.controllerMousePointerVelocity.floatValue]; + [self.controllerMouseVelocitySlider addTarget:self action:@selector(controllerMouseVelocitySliderMoved:) forControlEvents:UIControlEventValueChanged]; + [self controllerMouseVelocitySliderMoved:self.controllerMouseVelocitySlider]; + + [self.controllerMouseExpoSlider setValue:self->tempSettings.controllerMouseExpo.floatValue]; + [self.controllerMouseExpoSlider addTarget:self action:@selector(controllerMouseExpoSliderMoved:) forControlEvents:UIControlEventValueChanged]; + [self controllerMouseExpoSliderMoved:self.controllerMouseExpoSlider]; + [self.mapGyroToSelector addTarget:self action:@selector(mapGyroToChanged:) forControlEvents:UIControlEventValueChanged]; [self.yawPitchToRightStickSwitch addTarget:self action:@selector(yawPitchToRightStickSwitchFlipped:) forControlEvents:UIControlEventValueChanged]; [self.rollToLeftStickSwitch addTarget:self action:@selector(rollToLeftStickSwitchFlipped:) forControlEvents:UIControlEventValueChanged]; @@ -2186,14 +2207,15 @@ BOOL isCustomResolution(int resolutionSelected) { if(sender.selectedSegmentIndex == driftCorrection){ - [CountdownAlertController showAlertIn:self + [AlertControllerUtil showAlertIn:self title:[LocalizationHelper localizedStringForKey:@"Drift Correction"] message:[LocalizationHelper localizedStringForKey:@"Place the device flat on a surface and keep it still, then tap ‘Start’."] withCancel:YES buttonTitle:[LocalizationHelper localizedStringForKey:@"Start"] countdown:0 + action:^{} completion:^{ - if(CountdownAlertController.actionCancelled){ + if(AlertControllerUtil.actionCancelled){ [self.mapGyroToSelector setSelectedSegmentIndex:self->oscProfile.mapGyroTo]; [self mapGyroToChanged:self.mapGyroToSelector]; } @@ -2206,12 +2228,13 @@ BOOL isCustomResolution(int resolutionSelected) { currentSettings.gyroBiasZ = [NSNumber numberWithDouble:motionHandler.gyroBiasZ]; [self->dataMan saveData]; }]; - [CountdownAlertController showAlertIn:self + [AlertControllerUtil showAlertIn:self title:[LocalizationHelper localizedStringForKey:@"Drift Correction"] message:[LocalizationHelper localizedStringForKey:@"Calibrating..."] withCancel:NO buttonTitle:[LocalizationHelper localizedStringForKey:@"Finished!"] countdown:6 + action:^{} completion:^{ [self.mapGyroToSelector setSelectedSegmentIndex:self->oscProfile.mapGyroTo]; [self mapGyroToChanged:self.mapGyroToSelector]; @@ -2221,6 +2244,121 @@ BOOL isCustomResolution(int resolutionSelected) { } } +- (void)controllerToMouseSwitchFlipped:(UISwitch* )sender{ + [self setHidden:!sender.isOn forStack:_controllerMouseVelocityStack]; + [self setHidden:!sender.isOn forStack:_controllerMouseExpoStack]; + if(!sender.isOn || settingsViewJustLoaded) return; + [self.swapAbxySwitch setOn:false]; // swapAbxy can be enabled after mouse to controller is set + __block bool switchButtonCaptured = false; + __block bool stickCaptured = false; + __block bool leftButtonCaptured = false; + __block bool rightButtonCaptured = false; + Settings* currentSettings = [dataMan retrieveSettings]; + [AlertControllerUtil showAlertIn:self + title:[LocalizationHelper localizedStringForKey:@"Map controller to mouse"] + message:[LocalizationHelper localizedStringForKey:@"Press a button for switching to controller mouse mode (long press for 1 second during streaming)"] + withCancel:YES + buttonTitle:@"" + countdown:0 + action:^{ + self->capturedController = [GCController controllers].firstObject; + if(self->capturedController){ + __weak typeof(self) weakSelf = self; + + if (@available(iOS 14.0, tvOS 14.0, *)) { + for (GCControllerElement* element in self->capturedController.physicalInputProfile.allElements) { + element.preferredSystemGestureState = GCSystemGestureStateDisabled; + } + } + + [ControllerUtil listenWithController:self->capturedController swapABXY:false handler:^(NSDictionary * buttonDict, GCExtendedGamepad * gamepad, GCControllerElement * element) { + __strong typeof(weakSelf) self = weakSelf; + if (!self) return; + + if(!switchButtonCaptured){ + for(NSNumber* buttonFlagId in buttonDict){ + GCControllerButtonInput * button = (GCControllerButtonInput *)buttonDict[buttonFlagId]; + if(button.isPressed){ + currentSettings.controllerMouseSwitch = buttonFlagId; + switchButtonCaptured = true; + AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Move the stick you wish to use for mouse control. Another stick will be used for vertical & horizontal scroll."]; + return; + } + } + } + + if(!stickCaptured && switchButtonCaptured){ + float leftStickOffset = hypotf(gamepad.leftThumbstick.xAxis.value, gamepad.leftThumbstick.yAxis.value); + float rightStickOffset = hypotf(gamepad.rightThumbstick.xAxis.value, gamepad.rightThumbstick.yAxis.value); + if(leftStickOffset>0.1||rightStickOffset>0.1){ + ControllerMouseStick stick = leftStickOffset>rightStickOffset ? LeftStickToMouse : RightStickToMouse; + currentSettings.controllerMouseStick = @(stick); + stickCaptured = true; + AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Press the button for mouse left button"]; + } + return; + } + + if(!leftButtonCaptured && stickCaptured){ + for(NSNumber* buttonFlagId in buttonDict){ + GCControllerButtonInput * button = (GCControllerButtonInput *)buttonDict[buttonFlagId]; + if(button.isPressed){ + currentSettings.controllerMouseLeftButton = buttonFlagId; + leftButtonCaptured = true; + AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Press the button for mouse right button"]; + return; + } + } + } + + if(!rightButtonCaptured && leftButtonCaptured){ + for(NSNumber* buttonFlagId in buttonDict){ + GCControllerButtonInput * button = (GCControllerButtonInput *)buttonDict[buttonFlagId]; + if(button.isPressed){ + currentSettings.controllerMouseRightButton = buttonFlagId; + rightButtonCaptured = true; + AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Finished"]; + gamepad.valueChangedHandler = nil; + UIAlertAction* cancelAction = AlertControllerUtil.alertController.actions.firstObject; + if(cancelAction) [cancelAction setValue:[LocalizationHelper localizedStringForKey:@"OK"] forKey:@"title"]; + [self->dataMan saveData]; + } + } + } + }]; + } + else{ + [AlertControllerUtil.alertController dismissViewControllerAnimated:NO completion:^{ + [AlertControllerUtil showAlertIn:self + title:[LocalizationHelper localizedStringForKey:@""] + message:[LocalizationHelper localizedStringForKey:@"Waiting for controller..."] + withCancel:YES + buttonTitle:[LocalizationHelper localizedStringForKey:@"Continue"] + countdown:2 + action:^{} + completion:^{ + if(AlertControllerUtil.actionCancelled){ + [self.controllerToMouseSwitch setOn:NO]; + [self controllerToMouseSwitchFlipped:self.controllerToMouseSwitch]; + return; + } + [AlertControllerUtil.alertController dismissViewControllerAnimated:NO completion:^{}]; + [self controllerToMouseSwitchFlipped:self.controllerToMouseSwitch]; + }]; + }]; + } + } + completion:^{ + if(AlertControllerUtil.actionCancelled){ + if(!rightButtonCaptured){ + [self.controllerToMouseSwitch setOn:rightButtonCaptured]; + [self controllerToMouseSwitchFlipped:self.controllerToMouseSwitch]; + } + if(self->capturedController && self->capturedController.extendedGamepad) self->capturedController.extendedGamepad.valueChangedHandler = nil; + } + }]; +} + - (void)rollToLeftStickSwitchFlipped:(UISwitch* )sender{ [self setHidden:!sender.isOn forStack:_rollSensitivityStack]; } @@ -2340,6 +2478,14 @@ BOOL isCustomResolution(int resolutionSelected) { [self findDynamicLabelFromStack:_relativeTouchSlideThresholdStack].text = [NSString stringWithFormat:@" %.1f ",sender.value]; } +- (void) controllerMouseVelocitySliderMoved:(UISlider* )sender { + [self findDynamicLabelFromStack:_controllerMouseVelocityStack].text = [NSString stringWithFormat:@" %.1f ", sender.value]; +} + +- (void) controllerMouseExpoSliderMoved:(UISlider* )sender { + [self findDynamicLabelFromStack:_controllerMouseExpoStack].text = [NSString stringWithFormat:@" %.1f ", sender.value]; +} + - (uint32_t) getScreenEdgeFromSelector { switch (self.slideToSettingsScreenEdgeSelector.selectedSegmentIndex) { case 0: return UIRectEdgeLeft; @@ -3013,6 +3159,9 @@ BOOL isCustomResolution(int resolutionSelected) { BOOL ctrlDownForPinch = self.ctrlDownForPinchSwitch.isOn; CGFloat leftClickDelayMs = self.leftClickDelaySlider.value; BOOL passthroughGestures = self.passthroughGesturesSwitch.isOn; + BOOL mapControllerToMouse = self.controllerToMouseSwitch.isOn; + CGFloat controllerMousePointerVelocity = self.controllerMouseVelocitySlider.value; + CGFloat controllerMouseExpo = self.controllerMouseExpoSlider.value; NSInteger backgroundSessionTimer = self.backgroundSessionTimerSlider.value == self.backgroundSessionTimerSlider.maximumValue ? (uint32_t) INT16_MAX : (uint32_t)self.backgroundSessionTimerSlider.value; [dataMan saveSettingsWithBitrate:_bitrate @@ -3079,6 +3228,9 @@ BOOL isCustomResolution(int resolutionSelected) { leftClickDelayMs:leftClickDelayMs settingsMenuOffset:settingsMenuOffset passthroughGestures:passthroughGestures + mapControllerToMouse:mapControllerToMouse + controllerMousePointerVelocity:controllerMousePointerVelocity + controllerMouseExpo:controllerMouseExpo backgroundSessionTimer:backgroundSessionTimer]; } diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index 232f0482..cc945ed1 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -556,12 +556,13 @@ // 创建弹窗 NSString* tipText = [LocalizationHelper localizedStringForKey:@"firstLaunchTip", settingsEdgeSide, slideDist, cmdToolEdgeSide, slideDist]; - [CountdownAlertController showAlertIn:self + [AlertControllerUtil showAlertIn:self title:[LocalizationHelper localizedStringForKey:@"First Launch Tips"] message:tipText withCancel:NO buttonTitle:[LocalizationHelper localizedStringForKey:@"Got it!"] countdown:16 + action:^{} completion:^{}]; return; @@ -1652,6 +1653,13 @@ - (void)setupDisplayLink { if (_displayLink != nil) return; + TemporarySettings* tempSettings = [[[DataManager alloc] init] getSettings]; //StreamFrameViewController retrieve the settings here. + if (@available(iOS 15.0, tvOS 15.0, *)) { + [_displayLink setPreferredFrameRateRange:CAFrameRateRangeMake(tempSettings.framerate.intValue,tempSettings.framerate.intValue, tempSettings.framerate.intValue)]; + } + else { + _displayLink.preferredFramesPerSecond = tempSettings.framerate.intValue; + } _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTick:)]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings index 5d53aaec..9514cada 100644 --- a/VoidLink/mul.lproj/iPad.xcstrings +++ b/VoidLink/mul.lproj/iPad.xcstrings @@ -313,6 +313,36 @@ } } }, + "6Cy-aH-DXp.text" : { + "comment" : "Class = \"UILabel\"; text = \"Curve Exponent\"; ObjectID = \"6Cy-aH-DXp\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Curve Exponent" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "非线性因子" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "非線性因子" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "非線性因子" + } + } + } + }, "6GU-ZN-w6X.text" : { "comment" : "Class = \"UILabel\"; text = \"Theme\"; ObjectID = \"6GU-ZN-w6X\";", "extractionState" : "extracted_with_value", @@ -4009,6 +4039,36 @@ } } }, + "P1g-rW-Mgp.text" : { + "comment" : "Class = \"UILabel\"; text = \"Controller → Mouse\"; ObjectID = \"P1g-rW-Mgp\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Controller → Mouse" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "手柄映射为鼠标" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "手把映射為滑鼠" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "手把映射為滑鼠" + } + } + } + }, "P34-hO-6cX.title" : { "comment" : "Class = \"UIBarButtonItem\"; title = \"Duplicate\"; ObjectID = \"P34-hO-6cX\";", "extractionState" : "extracted_with_value", @@ -4927,6 +4987,36 @@ } } }, + "Xnd-C4-8N9.text" : { + "comment" : "Class = \"UILabel\"; text = \"Pointer Velocity\"; ObjectID = \"Xnd-C4-8N9\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Pointer Velocity" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "指针速度" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "指針速度" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "指針速度" + } + } + } + }, "XU0-qu-2p4.text" : { "comment" : "Class = \"UILabel\"; text = \"Yaw&Pitch → Right Stick\"; ObjectID = \"XU0-qu-2p4\";", "extractionState" : "extracted_with_value", diff --git a/VoidLink/mul.lproj/iPhone.xcstrings b/VoidLink/mul.lproj/iPhone.xcstrings index fe161a51..9226f71b 100644 --- a/VoidLink/mul.lproj/iPhone.xcstrings +++ b/VoidLink/mul.lproj/iPhone.xcstrings @@ -1189,6 +1189,36 @@ } } }, + "AEN-EU-8LZ.text" : { + "comment" : "Class = \"UILabel\"; text = \"Curve Exponent\"; ObjectID = \"AEN-EU-8LZ\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Curve Exponent" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "非线性因子" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "非線性因子" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "非線性因子" + } + } + } + }, "AN9-U4-nUG.text" : { "comment" : "Class = \"UILabel\"; text = \"Yaw&Pitch → Right Stick\"; ObjectID = \"AN9-U4-nUG\";", "extractionState" : "extracted_with_value", @@ -2053,6 +2083,36 @@ } } }, + "F0O-Ht-fW5.text" : { + "comment" : "Class = \"UILabel\"; text = \"Controller → Mouse\"; ObjectID = \"F0O-Ht-fW5\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Controller → Mouse" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "手柄映射为鼠标" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "手把映射為滑鼠" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "手把映射為滑鼠" + } + } + } + }, "f46-Pt-0Pv.text" : { "comment" : "Class = \"UILabel\"; text = \"Profile:\"; ObjectID = \"f46-Pt-0Pv\";", "extractionState" : "extracted_with_value", @@ -2197,6 +2257,36 @@ } } }, + "H3s-ud-yqh.text" : { + "comment" : "Class = \"UILabel\"; text = \"Pointer Velocity\"; ObjectID = \"H3s-ud-yqh\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Pointer Velocity" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "指针速度" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "指針速度" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "指針速度" + } + } + } + }, "hfR-K8-Ti4.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; hfR-K8-Ti4.segmentTitles[0] = \"Stereo\"; ObjectID = \"hfR-K8-Ti4\";", "extractionState" : "extracted_with_value",