diff --git a/VoidLink.xcodeproj/project.pbxproj b/VoidLink.xcodeproj/project.pbxproj index 25b08b9a..66878c76 100644 --- a/VoidLink.xcodeproj/project.pbxproj +++ b/VoidLink.xcodeproj/project.pbxproj @@ -15,12 +15,14 @@ 182760FD2E83E68B00EC1C5C /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 182760FC2E83E68B00EC1C5C /* Collections */; }; 1829CE3F2E2154DF0074A99A /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1829CE3E2E2154DF0074A99A /* SceneDelegate.m */; }; 18341A042DEB662C0094E5F2 /* FixedTintImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 18341A032DEB662C0094E5F2 /* FixedTintImageView.m */; }; + 18416EA42EBB3DCE00AE0124 /* UITouchUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18416EA32EBB3DCE00AE0124 /* UITouchUtil.swift */; }; 1850C82A2E667E4000845308 /* MicHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1850C8292E667E4000845308 /* MicHandler.swift */; }; 18729EDE2E8BD68800101194 /* UUIDHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18729EDD2E8BD68800101194 /* UUIDHelper.swift */; }; 187F4B282E04658A00EECC59 /* WidgetPanelStackView.m in Sources */ = {isa = PBXBuildFile; fileRef = 187F4B272E04658A00EECC59 /* WidgetPanelStackView.m */; }; 1888C1922E24C33100108730 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1888C1912E24C33100108730 /* AboutViewController.swift */; }; 1888C1932E24C41000108730 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1888C1902E24C2C800108730 /* AboutView.swift */; }; 188973D32E386A670025E48C /* widgetTemplatesIPhone.bin in Resources */ = {isa = PBXBuildFile; fileRef = 188973D22E386A670025E48C /* widgetTemplatesIPhone.bin */; }; + 18951D1D2EBB6D2300A86E12 /* TouchPadGestureHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18951D1C2EBB6D2300A86E12 /* TouchPadGestureHandler.swift */; }; 189AD29A2DE7446000E24B88 /* HostCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 189AD2992DE7446000E24B88 /* HostCollectionViewController.m */; }; 18E4BCEA2E891895000A8657 /* MotionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E4BCE92E891895000A8657 /* MotionHandler.swift */; }; 840751B32BF49C050096B657 /* CustomTapGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 840751B22BF49C050096B657 /* CustomTapGestureRecognizer.m */; }; @@ -167,6 +169,7 @@ 1829CE3E2E2154DF0074A99A /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; 18341A022DEB65AB0094E5F2 /* FixedTintImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FixedTintImageView.h; sourceTree = ""; }; 18341A032DEB662C0094E5F2 /* FixedTintImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FixedTintImageView.m; sourceTree = ""; }; + 18416EA32EBB3DCE00AE0124 /* UITouchUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITouchUtil.swift; sourceTree = ""; }; 1850C8292E667E4000845308 /* MicHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicHandler.swift; sourceTree = ""; }; 18729EDD2E8BD68800101194 /* UUIDHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UUIDHelper.swift; sourceTree = ""; }; 187F4B232E04633100EECC59 /* WidgetPanelStackView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WidgetPanelStackView.h; sourceTree = ""; }; @@ -174,6 +177,7 @@ 1888C1902E24C2C800108730 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 1888C1912E24C33100108730 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 188973D22E386A670025E48C /* widgetTemplatesIPhone.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = widgetTemplatesIPhone.bin; path = PresetConfigs/widgetTemplatesIPhone.bin; sourceTree = ""; }; + 18951D1C2EBB6D2300A86E12 /* TouchPadGestureHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchPadGestureHandler.swift; sourceTree = ""; }; 189AD2962DE743B700E24B88 /* HostCollectionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HostCollectionViewController.h; sourceTree = ""; }; 189AD2992DE7446000E24B88 /* HostCollectionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HostCollectionViewController.m; sourceTree = ""; }; 18BF141D2E0D7A00009DEE4F /* VoidLink v1.0.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "VoidLink v1.0.xcdatamodel"; sourceTree = ""; }; @@ -707,6 +711,8 @@ 9819CC21254F180F008A7C8E /* AbsoluteTouchHandler.m */, 988FCD3F293B091B003050E2 /* KeyboardInputField.h */, 988FCD40293B091B003050E2 /* KeyboardInputField.m */, + 18416EA32EBB3DCE00AE0124 /* UITouchUtil.swift */, + 18951D1C2EBB6D2300A86E12 /* TouchPadGestureHandler.swift */, 1850C8292E667E4000845308 /* MicHandler.swift */, 18E4BCE92E891895000A8657 /* MotionHandler.swift */, 84E1CDEB2C4F67D300547904 /* CommandManager.swift */, @@ -1011,6 +1017,7 @@ FB1A674D2131E65900507771 /* KeyboardSupport.m in Sources */, 97E20DDF2E2164A9000D5DCA /* Frame.m in Sources */, 97E20DE02E2164A9000D5DCA /* FrameQueue.m in Sources */, + 18951D1D2EBB6D2300A86E12 /* TouchPadGestureHandler.swift in Sources */, 97E20DE12E2164A9000D5DCA /* FloatBuffer.m in Sources */, 97E20DE22E2164A9000D5DCA /* PaddedLabel.m in Sources */, 97E20DE32E2164A9000D5DCA /* BandwidthTracker.m in Sources */, @@ -1100,6 +1107,7 @@ 84351B2B2C29AD6100A77989 /* LayoutOnScreenControlsViewController.m in Sources */, 84351B5C2C29ADBE00A77989 /* ProfileTableViewCell.m in Sources */, 18341A042DEB662C0094E5F2 /* FixedTintImageView.m in Sources */, + 18416EA42EBB3DCE00AE0124 /* UITouchUtil.swift in Sources */, FB89463119F646E200339C8A /* StreamManager.m in Sources */, 84351B2D2C29AD6100A77989 /* ToolBarContainerView.m in Sources */, 18E4BCEA2E891895000A8657 /* MotionHandler.swift in Sources */, diff --git a/VoidLink/Input/AbsoluteTouchHandler.m b/VoidLink/Input/AbsoluteTouchHandler.m index 75f1014d..475c5dc6 100644 --- a/VoidLink/Input/AbsoluteTouchHandler.m +++ b/VoidLink/Input/AbsoluteTouchHandler.m @@ -10,6 +10,7 @@ // #import "AbsoluteTouchHandler.h" +#import "VoidLink-Swift.h" #include @@ -50,6 +51,7 @@ bool _delayMouseLeftClick; bool dragButtonDown; + UInt8 currentTouchesCount; bool rightButtonClicked; } @@ -62,7 +64,7 @@ _delayMouseLeftClick = true; // deprecate legacy absolute touch dragButtonDown = false; - leftClickTimeThreshold = 0.15; + leftClickTimeThreshold = 0.1; // upper screen check _edgeTolerance = settings.edgeSlidingSensitivity.floatValue; @@ -77,6 +79,8 @@ - (void)onLongPressStart:(NSTimer*)timer { // Raise the left click and start a right click + if(currentTouchesCount>=2) return; + if([self touchDidntMoveOnScreen:movingTouchLocation]){ if(_delayMouseLeftClick){ LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT); @@ -137,6 +141,7 @@ lastTouchDownLocation = touchLocation; movingTouchLocation = touchLocation; touchBeganLocation = touchLocation; + currentTouchesCount = [UITouchUtil touchesIn:streamView from:event].count; } - (void)pauseLeftButtonDrag{ @@ -159,7 +164,17 @@ return; }*/ - if(![touches containsObject:capturedTouch]) return; + NSSet* currentTouches = [UITouchUtil touchesIn:streamView from:event]; + currentTouchesCount = currentTouches.count; + + if(currentTouchesCount == 2){ + if(_mouseButtonForCursorMove!=BUTTON_LEFT) LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, _mouseButtonForCursorMove); + [TouchPadGestureHandler handleGestureIn:streamView with:event]; + } + + if(currentTouchesCount>=2) return; + + if(![currentTouches containsObject:capturedTouch]) return; movingTouchLocation = [capturedTouch locationInView:streamView]; @@ -169,7 +184,9 @@ [longPressTimer invalidate]; longPressTimer = nil; - if(_delayMouseLeftClick && (CACurrentMediaTime()-touchBeganTimeStamp>leftClickTimeThreshold) && !dragButtonDown){ + NSTimeInterval dragDelay = _mouseButtonForCursorMove == BUTTON_LEFT ? leftClickTimeThreshold : 0; + + if(_delayMouseLeftClick && (CACurrentMediaTime()-touchBeganTimeStamp>dragDelay) && !dragButtonDown){ LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, _mouseButtonForCursorMove); dragButtonDown = true; } @@ -212,6 +229,8 @@ LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT); */ } + if(TouchPadGestureHandler.ctrlDown) LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[@"CTRL"].shortValue,KEY_ACTION_UP,0); + lastTouchUp = [touches anyObject]; lastTouchUpLocation = [lastTouchUp locationInView:streamView]; diff --git a/VoidLink/Input/CommandManager.swift b/VoidLink/Input/CommandManager.swift index 4de34fbc..1ba846cc 100644 --- a/VoidLink/Input/CommandManager.swift +++ b/VoidLink/Input/CommandManager.swift @@ -61,7 +61,9 @@ import UIKit "M_X1" : BUTTON_X1, "MX1" : BUTTON_X1, "M_X2" : BUTTON_X2, - "MX2" : BUTTON_X2 + "MX2" : BUTTON_X2, + "WHEELUP" : 0xFF, + "WHEELDOWN" : -0xFF, ] @@ -127,7 +129,7 @@ import UIKit // @objc public static let specialGameWidgets: [String] = ["YSRSV", "YSLT", "YSRT", "YSRB", "YSB", "YSRT2", "YSRB2", "YSB2", "YSEM", "YSML", "YSMR", "YSWASD"] - static let keyboardButtonMappings: [String: Int16] = [ + @objc public static let keyboardButtonMappings: [String: Int16] = [ // Windows Key Codes "NULL": 0xFF, "CTRL": 0x11, // VK_CONTROL diff --git a/VoidLink/Input/CustomTapGestureRecognizer.h b/VoidLink/Input/CustomTapGestureRecognizer.h index dbb88294..cbbb5674 100644 --- a/VoidLink/Input/CustomTapGestureRecognizer.h +++ b/VoidLink/Input/CustomTapGestureRecognizer.h @@ -14,7 +14,7 @@ CGFloat lowestTouchPointYCoord; } - +@property (nonatomic, weak) UIView* touchCapturingView; @property (nonatomic, assign) uint8_t numberOfTouchesRequired; @property (nonatomic, assign) bool immediateTriggering; // if enabled, trigger the signal on touchesBegan stage. @property (nonatomic, assign) double tapDownTimeThreshold; // tap down threshold in seconds. diff --git a/VoidLink/Input/CustomTapGestureRecognizer.m b/VoidLink/Input/CustomTapGestureRecognizer.m index 8dafa83e..2834c693 100644 --- a/VoidLink/Input/CustomTapGestureRecognizer.m +++ b/VoidLink/Input/CustomTapGestureRecognizer.m @@ -39,11 +39,18 @@ static CGFloat screenWidthInPoints; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // Check if the number of touches and taps meets the required criteria - if ([[event allTouches] count] == _numberOfTouchesRequired) { + NSSet* allTouches = [event allTouches]; + if (allTouches.count == _numberOfTouchesRequired) { + if([UITouchUtil touchesIn:_touchCapturingView from:event].count != allTouches.count){ + _gestureCaptured = false; + self.state = UIGestureRecognizerStateFailed; + return; + } + _gestureCapturedTime = CACurrentMediaTime(); _gestureCaptured = true; lowestTouchPointYCoord = 0; - for(UITouch *touch in [event allTouches]){ + for(UITouch *touch in allTouches){ if(lowestTouchPointYCoord < [touch locationInView:self.view].y) lowestTouchPointYCoord = [touch locationInView:self.view].y; // NSLog(@"lowest point coord: %f, %f", lowestTouchPointYCoord, CACurrentMediaTime()); } @@ -62,7 +69,7 @@ static CGFloat screenWidthInPoints; } self.state = UIGestureRecognizerStatePossible; } - if ([[event allTouches] count] > _numberOfTouchesRequired) { + if (allTouches.count > _numberOfTouchesRequired) { _gestureCaptured = false; self.state = UIGestureRecognizerStateFailed; } @@ -79,8 +86,7 @@ static CGFloat screenWidthInPoints; } else if(_gestureCaptured && allTouchesCount == [touches count] - && !_isOnScreenControllerBeingPressed - && (![self isOnScreenWidgetViewBeingPressed] || _numberOfTouchesRequired ==1)){ //must exclude virtual controller & onscreen button taps here to prevent stucked button, _areVirtualControllerTaps flag is set by onscreencontrols, containOnScreenButtonsTaps will be returned by iterating all widget views in streamframeview + && !_isOnScreenControllerBeingPressed){ //must exclude virtual controller & onscreen button taps here to prevent stucked button, _areVirtualControllerTaps flag is set by onscreencontrols, containOnScreenButtonsTaps will be returned by iterating all widget views in streamframeview _gestureCaptured = false; //reset for next recognition if((CACurrentMediaTime() - _gestureCapturedTime) < _tapDownTimeThreshold){ // lowestTouchPointYCoord = 0.0; //reset for next recognition @@ -91,26 +97,4 @@ static CGFloat screenWidthInPoints; if (allTouchesCount == [touches count]) _isOnScreenControllerBeingPressed = false; // need to reset this flag anyway, when all fingers are lefting } - -// this method will only be called when the recognizer is active & and the taps passes all the checks and is about to ge triggered. -- (bool)isOnScreenWidgetViewBeingPressed { - NSTimeInterval allFingersTapDownTime = CACurrentMediaTime() - _gestureCapturedTime; //RIGHTCLICK_TAP_DOWN_TIME_THRESHOLD_S has been passed to this recognizer as _tapDownTimeShreshold, we'll decide how to deal with pressed flag of on-screen widget views based on tapDownTime - bool gotOneWidgetPressed = false; - // the tap gestures are all added to the streamFrameTopLayerView, where all the OnScreenWidgetViews are added. so we can iterate them in this way: - for(UIView* view in self.view.subviews){ - if ([view isKindOfClass:[OnScreenWidgetView class]]) { - OnScreenWidgetView* widgetView = (OnScreenWidgetView*) view; - if(gotOneWidgetPressed && allFingersTapDownTime <= _tapDownTimeThreshold){ // once we have just 1 button pressed already,& the tapDownTime has not exceeded the threshold, we'll reset pressed flag for the all the widgetViews. - widgetView.pressedFlagForTapGesture = false; //reset the flag for widgetView - continue; - } - if(widgetView.pressedFlagForTapGesture){ - gotOneWidgetPressed = true; //got one button pressed - if(allFingersTapDownTime <= _tapDownTimeThreshold) widgetView.pressedFlagForTapGesture = false; // reset the flag for current widgetView if tapDowntime is still within the threshold, if not, leave the flag for mouse scroller gesture in relative touch mode. - } - } - } - return gotOneWidgetPressed; -} - @end diff --git a/VoidLink/Input/NativeTouchHandler.m b/VoidLink/Input/NativeTouchHandler.m index b7aaf31a..5548a1ae 100644 --- a/VoidLink/Input/NativeTouchHandler.m +++ b/VoidLink/Input/NativeTouchHandler.m @@ -213,7 +213,7 @@ if (asyncNativeTouch) dispatch_async(dispatch_get_global_queue(touchDownQos, 0), ^{ for (UITouch* touch in touches){ // 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; + if([OnScreenControls.touchesCapturedByOnScreenControls containsObject:touch]) continue; [self handleTouchDown:touch]; //generate & populate pointerId if(self->activateCoordSelector) [self populatePointerObjIntoDict:touch]; [self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_DOWN]; @@ -222,7 +222,7 @@ else{ for (UITouch* touch in touches){ // 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; + if([OnScreenControls.touchesCapturedByOnScreenControls containsObject:touch]) continue; [self handleTouchDown:touch]; //generate & populate pointerId if(self->activateCoordSelector) [self populatePointerObjIntoDict:touch]; [self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_DOWN]; @@ -233,7 +233,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 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([OnScreenControls.touchesCapturedByOnScreenControls containsObject:touch]) continue; if(self->activateCoordSelector) [self updatePointerObjInDict:touch]; [self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_MOVE]; [[self getPointerObjFromDict:touch] doesNeedResetCoords]; @@ -244,9 +244,8 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, moveEventIntervalNSec), dispatch_get_main_queue(), ^{ for (UITouch* touch in touches){ - NSNumber* touchAddrObj = @((uintptr_t)touch); - if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:touchAddrObj]){ - [OnScreenControls.touchAddrsCapturedByOnScreenControls removeObject:touchAddrObj]; + if([OnScreenControls.touchesCapturedByOnScreenControls containsObject:touch]){ + [OnScreenControls.touchesCapturedByOnScreenControls removeObject:touch]; continue; } [self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId diff --git a/VoidLink/Input/OnScreenControls.h b/VoidLink/Input/OnScreenControls.h index 5f9c1938..c759473c 100644 --- a/VoidLink/Input/OnScreenControls.h +++ b/VoidLink/Input/OnScreenControls.h @@ -110,7 +110,7 @@ typedef NS_ENUM(NSInteger, OnScreenControlsLevel) { -+ (NSMutableSet* )touchAddrsCapturedByOnScreenControls; ++ (NSMutableSet* )touchesCapturedByOnScreenControls; - (void) sendRightStickTouchPadEvent:(CGFloat) stickX : (CGFloat) stickY; - (void) clearRightStickTouchPadFlag; - (void) sendLeftStickTouchPadEvent:(CGFloat) stickX : (CGFloat) stickY; diff --git a/VoidLink/Input/OnScreenControls.m b/VoidLink/Input/OnScreenControls.m index 4f0034a4..94ba4092 100644 --- a/VoidLink/Input/OnScreenControls.m +++ b/VoidLink/Input/OnScreenControls.m @@ -23,7 +23,7 @@ #define UPDATE_BUTTON(x, y) (buttonFlags = \ (y) ? (buttonFlags | (x)) : (buttonFlags & ~(x))) -static NSMutableSet* touchAddrsCapturedByOnScreenControls; +static NSMutableSet* touchesCapturedByOnScreenControls; static CGRect standardRoundButtonBounds; static CGRect standardRectangleButtonBounds; static CGRect standardStickBounds; @@ -147,8 +147,8 @@ static float L2_Y; static float L3_X; static float L3_Y; -+ (NSMutableSet* )touchAddrsCapturedByOnScreenControls{ - return touchAddrsCapturedByOnScreenControls; ++ (NSMutableSet* )touchesCapturedByOnScreenControls{ + return touchesCapturedByOnScreenControls; } - (void) sendRightStickTouchPadEvent:(CGFloat) stickX : (CGFloat) stickY{ @@ -316,7 +316,7 @@ static float L3_Y; _standardLeftRightButtonBounds = standardLeftRightButtonBounds = CGRectMake(0, 0, [UIImage imageNamed:@"LeftButton"].size.width, [UIImage imageNamed:@"LeftButton"].size.height); _activeCustomOscButtonPositionDict = [[NSMutableDictionary alloc] init]; - touchAddrsCapturedByOnScreenControls = [[NSMutableSet alloc] init]; + touchesCapturedByOnScreenControls = [[NSMutableSet alloc] init]; return self; } @@ -1413,12 +1413,12 @@ static float L3_Y; touchEventCapturedByOsc = true; } // additionally, populate the touchesCapturedByOSButton set, for native/relative touch handler to deal with. - if(touchEventCapturedByOsc) [touchAddrsCapturedByOnScreenControls addObject:@((uintptr_t)touch)]; + if(touchEventCapturedByOsc) [touchesCapturedByOnScreenControls addObject:touch]; } if (updated) { [_controllerSupport updateFinished:_controller]; } - // NSLog(@"captured by OSB touches, OSC Class: %d", (uint32_t)[touchAddrsCapturedByOnScreenControls count]); + // NSLog(@"captured by OSB touches, OSC Class: %d", (uint32_t)[touchesCapturedByOnScreenControls count]); bool oscTouched = updated || stickTouch; @@ -1442,7 +1442,7 @@ static float L3_Y; for (UITouch* touch in touches) { // remove the touch obj from touchesCapturedByOnScreenButtons - // if([touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) [touchAddrsCapturedByOnScreenControls removeObject:@((uintptr_t)touch)]; + // if([touchesCapturedByOnScreenControls containsObject:touch]) [touchesCapturedByOnScreenControls removeObject:touch)]; if (touch == _aTouch) { [_controllerSupport clearButtonFlag:_controller flags:A_FLAG]; diff --git a/VoidLink/Input/OnScreenWidgetView.swift b/VoidLink/Input/OnScreenWidgetView.swift index 92c7bf31..19ff9521 100644 --- a/VoidLink/Input/OnScreenWidgetView.swift +++ b/VoidLink/Input/OnScreenWidgetView.swift @@ -60,7 +60,6 @@ import UIKit @objc public var comboButtonStrings: [String] = [] private var comboKeyTimeIntervalMs: UInt32 = 0 - @objc public var pressedFlagForTapGesture: Bool @objc public var logicallyDown: Bool = false @objc public var widthFactor: CGFloat = 1.0 @objc public var heightFactor: CGFloat = 1.0 @@ -287,7 +286,6 @@ import UIKit self.shape = shape self.label = UILabel() // self.originalBackgroundColor = UIColor(white: 0.2, alpha: 0.7) - self.pressedFlagForTapGesture = false // self.widthFactor = 1.0 // self.heightFactor = 1.0 // self.backgroundAlpha = 0.5 @@ -740,7 +738,7 @@ import UIKit stickMarkerRelativeLocation = CGPointMake(touchBeganLocation.x, touchBeganLocation.y) } - showStickBall(at: stickMarkerRelativeLocation) + getStickBallReady(at: stickMarkerRelativeLocation) showCrossMark(at: stickMarkerRelativeLocation) } @@ -810,7 +808,7 @@ import UIKit return stickBallLayer } - private func showStickBall(at point: CGPoint) { + private func getStickBallReady(at point: CGPoint) { // let path = UIBezierPath(arcCenter: center, radius: 8, startAngle: 0, endAngle: 2 * .pi, clockwise: true) CATransaction.begin() @@ -820,7 +818,7 @@ import UIKit // self.stickBallLayer.path = path.cgPath // Assign the circular path to the shape layer // self.stickBallLayer.position = CGPointMake(CGRectGetMidX(self.crossMarkLayer.frame), CGRectGetMidY(self.crossMarkLayer.frame)) self.lrudIndicatorBall.position = point - self.lrudIndicatorBall.isHidden = false + self.lrudIndicatorBall.isHidden = true CATransaction.commit() } @@ -831,14 +829,18 @@ import UIKit CATransaction.setDisableActions(true) self.lrudIndicatorBall.removeAllAnimations() if !OnScreenWidgetView.editMode { - let realOffsetX = touchInputToStickBallCoord(input: offSetX*sensitivityFactorX) - let realOffsetY = touchInputToStickBallCoord(input: offSetY*sensitivityFactorY) - self.lrudIndicatorBall.position = CGPointMake(touchBeganLocation.x + realOffsetX, touchBeganLocation.y + realOffsetY - self.stickIndicatorOffset) + if self.firstTouchMoved { + if self.lrudIndicatorBall.isHidden {self.lrudIndicatorBall.isHidden = false} + let realOffsetX = touchInputToStickBallCoord(input: offSetX*sensitivityFactorX) + let realOffsetY = touchInputToStickBallCoord(input: offSetY*sensitivityFactorY) + self.lrudIndicatorBall.position = CGPointMake(touchBeganLocation.x + realOffsetX, touchBeganLocation.y + realOffsetY - self.stickIndicatorOffset) + } /* if fabs(realOffsetX) == stickBallMaxOffset || fabs(realOffsetY) == stickBallMaxOffset {handleStickBallReachingBorder()} else{handleStickBallLeavingBorder()}*/ } else{ + if self.lrudIndicatorBall.isHidden {self.lrudIndicatorBall.isHidden = false} self.lrudIndicatorBall.position = CGPointMake(CGRectGetMidX(self.crossMarkLayer.frame), CGRectGetMidY(self.crossMarkLayer.frame)-stickIndicatorOffset) } CATransaction.commit() @@ -1096,7 +1098,7 @@ import UIKit private func startTrackballMomentum() { stopTrackballMomentum() - trackballDecelerationTimer = Timer.scheduledTimer(withTimeInterval: 1/60, repeats: true) { [weak self] _ in + trackballDecelerationTimer = Timer.scheduledTimer(withTimeInterval: 1/Double(UIScreen.main.maximumFramesPerSecond), repeats: true) { [weak self] _ in guard let self = self else { return } LiSendMouseMoveEvent( @@ -1356,7 +1358,11 @@ import UIKit LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[comboString]!,Int8(KEY_ACTION_DOWN), 0) } if CommandManager.mouseButtonMappings.keys.contains(comboString), self.functionalButtonString != "ABSTCHDRAG" { - LiSendMouseButtonEvent(CChar(BUTTON_ACTION_PRESS), Int32(CommandManager.mouseButtonMappings[comboString]!)) + let button = Int32(CommandManager.mouseButtonMappings[comboString]!) + if abs(button) == 0xFF { + LiSendScrollEvent(button>0 ? 3 : -3) + } + else {LiSendMouseButtonEvent(CChar(BUTTON_ACTION_PRESS), button)} } if comboString != comboStrings.last { usleep(self.comboKeyTimeIntervalMs*1000) // delay xxx ms @@ -1388,7 +1394,7 @@ import UIKit // Touch event handling private func getAllSpawnedTouchesCount(with event: UIEvent?)->Int{ - return event?.allTouches?.filter({ $0.view == self }).count ?? 0 + return UITouchUtil.touches(in: self, from: event).count } override func touchesBegan(_ touches: Set, with event: UIEvent?) { @@ -1426,8 +1432,6 @@ import UIKit self.twoTouchesDetected = true } - self.pressedFlagForTapGesture = true - if !OnScreenWidgetView.editMode { if self.widgetType == WidgetTypeEnum.touchPad && touches.count == 1{ // don't use event?.allTouches?.count here, it will counts all touches including the ones captured by other UIViews switch self.touchPadString { @@ -1667,46 +1671,52 @@ import UIKit return distance <= threshold } - private func updateTouchLocation (touch: UITouch) { + private func getVector (touch: UITouch) { let currentTouchLocation: CGPoint = (touch.location(in: self)) if !self.mousePointerMoved, !self.isAdjacentPoints(currentTouchLocation, from: latestTouchLocation, tolerance: 2.0){ self.mousePointerMoved = true } - - if !firstTouchMoved, !self.isAdjacentPoints(currentTouchLocation, from: latestTouchLocation, tolerance: 0.5) { + + self.deltaX = UITouchUtil.getDeltaX(from: touch, in: self) + self.deltaY = UITouchUtil.getDeltaY(from: touch, in: self) + self.offSetX = currentTouchLocation.x - self.touchBeganLocation.x + self.offSetY = currentTouchLocation.y - self.touchBeganLocation.y + } + + private func updateTouchLocation(_ touches: Set){ + let currentTouchLocation = touches.first!.location(in: self) + //if !firstTouchMoved, !self.isAdjacentPoints(currentTouchLocation, from: touchBeganLocation, tolerance: 0.5) { + if !firstTouchMoved, !self.isAdjacentPoints(currentTouchLocation, from: touchBeganLocation, tolerance: 5) { // First move event self.latestTouchLocation = currentTouchLocation self.firstTouchMoved = true } - - self.deltaX = currentTouchLocation.x - self.latestTouchLocation.x - self.deltaY = currentTouchLocation.y - self.latestTouchLocation.y - self.offSetX = currentTouchLocation.x - self.touchBeganLocation.x - self.offSetY = currentTouchLocation.y - self.touchBeganLocation.y - self.latestTouchLocation = currentTouchLocation } private func handleTouchPadMoveEvent (_ touches: Set, with event: UIEvent?){ if touches.count == 1{ // don't use event.alltouches.count here, it will counts all touches + self.getVector(touch: touches.first!) switch self.touchPadString{ case "MOUSEPAD": DispatchQueue.global(qos: .userInteractive).async { - self.updateTouchLocation(touch: touches.first!) - LiSendMouseMoveEvent(Int16(truncatingIfNeeded: Int(self.deltaX * 1.7 * self.sensitivityFactorX)), Int16(truncatingIfNeeded: Int(self.deltaY * 1.7 * self.sensitivityFactorY))) + if self.firstTouchMoved {LiSendMouseMoveEvent(Int16(self.deltaX * 1.7 * self.sensitivityFactorX), Int16(self.deltaY * 1.7 * self.sensitivityFactorY))} + self.updateTouchLocation(touches) } break case "TRACKBALL": DispatchQueue.global(qos: .userInteractive).async { - self.updateTouchLocation(touch: touches.first!) - LiSendMouseMoveEvent(Int16(truncatingIfNeeded: Int(self.deltaX * 1.7 * self.sensitivityFactorX)), Int16(truncatingIfNeeded: Int(self.deltaY * 1.7 * self.sensitivityFactorY))) - self.trackballVelocity = CGPoint(x: self.deltaX * 1.7 * self.sensitivityFactorX, y: self.deltaY * 1.7 * self.sensitivityFactorY) - self.stopTrackballMomentum() + if self.firstTouchMoved { + LiSendMouseMoveEvent(Int16(self.deltaX * 1.7 * self.sensitivityFactorX), Int16(self.deltaY * 1.7 * self.sensitivityFactorY)) + self.trackballVelocity = CGPoint(x: self.deltaX * 1.7 * self.sensitivityFactorX, y: self.deltaY * 1.7 * self.sensitivityFactorY) + self.stopTrackballMomentum() + } + self.updateTouchLocation(touches) } break case "ABSMOUSE": - self.updateTouchLocation(touch: touches.first!) - if !self.absMousePaused { + if self.firstTouchMoved { + if !self.absMousePaused { let reachedEdgeMask = LiSendMouseMoveAsMousePositionEvent(Int16(truncatingIfNeeded: Int(self.deltaX * 1.7 * self.sensitivityFactorX)), Int16(truncatingIfNeeded: Int(self.deltaY * 1.7 * self.sensitivityFactorY)), Int16(self.superViewWidth), Int16(self.superViewHeight)) if reachedEdgeMask>0 { self.handleMousePadButtonActionUp() @@ -1718,54 +1728,60 @@ import UIKit } } } + } + self.updateTouchLocation(touches) break case "LSPAD": - self.updateTouchLocation(touch: touches.first!) DispatchQueue.global(qos: .userInteractive).async { self.sendLeftStickTouchPadEvent(inputX: self.offSetX * self.sensitivityFactorX, inputY: self.offSetY * self.sensitivityFactorY) } if widgetType == WidgetTypeEnum.touchPad {updateStickIndicator()} + self.updateTouchLocation(touches) case "RSPAD": - self.updateTouchLocation(touch: touches.first!) DispatchQueue.global(qos: .userInteractive).async { self.sendRightStickTouchPadEvent(inputX: self.offSetX * self.sensitivityFactorX, inputY: self.offSetY * self.sensitivityFactorY) } if widgetType == WidgetTypeEnum.touchPad {updateStickIndicator()} + self.updateTouchLocation(touches) case "LSVPAD": DispatchQueue.global(qos: .userInteractive).async { - self.updateTouchLocation(touch: touches.first!) - self.sendLeftStickTouchPadEvent(inputX: self.deltaX*1.5167*self.sensitivityFactorX, inputY: self.deltaY*1.5167*self.sensitivityFactorY) + if self.firstTouchMoved { + self.sendLeftStickTouchPadEvent(inputX: self.deltaX*1.5167*self.sensitivityFactorX, inputY: self.deltaY*1.5167*self.sensitivityFactorY) + } + self.updateTouchLocation(touches) } case "RSVPAD": DispatchQueue.global(qos: .userInteractive).async { - self.updateTouchLocation(touch: touches.first!) - self.sendRightStickTouchPadEvent(inputX: self.deltaX*1.5167*self.sensitivityFactorX, inputY: self.deltaY*1.5167*self.sensitivityFactorY) + if self.firstTouchMoved { + self.sendRightStickTouchPadEvent(inputX: self.deltaX*1.5167*self.sensitivityFactorX, inputY: self.deltaY*1.5167*self.sensitivityFactorY) + } + self.updateTouchLocation(touches) } case "LTPAD": DispatchQueue.global(qos: .userInteractive).async { - self.updateTouchLocation(touch: touches.first!) self.sendLeftTriggerTouchPadEvent(inputY: (-self.offSetY*4.5*self.sensitivityFactorY)) + self.updateTouchLocation(touches) } case "RTPAD": DispatchQueue.global(qos: .userInteractive).async { - self.updateTouchLocation(touch: touches.first!) self.sendRightTriggerTouchPadEvent(inputY: (-self.offSetY*4.5*self.sensitivityFactorY)) + self.updateTouchLocation(touches) } - break case "DPAD", "WASDPAD", "ARROWPAD": - self.updateTouchLocation(touch: touches.first!) handleLrudTouchMove() + self.updateTouchLocation(touches) case "MOUSEWHEEL","WHEEL": - self.updateTouchLocation(touch: touches.first!) if firstTouchMoved {LiSendHighResScrollEvent(Int16(self.deltaY*7.5*self.sensitivityFactorY))} + self.updateTouchLocation(touches) case "DISCRETEWHEEL", "DSWHEEL": - self.updateTouchLocation(touch: touches.first!) + let currentLocation = touches.first!.location(in: self) tickFlag = (tickFlag+1)%UInt8(CGFloat(tickCycle)/abs(sensitivityFactorY)) - var delta = 0.0 - if self.deltaY==0 {delta=self.latestTouchLocation.y-self.touchBeganLocation.y} - else {delta = self.deltaY} + var delta = self.deltaY + if delta==0 {delta=self.touchBeganLocation.y-currentLocation.y} delta = delta * CGFloat(copysign(1.0, sensitivityFactorY)) - if(tickFlag == 1) {LiSendScrollEvent(delta > 0 ? -3 : 3)} + if tickFlag == 1, delta != 0 {LiSendScrollEvent(delta > 0 ? -3 : 3)} + // self.latestTouchLocation = currentLocation + self.updateTouchLocation(touches) default: break } @@ -1957,6 +1973,7 @@ import UIKit // touches.count == allCapturedTouchesCount means allfingers are lifting if(self.mouseButtonAction == MouseButtonAction.hovering) {self.sendMouseRightButtonClickEvent()} twoTouchesDetected = false + firstTouchMoved = false } // then other types of pads or buttons with touchPad function diff --git a/VoidLink/Input/RelativeTouchHandler.m b/VoidLink/Input/RelativeTouchHandler.m index 2e76e830..68098dac 100644 --- a/VoidLink/Input/RelativeTouchHandler.m +++ b/VoidLink/Input/RelativeTouchHandler.m @@ -23,7 +23,6 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2; BOOL firstTouchMoved; BOOL mousePointerMoved; BOOL quickTapDetected; - BOOL isInMouseWheelScrollingMode; // upper screen edge check bool touchPointSpawnedAtUpperScreenEdge; @@ -55,8 +54,8 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2; _mouseRightClickTapRecognizer.delaysTouchesBegan = NO; _mouseRightClickTapRecognizer.delaysTouchesEnded = NO; [self->streamView.streamFrameTopLayerView addGestureRecognizer:_mouseRightClickTapRecognizer]; // add all additional gestures to the streamFrameTopLayerView instead of the streamview. + _mouseRightClickTapRecognizer.touchCapturingView = streamView; - isInMouseWheelScrollingMode = false; firstTouchMoved = false; mousePointerMoved = false; mousePointerTimestamp = 0; @@ -86,35 +85,11 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2; - (bool)isOnScreenControllerBeingPressed:(NSSet* )touches{ for(UITouch* touch in touches){ - if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) return true; + if([OnScreenControls.touchesCapturedByOnScreenControls containsObject:touch]) return true; } return false; } - -- (bool)isOnScreenWidgetViewBeingPressed { - bool gotOneButtonPressed = false; - for(UIView* view in self->streamView.superview.subviews){ // iterates all on-screen widget views in StreamFrameView - if ([view isKindOfClass:[OnScreenWidgetView class]]) { - OnScreenWidgetView* widgetView = (OnScreenWidgetView*) view; - if(widgetView.pressedFlagForTapGesture){ - gotOneButtonPressed = true; //got one button pressed - } - } - } - return gotOneButtonPressed; -} - -- (void)resetAllPressedFlagsForOnScreenWidgetViews { - for(UIView* view in self->streamView.superview.subviews){ // iterates all on-screen widget views in StreamFrameView - if ([view isKindOfClass:[OnScreenWidgetView class]]) { - OnScreenWidgetView* widgetView = (OnScreenWidgetView*) view; - widgetView.pressedFlagForTapGesture = false; - } - } -} - - - (void)mouseRightClick { dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)); dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ @@ -153,23 +128,13 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2; } touchPointSpawnedAtUpperScreenEdge = false; // reset this flag immediately if we get a touch event passing the check above, this fixes irresponsive touch after closing the command tool menu. - - if([[event allTouches] count] == 2 && ![self isOnScreenWidgetViewBeingPressed] && ![self isOnScreenControllerBeingPressed:[event allTouches]]){ - NSLog(@"get in scrolling mode"); - isInMouseWheelScrollingMode = true; - return; // if we got 2 touches on the blank area, it's gonna be a mouse scroll touch, and must prevent UITtouch object for mouse pointer being captured & locked - } - - // NSLog(@"touches count in began stage: %llu", (uint64_t)[touches count]); - + UITouch* candidateTouch = nil; // the onscreen controllers are implmented by CALayer, which can not intercept UITouch event, touch will penetrate to the streamView level and captured in the touches callback of this touchHandler class. // the onscreen button are UIViews, they intercept UITouch events, so we don't need to worry about them. for(UITouch* touch in touches){ - // NSLog(@"candidate touch test: %llu", (uint64_t)touch); - if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]){ - // NSLog(@"%f controller tap detected", CACurrentMediaTime()); + if([OnScreenControls.touchesCapturedByOnScreenControls containsObject:touch]){ continue; } else candidateTouch = touch; @@ -185,7 +150,7 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2; // LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT); // do not press down mouse button here, or it wiil easily turn to double click on remote PC } - // we must use [event allTouches] to check if touchLockedForMouseMove is captured, because the UITouch object could be captured by upper layer of UIView(in cases like tap gestures), not passed to the touches callbacks in this class, but still available in [event allTouches] + // use [event allTouches] to check if touchLockedForMouseMove is captured, if already captured, don't update touchLockedForMouseMove if(candidateTouch != nil && ![[event allTouches] containsObject:touchLockedForMouseMove]){ touchLockedForMouseMove = candidateTouch; // NSLog(@"Candidate touch for mouse movement locked"); @@ -197,7 +162,7 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { //NSLog(@"%f, touchesMoved callback, is scrolling: %d, touches count: %d", CACurrentMediaTime(), isInMouseWheelScrollingMode, (uint32_t)[touches count]); - + /* if(isInMouseWheelScrollingMode && [[event allTouches] count] == 2 && ![self isOnScreenWidgetViewBeingPressed] && ![self isOnScreenControllerBeingPressed:touches]){ NSSet* twoTouches = [event allTouches]; CGPoint firstLocation = [[[twoTouches allObjects] objectAtIndex:0] locationInView:streamView]; @@ -211,8 +176,14 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2; twoFingerTouchLocation = avgLocation; return; } - - // NSLog(@"%f touchesMoved callback, locked touch: %llu", CACurrentMediaTime(), (uintptr_t)touchLockedForMouseMove); + */ + + NSSet* currentTouches = [UITouchUtil touchesIn:streamView from:event]; + uint8_t currentTouchesCount = currentTouches.count; + + if(![self isOnScreenControllerBeingPressed:currentTouches]) [TouchPadGestureHandler handleGestureIn:streamView with:event]; + + if(currentTouchesCount>=2) return; if([touches containsObject:touchLockedForMouseMove]){ CGPoint currentLocation = [touchLockedForMouseMove locationInView:streamView]; @@ -225,10 +196,6 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - if(isInMouseWheelScrollingMode){ - isInMouseWheelScrollingMode = false; - return; - } if([touches containsObject:touchLockedForMouseMove]){ // dealing with a single first tap, whether the button will be released, is going to be decided in sendLongMouseLeftButtonClickEvent @@ -245,48 +212,34 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2; mousePointerMoved = false; } - if([[event allTouches] count] == [touches count]){ - isInMouseWheelScrollingMode = false; + for(UITouch* touch in touches){ + [OnScreenControls.touchesCapturedByOnScreenControls removeObject:touch]; + } + + if(TouchPadGestureHandler.ctrlDown) LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[@"CTRL"].shortValue,KEY_ACTION_UP,0); + + if([UITouchUtil touchesIn:streamView from:event].count == [touches count]){ touchLockedForMouseMove = nil; mousePointerMoved = false; // need to reset this anyway - [self resetAllPressedFlagsForOnScreenWidgetViews]; // reset all pressed flag for on-screen widget views after all fingers lifted from screen. } touchPointSpawnedAtUpperScreenEdge = false; } -- (void)displayLinkCallback:(CADisplayLink *)link { - dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ - if(self->touchLockedForMouseMove && !self->mousePointerMoved){ - CGPoint testpoint = [self->touchLockedForMouseMove locationInView:self->streamView]; - NSLog(@"displayLinkCallback %f: %f, %f", CACurrentMediaTime(), testpoint.x, testpoint.y); - //[self sendMouseMoveEvent:touchLockedForMouseMove]; - } - }); -} - - (void)sendMouseMoveEvent:(CGPoint)currentLocation{ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ + int deltaX = ([UITouchUtil getDeltaXFrom:self->touchLockedForMouseMove in:self->streamView]) * 1.35 * self->currentSettings.mousePointerVelocityFactor.floatValue; + int deltaY = ([UITouchUtil getDeltaYFrom:self->touchLockedForMouseMove in:self->streamView]) * 1.35 * self->currentSettings.mousePointerVelocityFactor.floatValue; - bool isAdjacentPoints = [self isAdjacentPoints:self->initialMousePointerLocation from:currentLocation tolerance:0.5]; + if(self->touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC. + if(self->firstTouchMoved) LiSendMouseMoveEvent(deltaX, deltaY); + + bool isAdjacentPoints = [self isAdjacentPoints:self->initialMousePointerLocation from:currentLocation tolerance:5]; if (!self->firstTouchMoved && !isAdjacentPoints) { self->latestMousePointerLocation = currentLocation; self->firstTouchMoved = true; } - - if (self->latestMousePointerLocation.x != currentLocation.x || - self->latestMousePointerLocation.y != currentLocation.y) - { - int deltaX = (currentLocation.x - self->latestMousePointerLocation.x) * 1.35 * self->currentSettings.mousePointerVelocityFactor.floatValue; - int deltaY = (currentLocation.y - self->latestMousePointerLocation.y) * 1.35 * self->currentSettings.mousePointerVelocityFactor.floatValue; - - if (deltaX != 0 || deltaY != 0) { - self->latestMousePointerLocation = currentLocation; - if(self->touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC. - if(self->firstTouchMoved) LiSendMouseMoveEvent(deltaX, deltaY); - } - } }); } diff --git a/VoidLink/Input/StreamView.m b/VoidLink/Input/StreamView.m index 818f5825..a72365d6 100644 --- a/VoidLink/Input/StreamView.m +++ b/VoidLink/Input/StreamView.m @@ -170,7 +170,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; [_onScreenControls setLevel:OnScreenControlsLevelOff]; //pass touchesCaptureByOnScreenButtons Set to the native touchhandler, this NSSet is init witihin onscreencontrols class, don't do it again in native touch handler class - [OnScreenControls.touchAddrsCapturedByOnScreenControls removeAllObjects]; // reset the attribute to nil + [OnScreenControls.touchesCapturedByOnScreenControls removeAllObjects]; // reset the attribute to nil /* if(settings.touchMode.intValue == NativeTouch){ @@ -237,6 +237,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; keyboardToggleRecognizer.delaysTouchesBegan = NO; keyboardToggleRecognizer.delaysTouchesEnded = NO; [self->_streamFrameTopLayerView addGestureRecognizer:keyboardToggleRecognizer]; + keyboardToggleRecognizer.touchCapturingView = self; } - (void)keyboardWillShow:(NSNotification *)notification{ diff --git a/VoidLink/Input/TouchPadGestureHandler.swift b/VoidLink/Input/TouchPadGestureHandler.swift new file mode 100644 index 00000000..c650aa56 --- /dev/null +++ b/VoidLink/Input/TouchPadGestureHandler.swift @@ -0,0 +1,42 @@ +// +// TouchPadGestureHandler.swift +// VoidLink +// +// Created by True砖家 on 2025/11/5. +// Copyright © 2025 True砖家 on Bilibili. All rights reserved. +// + +import UIKit + +@objc class TouchPadGestureHandler: NSObject { + + @objc public static var ctrlDown:Bool = false + @objc public static func handleGesture(in view: UIView, with event: UIEvent) { + let currentTouches = UITouchUtil.touches(in: view, from: event) + guard currentTouches.count == 2 else { return } + + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_RELEASE), BUTTON_LEFT) + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_RELEASE), BUTTON_RIGHT) + + guard let touch1 = currentTouches.first else { return } + var mutable = Array(currentTouches) + mutable.removeAll { $0 == touch1 } + guard let touch2 = mutable.first else { return } + + let currentDistance = UITouchUtil.distance(between: touch1, and: touch2, in: view) + let previousDistance = UITouchUtil.previousDistance(between: touch1, and: touch2, in: view) + let midPointDeltaY = UITouchUtil.midPointDeltaY(between: touch1, and: touch2, in: view) + let midPointDeltaX = UITouchUtil.midPointDeltaX(between: touch1, and: touch2, in: view) + + LiSendHighResScrollEvent(Int16((currentDistance - previousDistance) * 5 + midPointDeltaY * 5)) + LiSendHighResHScrollEvent(Int16(-midPointDeltaX) * 5) + + if abs(currentDistance - previousDistance) > abs(midPointDeltaY) { + LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["CTRL"]!, CChar(KEY_ACTION_DOWN), 0) + ctrlDown = true + } else { + LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["CTRL"]!, CChar(KEY_ACTION_UP), 0) + ctrlDown = false + } + } +} diff --git a/VoidLink/Input/UITouchUtil.swift b/VoidLink/Input/UITouchUtil.swift new file mode 100644 index 00000000..2d555cbe --- /dev/null +++ b/VoidLink/Input/UITouchUtil.swift @@ -0,0 +1,51 @@ +// +// UITouchUtil.swift +// VoidLink +// +// Created by True砖家 on 2025/11/5. +// Copyright © 2025 True砖家 on Bilibili. All rights reserved. +// + +import UIKit + +@objc class UITouchUtil: NSObject { + @objc public static func touches(in view: UIView, from event: UIEvent?) -> Set { + guard let allTouches = event?.allTouches else { return [] } + return event?.allTouches?.filter({ $0.view == view }) ?? [] + } + + @objc public static func distance(between touch1: UITouch?, and touch2: UITouch?, in view: UIView?) -> CGFloat { + guard let view = view, let touch1 = touch1, let touch2 = touch2 else { return 0 } + let p1 = touch1.location(in: view) + let p2 = touch2.location(in: view) + return hypot(p1.x - p2.x, p1.y - p2.y) + } + + @objc public static func previousDistance(between touch1: UITouch?, and touch2: UITouch?, in view: UIView?) -> CGFloat { + guard let view = view, let touch1 = touch1, let touch2 = touch2 else { return 0 } + let p1 = touch1.previousLocation(in: view) + let p2 = touch2.previousLocation(in: view) + return hypot(p1.x - p2.x, p1.y - p2.y) + } + + @objc public static func getDeltaX(from touch: UITouch?, in view: UIView?) -> CGFloat { + guard let view = view, let touch = touch else { return 0 } + return touch.location(in: view).x - touch.previousLocation(in: view).x + } + + @objc public static func getDeltaY(from touch: UITouch?, in view: UIView?) -> CGFloat { + guard let view = view, let touch = touch else { return 0 } + return touch.location(in: view).y - touch.previousLocation(in: view).y + } + + @objc public static func midPointDeltaX(between touch1: UITouch?, and touch2: UITouch?, in view: UIView?) -> CGFloat { + guard let view = view, let touch1 = touch1, let touch2 = touch2 else { return 0 } + return (touch1.location(in: view).x+touch2.location(in: view).x)/2 - (touch1.previousLocation(in: view).x+touch2.previousLocation(in: view).x)/2 + } + + @objc public static func midPointDeltaY(between touch1: UITouch?, and touch2: UITouch?, in view: UIView?) -> CGFloat { + guard let view = view, let touch1 = touch1, let touch2 = touch2 else { return 0 } + return (touch1.location(in: view).y+touch2.location(in: view).y)/2 - (touch1.previousLocation(in: view).y+touch2.previousLocation(in: view).y)/2 + } + +} diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 51917f3a..3936fa23 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -716,7 +716,7 @@ BOOL isCustomResolution(int resolutionSelected) { [self addSetting:self.pointerVelocityFactorStack ofId:@"pointerVelocityFactorStack" withInfoTag:YES withDynamicLabel:YES to:touchAndControlSection]; [self addSetting:self.mousePointerVelocityStack ofId:@"mousePointerVelocityStack" withInfoTag:NO withDynamicLabel:YES to:touchAndControlSection]; [self addSetting:self.singleTapSensitivityStack ofId:@"singleTapSensitivityStack" withInfoTag:NO withDynamicLabel:YES to:touchAndControlSection]; - [self addSetting:self.delayLeftClickStack ofId:@"delayLeftClickStack" withInfoTag:YES withDynamicLabel:NO to:touchAndControlSection]; + // [self addSetting:self.delayLeftClickStack ofId:@"delayLeftClickStack" withInfoTag:YES withDynamicLabel:NO 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.swapAbxyStack ofId:@"swapAbaxyStack" withInfoTag:NO withDynamicLabel:NO to:touchAndControlSection]; @@ -2879,7 +2879,8 @@ BOOL isCustomResolution(int resolutionSelected) { NSInteger hapticEngine = self.hapticEngineSelector.selectedSegmentIndex; CGFloat edgeSlidingSensitivity = self.edgeSlidingSensitivitySlider.value; NSInteger audioEngine = self.audioEngineSelector.selectedSegmentIndex; - BOOL delayLeftClick = self.delayLeftClickSwitch.isOn; + // BOOL delayLeftClick = self.delayLeftClickSwitch.isOn; + BOOL delayLeftClick = true; BOOL duckOtherApps = self.duckOtherAppSwitch.isOn; BOOL muteInBackground = self.muteInBackgroundSwitch.isOn; NSInteger backgroundSessionTimer = self.backgroundSessionTimerSlider.value == self.backgroundSessionTimerSlider.maximumValue ? (uint32_t) INT16_MAX : (uint32_t)self.backgroundSessionTimerSlider.value; diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index 3bd87401..b8a8ebd1 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -304,7 +304,8 @@ _oscLayoutTapRecoginizer.delaysTouchesBegan = NO; _oscLayoutTapRecoginizer.delaysTouchesEnded = NO; if(_settings.touchMode.intValue == AbsoluteTouch) _oscLayoutTapRecoginizer.immediateTriggering = true; // make immediate triggering on for absolute touch mode - [self.view addGestureRecognizer:_oscLayoutTapRecoginizer]; // + [self.view addGestureRecognizer:_oscLayoutTapRecoginizer]; + _oscLayoutTapRecoginizer.touchCapturingView = _streamView; } } @@ -325,7 +326,7 @@ [_scrollView setShowsHorizontalScrollIndicator:NO]; [_scrollView setShowsVerticalScrollIndicator:NO]; [_scrollView setDelegate:self]; - [_scrollView setMaximumZoomScale:10.0f]; + [_scrollView setMaximumZoomScale:1.0f]; if(!_mainFrameViewcontroller.settingsExpandedInStreamView){ // Add StreamView inside a UIScrollView for absolute mode [_scrollView addSubview:_streamView];