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

commit3bfe7cda8b08f332ab706140838a7bd74c1841f4
parent65e4f95943
authorIsaac Freund <[email protected]>
date2025-01-04 11:22
Window: refactor states to better reflect reality

This commit finally gets rid of "backwards" state flow for window
dimensions committed by the client. State now always flows one direction
in a circle:

wm_pending -> wm_sent (update) pending -> sent (configure) -> wm_pending

It also gets rid of some dead wlr-foreign-toplevel-management code that
was getting in the way, I plan to drop this protocol anyways.

 river/ForeignToplevelHandle.zig | 106 --------------------
 river/Seat.zig                  |  36 +++----
 river/Window.zig                | 211 +++++++++++++---------------------------
 river/WindowManager.zig         |   6 +-
 river/XdgToplevel.zig           | 118 +++++++++-------------
 5 files changed, 138 insertions(+), 339 deletions(-)

diff --git a/river/ForeignToplevelHandle.zig b/river/ForeignToplevelHandle.zig
deleted file mode 100644
index 6cdb402..0000000
--- a/river/ForeignToplevelHandle.zig
+++ /dev/null
@@ -1,106 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2023 The River Developers
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-const ForeignToplevelHandle = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-
-const server = &@import("main.zig").server;
-
-const Window = @import("Window.zig");
-const Seat = @import("Seat.zig");
-
-wlr_handle: ?*wlr.ForeignToplevelHandleV1 = null,
-
-foreign_activate: wl.Listener(*wlr.ForeignToplevelHandleV1.event.Activated) =
-    wl.Listener(*wlr.ForeignToplevelHandleV1.event.Activated).init(handleForeignActivate),
-foreign_fullscreen: wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen) =
-    wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen).init(handleForeignFullscreen),
-foreign_close: wl.Listener(*wlr.ForeignToplevelHandleV1) =
-    wl.Listener(*wlr.ForeignToplevelHandleV1).init(handleForeignClose),
-
-pub fn map(handle: *ForeignToplevelHandle) void {
-    const window: *Window = @fieldParentPtr("foreign_toplevel_handle", handle);
-
-    assert(handle.wlr_handle == null);
-
-    handle.wlr_handle = wlr.ForeignToplevelHandleV1.create(server.foreign_toplevel_manager) catch {
-        std.log.err("out of memory", .{});
-        return;
-    };
-
-    handle.wlr_handle.?.events.request_activate.add(&handle.foreign_activate);
-    handle.wlr_handle.?.events.request_fullscreen.add(&handle.foreign_fullscreen);
-    handle.wlr_handle.?.events.request_close.add(&handle.foreign_close);
-
-    if (window.getTitle()) |title| handle.wlr_handle.?.setTitle(title);
-    if (window.getAppId()) |app_id| handle.wlr_handle.?.setAppId(app_id);
-}
-
-pub fn unmap(handle: *ForeignToplevelHandle) void {
-    const wlr_handle = handle.wlr_handle orelse return;
-
-    handle.foreign_activate.link.remove();
-    handle.foreign_fullscreen.link.remove();
-    handle.foreign_close.link.remove();
-
-    wlr_handle.destroy();
-
-    handle.wlr_handle = null;
-}
-
-/// Must be called just before the window's inflight state is made current.
-pub fn update(handle: *ForeignToplevelHandle) void {
-    const window: *Window = @fieldParentPtr("foreign_toplevel_handle", handle);
-
-    const wlr_handle = handle.wlr_handle orelse return;
-
-    wlr_handle.setActivated(window.inflight.activated);
-    wlr_handle.setFullscreen(window.inflight.fullscreen);
-}
-
-fn handleForeignActivate(
-    _: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Activated),
-    _: *wlr.ForeignToplevelHandleV1.event.Activated,
-) void {
-    //const handle: *ForeignToplevelHandle = @fieldParentPtr("foreign_activate", listener);
-    //const window: *Window = @fieldParentPtr("foreign_toplevel_handle", handle);
-
-    // XXX Can I just delete this protocol?
-}
-
-fn handleForeignFullscreen(
-    _: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen),
-    _: *wlr.ForeignToplevelHandleV1.event.Fullscreen,
-) void {
-    //const handle: *ForeignToplevelHandle = @fieldParentPtr("foreign_fullscreen", listener);
-    //const window: *Window = @fieldParentPtr("foreign_toplevel_handle", handle);
-
-    // XXX Can I just delete this protocol?
-}
-
-fn handleForeignClose(
-    listener: *wl.Listener(*wlr.ForeignToplevelHandleV1),
-    _: *wlr.ForeignToplevelHandleV1,
-) void {
-    const handle: *ForeignToplevelHandle = @fieldParentPtr("foreign_close", listener);
-    const window: *Window = @fieldParentPtr("foreign_toplevel_handle", handle);
-
-    window.close();
-}
diff --git a/river/Seat.zig b/river/Seat.zig
index ba7339f..9881a2f 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -227,8 +227,8 @@ pub fn destroy(seat: *Seat) void {
             inline for (.{
                 &window.uncommitted,
                 &window.committed,
-                &window.inflight,
-                &window.current,
+                &window.pending,
+                &window.sent,
             }) |state| {
                 switch (state.op) {
                     .none => {},
@@ -536,24 +536,24 @@ pub fn applyCommitted(seat: *Seat) void {
                         .none => {},
                         .move => |data| {
                             if (data.seat == seat) {
-                                assert(window.inflight.op == .none);
-                                window.inflight.op = .{
+                                assert(window.pending.op == .none);
+                                window.pending.op = .{
                                     .move = .{
                                         .seat = seat,
-                                        .start_x = window.pending.box.x,
-                                        .start_y = window.pending.box.y,
+                                        .start_x = window.wm_pending.box.x,
+                                        .start_y = window.wm_pending.box.y,
                                     },
                                 };
                             }
                         },
                         .resize => |data| {
                             if (data.seat == seat) {
-                                assert(window.inflight.op == .none);
-                                window.inflight.op = .{
+                                assert(window.pending.op == .none);
+                                window.pending.op = .{
                                     .resize = .{
                                         .seat = seat,
                                         .edges = data.edges,
-                                        .start_box = window.pending.box,
+                                        .start_box = window.wm_pending.box,
                                     },
                                 };
                             }
@@ -572,11 +572,11 @@ pub fn applyCommitted(seat: *Seat) void {
             {
                 var it = server.wm.windows.iterator(.forward);
                 while (it.next()) |window| {
-                    switch (window.inflight.op) {
+                    switch (window.pending.op) {
                         .none => {},
                         inline .move, .resize => |data| {
                             if (data.seat == seat) {
-                                window.inflight.op = .none;
+                                window.pending.op = .none;
                             }
                         },
                     }
@@ -769,13 +769,13 @@ pub fn updateOp(seat: *Seat, x: i32, y: i32) void {
     {
         var it = server.wm.windows.iterator(.forward);
         while (it.next()) |window| {
-            switch (window.inflight.op) {
+            switch (window.pending.op) {
                 .none => {},
                 .move => |data| {
                     if (data.seat != seat) continue;
 
-                    window.pending.box.x = data.start_x + dx;
-                    window.pending.box.y = data.start_y + dy;
+                    window.wm_pending.box.x = data.start_x + dx;
+                    window.wm_pending.box.y = data.start_y + dy;
 
                     seat.op.?.dirty = true;
                 },
@@ -788,15 +788,15 @@ pub fn updateOp(seat: *Seat, x: i32, y: i32) void {
                     // correctly place the top left corner in the case of a resize from
                     // the top or left edge.
                     if (data.edges.left) {
-                        window.pending.box.width = @max(1, data.start_box.width - dx);
+                        window.pending.width = @max(1, data.start_box.width - dx);
                     } else if (data.edges.right) {
-                        window.pending.box.width = @max(1, data.start_box.width + dx);
+                        window.pending.width = @max(1, data.start_box.width + dx);
                     }
 
                     if (data.edges.top) {
-                        window.pending.box.height = @max(1, data.start_box.height - dy);
+                        window.pending.height = @max(1, data.start_box.height - dy);
                     } else if (data.edges.bottom) {
-                        window.pending.box.height = @max(1, data.start_box.height + dy);
+                        window.pending.height = @max(1, data.start_box.height + dy);
                     }
 
                     seat.op.?.dirty = true;
diff --git a/river/Window.zig b/river/Window.zig
index 92a8344..7a10c77 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -29,7 +29,6 @@ const river = @import("wayland").server.river;
 const server = &@import("main.zig").server;
 const util = @import("util.zig");
 
-const ForeignToplevelHandle = @import("ForeignToplevelHandle.zig");
 const Output = @import("Output.zig");
 const SceneNodeData = @import("SceneNodeData.zig");
 const Seat = @import("Seat.zig");
@@ -67,8 +66,8 @@ pub const Border = struct {
 };
 
 pub const State = struct {
-    /// The output-relative coordinates of the window and dimensions requested by river.
-    box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
+    width: ?u31 = null,
+    height: ?u31 = null,
     hidden: bool = false,
     /// True if the window has keyboard focus from at least one seat.
     activated: bool = false,
@@ -130,7 +129,7 @@ pub const WmState = struct {
 
 /// The window management protocol object for this window
 /// Created after the window is ready to be configured.
-/// Lifetime is managed through pending.state
+/// Lifetime is managed through wm_pending.state
 object: ?*river.WindowV1 = null,
 node: WmNode,
 
@@ -156,7 +155,7 @@ destroying: bool = false,
 link: wl.list.Link,
 
 /// State to be sent to the window manager client in the next update sequence.
-pending: struct {
+wm_pending: struct {
     state: enum {
         /// Indicates that there is currently no associated river_window_v1
         /// object.
@@ -182,7 +181,7 @@ pending: struct {
 /// State sent to the window manager client in the latest update sequence.
 /// This state is only kept around in order to avoid sending redundant events
 /// to the window manager client.
-sent: struct {
+wm_sent: struct {
     position: ?struct { x: i32, y: i32 } = null,
     dimensions: ?struct { width: i32, height: i32 } = null,
     dimensions_hint: DimensionsHint = .{},
@@ -194,13 +193,10 @@ uncommitted: WmState = .{},
 /// State requested by the window manager client and committed.
 committed: WmState = .{},
 
-/// State sent to the window as part of a transaction.
-inflight: State = .{},
-
-/// The current state represented by the scene graph.
-current: State = .{},
-
-foreign_toplevel_handle: ForeignToplevelHandle = .{},
+/// State to be sent to the window in the next configure.
+pending: State = .{},
+/// State sent to the window in the latest configure.
+sent: State = .{},
 
 pub fn create(impl: Impl) error{OutOfMemory}!*Window {
     assert(impl != .none);
@@ -250,7 +246,7 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
 pub fn destroy(window: *Window, when: enum { lazy, assert }) void {
     assert(window.impl == .none);
     assert(!window.mapped);
-    switch (window.pending.state) {
+    switch (window.wm_pending.state) {
         .init, .closing => {},
         .ready => unreachable,
     }
@@ -278,74 +274,67 @@ pub fn destroy(window: *Window, when: enum { lazy, assert }) void {
 }
 
 pub fn setDimensionsHint(window: *Window, hint: DimensionsHint) void {
-    window.pending.dimensions_hint = hint;
-    if (!meta.eql(window.sent.dimensions_hint, hint)) {
+    window.wm_pending.dimensions_hint = hint;
+    if (!meta.eql(window.wm_sent.dimensions_hint, hint)) {
         server.wm.dirtyPending();
     }
 }
 
 pub fn setDimensions(window: *Window, width: i32, height: i32) void {
-    window.pending.box.width = width;
-    window.pending.box.height = height;
+    window.wm_pending.box.width = width;
+    window.wm_pending.box.height = height;
 
-    window.inflight.box.width = width;
-    window.inflight.box.height = height;
-
-    switch (window.inflight.op) {
-        .none => {},
-        .move => |data| assert(data.seat.op != null),
+    switch (window.sent.op) {
+        .none, .move => {},
         .resize => |data| {
             assert(data.seat.op != null);
 
             if (data.edges.left) {
-                window.pending.box.x = data.start_box.x + data.start_box.width - width;
+                window.wm_pending.box.x = data.start_box.x + data.start_box.width - width;
             } else if (data.edges.right) {
-                window.pending.box.x = data.start_box.x;
+                window.wm_pending.box.x = data.start_box.x;
             }
 
             if (data.edges.top) {
-                window.pending.box.y = data.start_box.y + data.start_box.height - height;
+                window.wm_pending.box.y = data.start_box.y + data.start_box.height - height;
             } else if (data.edges.bottom) {
-                window.pending.box.y = data.start_box.y;
+                window.wm_pending.box.y = data.start_box.y;
             }
-
-            window.inflight.box.x = window.pending.box.x;
-            window.inflight.box.y = window.pending.box.y;
         },
     }
 
-    if (window.sent.dimensions == null or window.sent.position == null or
-        width != window.sent.dimensions.?.width or
-        height != window.sent.dimensions.?.height or
-        window.pending.box.x != window.sent.position.?.x or
-        window.pending.box.y != window.sent.position.?.y)
+    if (window.wm_sent.dimensions == null or window.wm_sent.position == null or
+        width != window.wm_sent.dimensions.?.width or
+        height != window.wm_sent.dimensions.?.height or
+        window.wm_pending.box.x != window.wm_sent.position.?.x or
+        window.wm_pending.box.y != window.wm_sent.position.?.y)
     {
         server.wm.dirtyPending();
     }
 }
 
 pub fn setDecorationHint(window: *Window, hint: river.WindowV1.DecorationHint) void {
-    window.pending.decoration_hint = hint;
-    if (hint != window.sent.decoration_hint) {
+    window.wm_pending.decoration_hint = hint;
+    if (hint != window.wm_sent.decoration_hint) {
         server.wm.dirtyPending();
     }
 }
 
 pub fn setFullscreenRequested(window: *Window, fullscreen_requested: bool) void {
     if (fullscreen_requested) {
-        window.pending.fullscreen_requested = .fullscreen;
+        window.wm_pending.fullscreen_requested = .fullscreen;
     } else {
-        window.pending.fullscreen_requested = .exit;
+        window.wm_pending.fullscreen_requested = .exit;
     }
     server.wm.dirtyPending();
 }
 
 /// Send dirty pending state as part of an in progress update sequence.
 pub fn sendDirty(window: *Window) void {
-    switch (window.pending.state) {
+    switch (window.wm_pending.state) {
         .init => {},
         .closing => {
-            window.pending.state = .init;
+            window.wm_pending.state = .init;
             window.initialized = false;
             window.uncommitted = .{};
             window.committed = .{};
@@ -384,8 +373,8 @@ pub fn sendDirty(window: *Window) void {
             };
             errdefer comptime unreachable;
 
-            const pending = &window.pending;
-            const sent = &window.sent;
+            const pending = &window.wm_pending;
+            const sent = &window.wm_sent;
 
             // XXX send all dirty pending state
             if (new or sent.position == null or
@@ -403,7 +392,7 @@ pub fn sendDirty(window: *Window) void {
                 pending.box.width != sent.dimensions.?.width or
                 pending.box.height != sent.dimensions.?.height))
             {
-                window_v1.sendDimensions(window.pending.box.width, window.pending.box.height);
+                window_v1.sendDimensions(window.wm_pending.box.width, window.wm_pending.box.height);
                 sent.dimensions = .{
                     .width = pending.box.width,
                     .height = pending.box.height,
@@ -419,7 +408,7 @@ pub fn sendDirty(window: *Window) void {
                 sent.dimensions_hint = pending.dimensions_hint;
             }
             if (new or pending.decoration_hint != sent.decoration_hint) {
-                window_v1.sendDecorationHint(window.pending.decoration_hint);
+                window_v1.sendDecorationHint(window.wm_pending.decoration_hint);
                 sent.decoration_hint = pending.decoration_hint;
             }
             switch (pending.fullscreen_requested) {
@@ -546,18 +535,9 @@ pub fn configure(window: *Window) bool {
 
     const committed = &window.committed;
 
-    if (window.inflight.op == .none) {
-        if (committed.position) |position| {
-            window.pending.box.x = position.x;
-            window.pending.box.y = position.y;
-        }
-        if (committed.dimensions) |dimensions| {
-            window.pending.box.width = dimensions.width;
-            window.pending.box.height = dimensions.height;
-        }
-    }
-    window.inflight = .{
-        .box = window.pending.box,
+    window.pending = .{
+        .width = window.pending.width,
+        .height = window.pending.height,
         .hidden = committed.hidden,
         .activated = activated,
         .ssd = committed.ssd,
@@ -566,27 +546,37 @@ pub fn configure(window: *Window) bool {
         .capabilities = committed.capabilities,
         .maximized = committed.maximized,
         .fullscreen = committed.fullscreen,
-        .op = window.inflight.op,
-    };
-
-    const track_configure = switch (window.impl) {
-        .toplevel => |*toplevel| toplevel.configure(committed.dimensions != null),
-        .xwayland => |*xwindow| xwindow.configure(),
-        .none => unreachable,
+        .op = window.pending.op,
     };
 
     // Ensure a position/dimension event is sent if the window manager has
     // modified them even if the actual position/dimensions do not change.
-    if (committed.position != null) {
-        window.sent.position = null;
+    if (committed.position) |position| {
+        if (window.pending.op == .none) {
+            window.wm_pending.box.x = position.x;
+            window.wm_pending.box.y = position.y;
+        }
+
+        window.wm_sent.position = null;
         committed.position = null;
     }
-    if (committed.dimensions != null) {
-        window.sent.dimensions = null;
+    if (committed.dimensions) |dimensions| {
+        if (window.pending.op == .none) {
+            window.pending.width = dimensions.width;
+            window.pending.height = dimensions.height;
+        }
+
+        window.wm_sent.dimensions = null;
         committed.dimensions = null;
     }
     committed.op = .none;
 
+    const track_configure = switch (window.impl) {
+        .toplevel => |*toplevel| toplevel.configure(),
+        .xwayland => |*xwindow| xwindow.configure(),
+        .none => unreachable,
+    };
+
     if (track_configure and window.mapped) {
         window.saveSurfaceTree();
         window.sendFrameDone();
@@ -596,8 +586,6 @@ pub fn configure(window: *Window) bool {
 }
 
 pub fn commitTransaction(window: *Window) void {
-    window.foreign_toplevel_handle.update();
-
     switch (window.impl) {
         .toplevel => |*toplevel| {
             switch (toplevel.configure_state) {
@@ -626,84 +614,29 @@ pub fn commitTransaction(window: *Window) void {
                     // If we did not use the current geometry of the toplevel at this point
                     // we would be rendering the SSD border at initial size X but the surface
                     // would be rendered at size Y.
-                    window.setDimensions(toplevel.geometry.width, toplevel.geometry.height);
-                    window.current = window.inflight;
                 },
                 .idle, .committed => {
                     toplevel.configure_state = .idle;
-                    window.current = window.inflight;
                 },
                 .timed_out, .timed_out_acked => unreachable,
             }
+            window.setDimensions(toplevel.geometry.width, toplevel.geometry.height);
         },
         .xwayland => |xwindow| {
-            if (window.inflight.resizing) {
-                window.resizeUpdatePosition(
-                    xwindow.xsurface.width,
-                    xwindow.xsurface.height,
-                );
-            }
-
             window.setDimensions(xwindow.xsurface.width, xwindow.xsurface.height);
-
-            window.current = window.inflight;
         },
-        // This may seem pointless at first glance, but is in fact necessary
-        // to prevent an assertion failure in Root.commitTransaction() as that
-        // function assumes that the inflight tags/output will be applied by
-        // Window.commitTransaction() even for windows being destroyed.
-        .none => window.current = window.inflight,
+        .none => {},
     }
 
     window.updateSceneState();
 }
 
 pub fn updateSceneState(window: *Window) void {
-    const box = &window.current.box;
+    const box = &window.wm_pending.box;
     window.tree.node.setPosition(box.x, box.y);
     window.popup_tree.node.setPosition(box.x, box.y);
 
-    {
-        const config = &server.config;
-        const border_width: c_int = config.border_width;
-        const border_color = &config.border_color;
-
-        // Order is left, right, top, bottom
-        // left and right borders include the corners, top and bottom do not.
-        var border_boxes = [4]wlr.Box{
-            .{
-                .x = -border_width,
-                .y = -border_width,
-                .width = border_width,
-                .height = box.height + 2 * border_width,
-            },
-            .{
-                .x = box.width,
-                .y = -border_width,
-                .width = border_width,
-                .height = box.height + 2 * border_width,
-            },
-            .{
-                .x = 0,
-                .y = -border_width,
-                .width = box.width,
-                .height = border_width,
-            },
-            .{
-                .x = 0,
-                .y = box.height,
-                .width = box.width,
-                .height = border_width,
-            },
-        };
-
-        for (&window.borders, &border_boxes) |border, *border_box| {
-            border.node.setEnabled(window.current.ssd and !window.current.fullscreen);
-            border.node.setPosition(border_box.x, border_box.y);
-            border.setSize(border_box.width, border_box.height);
-            border.setColor(border_color);
-        }
-    }
+    // TODO borders
 }
 
 /// Returns null if the window is currently being destroyed and no longer has
@@ -809,8 +742,6 @@ pub fn map(window: *Window) !void {
 
     assert(!window.mapped and !window.destroying);
     window.mapped = true;
-
-    window.foreign_toplevel_handle.map();
 }
 
 /// Called by the impl when the surface will no longer be displayed
@@ -822,21 +753,17 @@ pub fn unmap(window: *Window) void {
     assert(window.mapped and !window.destroying);
     window.mapped = false;
 
-    window.foreign_toplevel_handle.unmap();
-
-    assert(window.pending.state != .closing);
-    window.pending.state = .closing;
+    assert(window.wm_pending.state != .closing);
+    window.wm_pending.state = .closing;
     server.wm.dirtyPending();
 }
 
 pub fn notifyTitle(window: *const Window) void {
-    if (window.foreign_toplevel_handle.wlr_handle) |wlr_handle| {
-        if (window.getTitle()) |title| wlr_handle.setTitle(title);
-    }
+    // TODO
+    _ = window;
 }
 
 pub fn notifyAppId(window: Window) void {
-    if (window.foreign_toplevel_handle.wlr_handle) |wlr_handle| {
-        if (window.getAppId()) |app_id| wlr_handle.setAppId(app_id);
-    }
+    // TODO
+    _ = window;
 }
diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index 27db7d5..43a859d 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -381,8 +381,10 @@ fn commitTransaction(wm: *WindowManager) void {
 
                     window.tree.node.reparent(server.scene.layers.wm);
                     window.tree.node.raiseToTop();
-                    window.tree.node.setEnabled(!window.current.hidden);
-                    window.popup_tree.node.setEnabled(!window.current.hidden);
+
+                    assert(window.pending.hidden == window.sent.hidden);
+                    window.tree.node.setEnabled(!window.pending.hidden);
+                    window.popup_tree.node.setEnabled(!window.pending.hidden);
                 },
             }
         }
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index f8ab16d..7515bbb 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -116,8 +116,7 @@ pub fn create(wlr_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void {
 }
 
 /// Send a configure event, applying the inflight state of the window.
-/// If force is true, a configure will always be sent but not necessarily tracked.
-pub fn configure(toplevel: *XdgToplevel, force: bool) bool {
+pub fn configure(toplevel: *XdgToplevel) bool {
     switch (toplevel.configure_state) {
         .idle, .timed_out, .timed_out_acked => {},
         .inflight, .acked, .committed => unreachable,
@@ -128,10 +127,9 @@ pub fn configure(toplevel: *XdgToplevel, force: bool) bool {
         .timed_out, .timed_out_acked, .committed => unreachable,
     };
 
-    const inflight = &toplevel.window.inflight;
-    const current = &toplevel.window.current;
+    const pending = &toplevel.window.pending;
 
-    if (!force and !toplevel.needsConfigure()) {
+    if (!toplevel.needsConfigure()) {
         // If no new configure is required, continue to track a timed out configure
         // from the previous transaction if any.
         switch (toplevel.configure_state) {
@@ -150,41 +148,43 @@ pub fn configure(toplevel: *XdgToplevel, force: bool) bool {
 
     const wlr_toplevel = toplevel.wlr_toplevel;
 
-    _ = wlr_toplevel.setActivated(inflight.activated);
+    _ = wlr_toplevel.setActivated(pending.activated);
     _ = wlr_toplevel.setTiled(.{
-        .top = inflight.tiled.top,
-        .bottom = inflight.tiled.bottom,
-        .left = inflight.tiled.left,
-        .right = inflight.tiled.right,
+        .top = pending.tiled.top,
+        .bottom = pending.tiled.bottom,
+        .left = pending.tiled.left,
+        .right = pending.tiled.right,
     });
     _ = wlr_toplevel.setWmCapabilities(.{
-        .window_menu = inflight.capabilities.window_menu,
-        .maximize = inflight.capabilities.maximize,
-        .fullscreen = inflight.capabilities.fullscreen,
-        .minimize = inflight.capabilities.minimize,
+        .window_menu = pending.capabilities.window_menu,
+        .maximize = pending.capabilities.maximize,
+        .fullscreen = pending.capabilities.fullscreen,
+        .minimize = pending.capabilities.minimize,
     });
-    _ = wlr_toplevel.setMaximized(inflight.maximized);
-    _ = wlr_toplevel.setFullscreen(inflight.fullscreen);
-    _ = wlr_toplevel.setResizing(inflight.op == .resize);
-
+    _ = wlr_toplevel.setMaximized(pending.maximized);
+    _ = wlr_toplevel.setFullscreen(pending.fullscreen);
+    _ = wlr_toplevel.setResizing(pending.op == .resize);
     if (toplevel.decoration) |decoration| {
-        _ = decoration.wlr_decoration.setMode(if (inflight.ssd) .server_side else .client_side);
+        _ = decoration.wlr_decoration.setMode(if (pending.ssd) .server_side else .client_side);
     }
 
-    // We need to call this wlroots function even if the inflight dimensions
-    // match the current dimensions in order to prevent wlroots internal state
-    // from getting out of sync in the case where a client has resized the toplevel.
-    const configure_serial = wlr_toplevel.setSize(inflight.box.width, inflight.box.height);
-
-    // Only track configures with the transaction system if they affect the dimensions of the window.
-    // If the configure state is not idle this means we are currently tracking a timed out
-    // configure from a previous transaction and should instead track the newly sent configure.
-    if (inflight.box.width != 0 and inflight.box.width == current.box.width and
-        inflight.box.height != 0 and inflight.box.height == current.box.height and
-        toplevel.configure_state == .idle)
-    {
-        return false;
-    }
+    const width: u31 = pending.width orelse switch (toplevel.configure_state) {
+        .idle => @intCast(toplevel.geometry.width),
+        .timed_out, .timed_out_acked => toplevel.window.sent.width.?,
+        .inflight, .acked, .committed => unreachable,
+    };
+    const height: u31 = pending.height orelse switch (toplevel.configure_state) {
+        .idle => @intCast(toplevel.geometry.height),
+        .timed_out, .timed_out_acked => toplevel.window.sent.height.?,
+        .inflight, .acked, .committed => unreachable,
+    };
+    const configure_serial = wlr_toplevel.setSize(width, height);
+
+    toplevel.window.sent = toplevel.window.pending;
+    toplevel.window.sent.width = width;
+    toplevel.window.sent.height = height;
+    toplevel.window.pending.width = null;
+    toplevel.window.pending.height = null;
 
     toplevel.configure_state = .{
         .inflight = configure_serial,
@@ -194,27 +194,18 @@ pub fn configure(toplevel: *XdgToplevel, force: bool) bool {
 }
 
 fn needsConfigure(toplevel: *XdgToplevel) bool {
-    const inflight = &toplevel.window.inflight;
-    const current = &toplevel.window.current;
-
-    // Never send configures to hidden windows.
-    // If transitioning from hidden to not-hidden, send a configure.
-    if (inflight.hidden) return false;
-    if (current.hidden) return true;
-
-    if (inflight.box.width == 0 or inflight.box.width != current.box.width or
-        inflight.box.height == 0 or inflight.box.height != current.box.height)
-    {
-        return true;
-    }
-
-    if (inflight.activated != current.activated) return true;
-    if (inflight.ssd != current.ssd) return true;
-    if (!std.meta.eql(inflight.tiled, current.tiled)) return true;
-    if (!std.meta.eql(inflight.capabilities, current.capabilities)) return true;
-    if (inflight.maximized != current.maximized) return true;
-    if (inflight.fullscreen != current.fullscreen) return true;
-    if ((inflight.op == .resize) != (current.op == .resize)) return true;
+    const pending = &toplevel.window.pending;
+    const sent = &toplevel.window.sent;
+
+    if (pending.width != null) return true;
+    if (pending.height != null) return true;
+    if (pending.activated != sent.activated) return true;
+    if (pending.ssd != sent.ssd) return true;
+    if (!std.meta.eql(pending.tiled, sent.tiled)) return true;
+    if (!std.meta.eql(pending.capabilities, sent.capabilities)) return true;
+    if (pending.maximized != sent.maximized) return true;
+    if (pending.fullscreen != sent.fullscreen) return true;
+    if ((pending.op == .resize) != (sent.op == .resize)) return true;
 
     return false;
 }
@@ -307,8 +298,8 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
     });
 
     if (toplevel.wlr_toplevel.base.initial_commit) {
-        assert(window.pending.state != .ready);
-        window.pending.state = .ready;
+        assert(window.wm_pending.state != .ready);
+        window.wm_pending.state = .ready;
         server.wm.dirtyPending();
         return;
     }
@@ -330,22 +321,8 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
                     "client initiated size change: {}x{} -> {}x{}",
                     .{ old_geometry.width, old_geometry.height, toplevel.geometry.width, toplevel.geometry.height },
                 );
-                // TODO check tiled state
-                if (!window.current.fullscreen) {
-                    // It seems that a disappointingly high number of clients have a buggy
-                    // response to configure events. They ack the configure immediately but then
-                    // proceed to make one or more wl_surface.commit requests with the old size
-                    // before updating the size of the surface. This obviously makes river's
-                    // efforts towards frame perfection futile for such clients. However, in the
-                    // interest of best serving river's users we will fix up their size here after
-                    // logging a shame message.
-                    log.err("client with app-id '{s}' is buggy and initiated size change while tiled or fullscreen, shame on it", .{
-                        window.getAppId() orelse "",
-                    });
-                }
 
                 window.setDimensions(toplevel.geometry.width, toplevel.geometry.height);
-                window.current = window.inflight;
                 window.updateSceneState();
             } else if (old_geometry.x != toplevel.geometry.x or
                 old_geometry.y != toplevel.geometry.y)
@@ -371,7 +348,6 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
                 },
                 .timed_out_acked => {
                     toplevel.configure_state = .idle;
-                    window.current = window.inflight;
                     window.updateSceneState();
                 },
                 else => unreachable,