macOS: Add option to hide window buttons (#7504)

Conversion of #7497 to a PR. This implements a feature requested in
#7331: an option to hide the default window buttons on macOS for a
cleaner aesthetic.

~~Builds on #7502 as it requires the same change to avoid the main
toolbar title showing on top of the tab bar.~~ EDIT: rebased on main now
that #7502 was merged.

I aligned the scope of the new option with `macos-titlebar-style`, since
they both customize titlebar elements. This means it has the same edge
case quirks: For example, if you change the setting, reload the config,
and then open a new tab, the appearance of the current window will
depend on which tab is in the foreground. I did it this way because
`macos-titlebar-style` provided an easy template for which derived
configs and functions to modify. Let me know if you want me to try
adjusting this so that a change in the setting also takes effect for
current windows/tabs, which I _think_ should be possible.

Screenshots:
* `macos-titlebar-style = transparent` (default)
![Screenshot 2025-06-01 at 18 04
56](https://github.com/user-attachments/assets/01fa3953-d2ef-4c39-a6e3-f236488dd841)
![Screenshot 2025-06-01 at 18 07
24](https://github.com/user-attachments/assets/cd463ded-a0b2-4f69-9abe-384e7eecaa27)
* `macos-titlebar-style = tabs`
![Screenshot 2025-06-01 at 17 56
35](https://github.com/user-attachments/assets/bf99d046-cdbb-4e5d-b1c5-d51bbba79007)
![Screenshot 2025-06-01 at 17 56
48](https://github.com/user-attachments/assets/098164b8-bf97-4df1-9dff-c1c17e12665d)
pull/7527/head
Mitchell Hashimoto 2025-06-05 07:46:57 -07:00 committed by GitHub
commit a2a3863ad2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 71 additions and 4 deletions

View File

@ -377,6 +377,14 @@ class TerminalController: BaseTerminalController {
shouldCascadeWindows = false shouldCascadeWindows = false
} }
fileprivate func hideWindowButtons() {
guard let window else { return }
window.standardWindowButton(.closeButton)?.isHidden = true
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
}
fileprivate func applyHiddenTitlebarStyle() { fileprivate func applyHiddenTitlebarStyle() {
guard let window else { return } guard let window else { return }
@ -398,9 +406,7 @@ class TerminalController: BaseTerminalController {
window.titlebarAppearsTransparent = true window.titlebarAppearsTransparent = true
// Hide the traffic lights (window control buttons) // Hide the traffic lights (window control buttons)
window.standardWindowButton(.closeButton)?.isHidden = true hideWindowButtons()
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
// Disallow tabbing if the titlebar is hidden, since that will (should) also hide the tab bar. // Disallow tabbing if the titlebar is hidden, since that will (should) also hide the tab bar.
window.tabbingMode = .disallowed window.tabbingMode = .disallowed
@ -456,6 +462,10 @@ class TerminalController: BaseTerminalController {
y: config.windowPositionY, y: config.windowPositionY,
windowDecorations: config.windowDecorations) windowDecorations: config.windowDecorations)
if config.macosWindowButtons == .hidden {
hideWindowButtons()
}
// Make sure our theme is set on the window so styling is correct. // Make sure our theme is set on the window so styling is correct.
if let windowTheme = config.windowTheme { if let windowTheme = config.windowTheme {
window.windowTheme = .init(rawValue: windowTheme) window.windowTheme = .init(rawValue: windowTheme)
@ -872,17 +882,20 @@ class TerminalController: BaseTerminalController {
struct DerivedConfig { struct DerivedConfig {
let backgroundColor: Color let backgroundColor: Color
let macosWindowButtons: Ghostty.MacOSWindowButtons
let macosTitlebarStyle: String let macosTitlebarStyle: String
let maximize: Bool let maximize: Bool
init() { init() {
self.backgroundColor = Color(NSColor.windowBackgroundColor) self.backgroundColor = Color(NSColor.windowBackgroundColor)
self.macosWindowButtons = .visible
self.macosTitlebarStyle = "system" self.macosTitlebarStyle = "system"
self.maximize = false self.maximize = false
} }
init(_ config: Ghostty.Config) { init(_ config: Ghostty.Config) {
self.backgroundColor = config.backgroundColor self.backgroundColor = config.backgroundColor
self.macosWindowButtons = config.macosWindowButtons
self.macosTitlebarStyle = config.macosTitlebarStyle self.macosTitlebarStyle = config.macosTitlebarStyle
self.maximize = config.maximize self.maximize = config.maximize
} }

View File

@ -45,6 +45,18 @@ class TerminalWindow: NSWindow {
}, },
] ]
private var hasWindowButtons: Bool {
get {
if let close = standardWindowButton(.closeButton),
let miniaturize = standardWindowButton(.miniaturizeButton),
let zoom = standardWindowButton(.zoomButton) {
return !(close.isHidden && miniaturize.isHidden && zoom.isHidden)
} else {
return false
}
}
}
// Both of these must be true for windows without decorations to be able to // Both of these must be true for windows without decorations to be able to
// still become key/main and receive events. // still become key/main and receive events.
override var canBecomeKey: Bool { return true } override var canBecomeKey: Bool { return true }
@ -613,7 +625,7 @@ class TerminalWindow: NSWindow {
view.translatesAutoresizingMaskIntoConstraints = false view.translatesAutoresizingMaskIntoConstraints = false
view.leftAnchor.constraint(equalTo: toolbarView.leftAnchor).isActive = true view.leftAnchor.constraint(equalTo: toolbarView.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: toolbarView.leftAnchor, constant: 78).isActive = true view.rightAnchor.constraint(equalTo: toolbarView.leftAnchor, constant: hasWindowButtons ? 78 : 0).isActive = true
view.topAnchor.constraint(equalTo: toolbarView.topAnchor).isActive = true view.topAnchor.constraint(equalTo: toolbarView.topAnchor).isActive = true
view.heightAnchor.constraint(equalTo: toolbarView.heightAnchor).isActive = true view.heightAnchor.constraint(equalTo: toolbarView.heightAnchor).isActive = true

View File

@ -250,6 +250,17 @@ extension Ghostty {
return String(cString: ptr) return String(cString: ptr)
} }
var macosWindowButtons: MacOSWindowButtons {
let defaultValue = MacOSWindowButtons.visible
guard let config = self.config else { return defaultValue }
var v: UnsafePointer<Int8>? = nil
let key = "macos-window-buttons"
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
guard let ptr = v else { return defaultValue }
let str = String(cString: ptr)
return MacOSWindowButtons(rawValue: str) ?? defaultValue
}
var macosTitlebarStyle: String { var macosTitlebarStyle: String {
let defaultValue = "transparent" let defaultValue = "transparent"
guard let config = self.config else { return defaultValue } guard let config = self.config else { return defaultValue }

View File

@ -239,6 +239,12 @@ extension Ghostty {
case chrome case chrome
} }
/// Enum for the macos-window-buttons config option
enum MacOSWindowButtons: String {
case visible
case hidden
}
/// Enum for the macos-titlebar-proxy-icon config option /// Enum for the macos-titlebar-proxy-icon config option
enum MacOSTitlebarProxyIcon: String { enum MacOSTitlebarProxyIcon: String {
case visible case visible

View File

@ -2069,6 +2069,25 @@ keybind: Keybinds = .{},
/// it will retain the previous setting until fullscreen is exited. /// it will retain the previous setting until fullscreen is exited.
@"macos-non-native-fullscreen": NonNativeFullscreen = .false, @"macos-non-native-fullscreen": NonNativeFullscreen = .false,
/// Whether the window buttons in the macOS titlebar are visible. The window
/// buttons are the colored buttons in the upper left corner of most macOS apps,
/// also known as the traffic lights, that allow you to close, miniaturize, and
/// zoom the window.
///
/// This setting has no effect when `window-decoration = false` or
/// `macos-titlebar-style = hidden`, as the window buttons are always hidden in
/// these modes.
///
/// Valid values are:
///
/// * `visible` - Show the window buttons.
/// * `hidden` - Hide the window buttons.
///
/// The default value is `visible`.
///
/// Changing this option at runtime only applies to new windows.
@"macos-window-buttons": MacWindowButtons = .visible,
/// The style of the macOS titlebar. Available values are: "native", /// The style of the macOS titlebar. Available values are: "native",
/// "transparent", "tabs", and "hidden". /// "transparent", "tabs", and "hidden".
/// ///
@ -5819,6 +5838,12 @@ pub const WindowColorspace = enum {
@"display-p3", @"display-p3",
}; };
/// See macos-window-buttons
pub const MacWindowButtons = enum {
visible,
hidden,
};
/// See macos-titlebar-style /// See macos-titlebar-style
pub const MacTitlebarStyle = enum { pub const MacTitlebarStyle = enum {
native, native,