Windows with per-pixel alpha render opaque during animations (`window_opacity off`)
- Dominant language
- C
- Stars
- 29.6k
- Forks
- 750
- PR merge metrics
- No merged PRs in 30d
Description
### Summary
Windows that are transparent via **per-pixel alpha in their own backing store** (rather than via window-server alpha) render **fully opaque** for the duration of a window animation. The window snaps back to correct transparency the instant the animation ends.
With several such windows on screen (e.g. a screen full of terminals), every animated move reads as the whole screen flashing opaque.
This is distinct from the snapshot-stretching in #2618 / #2727 — that one is understood and acknowledged. This is about the proxy losing alpha entirely.
### Environment
- yabai v7.1.25 (asmvik/formulae tap)
- macOS 26.6.1 (25G76), arm64
- SIP: custom configuration, scripting addition loaded, Screen Recording granted
- Affected app: Ghostty 1.3.1 with `background-opacity = 0.55`, `background-blur = 4`
Relevant config:
```sh
yabai -m config window_opacity off
yabai -m config window_animation_duration 0.15
yabai -m config window_animation_easing ease_out_quint
```
### Steps to reproduce
1. Set a terminal (Ghostty, but any app drawing a semi-transparent background should do) to `background-opacity` < 1.
2. Enable window animations with `window_opacity off`.
3. Swap or warp the window with `yabai -m window --swap east` (or any animated move).
4. During the animation the window is opaque; on completion it returns to 55% transparency.
### Suspected cause
`src/window_manager.c:521-525`:
```c
CFArrayRef image_array = SLSHWCaptureWindowList(animation->cid, &animation->wid, 1, (1 << 11) | (1 << 8));
if (image_array) {
animation->proxy.image = alpha == 1.0f
? (CGImageRef) CFRetain(CFArrayGetValueAtIndex(image_array, 0))
: cgimage_restore_alpha((CGImageRef) CFArrayGetValueAtIndex(image_array, 0));
```
`alpha` comes from `SLSGetWindowAlpha` a few lines above (`window_manager.c:511-512`) — that is the *window-server* alpha, i.e. what `window_opacity` / `active_window_opacity` control.
An app like Ghostty has window-server alpha of exactly `1.0`; its transparency lives in the per-pixel alpha channel of its own backing store. So the condition takes the left branch and the raw capture is used with no call to `cgimage_restore_alpha` (`src/misc/helpers.h:588`), which is the routine that redraws the capture into a premultiplied-alpha bitmap and un-premultiplies it.
The test appears to conflate "this window has window-server alpha applied" with "this window's capture needs alpha handling." Windows with `window_opacity off` but transparent *content* fall into the gap: they need the restore path and never get it.
If that reading is right, always taking the `cgimage_restore_alpha` branch would fix it, at the cost of the extra bitmap pass for every animated window (the SIMD loop in `helpers.h`, so presumably cheap relative to the capture itself).
I have not built a patched binary to confirm — happy to test a patch if that would help.
### Secondary note on blur
Even with alpha restored, `background-blur` would presumably still be missing during the animation: the proxy created in `window_manager_create_window_proxy` (`window_manager.c:463-487`) is a fresh `SLSNewWindowWithOpaqueShapeAndContext` with no backdrop-blur layer attached, whereas the real window has one. That would leave transparent-but-unblurred proxies — much less jarring than fully opaque, but still a visible difference. Mentioning it in case it is worth handling in the same place; possibly related to #2561.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in src/window_manager.c:463-525 and inspect how the animation proxy is created and how SLSHWCaptureWindowList chooses the image path. Read cgimage_restore_alpha in src/misc/helpers.h:588, reproduce the Ghostty animation case, and verify that the proxy preserves per-pixel transparency without regressing opaque windows; investigate blur separately against the scope of #2561.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, macos
- Domain
- desktop, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100