refactor: adopt UIScene lifecycle

feat: add initial support for pip

feat: add setting toggle for pip

fix: let os handle invokation of pip which is more reliable

fix: UIScene compatibility with airplay

fix: black screen on external display when on app view and streaming

fix: incorrect window size when launched directly on external display

fix: wrong resolution shown in settings when in stream session

Enable gamemode flag for iOS18+

Explicitly enters pip mode when in background
This commit is contained in:
Acaki committed 2025-07-11 01:34:53 +08:00
1 parent 51e915b598
commit b5ce7a8ff7
24 files changed
+480 -144

No files matched your search

+19 -1
View File
@@ -570,7 +570,7 @@ ICAgICAgICAgICAgIA
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Local Mouse Pointer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VGn-Oc-mKc">
<rect key="frame" x="22" y="2007" width="165" height="21"/>
<rect key="frame" x="20" y="2008" width="165" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -635,6 +635,23 @@ ICAgICAgICAgICAgIA
<action selector="backToStreamingButtonTapped:" destination="rYd-e6-cQU" eventType="touchUpInside" id="9P0-Yw-T4W"/>
</connections>
</button>
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="dcP-yB-6Un" userLabel="PiP Selector">
<rect key="frame" x="18" y="2102" width="459" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<segments>
<segment title="No"/>
<segment title="Yes"/>
</segments>
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Enable PiP" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ELc-8r-KSB">
<rect key="frame" x="20" y="2075" width="86" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
@@ -664,6 +681,7 @@ ICAgICAgICAgICAgIA
<outlet property="optimizeSettingsSelector" destination="Gob-Lu-b1y" id="9Wl-yp-0Ou"/>
<outlet property="oscTapExlusionAreaSizeLabel" destination="Dfl-gL-00M" id="Jbg-64-P3M"/>
<outlet property="oscTapExlusionAreaSizeSlider" destination="TSt-Hj-uTR" id="h9l-ap-AVd"/>
<outlet property="pipSelector" destination="dcP-yB-6Un" id="rrB-dR-2Bq"/>
<outlet property="pointerVelocityModeDividerSlider" destination="xet-C9-l5y" id="GnQ-xU-e0N"/>
<outlet property="pointerVelocityModeDividerUILabel" destination="VlN-ZW-1gm" id="vbe-QK-hZb"/>
<outlet property="resolutionDisplayView" destination="Dej-Bj-F6i" id="bi4-N6-snx"/>
+12
View File
@@ -0,0 +1,12 @@
#import <UIKit/UIKit.h>
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (strong, nonatomic) UIWindow * window;
+ (void)setExternalDisplayRenderView:(UIView *)renderView;
+ (void)clearExternalDisplayRenderView;
- (void)updatePreferredDisplayMode:(BOOL)streamActive withRenderView:(UIView *)renderView;
@end
+91
View File
@@ -0,0 +1,91 @@
#import "SceneDelegate.h"
#import "StreamFrameViewController.h"
@implementation SceneDelegate
static UIView *_sharedStreamVideoRenderView = nil;
static UIWindow *_externalSceneWindow = nil;
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
if (![scene isKindOfClass:[UIWindowScene class]]) {
return;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
if ([session.role isEqualToString:UIWindowSceneSessionRoleApplication]) {
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
NSString *storyboardName;
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
storyboardName = @"iPad";
} else {
storyboardName = @"iPhone";
}
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
UIViewController *initialViewController = [storyboard instantiateInitialViewController];
self.window.rootViewController = initialViewController;
[self.window makeKeyAndVisible];
Log(LOG_I, @"SceneDelegate: Main app scene connected.");
} else if ([session.role isEqualToString:UIWindowSceneSessionRoleExternalDisplay]) {
Log(LOG_I, @"SceneDelegate: External display scene connecting for screen: %@", ((UIWindowScene *)scene).screen.description);
UIWindowScene *windowScene = (UIWindowScene *)scene;
_externalSceneWindow = [[UIWindow alloc] initWithWindowScene:windowScene];
UIViewController *externalVC = [[UIViewController alloc] init];
externalVC.view.backgroundColor = [UIColor blackColor]; // Set a default background
_externalSceneWindow.rootViewController = externalVC;
if (_sharedStreamVideoRenderView) {
_sharedStreamVideoRenderView.frame = _externalSceneWindow.bounds;
[_externalSceneWindow.rootViewController.view addSubview:_sharedStreamVideoRenderView];
Log(LOG_I, @"SceneDelegate: External display scene connected.");
}
}
}
// Method for StreamFrameViewController to provide its render view
+ (void)setExternalDisplayRenderView:(UIView *)renderView {
_sharedStreamVideoRenderView = renderView;
if (_externalSceneWindow && _externalSceneWindow.rootViewController && _sharedStreamVideoRenderView) {
dispatch_async(dispatch_get_main_queue(), ^{
// Ensure it's removed from any previous parent (should have been done by StreamFrameVC)
[_sharedStreamVideoRenderView removeFromSuperview];
_sharedStreamVideoRenderView.frame = _externalSceneWindow.bounds; // Set frame for external window
[_externalSceneWindow.rootViewController.view addSubview:_sharedStreamVideoRenderView];
_externalSceneWindow.hidden = NO;
Log(LOG_I, @"SceneDelegate: Added render view to external window's root view.");
});
} else {
Log(LOG_E, @"SceneDelegate: External display window or root view controller not available.");
}
}
+ (void)clearExternalDisplayRenderView {
if (_sharedStreamVideoRenderView) {
dispatch_async(dispatch_get_main_queue(), ^{
[_sharedStreamVideoRenderView removeFromSuperview];
Log(LOG_I, @"SceneDelegate: Removed render view from external display.");
});
}
_sharedStreamVideoRenderView = nil;
}
- (void)sceneDidDisconnect:(UIScene *)scene {
Log(LOG_I, @"SceneDelegate: Scene disconnected: %@, role: %@", scene.title, scene.session.role);
if ([scene.session.role isEqualToString:UIWindowSceneSessionRoleExternalDisplay]) {
if ([scene isKindOfClass:[UIWindowScene class]]) {
UIWindowScene *windowScene = (UIWindowScene *)scene;
if (_externalSceneWindow == windowScene.windows.firstObject) { // Compare with the window from the disconnecting scene
[SceneDelegate clearExternalDisplayRenderView]; // Clears the shared view
_externalSceneWindow = nil;
Log(LOG_I, @"SceneDelegate: External display scene fully disconnected and cleaned up.");
} else {
Log(LOG_W, @"SceneDelegate: Disconnecting scene is not the one holding our _externalSceneWindow.");
}
} else {
Log(LOG_W, @"SceneDelegate: Disconnecting scene is not a UIWindowScene.");
}
}
}
@end
@@ -30,6 +30,7 @@
@property (strong, nonatomic) IBOutlet UISegmentedControl *audioOnPCSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *codecSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *yuv444Selector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *pipSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *hdrSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *btMouseSelector;
@@ -228,7 +228,7 @@ BOOL isCustomResolution(CGSize res) {
}
- (void)updateResolutionTable{
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
UIWindow *window = self.view.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;
@@ -465,6 +465,7 @@ BOOL isCustomResolution(CGSize res) {
}
[self.yuv444Selector setSelectedSegmentIndex:currentSettings.enableYUV444 ? 1 : 0];
[self.pipSelector setSelectedSegmentIndex:currentSettings.enablePIP ? 1 : 0];
[self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlayLevel.intValue];
[self.btMouseSelector setSelectedSegmentIndex:currentSettings.btMouseSupport ? 1 : 0];
[self.optimizeSettingsSelector setSelectedSegmentIndex:currentSettings.optimizeGames ? 1 : 0];
@@ -1079,6 +1080,7 @@ BOOL isCustomResolution(CGSize res) {
BOOL audioOnPC = [self.audioOnPCSelector selectedSegmentIndex] == 1;
uint32_t preferredCodec = [self getChosenCodecPreference];
BOOL enableYUV444 = [self.yuv444Selector selectedSegmentIndex] == 1;
BOOL enablePIP = [self.pipSelector selectedSegmentIndex] == 1;
BOOL btMouseSupport = [self.btMouseSelector selectedSegmentIndex] == 1;
BOOL useFramePacing = [self.framePacingSelector selectedSegmentIndex] == 1;
// BOOL absoluteTouchMode = [self.touchModeSelector selectedSegmentIndex] == 1;
@@ -1116,6 +1118,7 @@ BOOL isCustomResolution(CGSize res) {
audioOnPC:audioOnPC
preferredCodec:preferredCodec
enableYUV444:enableYUV444
enablePIP:enablePIP
useFramePacing:useFramePacing
enableHdr:enableHdr
btMouseSupport:btMouseSupport