diff --git a/Sources/KeyPathAppKit/UI/KeyboardStage/Metal/KeyboardStageMetalView.swift b/Sources/KeyPathAppKit/UI/KeyboardStage/Metal/KeyboardStageMetalView.swift index 699e26ca9..71484035b 100644 --- a/Sources/KeyPathAppKit/UI/KeyboardStage/Metal/KeyboardStageMetalView.swift +++ b/Sources/KeyPathAppKit/UI/KeyboardStage/Metal/KeyboardStageMetalView.swift @@ -244,6 +244,16 @@ final class KeyboardStageMTKView: MTKView { private var retryTask: Task? private var reportedWindowX = SIMD2(0, 1) + /// The renderer is visual output only; the native semantic overlay stacked + /// above it owns pointer interaction and accessibility. As a real `NSView` + /// this would otherwise return itself for every point inside the keyboard + /// and swallow clicks before those hit targets could see them, so pointer + /// presses worked in the SwiftUI fallback and silently failed whenever + /// Metal was active. + override func hitTest(_: NSPoint) -> NSView? { + nil + } + override func viewDidMoveToWindow() { super.viewDidMoveToWindow() stopObservingWindow() diff --git a/Tests/KeyPathTests/KeyboardStage/KeyboardStageInteractionTests.swift b/Tests/KeyPathTests/KeyboardStage/KeyboardStageInteractionTests.swift index 13f89bb62..7da309c72 100644 --- a/Tests/KeyPathTests/KeyboardStage/KeyboardStageInteractionTests.swift +++ b/Tests/KeyPathTests/KeyboardStage/KeyboardStageInteractionTests.swift @@ -1,7 +1,22 @@ +import AppKit @testable import KeyPathAppKit import XCTest final class KeyboardStageInteractionTests: XCTestCase { + /// The Metal view must stay transparent to the mouse. It is a real NSView, + /// so without this it returns itself for every point inside the keyboard + /// and swallows clicks before the semantic overlay's per-key hit targets + /// receive them — pointer presses then work only in the SwiftUI fallback. + @MainActor + func testMetalStageViewPassesPointerEventsToTheOverlayAbove() { + let view = KeyboardStageMTKView( + frame: NSRect(x: 0, y: 0, width: 400, height: 300) + ) + + XCTAssertNil(view.hitTest(NSPoint(x: 200, y: 150))) + XCTAssertNil(view.hitTest(NSPoint(x: 1, y: 1))) + } + func testKeyDownFeedbackIsImmediate() { var presentation = KeyboardStageInteractionPresentation() let pressed = KeyboardStageInteractionState(