diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 8c8e327d4..eb8c236e7 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -65,6 +65,7 @@ A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */; }; A5E112972AF7401B00C6E0C2 /* ClipboardConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112962AF7401B00C6E0C2 /* ClipboardConfirmationView.swift */; }; A5FEB3002ABB69450068369E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FEB2FF2ABB69450068369E /* main.swift */; }; + AEF9CE242B6AD07A0017E195 /* TerminalToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */; }; C159E81D2B66A06B00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; }; C159E89D2B69A2EF00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; }; /* End PBXBuildFile section */ @@ -125,6 +126,7 @@ A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardConfirmationController.swift; sourceTree = ""; }; A5E112962AF7401B00C6E0C2 /* ClipboardConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardConfirmationView.swift; sourceTree = ""; }; A5FEB2FF2ABB69450068369E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalToolbar.swift; sourceTree = ""; }; C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSColor+Extension.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -281,6 +283,7 @@ A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */, A596309D2AEE1D6C00D64628 /* TerminalView.swift */, A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */, + AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */, A535B9D9299C569B0017E2E4 /* ErrorView.swift */, ); path = Terminal; @@ -493,6 +496,7 @@ A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */, 8503D7C72A549C66006CFF3D /* FullScreenHandler.swift in Sources */, A596309E2AEE1D6C00D64628 /* TerminalView.swift in Sources */, + AEF9CE242B6AD07A0017E195 /* TerminalToolbar.swift in Sources */, C159E81D2B66A06B00FDFE9C /* OSColor+Extension.swift in Sources */, A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */, A5E112972AF7401B00C6E0C2 /* ClipboardConfirmationView.swift in Sources */, diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 2ffb6a60f..ab739a88f 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -482,7 +482,13 @@ class TerminalController: NSWindowController, NSWindowDelegate, } func titleDidChange(to: String) { - self.window?.title = to + guard let window = window as? TerminalWindow else { return } + + window.title = to + + // Custom toolbar-based title used when titlebar tabs are enabled. + guard let toolbar = window.toolbar as? TerminalToolbar else { return } + toolbar.setTitleText(to) } func cellSizeDidChange(to: NSSize) { diff --git a/macos/Sources/Features/Terminal/TerminalToolbar.swift b/macos/Sources/Features/Terminal/TerminalToolbar.swift new file mode 100644 index 000000000..685db8b53 --- /dev/null +++ b/macos/Sources/Features/Terminal/TerminalToolbar.swift @@ -0,0 +1,44 @@ +// Custom NSToolbar subclass that displays a centered window title, +// in order to accommodate the titlebar tabs feature. + +import Foundation +import Cocoa +import SwiftUI + +class TerminalToolbar: NSToolbar, NSToolbarDelegate { + static private let TitleIdentifier = NSToolbarItem.Identifier("TitleText") + private let TitleTextField = NSTextField( + labelWithString: "👻 Ghostty" + ) + + func setTitleText(_ text: String) { + self.TitleTextField.stringValue = text + } + + override init(identifier: NSToolbar.Identifier) { + super.init(identifier: identifier) + delegate = self + if #available(macOS 13.0, *) { + centeredItemIdentifiers.insert(Self.TitleIdentifier) + } else { + centeredItemIdentifier = Self.TitleIdentifier + } + } + + func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { + guard itemIdentifier == Self.TitleIdentifier else { return nil } + + let toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier) + toolbarItem.isEnabled = true + toolbarItem.view = self.TitleTextField + return toolbarItem + } + + func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + return [Self.TitleIdentifier] + } + + func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { + return [Self.TitleIdentifier] + } +} diff --git a/macos/Sources/Features/Terminal/TerminalWindow.swift b/macos/Sources/Features/Terminal/TerminalWindow.swift index fabbd8dc4..af0d303e4 100644 --- a/macos/Sources/Features/Terminal/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/TerminalWindow.swift @@ -42,8 +42,12 @@ class TerminalWindow: NSWindow { // so we make sure it's the right size/position, and exists. self.toolbarStyle = .unifiedCompact if (self.toolbar == nil) { - self.toolbar = NSToolbar(identifier: "Toolbar") + self.toolbar = TerminalToolbar(identifier: "Toolbar") } + // We directly hide the view containing the title text because if we use the + // `titleVisibility` property for this it prevents the window from hiding the + // tab bar when we get down to a single tab. + self.hideTitleText() } else { // "expanded" places the toolbar below the titlebar, so setting this style and // removing the toolbar ensures that the titlebar will be the default height. @@ -62,6 +66,21 @@ class TerminalWindow: NSWindow { titlebarContainer.layer?.backgroundColor = color } + // Directly hide the view containing the title text + func hideTitleText() { + guard let toolbarTitleView = contentView?.superview?.subviews.first(where: { + $0.className == "NSTitlebarContainerView" + })?.subviews.first(where: { + $0.className == "NSTitlebarView" + })?.subviews.first(where: { + $0.className == "NSToolbarView" + })?.subviews.first(where: { + $0.className == "NSToolbarTitleView" + }) else { return } + + toolbarTitleView.isHidden = true + } + // This is called by macOS for native tabbing in order to add the tab bar. We hook into // this, detect the tab bar being added, and override its behavior. override func addTitlebarAccessoryViewController(_ childViewController: NSTitlebarAccessoryViewController) { @@ -74,7 +93,8 @@ class TerminalWindow: NSWindow { // Ensure it has the right layoutAttribute to force it next to our titlebar childViewController.layoutAttribute = .right - // Hide the title text if the tab bar is showing since we show it in the tab + // If we don't set titleVisibility to hidden here, the toolbar will display a + // "collapsed items" indicator which interferes with the tab bar. titleVisibility = .hidden // Mark the controller for future reference so we can easily find it. Otherwise @@ -90,9 +110,9 @@ class TerminalWindow: NSWindow { } override func removeTitlebarAccessoryViewController(at index: Int) { - let childViewController = titlebarAccessoryViewControllers[index] + let isTabBar = titlebarAccessoryViewControllers[index].identifier == Self.TabBarController super.removeTitlebarAccessoryViewController(at: index) - if (childViewController.identifier == Self.TabBarController) { + if (isTabBar) { hideCustomTabBarViews() } } @@ -104,9 +124,6 @@ class TerminalWindow: NSWindow { // Hide the window drag handle. windowDragHandle?.isHidden = true - - // Enable the window title text. - titleVisibility = .visible } private func pushTabsToTitlebar(_ tabBarController: NSTitlebarAccessoryViewController) {