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

commited2666268a491b609acd7a9865762249cee23d72
parentdf057eb05e
authorIsaac Freund <[email protected]>
date2025-01-08 16:48
river: fix issues with window destruction

 river/Seat.zig         |  2 +-
 river/Window.zig       | 41 +++++++++++++++++++++++++++++------------
 rivercompat/Seat.zig   |  1 +
 rivercompat/Window.zig |  7 +++++++
 4 files changed, 38 insertions(+), 13 deletions(-)

diff --git a/river/Seat.zig b/river/Seat.zig
index 90a10dc..ee8b659 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -91,10 +91,10 @@ pub const WmFocus = union(enum) {
 };
 
 pub const Focus = union(enum) {
+    none,
     window: *Window,
     override_redirect: if (build_options.xwayland) *XwaylandOverrideRedirect else noreturn,
     lock_surface: *LockSurface,
-    none: void,
 
     pub fn surface(target: Focus) ?*wlr.Surface {
         return switch (target) {
diff --git a/river/Window.zig b/river/Window.zig
index cde2457..833fc7a 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -249,15 +249,28 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
 /// mark this window for destruction when the transaction completes. Otherwise
 /// destroy immediately.
 pub fn destroy(window: *Window, when: enum { lazy, assert }) void {
+    // We can't assert(window.wm_pending.state != .ready) since the client may
+    // have exited after making its empty initial commit but before the surface
+    // is mapped.
     assert(window.impl == .none);
     assert(!window.mapped);
-    switch (window.wm_pending.state) {
-        .init, .closing => {},
-        .ready => unreachable,
-    }
+
+    // We may need to send the closed event and make the window_v1/node_v1 objects
+    // inert here if the client exits after the empty initial commit but before
+    // the window is mapped.
+    window.makeInert();
 
     window.destroying = true;
 
+    {
+        var it = server.input_manager.seats.iterator(.forward);
+        while (it.next()) |seat| {
+            if (seat.focused == .window and seat.focused.window == window) {
+                seat.focus(.none);
+            }
+        }
+    }
+
     // If there are still saved buffers, then this window needs to be kept
     // around until the current transaction completes. This function will be
     // called again in WindowManager.commitTransaction()
@@ -351,14 +364,7 @@ pub fn sendDirty(window: *Window) void {
             window.node.link_inflight.remove();
             window.node.link_inflight.init();
 
-            if (window.object) |window_v1| {
-                window.object = null;
-                window_v1.sendClosed();
-                window_v1.setHandler(?*anyopaque, handleRequestInert, null, null);
-                window.node.makeInert();
-            } else {
-                assert(window.node.object == null);
-            }
+            window.close();
         },
         .ready => {
             const wm_v1 = server.wm.object orelse return;
@@ -435,6 +441,17 @@ pub fn sendDirty(window: *Window) void {
     }
 }
 
+pub fn makeInert(window: *Window) void {
+    if (window.object) |window_v1| {
+        window.object = null;
+        window_v1.sendClosed();
+        window_v1.setHandler(?*anyopaque, handleRequestInert, null, null);
+        window.node.makeInert();
+    } else {
+        assert(window.node.object == null);
+    }
+}
+
 fn handleRequestInert(
     window_v1: *river.WindowV1,
     request: river.WindowV1.Request,
diff --git a/rivercompat/Seat.zig b/rivercompat/Seat.zig
index 20094ee..dc48e96 100644
--- a/rivercompat/Seat.zig
+++ b/rivercompat/Seat.zig
@@ -50,6 +50,7 @@ pub fn create(wm: *WindowManager, seat_v1: *river.SeatV1) void {
 
     XkbBinding.create(seat, xkb.Keysym.n, .{ .mod4 = true }, .focus_next);
     XkbBinding.create(seat, xkb.Keysym.h, .{ .mod4 = true }, .hide_focused);
+    XkbBinding.create(seat, xkb.Keysym.k, .{ .mod4 = true }, .close_focused);
     XkbBinding.create(seat, xkb.Keysym.s, .{ .mod4 = true }, .show_all);
     PointerBinding.create(seat, c.BTN_LEFT, .{ .mod4 = true }, .move_start, .op_end);
     PointerBinding.create(seat, c.BTN_RIGHT, .{ .mod4 = true }, .resize_start, .op_end);
diff --git a/rivercompat/Window.zig b/rivercompat/Window.zig
index 0d89f70..99f51b7 100644
--- a/rivercompat/Window.zig
+++ b/rivercompat/Window.zig
@@ -53,6 +53,13 @@ pub fn create(window_v1: *river.WindowV1, wm: *WindowManager) void {
         @as(u32, (rgb >> 0) & 0xff) * (0xffff_ffff / 0xff),
         0xffff_ffff,
     );
+
+    {
+        var it = wm.seats.iterator(.forward);
+        while (it.next()) |seat| {
+            seat.focus(window);
+        }
+    }
 }
 
 fn handleEvent(window_v1: *river.WindowV1, event: river.WindowV1.Event, window: *Window) void {