macos: transparent titlebar needs to be rehidden when tabs change

pull/7588/head
Mitchell Hashimoto 2025-06-11 07:29:44 -07:00
parent 7d02977482
commit 6ce7f612a6
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
2 changed files with 85 additions and 9 deletions

View File

@ -1,24 +1,39 @@
import AppKit
class TransparentTitlebarTerminalWindow: TerminalWindow {
private var debugTimer: Timer?
// We need to restore our last synced appearance so that we can reapply
// the appearance in certain scenarios.
private var lastSurfaceConfig: Ghostty.SurfaceView.DerivedConfig?
// KVO observations
private var tabGroupWindowsObservation: NSKeyValueObservation?
override func awakeFromNib() {
super.awakeFromNib()
// Debug timer to print view hierarchy every second
debugTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
print("=== TransparentTitlebarTerminalWindow Debug ===")
self?.contentView?.rootView.printViewHierarchy()
print("===============================================\n")
}
// We need to observe the tab group because we need to redraw on
// tabbed window changes and there is no notification for that.
setupTabGroupObservation()
}
deinit {
debugTimer?.invalidate()
tabGroupWindowsObservation?.invalidate()
}
override func becomeMain() {
// On macOS Tahoe, the tab bar redraws and restores non-transparency when
// switching tabs. To overcome this, we resync the appearance whenever this
// window becomes main (focused).
if #available(macOS 26.0, *),
let lastSurfaceConfig {
syncAppearance(lastSurfaceConfig)
}
}
// MARK: Appearance
override func syncAppearance(_ surfaceConfig: Ghostty.SurfaceView.DerivedConfig) {
lastSurfaceConfig = surfaceConfig
if #available(macOS 26.0, *) {
syncAppearanceTahoe(surfaceConfig)
} else {
@ -47,6 +62,8 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
titlebarContainer.layer?.backgroundColor = configBgColor.withAlphaComponent(surfaceConfig.backgroundOpacity).cgColor
}
// MARK: View Finders
private var titlebarBackgroundView: NSView? {
titlebarContainer?.firstDescendant(withClassName: "NSTitlebarBackgroundView")
}
@ -76,4 +93,33 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
private var titlebarContainerView: NSView? {
contentView?.firstViewFromRoot(withClassName: "NSTitlebarContainerView")
}
// MARK: Tab Group Observation
private func setupTabGroupObservation() {
// Remove existing observation if any
tabGroupWindowsObservation?.invalidate()
tabGroupWindowsObservation = nil
// Check if tabGroup is available
guard let tabGroup else { return }
// Set up KVO observation for the windows array. Whenever it changes
// we resync the appearance because it can cause macOS to redraw the
// tab bar.
tabGroupWindowsObservation = tabGroup.observe(
\.windows,
options: [.new]
) { [weak self] _, _ in
// NOTE: At one point, I guarded this on only if we went from 0 to N
// or N to 0 under the assumption that the tab bar would only get
// replaced on those cases. This turned out to be false (Tahoe).
// It's cheap enough to always redraw this so we should just do it
// unconditionally.
guard let self else { return }
guard let lastSurfaceConfig else { return }
self.syncAppearance(lastSurfaceConfig)
}
}
}

View File

@ -118,6 +118,36 @@ extension NSView {
// Add frame info
result += " [frame: \(frame)]"
// Add visual properties
var properties: [String] = []
// Hidden status
if isHidden {
properties.append("hidden")
}
// Opaque status
properties.append(isOpaque ? "opaque" : "transparent")
// Layer backing
if wantsLayer {
properties.append("layer-backed")
if let bgColor = layer?.backgroundColor {
let color = NSColor(cgColor: bgColor)
if let rgb = color?.usingColorSpace(.deviceRGB) {
properties.append(String(format: "bg:rgba(%.0f,%.0f,%.0f,%.2f)",
rgb.redComponent * 255,
rgb.greenComponent * 255,
rgb.blueComponent * 255,
rgb.alphaComponent))
} else {
properties.append("bg:\(bgColor)")
}
}
}
result += " [\(properties.joined(separator: ", "))]"
result += "\n"
// Process subviews