Skip to content
Merged
32 changes: 32 additions & 0 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
36CE99ED050785B5E96B72BB /* AuthCallbackRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A19A4145F034965110CF87 /* AuthCallbackRouter.swift */; };
4378399A7C0245EF8186F306 /* OmnibarAndToolsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09C007F42697761B5F1A2AB /* OmnibarAndToolsTests.swift */; };
46F6AC15863EC84DCD3770A2 /* TerminalAndGhosttyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FC74F2C27127CC565B3E8C /* TerminalAndGhosttyTests.swift */; };
D0B10000A1B2C3D4E5F60001 /* TerminalPaneDropTargetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B10001A1B2C3D4E5F60001 /* TerminalPaneDropTargetView.swift */; };
D0B10002A1B2C3D4E5F60001 /* BonsplitTabBarPassThrough.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B10003A1B2C3D4E5F60001 /* BonsplitTabBarPassThrough.swift */; };
D0B10004A1B2C3D4E5F60001 /* WorkspacePortalPaneDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B10005A1B2C3D4E5F60001 /* WorkspacePortalPaneDrop.swift */; };
D0B10006A1B2C3D4E5F60001 /* TerminalWindowPortalDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B10007A1B2C3D4E5F60001 /* TerminalWindowPortalDebug.swift */; };
D0B10008A1B2C3D4E5F60001 /* PortalTabDragRoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B10009A1B2C3D4E5F60001 /* PortalTabDragRoutingTests.swift */; };
D0B1000AA1B2C3D4E5F60001 /* GhosttyTerminalViewSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B1000BA1B2C3D4E5F60001 /* GhosttyTerminalViewSupport.swift */; };
D0B1000CA1B2C3D4E5F60001 /* CmuxWebViewDragRoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B1000DA1B2C3D4E5F60001 /* CmuxWebViewDragRoutingTests.swift */; };
D0B1000EA1B2C3D4E5F60001 /* BrowserPaneDropRoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B1000FA1B2C3D4E5F60001 /* BrowserPaneDropRoutingTests.swift */; };
5EDB6027B346C46521A93C74 /* CMUXAuthCore in Frameworks */ = {isa = PBXBuildFile; productRef = 29813FE5A6CBC1019289A251 /* CMUXAuthCore */; };
AA11BB22CC33DD44EE550001 /* CMUXWorkstream in Frameworks */ = {isa = PBXBuildFile; productRef = AA11BB22CC33DD44EE550002 /* CMUXWorkstream */; };
FEED0000000000000000F002 /* FeedCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEED0000000000000000F001 /* FeedCoordinator.swift */; };
Expand Down Expand Up @@ -273,6 +281,9 @@

