diff --git a/KVMConsoleiPad/Input/PointerCaptureView.swift b/KVMConsoleiPad/Input/PointerCaptureView.swift index 998ca6f..dbb2e26 100644 --- a/KVMConsoleiPad/Input/PointerCaptureView.swift +++ b/KVMConsoleiPad/Input/PointerCaptureView.swift @@ -79,6 +79,11 @@ final class PointerCaptureUIView: UIView, UIGestureRecognizerDelegate { let secondaryTap = UITapGestureRecognizer(target: self, action: #selector(handleSecondaryTap(_:))) secondaryTap.buttonMaskRequired = .secondary + // `buttonMaskRequired` is only evaluated for indirect input devices, so on its own it does + // not stop a finger from matching this recognizer — a direct touch carries no buttons and + // satisfies both tap recognizers. UIKit then lets only one of them recognize, and this one + // wins, turning every tap into a right click. Restrict it to the input it is actually for. + secondaryTap.allowedTouchTypes = [NSNumber(value: UITouch.TouchType.indirectPointer.rawValue)] addGestureRecognizer(secondaryTap) } diff --git a/KVMConsoleiPadTests/NanoKVMiPadTests.swift b/KVMConsoleiPadTests/NanoKVMiPadTests.swift index 22bc6b6..c5baecb 100644 --- a/KVMConsoleiPadTests/NanoKVMiPadTests.swift +++ b/KVMConsoleiPadTests/NanoKVMiPadTests.swift @@ -77,6 +77,30 @@ final class KVMConsoleiPadTests: XCTestCase { ) } + // MARK: Tap button routing + + /// A direct touch carries no buttons, and `buttonMaskRequired` is only evaluated for indirect + /// input — so without a touch-type restriction a finger tap matches the secondary recognizer + /// too, and UIKit lets that one win. Every tap then arrives at the host as a right click. + @MainActor + func test_secondaryTapIgnoresDirectTouches() { + let view = PointerCaptureUIView() + let taps = (view.gestureRecognizers ?? []).compactMap { $0 as? UITapGestureRecognizer } + let secondary = taps.first { $0.buttonMaskRequired == .secondary } + let primary = taps.first { $0.buttonMaskRequired == .primary } + + XCTAssertEqual( + secondary?.allowedTouchTypes, + [NSNumber(value: UITouch.TouchType.indirectPointer.rawValue)], + "a finger tap must never reach the secondary recognizer" + ) + XCTAssertNotNil(primary, "finger taps still need a primary recognizer to land on") + XCTAssertFalse( + primary?.allowedTouchTypes == [NSNumber(value: UITouch.TouchType.indirectPointer.rawValue)], + "the primary recognizer must keep accepting direct touches" + ) + } + // MARK: Synthesized tap dwell @MainActor diff --git a/project.yml b/project.yml index 53afa40..034150d 100644 --- a/project.yml +++ b/project.yml @@ -20,7 +20,7 @@ settings: # Single source of truth for app version, shared by both targets via # $(MARKETING_VERSION) / $(CURRENT_PROJECT_VERSION) references in their # Info.plist. CI overrides CURRENT_PROJECT_VERSION per release (timestamp). - MARKETING_VERSION: "1.0.4" + MARKETING_VERSION: "1.0.5" CURRENT_PROJECT_VERSION: "1" targets: KVMConsole: