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 <noreply@anthropic.com>
This commit is contained in:
martinezhermes 2026-03-20 09:48:12 +01:00
parent c44e975855
commit 1235daff04
No known key found for this signature in database
GPG key ID: F891501130DDB222

View file

@ -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