diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index d7682952db33..db0eeed5d994 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -166,7 +166,7 @@ final class SidebarState: ObservableObject { /// embedded terminal views. This overlay sits above the entire content view hierarchy and /// intercepts file drags, forwarding drops to the GhosttyNSView under the cursor. /// -/// Mouse events are forwarded to the views below via a hide-send-unhide pattern so clicks, +/// Mouse events are forwarded to the view below the overlay via hit-testing so clicks, /// scrolls, and other interactions pass through normally. final class FileDropOverlayView: NSView { /// Fallback handler when no terminal is found under the drop point. @@ -194,25 +194,32 @@ final class FileDropOverlayView: NSView { return nil } - // MARK: Mouse forwarding — safety net for the rare case where stale drag pasteboard - // data causes hitTest to return self when no drag is actually active. + // MARK: Mouse forwarding – resolve the view under the overlay and dispatch directly. + // + // Calling the target view's mouseDown can propagate back through AppKit's event dispatch + // (NSWindow.sendEvent → _reallySendEvent) which hit-tests the overlay again, causing + // infinite recursion / stack overflow. The re-entrancy guard breaks the cycle. - private func forwardEvent(_ event: NSEvent) { - isHidden = true - window?.sendEvent(event) - isHidden = false + private var isForwarding = false + + private func forwardEvent(_ event: NSEvent, dispatch: (NSView, NSEvent) -> Void) { + guard !isForwarding else { return } + isForwarding = true + defer { isForwarding = false } + guard let target = viewUnderOverlay(at: event.locationInWindow) else { return } + dispatch(target, event) } - override func mouseDown(with event: NSEvent) { forwardEvent(event) } - override func mouseUp(with event: NSEvent) { forwardEvent(event) } - override func mouseDragged(with event: NSEvent) { forwardEvent(event) } - override func rightMouseDown(with event: NSEvent) { forwardEvent(event) } - override func rightMouseUp(with event: NSEvent) { forwardEvent(event) } - override func rightMouseDragged(with event: NSEvent) { forwardEvent(event) } - override func otherMouseDown(with event: NSEvent) { forwardEvent(event) } - override func otherMouseUp(with event: NSEvent) { forwardEvent(event) } - override func otherMouseDragged(with event: NSEvent) { forwardEvent(event) } - override func scrollWheel(with event: NSEvent) { forwardEvent(event) } + override func mouseDown(with event: NSEvent) { forwardEvent(event) { $0.mouseDown(with: $1) } } + override func mouseUp(with event: NSEvent) { forwardEvent(event) { $0.mouseUp(with: $1) } } + override func mouseDragged(with event: NSEvent) { forwardEvent(event) { $0.mouseDragged(with: $1) } } + override func rightMouseDown(with event: NSEvent) { forwardEvent(event) { $0.rightMouseDown(with: $1) } } + override func rightMouseUp(with event: NSEvent) { forwardEvent(event) { $0.rightMouseUp(with: $1) } } + override func rightMouseDragged(with event: NSEvent) { forwardEvent(event) { $0.rightMouseDragged(with: $1) } } + override func otherMouseDown(with event: NSEvent) { forwardEvent(event) { $0.otherMouseDown(with: $1) } } + override func otherMouseUp(with event: NSEvent) { forwardEvent(event) { $0.otherMouseUp(with: $1) } } + override func otherMouseDragged(with event: NSEvent) { forwardEvent(event) { $0.otherMouseDragged(with: $1) } } + override func scrollWheel(with event: NSEvent) { forwardEvent(event) { $0.scrollWheel(with: $1) } } // MARK: NSDraggingDestination – only accept file drops over terminal views. @@ -238,14 +245,20 @@ final class FileDropOverlayView: NSView { return .copy } - /// Temporarily hides self, hit-tests the window to find the GhosttyNSView under the cursor. - private func terminalUnderPoint(_ windowPoint: NSPoint) -> GhosttyNSView? { + /// Temporarily hides self and hit-tests content to find the view under the cursor. + private func viewUnderOverlay(at windowPoint: NSPoint) -> NSView? { guard let window, let contentView = window.contentView, let themeFrame = contentView.superview else { return nil } isHidden = true let point = themeFrame.convert(windowPoint, from: nil) let hitView = contentView.hitTest(point) isHidden = false + return hitView + } + + /// Temporarily hides self, hit-tests the window to find the GhosttyNSView under the cursor. + private func terminalUnderPoint(_ windowPoint: NSPoint) -> GhosttyNSView? { + guard let hitView = viewUnderOverlay(at: windowPoint) else { return nil } var current: NSView? = hitView while let view = current { diff --git a/homebrew-cmux b/homebrew-cmux index a06b95531ead..19af91f6d470 160000 --- a/homebrew-cmux +++ b/homebrew-cmux @@ -1 +1 @@ -Subproject commit a06b95531ead59479d4655b314f2b86cb4cc4c84 +Subproject commit 19af91f6d47016a74a08546e8693dc03eefb7ef5