From 1235daff045c0cb843966d318a922fd319829557 Mon Sep 17 00:00:00 2001 From: martinezhermes Date: Fri, 20 Mar 2026 09:48:12 +0100 Subject: [PATCH] fix: apply background-opacity and background-blur to terminal rendering area Two root causes for issue #879: 1. GhosttyNSView was missing makeBackingLayer(), so AppKit provided a generic CALayer as the view's backing layer. libghostty expects (view.layer as? CAMetalLayer) != nil to set up Metal rendering on the existing layer. Without a CAMetalLayer backing layer, the Metal surface defaulted to isOpaque=true, making the terminal area fully opaque regardless of background-opacity config. Fix: override makeBackingLayer() to return a CAMetalLayer with isOpaque=false and bgra8Unorm pixel format (matching standalone Ghostty's SurfaceView behavior). 2. ghostty_set_window_background_blur(app, window) is exposed in ghostty.h but was never called in cmux. Without this call the macOS window never gets the NSVisualEffectView blur backdrop that background-blur requires. Fix: add applyWindowBlurIfNeeded() on GhosttyApp that reads background-blur from config via ghostty_config_get and calls ghostty_set_window_background_blur when the value is non-zero. Called from applyBackgroundToKeyWindow() and applyWindowBackgroundIfActive() whenever the window is made transparent. Fixes #879 Co-Authored-By: Claude Sonnet 4.6 --- Sources/GhosttyTerminalView.swift | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 17fe87ab..a6ac27fe 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -2411,6 +2411,7 @@ class GhosttyApp { if cmuxShouldUseClearWindowBackground(for: defaultBackgroundOpacity) { window.backgroundColor = cmuxTransparentWindowBaseColor() window.isOpaque = false + applyWindowBlurIfNeeded(window) if backgroundLogEnabled { logBackground("applied transparent window background opacity=\(String(format: "%.3f", defaultBackgroundOpacity))") } @@ -2424,6 +2425,16 @@ class GhosttyApp { } } + func applyWindowBlurIfNeeded(_ window: NSWindow) { + guard let app = self.app else { return } + // ghostty_set_window_background_blur reads background-blur and + // background-opacity from the app config internally and calls + // CGSSetWindowBackgroundBlurRadius — a compositor-level setter that is + // idempotent. It is a no-op when opacity >= 1.0 or blur is disabled, + // so we can call it unconditionally whenever the window is transparent. + ghostty_set_window_background_blur(app, Unmanaged.passUnretained(window).toOpaque()) + } + private func activeMainWindow() -> NSWindow? { let keyWindow = NSApp.keyWindow if let raw = keyWindow?.identifier?.rawValue, @@ -3849,6 +3860,18 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { setup() } + override func makeBackingLayer() -> CALayer { + let metalLayer = CAMetalLayer() + metalLayer.pixelFormat = .bgra8Unorm + metalLayer.isOpaque = false + // framebufferOnly=false lets the macOS compositor read the drawable + // when blending translucent or blurred window layers. This matches + // standalone Ghostty's SurfaceView and is required for background-opacity + // and background-blur to render correctly. + metalLayer.framebufferOnly = false + return metalLayer + } + private func setup() { // Only enable our instrumented CAMetalLayer in targeted debug/test scenarios. // The lock in GhosttyMetalLayer.nextDrawable() adds overhead we don't want in normal runs. @@ -3931,6 +3954,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { if cmuxShouldUseClearWindowBackground(for: color.alphaComponent) { window.backgroundColor = cmuxTransparentWindowBaseColor() window.isOpaque = false + GhosttyApp.shared.applyWindowBlurIfNeeded(window) } else { window.backgroundColor = color window.isOpaque = color.alphaComponent >= 1.0