Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
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,