/* Begin PBXFileReference section */
02FC74F2C27127CC565B3E8C /* TerminalAndGhosttyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalAndGhosttyTests.swift; sourceTree = "<group>"; };
D0B10009A1B2C3D4E5F60001 /* PortalTabDragRoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortalTabDragRoutingTests.swift; sourceTree = "<group>"; };
D0B1000DA1B2C3D4E5F60001 /* CmuxWebViewDragRoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CmuxWebViewDragRoutingTests.swift; sourceTree = "<group>"; };
D0B1000FA1B2C3D4E5F60001 /* BrowserPaneDropRoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserPaneDropRoutingTests.swift; sourceTree = "<group>"; };
10D684CFFB8CDEF89CE2D9E1 /* TabManagerSessionSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabManagerSessionSnapshotTests.swift; sourceTree = "<group>"; };
14A7DC53B9CA33BE2A421711 /* WorkspacePullRequestSidebarTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspacePullRequestSidebarTests.swift; sourceTree = "<group>"; };
1D301919B10F22B8708E8883 /* WorkspaceManualUnreadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceManualUnreadTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -331,6 +342,8 @@
E30750000000000000000001 /* WorkspaceTabColorResolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceTabColorResolution.swift; sourceTree = "<group>"; };
A5001014 /* GhosttyConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyConfig.swift; sourceTree = "<group>"; };
A5001015 /* GhosttyTerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyTerminalView.swift; sourceTree = "<group>"; };
D0B1000BA1B2C3D4E5F60001 /* GhosttyTerminalViewSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyTerminalViewSupport.swift; sourceTree = "<group>"; };
D0B10001A1B2C3D4E5F60001 /* TerminalPaneDropTargetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalPaneDropTargetView.swift; sourceTree = "<group>"; };
A5001016 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
A5001017 /* ghostty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ghostty.h; sourceTree = "<group>"; };
A5001018 /* cmux-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "cmux-Bridging-Header.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -367,6 +380,7 @@
A5001414 /* BrowserPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/BrowserPanelView.swift; sourceTree = "<group>"; };
A5001415 /* PanelContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/PanelContentView.swift; sourceTree = "<group>"; };
A5001416 /* Workspace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Workspace.swift; sourceTree = "<group>"; };
D0B10005A1B2C3D4E5F60001 /* WorkspacePortalPaneDrop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspacePortalPaneDrop.swift; sourceTree = "<group>"; };
C0DE32470000000000000004 /* WorkspaceSurfaceIdentifierClipboardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceSurfaceIdentifierClipboardText.swift; sourceTree = "<group>"; };
A5001417 /* WorkspaceContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceContentView.swift; sourceTree = "<group>"; };
E30760000000000000000001 /* TmuxWorkspacePaneOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TmuxWorkspacePaneOverlayView.swift; sourceTree = "<group>"; };
Expand All @@ -376,7 +390,9 @@
A5001511 /* UITestRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestRecorder.swift; sourceTree = "<group>"; };
A5001520 /* PostHogAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalytics.swift; sourceTree = "<group>"; };
A5001531 /* TerminalWindowPortal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindowPortal.swift; sourceTree = "<group>"; };
D0B10007A1B2C3D4E5F60001 /* TerminalWindowPortalDebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindowPortalDebug.swift; sourceTree = "<group>"; };
A5001533 /* BrowserWindowPortal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserWindowPortal.swift; sourceTree = "<group>"; };
D0B10003A1B2C3D4E5F60001 /* BonsplitTabBarPassThrough.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BonsplitTabBarPassThrough.swift; sourceTree = "<group>"; };
A5001541 /* PortScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortScanner.swift; sourceTree = "<group>"; };
A5001544 /* TerminalImageTransfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalImageTransfer.swift; sourceTree = "<group>"; };
A5001545 /* TerminalSSHSessionDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalSSHSessionDetector.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -645,13 +661,18 @@
A5001511 /* UITestRecorder.swift */,
A5001520 /* PostHogAnalytics.swift */,
A5001416 /* Workspace.swift */,
D0B10005A1B2C3D4E5F60001 /* WorkspacePortalPaneDrop.swift */,
C0DE32470000000000000004 /* WorkspaceSurfaceIdentifierClipboardText.swift */,
A5001417 /* WorkspaceContentView.swift */,
E30760000000000000000001 /* TmuxWorkspacePaneOverlayView.swift */,
A5001014 /* GhosttyConfig.swift */,
A5001015 /* GhosttyTerminalView.swift */,
D0B1000BA1B2C3D4E5F60001 /* GhosttyTerminalViewSupport.swift */,
D0B10001A1B2C3D4E5F60001 /* TerminalPaneDropTargetView.swift */,
A5001531 /* TerminalWindowPortal.swift */,
D0B10007A1B2C3D4E5F60001 /* TerminalWindowPortalDebug.swift */,
A5001533 /* BrowserWindowPortal.swift */,
D0B10003A1B2C3D4E5F60001 /* BonsplitTabBarPassThrough.swift */,
A5001019 /* TerminalController.swift */,
A5001541 /* PortScanner.swift */,
A5001544 /* TerminalImageTransfer.swift */,
Expand Down Expand Up @@ -762,7 +783,10 @@
A50019B3 /* SettingsSearchIndexTests.swift */,
970226F3C99D0D937CD00539 /* BrowserConfigTests.swift */,
58C7B1B978620BE162CC057E /* BrowserPanelTests.swift */,
D0B1000DA1B2C3D4E5F60001 /* CmuxWebViewDragRoutingTests.swift */,
D0B1000FA1B2C3D4E5F60001 /* BrowserPaneDropRoutingTests.swift */,
02FC74F2C27127CC565B3E8C /* TerminalAndGhosttyTests.swift */,
D0B10009A1B2C3D4E5F60001 /* PortalTabDragRoutingTests.swift */,
71F8ED91A4B55D34BE6A0668 /* WorkspaceUnitTests.swift */,
BEE83F8394D90ACACD8E19DD /* WindowAndDragTests.swift */,
6083A7DAD962E287FC2FFE94 /* ShortcutAndCommandPaletteTests.swift */,
Expand Down Expand Up @@ -1049,13 +1073,18 @@
A5001501 /* UITestRecorder.swift in Sources */,
A5001521 /* PostHogAnalytics.swift in Sources */,
A5001406 /* Workspace.swift in Sources */,
D0B10004A1B2C3D4E5F60001 /* WorkspacePortalPaneDrop.swift in Sources */,
C0DE32470000000000000003 /* WorkspaceSurfaceIdentifierClipboardText.swift in Sources */,
A5001407 /* WorkspaceContentView.swift in Sources */,
E30760000000000000000002 /* TmuxWorkspacePaneOverlayView.swift in Sources */,
A5001004 /* GhosttyConfig.swift in Sources */,
A5001005 /* GhosttyTerminalView.swift in Sources */,
D0B1000AA1B2C3D4E5F60001 /* GhosttyTerminalViewSupport.swift in Sources */,
D0B10000A1B2C3D4E5F60001 /* TerminalPaneDropTargetView.swift in Sources */,
A5001532 /* TerminalWindowPortal.swift in Sources */,
D0B10006A1B2C3D4E5F60001 /* TerminalWindowPortalDebug.swift in Sources */,
A5001534 /* BrowserWindowPortal.swift in Sources */,
D0B10002A1B2C3D4E5F60001 /* BonsplitTabBarPassThrough.swift in Sources */,
A5001007 /* TerminalController.swift in Sources */,
A5001540 /* PortScanner.swift in Sources */,
A5001542 /* TerminalImageTransfer.swift in Sources */,
Expand Down Expand Up @@ -1200,7 +1229,10 @@
A50019B2 /* SettingsSearchIndexTests.swift in Sources */,
E12E88F82733EC42F32C36A3 /* BrowserConfigTests.swift in Sources */,
1F14445B9627DE9D3AF4FD2E /* BrowserPanelTests.swift in Sources */,
D0B1000CA1B2C3D4E5F60001 /* CmuxWebViewDragRoutingTests.swift in Sources */,
D0B1000EA1B2C3D4E5F60001 /* BrowserPaneDropRoutingTests.swift in Sources */,
46F6AC15863EC84DCD3770A2 /* TerminalAndGhosttyTests.swift in Sources */,
D0B10008A1B2C3D4E5F60001 /* PortalTabDragRoutingTests.swift in Sources */,
6B524A0BA34FD46A771335AB /* WorkspaceUnitTests.swift in Sources */,
063BC42CEE257D6213A2E30C /* WindowAndDragTests.swift in Sources */,
1521D55DC63D5E5FC4955E31 /* ShortcutAndCommandPaletteTests.swift in Sources */,
Expand Down
115 changes: 115 additions & 0 deletions Sources/BonsplitTabBarPassThrough.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import AppKit
import Bonsplit

