Skip to content

WM_TRANSIENT_FOR resolved only once at manage time — transient never follows its parent to another workspace if it was mapped first #846

Description

@d4rken

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)
  1. Run it, note the two window IDs.
  2. wmctrl -i -r <parent> -t 2
  3. The child stays on the original workspace, while xprop -id <child> WM_TRANSIENT_FOR still reports the correct parent.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions