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",