- fix appview background update issue

- relocate chevron view for ipad osw tool
- improve edge slide detection for native touchHandler
This commit is contained in:
TrueZhuanjia
2025-07-19 14:47:04 +08:00
parent 12ad168e13
commit 334788c26f
9 changed files with 64 additions and 57 deletions
+6 -6
View File
@@ -1262,7 +1262,7 @@
</constraints>
</stackView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hKp-ds-86I" userLabel="Chevron View">
<rect key="frame" x="624" y="50" width="52" height="30"/>
<rect key="frame" x="724" y="50" width="52" height="30"/>
<subviews>
<imageView clipsSubviews="YES" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ChevronCompactUp" translatesAutoresizingMaskIntoConstraints="NO" id="iVZ-Fd-dY7">
<rect key="frame" x="0.0" y="0.0" width="52" height="30"/>
@@ -1304,7 +1304,7 @@
<constraint firstItem="hKp-ds-86I" firstAttribute="top" secondItem="YLK-IE-ajS" secondAttribute="bottom" id="RRf-mC-xx8"/>
<constraint firstAttribute="height" constant="50" id="gfG-JH-DGG"/>
<constraint firstAttribute="bottom" secondItem="YLK-IE-ajS" secondAttribute="bottom" id="gkv-Gt-6iw"/>
<constraint firstItem="hKp-ds-86I" firstAttribute="centerX" secondItem="fZU-G0-fgM" secondAttribute="centerX" id="uZV-o0-RdA"/>
<constraint firstItem="hKp-ds-86I" firstAttribute="centerX" secondItem="fZU-G0-fgM" secondAttribute="centerX" constant="100" id="uZV-o0-RdA"/>
<constraint firstItem="YLK-IE-ajS" firstAttribute="centerX" secondItem="fZU-G0-fgM" secondAttribute="centerX" id="xm7-a0-8G7"/>
</constraints>
</view>
@@ -1659,19 +1659,19 @@
<image name="tray.full.fill" catalog="system" width="128" height="88"/>
<image name="video.and.waveform" catalog="system" width="128" height="94"/>
<systemColor name="linkColor">
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBrownColor">
<color red="0.63529411759999999" green="0.51764705879999995" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.63529411764705879" green="0.51764705882352946" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemGray3Color">
<color red="0.78039215689999997" green="0.78039215689999997" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.7803921568627451" green="0.7803921568627451" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemTealColor">
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.18823529411764706" green="0.69019607843137254" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
+13 -16
View File
@@ -19,7 +19,6 @@
StreamView* streamView;
TemporarySettings* currentSettings;
bool activateCoordSelector;
bool touchPointSpawnedAtUpperScreenEdge;
CGFloat pointerVelocityDividerLocationByPoints;
uint16_t touchMoveEventIntervalUs;
@@ -35,7 +34,8 @@
NSMutableSet<NSNumber *> *activePointerIds; //pointerId Set for active touches.
NSMutableSet<NSNumber *> *pointerIdPool; //pre-defined pool of pointerIds.
NSMutableSet<NSNumber *> *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;
}
+14 -16
View File
@@ -18,7 +18,6 @@
StreamView* streamView;
TemporarySettings* currentSettings;
bool activateCoordSelector;
bool touchPointSpawnedAtUpperScreenEdge;
CGFloat pointerVelocityDividerLocationByPoints;
uint16_t touchMoveEventIntervalUs;
@@ -34,7 +33,8 @@
NSMutableSet<NSNumber *> *activePointerIds; //pointerId Set for active touches.
NSMutableSet<NSNumber *> *pointerIdPool; //pre-defined pool of pointerIds.
NSMutableSet<NSNumber *> *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;
}
}
-1
View File
@@ -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;
-3
View File
@@ -12,9 +12,6 @@
#include <Limelight.h>
static const int REFERENCE_WIDTH = 1280;
static const int REFERENCE_HEIGHT = 720;
static const float QUICK_TAP_TIME_INTERVAL = 0.2;
@implementation RelativeTouchHandler {
+8
View File
@@ -9,6 +9,11 @@
#import <UIKit/UIKit.h>
#import "TemporaryApp.h"
@protocol AppViewUpdateLoopDelegate <NSObject>
- (bool)isStreaming;
@end
@protocol AppCallback <NSObject>
- (void) appClicked:(TemporaryApp*)app view:(UIView*)view;
@@ -22,6 +27,9 @@
@interface UIAppView : UIButton
#endif
@property (nonatomic, weak) id<AppViewUpdateLoopDelegate> updateLoopDelegate; // Delegate property
- (id) initWithApp:(TemporaryApp*)app cache:(NSCache*)cache andCallback:(id<AppCallback>)callback;
- (void) updateAppImage;
+13 -14
View File
@@ -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])) {
@@ -21,7 +21,7 @@
#import "HostCollectionViewController.h"
@interface MainFrameViewController : UICollectionViewController <DiscoveryCallback, PairCallback, HostCallback, AppCallback, AppAssetCallback, NSURLConnectionDelegate, SWRevealViewControllerDelegate, HostCardActionDelegate>
@interface MainFrameViewController : UICollectionViewController <DiscoveryCallback, PairCallback, HostCallback, AppCallback, AppAssetCallback, NSURLConnectionDelegate, SWRevealViewControllerDelegate, HostCardActionDelegate, AppViewUpdateLoopDelegate>
@property (nonatomic, strong) IBOutlet UIBarButtonItem *settingsButton;
#if !TARGET_OS_TV
@@ -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<AppViewUpdateLoopDelegate>)self;
if (appView.bounds.size.width > 10.0) {
CGFloat scale = cell.bounds.size.width / appView.bounds.size.width;