From 689221f10ebe25341553347661ce041906e25bdb Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Sun, 21 Sep 2025 18:35:56 +0800 Subject: [PATCH] send dummy event to prevent stuttering --- VoidLink/Base.lproj/iPad.storyboard | 22 ++++++++- VoidLink/Base.lproj/iPhone.storyboard | 20 +++++++++ VoidLink/Database/DataManager.h | 17 +++---- VoidLink/Database/DataManager.m | 2 + VoidLink/Database/TemporarySettings.h | 1 + VoidLink/Database/TemporarySettings.m | 1 + .../VoidLink v1.0.xcdatamodel/contents | 1 + VoidLink/Localization/Localizable.xcstrings | 29 ++++++++++++ .../ViewControllers/SettingsViewController.h | 3 ++ .../ViewControllers/SettingsViewController.m | 10 +++++ .../StreamFrameViewController.m | 45 ++++++++++++++++++- VoidLink/mul.lproj/iPad.xcstrings | 30 +++++++++++++ VoidLink/mul.lproj/iPhone.xcstrings | 30 +++++++++++++ 13 files changed, 201 insertions(+), 10 deletions(-) diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard index a32efcdb..331f2fb5 100644 --- a/VoidLink/Base.lproj/iPad.storyboard +++ b/VoidLink/Base.lproj/iPad.storyboard @@ -998,7 +998,7 @@ - + + + + + + + + + + + + + + @@ -1130,6 +1148,8 @@ + + diff --git a/VoidLink/Base.lproj/iPhone.storyboard b/VoidLink/Base.lproj/iPhone.storyboard index 5ffabcb4..e81e3e08 100644 --- a/VoidLink/Base.lproj/iPhone.storyboard +++ b/VoidLink/Base.lproj/iPhone.storyboard @@ -1795,6 +1795,24 @@ + + + + + + + + + + + + + @@ -1867,6 +1885,8 @@ + + diff --git a/VoidLink/Database/DataManager.h b/VoidLink/Database/DataManager.h index be2012c1..77029987 100644 --- a/VoidLink/Database/DataManager.h +++ b/VoidLink/Database/DataManager.h @@ -78,25 +78,25 @@ typedef NS_ENUM(NSInteger, WidgetSizeTransition) { gyroSensitivity:(CGFloat)gyroSensitivity touchMoveEventInterval:(NSInteger)touchMoveEventInterval reverseMouseWheelDirection:(BOOL)reverseMouseWheelDirection - asyncNativeTouchPriority:(NSInteger)asyncNativeTouchPriority + asyncNativeTouchPriority:(NSInteger)asyncNativeTouchPriority liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard showKeyboardToolbar:(BOOL)showKeyboardToolbar optimizeGames:(BOOL)optimizeGames multiController:(BOOL)multiController - buttonVisualFeedback:(BOOL)buttonVisualFeedback + buttonVisualFeedback:(BOOL)buttonVisualFeedback swapABXYButtons:(BOOL)swapABXYButtons audioOnPC:(BOOL)audioOnPC - redirectMic:(BOOL)redirectMic + redirectMic:(BOOL)redirectMic preferredCodec:(uint32_t)preferredCodec - enableYUV444:(BOOL)enableYUV444 + enableYUV444:(BOOL)enableYUV444 enablePIP:(BOOL)enablePIP enableHdr:(BOOL)enableHdr btMouseSupport:(BOOL)btMouseSupport // absoluteTouchMode:(BOOL)absoluteTouchMode touchMode:(NSInteger)touchMode statsOverlayLevel:(NSInteger)statsOverlayLevel - statsOverlayEnabled:(BOOL)statsOverlayEnabled - unlockDisplayOrientation:(BOOL)unlockDisplayOrientation + statsOverlayEnabled:(BOOL)statsOverlayEnabled + unlockDisplayOrientation:(BOOL)unlockDisplayOrientation resolutionSelected:(NSInteger)resolutionSelected externalDisplayMode:(NSInteger)externalDisplayMode localMousePointerMode:(NSInteger)localMousePointerMode @@ -104,8 +104,9 @@ typedef NS_ENUM(NSInteger, WidgetSizeTransition) { enableGraphs:(BOOL)enableGraphs graphOpacity:(NSInteger)graphOpacity renderingBackend:(NSInteger)renderingBackend - framePacingMode:(NSInteger)framePacingMode - backgroundSessionTimer:(NSInteger)backgroundSessionTimer; + framePacingMode:(NSInteger)framePacingMode + sendDummyEvent:(BOOL)sendDummyEvent + backgroundSessionTimer:(NSInteger)backgroundSessionTimer; - (NSArray*) getHosts; - (void) updateHost:(TemporaryHost*)host; diff --git a/VoidLink/Database/DataManager.m b/VoidLink/Database/DataManager.m index 96038ad8..57106b74 100644 --- a/VoidLink/Database/DataManager.m +++ b/VoidLink/Database/DataManager.m @@ -100,6 +100,7 @@ graphOpacity:(NSInteger)graphOpacity renderingBackend:(NSInteger)renderingBackend framePacingMode:(NSInteger)framePacingMode + sendDummyEvent:(BOOL)sendDummyEvent backgroundSessionTimer:(NSInteger)backgroundSessionTimer{ [_managedObjectContext performBlockAndWait:^{ @@ -151,6 +152,7 @@ settingsToSave.graphOpacity = [NSNumber numberWithInteger:graphOpacity]; settingsToSave.renderingBackend = [NSNumber numberWithInteger:renderingBackend]; settingsToSave.framePacingMode = [NSNumber numberWithInteger:framePacingMode]; + settingsToSave.sendDummyEvent = sendDummyEvent; [self saveData]; }]; } diff --git a/VoidLink/Database/TemporarySettings.h b/VoidLink/Database/TemporarySettings.h index d6456fb7..4a5f8d5d 100644 --- a/VoidLink/Database/TemporarySettings.h +++ b/VoidLink/Database/TemporarySettings.h @@ -70,6 +70,7 @@ @property (nonatomic, retain) NSNumber * frameQueueSize; @property (nonatomic, retain) NSNumber * graphOpacity; @property (nonatomic, retain) NSNumber * renderingBackend; +@property (nonatomic) BOOL sendDummyEvent; @property (nonatomic, retain) NSNumber * framePacingMode; - (id) initFromSettings:(Settings*)settings; diff --git a/VoidLink/Database/TemporarySettings.m b/VoidLink/Database/TemporarySettings.m index 9cfba10c..f40e5277 100644 --- a/VoidLink/Database/TemporarySettings.m +++ b/VoidLink/Database/TemporarySettings.m @@ -125,6 +125,7 @@ self.graphOpacity = settings.graphOpacity; self.renderingBackend = settings.renderingBackend; self.framePacingMode = settings.framePacingMode; + self.sendDummyEvent = settings.sendDummyEvent; self.backgroundSessionTimer = settings.backroundSessionTimer; #endif self.uniqueId = settings.uniqueId; diff --git a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents index bf836daa..508ded7a 100644 --- a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents +++ b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents @@ -56,6 +56,7 @@ + diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index d3327ddc..3f897c5e 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -4719,6 +4719,35 @@ } } }, + "sendDummyEventStackTip" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This fixes stuttering on some devices, particularly those on iPadOS 26." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "该选项解决了部分设备的串流卡顿问题, 尤其是升级了iPadOS 26的设备。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "該選項解決了部分裝置的串流卡頓問題,尤其是升級了 iPadOS 26 的裝置。" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "該選項解決了部分裝置的串流卡頓問題,尤其是升級了 iPadOS 26 的裝置。" + } + } + } + }, "SensitivityX: %.2f" : { "extractionState" : "manual", "localizations" : { diff --git a/VoidLink/ViewControllers/SettingsViewController.h b/VoidLink/ViewControllers/SettingsViewController.h index fffb74f5..675fcb3f 100644 --- a/VoidLink/ViewControllers/SettingsViewController.h +++ b/VoidLink/ViewControllers/SettingsViewController.h @@ -132,6 +132,9 @@ @property (strong, nonatomic) IBOutlet UIStackView *buttonVisualFeedbackStack; @property (strong, nonatomic) IBOutlet UISwitch *buttonVisualFeedbackSwitch; +@property (strong, nonatomic) IBOutlet UIStackView *sendDummyEventStack; +@property (strong, nonatomic) IBOutlet UISwitch *sendDummyEventSwitch; + @property (nonatomic, strong) MicHandler *micHandler; diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index af36a469..7c766698 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -704,6 +704,8 @@ BOOL isCustomResolution(int resolutionSelected) { [self addSetting:self.performanceGraphStack ofId:@"performanceGraphStack" withInfoTag:YES withDynamicLabel:NO to:experimentalSection]; [self addDynamicLabelForStack:self.graphOpacityStack]; + [self addSetting:self.sendDummyEventStack ofId:@"sendDummyEventStack" withInfoTag:YES withDynamicLabel:NO to:experimentalSection]; + [experimentalSection addToParentStack:_parentStack]; [experimentalSection setExpanded:YES]; } @@ -1145,6 +1147,10 @@ BOOL isCustomResolution(int resolutionSelected) { tipText = [LocalizationHelper localizedStringForKey:@"redirectMicStackTip"]; showOnlineDocAction = false; } + if([sender.superview.accessibilityIdentifier isEqualToString: @"sendDummyEventStack"]){ + tipText = [LocalizationHelper localizedStringForKey:@"sendDummyEventStackTip"]; + showOnlineDocAction = false; + } UIAlertController *tipsAlertController = [UIAlertController alertControllerWithTitle: [LocalizationHelper localizedStringForKey:@"Tips"] message:tipText preferredStyle:UIAlertControllerStyleAlert]; @@ -1683,6 +1689,8 @@ BOOL isCustomResolution(int resolutionSelected) { [self.externalDisplayModeSelector setSelectedSegmentIndex:currentSettings.externalDisplayMode.integerValue]; [self.localMousePointerModeSelector setSelectedSegmentIndex:currentSettings.localMousePointerMode.integerValue]; + [self.sendDummyEventSwitch setOn:currentSettings.sendDummyEvent];// Load old setting + settingsViewJustLoaded = false; } @@ -2476,6 +2484,7 @@ BOOL isCustomResolution(int resolutionSelected) { } NSInteger externalDisplayMode = [self.externalDisplayModeSelector selectedSegmentIndex]; NSInteger localMousePointerMode = [self.localMousePointerModeSelector selectedSegmentIndex]; + BOOL sendDummyEvent = self.sendDummyEventSwitch.isOn; NSInteger backgroundSessionTimer = self.backgroundSessionTimerSlider.value == self.backgroundSessionTimerSlider.maximumValue ? (uint32_t) INT16_MAX : (uint32_t)self.backgroundSessionTimerSlider.value; [dataMan saveSettingsWithBitrate:_bitrate @@ -2522,6 +2531,7 @@ BOOL isCustomResolution(int resolutionSelected) { graphOpacity:graphOpacity renderingBackend:renderingBackend framePacingMode:framePacingMode + sendDummyEvent:sendDummyEvent backgroundSessionTimer:backgroundSessionTimer]; } diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index e40bd0e5..0ae8d759 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -82,6 +82,8 @@ dispatch_block_t _delayedRemoveExtScreen; VideoDecoderRenderer *_videoRenderer; BOOL _isRestoringFromPiP; + CADisplayLink *_displayLink; + #if !TARGET_OS_TV CustomEdgeSlideGestureRecognizer *_slideToSettingsRecognizer; CustomEdgeSlideGestureRecognizer *_slideToCmdToolRecognizer; @@ -89,6 +91,7 @@ LayoutOnScreenControlsViewController *_layoutOnScreenControlsVC; ToolboxViewController* toolBoxViewController; MicHandler* micHandler; + #else UITapGestureRecognizer *_menuTapGestureRecognizer; UITapGestureRecognizer *_menuDoubleTapGestureRecognizer; @@ -425,11 +428,24 @@ if (self.imguiView && self.imguiView.mtkView.superview) { [self.view bringSubviewToFront:self.imguiView.mtkView]; } - + + if(_settings.sendDummyEvent){ + [self setupDisplayLink]; + [self startDisplayLink]; + } NSLog(@"frameview gestures: %d", (uint32_t)[self.view.gestureRecognizers count]); NSLog(@"streamview gestures: %d", (uint32_t)[_streamView.gestureRecognizers count]); } +- (void)viewWillAppear:(BOOL)animated { + if(_settings.sendDummyEvent) [self startDisplayLink]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self stopDisplayLink]; +} + - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; @@ -1123,6 +1139,7 @@ - (void)expandSettingsView{ self.mainFrameViewcontroller.settingsExpandedInStreamView = true; //notify mainFrameViewContorller that this is a setting expansion in stream view, some settings shall be disabled. + [self stopDisplayLink]; [self.mainFrameViewcontroller expandSettingsView]; } @@ -1592,4 +1609,30 @@ dispatch_after(delayTime, dispatch_get_main_queue(), block); } +- (void)dealloc { + [self stopDisplayLink]; +} + +- (void)setupDisplayLink { + if (_displayLink != nil) return; + _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTick:)]; + [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; +} + +- (void)startDisplayLink { + _displayLink.paused = NO; +} + +- (void)stopDisplayLink { + [_displayLink invalidate]; + _displayLink = nil; +} + +- (void)displayLinkTick:(CADisplayLink *)link { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + LiSendKeyboardEvent(0xFF, KEY_ACTION_UP, 0); + }); +} + + @end diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings index 1025c1db..1569f104 100644 --- a/VoidLink/mul.lproj/iPad.xcstrings +++ b/VoidLink/mul.lproj/iPad.xcstrings @@ -1717,6 +1717,36 @@ } } }, + "Gus-CK-wpc.text" : { + "comment" : "Class = \"UILabel\"; text = \"Send Dummy Event\"; ObjectID = \"Gus-CK-wpc\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Send Dummy Event" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发送假事件" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "發送假事件" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "發送假事件" + } + } + } + }, "H8S-aI-EBo.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; H8S-aI-EBo.segmentTitles[0] = \"hovering\"; ObjectID = \"H8S-aI-EBo\";", "extractionState" : "extracted_with_value", diff --git a/VoidLink/mul.lproj/iPhone.xcstrings b/VoidLink/mul.lproj/iPhone.xcstrings index 1386171a..64dc4126 100644 --- a/VoidLink/mul.lproj/iPhone.xcstrings +++ b/VoidLink/mul.lproj/iPhone.xcstrings @@ -3631,6 +3631,36 @@ } } }, + "Vgl-rL-Ppi.text" : { + "comment" : "Class = \"UILabel\"; text = \"Send Dummy Event\"; ObjectID = \"Vgl-rL-Ppi\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Send Dummy Event" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发送假事件" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "發送假事件" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "發送假事件" + } + } + } + }, "wOV-Sp-UUE.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; wOV-Sp-UUE.segmentTitles[0] = \"Slide From Left Edge\"; ObjectID = \"wOV-Sp-UUE\";", "extractionState" : "extracted_with_value",