diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard
index 40d4b31e..80ddac45 100644
--- a/VoidLink/Base.lproj/iPad.storyboard
+++ b/VoidLink/Base.lproj/iPad.storyboard
@@ -1360,16 +1360,16 @@
-
+
-
+
@@ -1474,6 +1474,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1481,6 +1501,8 @@
+
+
@@ -2377,7 +2399,7 @@
-
+
@@ -2386,13 +2408,13 @@
-
+
-
+
-
+
diff --git a/VoidLink/Base.lproj/iPhone.storyboard b/VoidLink/Base.lproj/iPhone.storyboard
index a1419f41..1dc675dd 100644
--- a/VoidLink/Base.lproj/iPhone.storyboard
+++ b/VoidLink/Base.lproj/iPhone.storyboard
@@ -2214,16 +2214,16 @@
-
+
-
+
@@ -2257,6 +2257,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2264,6 +2284,8 @@
+
+
diff --git a/VoidLink/Database/DataManager.h b/VoidLink/Database/DataManager.h
index 2b636aeb..3c2efe89 100644
--- a/VoidLink/Database/DataManager.h
+++ b/VoidLink/Database/DataManager.h
@@ -126,6 +126,7 @@ typedef NS_ENUM(NSInteger, WidgetSizeTransition) {
hapticEngine:(NSInteger)hapticEngine
edgeSlidingSensitivity:(CGFloat)edgeSlidingSensitivity
audioEngine:(NSInteger)audioEngine
+ appendLeftClick:(BOOL)appendLeftClick
backgroundSessionTimer:(NSInteger)backgroundSessionTimer;
- (NSArray*) getHosts;
diff --git a/VoidLink/Database/DataManager.m b/VoidLink/Database/DataManager.m
index 8d3c8711..a1c03f0a 100644
--- a/VoidLink/Database/DataManager.m
+++ b/VoidLink/Database/DataManager.m
@@ -109,6 +109,7 @@
hapticEngine:(NSInteger)hapticEngine
edgeSlidingSensitivity:(CGFloat)edgeSlidingSensitivity
audioEngine:(NSInteger)audioEngine
+ appendLeftClick:(BOOL)appendLeftClick
backgroundSessionTimer:(NSInteger)backgroundSessionTimer{
[_managedObjectContext performBlockAndWait:^{
@@ -168,6 +169,7 @@
settingsToSave.hapticEngine = [NSNumber numberWithInteger:hapticEngine];
settingsToSave.edgeSlidingSensitivity = [NSNumber numberWithFloat:edgeSlidingSensitivity];
settingsToSave.audioEngine = [NSNumber numberWithInteger:audioEngine];
+ settingsToSave.appendLeftClick = appendLeftClick;
settingsToSave.rememberFoldState = rememberFoldState;
[self saveData];
}];
diff --git a/VoidLink/Database/TemporarySettings.h b/VoidLink/Database/TemporarySettings.h
index eff564bd..300c7221 100644
--- a/VoidLink/Database/TemporarySettings.h
+++ b/VoidLink/Database/TemporarySettings.h
@@ -83,6 +83,7 @@
@property (nonatomic, retain) NSNumber * appTheme;
@property (nonatomic, retain) NSNumber * edgeSlidingSensitivity;
@property (nonatomic, retain) NSNumber * audioEngine;
+@property (nonatomic) BOOL appendLeftClick;
@property (nonatomic, retain) NSNumber * framePacingMode;
- (id) initFromSettings:(Settings*)settings;
diff --git a/VoidLink/Database/TemporarySettings.m b/VoidLink/Database/TemporarySettings.m
index 900813f7..005e863a 100644
--- a/VoidLink/Database/TemporarySettings.m
+++ b/VoidLink/Database/TemporarySettings.m
@@ -140,6 +140,7 @@
self.hapticEngine = settings.hapticEngine;
self.uniqueId = settings.uniqueId;
self.audioEngine = settings.audioEngine;
+ self.appendLeftClick = settings.appendLeftClick;
#endif
return self;
diff --git a/VoidLink/Input/AbsoluteTouchHandler.m b/VoidLink/Input/AbsoluteTouchHandler.m
index c356387d..841b2f6a 100644
--- a/VoidLink/Input/AbsoluteTouchHandler.m
+++ b/VoidLink/Input/AbsoluteTouchHandler.m
@@ -39,12 +39,16 @@
CGFloat slideGestureVerticalThreshold;
CGFloat screenWidthWithThreshold;
CGFloat _edgeTolerance;
+
+ bool _appendMouseLeftClick;
}
- (id)initWithView:(StreamView*)view andSettings:(TemporarySettings*)settings {
self = [self init];
self->streamView = view;
+ _appendMouseLeftClick = settings.appendLeftClick;
+
// upper screen check
_edgeTolerance = settings.edgeSlidingSensitivity.floatValue;
slideGestureVerticalThreshold = CGRectGetHeight([[UIScreen mainScreen] bounds]) * 0.4;
@@ -140,6 +144,8 @@
// Remember this last touch for touch-down deadzoning
lastTouchUp = [touches anyObject];
lastTouchUpLocation = [lastTouchUp locationInView:streamView];
+
+ if(_appendMouseLeftClick && [self touchDidntMoveOnScreen:lastTouchUp]) [self sendShortMouseLeftButtonClickEvent];
}
}
@@ -148,4 +154,25 @@
[self touchesEnded:touches withEvent:event];
}
+- (void)sendShortMouseLeftButtonClickEvent{
+ dispatch_time_t delayShort = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC));
+ dispatch_time_t delayLong = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03 * NSEC_PER_SEC));
+ dispatch_after(delayShort, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
+ dispatch_after(delayLong, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
+ });
+ });
+}
+
+- (bool)touchDidntMoveOnScreen:(UITouch* )touch{
+ return [self isAdjacentPoints:[touch locationInView:streamView] from:lastTouchDownLocation tolerance:3];
+}
+
+- (BOOL)isAdjacentPoints:(CGPoint)currentPoint from:(CGPoint)originalPoint tolerance:(CGFloat)tolerance {
+ bool isAdjacent = hypotf(originalPoint.x - currentPoint.x, originalPoint.y - currentPoint.y) <= hypot(tolerance, tolerance);
+ return isAdjacent;
+}
+
+
@end
diff --git a/VoidLink/Input/NativeTouchHandler.m b/VoidLink/Input/NativeTouchHandler.m
index b7aaf31a..757919e7 100644
--- a/VoidLink/Input/NativeTouchHandler.m
+++ b/VoidLink/Input/NativeTouchHandler.m
@@ -17,7 +17,6 @@
@implementation NativeTouchHandler {
StreamView* streamView;
TemporarySettings* currentSettings;
- bool activateCoordSelector;
CGFloat pointerVelocityDividerLocationByPoints;
bool asyncNativeTouch;
@@ -43,13 +42,14 @@
CGRect streamViewBounds;
int64_t moveEventIntervalNSec;
+
+ bool _appendMouseLeftClick;
}
- (id)initWithView:(StreamView*)view andSettings:(TemporarySettings*)settings{
self = [super init];
self->streamView = view;
self->currentSettings = settings;
- self->activateCoordSelector = currentSettings.pointerVelocityModeDivider.floatValue != 1.0;
self->moveEventIntervalNSec = (int64_t)(currentSettings.touchMoveEventInterval.intValue * 1000);;
self->streamViewBounds = view.bounds;
@@ -60,7 +60,9 @@
}
self->activePointerIds = [NSMutableSet set];
self->blacklistedTouches = [NSMutableSet set];
-
+
+ _appendMouseLeftClick = settings.appendLeftClick;
+
self->asyncNativeTouch = settings.asyncNativeTouchPriority.intValue != AsyncNativeTouchOff;
switch(settings.asyncNativeTouchPriority.intValue){
@@ -195,7 +197,7 @@
//NSLog(@"selecting coords: %d", touch.phase == UITouchPhaseMoved);
// NSLog(@"excluded count: %d", (uint32_t)[excludedPointerIds count]);
- targetCoords = activateCoordSelector ? [self selectCoordsFor:touch] : [touch locationInView:streamView];
+ targetCoords = [self selectCoordsFor:touch];
CGPoint location = [self adjustCoordinatesForVideoArea:targetCoords];
CGSize videoSize = [self getVideoAreaSize];
@@ -215,7 +217,7 @@
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
[self handleTouchDown:touch]; //generate & populate pointerId
- if(self->activateCoordSelector) [self populatePointerObjIntoDict:touch];
+ [self populatePointerObjIntoDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_DOWN];
}
});
@@ -224,7 +226,7 @@
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
[self handleTouchDown:touch]; //generate & populate pointerId
- if(self->activateCoordSelector) [self populatePointerObjIntoDict:touch];
+ [self populatePointerObjIntoDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_DOWN];
}
}
@@ -234,7 +236,7 @@
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, moveEventIntervalNSec), dispatch_get_main_queue(), ^{
for (UITouch* touch in touches){
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
- if(self->activateCoordSelector) [self updatePointerObjInDict:touch];
+ [self updatePointerObjInDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_MOVE];
[[self getPointerObjFromDict:touch] doesNeedResetCoords];
}
@@ -251,8 +253,11 @@
}
[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->_appendMouseLeftClick && [self touchDidntMoveOnScreen:touch]) [self sendShortMouseLeftButtonClickEvent];
+ [self removePointerObjFromDict:touch];
+
+ [self->blacklistedTouches removeObject:touchAddrObj];
}
});
}
@@ -261,7 +266,6 @@
[self touchesEnded:touches withEvent:event];
}
-
- (void)populatePointerObjIntoDict:(UITouch*)touch{
NativeTouchPointer* pointer = [[NativeTouchPointer alloc] initWithTouch:touch];
pointer.useRelativeCoords = pointer.initialPoint.x > pointerVelocityDividerLocationByPoints;
@@ -290,4 +294,25 @@
return pointer.useRelativeCoords ? pointer.latestRelativePoint : pointer.latestPoint;
}
+- (void)sendShortMouseLeftButtonClickEvent{
+ dispatch_time_t delayShort = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC));
+ dispatch_time_t delayLong = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03 * NSEC_PER_SEC));
+ dispatch_after(delayShort, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
+ dispatch_after(delayLong, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
+ });
+ });
+}
+
+- (bool)touchDidntMoveOnScreen:(UITouch* )touch{
+ NativeTouchPointer *pointer = [pointerObjDict objectForKey:@((uintptr_t)touch)];
+ return [self isAdjacentPoints:[touch locationInView:streamView] from:pointer.initialPoint tolerance:3];
+}
+
+- (BOOL)isAdjacentPoints:(CGPoint)currentPoint from:(CGPoint)originalPoint tolerance:(CGFloat)tolerance {
+ bool isAdjacent = hypotf(originalPoint.x - currentPoint.x, originalPoint.y - currentPoint.y) <= hypot(tolerance, tolerance);
+ return isAdjacent;
+}
+
@end
diff --git a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents
index 5358b8b0..72711279 100644
--- a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents
+++ b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents
@@ -21,6 +21,7 @@
+
diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings
index a7835276..6689dd82 100644
--- a/VoidLink/Localization/Localizable.xcstrings
+++ b/VoidLink/Localization/Localizable.xcstrings
@@ -766,6 +766,35 @@
}
}
},
+ "appendLeftClickStackTip" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "This works for games like “Old School RuneScape” that requires double tap on touch screen"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : "对需要在触摸屏上双击的游戏, 如 OSRS (Old School RuneScape) 有效"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : "對需要在觸控螢幕上雙擊的遊戲, 如 OSRS(Old School RuneScape)有效"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : "對需要在觸控螢幕上雙擊的遊戲, 如 OSRS(Old School RuneScape)有效"
+ }
+ }
+ }
+ },
"Applications" : {
"extractionState" : "manual",
"localizations" : {
diff --git a/VoidLink/ViewControllers/SettingsViewController.h b/VoidLink/ViewControllers/SettingsViewController.h
index b11fafe7..6c62d381 100644
--- a/VoidLink/ViewControllers/SettingsViewController.h
+++ b/VoidLink/ViewControllers/SettingsViewController.h
@@ -181,6 +181,8 @@
@property (strong, nonatomic) IBOutlet UIStackView *audioEngineStack;
@property (strong, nonatomic) IBOutlet UISegmentedControl *audioEngineSelector;
+@property (strong, nonatomic) IBOutlet UIStackView *appendLeftClickStack;
+@property (strong, nonatomic) IBOutlet UISwitch *appendLeftClickSwitch;
@property (strong, nonatomic) IBOutlet UIStackView *testStack;
diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m
index 93e67b5d..37afc950 100644
--- a/VoidLink/ViewControllers/SettingsViewController.m
+++ b/VoidLink/ViewControllers/SettingsViewController.m
@@ -718,6 +718,7 @@ BOOL isCustomResolution(int resolutionSelected) {
[self addSetting:self.singleTapSensitivityStack ofId:@"singleTapSensitivityStack" withInfoTag:NO withDynamicLabel:YES to:touchAndControlSection];
[self addSetting:self.onScreenWidgetStack ofId:@"onScreenWidgetStack" withInfoTag:YES withDynamicLabel:YES to:touchAndControlSection];
[self addSetting:self.buttonVisualFeedbackStack ofId:@"buttonVisualFeedbackStack" withInfoTag:NO withDynamicLabel:NO to:touchAndControlSection];
+ [self addSetting:self.appendLeftClickStack ofId:@"appendLeftClickStack" withInfoTag:YES withDynamicLabel:NO to:touchAndControlSection];
[self addSetting:self.swapAbxyStack ofId:@"swapAbaxyStack" withInfoTag:NO withDynamicLabel:NO to:touchAndControlSection];
[self addSetting:self.hapticEngineStack ofId:@"hapticEngineStack" withInfoTag:NO withDynamicLabel:NO to:touchAndControlSection];
[self addSetting:self.emulatedControllerTypeStack ofId:@"emulatedControllerTypeStack" withInfoTag:YES withDynamicLabel:NO to:touchAndControlSection];
@@ -1281,6 +1282,10 @@ BOOL isCustomResolution(int resolutionSelected) {
tipText = [LocalizationHelper localizedStringForKey:@"useBuiltinMicStackTip"];
showOnlineDocAction = false;
}
+ if([sender.superview.accessibilityIdentifier isEqualToString: @"appendLeftClickStack"]){
+ tipText = [LocalizationHelper localizedStringForKey:@"appendLeftClickStackTip"];
+ showOnlineDocAction = false;
+ }
UIAlertController *tipsAlertController = [UIAlertController alertControllerWithTitle: [LocalizationHelper localizedStringForKey:@"Tips"] message:tipText preferredStyle:UIAlertControllerStyleAlert];
@@ -1673,7 +1678,8 @@ BOOL isCustomResolution(int resolutionSelected) {
[self.multiControllerSwitch setOn:self->tempSettings.multiController];
[self.swapAbxySwitch setOn:self->tempSettings.swapABXYButtons];
[self.buttonVisualFeedbackSwitch setOn:self->tempSettings.buttonVisualFeedback];
-
+ [self.appendLeftClickSwitch setOn:self->tempSettings.appendLeftClick];
+
[self.hapticEngineSelector setSelectedSegmentIndex:self->tempSettings.hapticEngine.intValue];
bool hideHapticEngineStack = false;
if(@available(iOS 13.0, tvOS 13.0, *)) hideHapticEngineStack = false;
@@ -2324,6 +2330,8 @@ BOOL isCustomResolution(int resolutionSelected) {
[self setHidden:!(sender.selectedSegmentIndex == RelativeTouch) forStack:self.singleTapSensitivityStack];
[self setHidden:![self isNotNativeTouchOnly] forStack:self.onScreenWidgetStack];
[self setHidden:![self isNotNativeTouchOnly] forStack:self.buttonVisualFeedbackStack];
+ [self setHidden:!(([self isNotNativeTouchOnly] && sender.selectedSegmentIndex == NativeTouch)
+ || sender.selectedSegmentIndex == AbsoluteTouch) forStack:self.appendLeftClickStack];
[self handleOswGestureChange];
}
@@ -2392,6 +2400,7 @@ BOOL isCustomResolution(int resolutionSelected) {
- (void)enableOswForNativeTouchSwitchFlipped:(UISwitch *)sender{
[self setHidden:!sender.isOn forStack:self.onScreenWidgetStack];
[self setHidden:!sender.isOn forStack:self.buttonVisualFeedbackStack];
+ [self setHidden:!sender.isOn forStack:self.appendLeftClickStack];
[self handleOswGestureChange];
}
@@ -2861,6 +2870,7 @@ BOOL isCustomResolution(int resolutionSelected) {
NSInteger hapticEngine = self.hapticEngineSelector.selectedSegmentIndex;
CGFloat edgeSlidingSensitivity = self.edgeSlidingSensitivitySlider.value;
NSInteger audioEngine = self.audioEngineSelector.selectedSegmentIndex;
+ BOOL appendLeftClick = self.appendLeftClickSwitch.isOn;
NSInteger backgroundSessionTimer = self.backgroundSessionTimerSlider.value == self.backgroundSessionTimerSlider.maximumValue ? (uint32_t) INT16_MAX : (uint32_t)self.backgroundSessionTimerSlider.value;
[dataMan saveSettingsWithBitrate:_bitrate
@@ -2916,6 +2926,7 @@ BOOL isCustomResolution(int resolutionSelected) {
hapticEngine:hapticEngine
edgeSlidingSensitivity:edgeSlidingSensitivity
audioEngine:audioEngine
+ appendLeftClick:appendLeftClick
backgroundSessionTimer:backgroundSessionTimer];
}
diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings
index 98f0b360..1d29bc0a 100644
--- a/VoidLink/mul.lproj/iPad.xcstrings
+++ b/VoidLink/mul.lproj/iPad.xcstrings
@@ -559,6 +559,36 @@
}
}
},
+ "9Yx-H8-E3N.text" : {
+ "comment" : "Class = \"UILabel\"; text = \"Additional Left Click\"; ObjectID = \"9Yx-H8-E3N\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Additional Left Click"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : "追加鼠标左单击"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : "追加滑鼠左單擊"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : "追加滑鼠左單擊"
+ }
+ }
+ }
+ },
"53H-ST-OtY.segmentTitles[0]" : {
"comment" : "Class = \"UISegmentedControl\"; 53H-ST-OtY.segmentTitles[0] = \"Balanced\"; ObjectID = \"53H-ST-OtY\";",
"extractionState" : "extracted_with_value",
diff --git a/VoidLink/mul.lproj/iPhone.xcstrings b/VoidLink/mul.lproj/iPhone.xcstrings
index 8865b2f6..e2cb18e0 100644
--- a/VoidLink/mul.lproj/iPhone.xcstrings
+++ b/VoidLink/mul.lproj/iPhone.xcstrings
@@ -3481,6 +3481,36 @@
}
}
},
+ "qOa-te-sPj.text" : {
+ "comment" : "Class = \"UILabel\"; text = \"Additional Left Click\"; ObjectID = \"qOa-te-sPj\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Additional Left Click"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : "追加鼠标左单击"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : "追加滑鼠左單擊"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "needs_review",
+ "value" : "追加滑鼠左單擊"
+ }
+ }
+ }
+ },
"qPQ-U2-aSg.text" : {
"comment" : "Class = \"UILabel\"; text = \"Alpha\"; ObjectID = \"qPQ-U2-aSg\";",
"extractionState" : "extracted_with_value",