From 85af0f75622bb2636481afda8b0fc5cc33d5956e Mon Sep 17 00:00:00 2001 From: Michael Pishchagin Date: Mon, 25 Aug 2025 21:46:20 +1000 Subject: [PATCH 1/3] Add Game Mode support with LSSupportsGameMode - Added LSSupportsGameMode=true to both iOS and tvOS Info.plist files - LSSupportsGameMode is supported on iOS 18.6+/iPadOS 18.6+/macOS 26.0+ - GCSupportsGameMode key is marked as deprecated, so I haven't added it --- Limelight/Limelight-Info.plist | 2 ++ Moonlight TV/Info.plist | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Limelight/Limelight-Info.plist b/Limelight/Limelight-Info.plist index 7d95d89bd..963c433d5 100644 --- a/Limelight/Limelight-Info.plist +++ b/Limelight/Limelight-Info.plist @@ -35,6 +35,8 @@ GCSupportsControllerUserInteraction + LSSupportsGameMode + ITSAppUsesNonExemptEncryption LSApplicationCategoryType diff --git a/Moonlight TV/Info.plist b/Moonlight TV/Info.plist index b91ee818e..c1f0beebf 100644 --- a/Moonlight TV/Info.plist +++ b/Moonlight TV/Info.plist @@ -54,6 +54,8 @@ arm64 + LSSupportsGameMode + UIUserInterfaceStyle Dark From 26ccdc0b49bc016f6aeca131759bab7a8593fb82 Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Tue, 21 May 2024 02:57:02 -0700 Subject: [PATCH 2/3] add passthrough touch support --- Limelight/Database/DataManager.h | 1 + Limelight/Database/DataManager.m | 2 + Limelight/Database/TemporarySettings.h | 1 + Limelight/Database/TemporarySettings.m | 1 + Limelight/Input/PassthroughTouchHandler.h | 19 ++++++ Limelight/Input/PassthroughTouchHandler.m | 64 +++++++++++++++++++ Limelight/Input/StreamView.h | 4 ++ Limelight/Input/StreamView.m | 30 ++++++++- .../Moonlight v1.10.xcdatamodel/contents | 3 +- .../ViewControllers/SettingsViewController.h | 1 + .../ViewControllers/SettingsViewController.m | 5 ++ .../StreamFrameViewController.m | 10 ++- Moonlight.xcodeproj/project.pbxproj | 6 ++ iPad.storyboard | 62 +++++++++++------- iPhone.storyboard | 62 +++++++++++------- 15 files changed, 222 insertions(+), 49 deletions(-) create mode 100644 Limelight/Input/PassthroughTouchHandler.h create mode 100644 Limelight/Input/PassthroughTouchHandler.m diff --git a/Limelight/Database/DataManager.h b/Limelight/Database/DataManager.h index 5717a7954..0f36b87ce 100644 --- a/Limelight/Database/DataManager.h +++ b/Limelight/Database/DataManager.h @@ -28,6 +28,7 @@ enableHdr:(BOOL)enableHdr btMouseSupport:(BOOL)btMouseSupport absoluteTouchMode:(BOOL)absoluteTouchMode + passthroughTouchMode:(BOOL)passthroughTouchMode statsOverlay:(BOOL)statsOverlay; - (NSArray*) getHosts; diff --git a/Limelight/Database/DataManager.m b/Limelight/Database/DataManager.m index 0f5edeb19..f96136092 100644 --- a/Limelight/Database/DataManager.m +++ b/Limelight/Database/DataManager.m @@ -67,6 +67,7 @@ - (void) saveSettingsWithBitrate:(NSInteger)bitrate enableHdr:(BOOL)enableHdr btMouseSupport:(BOOL)btMouseSupport absoluteTouchMode:(BOOL)absoluteTouchMode + passthroughTouchMode:(BOOL)passthroughTouchMode statsOverlay:(BOOL)statsOverlay { [_managedObjectContext performBlockAndWait:^{ @@ -86,6 +87,7 @@ - (void) saveSettingsWithBitrate:(NSInteger)bitrate settingsToSave.enableHdr = enableHdr; settingsToSave.btMouseSupport = btMouseSupport; settingsToSave.absoluteTouchMode = absoluteTouchMode; + settingsToSave.passthroughTouchMode = passthroughTouchMode; settingsToSave.statsOverlay = statsOverlay; [self saveData]; diff --git a/Limelight/Database/TemporarySettings.h b/Limelight/Database/TemporarySettings.h index da35b30c1..80852373e 100644 --- a/Limelight/Database/TemporarySettings.h +++ b/Limelight/Database/TemporarySettings.h @@ -33,6 +33,7 @@ @property (nonatomic) BOOL enableHdr; @property (nonatomic) BOOL btMouseSupport; @property (nonatomic) BOOL absoluteTouchMode; +@property (nonatomic) BOOL passthroughTouchMode; @property (nonatomic) BOOL statsOverlay; - (id) initFromSettings:(Settings*)settings; diff --git a/Limelight/Database/TemporarySettings.m b/Limelight/Database/TemporarySettings.m index e627e0c1f..2886e2b15 100644 --- a/Limelight/Database/TemporarySettings.m +++ b/Limelight/Database/TemporarySettings.m @@ -84,6 +84,7 @@ - (id) initFromSettings:(Settings*)settings { self.onscreenControls = settings.onscreenControls; self.btMouseSupport = settings.btMouseSupport; self.absoluteTouchMode = settings.absoluteTouchMode; + self.passthroughTouchMode = settings.passthroughTouchMode; self.statsOverlay = settings.statsOverlay; #endif self.uniqueId = settings.uniqueId; diff --git a/Limelight/Input/PassthroughTouchHandler.h b/Limelight/Input/PassthroughTouchHandler.h new file mode 100644 index 000000000..48719391f --- /dev/null +++ b/Limelight/Input/PassthroughTouchHandler.h @@ -0,0 +1,19 @@ +// +// PassthroughTouchHandler.h +// Moonlight +// +// Created by ZigZagT on 5/20/2024. +// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved. +// + +#import "StreamView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface PassthroughTouchHandler : UIResponder + +-(id)initWithView:(StreamView*)view; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Limelight/Input/PassthroughTouchHandler.m b/Limelight/Input/PassthroughTouchHandler.m new file mode 100644 index 000000000..9f7a2c0b0 --- /dev/null +++ b/Limelight/Input/PassthroughTouchHandler.m @@ -0,0 +1,64 @@ +// +// PassthroughTouchHandler.m +// Moonlight +// +// Created by ZigZagT on 5/20/2024. +// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved. +// + +#import "PassthroughTouchHandler.h" + +#include + +@implementation PassthroughTouchHandler { + StreamView* view; +} + +- (id)initWithView:(StreamView*)view { + self = [self init]; + self->view = view; + return self; +} + +- (int)sendTouchEvent:(UITouch*)touch withType:(uint8_t)eventType { + CGPoint location = [self->view adjustCoordinatesForVideoArea:[touch locationInView:self->view]]; + CGSize videoSize = [self->view getVideoAreaSize]; + return LiSendTouchEvent( + eventType, + (uint32_t) (uintptr_t) touch, + location.x / videoSize.width, + location.y / videoSize.height, + 0, + 0, + 0, + LI_ROT_UNKNOWN + ); +} + + + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + for (UITouch* touch in touches) { + [self sendTouchEvent:touch withType:LI_TOUCH_EVENT_DOWN]; + } +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + for (UITouch* touch in touches) { + [self sendTouchEvent:touch withType:LI_TOUCH_EVENT_MOVE]; + } +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + for (UITouch* touch in touches) { + [self sendTouchEvent:touch withType:LI_TOUCH_EVENT_UP]; + } +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + for (UITouch* touch in touches) { + [self sendTouchEvent:touch withType:LI_TOUCH_EVENT_CANCEL]; + } +} + +@end diff --git a/Limelight/Input/StreamView.h b/Limelight/Input/StreamView.h index 9f500101a..7577de37d 100644 --- a/Limelight/Input/StreamView.h +++ b/Limelight/Input/StreamView.h @@ -34,4 +34,8 @@ - (void) updateCursorLocation:(CGPoint)location isMouse:(BOOL)isMouse; #endif +- (CGSize) getVideoAreaSize; +- (CGPoint) adjustCoordinatesForVideoArea:(CGPoint)point; +- (void) cancelAllTouchEvents; + @end diff --git a/Limelight/Input/StreamView.m b/Limelight/Input/StreamView.m index 7dedeb35e..71d0777e8 100644 --- a/Limelight/Input/StreamView.m +++ b/Limelight/Input/StreamView.m @@ -13,6 +13,7 @@ #import "KeyboardSupport.h" #import "RelativeTouchHandler.h" #import "AbsoluteTouchHandler.h" +#import "PassthroughTouchHandler.h" #import "KeyboardInputField.h" static const double X1_MOUSE_SPEED_DIVISOR = 2.5; @@ -20,6 +21,7 @@ @implementation StreamView { OnScreenControls* onScreenControls; + int keyboardGestureNFingers; KeyboardInputField* keyInputField; BOOL isInputingText; NSMutableSet* keysDown; @@ -51,6 +53,7 @@ - (void) setupStreamView:(ControllerSupport*)controllerSupport config:(StreamConfiguration*)streamConfig { self->interactionDelegate = interactionDelegate; self->streamAspectRatio = (float)streamConfig.width / (float)streamConfig.height; + self->keyboardGestureNFingers = 3; TemporarySettings* settings = [[[DataManager alloc] init] getSettings]; @@ -68,7 +71,15 @@ - (void) setupStreamView:(ControllerSupport*)controllerSupport #else // iOS uses RelativeTouchHandler or AbsoluteTouchHandler depending on user preference if (settings.absoluteTouchMode) { - self->touchHandler = [[AbsoluteTouchHandler alloc] initWithView:self]; + if (settings.passthroughTouchMode) { + self->touchHandler = [[PassthroughTouchHandler alloc] initWithView:self]; + // "Touch gestures for Windows" uses up to four fingers according to Microsoft support articles. + // Register keyboard gesture with 5 fingers can avoid conflict. + // Also the popup keyboarde became less useful since Windows has a built in on-screen keyboard. + self->keyboardGestureNFingers = 5; + } else { + self->touchHandler = [[AbsoluteTouchHandler alloc] initWithView:self]; + } } else { self->touchHandler = [[RelativeTouchHandler alloc] initWithView:self]; @@ -318,6 +329,21 @@ - (void)sendStylusHoverEvent:(UIHoverGestureRecognizer*)gesture API_AVAILABLE(io #endif +- (void)cancelAllTouchEvents { + // Use this to cancel touch events when the intention is meant for local device. + // i.e. open notification center, end streaming by swiping on edge + LiSendTouchEvent( + LI_TOUCH_EVENT_CANCEL_ALL, + 0, + 0, + 0, + 0, + 0, + 0, + LI_ROT_UNKNOWN + ); +} + - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if ([self handleMouseButtonEvent:BUTTON_ACTION_PRESS forTouches:touches @@ -351,7 +377,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // is triggered. [touchHandler touchesBegan:touches withEvent:event]; - if ([[event allTouches] count] == 3) { + if ([[event allTouches] count] == self->keyboardGestureNFingers) { if (isInputingText) { Log(LOG_D, @"Closing the keyboard"); [keyInputField resignFirstResponder]; diff --git a/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents b/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents index a2cc61c34..815d340a8 100644 --- a/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents +++ b/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -32,6 +32,7 @@ + diff --git a/Limelight/ViewControllers/SettingsViewController.h b/Limelight/ViewControllers/SettingsViewController.h index 1a79162da..c671715d1 100644 --- a/Limelight/ViewControllers/SettingsViewController.h +++ b/Limelight/ViewControllers/SettingsViewController.h @@ -16,6 +16,7 @@ @property (strong, nonatomic) IBOutlet UISegmentedControl *resolutionSelector; @property (strong, nonatomic) IBOutlet UIView *resolutionDisplayView; @property (strong, nonatomic) IBOutlet UISegmentedControl *touchModeSelector; +@property (strong, nonatomic) IBOutlet UISegmentedControl *passthroughTouchModeSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *onscreenControlSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *optimizeSettingsSelector; @property (strong, nonatomic) IBOutlet UISegmentedControl *multiControllerSelector; diff --git a/Limelight/ViewControllers/SettingsViewController.m b/Limelight/ViewControllers/SettingsViewController.m index bbe8eb2de..7acb40c78 100644 --- a/Limelight/ViewControllers/SettingsViewController.m +++ b/Limelight/ViewControllers/SettingsViewController.m @@ -242,6 +242,8 @@ - (void)viewDidLoad { [self.touchModeSelector setSelectedSegmentIndex:currentSettings.absoluteTouchMode ? 1 : 0]; [self.touchModeSelector addTarget:self action:@selector(touchModeChanged) forControlEvents:UIControlEventValueChanged]; + [self.passthroughTouchModeSelector setSelectedSegmentIndex:currentSettings.passthroughTouchMode ? 0 : 1]; + [self.passthroughTouchModeSelector setEnabled:currentSettings.absoluteTouchMode]; [self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlay ? 1 : 0]; [self.btMouseSelector setSelectedSegmentIndex:currentSettings.btMouseSupport ? 1 : 0]; [self.optimizeSettingsSelector setSelectedSegmentIndex:currentSettings.optimizeGames ? 1 : 0]; @@ -268,6 +270,7 @@ - (void)viewDidLoad { - (void) touchModeChanged { // Disable on-screen controls in absolute touch mode [self.onscreenControlSelector setEnabled:[self.touchModeSelector selectedSegmentIndex] == 0]; + [self.passthroughTouchModeSelector setEnabled:[self.touchModeSelector selectedSegmentIndex] == 1]; } - (void) updateBitrate { @@ -536,6 +539,7 @@ - (void) saveSettings { BOOL btMouseSupport = [self.btMouseSelector selectedSegmentIndex] == 1; BOOL useFramePacing = [self.framePacingSelector selectedSegmentIndex] == 1; BOOL absoluteTouchMode = [self.touchModeSelector selectedSegmentIndex] == 1; + BOOL passthroughTouchMode = [self.passthroughTouchModeSelector selectedSegmentIndex] == 0; BOOL statsOverlay = [self.statsOverlaySelector selectedSegmentIndex] == 1; BOOL enableHdr = [self.hdrSelector selectedSegmentIndex] == 1; [dataMan saveSettingsWithBitrate:_bitrate @@ -553,6 +557,7 @@ - (void) saveSettings { enableHdr:enableHdr btMouseSupport:btMouseSupport absoluteTouchMode:absoluteTouchMode + passthroughTouchMode:passthroughTouchMode statsOverlay:statsOverlay]; } diff --git a/Limelight/ViewControllers/StreamFrameViewController.m b/Limelight/ViewControllers/StreamFrameViewController.m index a4c8f3448..fd129c31c 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -186,11 +186,15 @@ - (void)viewDidLoad object: nil]; #endif - // Only enable scroll and zoom in absolute touch mode - if (_settings.absoluteTouchMode) { + // Only enable scroll and zoom in absolute touch mode with exception of passthrough touch mode + // Windows has its own pinch / pan / scroll handling which conflicts with UIScrollView gestures. + if (_settings.absoluteTouchMode && !_settings.passthroughTouchMode) { _scrollView = [[UIScrollView alloc] initWithFrame:self.view.frame]; + _scrollView.delaysContentTouches = NO; #if !TARGET_OS_TV [_scrollView.panGestureRecognizer setMinimumNumberOfTouches:2]; + [_scrollView.panGestureRecognizer setDelaysTouchesBegan: NO]; + [_scrollView.panGestureRecognizer setDelaysTouchesEnded: NO]; #endif [_scrollView setShowsHorizontalScrollIndicator:NO]; [_scrollView setShowsVerticalScrollIndicator:NO]; @@ -306,6 +310,7 @@ - (void)updateOverlayText:(NSString*)text { } - (void) returnToMainFrame { + [self->_streamView cancelAllTouchEvents]; // Reset display mode back to default [self updatePreferredDisplayMode:NO]; @@ -317,6 +322,7 @@ - (void) returnToMainFrame { // This will fire if the user opens control center or gets a low battery message - (void)applicationWillResignActive:(NSNotification *)notification { + [self->_streamView cancelAllTouchEvents]; if (_inactivityTimer != nil) { [_inactivityTimer invalidate]; } diff --git a/Moonlight.xcodeproj/project.pbxproj b/Moonlight.xcodeproj/project.pbxproj index 6f055579b..7f4033289 100644 --- a/Moonlight.xcodeproj/project.pbxproj +++ b/Moonlight.xcodeproj/project.pbxproj @@ -136,6 +136,7 @@ FBD349621A0089F6002D2A60 /* DataManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD349611A0089F6002D2A60 /* DataManager.m */; }; FBDE86E019F7A837001C18A8 /* UIComputerView.m in Sources */ = {isa = PBXBuildFile; fileRef = FBDE86DF19F7A837001C18A8 /* UIComputerView.m */; }; FBDE86E619F82297001C18A8 /* UIAppView.m in Sources */ = {isa = PBXBuildFile; fileRef = FBDE86E519F82297001C18A8 /* UIAppView.m */; }; + FD10B0BF2BF1729A009AC503 /* PassthroughTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = FD10B0BE2BF1729A009AC503 /* PassthroughTouchHandler.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -320,6 +321,8 @@ FBDE86DF19F7A837001C18A8 /* UIComputerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIComputerView.m; sourceTree = ""; }; FBDE86E419F82297001C18A8 /* UIAppView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIAppView.h; sourceTree = ""; }; FBDE86E519F82297001C18A8 /* UIAppView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIAppView.m; sourceTree = ""; }; + FD10B0BD2BF1729A009AC503 /* PassthroughTouchHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PassthroughTouchHandler.h; sourceTree = ""; }; + FD10B0BE2BF1729A009AC503 /* PassthroughTouchHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PassthroughTouchHandler.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -501,6 +504,8 @@ FB89460919F646E200339C8A /* Input */ = { isa = PBXGroup; children = ( + FD10B0BD2BF1729A009AC503 /* PassthroughTouchHandler.h */, + FD10B0BE2BF1729A009AC503 /* PassthroughTouchHandler.m */, FB89460A19F646E200339C8A /* ControllerSupport.h */, FB89460B19F646E200339C8A /* ControllerSupport.m */, FB89460C19F646E200339C8A /* StreamView.h */, @@ -905,6 +910,7 @@ FBD3495319FF36FB002D2A60 /* SWRevealViewController.m in Sources */, FB1D59971BBCCB6400F482CA /* ComputerScrollView.m in Sources */, FBD3495019FF2174002D2A60 /* SettingsViewController.m in Sources */, + FD10B0BF2BF1729A009AC503 /* PassthroughTouchHandler.m in Sources */, FB89462C19F646E200339C8A /* HttpManager.m in Sources */, FB89462D19F646E200339C8A /* MDNSManager.m in Sources */, FB89462B19F646E200339C8A /* StreamView.m in Sources */, diff --git a/iPad.storyboard b/iPad.storyboard index 9017b15b9..527da84be 100644 --- a/iPad.storyboard +++ b/iPad.storyboard @@ -1,9 +1,9 @@ - + - + @@ -139,8 +139,25 @@ + + + + + + + + + + + - + @@ -160,14 +177,14 @@ - + @@ -177,14 +194,14 @@ - + @@ -194,21 +211,21 @@ - + @@ -218,14 +235,14 @@ - + @@ -237,14 +254,14 @@ - + @@ -254,14 +271,14 @@ - + @@ -271,14 +288,14 @@ - + @@ -288,14 +305,14 @@ - + @@ -305,7 +322,7 @@ - + @@ -329,6 +346,7 @@ + diff --git a/iPhone.storyboard b/iPhone.storyboard index 03aa00750..21aafb675 100644 --- a/iPhone.storyboard +++ b/iPhone.storyboard @@ -1,9 +1,9 @@ - + - + @@ -160,15 +160,32 @@ + + + + + + + + + + + - + @@ -180,14 +197,14 @@ - + @@ -197,14 +214,14 @@ - + @@ -214,14 +231,14 @@ - + @@ -231,14 +248,14 @@ - + @@ -248,14 +265,14 @@ - + @@ -267,14 +284,14 @@ - + @@ -284,14 +301,14 @@ - + @@ -301,14 +318,14 @@ - + @@ -318,14 +335,14 @@ - + @@ -349,6 +366,7 @@ + From b61b945887e2e929dd683fe9baf8fd7249a67d31 Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Wed, 22 May 2024 01:19:48 -0700 Subject: [PATCH 3/3] requires swiping over 50% distance to end streaming --- .../ViewControllers/StreamFrameViewController.m | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Limelight/ViewControllers/StreamFrameViewController.m b/Limelight/ViewControllers/StreamFrameViewController.m index fd129c31c..c1664ccc0 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -129,7 +129,7 @@ - (void)viewDidLoad [self.view addGestureRecognizer:_playPauseTapGestureRecognizer]; #else - _exitSwipeRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(edgeSwiped)]; + _exitSwipeRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(edgeSwiped:)]; _exitSwipeRecognizer.edges = UIRectEdgeLeft; _exitSwipeRecognizer.delaysTouchesBegan = NO; _exitSwipeRecognizer.delaysTouchesEnded = NO; @@ -367,10 +367,14 @@ - (void)applicationDidEnterBackground:(UIApplication *)application { [self returnToMainFrame]; } -- (void)edgeSwiped { - Log(LOG_I, @"User swiped to end stream"); - - [self returnToMainFrame]; +- (void)edgeSwiped:(UIScreenEdgePanGestureRecognizer*)gestureRecognizer { + CGPoint touch = [gestureRecognizer locationInView:self.view]; + double distancePct = touch.x / self.view.bounds.size.width; + Log(LOG_I, @"User swiped distance = %f%%", distancePct * 100); + if (distancePct > 0.5) { + Log(LOG_I, @"User swiped to end stream"); + [self returnToMainFrame]; + } } - (void) connectionStarted {