From 589ca19e4f1939038aab4af943ecfda89ff2b5bd Mon Sep 17 00:00:00 2001 From: xzzpig Date: Mon, 18 Nov 2024 19:04:08 +0800 Subject: [PATCH 1/3] Add AirPlay support --- Limelight/Base.lproj/iPad.storyboard | 36 +++-- Limelight/Base.lproj/iPhone.storyboard | 22 +++- Limelight/Database/DataManager.h | 3 +- Limelight/Database/DataManager.m | 5 +- Limelight/Database/TemporarySettings.h | 1 + Limelight/Database/TemporarySettings.m | 1 + Limelight/Limelight-Info.plist | 2 +- .../Moonlight v1.10.xcdatamodel/contents | 1 + .../Moonlight v1.11.xcdatamodel/contents | 1 + Limelight/Stream/VideoDecoderRenderer.m | 5 + .../ViewControllers/MainFrameViewController.m | 43 +++--- .../ViewControllers/SettingsViewController.h | 1 + .../ViewControllers/SettingsViewController.m | 17 ++- .../StreamFrameViewController.m | 124 +++++++++++++++++- Limelight/mul.lproj/iPad.xcstrings | 90 +++++++++++++ Limelight/mul.lproj/iPhone.xcstrings | 90 +++++++++++++ 16 files changed, 408 insertions(+), 34 deletions(-) diff --git a/Limelight/Base.lproj/iPad.storyboard b/Limelight/Base.lproj/iPad.storyboard index 800737c5..eb385bf3 100644 --- a/Limelight/Base.lproj/iPad.storyboard +++ b/Limelight/Base.lproj/iPad.storyboard @@ -1,9 +1,9 @@ - + - + @@ -12,7 +12,7 @@ - + @@ -547,6 +547,23 @@ ICAgICAgICAgICAgICA + + + + + + + + + + + @@ -559,6 +576,7 @@ ICAgICAgICAgICAgICA + @@ -607,7 +625,7 @@ ICAgICAgICAgICAgICA - + @@ -647,7 +665,7 @@ ICAgICAgICAgICAgICA - + @@ -669,7 +687,7 @@ ICAgICAgICAgICAgICA - + @@ -696,7 +714,7 @@ ICAgICAgICAgICAgICA - + @@ -893,11 +911,11 @@ ICAgICAgICAgICAgICA - + - + diff --git a/Limelight/Base.lproj/iPhone.storyboard b/Limelight/Base.lproj/iPhone.storyboard index 11ded364..1cc95c98 100644 --- a/Limelight/Base.lproj/iPhone.storyboard +++ b/Limelight/Base.lproj/iPhone.storyboard @@ -1,9 +1,9 @@ - + - + @@ -563,6 +563,23 @@ ICAgICAgICAgICAgIA + + + + + + + + + + + @@ -575,6 +592,7 @@ ICAgICAgICAgICAgIA + diff --git a/Limelight/Database/DataManager.h b/Limelight/Database/DataManager.h index fd6e03ec..d4708696 100644 --- a/Limelight/Database/DataManager.h +++ b/Limelight/Database/DataManager.h @@ -43,7 +43,8 @@ touchMode:(NSInteger)touchMode statsOverlay:(BOOL)statsOverlay allowPortrait:(BOOL)allowPortrait - resolutionSelected:(NSInteger)resolutionSelected; + resolutionSelected:(NSInteger)resolutionSelected + externalDisplayMode:(NSInteger)externalDisplayMode; - (NSArray*) getHosts; - (void) updateHost:(TemporaryHost*)host; diff --git a/Limelight/Database/DataManager.m b/Limelight/Database/DataManager.m index 95be2e2d..ef1de168 100644 --- a/Limelight/Database/DataManager.m +++ b/Limelight/Database/DataManager.m @@ -82,7 +82,8 @@ touchMode:(NSInteger)touchMode statsOverlay:(BOOL)statsOverlay allowPortrait:(BOOL)allowPortrait - resolutionSelected:(NSInteger)resolutionSelected { + resolutionSelected:(NSInteger)resolutionSelected + externalDisplayMode:(NSInteger)externalDisplayMode { [_managedObjectContext performBlockAndWait:^{ Settings* settingsToSave = [self retrieveSettings]; @@ -117,7 +118,7 @@ settingsToSave.statsOverlay = statsOverlay; settingsToSave.allowPortrait = allowPortrait; settingsToSave.resolutionSelected = [NSNumber numberWithInteger:resolutionSelected]; - + settingsToSave.externalDisplayMode = [NSNumber numberWithInteger:externalDisplayMode]; [self saveData]; }]; } diff --git a/Limelight/Database/TemporarySettings.h b/Limelight/Database/TemporarySettings.h index 1c238c05..b536d596 100644 --- a/Limelight/Database/TemporarySettings.h +++ b/Limelight/Database/TemporarySettings.h @@ -34,6 +34,7 @@ @property (nonatomic, retain) NSNumber * pointerVelocityModeDivider; @property (nonatomic, retain) NSString * uniqueId; @property (nonatomic, retain) NSNumber * resolutionSelected; +@property (nonatomic, retain) NSNumber * externalDisplayMode; @property (nonatomic) enum { CODEC_PREF_AUTO, CODEC_PREF_H264, diff --git a/Limelight/Database/TemporarySettings.m b/Limelight/Database/TemporarySettings.m index c3eef3c0..837ebbc6 100644 --- a/Limelight/Database/TemporarySettings.m +++ b/Limelight/Database/TemporarySettings.m @@ -100,6 +100,7 @@ self.pointerVelocityModeDivider = settings.pointerVelocityModeDivider; self.allowPortrait = settings.allowPortrait; self.resolutionSelected = settings.resolutionSelected; + self.externalDisplayMode = settings.externalDisplayMode; #endif self.uniqueId = settings.uniqueId; diff --git a/Limelight/Limelight-Info.plist b/Limelight/Limelight-Info.plist index f29ea4d0..541e31d5 100644 --- a/Limelight/Limelight-Info.plist +++ b/Limelight/Limelight-Info.plist @@ -74,7 +74,7 @@ armv7 UIRequiresFullScreen - + UIStatusBarHidden UISupportedInterfaceOrientations diff --git a/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents b/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents index 6bb357d8..bc7f12b1 100644 --- a/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents +++ b/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents @@ -27,6 +27,7 @@ + diff --git a/Limelight/Limelight.xcdatamodeld/Moonlight v1.11.xcdatamodel/contents b/Limelight/Limelight.xcdatamodeld/Moonlight v1.11.xcdatamodel/contents index 2f8369a0..77d2f0e8 100644 --- a/Limelight/Limelight.xcdatamodeld/Moonlight v1.11.xcdatamodel/contents +++ b/Limelight/Limelight.xcdatamodeld/Moonlight v1.11.xcdatamodel/contents @@ -27,6 +27,7 @@ + diff --git a/Limelight/Stream/VideoDecoderRenderer.m b/Limelight/Stream/VideoDecoderRenderer.m index a2adf08d..9c019a9e 100644 --- a/Limelight/Stream/VideoDecoderRenderer.m +++ b/Limelight/Stream/VideoDecoderRenderer.m @@ -90,6 +90,11 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, [self reinitializeDisplayLayer]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reinitializeDisplayLayer) + name:@"ScreenChanged" + object:nil]; + return self; } diff --git a/Limelight/ViewControllers/MainFrameViewController.m b/Limelight/ViewControllers/MainFrameViewController.m index 9a3dd723..fc03be11 100644 --- a/Limelight/ViewControllers/MainFrameViewController.m +++ b/Limelight/ViewControllers/MainFrameViewController.m @@ -617,6 +617,7 @@ static NSMutableSet* hostList; } - (void) prepareToStreamApp:(TemporaryApp *)app { + [self updateResolutionAccordingly]; self.revealViewController.isStreaming = true; // tell the revealViewController streaming is started. _streamConfig = [[StreamConfiguration alloc] init]; _streamConfig.host = app.host.activeAddress; @@ -634,9 +635,14 @@ static NSMutableSet* hostList; _streamConfig.frameRate = [streamSettings.framerate intValue]; if (@available(iOS 10.3, *)) { + UIWindow *window = UIApplication.sharedApplication.windows.firstObject; + NSInteger maximumFramesPerSecond = window.screen.maximumFramesPerSecond; + if(UIScreen.screens.count > 1 && streamSettings.externalDisplayMode.intValue == 1){ //AirPlaying + maximumFramesPerSecond = UIScreen.screens.lastObject.maximumFramesPerSecond; + } // Don't stream more FPS than the display can show - if (_streamConfig.frameRate > [UIScreen mainScreen].maximumFramesPerSecond) { - _streamConfig.frameRate = (int)[UIScreen mainScreen].maximumFramesPerSecond; + if (_streamConfig.frameRate > maximumFramesPerSecond) { + _streamConfig.frameRate = (int)maximumFramesPerSecond; Log(LOG_W, @"Clamping FPS to maximum refresh rate: %d", _streamConfig.frameRate); } } @@ -1171,7 +1177,7 @@ static NSMutableSet* hostList; //if([SettingsViewController isLandscapeNow] != _streamConfig.width > _streamConfig.height) //[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app. //[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app. - [self updateResolutionAccordingly]; + //[self updateResolutionAccordingly]; // SettingsViewController* settingsViewController = (SettingsViewController*)[self.revealViewController rearViewController]; // [settingsViewController updateResolutionTable]; @@ -1219,17 +1225,17 @@ static NSMutableSet* hostList; } // this will also be called back when device orientation changes -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - - double delayInSeconds = 0.7; - // Convert the delay into a dispatch_time_t value - dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - // Perform some task after the delay - dispatch_after(delayTime, dispatch_get_main_queue(), ^{// Code to execute after the delay - [self updateResolutionAccordingly]; - }); -} +//- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { +// [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; +// +// double delayInSeconds = 0.7; +// // Convert the delay into a dispatch_time_t value +// dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); +// // Perform some task after the delay +// dispatch_after(delayTime, dispatch_get_main_queue(), ^{// Code to execute after the delay +// [self updateResolutionAccordingly]; +// }); +//} -(void) updateResolutionAccordingly { DataManager* dataMan = [[DataManager alloc] init]; @@ -1240,6 +1246,13 @@ static NSMutableSet* hostList; 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){ + CGRect bounds = [UIScreen.screens.lastObject bounds]; + screenScale = [UIScreen.screens.lastObject scale]; + appWindowWidth = bounds.size.width * screenScale; + appWindowHeight = bounds.size.height * screenScale; + } bool needSwap = false; @@ -1398,7 +1411,7 @@ static NSMutableSet* hostList; object: nil]; //[self simulateSettingsButtonPress]; //force reload resolution table in the setting //[self simulateSettingsButtonPress]; - [self updateResolutionAccordingly]; + //[self updateResolutionAccordingly]; } diff --git a/Limelight/ViewControllers/SettingsViewController.h b/Limelight/ViewControllers/SettingsViewController.h index 852a5e77..360d101e 100644 --- a/Limelight/ViewControllers/SettingsViewController.h +++ b/Limelight/ViewControllers/SettingsViewController.h @@ -57,6 +57,7 @@ @property (strong, nonatomic) LayoutOnScreenControlsViewController *layoutOnScreenControlsVC; @property (nonatomic, strong) MainFrameViewController *mainFrameViewController; +@property (strong, nonatomic) IBOutlet UISegmentedControl *externalDisplayModeSelector; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" diff --git a/Limelight/ViewControllers/SettingsViewController.m b/Limelight/ViewControllers/SettingsViewController.m index 02a3a5a3..6672229d 100644 --- a/Limelight/ViewControllers/SettingsViewController.m +++ b/Limelight/ViewControllers/SettingsViewController.m @@ -222,6 +222,9 @@ BOOL isCustomResolution(CGSize res) { return true; } +- (bool)isAirPlayEnabled{ + return [self.externalDisplayModeSelector selectedSegmentIndex] == 1; +} - (void)updateResolutionTable{ UIWindow *window = UIApplication.sharedApplication.windows.firstObject; @@ -229,6 +232,12 @@ BOOL isCustomResolution(CGSize res) { 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; resolutionTable[4] = CGSizeMake(safeAreaWidth, appWindowHeight); @@ -248,7 +257,7 @@ BOOL isCustomResolution(CGSize res) { // this will also be called back when device orientation changes - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - double delayInSeconds = 1.0; + double delayInSeconds = 0.2; // Convert the delay into a dispatch_time_t value dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); // Perform some task after the delay @@ -549,6 +558,8 @@ BOOL isCustomResolution(CGSize res) { self.layoutOnScreenControlsVC = [storyboard instantiateViewControllerWithIdentifier:@"LayoutOnScreenControlsViewController"]; self.layoutOnScreenControlsVC.modalPresentationStyle = UIModalPresentationFullScreen; } + + [self.externalDisplayModeSelector setSelectedSegmentIndex:currentSettings.externalDisplayMode.integerValue]; } - (void)slideToSettingsScreenEdgeChanged{ @@ -1055,6 +1066,7 @@ BOOL isCustomResolution(CGSize res) { BOOL enableHdr = [self.hdrSelector selectedSegmentIndex] == 1; BOOL allowPortrait = [self.allowPortraitSelector selectedSegmentIndex] == 1; NSInteger resolutionSelected = [self.resolutionSelector selectedSegmentIndex]; + NSInteger externalDisplayMode = [self.externalDisplayModeSelector selectedSegmentIndex]; [dataMan saveSettingsWithBitrate:_bitrate framerate:framerate height:height @@ -1085,7 +1097,8 @@ BOOL isCustomResolution(CGSize res) { touchMode:touchMode statsOverlay:statsOverlay allowPortrait:allowPortrait - resolutionSelected:resolutionSelected]; + resolutionSelected:resolutionSelected + externalDisplayMode:externalDisplayMode]; } - (void)didReceiveMemoryWarning { diff --git a/Limelight/ViewControllers/StreamFrameViewController.m b/Limelight/ViewControllers/StreamFrameViewController.m index babc59fd..07ff44ba 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -54,6 +54,10 @@ UIScrollView *_scrollView; BOOL _userIsInteracting; CGSize _keyboardSize; + UIWindow *_extWindow; + UIView *_renderView; + UIWindow *_deviceWindow; + dispatch_block_t _delayedRemoveExtScreen; #if !TARGET_OS_TV CustomEdgeSlideGestureRecognizer *_slideToSettingsRecognizer; CustomEdgeSlideGestureRecognizer *_slideToCmdToolRecognizer; @@ -162,6 +166,8 @@ [self->_streamView reloadOnScreenControlsRealtimeWith:(ControllerSupport*)_controllerSupport andConfig:(StreamConfiguration*)_streamConfig]; //reload OSC here. [self->_streamView reloadOnScreenButtonViews]; //reload keyboard buttons here. the keyboard button view will be added to the streamframe view instead streamview, the highest layer, which saves a lot of reengineering + [self reloadAirPlayConfig]; + //reconfig statsOverlay self->_statsUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self @@ -179,6 +185,29 @@ - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; + + _deviceWindow = self.view.window; + + if (UIScreen.screens.count > 1 && [self isAirPlayEnabled]) { + [self prepExtScreen:UIScreen.screens.lastObject]; + } + else { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.view insertSubview:self->_renderView atIndex:0]; + }); + } + + // check to see if external screen is connected/disconnected + + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(extScreenDidConnect:) + name: UIScreenDidConnectNotification + object: nil]; + + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(extScreenDidDisconnect:) + name: UIScreenDidDisconnectNotification + object: nil]; #if !TARGET_OS_TV [[self revealViewController] setPrimaryViewController:self]; @@ -239,7 +268,9 @@ _controllerSupport = [[ControllerSupport alloc] initWithConfig:self.streamConfig delegate:self]; _inactivityTimer = nil; + _renderView = (StreamView*)[[UIView alloc] initWithFrame:self.view.frame]; _streamView = [[StreamView alloc] initWithFrame:self.view.frame]; + _renderView.bounds = _streamView.bounds; //[_streamView setupStreamView:_controllerSupport interactionDelegate:self config:self.streamConfig]; [self reConfigStreamViewRealtime]; // call this method again to make sure all gestures are configured & added to the superview(self.view), including the gestures added from inside the streamview. @@ -281,7 +312,7 @@ _tipLabel.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height * 0.9); _streamMan = [[StreamManager alloc] initWithConfig:self.streamConfig - renderView:_streamView + renderView:_renderView connectionCallbacks:self]; NSOperationQueue* opQueue = [[NSOperationQueue alloc] init]; [opQueue addOperation:_streamMan]; @@ -449,8 +480,86 @@ _statsUpdateTimer = nil; [self.navigationController popToRootViewControllerAnimated:YES]; + + _extWindow = nil; } +// External Screen connected +- (void)extScreenDidConnect:(NSNotification *)notification { + Log(LOG_I, @"External Screen Connected"); + dispatch_async(dispatch_get_main_queue(), ^{ + [self prepExtScreen:notification.object]; + }); +} + +// External Screen disconnected +- (void)extScreenDidDisconnect:(NSNotification *)notification { + Log(LOG_I, @"External Screen Disconnected"); + if(UIScreen.screens.count < 2) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self removeExtScreen]; + }); + } +} + +- (BOOL) isAirPlaying{ + return _extWindow != nil && _extWindow.hidden == NO; +} + +- (BOOL) isAirPlayEnabled{ + return _settings.externalDisplayMode.intValue == 1; +} + +- (void) reloadAirPlayConfig{ + if (UIScreen.screens.count == 1){return;} + if (![self isAirPlaying] && [self isAirPlayEnabled]){ + [self prepExtScreen:UIScreen.screens.lastObject]; + }else if ([self isAirPlaying] && ![self isAirPlayEnabled]){ + [self removeExtScreen]; + } +} + +// Prepare Screen +- (void)prepExtScreen:(UIScreen*)extScreen { + Log(LOG_I, @"Preparing External Screen"); + if(![self isAirPlayEnabled]){ + return; + } + CGRect frame = extScreen.bounds; + extScreen.overscanCompensation = UIScreenOverscanCompensationNone; + if(_extWindow==nil){ + _extWindow = [[UIWindow alloc] initWithFrame:frame]; + } + _extWindow.screen = extScreen; + _renderView.bounds = frame; + _renderView.frame = frame; + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName:@"ScreenChanged" object:self]; + [_extWindow addSubview:_renderView]; + _extWindow.hidden = NO; +} + +- (void)removeExtScreen { + Log(LOG_I, @"Removing External Screen"); + _extWindow.hidden = YES; + [self handleViewResize]; + [self.view insertSubview:_renderView atIndex:0]; +} + +- (void) handleViewResize{ + _streamView.bounds = _deviceWindow.bounds; + _streamView.frame = _deviceWindow.frame; + if(![self isAirPlaying]){ + _renderView.bounds = _deviceWindow.bounds; + _renderView.frame = _deviceWindow.frame; + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName:@"ScreenChanged" object:self]; + } + [self reConfigStreamViewRealtime]; +} + + // This will fire if the user opens control center or gets a low battery message - (void)applicationWillResignActive:(NSNotification *)notification { if (_inactivityTimer != nil) { @@ -523,6 +632,7 @@ // the first frame of video. self->_stageLabel.hidden = YES; self->_tipLabel.hidden = YES; + self->_spinner.hidden = YES; [self->_streamView showOnScreenControls]; @@ -864,7 +974,17 @@ [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; Log(LOG_I, @"View size changed, terminating stream"); - // [self returnToMainFrame]; + + double delayInSeconds = 0.2; + if (_delayedRemoveExtScreen) { + dispatch_block_cancel(_delayedRemoveExtScreen); + } + dispatch_block_t block = dispatch_block_create(0, ^{ + [self handleViewResize]; + }); + _delayedRemoveExtScreen = block; + dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); + dispatch_after(delayTime, dispatch_get_main_queue(), block); } @end diff --git a/Limelight/mul.lproj/iPad.xcstrings b/Limelight/mul.lproj/iPad.xcstrings index dc2f0eaf..2b332554 100644 --- a/Limelight/mul.lproj/iPad.xcstrings +++ b/Limelight/mul.lproj/iPad.xcstrings @@ -91,6 +91,36 @@ } } }, + "4Qy-hB-oa5.text" : { + "comment" : "Class = \"UILabel\"; text = \"External Display Mode\"; ObjectID = \"4Qy-hB-oa5\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "External Display Mode" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + } + } + }, "6w5-Md-NpV.text" : { "comment" : "Class = \"UILabel\"; text = \"Preferred Codec \"; ObjectID = \"6w5-Md-NpV\";", "extractionState" : "extracted_with_value", @@ -2191,6 +2221,66 @@ } } }, + "VWB-YB-Ptj.segmentTitles[0]" : { + "comment" : "Class = \"UISegmentedControl\"; VWB-YB-Ptj.segmentTitles[0] = \"Stage Manager\"; ObjectID = \"VWB-YB-Ptj\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Stage Manager" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + } + } + }, + "VWB-YB-Ptj.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; VWB-YB-Ptj.segmentTitles[1] = \"AirPlay\"; ObjectID = \"VWB-YB-Ptj\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "AirPlay" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + } + } + }, "WDY-9o-6Hb.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; WDY-9o-6Hb.segmentTitles[0] = \"No\"; ObjectID = \"WDY-9o-6Hb\";", "extractionState" : "extracted_with_value", diff --git a/Limelight/mul.lproj/iPhone.xcstrings b/Limelight/mul.lproj/iPhone.xcstrings index ec7663ec..26e95e57 100644 --- a/Limelight/mul.lproj/iPhone.xcstrings +++ b/Limelight/mul.lproj/iPhone.xcstrings @@ -271,6 +271,36 @@ } } }, + "4Qy-hB-oa5.text" : { + "comment" : "Class = \"UILabel\"; text = \"External Display Mode\"; ObjectID = \"4Qy-hB-oa5\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "External Display Mode" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + } + } + }, "5pN-Sn-SCN.title" : { "comment" : "Class = \"UINavigationItem\"; title = \"Controller & Button Profiles\"; ObjectID = \"5pN-Sn-SCN\";", "extractionState" : "extracted_with_value", @@ -2011,6 +2041,66 @@ } } }, + "VWB-YB-Ptj.segmentTitles[0]" : { + "comment" : "Class = \"UISegmentedControl\"; VWB-YB-Ptj.segmentTitles[0] = \"Stage Manager\"; ObjectID = \"VWB-YB-Ptj\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Stage Manager" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + } + } + }, + "VWB-YB-Ptj.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; VWB-YB-Ptj.segmentTitles[1] = \"AirPlay\"; ObjectID = \"VWB-YB-Ptj\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "AirPlay" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + } + } + }, "WGf-9d-eAm.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; WGf-9d-eAm.segmentTitles[0] = \"Off\"; ObjectID = \"WGf-9d-eAm\";", "extractionState" : "extracted_with_value", From 62ae63d7f6fe6ce48b49d0f302b2fdc7aa709644 Mon Sep 17 00:00:00 2001 From: xzzpig Date: Fri, 29 Nov 2024 23:00:54 +0800 Subject: [PATCH 2/3] Allow not capture mouse --- Limelight/Base.lproj/iPad.storyboard | 19 +++ Limelight/Base.lproj/iPhone.storyboard | 19 +++ Limelight/Database/DataManager.h | 3 +- Limelight/Database/DataManager.m | 4 +- Limelight/Database/TemporarySettings.h | 1 + Limelight/Database/TemporarySettings.m | 1 + Limelight/Input/ControllerSupport.m | 33 +++-- Limelight/Input/StreamView.m | 13 +- .../Moonlight v1.10.xcdatamodel/contents | 3 +- Limelight/Stream/StreamConfiguration.h | 1 + .../CustomEdgeSlideGestureRecognizer.m | 2 +- .../ViewControllers/MainFrameViewController.m | 1 + .../ViewControllers/SettingsViewController.h | 1 + .../ViewControllers/SettingsViewController.m | 5 +- .../StreamFrameViewController.m | 3 +- Limelight/mul.lproj/iPad.xcstrings | 120 ++++++++++++++++++ Limelight/mul.lproj/iPhone.xcstrings | 120 ++++++++++++++++++ 17 files changed, 327 insertions(+), 22 deletions(-) diff --git a/Limelight/Base.lproj/iPad.storyboard b/Limelight/Base.lproj/iPad.storyboard index eb385bf3..9923baa9 100644 --- a/Limelight/Base.lproj/iPad.storyboard +++ b/Limelight/Base.lproj/iPad.storyboard @@ -554,6 +554,13 @@ ICAgICAgICAgICAgICA + @@ -564,6 +571,17 @@ ICAgICAgICAgICAgICA + + + + + + + + + + + @@ -585,6 +603,7 @@ ICAgICAgICAgICAgICA + diff --git a/Limelight/Base.lproj/iPhone.storyboard b/Limelight/Base.lproj/iPhone.storyboard index 1cc95c98..943ff049 100644 --- a/Limelight/Base.lproj/iPhone.storyboard +++ b/Limelight/Base.lproj/iPhone.storyboard @@ -580,6 +580,24 @@ ICAgICAgICAgICAgIA + + + + + + + + + + + + @@ -601,6 +619,7 @@ ICAgICAgICAgICAgIA + diff --git a/Limelight/Database/DataManager.h b/Limelight/Database/DataManager.h index d4708696..eeed64e2 100644 --- a/Limelight/Database/DataManager.h +++ b/Limelight/Database/DataManager.h @@ -44,7 +44,8 @@ statsOverlay:(BOOL)statsOverlay allowPortrait:(BOOL)allowPortrait resolutionSelected:(NSInteger)resolutionSelected - externalDisplayMode:(NSInteger)externalDisplayMode; + externalDisplayMode:(NSInteger)externalDisplayMode + mouseMode:(NSInteger)mouseMode; - (NSArray*) getHosts; - (void) updateHost:(TemporaryHost*)host; diff --git a/Limelight/Database/DataManager.m b/Limelight/Database/DataManager.m index ef1de168..bc6e1168 100644 --- a/Limelight/Database/DataManager.m +++ b/Limelight/Database/DataManager.m @@ -83,7 +83,8 @@ statsOverlay:(BOOL)statsOverlay allowPortrait:(BOOL)allowPortrait resolutionSelected:(NSInteger)resolutionSelected - externalDisplayMode:(NSInteger)externalDisplayMode { + externalDisplayMode:(NSInteger)externalDisplayMode + mouseMode:(NSInteger)mouseMode { [_managedObjectContext performBlockAndWait:^{ Settings* settingsToSave = [self retrieveSettings]; @@ -119,6 +120,7 @@ settingsToSave.allowPortrait = allowPortrait; settingsToSave.resolutionSelected = [NSNumber numberWithInteger:resolutionSelected]; settingsToSave.externalDisplayMode = [NSNumber numberWithInteger:externalDisplayMode]; + settingsToSave.mouseMode = [NSNumber numberWithInteger:mouseMode]; [self saveData]; }]; } diff --git a/Limelight/Database/TemporarySettings.h b/Limelight/Database/TemporarySettings.h index b536d596..c01ee84d 100644 --- a/Limelight/Database/TemporarySettings.h +++ b/Limelight/Database/TemporarySettings.h @@ -35,6 +35,7 @@ @property (nonatomic, retain) NSString * uniqueId; @property (nonatomic, retain) NSNumber * resolutionSelected; @property (nonatomic, retain) NSNumber * externalDisplayMode; +@property (nonatomic, retain) NSNumber * mouseMode; @property (nonatomic) enum { CODEC_PREF_AUTO, CODEC_PREF_H264, diff --git a/Limelight/Database/TemporarySettings.m b/Limelight/Database/TemporarySettings.m index 837ebbc6..02f81033 100644 --- a/Limelight/Database/TemporarySettings.m +++ b/Limelight/Database/TemporarySettings.m @@ -101,6 +101,7 @@ self.allowPortrait = settings.allowPortrait; self.resolutionSelected = settings.resolutionSelected; self.externalDisplayMode = settings.externalDisplayMode; + self.mouseMode = settings.mouseMode; #endif self.uniqueId = settings.uniqueId; diff --git a/Limelight/Input/ControllerSupport.m b/Limelight/Input/ControllerSupport.m index e8af49d9..9ceec98f 100644 --- a/Limelight/Input/ControllerSupport.m +++ b/Limelight/Input/ControllerSupport.m @@ -46,6 +46,7 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; char _controllerNumbers; bool _multiController; bool _swapABXYButtons; + bool _captureMouse; } // UPDATE_BUTTON_FLAG(controller, flag, pressed) @@ -864,20 +865,25 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; } -(void) registerMouseCallbacks:(GCMouse*) mouse API_AVAILABLE(ios(14.0)) { - mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput * _Nonnull mouse, float deltaX, float deltaY) { - self->accumulatedDeltaX += deltaX / MOUSE_SPEED_DIVISOR; - self->accumulatedDeltaY += -deltaY / MOUSE_SPEED_DIVISOR; - - short truncatedDeltaX = (short)self->accumulatedDeltaX; - short truncatedDeltaY = (short)self->accumulatedDeltaY; - - if (truncatedDeltaX != 0 || truncatedDeltaY != 0) { - LiSendMouseMoveEvent(truncatedDeltaX, truncatedDeltaY); + if (_captureMouse){ + mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput * _Nonnull mouse, float deltaX, float deltaY) { + self->accumulatedDeltaX += deltaX / MOUSE_SPEED_DIVISOR; + self->accumulatedDeltaY += -deltaY / MOUSE_SPEED_DIVISOR; - self->accumulatedDeltaX -= truncatedDeltaX; - self->accumulatedDeltaY -= truncatedDeltaY; - } - }; + short truncatedDeltaX = (short)self->accumulatedDeltaX; + short truncatedDeltaY = (short)self->accumulatedDeltaY; + + if (truncatedDeltaX != 0 || truncatedDeltaY != 0) { + LiSendMouseMoveEvent(truncatedDeltaX, truncatedDeltaY); + + self->accumulatedDeltaX -= truncatedDeltaX; + self->accumulatedDeltaY -= truncatedDeltaY; + } + }; + } else { + mouse.mouseInput.mouseMovedHandler = nil; + } + mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) { LiSendMouseButtonEvent(pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_LEFT); @@ -1120,6 +1126,7 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; } } + _captureMouse = (streamConfig.mouseMode == 0); if (@available(iOS 14.0, tvOS 14.0, *)) { for (GCMouse* mouse in [GCMouse mice]) { [self registerMouseCallbacks:mouse]; diff --git a/Limelight/Input/StreamView.m b/Limelight/Input/StreamView.m index 70a49db2..ed69488b 100644 --- a/Limelight/Input/StreamView.m +++ b/Limelight/Input/StreamView.m @@ -48,6 +48,8 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; double accumulatedMouseDeltaX; double accumulatedMouseDeltaY; + int mouseMode; + UIResponder* touchHandler; id interactionDelegate; @@ -76,6 +78,8 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; settings = [[[DataManager alloc] init] getSettings]; + mouseMode = streamConfig.mouseMode; + keysDown = [[NSMutableSet alloc] init]; keyInputField = [[KeyboardInputField alloc] initWithFrame:CGRectZero]; [keyInputField setKeyboardType:UIKeyboardTypeDefault]; @@ -869,7 +873,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; regionForRequest:(UIPointerRegionRequest *)request defaultRegion:(UIPointerRegion *)defaultRegion API_AVAILABLE(ios(13.4)) { if (@available(iOS 14.0, *)) { - if ([GCMouse current] != nil) { + if ([GCMouse current] != nil && mouseMode == 0) { // We'll handle this with GCMouse. Do nothing here. return nil; } @@ -897,8 +901,11 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; } - (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region API_AVAILABLE(ios(13.4)) { - // Always hide the mouse cursor over our stream view - return [UIPointerStyle hiddenPointerStyle]; + if(mouseMode != 2){ + return [UIPointerStyle hiddenPointerStyle]; + }else{ + return nil; + } } - (void)mouseWheelMovedContinuous:(UIPanGestureRecognizer *)gesture { diff --git a/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents b/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents index bc7f12b1..7cbb880a 100644 --- a/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents +++ b/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -33,6 +33,7 @@ + diff --git a/Limelight/Stream/StreamConfiguration.h b/Limelight/Stream/StreamConfiguration.h index 3073ea62..ab1aa33d 100644 --- a/Limelight/Stream/StreamConfiguration.h +++ b/Limelight/Stream/StreamConfiguration.h @@ -32,5 +32,6 @@ @property BOOL multiController; @property BOOL useFramePacing; @property NSData* serverCert; +@property int mouseMode; @end diff --git a/Limelight/ViewControllers/CustomEdgeSlideGestureRecognizer.m b/Limelight/ViewControllers/CustomEdgeSlideGestureRecognizer.m index 4703ee4f..0b202d93 100644 --- a/Limelight/ViewControllers/CustomEdgeSlideGestureRecognizer.m +++ b/Limelight/ViewControllers/CustomEdgeSlideGestureRecognizer.m @@ -18,7 +18,7 @@ static CGFloat screenWidthInPoints; - (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action { self = [super initWithTarget:target action:action]; - screenWidthInPoints = CGRectGetWidth([[UIScreen mainScreen] 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 = 15.0f; return self; diff --git a/Limelight/ViewControllers/MainFrameViewController.m b/Limelight/ViewControllers/MainFrameViewController.m index fc03be11..2331b8ea 100644 --- a/Limelight/ViewControllers/MainFrameViewController.m +++ b/Limelight/ViewControllers/MainFrameViewController.m @@ -670,6 +670,7 @@ static NSMutableSet* hostList; // multiController must be set before calling getConnectedGamepadMask _streamConfig.multiController = streamSettings.multiController; _streamConfig.gamepadMask = [ControllerSupport getConnectedGamepadMask:_streamConfig]; + _streamConfig.mouseMode = streamSettings.mouseMode.intValue; // Probe for supported channel configurations int physicalOutputChannels = (int)[AVAudioSession sharedInstance].maximumOutputNumberOfChannels; diff --git a/Limelight/ViewControllers/SettingsViewController.h b/Limelight/ViewControllers/SettingsViewController.h index 360d101e..4e1bc240 100644 --- a/Limelight/ViewControllers/SettingsViewController.h +++ b/Limelight/ViewControllers/SettingsViewController.h @@ -58,6 +58,7 @@ @property (nonatomic, strong) MainFrameViewController *mainFrameViewController; @property (strong, nonatomic) IBOutlet UISegmentedControl *externalDisplayModeSelector; +@property (strong, nonatomic) IBOutlet UISegmentedControl *mouseModeSelector; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" diff --git a/Limelight/ViewControllers/SettingsViewController.m b/Limelight/ViewControllers/SettingsViewController.m index 6672229d..3ba5545b 100644 --- a/Limelight/ViewControllers/SettingsViewController.m +++ b/Limelight/ViewControllers/SettingsViewController.m @@ -560,6 +560,7 @@ BOOL isCustomResolution(CGSize res) { } [self.externalDisplayModeSelector setSelectedSegmentIndex:currentSettings.externalDisplayMode.integerValue]; + [self.mouseModeSelector setSelectedSegmentIndex:currentSettings.mouseMode.integerValue]; } - (void)slideToSettingsScreenEdgeChanged{ @@ -1067,6 +1068,7 @@ BOOL isCustomResolution(CGSize res) { BOOL allowPortrait = [self.allowPortraitSelector selectedSegmentIndex] == 1; NSInteger resolutionSelected = [self.resolutionSelector selectedSegmentIndex]; NSInteger externalDisplayMode = [self.externalDisplayModeSelector selectedSegmentIndex]; + NSInteger mouseMode = [self.mouseModeSelector selectedSegmentIndex]; [dataMan saveSettingsWithBitrate:_bitrate framerate:framerate height:height @@ -1098,7 +1100,8 @@ BOOL isCustomResolution(CGSize res) { statsOverlay:statsOverlay allowPortrait:allowPortrait resolutionSelected:resolutionSelected - externalDisplayMode:externalDisplayMode]; + externalDisplayMode:externalDisplayMode + mouseMode:mouseMode]; } - (void)didReceiveMemoryWarning { diff --git a/Limelight/ViewControllers/StreamFrameViewController.m b/Limelight/ViewControllers/StreamFrameViewController.m index 07ff44ba..2ec28416 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -167,6 +167,7 @@ andConfig:(StreamConfiguration*)_streamConfig]; //reload OSC here. [self->_streamView reloadOnScreenButtonViews]; //reload keyboard buttons here. the keyboard button view will be added to the streamframe view instead streamview, the highest layer, which saves a lot of reengineering [self reloadAirPlayConfig]; + [self mousePresenceChanged]; //reconfig statsOverlay self->_statsUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f @@ -966,7 +967,7 @@ // Pointer lock breaks the UIKit mouse APIs, which is a problem because // GCMouse is horribly broken on iOS 14.0 for certain mice. Only lock // the cursor if there is a GCMouse present. - return [GCMouse mice].count > 0; + return ([GCMouse mice].count > 0) && [_settings mouseMode].intValue == 0; } #endif diff --git a/Limelight/mul.lproj/iPad.xcstrings b/Limelight/mul.lproj/iPad.xcstrings index 2b332554..9d5fead5 100644 --- a/Limelight/mul.lproj/iPad.xcstrings +++ b/Limelight/mul.lproj/iPad.xcstrings @@ -91,6 +91,96 @@ } } }, + "4FU-iP-c6T.segmentTitles[0]" : { + "comment" : "Class = \"UISegmentedControl\"; 4FU-iP-c6T.segmentTitles[0] = \"Capture\"; ObjectID = \"4FU-iP-c6T\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Capture" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "捕获" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "捕获" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "捕获" + } + } + } + }, + "4FU-iP-c6T.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; 4FU-iP-c6T.segmentTitles[1] = \"Hidden\"; ObjectID = \"4FU-iP-c6T\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Hidden" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐藏" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐藏" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐藏" + } + } + } + }, + "4FU-iP-c6T.segmentTitles[2]" : { + "comment" : "Class = \"UISegmentedControl\"; 4FU-iP-c6T.segmentTitles[2] = \"Visible\"; ObjectID = \"4FU-iP-c6T\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Visible" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "可见" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "可见" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "可见" + } + } + } + }, "4Qy-hB-oa5.text" : { "comment" : "Class = \"UILabel\"; text = \"External Display Mode\"; ObjectID = \"4Qy-hB-oa5\";", "extractionState" : "extracted_with_value", @@ -2167,6 +2257,36 @@ } } }, + "tAd-bm-2IO.text" : { + "comment" : "Class = \"UILabel\"; text = \"Mouse Mode\"; ObjectID = \"tAd-bm-2IO\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Mouse Mode" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "鼠标模式" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "鼠标模式" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "鼠标模式" + } + } + } + }, "uSS-ev-IFe.configuration.title" : { "comment" : "Class = \"UIButton\"; configuration.title = \"Go Back to Stream View\"; ObjectID = \"uSS-ev-IFe\";", "extractionState" : "extracted_with_value", diff --git a/Limelight/mul.lproj/iPhone.xcstrings b/Limelight/mul.lproj/iPhone.xcstrings index 26e95e57..2af3742b 100644 --- a/Limelight/mul.lproj/iPhone.xcstrings +++ b/Limelight/mul.lproj/iPhone.xcstrings @@ -961,6 +961,96 @@ } } }, + "F96-Um-Q6t.segmentTitles[0]" : { + "comment" : "Class = \"UISegmentedControl\"; F96-Um-Q6t.segmentTitles[0] = \"Capture\"; ObjectID = \"F96-Um-Q6t\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Capture" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "捕获" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "捕获" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "捕获" + } + } + } + }, + "F96-Um-Q6t.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; F96-Um-Q6t.segmentTitles[1] = \"Hidden\"; ObjectID = \"F96-Um-Q6t\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Hidden" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐藏" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐藏" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐藏" + } + } + } + }, + "F96-Um-Q6t.segmentTitles[2]" : { + "comment" : "Class = \"UISegmentedControl\"; F96-Um-Q6t.segmentTitles[2] = \"Visible\"; ObjectID = \"F96-Um-Q6t\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Visible" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "可见" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "可见" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "可见" + } + } + } + }, "FqF-bM-afQ.configuration.title" : { "comment" : "Class = \"UIButton\"; configuration.title = \"Go Back to Stream View\"; ObjectID = \"FqF-bM-afQ\";", "extractionState" : "extracted_with_value", @@ -2029,6 +2119,36 @@ } } }, + "VGn-Oc-mKc.text" : { + "comment" : "Class = \"UILabel\"; text = \"Mouse Mode\"; ObjectID = \"VGn-Oc-mKc\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Mouse Mode" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "鼠标模式" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "鼠标模式" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "鼠标模式" + } + } + } + }, "VlN-ZW-1gm.text" : { "comment" : "Class = \"UILabel\"; text = \"Touch Pointer Velocity: Scaled on 0% of Right Screen \"; ObjectID = \"VlN-ZW-1gm\";", "extractionState" : "extracted_with_value", From a0f13881bf053335bba498a6b4e0e2a348621a7c Mon Sep 17 00:00:00 2001 From: xzzpig Date: Sun, 1 Dec 2024 01:30:17 +0800 Subject: [PATCH 3/3] Add key combo support --- Limelight/Input/KeyboardSupport.m | 3 ++ Limelight/Input/StreamView.h | 4 ++ Limelight/Input/StreamView.m | 26 +++++++++++++ .../StreamFrameViewController.m | 38 +++++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/Limelight/Input/KeyboardSupport.m b/Limelight/Input/KeyboardSupport.m index 8cbf7a33..23e987d9 100644 --- a/Limelight/Input/KeyboardSupport.m +++ b/Limelight/Input/KeyboardSupport.m @@ -248,6 +248,9 @@ case UIKeyboardHIDUsageKeyboardRightAlt: keyCode = 0xA5; break; + case UIKeyboardHIDUsageKeyboardErrorUndefined: + NSLog(@"Undefined key pressed"); + return false; case 669: // This value corresponds to the "Globe" or "Language" key on most Apple branded iPad keyboards. keyCode = 0x1B; // This value corresponds to "Escape", which is missing from most Apple branded iPad keyboards. break; diff --git a/Limelight/Input/StreamView.h b/Limelight/Input/StreamView.h index 7790a8e0..5006026b 100644 --- a/Limelight/Input/StreamView.h +++ b/Limelight/Input/StreamView.h @@ -15,6 +15,10 @@ - (void) userInteractionBegan; - (void) userInteractionEnded; +- (void) streamExitRequested; +- (void) toggleStatsOverlay; +- (void) toggleMouseCapture; +- (void) toggleMouseVisible; @end diff --git a/Limelight/Input/StreamView.m b/Limelight/Input/StreamView.m index ed69488b..4eb92597 100644 --- a/Limelight/Input/StreamView.m +++ b/Limelight/Input/StreamView.m @@ -64,12 +64,15 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; UIHoverGestureRecognizer *stylusHoverRecognizer; #endif CGFloat HeightViewLiftedTo; + + UIKeyModifierFlags comboKeyModifierFlags; } - (void) setupStreamView:(ControllerSupport*)controllerSupport interactionDelegate:(id)interactionDelegate config:(StreamConfiguration*)streamConfig streamFrameTopLayerView:(UIView* )topLayerView{ + self->comboKeyModifierFlags = (UIKeyModifierControl|UIKeyModifierAlternate|UIKeyModifierShift); self->streamFrameTopLayerView = topLayerView; self.streamFrameTopLayerView = topLayerView; // this will be used as a read-only pointer for other class @@ -755,11 +758,34 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; else if(![onScreenControls handleTouchMovedEvent:touches]) [touchHandler touchesMoved:touches withEvent:event]; } +- (void) handleKeyCombos:(UIPress*) press{ + if(press.key.modifierFlags != comboKeyModifierFlags){ + return; + } + switch (press.key.keyCode) { + case UIKeyboardHIDUsageKeyboardQ: + [interactionDelegate streamExitRequested]; + break; + case UIKeyboardHIDUsageKeyboardS: + [interactionDelegate toggleStatsOverlay]; + break; + case UIKeyboardHIDUsageKeyboardM: + [interactionDelegate toggleMouseCapture]; + break; + case UIKeyboardHIDUsageKeyboardC: + [interactionDelegate toggleMouseVisible]; + break; + default: + break; + } +} + - (void)pressesBegan:(NSSet *)presses withEvent:(UIPressesEvent *)event { BOOL handled = NO; if (@available(iOS 13.4, tvOS 13.4, *)) { for (UIPress* press in presses) { + [self handleKeyCombos:press]; // For now, we'll treated it as handled if we handle at least one of the // UIPress events inside the set. if ([KeyboardSupport sendKeyEventForPress:press down:YES]) { diff --git a/Limelight/ViewControllers/StreamFrameViewController.m b/Limelight/ViewControllers/StreamFrameViewController.m index 2ec28416..79487db4 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -936,6 +936,44 @@ #endif } +- (void)toggleStatsOverlay{ + DataManager* dataMan = [[DataManager alloc] init]; + Settings *currentSettings = [dataMan retrieveSettings]; + + currentSettings.statsOverlay = !currentSettings.statsOverlay; + + [dataMan saveData]; + [self reConfigStreamViewRealtime]; +} + +- (void)toggleMouseCapture{ + DataManager* dataMan = [[DataManager alloc] init]; + Settings *currentSettings = [dataMan retrieveSettings]; + + if(currentSettings.mouseMode.intValue == 0){ + currentSettings.mouseMode = @1; + }else{ + currentSettings.mouseMode = @0; + } + + [dataMan saveData]; + [self reConfigStreamViewRealtime]; +} + +- (void)toggleMouseVisible{ + DataManager* dataMan = [[DataManager alloc] init]; + Settings *currentSettings = [dataMan retrieveSettings]; + + if(currentSettings.mouseMode.intValue == 2){ + currentSettings.mouseMode = @1; + }else{ + currentSettings.mouseMode = @2; + } + + [dataMan saveData]; + [self reConfigStreamViewRealtime]; +} + #if !TARGET_OS_TV // Require a confirmation when streaming to activate a system gesture - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {