From 2415116ad0680c9ae2d18f8445e067b24830ca49 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Dec 2025 13:52:59 -0800 Subject: [PATCH] Revert "macOS: move `NSGlassEffectView` into `TerminalViewContainer` (#10046)" This reverts commit b8490f40c5a88d3ef879043d05a5493f24e123d4, reversing changes made to 050278feaeb79fe9d849fca48e445acadcd74397. --- macos/Ghostty.xcodeproj/project.pbxproj | 1 - .../QuickTerminalController.swift | 13 +- .../Terminal/BaseTerminalController.swift | 2 +- .../Terminal/TerminalController.swift | 4 +- .../Terminal/TerminalViewContainer.swift | 127 ------------------ .../Window Styles/TerminalWindow.swift | 62 ++++++++- macos/Sources/Ghostty/Ghostty.Config.swift | 12 +- .../Sources/Ghostty/SurfaceView_AppKit.swift | 3 - 8 files changed, 68 insertions(+), 156 deletions(-) delete mode 100644 macos/Sources/Features/Terminal/TerminalViewContainer.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index feda1bed0..91c2300cc 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -118,7 +118,6 @@ Features/Terminal/TerminalRestorable.swift, Features/Terminal/TerminalTabColor.swift, Features/Terminal/TerminalView.swift, - Features/Terminal/TerminalViewContainer.swift, "Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift", "Features/Terminal/Window Styles/Terminal.xib", "Features/Terminal/Window Styles/TerminalHiddenTitlebar.xib", diff --git a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift index 07c0c4c19..8a642034f 100644 --- a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift +++ b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift @@ -137,11 +137,11 @@ class QuickTerminalController: BaseTerminalController { } // Setup our content - window.contentView = TerminalViewContainer( + window.contentView = NSHostingView(rootView: TerminalView( ghostty: self.ghostty, viewModel: self, delegate: self - ) + )) // Clear out our frame at this point, the fixup from above is complete. if let qtWindow = window as? QuickTerminalWindow { @@ -609,7 +609,7 @@ class QuickTerminalController: BaseTerminalController { // If we have window transparency then set it transparent. Otherwise set it opaque. // Also check if the user has overridden transparency to be fully opaque. - if !isBackgroundOpaque && (self.derivedConfig.backgroundOpacity < 1 || derivedConfig.backgroundBlur.isGlassStyle) { + if !isBackgroundOpaque && self.derivedConfig.backgroundOpacity < 1 { window.isOpaque = false // This is weird, but we don't use ".clear" because this creates a look that @@ -617,9 +617,7 @@ class QuickTerminalController: BaseTerminalController { // Terminal.app more easily. window.backgroundColor = .white.withAlphaComponent(0.001) - if !derivedConfig.backgroundBlur.isGlassStyle { - ghostty_set_window_background_blur(ghostty.app, Unmanaged.passUnretained(window).toOpaque()) - } + ghostty_set_window_background_blur(ghostty.app, Unmanaged.passUnretained(window).toOpaque()) } else { window.isOpaque = true window.backgroundColor = .windowBackgroundColor @@ -724,7 +722,6 @@ class QuickTerminalController: BaseTerminalController { let quickTerminalSpaceBehavior: QuickTerminalSpaceBehavior let quickTerminalSize: QuickTerminalSize let backgroundOpacity: Double - let backgroundBlur: Ghostty.Config.BackgroundBlur init() { self.quickTerminalScreen = .main @@ -733,7 +730,6 @@ class QuickTerminalController: BaseTerminalController { self.quickTerminalSpaceBehavior = .move self.quickTerminalSize = QuickTerminalSize() self.backgroundOpacity = 1.0 - self.backgroundBlur = .disabled } init(_ config: Ghostty.Config) { @@ -743,7 +739,6 @@ class QuickTerminalController: BaseTerminalController { self.quickTerminalSpaceBehavior = config.quickTerminalSpaceBehavior self.quickTerminalSize = config.quickTerminalSize self.backgroundOpacity = config.backgroundOpacity - self.backgroundBlur = config.backgroundBlur } } diff --git a/macos/Sources/Features/Terminal/BaseTerminalController.swift b/macos/Sources/Features/Terminal/BaseTerminalController.swift index 1750e949d..d79c89d2d 100644 --- a/macos/Sources/Features/Terminal/BaseTerminalController.swift +++ b/macos/Sources/Features/Terminal/BaseTerminalController.swift @@ -1298,7 +1298,7 @@ extension BaseTerminalController: NSMenuItemValidation { } else { scheme = GHOSTTY_COLOR_SCHEME_LIGHT } - guard scheme != appliedColorScheme, !surfaceTree.isEmpty else { + guard scheme != appliedColorScheme else { return } for surfaceView in surfaceTree { diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index c5481851b..bccdd9c69 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -936,11 +936,11 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr } // Initialize our content view to the SwiftUI root - window.contentView = TerminalViewContainer( + window.contentView = NSHostingView(rootView: TerminalView( ghostty: self.ghostty, viewModel: self, delegate: self, - ) + )) // If we have a default size, we want to apply it. if let defaultSize { diff --git a/macos/Sources/Features/Terminal/TerminalViewContainer.swift b/macos/Sources/Features/Terminal/TerminalViewContainer.swift deleted file mode 100644 index f4e2fc080..000000000 --- a/macos/Sources/Features/Terminal/TerminalViewContainer.swift +++ /dev/null @@ -1,127 +0,0 @@ -import AppKit -import SwiftUI - -/// Use this container to achieve a glass effect at the window level. -/// Modifying `NSThemeFrame` can sometimes be unpredictable. -class TerminalViewContainer: NSView { - private let terminalView: NSView - - /// Glass effect view for liquid glass background when transparency is enabled - private var glassEffectView: NSView? - private var derivedConfig: DerivedConfig - - init(ghostty: Ghostty.App, viewModel: ViewModel, delegate: (any TerminalViewDelegate)? = nil) { - self.derivedConfig = DerivedConfig(config: ghostty.config) - self.terminalView = NSHostingView(rootView: TerminalView( - ghostty: ghostty, - viewModel: viewModel, - delegate: delegate - )) - super.init(frame: .zero) - setup() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setup() { - addSubview(terminalView) - terminalView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - terminalView.topAnchor.constraint(equalTo: topAnchor), - terminalView.leadingAnchor.constraint(equalTo: leadingAnchor), - terminalView.bottomAnchor.constraint(equalTo: bottomAnchor), - terminalView.trailingAnchor.constraint(equalTo: trailingAnchor), - ]) - - NotificationCenter.default.addObserver( - self, - selector: #selector(ghosttyConfigDidChange(_:)), - name: .ghosttyConfigDidChange, - object: nil - ) - } - - override func viewDidMoveToWindow() { - super.viewDidMoveToWindow() - updateGlassEffectIfNeeded() - } - - @objc private func ghosttyConfigDidChange(_ notification: Notification) { - guard let config = notification.userInfo?[ - Notification.Name.GhosttyConfigChangeKey - ] as? Ghostty.Config else { return } - let newValue = DerivedConfig(config: config) - guard newValue != derivedConfig else { return } - derivedConfig = newValue - DispatchQueue.main.async(execute: updateGlassEffectIfNeeded) - } -} - -// MARK: Glass - -private extension TerminalViewContainer { -#if compiler(>=6.2) - @available(macOS 26.0, *) - func addGlassEffectViewIfNeeded() -> NSGlassEffectView? { - if let existed = glassEffectView as? NSGlassEffectView { - return existed - } - guard let themeFrameView = window?.contentView?.superview else { - return nil - } - let effectView = NSGlassEffectView() - addSubview(effectView, positioned: .below, relativeTo: terminalView) - effectView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - effectView.topAnchor.constraint(equalTo: topAnchor, constant: -themeFrameView.safeAreaInsets.top), - effectView.leadingAnchor.constraint(equalTo: leadingAnchor), - effectView.bottomAnchor.constraint(equalTo: bottomAnchor), - effectView.trailingAnchor.constraint(equalTo: trailingAnchor), - ]) - glassEffectView = effectView - return effectView - } -#endif // compiler(>=6.2) - - func updateGlassEffectIfNeeded() { -#if compiler(>=6.2) - guard #available(macOS 26.0, *), derivedConfig.backgroundBlur.isGlassStyle else { - glassEffectView?.removeFromSuperview() - glassEffectView = nil - return - } - guard let effectView = addGlassEffectViewIfNeeded() else { - return - } - switch derivedConfig.backgroundBlur { - case .macosGlassRegular: - effectView.style = NSGlassEffectView.Style.regular - case .macosGlassClear: - effectView.style = NSGlassEffectView.Style.clear - default: - break - } - let backgroundColor = (window as? TerminalWindow)?.preferredBackgroundColor ?? NSColor(derivedConfig.backgroundColor) - effectView.tintColor = backgroundColor - .withAlphaComponent(derivedConfig.backgroundOpacity) - if let window, window.responds(to: Selector(("_cornerRadius"))), let cornerRadius = window.value(forKey: "_cornerRadius") as? CGFloat { - effectView.cornerRadius = cornerRadius - } -#endif // compiler(>=6.2) - } - - struct DerivedConfig: Equatable { - var backgroundOpacity: Double = 0 - var backgroundBlur: Ghostty.Config.BackgroundBlur - var backgroundColor: Color = .clear - - init(config: Ghostty.Config) { - self.backgroundBlur = config.backgroundBlur - self.backgroundOpacity = config.backgroundOpacity - self.backgroundColor = config.backgroundColor - } - } -} diff --git a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift index 9debd2cb3..4196df97f 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift @@ -474,7 +474,7 @@ class TerminalWindow: NSWindow { let forceOpaque = terminalController?.isBackgroundOpaque ?? false if !styleMask.contains(.fullScreen) && !forceOpaque && - (surfaceConfig.backgroundOpacity < 1 || surfaceConfig.backgroundBlur.isGlassStyle) + surfaceConfig.backgroundOpacity < 1 { isOpaque = false @@ -483,8 +483,15 @@ class TerminalWindow: NSWindow { // Terminal.app more easily. backgroundColor = .white.withAlphaComponent(0.001) - // We don't need to set blur when using glass - if !surfaceConfig.backgroundBlur.isGlassStyle, let appDelegate = NSApp.delegate as? AppDelegate { + // Add liquid glass behind terminal content + if #available(macOS 26.0, *), derivedConfig.backgroundBlur.isGlassStyle { + setupGlassLayer() + } else if let appDelegate = NSApp.delegate as? AppDelegate { + // If we had a prior glass layer we should remove it + if #available(macOS 26.0, *) { + removeGlassLayer() + } + ghostty_set_window_background_blur( appDelegate.ghostty.app, Unmanaged.passUnretained(self).toOpaque()) @@ -492,6 +499,11 @@ class TerminalWindow: NSWindow { } else { isOpaque = true + // Remove liquid glass when not transparent + if #available(macOS 26.0, *) { + removeGlassLayer() + } + let backgroundColor = preferredBackgroundColor ?? NSColor(surfaceConfig.backgroundColor) self.backgroundColor = backgroundColor.withAlphaComponent(1) } @@ -569,6 +581,50 @@ class TerminalWindow: NSWindow { NotificationCenter.default.removeObserver(observer) } } + +#if compiler(>=6.2) + // MARK: Glass + + @available(macOS 26.0, *) + private func setupGlassLayer() { + // Remove existing glass effect view + removeGlassLayer() + + // Get the window content view (parent of the NSHostingView) + guard let contentView else { return } + guard let windowContentView = contentView.superview else { return } + + // Create NSGlassEffectView for native glass effect + let effectView = NSGlassEffectView() + + // Map Ghostty config to NSGlassEffectView style + switch derivedConfig.backgroundBlur { + case .macosGlassRegular: + effectView.style = NSGlassEffectView.Style.regular + case .macosGlassClear: + effectView.style = NSGlassEffectView.Style.clear + default: + // Should not reach here since we check for glass style before calling + // setupGlassLayer() + assertionFailure() + } + + effectView.cornerRadius = derivedConfig.windowCornerRadius + effectView.tintColor = preferredBackgroundColor + effectView.frame = windowContentView.bounds + effectView.autoresizingMask = [.width, .height] + + // Position BELOW the terminal content to act as background + windowContentView.addSubview(effectView, positioned: .below, relativeTo: contentView) + glassEffectView = effectView + } + + @available(macOS 26.0, *) + private func removeGlassLayer() { + glassEffectView?.removeFromSuperview() + glassEffectView = nil + } +#endif // compiler(>=6.2) // MARK: Config diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index b3a8700e9..5aa79a149 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -658,17 +658,9 @@ extension Ghostty.Config { case 0: self = .disabled case -1: - if #available(macOS 26.0, *) { - self = .macosGlassRegular - } else { - self = .disabled - } + self = .macosGlassRegular case -2: - if #available(macOS 26.0, *) { - self = .macosGlassClear - } else { - self = .disabled - } + self = .macosGlassClear default: self = .radius(Int(value)) } diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 77e1c43d4..37cc9282e 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -1654,7 +1654,6 @@ extension Ghostty { struct DerivedConfig { let backgroundColor: Color let backgroundOpacity: Double - let backgroundBlur: Ghostty.Config.BackgroundBlur let macosWindowShadow: Bool let windowTitleFontFamily: String? let windowAppearance: NSAppearance? @@ -1663,7 +1662,6 @@ extension Ghostty { init() { self.backgroundColor = Color(NSColor.windowBackgroundColor) self.backgroundOpacity = 1 - self.backgroundBlur = .disabled self.macosWindowShadow = true self.windowTitleFontFamily = nil self.windowAppearance = nil @@ -1673,7 +1671,6 @@ extension Ghostty { init(_ config: Ghostty.Config) { self.backgroundColor = config.backgroundColor self.backgroundOpacity = config.backgroundOpacity - self.backgroundBlur = config.backgroundBlur self.macosWindowShadow = config.macosWindowShadow self.windowTitleFontFamily = config.windowTitleFontFamily self.windowAppearance = .init(ghosttyConfig: config)