diff --git a/Limelight/Input/StreamView.h b/Limelight/Input/StreamView.h index 2c1e97f..c82efea 100644 --- a/Limelight/Input/StreamView.h +++ b/Limelight/Input/StreamView.h @@ -15,11 +15,18 @@ @end +@protocol UserInteractionDelegate + +- (void) userInteractionBegan; +- (void) userInteractionEnded; + +@end + @interface StreamView : OSView @property (nonatomic, retain) IBOutlet UITextField* keyInputField; -- (void) setupStreamView:(ControllerSupport*)controllerSupport swipeDelegate:(id)swipeDelegate; +- (void) setupStreamView:(ControllerSupport*)controllerSupport swipeDelegate:(id)swipeDelegate interactionDelegate:(id)interactionDelegate; - (void) showOnScreenControls; - (void) setMouseDeltaFactors:(float)x y:(float)y; - (OnScreenControlsLevel) getCurrentOscState; diff --git a/Limelight/Input/StreamView.m b/Limelight/Input/StreamView.m index 83b47be..1f36592 100644 --- a/Limelight/Input/StreamView.m +++ b/Limelight/Input/StreamView.m @@ -30,6 +30,10 @@ UIGestureRecognizer* remoteLongPressRecognizer; #endif + id interactionDelegate; + NSTimer* interactionTimer; + BOOL hasUserInteracted; + NSDictionary *dictCodes; } @@ -46,7 +50,8 @@ #endif } -- (void) setupStreamView:(ControllerSupport*)controllerSupport swipeDelegate:(id)swipeDelegate { +- (void) setupStreamView:(ControllerSupport*)controllerSupport swipeDelegate:(id)swipeDelegate interactionDelegate:(id)interactionDelegate { + self->interactionDelegate = interactionDelegate; #if TARGET_OS_TV remotePressRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(remoteButtonPressed:)]; remotePressRecognizer.allowedPressTypes = @[@(UIPressTypeSelect)]; @@ -70,6 +75,38 @@ #endif } +- (void)startInteractionTimer { + // Restart user interaction tracking + hasUserInteracted = NO; + + BOOL timerAlreadyRunning = interactionTimer != nil; + + // Start/restart the timer + [interactionTimer invalidate]; + interactionTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 + target:self + selector:@selector(interactionTimerExpired:) + userInfo:nil + repeats:NO]; + + // Notify the delegate if this was a new user interaction + if (!timerAlreadyRunning) { + [interactionDelegate userInteractionBegan]; + } +} + +- (void)interactionTimerExpired:(NSTimer *)timer { + if (!hasUserInteracted) { + // User has finished touching the screen + interactionTimer = nil; + [interactionDelegate userInteractionEnded]; + } + else { + // User is still touching the screen. Restart the timer. + [self startInteractionTimer]; + } +} + - (void) showOnScreenControls { #if !TARGET_OS_TV [onScreenControls show]; @@ -93,6 +130,10 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { Log(LOG_D, @"Touch down"); + + // Notify of user interaction and start expiration timer + [self startInteractionTimer]; + if (![onScreenControls handleTouchDownEvent:touches]) { UITouch *touch = [[event allTouches] anyObject]; originalLocation = touchLocation = [touch locationInView:self]; @@ -115,6 +156,8 @@ } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + hasUserInteracted = YES; + if (![onScreenControls handleTouchMovedEvent:touches]) { if ([[event allTouches] count] == 1) { UITouch *touch = [[event allTouches] anyObject]; @@ -163,6 +206,9 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { Log(LOG_D, @"Touch up"); + + hasUserInteracted = YES; + if (![onScreenControls handleTouchUpEvent:touches]) { [dragTimer invalidate]; dragTimer = nil; diff --git a/Limelight/ViewControllers/StreamFrameViewController.h b/Limelight/ViewControllers/StreamFrameViewController.h index d8a5224..b8418a8 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.h +++ b/Limelight/ViewControllers/StreamFrameViewController.h @@ -15,9 +15,9 @@ #if TARGET_OS_TV @import GameController; -@interface StreamFrameViewController : GCEventViewController +@interface StreamFrameViewController : GCEventViewController #else -@interface StreamFrameViewController : UIViewController +@interface StreamFrameViewController : UIViewController #endif @property (strong, nonatomic) IBOutlet UILabel *stageLabel; @property (strong, nonatomic) IBOutlet UILabel *tipLabel; diff --git a/Limelight/ViewControllers/StreamFrameViewController.m b/Limelight/ViewControllers/StreamFrameViewController.m index 3caf598..6e67571 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -25,6 +25,7 @@ UITapGestureRecognizer *_menuDoubleTapGestureRecognizer; UITextView *_overlayView; StreamView *_streamView; + BOOL _userIsInteracting; } - (void)viewDidAppear:(BOOL)animated @@ -62,7 +63,7 @@ _inactivityTimer = nil; _streamView = (StreamView*)self.view; - [_streamView setupStreamView:_controllerSupport swipeDelegate:self]; + [_streamView setupStreamView:_controllerSupport swipeDelegate:self interactionDelegate:self]; #if TARGET_OS_TV if (!_menuGestureRecognizer || !_menuDoubleTapGestureRecognizer) { @@ -343,6 +344,29 @@ #endif } +- (void)userInteractionBegan { + // Disable hiding home bar when user is interacting. + // iOS will force it to be shown anyway, but it will + // also discard our edges deferring system gestures unless + // we willingly give up home bar hiding preference. + _userIsInteracting = YES; +#if !TARGET_OS_TV + if (@available(iOS 11.0, *)) { + [self setNeedsUpdateOfHomeIndicatorAutoHidden]; + } +#endif +} + +- (void)userInteractionEnded { + // Enable home bar hiding again if conditions allow + _userIsInteracting = NO; +#if !TARGET_OS_TV + if (@available(iOS 11.0, *)) { + [self setNeedsUpdateOfHomeIndicatorAutoHidden]; + } +#endif +} + #if !TARGET_OS_TV // Require a confirmation when streaming to activate a system gesture - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { @@ -351,7 +375,8 @@ - (BOOL)prefersHomeIndicatorAutoHidden { if ([_controllerSupport getConnectedGamepadCount] > 0 && - [_streamView getCurrentOscState] == OnScreenControlsLevelOff) { + [_streamView getCurrentOscState] == OnScreenControlsLevelOff && + _userIsInteracting == NO) { // Autohide the home bar when a gamepad is connected // and the on-screen controls are disabled. We can't // do this all the time because any touch on the display