/// Shared helpers for portal hosts that must defer to the minimal-mode
/// Bonsplit tab strip rendered underneath them.
enum BonsplitTabBarPassThrough {
static func isPassThroughPointerEvent(_ eventType: NSEvent.EventType?) -> Bool {
switch eventType {
case nil:
// Unit tests can call hitTest directly without an active AppKit event.
return true
case .leftMouseDown, .leftMouseUp, .leftMouseDragged,
.rightMouseDown, .rightMouseUp, .rightMouseDragged,
.otherMouseDown, .otherMouseUp, .otherMouseDragged,
.mouseMoved, .mouseEntered,
.mouseExited, .cursorUpdate,
.appKitDefined, .applicationDefined, .systemDefined, .periodic:
return true
default:
return false
}
}

static func titlebarInteractionBandMinY(in window: NSWindow) -> CGFloat {
let nativeTitlebarHeight = window.frame.height - window.contentLayoutRect.height
let customTitlebarBandHeight = max(28, min(72, nativeTitlebarHeight))
return window.contentLayoutRect.maxY - customTitlebarBandHeight - 0.5
}

// The minimal-mode tab strip lives just under the titlebar. Anything more
// than this many points below the content top can't overlap it, so we skip
// the recursive subtree scan on the pointer-event hot path.
private static let tabStripScanBandHeight: CGFloat = 200

static func shouldPassThroughToPaneTabBar(
windowPoint: NSPoint,
below portalHost: NSView
) -> (result: Bool, registryHit: Bool) {
let registryHit = portalHost.window.map {
BonsplitTabBarHitRegionRegistry.containsWindowPoint(windowPoint, in: $0)
} ?? false
if registryHit {
return (true, true)
}

// High-frequency pointer events (mouseMoved/cursorUpdate) flow through
// here on every hover; cap the recursive view-tree walk to the top
// band where the tab strip can actually live.
if let window = portalHost.window {
let scanFloor = window.contentLayoutRect.maxY - tabStripScanBandHeight
if windowPoint.y < scanFloor {
return (false, false)
}
}

let fallbackHit = hasUnderlyingBonsplitTabBarBackground(
at: windowPoint,
below: portalHost
)
return (fallbackHit, false)
}

static func passThroughDecision(
at point: NSPoint,
in portalHost: NSView,
eventType: NSEvent.EventType?
) -> (windowPoint: NSPoint, result: Bool, registryHit: Bool)? {
guard isPassThroughPointerEvent(eventType) else { return nil }
let windowPoint = portalHost.convert(point, to: nil)
let decision = shouldPassThroughToPaneTabBar(windowPoint: windowPoint, below: portalHost)
return (windowPoint, decision.result, decision.registryHit)
}

static func hasBonsplitTabBarBackground(at windowPoint: NSPoint, in view: NSView) -> Bool {
guard !view.isHidden, view.alphaValue > 0 else { return false }

// NSView subviews are not clipped to parent bounds by default, and the
// minimal tab strip can render outside its immediate container.
let className = NSStringFromClass(type(of: view))
if className.contains("TabBarBackgroundNSView") {
let pointInView = view.convert(windowPoint, from: nil)
if view.bounds.contains(pointInView) {
return true
}
}

for subview in view.subviews.reversed() {
if hasBonsplitTabBarBackground(at: windowPoint, in: subview) {
return true
}
}
return false
}

static func hasUnderlyingBonsplitTabBarBackground(
at windowPoint: NSPoint,
below portalHost: NSView
) -> Bool {
// Only walk siblings rendered below the host. Falling back to the full
// window content tree when the host has no superview would risk a
// false-positive pass-through against a tab bar painted above an
// unparented host.
guard let container = portalHost.superview,
let hostIndex = container.subviews.firstIndex(of: portalHost) else {
return false
}
for sibling in container.subviews[..<hostIndex].reversed() {
guard !sibling.isHidden, sibling.alphaValue > 0 else { continue }
if hasBonsplitTabBarBackground(at: windowPoint, in: sibling) {
return true
}
}
return false
}
}
Loading