From fb04850ae8225500cf9aee78de313e371468ee26 Mon Sep 17 00:00:00 2001 From: xzzpig Date: Tue, 29 Jul 2025 18:18:47 +0800 Subject: [PATCH 01/13] optimize resolution update handling logic --- .../CustomEdgeSlideGestureRecognizer.m | 6 +- .../ViewControllers/MainFrameViewController.h | 1 + .../ViewControllers/MainFrameViewController.m | 76 ++++++++++--------- .../ViewControllers/SettingsViewController.m | 56 +++----------- 4 files changed, 57 insertions(+), 82 deletions(-) diff --git a/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m b/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m index 858c0486..ba353afc 100644 --- a/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m +++ b/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m @@ -14,11 +14,11 @@ @implementation CustomEdgeSlideGestureRecognizer UITouch* capturedUITouch; CGFloat startPointX; -static CGFloat screenWidthInPoints; +//static CGFloat screenWidthInPoints; - (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action { self = [super initWithTarget:target action:action]; - screenWidthInPoints = CGRectGetWidth([UIApplication.sharedApplication.windows.firstObject.screen bounds]); // Get the screen's bounds (in points) +// screenWidthInPoints = CGRectGetWidth([UIApplication.sharedApplication.windows.firstObject.screen bounds]); // Get the screen's bounds (in points) _immediateTriggering = false; _EDGE_TOLERANCE = 10.0f; return self; @@ -29,6 +29,7 @@ static CGFloat screenWidthInPoints; UITouch *touch = [touches anyObject]; capturedUITouch = touch; startPointX = [capturedUITouch locationInView:self.view].x; + CGFloat screenWidthInPoints = self.view.frame.size.width; if(_immediateTriggering){ if(self.edges & UIRectEdgeLeft){ @@ -52,6 +53,7 @@ static CGFloat screenWidthInPoints; if([touches containsObject:capturedUITouch]){ CGFloat _endPointX = [capturedUITouch locationInView:self.view].x; + CGFloat screenWidthInPoints = self.view.frame.size.width; CGFloat normalizedGestureDistance = fabs(_endPointX - startPointX)/screenWidthInPoints; if(self.edges & UIRectEdgeLeft){ diff --git a/VoidLink/ViewControllers/MainFrameViewController.h b/VoidLink/ViewControllers/MainFrameViewController.h index 8d978cd8..1f11da5a 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.h +++ b/VoidLink/ViewControllers/MainFrameViewController.h @@ -34,5 +34,6 @@ -(void)reloadStreamConfig; -(bool)isIPhonePortrait; #endif +- (void)fillResolutionTable:(CGSize*)resolutionTable externalDisplayMode:(NSInteger)externalDisplayMode; @end diff --git a/VoidLink/ViewControllers/MainFrameViewController.m b/VoidLink/ViewControllers/MainFrameViewController.m index d49bd450..37021f48 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.m +++ b/VoidLink/ViewControllers/MainFrameViewController.m @@ -1599,50 +1599,56 @@ static NSMutableSet* hostList; // }); //} --(void) updateResolutionAccordingly { - DataManager* dataMan = [[DataManager alloc] init]; - Settings *currentSettings = [dataMan retrieveSettings]; - UIWindow *window = UIApplication.sharedApplication.windows.firstObject; +-(void) fillResolutionTable:(CGSize*)resolutionTable externalDisplayMode:(NSInteger)externalDisplayMode{ + UIWindow *window = self.view.window; + NSLog(@" window %@", window); + CGFloat screenScale = window.screen.scale; - CGFloat appWindowWidth = CGRectGetWidth(window.frame) * screenScale; - CGFloat appWindowHeight = CGRectGetHeight(window.frame) * screenScale; - CGFloat screenWidthInPoints = CGRectGetWidth([[UIScreen mainScreen] bounds]); - CGFloat screenHeightInPoints = CGRectGetHeight([[UIScreen mainScreen] bounds]); - - if(currentSettings.externalDisplayMode.intValue == 1 && UIScreen.screens.count > 1){ + CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale; + CGFloat appWindowWidth = window.frame.size.width * screenScale; + CGFloat appWindowHeight = window.frame.size.height * screenScale; + + if(externalDisplayMode == 1 && UIScreen.screens.count > 1){ CGRect bounds = [UIScreen.screens.lastObject bounds]; screenScale = [UIScreen.screens.lastObject scale]; appWindowWidth = bounds.size.width * screenScale; appWindowHeight = bounds.size.height * screenScale; } - - bool needSwap = false; - if([self isFullScreenRequired]){ // if force fullscreen is enabled in app bundle, we use screen bounds to tell if a swap between width & height is needed - needSwap = (currentSettings.width.floatValue - currentSettings.height.floatValue) * (screenWidthInPoints - screenHeightInPoints) < 0; //update the current resolution accordingly - NSLog(@"need to swap width & height (non-app window mode): %d", needSwap); - if(needSwap){ - CGFloat tmpLength = currentSettings.width.floatValue; - currentSettings.width = @(currentSettings.height.floatValue); - currentSettings.height = @(tmpLength); - } + bool needSwapWidthAndHeight = appWindowWidth < appWindowHeight; + + resolutionTable[0] = CGSizeMake(1280, 720); + resolutionTable[1] = CGSizeMake(1920, 1080); + resolutionTable[2] = CGSizeMake(3840, 2160); + + for(uint8_t i=0;i<6;i++){ + CGFloat longSideLen = resolutionTable[i].height > resolutionTable[i].width ? resolutionTable[i].height : resolutionTable[i].width; + CGFloat shortSideLen = resolutionTable[i].height < resolutionTable[i].width ? resolutionTable[i].height : resolutionTable[i].width; + if(needSwapWidthAndHeight) resolutionTable[i] = CGSizeMake(shortSideLen, longSideLen); + else resolutionTable[i] = CGSizeMake(longSideLen, shortSideLen); } - else{// if force fullscreen is disabled in app bundle, we use appWindowSize to directly update or to get if we need a swap - if(currentSettings.resolutionSelected.intValue == 5){ // app window res, previous fullscreen, update resolution directly - currentSettings.width = @(appWindowWidth); - currentSettings.height = @(appWindowHeight); - NSLog(@"Directly Update app window resolution: %f, %f", appWindowWidth, appWindowHeight); - } - else if(currentSettings.resolutionSelected.intValue){ - needSwap = (currentSettings.width.floatValue - currentSettings.height.floatValue) * (appWindowWidth - appWindowHeight) < 0; - if(needSwap){ - CGFloat tmpLength = currentSettings.width.floatValue; - currentSettings.width = @(currentSettings.height.floatValue); - currentSettings.height = @(tmpLength); - NSLog(@"Swap resolution width & height"); - } - } + + // add app window resolution and not swap width and height + resolutionTable[3] = CGSizeMake(safeAreaWidth, appWindowHeight); + resolutionTable[4] = CGSizeMake(appWindowWidth, appWindowHeight); +} + +-(void) updateResolutionAccordingly { + DataManager* dataMan = [[DataManager alloc] init]; + Settings *currentSettings = [dataMan retrieveSettings]; + + CGSize tempResolutionTable[6] = {0}; + tempResolutionTable[5] = CGSizeMake(currentSettings.width.intValue, currentSettings.height.intValue); + [self fillResolutionTable:tempResolutionTable externalDisplayMode:currentSettings.externalDisplayMode.intValue]; + + int selectedIndex = currentSettings.resolutionSelected.intValue; + if (selectedIndex >= 0 && selectedIndex < 6) { + CGSize selectedSize = tempResolutionTable[selectedIndex]; + currentSettings.width = @(selectedSize.width); + currentSettings.height = @(selectedSize.height); + NSLog(@"Updated resolution to: %@ x %@", currentSettings.width, currentSettings.height); } + [dataMan saveData]; } diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index c45fc929..7f8069a6 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -250,19 +250,8 @@ CGSize resolutionTable[RESOLUTION_TABLE_SIZE]; } } -BOOL isCustomResolution(CGSize res) { - if (res.width == 0 && res.height == 0) { - return NO; - } - - for (int i = 0; i < RESOLUTION_TABLE_CUSTOM_INDEX; i++) { - NSLog(@"customRes: %f, %f, isrl: %f, %f", res.width, res.height, resolutionTable[i].width, resolutionTable[i].height); - if ((res.width == resolutionTable[i].width && res.height == resolutionTable[i].height) || (res.height == resolutionTable[i].width && res.width == resolutionTable[i].height)) { - return NO; - } - } - - return YES; +BOOL isCustomResolution(int resolutionSelected) { + return resolutionSelected == RESOLUTION_TABLE_CUSTOM_INDEX; } + (bool)isLandscapeNow { @@ -286,32 +275,10 @@ BOOL isCustomResolution(CGSize res) { - (void)updateResolutionTable{ if(self.mainFrameViewController.settingsExpandedInStreamView) return; - - UIWindow *window = self.view.window; - NSLog(@" window %@", window); - CGFloat screenScale = window.screen.scale; - CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale; - CGFloat appWindowWidth = window.frame.size.width * screenScale; - CGFloat appWindowHeight = window.frame.size.height * screenScale; - if([self isAirPlayEnabled] && UIScreen.screens.count > 1){ - CGRect bounds = [UIScreen.screens.lastObject bounds]; - screenScale = [UIScreen.screens.lastObject scale]; - appWindowWidth = bounds.size.width * screenScale; - appWindowHeight = bounds.size.height * screenScale; - } - - bool needSwapWidthAndHeight = appWindowWidth < appWindowHeight; - - for(uint8_t i=0;i<6;i++){ - CGFloat longSideLen = resolutionTable[i].height > resolutionTable[i].width ? resolutionTable[i].height : resolutionTable[i].width; - CGFloat shortSideLen = resolutionTable[i].height < resolutionTable[i].width ? resolutionTable[i].height : resolutionTable[i].width; - if(needSwapWidthAndHeight) resolutionTable[i] = CGSizeMake(shortSideLen, longSideLen); - else resolutionTable[i] = CGSizeMake(longSideLen, shortSideLen); - } - - resolutionTable[3] = CGSizeMake(safeAreaWidth, appWindowHeight); - resolutionTable[4] = CGSizeMake(appWindowWidth, appWindowHeight); + NSInteger externalDisplayMode = [self.externalDisplayModeSelector selectedSegmentIndex]; + // 调用主界面方法统一填充 resolutionTable + [self.mainFrameViewController fillResolutionTable:resolutionTable externalDisplayMode:externalDisplayMode]; [self updateResolutionDisplayLabel]; } @@ -340,8 +307,8 @@ BOOL isCustomResolution(CGSize res) { DataManager* dataMan = [[DataManager alloc] init]; TemporarySettings *currentSettings = [dataMan getSettings]; - CGSize currentResolution = CGSizeMake(currentSettings.width.intValue, currentSettings.height.intValue); - [self.customResolutionSwitch setOn: isCustomResolution(currentResolution)]; + //CGSize currentResolution = CGSizeMake(currentSettings.width.intValue, currentSettings.height.intValue); + [self.customResolutionSwitch setOn: isCustomResolution(currentSettings.resolutionSelected.intValue)]; [self.resolutionSelector setEnabled:!self.customResolutionSwitch.isOn]; } @@ -1290,12 +1257,8 @@ BOOL isCustomResolution(CGSize res) { [self.resolutionSelector removeSegmentAtIndex:0 animated:NO]; // remove 360p [self.resolutionSelector removeSegmentAtIndex:5 animated:NO]; // remove custom segment - resolutionTable[0] = CGSizeMake(1280, 720); - resolutionTable[1] = CGSizeMake(1920, 1080); - resolutionTable[2] = CGSizeMake(3840, 2160); - resolutionTable[3] = CGSizeMake(safeAreaWidth, fullScreenHeight); - resolutionTable[4] = CGSizeMake(fullScreenWidth, fullScreenHeight); resolutionTable[5] = CGSizeMake([currentSettings.width integerValue], [currentSettings.height integerValue]); // custom initial value + [self updateResolutionTable]; NSInteger framerate; @@ -2135,6 +2098,9 @@ BOOL isCustomResolution(CGSize res) { BOOL enableHdr = self.hdrSwitch.isOn; BOOL unlockDisplayOrientation = [self.unlockDisplayOrientationSelector selectedSegmentIndex] == 1; NSInteger resolutionSelected = [self.resolutionSelector selectedSegmentIndex]; + if (self.customResolutionSwitch.isOn) { + resolutionSelected = RESOLUTION_TABLE_CUSTOM_INDEX; + } NSInteger externalDisplayMode = [self.externalDisplayModeSelector selectedSegmentIndex]; NSInteger localMousePointerMode = [self.localMousePointerModeSelector selectedSegmentIndex]; NSInteger backgroundSessionTimer = self.backgroundSessionTimerSlider.value == self.backgroundSessionTimerSlider.maximumValue ? (uint32_t) INT16_MAX : (uint32_t)self.backgroundSessionTimerSlider.value; From 3e48df97563f4f9c8b94d3549e2fd3581640fc1d Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Tue, 29 Jul 2025 20:37:38 +0800 Subject: [PATCH 02/13] remove KVC accessed api --- VoidLink/ViewControllers/SettingsViewController.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index c45fc929..8ce28d00 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -1029,10 +1029,10 @@ BOOL isCustomResolution(CGSize res) { } - UIAlertController *tipsAlertController = [UIAlertController alertControllerWithTitle: [LocalizationHelper localizedStringForKey:@"Tips"] message: [LocalizationHelper localizedStringForKey:@"%@", tipText] preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *tipsAlertController = [UIAlertController alertControllerWithTitle: [LocalizationHelper localizedStringForKey:@"Tips"] message:tipText preferredStyle:UIAlertControllerStyleAlert]; - + /* NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; paragraphStyle.alignment = NSTextAlignmentLeft; @@ -1046,7 +1046,7 @@ BOOL isCustomResolution(CGSize res) { // 使用 KVC 设置 attributedMessage(注意审核风险) [tipsAlertController setValue:attributedMessage forKey:@"attributedMessage"]; - + */ UIAlertAction *readInstruction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Online Documentation"] style:UIAlertActionStyleDefault From d2eb674bb50f10392846b145daa32741913471f8 Mon Sep 17 00:00:00 2001 From: TrueZhuanJia <78474576+TrueZhuangJia@users.noreply.github.com> Date: Fri, 1 Aug 2025 20:43:43 +0800 Subject: [PATCH 03/13] Update README.md --- README.md | 54 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 6580a883..82a49a80 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,42 @@ -# Moonlight iOS/tvOS +# VoidLink 已上架App Store. +# VoidLink is available on App Store +### [https://apps.apple.com/cn/app/voidlink/id6747717070](https://apps.apple.com/cn/app/voidlink/id6747717070) -[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/kwv8vpwr457lqn25/branch/master?svg=true)](https://ci.appveyor.com/project/cgutman/moonlight-ios/branch/master) +
-[Moonlight for iOS/tvOS](https://moonlight-stream.org) is an open source client for [Sunshine](https://github.com/LizardByte/Sunshine) and NVIDIA GameStream. Moonlight for iOS/tvOS allows you to stream your full collection of games and apps from your powerful desktop computer to your iOS device or Apple TV. +# 代码 Coding +- 代码提交在`Integration`分支。 +- For latest coding commits, go to branch `Integration`. -Moonlight also has a [PC client](https://github.com/moonlight-stream/moonlight-qt) and [Android client](https://github.com/moonlight-stream/moonlight-android). +
-Check out [the Moonlight wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed project information, setup guide, or troubleshooting steps. +# 关于VoidLink. About VoidLink +- 本项目最初基于开源项目 [moonlight-iOS] fork 而来。在此基础上,True砖家(True Zhuanjia)@ Bilibili 及其他社区开发者对项目进行了大量重构、重新设计与功能扩展,包括全新的用户界面和显著增强的功能特性。
我们对 moonlight-iOS 开发者的开创性工作表示衷心感谢。

+- VoidLink was originally forked from the open-source project [moonlight-iOS], but has since been extensively reworked, redesigned, and expanded by True砖家 (True Zhuanjia) @ Bilibili and other community developers. These contributions include a completely new user interface and significant enhancements to the application's functionality.
We gratefully acknowledge the foundational work of the moonlight-iOS developers. -[![Moonlight for iOS and tvOS](https://moonlight-stream.org/images/App_Store_Badge_135x40.svg)](https://apps.apple.com/us/app/moonlight-game-streaming/id1000551566) +
-## Building -* Install Xcode from the [App Store page](https://apps.apple.com/us/app/xcode/id497799835) -* Run `git clone --recursive https://github.com/moonlight-stream/moonlight-ios.git` - * If you've already clone the repo without `--recursive`, run `git submodule update --init --recursive` -* Open Moonlight.xcodeproj in Xcode -* To run on a real device, you will need to locally modify the signing options: - * Click on "Moonlight" at the top of the left sidebar - * Click on the "Signing & Capabilities" tab - * Under "Targets", select "Moonlight" (for iOS/iPadOS) or "Moonlight TV" (for tvOS) - * In the "Team" dropdown, select your name. If your name doesn't appear, you may need to sign into Xcode with your Apple account. - * Change the "Bundle Identifier" to something different. You can add your name or some random letters to make it unique. - * Now you can select your Apple device in the top bar as a target and click the Play button to run. +# App Store 分发一次性收费声明. +# Statement on One-Time App Store Distribution Fee. +- VoidLink 的 App Store 安装费用用于覆盖通过 Apple 框架进行安全可靠应用分发的成本。同时也支持项目维护者进行持续的开发、维护,以及公众用户访问并下载官方签名版本。感谢您对项目的支持,帮助我们持续改进和优化。

+- The App Store fee for VoidLink covers the cost of secure and trusted distribution using Apple's infrastructure. It supports ongoing development, maintenance, and access to the official signed build. Thank you for supporting the project and helping sustain ongoing improvements. + +
+ +# 开发者B站号. Developer on Bilibili + +如果你在用Bilibili, 请关注`True砖家`,了解该fork的最新消息。
+If you are on Bilibili, subscribe `True砖家` to get the latest news of this fork:
+https://b23.tv/A0F9v7n + +
+ +# 贡献者 Contributors +[@TrueZhuangJia](https://github.com/TrueZhuangJia)
+[@All contributors from moonlight-iOS](https://github.com/moonlight-stream/moonlight-ios/graphs/contributors)
+[@stefanilijev97](https://github.com/stefanilijev97/stefanilijev97)
+[@Acaki](https://github.com/Acaki)
+[@seastwood](https://github.com/seastwood)
+[@Danos0100](https://github.com/Danos0100)
+[@xzzpig](https://github.com/xzzpig)
+[@King0fSpace](https://github.com/King0fSpace)
From 245613162a37b4cbc0f40e79f873def8776ce20f Mon Sep 17 00:00:00 2001 From: TrueZhuanJia <78474576+TrueZhuangJia@users.noreply.github.com> Date: Fri, 1 Aug 2025 20:49:17 +0800 Subject: [PATCH 04/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 82a49a80..5f1ba5ed 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # VoidLink 已上架App Store. # VoidLink is available on App Store -### [https://apps.apple.com/cn/app/voidlink/id6747717070](https://apps.apple.com/cn/app/voidlink/id6747717070) +### [https://apps.apple.com/app/voidlink/id6747717070](https://apps.apple.com/cn/app/voidlink/id6747717070)
From 0c386ef3370057b9f3058a998890d0ea06637b3c Mon Sep 17 00:00:00 2001 From: xzzpig Date: Fri, 1 Aug 2025 22:37:56 +0800 Subject: [PATCH 05/13] feat: Allow WOL during host detection --- VoidLink/HostCardView.m | 11 +++++------ VoidLink/ViewControllers/MainFrameViewController.m | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/VoidLink/HostCardView.m b/VoidLink/HostCardView.m index ff4852e2..061394de 100644 --- a/VoidLink/HostCardView.m +++ b/VoidLink/HostCardView.m @@ -521,6 +521,8 @@ static const float REFRESH_CYCLE = 2.0f; _hostNameLabel.text = host.name; backgroundLayer.hidden = !(host.state == StateOnline && host.pairState == PairStatePaired); + BOOL hostPaired = host.pairState == PairStatePaired; + switch (host.state) { case StateOnline: [_hostSpinner stopAnimating]; @@ -576,8 +578,6 @@ static const float REFRESH_CYCLE = 2.0f; self.statusIcon.hidden = YES; } - bool hostPaired = host.pairState == PairStatePaired; - _hostIconView.tintColor = [ThemeManager lowProfileGray]; lockIconView.tintColor = [ThemeManager lowProfileGray]; lockIconView.hidden = hostPaired; @@ -620,17 +620,16 @@ static const float REFRESH_CYCLE = 2.0f; _pairButton.hidden = YES; _wakeupButton.hidden = NO; - [_wakeupButton setTitleColor:[ThemeManager textColorGray] forState:UIControlStateNormal]; // theme if (@available(iOS 13.0, *)) { UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:buttonHeight/3.45 weight:UIImageSymbolWeightBold]; UIImage *templateImage = [UIImage systemImageNamed:@"power" withConfiguration:config]; - UIImage *coloredImage = [templateImage imageWithTintColor:[ThemeManager textColorGray] renderingMode:UIImageRenderingModeAlwaysOriginal]; + UIImage *coloredImage = [templateImage imageWithTintColor:hostPaired ? defaultBlue : [ThemeManager textColorGray] renderingMode:UIImageRenderingModeAlwaysOriginal]; [self.wakeupButton setImage:coloredImage forState:UIControlStateNormal]; } else { // Fallback on earlier versions } - self.wakeupButton.backgroundColor = [[ThemeManager textColorGray] colorWithAlphaComponent:0.2]; - [self.wakeupButton setTitleColor:[ThemeManager textColorGray] forState:UIControlStateNormal]; + self.wakeupButton.backgroundColor = hostPaired ? [ThemeManager textTintColorWithAlpha] : [[ThemeManager textColorGray] colorWithAlphaComponent:0.2]; + [self.wakeupButton setTitleColor: hostPaired ? defaultBlue : [ThemeManager textColorGray] forState:UIControlStateNormal]; break; default:break; } diff --git a/VoidLink/ViewControllers/MainFrameViewController.m b/VoidLink/ViewControllers/MainFrameViewController.m index d49bd450..1394bd0f 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.m +++ b/VoidLink/ViewControllers/MainFrameViewController.m @@ -409,7 +409,7 @@ static NSMutableSet* hostList; - (void)wakeupButtonTappedForHost:(TemporaryHost *)host{ _selectedHost = host; - if (host.state == StateOffline && host.pairState == PairStatePaired) { + if ((host.state == StateOffline || host.state == StateUnknown) && host.pairState == PairStatePaired) { UIAlertController* wolAlert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Wake-On-LAN"] message:@"" preferredStyle:UIAlertControllerStyleAlert]; [wolAlert addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:nil]]; if (host.mac == nil || [host.mac isEqualToString:@"00:00:00:00:00:00"]) { From f50d1bd488201968f525ff569bb5b1c9dfa39ac1 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Tue, 5 Aug 2025 00:52:45 +0800 Subject: [PATCH 06/13] - Add online documentation link, including "Read Widget Instruction" button. --- VoidLink/Localization/Localizable.xcstrings | 126 +++++++++++++----- .../ViewControllers/SettingsViewController.m | 2 + 2 files changed, 97 insertions(+), 31 deletions(-) diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index 7c90308f..a1275f2c 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -1480,25 +1480,25 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/uscwmv3vz8g30rmk?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/uscwmv3vz8g30rmk" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3" } }, "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3" } } } @@ -1728,25 +1728,25 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bu3hki4pezy93g6r?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bu3hki4pezy93g6r" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/cqbse6t4o6f9hil1?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/cqbse6t4o6f9hil1" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/cqbse6t4o6f9hil1?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/cqbse6t4o6f9hil1" } }, "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/cqbse6t4o6f9hil1?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/cqbse6t4o6f9hil1" } } } @@ -2045,25 +2045,25 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/uscwmv3vz8g30rmk?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/uscwmv3vz8g30rmk" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3" } }, "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/bz6ggagp0ly4loq3" } } } @@ -2477,25 +2477,25 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/zdf5l29s3vohs6iq?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/zdf5l29s3vohs6iq" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/yfao3s5bhl93rs01?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/yfao3s5bhl93rs01" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/yfao3s5bhl93rs01?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/yfao3s5bhl93rs01" } }, "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/yfao3s5bhl93rs01?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/yfao3s5bhl93rs01" } } } @@ -3108,22 +3108,28 @@ "onScreenWidgetStackDoc" : { "extractionState" : "manual", "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/dcke52amz8ay3g37" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/zrg0ga93avz4s747?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/zrg0ga93avz4s747" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/zrg0ga93avz4s747?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/zrg0ga93avz4s747" } }, "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/zrg0ga93avz4s747?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/zrg0ga93avz4s747" } } } @@ -3330,24 +3336,24 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "- Enable auto picture-in-picture by switching on Settings -> Multitasking & Gestures -> Start PiP Automatically.\n\n- PiP can be manually started in toolbox menu.\n\n- PiP connection timeout is also defined by “Background Session” settings in “Others” section\n\n-PiP is available for iOS15+" + "value" : "- Enable auto picture-in-picture by switching on Settings -> Multitasking & Gestures -> Start PiP Automatically.\n\n- PiP can be manually started in toolbox menu.\n\n- PiP connection timeout is also defined by “Background Session” settings in “Others” section\n\n- PiP is available for iOS15+" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "- App切后台时自动开启画中画, 需要打开 iOS 设置-> 多任务与手势 -> 自动开启画中画。\n\n- 画中画可在工具箱菜单中手动开启。\n\n- 画中画时长由 “其他 -> 后台会话” 设置项控制\n\n- 仅 iOS15+ 支持画中画。" } }, "zh-Hant" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "- App 切到背景時會自動啟用子母畫面,需開啟 iOS 「設定」→「多工與手勢」→「自動啟用子母畫面」。\n\n- 子母畫面也可透過工具箱選單手動啟用。\n\n- 子母畫面顯示時長由「其他」→「背景執行」設定項目控制。\n\n- 子母畫面功能自 iOS 15 起支援。" } }, "zh-HK" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "- App 切到背景時會自動啟用子母畫面,需開啟 iOS 「設定」→「多工與手勢」→「自動啟用子母畫面」。\n\n- 子母畫面也可透過工具箱選單手動啟用。\n\n- 子母畫面顯示時長由「其他」→「背景執行」設定項目控制。\n\n- 子母畫面功能自 iOS 15 起支援。" } } @@ -3376,6 +3382,35 @@ } } }, + "pointerVelocityDividerStackDoc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/ct0xcq77u6pgyvxy" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/qt0gthxxnr94b3sz" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/qt0gthxxnr94b3sz" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/qt0gthxxnr94b3sz" + } + } + } + }, "pointerVelocityDividerStackTip" : { "extractionState" : "manual", "localizations" : { @@ -3405,6 +3440,35 @@ } } }, + "pointerVelocityFactorStackDoc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/ct0xcq77u6pgyvxy" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/qt0gthxxnr94b3sz" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/qt0gthxxnr94b3sz" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/qt0gthxxnr94b3sz" + } + } + } + }, "pointerVelocityFactorStackTip" : { "extractionState" : "manual", "localizations" : { @@ -4286,25 +4350,25 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/rgc9if4xrf352555?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/rgc9if4xrf352555" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/ok4egvu4lt37wc1f?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/ok4egvu4lt37wc1f" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/ok4egvu4lt37wc1f?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/ok4egvu4lt37wc1f" } }, "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/ok4egvu4lt37wc1f?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/ok4egvu4lt37wc1f" } } } @@ -4390,25 +4454,25 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/dhmg3h27ls8mwt4a?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/dhmg3h27ls8mwt4a" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/fdi4rxkxykhl5zpp?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/fdi4rxkxykhl5zpp" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/fdi4rxkxykhl5zpp?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/fdi4rxkxykhl5zpp" } }, "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/fdi4rxkxykhl5zpp?singleDoc" + "value" : "https://voidlink.yuque.com/org-wiki-voidlink-znirha/fa3tgr/fdi4rxkxykhl5zpp" } } } diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 8ce28d00..b52d27d9 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -972,10 +972,12 @@ BOOL isCustomResolution(CGSize res) { if([sender.superview.accessibilityIdentifier isEqualToString: @"pointerVelocityDividerStack"]){ tipText = [LocalizationHelper localizedStringForKey:@"pointerVelocityDividerStackTip"]; showOnlineDocAction = true; + onlineDocLink =[LocalizationHelper localizedStringForKey:@"pointerVelocityDividerStackDoc"]; } if([sender.superview.accessibilityIdentifier isEqualToString: @"pointerVelocityFactorStack"]){ tipText = [LocalizationHelper localizedStringForKey:@"pointerVelocityFactorStackTip"]; showOnlineDocAction = true; + onlineDocLink = [LocalizationHelper localizedStringForKey:@"pointerVelocityFactorStackDoc"]; } if([sender.superview.accessibilityIdentifier isEqualToString: @"hdrStack"]){ tipText = [LocalizationHelper localizedStringForKey:@"hdrStackTip"]; From a44e9c87d73a0fd7bafbf92715d60831b74a7ba9 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Wed, 6 Aug 2025 10:26:38 +0800 Subject: [PATCH 07/13] - make wakeup button available as long as the host has valid mac address --- VoidLink/HostCardView.m | 5 +++-- VoidLink/ViewControllers/MainFrameViewController.m | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/VoidLink/HostCardView.m b/VoidLink/HostCardView.m index 061394de..5b130af8 100644 --- a/VoidLink/HostCardView.m +++ b/VoidLink/HostCardView.m @@ -522,6 +522,7 @@ static const float REFRESH_CYCLE = 2.0f; backgroundLayer.hidden = !(host.state == StateOnline && host.pairState == PairStatePaired); BOOL hostPaired = host.pairState == PairStatePaired; + bool hasValidMac = host.mac != nil && ![host.mac isEqualToString:@"00:00:00:00:00:00"]; switch (host.state) { case StateOnline: @@ -628,8 +629,8 @@ static const float REFRESH_CYCLE = 2.0f; } else { // Fallback on earlier versions } - self.wakeupButton.backgroundColor = hostPaired ? [ThemeManager textTintColorWithAlpha] : [[ThemeManager textColorGray] colorWithAlphaComponent:0.2]; - [self.wakeupButton setTitleColor: hostPaired ? defaultBlue : [ThemeManager textColorGray] forState:UIControlStateNormal]; + self.wakeupButton.backgroundColor = hasValidMac ? [ThemeManager textTintColorWithAlpha] : [[ThemeManager textColorGray] colorWithAlphaComponent:0.2]; + [self.wakeupButton setTitleColor: hasValidMac ? defaultBlue : [ThemeManager textColorGray] forState:UIControlStateNormal]; break; default:break; } diff --git a/VoidLink/ViewControllers/MainFrameViewController.m b/VoidLink/ViewControllers/MainFrameViewController.m index 1394bd0f..3f067a41 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.m +++ b/VoidLink/ViewControllers/MainFrameViewController.m @@ -409,10 +409,13 @@ static NSMutableSet* hostList; - (void)wakeupButtonTappedForHost:(TemporaryHost *)host{ _selectedHost = host; - if ((host.state == StateOffline || host.state == StateUnknown) && host.pairState == PairStatePaired) { + bool hasValidMac = host.mac != nil && ![host.mac isEqualToString:@"00:00:00:00:00:00"]; + + if ((host.state == StateOffline || host.state == StateUnknown)) { UIAlertController* wolAlert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Wake-On-LAN"] message:@"" preferredStyle:UIAlertControllerStyleAlert]; [wolAlert addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:nil]]; - if (host.mac == nil || [host.mac isEqualToString:@"00:00:00:00:00:00"]) { + + if (!hasValidMac) { wolAlert.message = [LocalizationHelper localizedStringForKey: @"Host MAC unknown, unable to send WOL Packet"]; } else { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ From 900a4cca14e87f8790bf44bdd0b0d0df18ffd7a2 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Wed, 6 Aug 2025 15:36:14 +0800 Subject: [PATCH 08/13] ignore readme --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 634eb150..bca4bde6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ Build DerivedData *.xccheckout -.github \ No newline at end of file +.github +README.md \ No newline at end of file From 905e3eaf733c080a6963087a05fac363c1aca518 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Wed, 6 Aug 2025 21:01:34 +0800 Subject: [PATCH 09/13] settings menu safeArea fix for iPhone --- .../ViewControllers/SettingsViewController.m | 74 ++++++++++++++----- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index b52d27d9..bdf40f51 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -21,6 +21,9 @@ #import "LocalizationHelper.h" @implementation SettingsViewController { + NSLayoutConstraint *parentStackLeadingConstraint; + NSLayoutConstraint *parentStackWidthConstraint; + NSInteger _bitrate; NSInteger _lastSelectedResolutionIndex; bool justEnteredSettingsView; @@ -230,24 +233,11 @@ CGSize resolutionTable[RESOLUTION_TABLE_SIZE]; dispatch_after(delayTime, dispatch_get_main_queue(), ^{// Code to execute after the delay // [self updateResolutionAccordingly]; }); - } // Adjust the subviews for the safe area on the iPhone X. - (void)viewSafeAreaInsetsDidChange { [super viewSafeAreaInsetsDidChange]; - - //if (@available(iOS 11.0, *)) { - if (false) { // cancel this for portrait mode - for (UIView* view in self.view.subviews) { - // HACK: The official safe area is much too large for our purposes - // so we'll just use the presence of any safe area to indicate we should - // pad by 20. - if (self.view.safeAreaInsets.left >= 20 || self.view.safeAreaInsets.right >= 20) { - view.frame = CGRectMake(view.frame.origin.x + 20, view.frame.origin.y, view.frame.size.width, view.frame.size.height); - } - } - } } BOOL isCustomResolution(CGSize res) { @@ -330,6 +320,11 @@ BOOL isCustomResolution(CGSize res) { - (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:NO]; + [self updateParentStackHorizontalConstraints]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(deviceOrientationDidChange:) // handle orientation change since i made portrait mode available + name:UIDeviceOrientationDidChangeNotification + object:nil]; } - (void)viewDidAppear:(BOOL)animated{ @@ -345,12 +340,17 @@ BOOL isCustomResolution(CGSize res) { [self.resolutionSelector setEnabled:!self.customResolutionSwitch.isOn]; } +- (void)viewWillDisappear:(BOOL)animated{ + [super viewWillDisappear:animated]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} - (void)viewDidDisappear:(BOOL)animated{ [super viewDidDisappear:NO]; [[NSNotificationCenter defaultCenter] postNotificationName:@"SettingsViewClosedNotification" object:self]; // notify other view that settings view just closed } + - (SettingsMenuMode)getSettingsMenuMode{ return currentSettingsMenuMode; } @@ -390,14 +390,53 @@ BOOL isCustomResolution(CGSize res) { _parentStack.spacing = 0; _parentStack.translatesAutoresizingMaskIntoConstraints = NO; [self.scrollView addSubview:_parentStack]; + [NSLayoutConstraint activateConstraints:@[ [_parentStack.topAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.topAnchor constant: currentSettingsMenuMode == AllSettings ? [self getStandardNavBarHeight] : [self getStandardNavBarHeight]+10], [_parentStack.bottomAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.bottomAnchor constant:-20], - [_parentStack.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor constant: 0], //mark: settingMenuLayout - [_parentStack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-20] // section width adjusted here //mark: settingMenuLayout ]]; + + [self updateParentStackHorizontalConstraints]; } +-(void)deviceOrientationDidChange:(NSNotification *)notification { + [self updateParentStackHorizontalConstraints]; +} + +- (void)updateParentStackHorizontalConstraints{ + if(![self isIPhone]){ + [NSLayoutConstraint activateConstraints:@[ + [_parentStack.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor constant: 0], //mark: settingMenuLayout + [_parentStack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-20] // section width adjusted here + ]]; + return; + } + + UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; + + if(parentStackWidthConstraint && parentStackLeadingConstraint) [NSLayoutConstraint deactivateConstraints:@[parentStackLeadingConstraint, parentStackWidthConstraint]]; + + switch (orientation) { + case UIDeviceOrientationLandscapeLeft: + parentStackLeadingConstraint = [_parentStack.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:0]; + parentStackWidthConstraint = [_parentStack.widthAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.widthAnchor constant:-10]; + break; + default: + parentStackLeadingConstraint = [_parentStack.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:10]; + parentStackWidthConstraint = [_parentStack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-20]; + break; + } + + [NSLayoutConstraint activateConstraints:@[parentStackLeadingConstraint, parentStackWidthConstraint]]; + + double delayInSeconds = 0.05; + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); + dispatch_after(popTime, dispatch_get_main_queue(), ^{ + [self hideOverlappedDynamicLabels]; + }); +} + + - (UIButton* )findInfoButtonFromStack:(UIStackView* )stack{ UIButton* button; for(UIView *view in stack.subviews){ @@ -495,7 +534,6 @@ BOOL isCustomResolution(CGSize res) { [label.heightAnchor constraintEqualToAnchor:stack.arrangedSubviews[0].heightAnchor], [label.widthAnchor constraintEqualToConstant:150], ]]; - } } @@ -508,8 +546,6 @@ BOOL isCustomResolution(CGSize res) { } - (void)layoutSections{ - - MenuSectionView *videoSection = [[MenuSectionView alloc] init]; videoSection.delegate = self; videoSection.sectionTitle = [LocalizationHelper localizedStringForKey:@"Video"]; @@ -715,8 +751,6 @@ BOOL isCustomResolution(CGSize res) { } - - - (NSInteger)parentStackIndexForLocation:(CGPoint)location { for (NSInteger i = _parentStack.arrangedSubviews.count-1; i >=0; i--) { UIView *subview = _parentStack.arrangedSubviews[i]; From 54bf7014954d05b2ccfa75a9f454175e5daa77af Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Wed, 6 Aug 2025 22:15:49 +0800 Subject: [PATCH 10/13] minor change --- VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m b/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m index ba353afc..af0e2a39 100644 --- a/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m +++ b/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m @@ -29,7 +29,7 @@ CGFloat startPointX; UITouch *touch = [touches anyObject]; capturedUITouch = touch; startPointX = [capturedUITouch locationInView:self.view].x; - CGFloat screenWidthInPoints = self.view.frame.size.width; + CGFloat streamFrameViewWidthInPoints = self.view.frame.size.width; if(_immediateTriggering){ if(self.edges & UIRectEdgeLeft){ @@ -39,7 +39,7 @@ CGFloat startPointX; // NSLog(@"startPointX %f , normalizedGestureDeltaX %f", startPointX, normalizedGestureDistance); } if(self.edges & UIRectEdgeRight){ - if(startPointX > screenWidthInPoints - _EDGE_TOLERANCE){ + if(startPointX > streamFrameViewWidthInPoints - _EDGE_TOLERANCE){ self.state = UIGestureRecognizerStateEnded; } // NSLog(@"startPointX %f , normalizedGestureDeltaX %f", startPointX, normalizedGestureDistance); From 338523e216fa9255d72ca384497a78ef68c40ef3 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Wed, 6 Aug 2025 23:04:04 +0800 Subject: [PATCH 11/13] - further fix for wake-on-lan button & logic --- VoidLink/HostCardView.m | 8 +++--- .../ViewControllers/MainFrameViewController.m | 26 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/VoidLink/HostCardView.m b/VoidLink/HostCardView.m index 5b130af8..692b7b07 100644 --- a/VoidLink/HostCardView.m +++ b/VoidLink/HostCardView.m @@ -590,14 +590,14 @@ static const float REFRESH_CYCLE = 2.0f; if (@available(iOS 13.0, *)) { UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:buttonHeight/3.45 weight:UIImageSymbolWeightBold]; UIImage *templateImage = [UIImage systemImageNamed:@"power" withConfiguration:config]; - UIImage *coloredImage = [templateImage imageWithTintColor:hostPaired ? defaultBlue : [ThemeManager textColorGray] renderingMode:UIImageRenderingModeAlwaysOriginal]; + UIImage *coloredImage = [templateImage imageWithTintColor:hasValidMac ? defaultBlue : [ThemeManager textColorGray] renderingMode:UIImageRenderingModeAlwaysOriginal]; [self.wakeupButton setImage:coloredImage forState:UIControlStateNormal]; } else { // Fallback on earlier versions } - self.wakeupButton.backgroundColor = hostPaired ? [ThemeManager textTintColorWithAlpha] : [[ThemeManager textColorGray] colorWithAlphaComponent:0.2]; - [self.wakeupButton setTitleColor: hostPaired ? defaultBlue : [ThemeManager textColorGray] forState:UIControlStateNormal]; + self.wakeupButton.backgroundColor = hasValidMac ? [ThemeManager textTintColorWithAlpha] : [[ThemeManager textColorGray] colorWithAlphaComponent:0.2]; + [self.wakeupButton setTitleColor: hasValidMac ? defaultBlue : [ThemeManager textColorGray] forState:UIControlStateNormal]; break; case StateUnknown: @@ -624,7 +624,7 @@ static const float REFRESH_CYCLE = 2.0f; if (@available(iOS 13.0, *)) { UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:buttonHeight/3.45 weight:UIImageSymbolWeightBold]; UIImage *templateImage = [UIImage systemImageNamed:@"power" withConfiguration:config]; - UIImage *coloredImage = [templateImage imageWithTintColor:hostPaired ? defaultBlue : [ThemeManager textColorGray] renderingMode:UIImageRenderingModeAlwaysOriginal]; + UIImage *coloredImage = [templateImage imageWithTintColor:hasValidMac ? defaultBlue : [ThemeManager textColorGray] renderingMode:UIImageRenderingModeAlwaysOriginal]; [self.wakeupButton setImage:coloredImage forState:UIControlStateNormal]; } else { // Fallback on earlier versions diff --git a/VoidLink/ViewControllers/MainFrameViewController.m b/VoidLink/ViewControllers/MainFrameViewController.m index f43765fe..e779f008 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.m +++ b/VoidLink/ViewControllers/MainFrameViewController.m @@ -411,20 +411,20 @@ static NSMutableSet* hostList; _selectedHost = host; bool hasValidMac = host.mac != nil && ![host.mac isEqualToString:@"00:00:00:00:00:00"]; - if ((host.state == StateOffline || host.state == StateUnknown)) { - UIAlertController* wolAlert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Wake-On-LAN"] message:@"" preferredStyle:UIAlertControllerStyleAlert]; - [wolAlert addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:nil]]; - - if (!hasValidMac) { - wolAlert.message = [LocalizationHelper localizedStringForKey: @"Host MAC unknown, unable to send WOL Packet"]; - } else { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [WakeOnLanManager wakeHost:host]; - }); - wolAlert.message = [LocalizationHelper localizedStringForKey:@"Successfully sent wake-up request. It may take a few moments for the PC to wake. If it never wakes up, ensure it's properly configured for Wake-on-LAN."]; - } - [[self activeViewController] presentViewController:wolAlert animated:YES completion:nil]; + //if (hasValidMac) { + UIAlertController* wolAlert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Wake-On-LAN"] message:@"" preferredStyle:UIAlertControllerStyleAlert]; + [wolAlert addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:nil]]; + + if (!hasValidMac) { + wolAlert.message = [LocalizationHelper localizedStringForKey: @"Host MAC unknown, unable to send WOL Packet"]; + } else { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [WakeOnLanManager wakeHost:host]; + }); + wolAlert.message = [LocalizationHelper localizedStringForKey:@"Successfully sent wake-up request. It may take a few moments for the PC to wake. If it never wakes up, ensure it's properly configured for Wake-on-LAN."]; } + [[self activeViewController] presentViewController:wolAlert animated:YES completion:nil]; + //} } - (void)pairButtonTappedForHost:(TemporaryHost *)host{ From c2d302866816281e1a27e84e25a24497f9346577 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Wed, 6 Aug 2025 23:45:53 +0800 Subject: [PATCH 12/13] further fix iPhone settings menu safe area issue --- .../ViewControllers/SettingsViewController.m | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 5dd4c0d9..fc4bb18b 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -379,22 +379,26 @@ BOOL isCustomResolution(int resolutionSelected) { return; } - UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; + UIWindow *keyWindow = [UIApplication sharedApplication].windows.firstObject; - if(parentStackWidthConstraint && parentStackLeadingConstraint) [NSLayoutConstraint deactivateConstraints:@[parentStackLeadingConstraint, parentStackWidthConstraint]]; - - switch (orientation) { - case UIDeviceOrientationLandscapeLeft: - parentStackLeadingConstraint = [_parentStack.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:0]; - parentStackWidthConstraint = [_parentStack.widthAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.widthAnchor constant:-10]; - break; - default: - parentStackLeadingConstraint = [_parentStack.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:10]; - parentStackWidthConstraint = [_parentStack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-20]; - break; + if (@available(iOS 13.0, *)) { + if(parentStackWidthConstraint && parentStackLeadingConstraint) [NSLayoutConstraint deactivateConstraints:@[parentStackLeadingConstraint, parentStackWidthConstraint]]; + UIInterfaceOrientation currentOrientation = keyWindow.windowScene.interfaceOrientation; + switch (currentOrientation) { + case UIInterfaceOrientationLandscapeRight: + parentStackLeadingConstraint = [_parentStack.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:0]; + parentStackWidthConstraint = [_parentStack.widthAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.widthAnchor constant:-10]; + break; + default: + parentStackLeadingConstraint = [_parentStack.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:10]; + parentStackWidthConstraint = [_parentStack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-20]; + break; + } + [NSLayoutConstraint activateConstraints:@[parentStackLeadingConstraint, parentStackWidthConstraint]]; + } else { + // Fallback on earlier versions } - [NSLayoutConstraint activateConstraints:@[parentStackLeadingConstraint, parentStackWidthConstraint]]; double delayInSeconds = 0.05; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); From c32f2d3f5022da8b9602e056e4eb87beb1332547 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Thu, 7 Aug 2025 01:50:49 +0800 Subject: [PATCH 13/13] fix broken osw widget profile after switching app to background --- .../LayoutOnScreenControlsViewController.m | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m index 8b847391..25a389ad 100644 --- a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m +++ b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.m @@ -255,6 +255,10 @@ selector: @selector(handleReturnToForeground) name: UIApplicationDidBecomeActiveNotification object: nil]; + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(handleEnterBackground) + name: UIApplicationWillResignActiveNotification + object: nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange) // handle orientation change since i made portrait mode available name:UIDeviceOrientationDidChangeNotification @@ -267,24 +271,7 @@ #pragma mark - Class Helper Functions -- (void)handleReturnToForeground { - [OSCProfilesManager setOnScreenWidgetViewsSet:self.onScreenWidgetViews]; // pass the keyboard button dict to profiles manager -} - -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator{ - viewWillBeResized = true; - [self clearStickIndicator]; - if(!_quickSwitchEnabled) [self saveTapped:nil]; -} - -- (void)deviceOrientationDidChange{ - [self performSelector:@selector(handleOrientationChangeForOnScreenWidgets) withObject:self afterDelay:0.05]; -} - -- (void)handleOrientationChangeForOnScreenWidgets{ - if(!viewWillBeResized) return; - [self setupWidgetPanel]; - +- (void)updateViewBounds{ viewWillBeResized = false; selectedWidgetView = nil; selectedControllerLayer = nil; @@ -295,6 +282,33 @@ [self reloadLegacyOnScreenControls]; } +- (void)handleEnterBackground{ + [self saveTapped:nil]; +} + +- (void)handleReturnToForeground { + // [OSCProfilesManager setOnScreenWidgetViewsSet:self.onScreenWidgetViews]; // pass the keyboard button dict to profiles manager + [self setupWidgetPanel]; + [self updateViewBounds]; +} + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator{ + if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) return; + viewWillBeResized = true; + [self clearStickIndicator]; + if(!_quickSwitchEnabled) [self saveTapped:nil]; +} + +- (void)deviceOrientationDidChange{ + [self performSelector:@selector(handleOrientationChangeForOnScreenWidgets) withObject:self afterDelay:0.0]; +} + +- (void)handleOrientationChangeForOnScreenWidgets{ + if(!viewWillBeResized) return; + [self setupWidgetPanel]; + [self updateViewBounds]; +} + /* fades the 'Undo Button' in or out depending on whether the user has any OSC layout changes to undo */ - (void) OSCLayoutChanged { if ([self.layoutOSC.layoutChanges count] > 0) {