WM_TRANSIENT_FOR resolved only once at manage time — transient never follows its parent to another workspace if it was mapped first
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 245
- Forks
- 127
- PR merge metrics
- No merged PRs in 30d
Description
Distribution: Linux Mint 22.3 (Zena)
Package version: muffin 6.6.3+zena, cinnamon 6.6.7+zena, X11
Graphics hardware: NVIDIA GeForce RTX 4090 (AD102), proprietary driver
Frequency: Always
Bug description
If a transient window is adopted by the WM before its parent, it never follows that parent to another workspace.
The behaviour is consistent with reload_transient_for() (src/x11/window-props.c) resolving WM_TRANSIENT_FOR only once, via meta_x11_display_lookup_x_window(): when the parent is not managed yet the lookup fails and transient_for becomes NULL, and the early return
if (transient_for == window->xtransient_for)
return;
would prevent any later retry, since the property itself never changes again. meta_window_change_workspace() then walks a transient list that does not contain the child. I have not instrumented muffin to confirm that specific line — the reproducer below establishes the ordering-dependent behaviour, not the exact code path.
The hint stays correct on the client throughout; only the window manager's resolution of it appears to be lost.
Real-world case: the Android emulator's floating control toolbar is a separate top-level window (_NET_WM_WINDOW_TYPE_UTILITY, WM_TRANSIENT_FOR the device window) and is stranded on the old workspace every time.
Steps to reproduce
#!/usr/bin/env python3
# needs python3-xlib
import time
from Xlib import X, Xatom, display
d = display.Display(); s = d.screen(); root = s.root
def win(name, x, w):
o = root.create_window(x, 60, w, 160, 1, s.root_depth, X.InputOutput,
X.CopyFromParent, background_pixel=s.black_pixel)
o.set_wm_name(name); o.set_wm_class("repro", "Repro"); return o
parent, child = win("repro-parent", 60, 260), win("repro-child", 340, 60)
child.set_wm_transient_for(parent)
child.change_property(d.intern_atom("_NET_WM_WINDOW_TYPE"), Xatom.ATOM, 32,
[d.intern_atom("_NET_WM_WINDOW_TYPE_UTILITY")])
child.map(); d.sync(); time.sleep(0.5) # child first -> bug
parent.map(); d.sync() # swap these two lines -> works
print(f"parent 0x{parent.id:08x} child 0x{child.id:08x}", flush=True)
time.sleep(300)
- Run it, note the two window IDs.
wmctrl -i -r <parent> -t 2- The child stays on the original workspace, while
xprop -id <child> WM_TRANSIENT_FORstill reports the correct parent. - Swap the two
map()lines so the parent is mapped first — the child now follows correctly.
Deterministic here, 3/3 runs. Mapping both back to back without synchronisation also works; only child-before-parent fails.
Expected behavior
The child follows its parent, as meta_window_change_workspace() intends via meta_window_foreach_transient().
Additional information
Affects any application that maps a utility/tool window before its main window. GNOME Bugzilla 645853 and 692976 described the symptom in 2011/2013 but were mass-closed RESOLVED OBSOLETE in the 2021 GitLab migration without a fix. The same code is present in mutter.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the child-before-parent ordering with the provided Python script, then inspect reload_transient_for() in src/x11/window-props.c and the workspace path through meta_window_change_workspace() and meta_window_foreach_transient(). Done means a transient mapped before its parent follows that parent after a workspace change, while the parent-first case continues to work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- desktop-dev, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100