diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 2d8314a7f..8179e1950 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -496,6 +496,11 @@ class AppDelegate: NSObject, // AppKit mutex on the appearance. DispatchQueue.main.async { self.syncAppearance() } + // Update all of our windows + terminalManager.windows.forEach { window in + window.controller.configDidReload() + } + // If we have configuration errors, we need to show them. let c = ConfigurationErrorsController.sharedInstance c.errors = state.config.errors diff --git a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift index 82052eabb..c382a62a0 100644 --- a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift +++ b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift @@ -33,6 +33,11 @@ class QuickTerminalController: BaseTerminalController { selector: #selector(onToggleFullscreen), name: Ghostty.Notification.ghosttyToggleFullscreen, object: nil) + center.addObserver( + self, + selector: #selector(ghosttyDidReloadConfig), + name: Ghostty.Notification.ghosttyDidReloadConfig, + object: nil) } required init?(coder: NSCoder) { @@ -48,8 +53,6 @@ class QuickTerminalController: BaseTerminalController { // MARK: NSWindowController override func windowDidLoad() { - super.windowDidLoad() - guard let window = self.window else { return } // The controller is the window delegate so we can detect events such as @@ -60,6 +63,9 @@ class QuickTerminalController: BaseTerminalController { // make this restorable, but it isn't currently implemented. window.isRestorable = false + // Setup our configured appearance that we support. + syncAppearance() + // Setup our initial size based on our configured position position.setLoaded(window) @@ -291,6 +297,35 @@ class QuickTerminalController: BaseTerminalController { }) } + private func syncAppearance() { + guard let window else { return } + + // If our window is not visible, then delay this. This is possible specifically + // during state restoration but probably in other scenarios as well. To delay, + // we just loop directly on the dispatch queue. We have to delay because some + // APIs such as window blur have no effect unless the window is visible. + guard window.isVisible else { + // Weak window so that if the window changes or is destroyed we aren't holding a ref + DispatchQueue.main.async { [weak self] in self?.syncAppearance() } + return + } + + // If we have window transparency then set it transparent. Otherwise set it opaque. + if (ghostty.config.backgroundOpacity < 1) { + window.isOpaque = false + + // This is weird, but we don't use ".clear" because this creates a look that + // matches Terminal.app much more closer. This lets users transition from + // Terminal.app more easily. + window.backgroundColor = .white.withAlphaComponent(0.001) + + ghostty_set_window_background_blur(ghostty.app, Unmanaged.passUnretained(window).toOpaque()) + } else { + window.isOpaque = true + window.backgroundColor = .windowBackgroundColor + } + } + // MARK: First Responder @IBAction override func closeWindow(_ sender: Any) { @@ -322,6 +357,10 @@ class QuickTerminalController: BaseTerminalController { // We ignore the requested mode and always use non-native for the quick terminal toggleFullscreen(mode: .nonNative) } + + @objc private func ghosttyDidReloadConfig(notification: SwiftUI.Notification) { + syncAppearance() + } } extension Notification.Name { diff --git a/macos/Sources/Features/Terminal/BaseTerminalController.swift b/macos/Sources/Features/Terminal/BaseTerminalController.swift index 95c8d637d..000d72418 100644 --- a/macos/Sources/Features/Terminal/BaseTerminalController.swift +++ b/macos/Sources/Features/Terminal/BaseTerminalController.swift @@ -93,11 +93,6 @@ class BaseTerminalController: NSWindowController, selector: #selector(didChangeScreenParametersNotification), name: NSApplication.didChangeScreenParametersNotification, object: nil) - center.addObserver( - self, - selector: #selector(ghosttyDidReloadConfigNotification), - name: Ghostty.Notification.ghosttyDidReloadConfig, - object: nil) // Listen for local events that we need to know of outside of // single surface handlers. @@ -128,7 +123,7 @@ class BaseTerminalController: NSWindowController, /// Update all surfaces with the focus state. This ensures that libghostty has an accurate view about /// what surface is focused. This must be called whenever a surface OR window changes focus. - private func syncFocusToSurfaceTree() { + func syncFocusToSurfaceTree() { guard let tree = self.surfaceTree else { return } for leaf in tree { @@ -141,48 +136,6 @@ class BaseTerminalController: NSWindowController, } } - // Call this whenever we want to setup our appearance parameters based on - // configuration changes. - private func syncAppearance() { - guard let window else { return } - - // If our window is not visible, then delay this. This is possible specifically - // during state restoration but probably in other scenarios as well. To delay, - // we just loop directly on the dispatch queue. We have to delay because some - // APIs such as window blur have no effect unless the window is visible. - guard window.isVisible else { - // Weak window so that if the window changes or is destroyed we aren't holding a ref - DispatchQueue.main.async { [weak self] in self?.syncAppearance() } - return - } - - // If we have window transparency then set it transparent. Otherwise set it opaque. - if (ghostty.config.backgroundOpacity < 1) { - window.isOpaque = false - - // This is weird, but we don't use ".clear" because this creates a look that - // matches Terminal.app much more closer. This lets users transition from - // Terminal.app more easily. - window.backgroundColor = .white.withAlphaComponent(0.001) - - ghostty_set_window_background_blur(ghostty.app, Unmanaged.passUnretained(window).toOpaque()) - } else { - window.isOpaque = true - window.backgroundColor = .windowBackgroundColor - } - - // Terminals typically operate in sRGB color space and macOS defaults - // to "native" which is typically P3. There is a lot more resources - // covered in this GitHub issue: https://github.com/mitchellh/ghostty/pull/376 - // Ghostty defaults to sRGB but this can be overridden. - switch (ghostty.config.windowColorspace) { - case .displayP3: - window.colorSpace = .displayP3 - case .srgb: - window.colorSpace = .sRGB - } - } - // Call this whenever the frame changes private func windowFrameDidChange() { // We need to update our saved frame information in case of monitor @@ -192,14 +145,6 @@ class BaseTerminalController: NSWindowController, savedFrame = .init(window: window.frame, screen: screen.visibleFrame) } - // MARK: Overridable Callbacks - - /// Called whenever Ghostty reloads the configuration. Callers should call super. - open func ghosttyDidReloadConfig() { - // Whenever the config changes we setup our appearance. - syncAppearance() - } - // MARK: Notifications @objc private func didChangeScreenParametersNotification(_ notification: Notification) { @@ -246,10 +191,6 @@ class BaseTerminalController: NSWindowController, window.setFrame(newFrame, display: true) } - @objc private func ghosttyDidReloadConfigNotification(notification: SwiftUI.Notification) { - ghosttyDidReloadConfig() - } - // MARK: Local Events private func localEventHandler(_ event: NSEvent) -> NSEvent? { @@ -440,16 +381,7 @@ class BaseTerminalController: NSWindowController, } } - // MARK: NSWindowController - - override func windowDidLoad() { - super.windowDidLoad() - - // Setup our configured appearance that we support. - syncAppearance() - } - - // MARK: NSWindowDelegate + //MARK: - NSWindowDelegate // This is called when performClose is called on a window (NOT when close() // is called directly). performClose is called primarily when UI elements such diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index f0216d520..f31740105 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -78,16 +78,14 @@ class TerminalController: BaseTerminalController { } } - override func ghosttyDidReloadConfig() { - super.ghosttyDidReloadConfig() + //MARK: - Methods + func configDidReload() { guard let window = window as? TerminalWindow else { return } window.focusFollowsMouse = ghostty.config.focusFollowsMouse syncAppearance() } - //MARK: - Methods - /// Update the accessory view of each tab according to the keyboard /// shortcut that activates it (if any). This is called when the key window /// changes, when a window is closed, and when tabs are reordered @@ -166,6 +164,21 @@ class TerminalController: BaseTerminalController { window.titlebarFont = nil } + // If we have window transparency then set it transparent. Otherwise set it opaque. + if (ghostty.config.backgroundOpacity < 1) { + window.isOpaque = false + + // This is weird, but we don't use ".clear" because this creates a look that + // matches Terminal.app much more closer. This lets users transition from + // Terminal.app more easily. + window.backgroundColor = .white.withAlphaComponent(0.001) + + ghostty_set_window_background_blur(ghostty.app, Unmanaged.passUnretained(window).toOpaque()) + } else { + window.isOpaque = true + window.backgroundColor = .windowBackgroundColor + } + window.hasShadow = ghostty.config.macosWindowShadow guard window.hasStyledTabs else { return } @@ -195,20 +208,31 @@ class TerminalController: BaseTerminalController { } override func windowDidLoad() { - super.windowDidLoad() - guard let window = window as? TerminalWindow else { return } - + // Setting all three of these is required for restoration to work. window.isRestorable = restorable if (restorable) { window.restorationClass = TerminalWindowRestoration.self window.identifier = .init(String(describing: TerminalWindowRestoration.self)) } - + // If window decorations are disabled, remove our title if (!ghostty.config.windowDecorations) { window.styleMask.remove(.titled) } - + + // Terminals typically operate in sRGB color space and macOS defaults + // to "native" which is typically P3. There is a lot more resources + // covered in this GitHub issue: https://github.com/mitchellh/ghostty/pull/376 + // Ghostty defaults to sRGB but this can be overridden. + switch (ghostty.config.windowColorspace) { + case "display-p3": + window.colorSpace = .displayP3 + case "srgb": + fallthrough + default: + window.colorSpace = .sRGB + } + // If we have only a single surface (no splits) and that surface requested // an initial size then we set it here now. if case let .leaf(leaf) = surfaceTree { @@ -221,21 +245,21 @@ class TerminalController: BaseTerminalController { frame.size.height -= leaf.surface.frame.size.height frame.size.width += min(initialSize.width, screen.frame.width) frame.size.height += min(initialSize.height, screen.frame.height) - + // We have no tabs and we are not a split, so set the initial size of the window. window.setFrame(frame, display: true) } } - + // Center the window to start, we'll move the window frame automatically // when cascading. window.center() - + // Make sure our theme is set on the window so styling is correct. if let windowTheme = ghostty.config.windowTheme { window.windowTheme = .init(rawValue: windowTheme) } - + // 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. @@ -248,50 +272,50 @@ class TerminalController: BaseTerminalController { } else if (ghostty.config.macosTitlebarStyle == "transparent") { window.transparentTabs = true } - + if window.hasStyledTabs { // Set the background color of the window let backgroundColor = NSColor(ghostty.config.backgroundColor) window.backgroundColor = backgroundColor - + // This makes sure our titlebar renders correctly when there is a transparent background window.titlebarColor = backgroundColor.withAlphaComponent(ghostty.config.backgroundOpacity) } - + // Initialize our content view to the SwiftUI root window.contentView = NSHostingView(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, + .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. There are // some operations that appear to bring back the titlebar visibility so this ensures // it is gone forever. @@ -300,7 +324,7 @@ class TerminalController: BaseTerminalController { 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. @@ -320,9 +344,9 @@ class TerminalController: BaseTerminalController { window.tabGroup?.removeWindow(window) } } - + window.focusFollowsMouse = ghostty.config.focusFollowsMouse - + // Apply any additional appearance-related properties to the new window. syncAppearance() } diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index f8375adf1..b8c7d2594 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -128,14 +128,13 @@ extension Ghostty { return v } - var windowColorspace: WindowColorspace { - guard let config = self.config else { return .srgb } + var windowColorspace: String { + guard let config = self.config else { return "" } var v: UnsafePointer? = nil let key = "window-colorspace" - guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .srgb } - guard let ptr = v else { return .srgb } - let str = String(cString: ptr) - return WindowColorspace(rawValue: str) ?? .srgb + guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" } + guard let ptr = v else { return "" } + return String(cString: ptr) } var windowSaveState: String { @@ -475,9 +474,4 @@ extension Ghostty.Config { } } } - - enum WindowColorspace : String { - case srgb - case displayP3 = "display-p3" - } }