git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit9f1336d6f659e83d93b85fd4d014e0225255f96d
parent94262cfdba
authorIsaac Freund <[email protected]>
date2025-06-30 23:02
Window: fix destroy post initial commit/pre map

In this case, the scheduled state will be .ready rather than .closing or
.init as the code currently expects.

It is good that we have this assertion, as it would be poor form to send
a new window object to the window manager that we know is about to be
destroyed.

/opt/zig-bin-0.13.0/lib/std/debug.zig:412:14: 0x1062edc in assert (river)
    if (!ok) unreachable; // assertion failure
             ^
/home/zuki/src/river/river/Window.zig:813:11: 0x1135b1e in getAppId (river)
    assert(!window.destroying);
          ^
/home/zuki/src/river/river/Window.zig:402:52: 0x110ddb5 in updateWindowingStart (river)
                window_v1.sendAppId(window.getAppId());
                                                   ^
/home/zuki/src/river/river/WindowManager.zig:280:63: 0x10e5ed5 in updateWindowingStart (river)
        while (it.next()) |window| window.updateWindowingStart();
                                                              ^
/home/zuki/src/river/river/WindowManager.zig:255:40: 0x10ccca6 in dirtyIdle (river)
                wm.updateWindowingStart();
                                       ^

 river/XdgToplevel.zig | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index fd4f0b6..8553993 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -243,6 +243,18 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
     const window = toplevel.window;
     window.impl = .none;
     window.destroying = true;
+    switch (window.windowing_scheduled.state) {
+        .init, .closing => {},
+        // This can happen if the xdg toplevel is destroyed after the initial
+        // commit but before the window is mapped. In this case, the state is
+        // not set to .closing by handleUnmap() since handleUnmap() is not
+        // called due to the window not being mapped.
+        .ready => {
+            assert(!window.initialized);
+            assert(!window.mapped);
+            window.windowing_scheduled.state = .init;
+        },
+    }
 }
 
 fn handleMap(listener: *wl.Listener(void)) void {