From 334788c26f21e741b09f301d7422fdf4cb628741 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Sat, 19 Jul 2025 14:47:04 +0800 Subject: [PATCH] - fix appview background update issue - relocate chevron view for ipad osw tool - improve edge slide detection for native touchHandler --- VoidLink/Base.lproj/iPad.storyboard | 12 ++++---- VoidLink/Input/NativeTouchHandler.m | 29 ++++++++---------- VoidLink/Input/PureNativeTouchHandler.m | 30 +++++++++---------- VoidLink/Input/RelativeTouchHandler.h | 1 - VoidLink/Input/RelativeTouchHandler.m | 3 -- VoidLink/UIAppView.h | 8 +++++ VoidLink/UIAppView.m | 27 ++++++++--------- .../ViewControllers/MainFrameViewController.h | 2 +- .../ViewControllers/MainFrameViewController.m | 9 ++++++ 9 files changed, 64 insertions(+), 57 deletions(-) diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard index 7ec05301..aadae408 100644 --- a/VoidLink/Base.lproj/iPad.storyboard +++ b/VoidLink/Base.lproj/iPad.storyboard @@ -1262,7 +1262,7 @@ - + @@ -1304,7 +1304,7 @@ - + @@ -1659,19 +1659,19 @@ - + - + - + - + diff --git a/VoidLink/Input/NativeTouchHandler.m b/VoidLink/Input/NativeTouchHandler.m index 87833bfb..0c82e10b 100644 --- a/VoidLink/Input/NativeTouchHandler.m +++ b/VoidLink/Input/NativeTouchHandler.m @@ -19,7 +19,6 @@ StreamView* streamView; TemporarySettings* currentSettings; bool activateCoordSelector; - bool touchPointSpawnedAtUpperScreenEdge; CGFloat pointerVelocityDividerLocationByPoints; uint16_t touchMoveEventIntervalUs; @@ -35,7 +34,8 @@ NSMutableSet *activePointerIds; //pointerId Set for active touches. NSMutableSet *pointerIdPool; //pre-defined pool of pointerIds. NSMutableSet *unassignedPointerIds; - + NSMutableSet *blacklistedTouches; + NSMutableDictionary *pointerObjDict; CGFloat slideGestureVerticalThreshold; @@ -59,9 +59,8 @@ [self->pointerIdPool addObject:@(i)]; } self->activePointerIds = [NSMutableSet set]; - // self->excludedPointerIds = [[NSMutableSet alloc] init]; - self->touchPointSpawnedAtUpperScreenEdge = false; - + self->blacklistedTouches = [NSMutableSet set]; + self->asyncNativeTouch = settings.asyncNativeTouchPriority.intValue != AsyncNativeTouchOff; switch(settings.asyncNativeTouchPriority.intValue){ @@ -156,28 +155,27 @@ // generate & populate pointerId into NSDict & NSSet, called in touchesBegan - (void)handleTouchDown:(UITouch*)touch{ //populate pointerId - uintptr_t memAddrValue = (uintptr_t)touch; + NSNumber* touchAddrObj = @((uintptr_t)touch); unassignedPointerIds = [pointerIdPool mutableCopy]; //reset unassignedPointerIds [unassignedPointerIds minusSet:activePointerIds]; uint8_t pointerId = [[unassignedPointerIds anyObject] unsignedIntValue]; - [pointerIdDict setObject:@(pointerId) forKey:@(memAddrValue)]; + [pointerIdDict setObject:@(pointerId) forKey:touchAddrObj]; [activePointerIds addObject:@(pointerId)]; //check if touch point is spawned on the left or right upper half screen edges, event to remote PC. this is for better handling in-stream slide gesture CGPoint initialPoint = [touch locationInView:self->streamView]; if(initialPoint.y < slideGestureVerticalThreshold && (initialPoint.x < EDGE_TOLERANCE || initialPoint.x > screenWidthWithThreshold)) { - self->touchPointSpawnedAtUpperScreenEdge = true; - // [excludedPointerIds addObject:@(pointerId)]; + [blacklistedTouches addObject:touchAddrObj]; } } // remove pointerId in touchesEnded or touchesCancelled - (void)removePointerId:(UITouch*)touch{ - uintptr_t memAddrValue = (uintptr_t)touch; - NSNumber* pointerIdObj = [pointerIdDict objectForKey:@(memAddrValue)]; + NSNumber* touchAddrObj = @((uintptr_t)touch); + NSNumber* pointerIdObj = [pointerIdDict objectForKey:touchAddrObj]; if(pointerIdObj != nil){ [activePointerIds removeObject:pointerIdObj]; - [pointerIdDict removeObjectForKey:@(memAddrValue)]; + [pointerIdDict removeObjectForKey:touchAddrObj]; // if([excludedPointerIds containsObject:pointerIdObj]) [excludedPointerIds removeObject:pointerIdObj]; // remove pointer id from excludedPointerId NSSet } } @@ -190,7 +188,8 @@ - (void)sendTouchEvent:(UITouch*)touch withTouchtype:(uint8_t)touchType{ - if(touchPointSpawnedAtUpperScreenEdge && touchType != LI_TOUCH_EVENT_UP) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash + //if(touchPointSpawnedAtUpperScreenEdge && touchType != LI_TOUCH_EVENT_UP) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash + if([blacklistedTouches containsObject:@((uintptr_t)touch)]) return; CGPoint targetCoords; //NSLog(@"selecting coords: %d", touch.phase == UITouchPhaseMoved); @@ -263,9 +262,9 @@ [self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId [self removePointerId:touch]; //then remove pointerId if(self->activateCoordSelector) [self removePointerObjFromDict:touch]; + [self->blacklistedTouches removeObject:@((uintptr_t)touch)]; } //if(self->touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count]) - self->touchPointSpawnedAtUpperScreenEdge = false; }); else{ for (UITouch* touch in touches){ @@ -275,7 +274,6 @@ [self removePointerId:touch]; //then remove pointerId if(self->activateCoordSelector) [self removePointerObjFromDict:touch]; } - if(self->touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count]) self->touchPointSpawnedAtUpperScreenEdge = false; } } @@ -309,7 +307,6 @@ - (CGPoint)selectCoordsFor:(UITouch *)touch{ NativeTouchPointer *pointer = [pointerObjDict objectForKey:@((uintptr_t)touch)]; if(pointer == nil) return CGPointMake(0, 0); - // NSLog(@"using relative Coords: intialX %f, divider loc %f", pointer.initialPoint.x, pointerVelocityDividerLocationByPoints); return pointer.useRelativeCoords ? pointer.latestRelativePoint : pointer.latestPoint; } diff --git a/VoidLink/Input/PureNativeTouchHandler.m b/VoidLink/Input/PureNativeTouchHandler.m index cc34d5eb..3b029709 100644 --- a/VoidLink/Input/PureNativeTouchHandler.m +++ b/VoidLink/Input/PureNativeTouchHandler.m @@ -18,7 +18,6 @@ StreamView* streamView; TemporarySettings* currentSettings; bool activateCoordSelector; - bool touchPointSpawnedAtUpperScreenEdge; CGFloat pointerVelocityDividerLocationByPoints; uint16_t touchMoveEventIntervalUs; @@ -34,7 +33,8 @@ NSMutableSet *activePointerIds; //pointerId Set for active touches. NSMutableSet *pointerIdPool; //pre-defined pool of pointerIds. NSMutableSet *unassignedPointerIds; - + NSMutableSet *blacklistedTouches; + NSMutableDictionary *pointerObjDict; CGFloat slideGestureVerticalThreshold; @@ -58,9 +58,8 @@ [self->pointerIdPool addObject:@(i)]; } self->activePointerIds = [NSMutableSet set]; - // self->excludedPointerIds = [[NSMutableSet alloc] init]; - self->touchPointSpawnedAtUpperScreenEdge = false; - + self->blacklistedTouches = [NSMutableSet set]; + self->asyncNativeTouch = settings.asyncNativeTouchPriority.intValue != AsyncNativeTouchOff; switch(settings.asyncNativeTouchPriority.intValue){ @@ -155,28 +154,27 @@ // generate & populate pointerId into NSDict & NSSet, called in touchesBegan - (void)handleTouchDown:(UITouch*)touch{ //populate pointerId - uintptr_t memAddrValue = (uintptr_t)touch; + NSNumber* touchAddrObj = @((uintptr_t)touch); unassignedPointerIds = [pointerIdPool mutableCopy]; //reset unassignedPointerIds [unassignedPointerIds minusSet:activePointerIds]; uint8_t pointerId = [[unassignedPointerIds anyObject] unsignedIntValue]; - [pointerIdDict setObject:@(pointerId) forKey:@(memAddrValue)]; + [pointerIdDict setObject:@(pointerId) forKey:touchAddrObj]; [activePointerIds addObject:@(pointerId)]; //check if touch point is spawned on the left or right upper half screen edges, event to remote PC. this is for better handling in-stream slide gesture CGPoint initialPoint = [touch locationInView:self->streamView]; if(initialPoint.y < slideGestureVerticalThreshold && (initialPoint.x < EDGE_TOLERANCE || initialPoint.x > screenWidthWithThreshold)) { - self->touchPointSpawnedAtUpperScreenEdge = true; - // [excludedPointerIds addObject:@(pointerId)]; + [blacklistedTouches addObject:touchAddrObj]; } } // remove pointerId in touchesEnded or touchesCancelled - (void)removePointerId:(UITouch*)touch{ - uintptr_t memAddrValue = (uintptr_t)touch; - NSNumber* pointerIdObj = [pointerIdDict objectForKey:@(memAddrValue)]; + NSNumber* touchAddrObj = @((uintptr_t)touch); + NSNumber* pointerIdObj = [pointerIdDict objectForKey:touchAddrObj]; if(pointerIdObj != nil){ [activePointerIds removeObject:pointerIdObj]; - [pointerIdDict removeObjectForKey:@(memAddrValue)]; + [pointerIdDict removeObjectForKey:touchAddrObj]; // if([excludedPointerIds containsObject:pointerIdObj]) [excludedPointerIds removeObject:pointerIdObj]; // remove pointer id from excludedPointerId NSSet } } @@ -189,8 +187,9 @@ - (void)sendTouchEvent:(UITouch*)touch withTouchtype:(uint8_t)touchType{ - if(touchPointSpawnedAtUpperScreenEdge && touchType != LI_TOUCH_EVENT_UP) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash - + //if(touchPointSpawnedAtUpperScreenEdge && touchType != LI_TOUCH_EVENT_UP) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash + if([blacklistedTouches containsObject:@((uintptr_t)touch)]) return; + CGPoint targetCoords; //NSLog(@"selecting coords: %d", touch.phase == UITouchPhaseMoved); // NSLog(@"excluded count: %d", (uint32_t)[excludedPointerIds count]); @@ -252,8 +251,8 @@ [self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId [self removePointerId:touch]; //then remove pointerId if(self->activateCoordSelector) [self removePointerObjFromDict:touch]; + [self->blacklistedTouches removeObject:@((uintptr_t)touch)]; } - self->touchPointSpawnedAtUpperScreenEdge = false; }); else{ for (UITouch* touch in touches){ @@ -261,7 +260,6 @@ [self removePointerId:touch]; //then remove pointerId if(self->activateCoordSelector) [self removePointerObjFromDict:touch]; } - if(self->touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count]) self->touchPointSpawnedAtUpperScreenEdge = false; } } diff --git a/VoidLink/Input/RelativeTouchHandler.h b/VoidLink/Input/RelativeTouchHandler.h index d856d468..0bd4a512 100644 --- a/VoidLink/Input/RelativeTouchHandler.h +++ b/VoidLink/Input/RelativeTouchHandler.h @@ -19,7 +19,6 @@ NS_ASSUME_NONNULL_BEGIN @interface RelativeTouchHandler : UIResponder @property (nonatomic, readonly) CustomTapGestureRecognizer* mouseRightClickTapRecognizer; // this object will be passed to onscreencontrol class for areVirtualControllerTaps flag setting -@property (nonatomic, strong) CADisplayLink *displayLink; - (id)initWithView:(StreamView*)view andSettings:(TemporarySettings*)settings; diff --git a/VoidLink/Input/RelativeTouchHandler.m b/VoidLink/Input/RelativeTouchHandler.m index 0f67455d..46a2d932 100644 --- a/VoidLink/Input/RelativeTouchHandler.m +++ b/VoidLink/Input/RelativeTouchHandler.m @@ -12,9 +12,6 @@ #include - -static const int REFERENCE_WIDTH = 1280; -static const int REFERENCE_HEIGHT = 720; static const float QUICK_TAP_TIME_INTERVAL = 0.2; @implementation RelativeTouchHandler { diff --git a/VoidLink/UIAppView.h b/VoidLink/UIAppView.h index 9f2504cb..9554d036 100644 --- a/VoidLink/UIAppView.h +++ b/VoidLink/UIAppView.h @@ -9,6 +9,11 @@ #import #import "TemporaryApp.h" +@protocol AppViewUpdateLoopDelegate +- (bool)isStreaming; +@end + + @protocol AppCallback - (void) appClicked:(TemporaryApp*)app view:(UIView*)view; @@ -22,6 +27,9 @@ @interface UIAppView : UIButton #endif + +@property (nonatomic, weak) id updateLoopDelegate; // Delegate property + - (id) initWithApp:(TemporaryApp*)app cache:(NSCache*)cache andCallback:(id)callback; - (void) updateAppImage; diff --git a/VoidLink/UIAppView.m b/VoidLink/UIAppView.m index 9d98d353..71cfbfda 100644 --- a/VoidLink/UIAppView.m +++ b/VoidLink/UIAppView.m @@ -48,6 +48,7 @@ static UIImage* noImage; _appImage = [[UIImageView alloc] initWithFrame:self.frame]; [_appImage setImage:noImage]; + [self addSubview:_appImage]; // Use UIContextMenuInteraction on iOS 13.0+ and a standard UILongPressGestureRecognizer @@ -74,7 +75,7 @@ static UIImage* noImage; #else // Rasterizing the cell layer increases rendering performance by quite a bit // but we want it unrasterized for tvOS where it must be scaled. - //self.layer.shouldRasterize = YES; + self.layer.shouldRasterize = YES; self.layer.rasterizationScale = [UIScreen mainScreen].scale; if (@available(iOS 13.4.1, *)) { @@ -136,7 +137,8 @@ static UIImage* noImage; BOOL noAppImage = false; // First check the memory cache - UIImage* appImage = [_artCache objectForKey:_app]; + //UIImage* appImage = [_artCache objectForKey:_app]; + UIImage* appImage = nil; // NSLog(@"appImage instance: %lu", (uintptr_t)appImage); if (appImage == nil) { // Next try to load from the on disk cache @@ -170,7 +172,7 @@ static UIImage* noImage; // Only create the app overlay if needed if (@available(iOS 13.0, *)) { - UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:23 weight:UIImageSymbolWeightBlack ]; + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:23]; UIImageView* playIcon = [[UIImageView alloc] initWithImage:[[UIImage systemImageNamed:@"play.circle.fill" withConfiguration:config] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]]; //playIcon.tintColor = [[ThemeManager widgetBackgroundColor] colorWithAlphaComponent:0.85]; playIcon.tintColor = [[UIColor blackColor] colorWithAlphaComponent:0.55]; @@ -186,7 +188,7 @@ static UIImage* noImage; //_appOverlay.layer.shadowOffset = CGSizeMake(1, 1); //_appOverlay.layer.shadowOpacity = 1; //_appOverlay.layer.shadowRadius = 1.3; - _appOverlay.contentMode = UIViewContentModeScaleAspectFit; + //_appOverlay.contentMode = UIViewContentModeScaleAspectFit; } else if(noAppImage){ if (@available(iOS 13.0, *)) { @@ -195,18 +197,18 @@ static UIImage* noImage; UIImageView* appIcon = [[UIImageView alloc] initWithImage:[appIconImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]]; //layIcon.tintColor = [[ThemeManager widgetBackgroundColor] colorWithAlphaComponent:0.85]; - appIcon.tintColor = [[UIColor blackColor] colorWithAlphaComponent:0.55]; + appIcon.tintColor = [[UIColor blackColor] colorWithAlphaComponent:0.5]; _appOverlay = appIcon; } } - if (true) { + if(true) { _appLabel = [[UILabel alloc] init]; _appLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.55]; [_appLabel setTextColor:[[UIColor whiteColor] colorWithAlphaComponent:1]]; //_appLabel.shadowColor = [UIColor blackColor]; - [_appLabel setText:_app.name]; + [_appLabel setText:[_app.name isEqualToString:@"Steam Big Picture"] ? @"Steam" : _app.name]; [_appLabel setFont:[UIFont systemFontOfSize:15]]; [_appLabel setBaselineAdjustment:UIBaselineAdjustmentAlignCenters]; [_appLabel setTextAlignment:NSTextAlignmentCenter]; @@ -221,8 +223,8 @@ static UIImage* noImage; [_appImage.overlayContentView addSubview:_appLabel]; [_appImage.overlayContentView addSubview:_appOverlay]; #else - [self addSubview:_appLabel]; [self addSubview:_appOverlay]; + [self addSubview:_appLabel]; #endif } @@ -249,19 +251,16 @@ static UIImage* noImage; //[_appLabel setFrame:CGRectMake(padding, _appOverlay.frame.size.height + padding, frameSize.width - 2 * padding, frameSize.height - _appOverlay.frame.size.height - 2 * padding)]; } - /* - else if (_appOverlay != nil) { - _appOverlay.frame = CGRectMake(0, 0, frameSize.width / 2, frameSize.width / 2); - _appOverlay.center = center; - }*/ } - (void) updateLoop { // Stop immediately if the view has been detached - if (self.superview == nil) { + if ([self.updateLoopDelegate isStreaming] || self.superview == nil) { return; } + NSLog(@"appview update loop %f", CACurrentMediaTime()); + // Update the app image if neccessary if ((_appOverlay != nil && ![_app.id isEqualToString:_app.host.currentGame]) || (_appOverlay == nil && [_app.id isEqualToString:_app.host.currentGame])) { diff --git a/VoidLink/ViewControllers/MainFrameViewController.h b/VoidLink/ViewControllers/MainFrameViewController.h index 2f0d501e..ebc9b42e 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.h +++ b/VoidLink/ViewControllers/MainFrameViewController.h @@ -21,7 +21,7 @@ #import "HostCollectionViewController.h" -@interface MainFrameViewController : UICollectionViewController +@interface MainFrameViewController : UICollectionViewController @property (nonatomic, strong) IBOutlet UIBarButtonItem *settingsButton; #if !TARGET_OS_TV diff --git a/VoidLink/ViewControllers/MainFrameViewController.m b/VoidLink/ViewControllers/MainFrameViewController.m index d1a97e44..7d133c14 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.m +++ b/VoidLink/ViewControllers/MainFrameViewController.m @@ -331,6 +331,7 @@ static NSMutableSet* hostList; // when at the host selection view. [self.navigationController.view removeGestureRecognizer:_menuRecognizer]; #endif + [self.collectionView setContentOffset:CGPointZero animated:NO]; [_appManager stopRetrieving]; _showHiddenApps = NO; _selectedHost = nil; @@ -1775,6 +1776,8 @@ static NSMutableSet* hostList; - (void)viewWillDisappear:(BOOL)animated{ NSLog(@"willDisappear"); [super viewWillDisappear:animated]; + [self.collectionView removeFromSuperview]; + [self.hostCollectionVC.view removeFromSuperview]; [_foregroundHostUpdateTimer invalidate]; _foregroundHostUpdateTimer = nil; } @@ -1811,6 +1814,7 @@ static NSMutableSet* hostList; [self retrieveSavedHosts]; _discMan = [[DiscoveryManager alloc] initWithHosts:[hostList allObjects] andCallback:self]; + } - (void)viewDidDisappear:(BOOL)animated @@ -2002,11 +2006,16 @@ static NSMutableSet* hostList; [self.collectionView reloadData]; } +- (bool)isStreaming{ + return self.revealViewController.isStreaming; +} + - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AppCell" forIndexPath:indexPath]; TemporaryApp* app = _sortedAppList[indexPath.row]; UIAppView* appView = [[UIAppView alloc] initWithApp:app cache:_boxArtCache andCallback:self]; + appView.updateLoopDelegate = (id)self; if (appView.bounds.size.width > 10.0) { CGFloat scale = cell.bounds.size.width / appView.bounds.size.width;