diff --git a/VoidLink.xcodeproj/project.pbxproj b/VoidLink.xcodeproj/project.pbxproj index 82a7fe8a..dcf7bf3c 100644 --- a/VoidLink.xcodeproj/project.pbxproj +++ b/VoidLink.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ 18D43D7F2F050240008C326F /* PressureCurve.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D43D7E2F050240008C326F /* PressureCurve.swift */; }; 18E4BCEA2E891895000A8657 /* MotionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E4BCE92E891895000A8657 /* MotionHandler.swift */; }; 18FCADAF2ECAC23200787A96 /* PencilHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FCADAE2ECAC23200787A96 /* PencilHandler.swift */; }; + 2AC1E3832F507D80004F76C0 /* GenericUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AC1E3822F507D80004F76C0 /* GenericUtils.swift */; }; 840751B32BF49C050096B657 /* CustomTapGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 840751B22BF49C050096B657 /* CustomTapGestureRecognizer.m */; }; 841BAC922DE35D6B00DA639E /* ThemeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 841BAC912DE35D6B00DA639E /* ThemeManager.m */; }; 84203F232C5F24C00032EEED /* OnScreenWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84203F222C5F24C00032EEED /* OnScreenWidgetView.swift */; }; @@ -198,6 +199,7 @@ 18D43D7E2F050240008C326F /* PressureCurve.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PressureCurve.swift; sourceTree = ""; }; 18E4BCE92E891895000A8657 /* MotionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionHandler.swift; sourceTree = ""; }; 18FCADAE2ECAC23200787A96 /* PencilHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PencilHandler.swift; sourceTree = ""; }; + 2AC1E3822F507D80004F76C0 /* GenericUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericUtils.swift; sourceTree = ""; }; 693B3A9A218638CD00982F7B /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 840751B12BF49B5A0096B657 /* CustomTapGestureRecognizer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomTapGestureRecognizer.h; sourceTree = ""; }; 840751B22BF49C050096B657 /* CustomTapGestureRecognizer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomTapGestureRecognizer.m; sourceTree = ""; }; @@ -787,6 +789,7 @@ isa = PBXGroup; children = ( 18729EDD2E8BD68800101194 /* UUIDHelper.swift */, + 2AC1E3822F507D80004F76C0 /* GenericUtils.swift */, 18A0EE2B2EFBCCFD00BA853A /* GraphicUtils.swift */, 18780CC92F10D6CC00B90483 /* IAPManager.swift */, 97E20DD42E2164A9000D5DCA /* BandwidthTracker.h */, @@ -1088,6 +1091,7 @@ 1850C82A2E667E4000845308 /* MicHandler.swift in Sources */, FBD3495319FF36FB002D2A60 /* SWRevealViewController.m in Sources */, 84203F232C5F24C00032EEED /* OnScreenWidgetView.swift in Sources */, + 2AC1E3832F507D80004F76C0 /* GenericUtils.swift in Sources */, 97E21BB52E2186A3000D5DCA /* imgui_impl_metal.mm in Sources */, 9777AAF32E2CF7A400D7FBAF /* Shaders-Linear.metal in Sources */, 84FD0B512C31338D006B5BE5 /* LocalizationHelper.m in Sources */, diff --git a/VoidLink/Input/CustomOSC/OSCProfilesManager.m b/VoidLink/Input/CustomOSC/OSCProfilesManager.m index d1c9cec4..dab5a2b4 100644 --- a/VoidLink/Input/CustomOSC/OSCProfilesManager.m +++ b/VoidLink/Input/CustomOSC/OSCProfilesManager.m @@ -177,7 +177,7 @@ static CGRect layoutViewBounds; || [bundleId isEqualToString:@"com.voidlinkextreme.iOS"] || [bundleId isEqualToString:@"com.voidlink.tf.debug10.iOS"]); - if(!importPencilProProfile && [Utils isIPad]){ + if(!importPencilProProfile && [GenericUtils isIPad]){ [profilesEncoded removeObjectAtIndex:1]; } diff --git a/VoidLink/Input/OnScreenWidgetView.swift b/VoidLink/Input/OnScreenWidgetView.swift index 596e160e..d00a7ed7 100644 --- a/VoidLink/Input/OnScreenWidgetView.swift +++ b/VoidLink/Input/OnScreenWidgetView.swift @@ -2478,7 +2478,7 @@ import SVGKit if ["com.voidlink.iOS" , "com.voidlinkextreme.iOS" , "com.voidlink.tf.debug10.iOS" - ].contains(Bundle.main.bundleIdentifier) && Utils.isIPad() { + ].contains(Bundle.main.bundleIdentifier) && GenericUtils.isIPad() { self.functionalButtonDelegate?.presentPressureCurveVC() } default: diff --git a/VoidLink/Input/PressureCurve.swift b/VoidLink/Input/PressureCurve.swift index c4edf0ea..e7aa3ebe 100644 --- a/VoidLink/Input/PressureCurve.swift +++ b/VoidLink/Input/PressureCurve.swift @@ -485,18 +485,7 @@ class PressureCurveViewController: UIViewController { private var minPressure: CGFloat = 0 private var maxPressure: CGFloat = 1 - private var isFirstLaunch: Bool = { - let key = "hasLaunchedPressureCurveTool" - let defaults = UserDefaults.standard - - var launchedBefore = defaults.bool(forKey: key) - - if !launchedBefore { - defaults.set(true, forKey: key) - return true - } - return false - }() + private var isFirstLaunch: Bool = GenericUtils.isFirstLaunchPressureCurveTool() override func viewDidLoad() { super.viewDidLoad() diff --git a/VoidLink/Input/StreamView.m b/VoidLink/Input/StreamView.m index f14e7e38..90f597c8 100644 --- a/VoidLink/Input/StreamView.m +++ b/VoidLink/Input/StreamView.m @@ -857,6 +857,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; case HoverBoth: LiSendPenEvent(type, LI_TOOL_TYPE_PEN, 0, location.x / videoSize.width, location.y / videoSize.height, distance, 0.0f, 0.0f, rotationAngle, tiltAngle); [self updateCursorLocation:location isMouse:YES]; + break; default: break; } diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index 46dea5d4..8b600c12 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -2954,6 +2954,35 @@ } } }, + "enableOswForNativeTouchSwitchTip" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Disabling this option will turn off the on-screen controls and physical mouse.\nApple Pencil will also be unable to function properly." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭此选项将禁用屏显控件、物理鼠标。Apple Pencil 也将无法正常使用。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉此選項將停用螢幕控制元件與實體滑鼠。Apple Pencil 也將無法正常使用。" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉此選項將停用螢幕控制元件與實體滑鼠。Apple Pencil 也將無法正常使用。" + } + } + } + }, "Enter a new command and alias" : { "extractionState" : "manual", "localizations" : { diff --git a/VoidLink/Stream/VideoDecoderRenderer.m b/VoidLink/Stream/VideoDecoderRenderer.m index 0786c33d..e984d57e 100644 --- a/VoidLink/Stream/VideoDecoderRenderer.m +++ b/VoidLink/Stream/VideoDecoderRenderer.m @@ -62,9 +62,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, FramePacingMode _framePacingMode; bool _enableTimebase; bool _asyncFrameDequeue; - - bool isIPhone; - + // CMTime playTime; // NSTimeInterval previousLinkTime; } @@ -150,7 +148,6 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, _enableTimebase = false; _queueSize = tempSettings.frameQueueSize.intValue; _needRequeuing = _queueSize>0; - isIPhone = [Utils isIPhone]; _frameQueue = [FrameQueue sharedInstance]; [_frameQueue start]; diff --git a/VoidLink/Utility/GenericUtils.swift b/VoidLink/Utility/GenericUtils.swift new file mode 100644 index 00000000..f40a36b2 --- /dev/null +++ b/VoidLink/Utility/GenericUtils.swift @@ -0,0 +1,52 @@ +// +// GenericUtils.swift +// VoidLink +// +// Created by True砖家 on 2026/2/26. +// Copyright © 2026 True砖家@Bilibili. All rights reserved. +// + + +import Foundation + +@objc public class GenericUtils: NSObject { + + @objc static func needUpdateDefaultSettings() -> Bool { + // let key = "needUpdateDefaultSettings20260226-1" + let key = "needUpdateDefaultSettings20260226-2" + guard !UserDefaults.standard.bool(forKey: key) else { + return false + } + UserDefaults.standard.set(true, forKey: key) + return true + } + + @objc static func isEnableOswForNativeTouchSwitchFirstFlipping() -> Bool { + let key = "enableOswForNativeTouchSwitchFlipped" + guard !UserDefaults.standard.bool(forKey: key) else { + return false + } + UserDefaults.standard.set(true, forKey: key) + return true + } + + @objc static func isFirstLaunchPressureCurveTool() -> Bool { + let key = "hasLaunchedPressureCurveTool" + let defaults = UserDefaults.standard + let launchedBefore = defaults.bool(forKey: key) + + if !launchedBefore { + defaults.set(true, forKey: key) + return true + } + return false + } + + @objc static func isIPhone() -> Bool { + return UIDevice.current.userInterfaceIdiom == .phone + } + + @objc static func isIPad() -> Bool { + return UIDevice.current.userInterfaceIdiom == .pad + } +} diff --git a/VoidLink/Utility/GraphicUtils.swift b/VoidLink/Utility/GraphicUtils.swift index 186e7551..fcdac87d 100644 --- a/VoidLink/Utility/GraphicUtils.swift +++ b/VoidLink/Utility/GraphicUtils.swift @@ -118,7 +118,7 @@ import SVGKit var trackPoint = CAShapeLayer() let path = UIBezierPath( arcCenter: CGPoint(x: 0, y: 0), - radius: Utils.isIPhone() ? 12 : 15, + radius: GenericUtils.isIPhone() ? 12 : 15, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true diff --git a/VoidLink/Utility/Utils.h b/VoidLink/Utility/Utils.h index 4f58de70..4daca232 100644 --- a/VoidLink/Utility/Utils.h +++ b/VoidLink/Utility/Utils.h @@ -32,9 +32,7 @@ FOUNDATION_EXPORT NSString *const deviceName; + (unsigned short) addressPortStringToPort:(NSString*)addressPort; + (NSString*) addressAndPortToAddressPortString:(NSString*)address port:(unsigned short)port; + (bool)hdrSupported; -+ (bool)needUpdateDefaultSettings; -+ (BOOL)isIPhone; -+ (BOOL)isIPad; + #if !TARGET_OS_TV + (void) launchUrl:(NSString*)urlString; diff --git a/VoidLink/Utility/Utils.m b/VoidLink/Utility/Utils.m index e07595d6..3bf689da 100644 --- a/VoidLink/Utility/Utils.m +++ b/VoidLink/Utility/Utils.m @@ -155,28 +155,6 @@ NSString *const deviceName = @"roth"; return VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC) && (AVPlayer.availableHDRModes & AVPlayerHDRModeHDR10); } -+ (bool)needUpdateDefaultSettings { - // NSString *key = @"needUpdateDefaultSettings20260209"; - // NSString *key = @"needUpdateDefaultSettings20260219-1"; - NSString *key = @"needUpdateDefaultSettings20260226-1"; - BOOL updatedBefore = [[NSUserDefaults standardUserDefaults] boolForKey:key]; - - if (!updatedBefore) { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:key]; - [[NSUserDefaults standardUserDefaults] synchronize]; - return YES; - } - return NO; -} - -+ (BOOL)isIPhone{ - return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone); -} - -+ (BOOL)isIPad{ - return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad); -} - @end @implementation NSString (NSStringWithTrim) diff --git a/VoidLink/ViewControllers/MainFrameViewController.m b/VoidLink/ViewControllers/MainFrameViewController.m index 50226201..db165fdd 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.m +++ b/VoidLink/ViewControllers/MainFrameViewController.m @@ -646,11 +646,10 @@ static NSMutableSet* hostList; }]]; } else if (host.pairState == PairStatePaired) { - /* [longClickAlert addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"View All Apps"] style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){ self->_showHiddenApps = YES; - [self hostClicked:host view:view]; - }]]; */ + [self appButtonTappedForHost:host]; + }]]; #if !TARGET_OS_TV @@ -1587,7 +1586,7 @@ static NSMutableSet* hostList; } - (void)changeDefaultSettings{ - if(![Utils needUpdateDefaultSettings]) return; + if(![GenericUtils needUpdateDefaultSettings]) return; DataManager* dataMan = [[DataManager alloc] init]; Settings* settings = [dataMan retrieveSettings]; switch ([UIDevice currentDevice].userInterfaceIdiom) { @@ -1603,12 +1602,11 @@ static NSMutableSet* hostList; settings.framePacingMode = @(FramePacingModeQueue); if([UIScreen mainScreen].maximumFramesPerSecond > 110) settings.asyncFrameDequeue = false; if([UIScreen mainScreen].maximumFramesPerSecond < 65) settings.asyncFrameDequeue = false; - settings.touchMoveEventInterval = @(0); break; } if([UIScreen mainScreen].maximumFramesPerSecond > 110) settings.framerate = @(120); - if([UIScreen mainScreen].maximumFramesPerSecond < 65) settings.asyncFrameDequeue = @(60); + if([UIScreen mainScreen].maximumFramesPerSecond < 65) settings.framerate = @(60); // if([UIScreen mainScreen].maximumFramesPerSecond < 65) settings.touchMoveEventInterval = @(60); diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 87895124..d95faef5 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -876,7 +876,7 @@ BOOL isCustomResolution(int resolutionSelected) { || [bundleId isEqualToString:@"com.voidlinkextreme.iOS"] || [bundleId isEqualToString:@"com.voidlink.tf.debug10.iOS"]); - if([Utils isIPad] && loadPencilSection){ + if([GenericUtils isIPad] && loadPencilSection){ MenuSectionView* pencilSection = [[MenuSectionView alloc] init]; pencilSection.delegate = self; pencilSection.sectionTitle = [LocalizationHelper localizedStringForKey:@"Pencil"]; @@ -3013,9 +3013,28 @@ BOOL isCustomResolution(int resolutionSelected) { } - (void)enableOswForNativeTouchSwitchFlipped:(UISwitch *)sender{ + if(!settingsViewJustLoaded + && sender.isOn==false + && [GenericUtils isEnableOswForNativeTouchSwitchFirstFlipping]){ + [AlertControllerUtil showAlertIn:self + title:[LocalizationHelper localizedStringForKey:@"Tips"] + message:[LocalizationHelper localizedStringForKey:@"enableOswForNativeTouchSwitchTip"] + withCancel:NO + buttonTitle:[LocalizationHelper localizedStringForKey:@"This tip won't be shown again"] + countdown:6 + action:nil + completion:^{ + [self setHidden:!sender.isOn forStack:self.onScreenWidgetStack]; + [self setHidden:!sender.isOn forStack:self.buttonVisualFeedbackStack]; + [self handleOswGestureChange]; + if(!sender.isOn) self.onScreenWidgetSelector.selectedSegmentIndex = OnScreenControlsLevelOff; + }]; + } + [self setHidden:!sender.isOn forStack:self.onScreenWidgetStack]; [self setHidden:!sender.isOn forStack:self.buttonVisualFeedbackStack]; [self handleOswGestureChange]; + if(!sender.isOn) self.onScreenWidgetSelector.selectedSegmentIndex = OnScreenControlsLevelOff; } - (void)trackTouchPointSwitchFlipped:(UISwitch *)sender{ @@ -3541,7 +3560,7 @@ BOOL isCustomResolution(int resolutionSelected) { */ - (void)loadPencilSettings:(TemporarySettings*) tempSettings{ - if([Utils isIPad]){ + if([GenericUtils isIPad]){ self.pencilTickSelector.selectedSegmentIndex = tempSettings.pencilTickMode.intValue; [self.pencilTickSelector addTarget:self action:@selector(pencilTickModeChanged:) forControlEvents:UIControlEventValueChanged]; [self pencilTickModeChanged:self.pencilTickSelector]; @@ -3564,7 +3583,7 @@ BOOL isCustomResolution(int resolutionSelected) { } - (void)populatePencilSettings:(Settings*)currentSettings{ - if([Utils isIPad]){ + if([GenericUtils isIPad]){ currentSettings.pencilTickMode = @(self.pencilTickSelector.selectedSegmentIndex); currentSettings.pencilTickIntervalUs = @(self.pencilTickIntervalSlider.value); } diff --git a/VoidLink/ViewControllers/ToolboxViewController.swift b/VoidLink/ViewControllers/ToolboxViewController.swift index be025115..1f3e15da 100644 --- a/VoidLink/ViewControllers/ToolboxViewController.swift +++ b/VoidLink/ViewControllers/ToolboxViewController.swift @@ -119,7 +119,7 @@ import UIKit contentView.addSubview(addButton) contentView.addSubview(deleteButton) contentView.addSubview(editButton) - contentView.addSubview(exitButton) + // contentView.addSubview(exitButton) contentView.addSubview(pinButton) self.view.addSubview(contentView) @@ -153,7 +153,7 @@ import UIKit addButton.translatesAutoresizingMaskIntoConstraints = false deleteButton.translatesAutoresizingMaskIntoConstraints = false editButton.translatesAutoresizingMaskIntoConstraints = false - exitButton.translatesAutoresizingMaskIntoConstraints = false + // exitButton.translatesAutoresizingMaskIntoConstraints = false pinButton.translatesAutoresizingMaskIntoConstraints = false titleLabel.translatesAutoresizingMaskIntoConstraints = false @@ -183,11 +183,11 @@ import UIKit tableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -60), // ExitButton constraints - exitButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25), - exitButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10), + // exitButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25), + // exitButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10), // EditButton constraints - editButton.leadingAnchor.constraint(equalTo: exitButton.trailingAnchor, constant: 25), + editButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25), editButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10), // AddButton constraints