From 16919488da29842cfc5c3f6aa8229c5c1c2026ec Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Fri, 20 Sep 2024 16:11:51 -0600 Subject: [PATCH] macOS: add `macos-titlebar-style = hidden` Hides titlebar without removing the other typical window frame elements --- macos/Ghostty.xcodeproj/project.pbxproj | 4 ++ .../Terminal/TerminalController.swift | 40 ++++++++++++++++++- .../Features/Terminal/TerminalManager.swift | 29 ++++++++------ .../Features/Terminal/TerminalView.swift | 2 + .../Helpers/EventSinkHostingView.swift | 33 +++++++++++++++ src/config/Config.zig | 8 +++- 6 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 macos/Sources/Helpers/EventSinkHostingView.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 0c0b95418..0c38f12fd 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -72,6 +72,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 */; }; + AEA619802C9E1DF7004B3751 /* EventSinkHostingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEA6197F2C9E1DE5004B3751 /* EventSinkHostingView.swift */; }; AEE8B3452B9AA39600260C5E /* NSPasteboard+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.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 */; }; @@ -142,6 +143,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 = ""; }; + AEA6197F2C9E1DE5004B3751 /* EventSinkHostingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventSinkHostingView.swift; sourceTree = ""; }; AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPasteboard+Extension.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 = ""; }; @@ -214,6 +216,7 @@ 8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */, A59630962AEE163600D64628 /* HostingWindow.swift */, A59FB5D02AE0DEA7009128F3 /* MetalView.swift */, + AEA6197F2C9E1DE5004B3751 /* EventSinkHostingView.swift */, C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */, C1F26EA62B738B9900404083 /* NSView+Extension.swift */, AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */, @@ -541,6 +544,7 @@ A55685E029A03A9F004303CE /* AppError.swift in Sources */, A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */, A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */, + AEA619802C9E1DF7004B3751 /* EventSinkHostingView.swift in Sources */, A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */, A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */, A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */, diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index bc69255fc..aaabd3c8c 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -328,12 +328,48 @@ class TerminalController: NSWindowController, NSWindowDelegate, } // Initialize our content view to the SwiftUI root - window.contentView = NSHostingView(rootView: TerminalView( + window.contentView = EventSinkHostingView(rootView: TerminalView( ghostty: self.ghostty, viewModel: self, delegate: self )) + // If our titlebar style is "hidden" we adjust the style appropriately + if (ghostty.config.macosTitlebarStyle == "hidden") { + window.styleMask = [ + // We need `titled` in the mask to get the normal window frame + .titled, + + // Full size content view so we can extend + // content in to the hidden titlebar's area + .fullSizeContentView, + + .resizable, + .closable, + .miniaturizable, + ] + + // Hide the title + window.titleVisibility = .hidden + window.titlebarAppearsTransparent = true + + // Hide the traffic lights (window control buttons) + window.standardWindowButton(.closeButton)?.isHidden = true + 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. + window.tabbingMode = .disallowed + + // Nuke it from orbit -- hide the titlebar container entirely, just in case. + if let themeFrame = window.contentView?.superview { + // Hide the titlebar container + if let titleBarContainer = themeFrame.firstDescendant(withClassName: "NSTitlebarContainerView") { + titleBarContainer.isHidden = true + } + } + } + // In various situations, macOS automatically tabs new windows. Ghostty handles // its own tabbing so we DONT want this behavior. This detects this scenario and undoes // it. @@ -634,7 +670,7 @@ class TerminalController: NSWindowController, NSWindowDelegate, // Custom toolbar-based title used when titlebar tabs are enabled. if let toolbar = window.toolbar as? TerminalToolbar { - if (window.titlebarTabs) { + if (window.titlebarTabs || ghostty.config.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. diff --git a/macos/Sources/Features/Terminal/TerminalManager.swift b/macos/Sources/Features/Terminal/TerminalManager.swift index 2559e1ec8..8b9ed3cad 100644 --- a/macos/Sources/Features/Terminal/TerminalManager.swift +++ b/macos/Sources/Features/Terminal/TerminalManager.swift @@ -142,19 +142,24 @@ class TerminalManager { // the macOS APIs only work on a visible window. controller.showWindow(self) - // Add the window to the tab group and show it. - switch ghostty.config.windowNewTabPosition { - case "end": - // If we already have a tab group and we want the new tab to open at the end, - // then we use the last window in the tab group as the parent. - if let last = parent.tabGroup?.windows.last { - last.addTabbedWindow(window, ordered: .above) - } else { - fallthrough + // If we have the "hidden" titlebar style we want to create new + // tabs as windows instead, so just skip adding it to the parent. + if (ghostty.config.macosTitlebarStyle != "hidden") { + // Add the window to the tab group and show it. + switch ghostty.config.windowNewTabPosition { + case "end": + // If we already have a tab group and we want the new tab to open at the end, + // then we use the last window in the tab group as the parent. + if let last = parent.tabGroup?.windows.last { + last.addTabbedWindow(window, ordered: .above) + } else { + fallthrough + } + case "current": fallthrough + default: + parent.addTabbedWindow(window, ordered: .above) + } - case "current": fallthrough - default: - parent.addTabbedWindow(window, ordered: .above) } window.makeKeyAndOrderFront(self) diff --git a/macos/Sources/Features/Terminal/TerminalView.swift b/macos/Sources/Features/Terminal/TerminalView.swift index 33193fb0e..196cd2f47 100644 --- a/macos/Sources/Features/Terminal/TerminalView.swift +++ b/macos/Sources/Features/Terminal/TerminalView.swift @@ -108,6 +108,8 @@ struct TerminalView: View { self.delegate?.zoomStateDidChange(to: newValue ?? false) } } + // Ignore safe area to extend up in to the titlebar region if we have the "hidden" titlebar style + .ignoresSafeArea(.container, edges: ghostty.config.macosTitlebarStyle == "hidden" ? .top : []) } } } diff --git a/macos/Sources/Helpers/EventSinkHostingView.swift b/macos/Sources/Helpers/EventSinkHostingView.swift new file mode 100644 index 000000000..f5b3cbe1c --- /dev/null +++ b/macos/Sources/Helpers/EventSinkHostingView.swift @@ -0,0 +1,33 @@ +import SwiftUI + +/// Custom subclass of NSHostingView which sinks events so that we can +/// stop the window from receiving events originating from within this view. +class EventSinkHostingView: NSHostingView { + override var acceptsFirstResponder: Bool { + return true + } + + override func becomeFirstResponder() -> Bool { + return true + } + + override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + return true + } + + override func mouseDown(with event: NSEvent) { + // Do nothing + } + + override func mouseDragged(with event: NSEvent) { + // Do nothing + } + + override func mouseUp(with event: NSEvent) { + // Do nothing + } + + override var mouseDownCanMoveWindow: Bool { + return false + } +} diff --git a/src/config/Config.zig b/src/config/Config.zig index fd7ce996f..bdddaf4f2 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1304,7 +1304,7 @@ keybind: Keybinds = .{}, @"macos-non-native-fullscreen": NonNativeFullscreen = .false, /// The style of the macOS titlebar. Available values are: "native", -/// "transparent", and "tabs". +/// "transparent", "tabs", and "hidden". /// /// The "native" style uses the native macOS titlebar with zero customization. /// The titlebar will match your window theme (see `window-theme`). @@ -1321,6 +1321,11 @@ keybind: Keybinds = .{}, /// macOS 14 does not have this issue and any other macOS version has not /// been tested. /// +/// The "hidden" style hides the titlebar. Unlike `window-decoration = false`, +/// however, it does not remove the frame from the window or cause it to have +/// squared corners. Changing to or from this option at run-time may affect +/// existing windows in buggy ways. +/// /// The default value is "transparent". This is an opinionated choice /// but its one I think is the most aesthetically pleasing and works in /// most cases. @@ -4269,6 +4274,7 @@ pub const MacTitlebarStyle = enum { native, transparent, tabs, + hidden, }; /// See gtk-single-instance