macoS: Split out terminal tabs for ventura vs tahoe

pull/7588/head
Mitchell Hashimoto 2025-06-12 12:02:31 -07:00
parent fd785f98bb
commit 5877913ab8
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
10 changed files with 120 additions and 145 deletions

View File

@ -16,9 +16,9 @@
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
A514C8D82B54DC6800493A16 /* Ghostty.App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */; };
A51544FE2DFB111C009E85D8 /* TabsTitlebarTerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51544FD2DFB1110009E85D8 /* TabsTitlebarTerminalWindow.swift */; };
A51545002DFB112E009E85D8 /* TerminalTabsTitlebar.xib in Resources */ = {isa = PBXBuildFile; fileRef = A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebar.xib */; };
A51B78472AF4B58B00F3EDB9 /* LegacyTerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51B78462AF4B58B00F3EDB9 /* LegacyTerminalWindow.swift */; };
A51544FE2DFB111C009E85D8 /* TitlebarTabsTahoeTerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51544FD2DFB1110009E85D8 /* TitlebarTabsTahoeTerminalWindow.swift */; };
A51545002DFB112E009E85D8 /* TerminalTabsTitlebarTahoe.xib in Resources */ = {isa = PBXBuildFile; fileRef = A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebarTahoe.xib */; };
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51B78462AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift */; };
A51BFC1E2B2FB5CE00E92F16 /* About.xib in Resources */ = {isa = PBXBuildFile; fileRef = A51BFC1D2B2FB5CE00E92F16 /* About.xib */; };
A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */; };
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC212B2FB6B400E92F16 /* AboutView.swift */; };
@ -57,7 +57,7 @@
A5593FDF2DF8D57C00B47B10 /* TerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5593FDE2DF8D57100B47B10 /* TerminalWindow.swift */; };
A5593FE12DF8D74000B47B10 /* HiddenTitlebarTerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5593FE02DF8D73400B47B10 /* HiddenTitlebarTerminalWindow.swift */; };
A5593FE32DF8D78600B47B10 /* TerminalHiddenTitlebar.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5593FE22DF8D78600B47B10 /* TerminalHiddenTitlebar.xib */; };
A5593FE52DF8DE3000B47B10 /* TerminalLegacy.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5593FE42DF8DE3000B47B10 /* TerminalLegacy.xib */; };
A5593FE52DF8DE3000B47B10 /* TerminalTabsTitlebarVentura.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5593FE42DF8DE3000B47B10 /* TerminalTabsTitlebarVentura.xib */; };
A5593FE72DF927D200B47B10 /* TransparentTitlebarTerminalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5593FE62DF927CC00B47B10 /* TransparentTitlebarTerminalWindow.swift */; };
A5593FE92DF927DF00B47B10 /* TerminalTransparentTitlebar.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5593FE82DF927DF00B47B10 /* TerminalTransparentTitlebar.xib */; };
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BB729B6F53A0055DE60 /* Package.swift */; };
@ -139,9 +139,9 @@
9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = "<group>"; };
A50297342DFA0F3300B4E924 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = "<group>"; };
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Config.swift; sourceTree = "<group>"; };
A51544FD2DFB1110009E85D8 /* TabsTitlebarTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTitlebarTerminalWindow.swift; sourceTree = "<group>"; };
A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebar.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TerminalTabsTitlebar.xib; sourceTree = "<group>"; };
A51B78462AF4B58B00F3EDB9 /* LegacyTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyTerminalWindow.swift; sourceTree = "<group>"; };
A51544FD2DFB1110009E85D8 /* TitlebarTabsTahoeTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitlebarTabsTahoeTerminalWindow.swift; sourceTree = "<group>"; };
A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebarTahoe.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TerminalTabsTitlebarTahoe.xib; sourceTree = "<group>"; };
A51B78462AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitlebarTabsVenturaTerminalWindow.swift; sourceTree = "<group>"; };
A51BFC1D2B2FB5CE00E92F16 /* About.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = About.xib; sourceTree = "<group>"; };
A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutController.swift; sourceTree = "<group>"; };
A51BFC212B2FB6B400E92F16 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
@ -174,7 +174,7 @@
A5593FDE2DF8D57100B47B10 /* TerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindow.swift; sourceTree = "<group>"; };
A5593FE02DF8D73400B47B10 /* HiddenTitlebarTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenTitlebarTerminalWindow.swift; sourceTree = "<group>"; };
A5593FE22DF8D78600B47B10 /* TerminalHiddenTitlebar.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TerminalHiddenTitlebar.xib; sourceTree = "<group>"; };
A5593FE42DF8DE3000B47B10 /* TerminalLegacy.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TerminalLegacy.xib; sourceTree = "<group>"; };
A5593FE42DF8DE3000B47B10 /* TerminalTabsTitlebarVentura.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TerminalTabsTitlebarVentura.xib; sourceTree = "<group>"; };
A5593FE62DF927CC00B47B10 /* TransparentTitlebarTerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransparentTitlebarTerminalWindow.swift; sourceTree = "<group>"; };
A5593FE82DF927DF00B47B10 /* TerminalTransparentTitlebar.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TerminalTransparentTitlebar.xib; sourceTree = "<group>"; };
A55B7BB729B6F53A0055DE60 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
@ -407,13 +407,13 @@
children = (
A59630992AEE1C6400D64628 /* Terminal.xib */,
A5593FE22DF8D78600B47B10 /* TerminalHiddenTitlebar.xib */,
A5593FE42DF8DE3000B47B10 /* TerminalLegacy.xib */,
A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebar.xib */,
A51544FF2DFB112E009E85D8 /* TerminalTabsTitlebarTahoe.xib */,
A5593FE42DF8DE3000B47B10 /* TerminalTabsTitlebarVentura.xib */,
A5593FE82DF927DF00B47B10 /* TerminalTransparentTitlebar.xib */,
A5593FDE2DF8D57100B47B10 /* TerminalWindow.swift */,
A5593FE02DF8D73400B47B10 /* HiddenTitlebarTerminalWindow.swift */,
A51B78462AF4B58B00F3EDB9 /* LegacyTerminalWindow.swift */,
A51544FD2DFB1110009E85D8 /* TabsTitlebarTerminalWindow.swift */,
A51B78462AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift */,
A51544FD2DFB1110009E85D8 /* TitlebarTabsTahoeTerminalWindow.swift */,
A5593FE62DF927CC00B47B10 /* TransparentTitlebarTerminalWindow.swift */,
);
path = "Window Styles";
@ -682,7 +682,7 @@
buildActionMask = 2147483647;
files = (
FC9ABA9C2D0F53F80020D4C8 /* bash-completion in Resources */,
A5593FE52DF8DE3000B47B10 /* TerminalLegacy.xib in Resources */,
A5593FE52DF8DE3000B47B10 /* TerminalTabsTitlebarVentura.xib in Resources */,
29C15B1D2CDC3B2900520DD4 /* bat in Resources */,
A586167C2B7703CC009BDB1D /* fish in Resources */,
55154BE02B33911F001622DC /* ghostty in Resources */,
@ -700,7 +700,7 @@
A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */,
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */,
A596309A2AEE1C6400D64628 /* Terminal.xib in Resources */,
A51545002DFB112E009E85D8 /* TerminalTabsTitlebar.xib in Resources */,
A51545002DFB112E009E85D8 /* TerminalTabsTitlebarTahoe.xib in Resources */,
A5CBD05C2CA0C5C70017A1AE /* QuickTerminal.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -768,7 +768,7 @@
A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */,
A5874D992DAD751B00E83852 /* CGS.swift in Sources */,
A586366B2DF0A98C00E04A10 /* Array+Extension.swift in Sources */,
A51544FE2DFB111C009E85D8 /* TabsTitlebarTerminalWindow.swift in Sources */,
A51544FE2DFB111C009E85D8 /* TitlebarTabsTahoeTerminalWindow.swift in Sources */,
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
A56D58862ACDDB4100508D2C /* Ghostty.Shell.swift in Sources */,
A5985CD72C320C4500C57AD3 /* String+Extension.swift in Sources */,
@ -777,7 +777,7 @@
A53A297F2DB4480F00B6E02C /* EventModifiers+Extension.swift in Sources */,
A53A297B2DB2E49700B6E02C /* CommandPalette.swift in Sources */,
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,
A51B78472AF4B58B00F3EDB9 /* LegacyTerminalWindow.swift in Sources */,
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */,
A57D79272C9C879B001D522E /* SecureInput.swift in Sources */,
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
A5593FE12DF8D74000B47B10 /* HiddenTitlebarTerminalWindow.swift in Sources */,

View File

@ -13,10 +13,15 @@ class TerminalController: BaseTerminalController {
let config = appDelegate.ghostty.config
let nib = switch config.macosTitlebarStyle {
case "native": "Terminal"
//case "tabs": "TerminalTabsTitlebar"
case "tabs": "TerminalLegacy"
case "hidden": "TerminalHiddenTitlebar"
case "transparent": "TerminalTransparentTitlebar"
case "tabs":
if #available(macOS 26.0, *) {
// TODO: Switch to Tahoe when ready
"TerminalTabsTitlebarVentura"
} else {
"TerminalTabsTitlebarVentura"
}
default: defaultValue
}
@ -447,68 +452,20 @@ class TerminalController: BaseTerminalController {
private func syncAppearance(_ surfaceConfig: Ghostty.SurfaceView.DerivedConfig) {
// Let our window handle its own appearance
if let window = window as? TerminalWindow {
// Sync our zoom state for splits
window.surfaceIsZoomed = surfaceTree.zoomed != nil
guard let window = window as? TerminalWindow else { return }
// Set the font for the window and tab titles.
if let titleFontName = surfaceConfig.windowTitleFontFamily {
window.titlebarFont = NSFont(name: titleFontName, size: NSFont.systemFontSize)
} else {
window.titlebarFont = nil
}
// Sync our zoom state for splits
window.surfaceIsZoomed = surfaceTree.zoomed != nil
// Call this last in case it uses any of the properties above.
window.syncAppearance(surfaceConfig)
}
guard let window else { return }
if let window = window as? LegacyTerminalWindow {
// Update our window light/darkness based on our updated background color
window.isLightTheme = OSColor(surfaceConfig.backgroundColor).isLightColor
}
// If our window is not visible, then we do nothing. Some things such as blurring
// have no effect if the window is not visible. Ultimately, we'll have this called
// at some point when a surface becomes focused.
guard window.isVisible else { return }
guard let window = window as? LegacyTerminalWindow, window.hasStyledTabs else { return }
// Our background color depends on if our focused surface borders the top or not.
// If it does, we match the focused surface. If it doesn't, we use the app
// configuration.
let backgroundColor: OSColor
if !surfaceTree.isEmpty {
if let focusedSurface = focusedSurface,
let treeRoot = surfaceTree.root,
let focusedNode = treeRoot.node(view: focusedSurface),
treeRoot.spatial().doesBorder(side: .up, from: focusedNode) {
// Similar to above, an alpha component of "0" causes compositor issues, so
// we use 0.001. See: https://github.com/ghostty-org/ghostty/pull/4308
backgroundColor = OSColor(focusedSurface.backgroundColor ?? surfaceConfig.backgroundColor).withAlphaComponent(0.001)
} else {
// We don't have a focused surface or our surface doesn't border the
// top. We choose to match the color of the top-left most surface.
let topLeftSurface = surfaceTree.root?.leftmostLeaf()
backgroundColor = OSColor(topLeftSurface?.backgroundColor ?? derivedConfig.backgroundColor)
}
// Set the font for the window and tab titles.
if let titleFontName = surfaceConfig.windowTitleFontFamily {
window.titlebarFont = NSFont(name: titleFontName, size: NSFont.systemFontSize)
} else {
backgroundColor = OSColor(self.derivedConfig.backgroundColor)
window.titlebarFont = nil
}
window.titlebarColor = backgroundColor.withAlphaComponent(surfaceConfig.backgroundOpacity)
if (window.isOpaque) {
// Bg color is only synced if we have no transparency. This is because
// the transparency is handled at the surface level (window.backgroundColor
// ignores alpha components)
window.backgroundColor = backgroundColor
// If there is transparency, calling this will make the titlebar opaque
// so we only call this if we are opaque.
window.updateTabBar()
}
// Call this last in case it uses any of the properties above.
window.syncAppearance(surfaceConfig)
}
/// Returns the default size of the window. This is contextual based on the focused surface because
@ -884,28 +841,6 @@ class TerminalController: BaseTerminalController {
}
}
// TODO: remove
if let window = window as? LegacyTerminalWindow,
config.macosTitlebarStyle == "tabs" {
// Handle titlebar tabs config option. Something about what we do while setting up the
// titlebar tabs interferes with the window restore process unless window.tabbingMode
// is set to .preferred, so we set it, and switch back to automatic as soon as we can.
window.tabbingMode = .preferred
window.titlebarTabs = true
DispatchQueue.main.async {
window.tabbingMode = .automatic
}
if window.hasStyledTabs {
// Set the background color of the window
let backgroundColor = NSColor(config.backgroundColor)
window.backgroundColor = backgroundColor
// This makes sure our titlebar renders correctly when there is a transparent background
window.titlebarColor = backgroundColor.withAlphaComponent(config.backgroundOpacity)
}
}
// Initialize our content view to the SwiftUI root
window.contentView = NSHostingView(rootView: TerminalView(
ghostty: self.ghostty,
@ -1110,23 +1045,6 @@ class TerminalController: BaseTerminalController {
}
//MARK: - TerminalViewDelegate
override func titleDidChange(to: String) {
super.titleDidChange(to: to)
guard let window = window as? LegacyTerminalWindow else { return }
// Custom toolbar-based title used when titlebar tabs are enabled.
if let toolbar = window.toolbar as? TerminalToolbar {
if (window.titlebarTabs || derivedConfig.macosTitlebarStyle == "hidden") {
// Updating the title text as above automatically reveals the
// native title view in macOS 15.0 and above. Since we're using
// a custom view instead, we need to re-hide it.
window.titleVisibility = .hidden
}
toolbar.titleText = to
}
}
override func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) {
super.focusedSurfaceDidChange(to: to)

View File

@ -19,15 +19,6 @@ class HiddenTitlebarTerminalWindow: TerminalWindow {
NotificationCenter.default.removeObserver(self)
}
// We override this so that with the hidden titlebar style the titlebar
// area is not draggable.
override var contentLayoutRect: CGRect {
var rect = super.contentLayoutRect
rect.origin.y = 0
rect.size.height = self.frame.height
return rect
}
/// Apply the hidden titlebar style.
private func reapplyHiddenStyle() {
styleMask = [
@ -64,6 +55,26 @@ class HiddenTitlebarTerminalWindow: TerminalWindow {
}
}
// MARK: NSWindow
override var title: String {
didSet {
// Updating the title text as above automatically reveals the
// native title view in macOS 15.0 and above. Since we're using
// a custom view instead, we need to re-hide it.
reapplyHiddenStyle()
}
}
// We override this so that with the hidden titlebar style the titlebar
// area is not draggable.
override var contentLayoutRect: CGRect {
var rect = super.contentLayoutRect
rect.origin.y = 0
rect.size.height = self.frame.height
return rect
}
// MARK: Notifications
@objc private func fullscreenDidExit(_ notification: Notification) {

View File

@ -17,7 +17,7 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="1512" height="948"/>
<rect key="screenRect" x="0.0" y="0.0" width="3008" height="1661"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>

View File

@ -13,7 +13,7 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="👻 Ghostty" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="LegacyTerminalWindow" customModule="Ghostty" customModuleProvider="target">
<window title="👻 Ghostty" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="TitlebarTabsTahoeTerminalWindow" customModule="Ghostty" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="800" height="600"/>

View File

@ -13,11 +13,11 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="👻 Ghostty" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="TabsTitlebarTerminalWindow" customModule="Ghostty" customModuleProvider="target">
<window title="👻 Ghostty" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="TitlebarTabsVenturaTerminalWindow" customModule="Ghostty" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="1512" height="948"/>
<rect key="screenRect" x="0.0" y="0.0" width="3008" height="1661"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>

View File

@ -10,7 +10,7 @@ class TerminalWindow: NSWindow {
static let defaultLevelKey: String = "TerminalDefaultLevel"
/// The configuration derived from the Ghostty config so we don't need to rely on references.
private var derivedConfig: DerivedConfig?
private(set) var derivedConfig: DerivedConfig?
/// Gets the terminal controller from the window controller.
var terminalController: TerminalController? {

View File

@ -1,7 +1,8 @@
import AppKit
import SwiftUI
class TabsTitlebarTerminalWindow: TerminalWindow, NSToolbarDelegate {
/// `macos-titlebar-style = tabs` for macOS 26 (Tahoe) and later.
class TitlebarTabsTahoeTerminalWindow: TerminalWindow, NSToolbarDelegate {
override func awakeFromNib() {
super.awakeFromNib()
@ -49,7 +50,7 @@ extension NSToolbarItem.Identifier {
static let title = NSToolbarItem.Identifier("Title")
}
extension TabsTitlebarTerminalWindow {
extension TitlebarTabsTahoeTerminalWindow {
struct TitleItem: View {
var body: some View {
Text("HELLO THIS IS A PRETTY LONG TITLE")

View File

@ -1,11 +1,10 @@
import Cocoa
/// The terminal window that we originally had in Ghostty for a long time. Kind of a soupy mess
/// of styling.
class LegacyTerminalWindow: TerminalWindow {
/// Titlebar tabs for macOS 13 to 15.
class TitlebarTabsVenturaTerminalWindow: TerminalWindow {
/// This is used to determine if certain elements should be drawn light or dark and should
/// be updated whenever the window background color or surrounding elements changes.
var isLightTheme: Bool = false
fileprivate var isLightTheme: Bool = false
override var surfaceIsZoomed: Bool {
didSet {
@ -32,17 +31,30 @@ class LegacyTerminalWindow: TerminalWindow {
}
}
// MARK: - Lifecycle
// MARK: NSWindow
override func awakeFromNib() {
super.awakeFromNib()
if titlebarTabs {
generateToolbar()
}
}
// Handle titlebar tabs config option. Something about what we do while setting up the
// titlebar tabs interferes with the window restore process unless window.tabbingMode
// is set to .preferred, so we set it, and switch back to automatic as soon as we can.
tabbingMode = .preferred
DispatchQueue.main.async {
self.tabbingMode = .automatic
}
// MARK: - NSWindow
titlebarTabs = true
// This should always be true since our super sets this up.
if let derivedConfig {
// Set the background color of the window
backgroundColor = derivedConfig.backgroundColor
// This makes sure our titlebar renders correctly when there is a transparent background
titlebarColor = derivedConfig.backgroundColor.withAlphaComponent(derivedConfig.backgroundOpacity)
}
}
// We only need to set this once, but need to do it after the window has been created in order
// to determine if the theme is using a very dark background, in which case we don't want to
@ -145,7 +157,29 @@ class LegacyTerminalWindow: TerminalWindow {
}
}
// MARK: - Tab Bar Styling
// MARK: Appearance
override func syncAppearance(_ surfaceConfig: Ghostty.SurfaceView.DerivedConfig) {
super.syncAppearance(surfaceConfig)
// Update our window light/darkness based on our updated background color
isLightTheme = OSColor(surfaceConfig.backgroundColor).isLightColor
// Update our titlebar color
if let preferredBackgroundColor {
titlebarColor = preferredBackgroundColor
} else if let derivedConfig {
titlebarColor = derivedConfig.backgroundColor.withAlphaComponent(derivedConfig.backgroundOpacity)
}
if (isOpaque) {
// If there is transparency, calling this will make the titlebar opaque
// so we only call this if we are opaque.
updateTabBar()
}
}
// MARK: Tab Bar Styling
// This is true if we should apply styles to the titlebar or tab bar.
var hasStyledTabs: Bool {
@ -333,6 +367,18 @@ class LegacyTerminalWindow: TerminalWindow {
}
}
override var title: String {
didSet {
// Updating the title text as above automatically reveals the
// native title view in macOS 15.0 and above. Since we're using
// a custom view instead, we need to re-hide it.
titleVisibility = .hidden
if let toolbar = toolbar as? TerminalToolbar {
toolbar.titleText = title
}
}
}
// We have to regenerate a toolbar when the titlebar tabs setting changes since our
// custom toolbar conditionally generates the items based on this setting. I tried to
// invalidate the toolbar items and force a refresh, but as far as I can tell that
@ -559,7 +605,7 @@ fileprivate class WindowDragView: NSView {
fileprivate class WindowButtonsBackdropView: NSView {
// This must be weak because the window has this view. Otherwise
// a retain cycle occurs.
private weak var terminalWindow: LegacyTerminalWindow?
private weak var terminalWindow: TitlebarTabsVenturaTerminalWindow?
private let isLightTheme: Bool
private let overlayLayer = VibrantLayer()
@ -587,7 +633,7 @@ fileprivate class WindowButtonsBackdropView: NSView {
fatalError("init(coder:) has not been implemented")
}
init(window: LegacyTerminalWindow) {
init(window: TitlebarTabsVenturaTerminalWindow) {
self.terminalWindow = window
self.isLightTheme = window.isLightTheme

View File

@ -271,8 +271,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
// This is a hack that I want to remove from this but for now, we need to
// fix up the titlebar tabs here before we do everything below.
if let window = window as? LegacyTerminalWindow,
window.titlebarTabs {
if let window = window as? TitlebarTabsVenturaTerminalWindow, window.titlebarTabs {
window.titlebarTabs = true
}