asmvik / asmvik/yabai

Windows with per-pixel alpha render opaque during animations (`window_opacity off`)

Open
#2,820 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.