Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
river: move transaction code to WindowManager
river/Cursor.zig | 16 +--
river/ForeignToplevelHandle.zig | 4 +-
river/InputPopup.zig | 6 +-
river/LayerSurface.zig | 8 +-
river/LockManager.zig | 2 +-
river/Output.zig | 6 +-
river/Root.zig | 220 +------------------------------------
river/Seat.zig | 2 +-
river/Server.zig | 2 +-
river/Window.zig | 22 ++--
river/WindowManager.zig | 175 +++++++++++++++++++++++++++++
river/XdgDecoration.zig | 2 +-
river/XdgToplevel.zig | 4 +-
river/XwaylandOverrideRedirect.zig | 2 +-
river/XwaylandWindow.zig | 6 +-
15 files changed, 219 insertions(+), 258 deletions(-)
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 786fb94..5d66fea 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -338,7 +338,7 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
cursor.mode = .passthrough;
cursor.passthrough(event.time_msec);
- server.root.applyPending();
+ server.wm.applyPending();
} else {
_ = cursor.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
}
@@ -375,10 +375,10 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
}
}
- server.root.applyPending();
+ server.wm.applyPending();
}
-/// Requires a call to Root.applyPending()
+/// Requires a call to WindowManager.applyPending()
fn updateKeyboardFocus(cursor: Cursor, result: Root.AtResult) void {
switch (result.data) {
.window => |window| {
@@ -509,7 +509,7 @@ fn handleTouchDown(
}
}
- server.root.applyPending();
+ server.wm.applyPending();
}
fn handleTouchMotion(
@@ -796,7 +796,7 @@ fn enterMode(cursor: *Cursor, mode: Mode, window: *Window, xcursor_name: [*:0]co
cursor.seat.wlr_seat.pointerNotifyClearFocus();
cursor.setXcursor(xcursor_name);
- server.root.applyPending();
+ server.wm.applyPending();
}
fn processMotion(cursor: *Cursor, device: *wlr.InputDevice, time: u32, delta_x: f64, delta_y: f64, unaccel_dx: f64, unaccel_dy: f64) void {
@@ -853,7 +853,7 @@ fn processMotion(cursor: *Cursor, device: *wlr.InputDevice, time: u32, delta_x:
data.window.pending.move(@intFromFloat(dx), @intFromFloat(dy));
- server.root.applyPending();
+ server.wm.applyPending();
},
.resize => |*data| {
dx += data.delta_x;
@@ -906,7 +906,7 @@ fn processMotion(cursor: *Cursor, device: *wlr.InputDevice, time: u32, delta_x:
data.y = box.height - data.initial_height;
}
- server.root.applyPending();
+ server.wm.applyPending();
},
}
}
@@ -948,7 +948,7 @@ pub fn updateState(cursor: *Cursor) void {
.passthrough, .down => {},
inline .move, .resize => |data, mode| {
- // These conditions are checked in Root.applyPending()
+ // These conditions are checked in WindowManager.applyPending()
assert(!data.window.current.fullscreen);
// Keep the cursor locked to the original offset from the edges of the window.
diff --git a/river/ForeignToplevelHandle.zig b/river/ForeignToplevelHandle.zig
index f6e0555..1bd5deb 100644
--- a/river/ForeignToplevelHandle.zig
+++ b/river/ForeignToplevelHandle.zig
@@ -86,7 +86,7 @@ fn handleForeignActivate(
const seat: *Seat = @ptrFromInt(event.seat.data);
seat.focus(window);
- server.root.applyPending();
+ server.wm.applyPending();
}
fn handleForeignFullscreen(
@@ -97,7 +97,7 @@ fn handleForeignFullscreen(
const window: *Window = @fieldParentPtr("foreign_toplevel_handle", handle);
window.pending.fullscreen = event.fullscreen;
- server.root.applyPending();
+ server.wm.applyPending();
}
fn handleForeignClose(
diff --git a/river/InputPopup.zig b/river/InputPopup.zig
index 19abbf8..b4cb096 100644
--- a/river/InputPopup.zig
+++ b/river/InputPopup.zig
@@ -47,7 +47,7 @@ pub fn create(wlr_popup: *wlr.InputPopupSurfaceV2, input_relay: *InputRelay) !vo
.link = undefined,
.input_relay = input_relay,
.wlr_popup = wlr_popup,
- .surface_tree = try server.root.hidden.tree.createSceneSubsurfaceTree(wlr_popup.surface),
+ .surface_tree = try server.root.hidden_tree.createSceneSubsurfaceTree(wlr_popup.surface),
};
input_relay.input_popups.append(input_popup);
@@ -82,7 +82,7 @@ fn handleMap(listener: *wl.Listener(void)) void {
fn handleUnmap(listener: *wl.Listener(void)) void {
const input_popup: *InputPopup = @fieldParentPtr("unmap", listener);
- input_popup.surface_tree.node.reparent(server.root.hidden.tree);
+ input_popup.surface_tree.node.reparent(server.root.hidden_tree);
}
fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
@@ -93,7 +93,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
pub fn update(input_popup: *InputPopup) void {
const text_input = input_popup.input_relay.text_input orelse {
- input_popup.surface_tree.node.reparent(server.root.hidden.tree);
+ input_popup.surface_tree.node.reparent(server.root.hidden_tree);
return;
};
diff --git a/river/LayerSurface.zig b/river/LayerSurface.zig
index af97e46..348a397 100644
--- a/river/LayerSurface.zig
+++ b/river/LayerSurface.zig
@@ -108,7 +108,7 @@ fn handleMap(listener: *wl.Listener(void)) void {
if (consider) layer_surface else null,
);
- server.root.applyPending();
+ server.wm.applyPending();
}
fn handleUnmap(listener: *wl.Listener(void)) void {
@@ -118,7 +118,7 @@ fn handleUnmap(listener: *wl.Listener(void)) void {
layer_surface.output.arrangeLayers();
handleKeyboardInteractiveExclusive(layer_surface.output, null);
- server.root.applyPending();
+ server.wm.applyPending();
}
fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
@@ -138,13 +138,13 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
{
layer_surface.output.arrangeLayers();
handleKeyboardInteractiveExclusive(layer_surface.output, null);
- server.root.applyPending();
+ server.wm.applyPending();
}
}
/// Focus topmost keyboard-interactivity-exclusive layer surface above normal
/// content, or if none found, focus the surface given as `consider`.
-/// Requires a call to Root.applyPending()
+/// Requires a call to WindowManager.applyPending()
fn handleKeyboardInteractiveExclusive(output: *Output, consider: ?*LayerSurface) void {
if (server.lock_manager.state != .unlocked) return;
diff --git a/river/LockManager.zig b/river/LockManager.zig
index 85332e1..1a16c66 100644
--- a/river/LockManager.zig
+++ b/river/LockManager.zig
@@ -218,7 +218,7 @@ fn handleUnlock(listener: *wl.Listener(void)) void {
handleDestroy(&manager.destroy);
- server.root.applyPending();
+ server.wm.applyPending();
}
fn handleDestroy(listener: *wl.Listener(void)) void {
diff --git a/river/Output.zig b/river/Output.zig
index 7a07f6d..e816404 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -237,7 +237,7 @@ pub fn layerSurfaceTree(output: Output, layer: zwlr.LayerShellV1.Layer) *wlr.Sce
/// Arrange all layer surfaces of this output and adjust the usable area.
/// Will arrange windows as well if the usable area changes.
-/// Requires a call to Root.applyPending()
+/// Requires a call to WindowManager.applyPending()
pub fn arrangeLayers(output: *Output) void {
var full_box: wlr.Box = .{
.x = 0,
@@ -329,7 +329,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
server.root.handleOutputConfigChange() catch std.log.err("out of memory", .{});
- server.root.applyPending();
+ server.wm.applyPending();
}
fn handleRequestState(listener: *wl.Listener(*wlr.Output.event.RequestState), event: *wlr.Output.event.RequestState) void {
@@ -340,7 +340,7 @@ fn handleRequestState(listener: *wl.Listener(*wlr.Output.event.RequestState), ev
return;
};
- server.root.applyPending();
+ server.wm.applyPending();
}
// TODO double buffer output state changes for frame perfection and cleaner code.
diff --git a/river/Root.zig b/river/Root.zig
index 262233a..43ef70f 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -52,31 +52,7 @@ layers: struct {
override_redirect: if (build_options.xwayland) *wlr.SceneTree else void,
},
-wm: struct {
- pending: struct {
- render_list: wl.list.Head(Window, .pending_render_list_link),
- },
-
- inflight: struct {
- render_list: wl.list.Head(Window, .inflight_render_list_link),
- },
-},
-
-/// This is kind of like an imaginary output where windows start and end their life.
-hidden: struct {
- /// This tree is always disabled.
- tree: *wlr.SceneTree,
-
- pending: struct {
- render_list: wl.list.Head(Window, .pending_render_list_link),
- },
-
- inflight: struct {
- render_list: wl.list.Head(Window, .inflight_render_list_link),
- },
-},
-
-windows: wl.list.Head(Window, .link),
+hidden_tree: *wlr.SceneTree,
new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput),
@@ -107,13 +83,6 @@ all_outputs: wl.list.Head(Output, .all_link),
/// it's turned off by dpms)
active_outputs: wl.list.Head(Output, .active_link),
-/// Number of inflight configures sent in the current transaction.
-inflight_configures: u32 = 0,
-transaction_timeout: *wl.EventSource,
-/// Set to true if applyPending() is called while a transaction is inflight.
-/// If true when a transaction completes, causes applyPending() to be called again.
-pending_state_dirty: bool = false,
-
pub fn init(root: *Root) !void {
const output_layout = try wlr.OutputLayout.create(server.wl_server);
errdefer output_layout.destroy();
@@ -129,10 +98,6 @@ pub fn init(root: *Root) !void {
const outputs = try interactive_content.createSceneTree();
const override_redirect = if (build_options.xwayland) try interactive_content.createSceneTree();
- const event_loop = server.wl_server.getEventLoop();
- const transaction_timeout = try event_loop.addTimer(*Root, handleTransactionTimeout, root);
- errdefer transaction_timeout.remove();
-
root.* = .{
.scene = scene,
.interactive_content = interactive_content,
@@ -141,41 +106,19 @@ pub fn init(root: *Root) !void {
.outputs = outputs,
.override_redirect = override_redirect,
},
- .wm = .{
- .pending = .{
- .render_list = undefined,
- },
- .inflight = .{
- .render_list = undefined,
- },
- },
- .hidden = .{
- .tree = hidden_tree,
- .pending = .{
- .render_list = undefined,
- },
- .inflight = .{
- .render_list = undefined,
- },
- },
- .windows = undefined,
.output_layout = output_layout,
.all_outputs = undefined,
.active_outputs = undefined,
+ .hidden_tree = hidden_tree,
+
.presentation = try wlr.Presentation.create(server.wl_server, server.backend),
.xdg_output_manager = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout),
.output_manager = try wlr.OutputManagerV1.create(server.wl_server),
.power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
.gamma_control_manager = try wlr.GammaControlManagerV1.create(server.wl_server),
- .transaction_timeout = transaction_timeout,
};
- root.wm.pending.render_list.init();
- root.wm.inflight.render_list.init();
- root.hidden.pending.render_list.init();
- root.hidden.inflight.render_list.init();
- root.windows.init();
root.all_outputs.init();
root.active_outputs.init();
@@ -189,7 +132,6 @@ pub fn init(root: *Root) !void {
pub fn deinit(root: *Root) void {
root.output_layout.destroy();
- root.transaction_timeout.remove();
}
pub const AtResult = struct {
@@ -307,160 +249,6 @@ pub fn activateOutput(root: *Root, output: *Output) void {
};
}
-/// Trigger asynchronous application of pending state for all outputs and windows.
-/// Changes will not be applied to the scene graph until the layout generator
-/// generates a new layout for all outputs and all affected clients ack a
-/// configure and commit a new buffer.
-pub fn applyPending(root: *Root) void {
- {
- // Changes to the pending state may require a focus update to keep
- // state consistent. Instead of having focus(null) calls spread all
- // around the codebase and risk forgetting one, always ensure focus
- // state is synchronized here.
- var it = server.input_manager.seats.first;
- while (it) |node| : (it = node.next) node.data.focus(null);
- }
-
- // If there is already a transaction inflight, wait until it completes.
- if (root.inflight_configures > 0) {
- root.pending_state_dirty = true;
- return;
- }
- root.pending_state_dirty = false;
-
- {
- var it = root.hidden.pending.render_list.iterator(.forward);
- while (it.next()) |window| {
- window.inflight_render_list_link.remove();
- root.hidden.inflight.render_list.append(window);
- }
- }
-
- {
- var it = server.input_manager.seats.first;
- while (it) |node| : (it = node.next) {
- const cursor = &node.data.cursor;
-
- switch (cursor.mode) {
- .passthrough, .down => {},
- inline .move, .resize => |data| {
- if (data.window.inflight.fullscreen) {
- cursor.mode = .passthrough;
- data.window.pending.resizing = false;
- data.window.inflight.resizing = false;
- }
- },
- }
-
- cursor.inflight_mode = cursor.mode;
- }
- }
-
- root.sendConfigures();
-}
-
-fn sendConfigures(root: *Root) void {
- assert(root.inflight_configures == 0);
-
- {
- var it = root.wm.inflight.render_list.iterator(.forward);
- while (it.next()) |window| {
- assert(!window.inflight_transaction);
- window.inflight_transaction = true;
-
- // This can happen if a window is unmapped while a layout demand including it is inflight
- // If a window has been unmapped, don't send it a configure.
- if (!window.mapped) continue;
-
- if (window.configure()) {
- root.inflight_configures += 1;
-
- window.saveSurfaceTree();
- window.sendFrameDone();
- }
- }
- }
-
- if (root.inflight_configures > 0) {
- std.log.scoped(.transaction).debug("started transaction with {} pending configure(s)", .{
- root.inflight_configures,
- });
-
- root.transaction_timeout.timerUpdate(100) catch {
- std.log.scoped(.transaction).err("failed to update timer", .{});
- root.commitTransaction();
- };
- } else {
- root.commitTransaction();
- }
-}
-
-fn handleTransactionTimeout(root: *Root) c_int {
- std.log.scoped(.transaction).err("timeout occurred, some imperfect frames may be shown", .{});
-
- root.inflight_configures = 0;
- root.commitTransaction();
-
- return 0;
-}
-
-pub fn notifyConfigured(root: *Root) void {
- root.inflight_configures -= 1;
- if (root.inflight_configures == 0) {
- // Disarm the timer, as we didn't timeout
- root.transaction_timeout.timerUpdate(0) catch std.log.scoped(.transaction).err("error disarming timer", .{});
- root.commitTransaction();
- }
-}
-
-/// Apply the inflight state and drop stashed buffers. This means that
-/// the next frame drawn will be the post-transaction state of the
-/// layout. Should only be called after all clients have configured for
-/// the new layout. If called early imperfect frames may be drawn.
-fn commitTransaction(root: *Root) void {
- assert(root.inflight_configures == 0);
-
- std.log.scoped(.transaction).debug("commiting transaction", .{});
-
- {
- var it = root.hidden.inflight.render_list.safeIterator(.forward);
- while (it.next()) |window| {
- window.tree.node.reparent(root.hidden.tree);
- window.popup_tree.node.reparent(root.hidden.tree);
- }
- }
-
- {
- var it = root.wm.inflight.render_list.iterator(.forward);
- while (it.next()) |window| {
- window.commitTransaction();
-
- window.tree.node.setEnabled(true);
- window.popup_tree.node.setEnabled(true);
- }
- }
-
- {
- var it = server.input_manager.seats.first;
- while (it) |node| : (it = node.next) node.data.cursor.updateState();
- }
-
- {
- // This must be done after updating cursor state in case the window was the target of move/resize.
- var it = root.hidden.inflight.render_list.safeIterator(.forward);
- while (it.next()) |window| {
- window.dropSavedSurfaceTree();
- if (window.destroying) window.destroy(.assert);
- }
- }
-
- server.idle_inhibit_manager.checkActive();
-
- if (root.pending_state_dirty) {
- root.applyPending();
- }
-}
-
// We need this listener to deal with outputs that have their position auto-configured
// by the wlr_output_layout.
fn handleLayoutChange(listener: *wl.Listener(*wlr.OutputLayout), _: *wlr.OutputLayout) void {
@@ -571,7 +359,7 @@ fn processOutputConfig(
}
}
- if (action == .apply) root.applyPending();
+ if (action == .apply) server.wm.applyPending();
if (success) {
config.sendSucceeded();
diff --git a/river/Seat.zig b/river/Seat.zig
index 77864e5..921a86d 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -129,7 +129,7 @@ pub fn deinit(seat: *Seat) void {
/// Set the current focus. If a visible window is passed it will be focused.
/// If null is passed, the top window in the stack of the focused output will be focused.
-/// Requires a call to Root.applyPending()
+/// Requires a call to WindowManager.applyPending()
pub fn focus(seat: *Seat, target: ?*Window) void {
// Views may not receive focus while locked.
if (server.lock_manager.state != .unlocked) return;
diff --git a/river/Server.zig b/river/Server.zig
index 8ec9201..7e15659 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -415,7 +415,7 @@ fn handleRequestActivate(
switch (node_data.data) {
.window => |window| if (window.pending.focus == 0) {
window.pending.urgent = true;
- server.root.applyPending();
+ server.wm.applyPending();
},
else => |tag| {
log.info("ignoring xdg-activation-v1 activate request of {s} surface", .{@tagName(tag)});
diff --git a/river/Window.zig b/river/Window.zig
index 779c251..3a327b7 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -131,7 +131,7 @@ destroying: bool = false,
/// to be applied as a single atomic transaction across all clients as soon as any
/// in progress transaction has been completed.
///
-/// Any time pending state is modified Root.applyPending() must be called
+/// Any time pending state is modified WindowManager.applyPending() must be called
/// before yielding back to the event loop.
pending: State = .{},
pending_render_list_link: wl.list.Link,
@@ -153,10 +153,10 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
const window = try util.gpa.create(Window);
errdefer util.gpa.destroy(window);
- const tree = try server.root.hidden.tree.createSceneTree();
+ const tree = try server.root.hidden_tree.createSceneTree();
errdefer tree.node.destroy();
- const popup_tree = try server.root.hidden.tree.createSceneTree();
+ const popup_tree = try server.root.hidden_tree.createSceneTree();
errdefer popup_tree.node.destroy();
window.* = .{
@@ -177,9 +177,9 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
.inflight_render_list_link = undefined,
};
- server.root.windows.prepend(window);
- server.root.hidden.pending.render_list.prepend(window);
- server.root.hidden.inflight.render_list.prepend(window);
+ server.wm.windows.prepend(window);
+ server.wm.pending.render_list.prepend(window);
+ server.wm.inflight.render_list.prepend(window);
window.tree.node.setEnabled(false);
window.popup_tree.node.setEnabled(false);
@@ -492,7 +492,7 @@ pub fn map(window: *Window) !void {
window.foreign_toplevel_handle.map();
- server.root.applyPending();
+ server.wm.applyPending();
}
/// Called by the impl when the surface will no longer be displayed
@@ -501,17 +501,15 @@ pub fn unmap(window: *Window) void {
if (!window.saved_surface_tree.node.enabled) window.saveSurfaceTree();
- {
- window.pending_render_list_link.remove();
- server.root.hidden.pending.render_list.prepend(window);
- }
+ //window.pending_render_list_link.remove();
+ //server.root.hidden.pending.render_list.prepend(window);
assert(window.mapped and !window.destroying);
window.mapped = false;
window.foreign_toplevel_handle.unmap();
- server.root.applyPending();
+ server.wm.applyPending();
}
pub fn notifyTitle(window: *const Window) void {
diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index 38edd54..bc9be75 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -24,6 +24,8 @@ const river = @import("wayland").server.river;
const server = &@import("main.zig").server;
const util = @import("util.zig");
+const Window = @import("Window.zig");
+
const log = std.log.scoped(.wm);
global: *wl.Global,
@@ -32,17 +34,52 @@ server_destroy: wl.Listener(*wl.Server) = wl.Listener(*wl.Server).init(handleSer
/// The active window manager, if any.
wm_v1: ?*river.WindowManagerV1 = null,
+windows: wl.list.Head(Window, .link),
+
+/// Pending state sent by the window manager but not yet committed
+/// TODO maybe need to split this into uncommitted and committed but not yet part of transaction
+pending: struct {
+ render_list: wl.list.Head(Window, .pending_render_list_link),
+},
+
+/// Committed state from the window manager that is part of the current transaction.
+inflight: struct {
+ render_list: wl.list.Head(Window, .inflight_render_list_link),
+},
+
+/// Number of inflight configures sent to windows in the current transaction.
+inflight_configures: u32 = 0,
+transaction_timeout: *wl.EventSource,
+/// Set to true if applyPending() is called while a transaction is inflight.
+/// If true when a transaction completes, causes applyPending() to be called again.
+pending_state_dirty: bool = false,
+
pub fn init(wm: *WindowManager) !void {
+ const event_loop = server.wl_server.getEventLoop();
+ const transaction_timeout = try event_loop.addTimer(*WindowManager, handleTransactionTimeout, wm);
+ errdefer transaction_timeout.remove();
+
wm.* = .{
.global = try wl.Global.create(server.wl_server, river.WindowManagerV1, 1, *WindowManager, wm, bind),
+ .windows = undefined,
+ .pending = .{
+ .render_list = undefined,
+ },
+ .inflight = .{
+ .render_list = undefined,
+ },
+ .transaction_timeout = transaction_timeout,
};
+ wm.windows.init();
server.wl_server.addDestroyListener(&wm.server_destroy);
}
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
const wm: *WindowManager = @fieldParentPtr("server_destroy", listener);
+
wm.global.destroy();
+ wm.transaction_timeout.remove();
}
fn bind(client: *wl.Client, wm: *WindowManager, version: u32, id: u32) void {
@@ -91,3 +128,141 @@ fn handleRequest(
.get_shell_surface => |_| {},
}
}
+
+/// Trigger asynchronous application of pending state for all outputs and windows.
+/// Changes will not be applied to the scene graph until the layout generator
+/// generates a new layout for all outputs and all affected clients ack a
+/// configure and commit a new buffer.
+pub fn applyPending(wm: *WindowManager) void {
+ {
+ // Changes to the pending state may require a focus update to keep
+ // state consistent. Instead of having focus(null) calls spread all
+ // around the codebase and risk forgetting one, always ensure focus
+ // state is synchronized here.
+ var it = server.input_manager.seats.first;
+ while (it) |node| : (it = node.next) node.data.focus(null);
+ }
+
+ // If there is already a transaction inflight, wait until it completes.
+ if (wm.inflight_configures > 0) {
+ wm.pending_state_dirty = true;
+ return;
+ }
+ wm.pending_state_dirty = false;
+
+ {
+ var it = server.input_manager.seats.first;
+ while (it) |node| : (it = node.next) {
+ const cursor = &node.data.cursor;
+
+ switch (cursor.mode) {
+ .passthrough, .down => {},
+ inline .move, .resize => |data| {
+ if (data.window.inflight.fullscreen) {
+ cursor.mode = .passthrough;
+ data.window.pending.resizing = false;
+ data.window.inflight.resizing = false;
+ }
+ },
+ }
+
+ cursor.inflight_mode = cursor.mode;
+ }
+ }
+
+ wm.sendConfigures();
+}
+
+fn sendConfigures(wm: *WindowManager) void {
+ assert(wm.inflight_configures == 0);
+
+ {
+ var it = wm.inflight.render_list.iterator(.forward);
+ while (it.next()) |window| {
+ assert(!window.inflight_transaction);
+ window.inflight_transaction = true;
+
+ // This can happen if a window is unmapped while a layout demand including it is inflight
+ // If a window has been unmapped, don't send it a configure.
+ if (!window.mapped) continue;
+
+ if (window.configure()) {
+ wm.inflight_configures += 1;
+
+ window.saveSurfaceTree();
+ window.sendFrameDone();
+ }
+ }
+ }
+
+ if (wm.inflight_configures > 0) {
+ std.log.scoped(.transaction).debug("started transaction with {} pending configure(s)", .{
+ wm.inflight_configures,
+ });
+
+ wm.transaction_timeout.timerUpdate(100) catch {
+ std.log.scoped(.transaction).err("failed to update timer", .{});
+ wm.commitTransaction();
+ };
+ } else {
+ wm.commitTransaction();
+ }
+}
+
+fn handleTransactionTimeout(wm: *WindowManager) c_int {
+ std.log.scoped(.transaction).err("timeout occurred, some imperfect frames may be shown", .{});
+
+ wm.inflight_configures = 0;
+ wm.commitTransaction();
+
+ return 0;
+}
+
+pub fn notifyConfigured(wm: *WindowManager) void {
+ wm.inflight_configures -= 1;
+ if (wm.inflight_configures == 0) {
+ // Disarm the timer, as we didn't timeout
+ wm.transaction_timeout.timerUpdate(0) catch std.log.scoped(.transaction).err("error disarming timer", .{});
+ wm.commitTransaction();
+ }
+}
+
+/// Apply the inflight state and drop stashed buffers. This means that
+/// the next frame drawn will be the post-transaction state of the
+/// layout. Should only be called after all clients have configured for
+/// the new layout. If called early imperfect frames may be drawn.
+fn commitTransaction(wm: *WindowManager) void {
+ assert(wm.inflight_configures == 0);
+
+ std.log.scoped(.transaction).debug("commiting transaction", .{});
+
+ {
+ var it = wm.inflight.render_list.iterator(.forward);
+ while (it.next()) |window| {
+ window.commitTransaction();
+
+ window.tree.node.setEnabled(true);
+ window.popup_tree.node.setEnabled(true);
+ }
+ }
+
+ {
+ var it = server.input_manager.seats.first;
+ while (it) |node| : (it = node.next) node.data.cursor.updateState();
+ }
+
+ {
+ // This must be done after updating cursor state in case the window was the target of move/resize.
+ var it = wm.inflight.render_list.safeIterator(.forward);
+ while (it.next()) |window| {
+ window.dropSavedSurfaceTree();
+ if (window.destroying) window.destroy(.assert);
+ }
+ }
+
+ server.idle_inhibit_manager.checkActive();
+
+ if (wm.pending_state_dirty) {
+ wm.applyPending();
+ }
+}
diff --git a/river/XdgDecoration.zig b/river/XdgDecoration.zig
index 6545eb9..909eba1 100644
--- a/river/XdgDecoration.zig
+++ b/river/XdgDecoration.zig
@@ -79,6 +79,6 @@ fn handleRequestMode(
if (window.pending.ssd != ssd) {
window.pending.ssd = ssd;
- server.root.applyPending();
+ server.wm.applyPending();
}
}
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 2c40642..c70b130 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -381,7 +381,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
switch (toplevel.configure_state) {
.acked => {
toplevel.configure_state = .committed;
- server.root.notifyConfigured();
+ server.wm.notifyConfigured();
},
.timed_out_acked => {
toplevel.configure_state = .idle;
@@ -400,7 +400,7 @@ fn handleRequestFullscreen(listener: *wl.Listener(void)) void {
const toplevel: *XdgToplevel = @fieldParentPtr("request_fullscreen", listener);
if (toplevel.window.pending.fullscreen != toplevel.wlr_toplevel.requested.fullscreen) {
toplevel.window.pending.fullscreen = toplevel.wlr_toplevel.requested.fullscreen;
- server.root.applyPending();
+ server.wm.applyPending();
}
}
diff --git a/river/XwaylandOverrideRedirect.zig b/river/XwaylandOverrideRedirect.zig
index 28f3480..4f4680e 100644
--- a/river/XwaylandOverrideRedirect.zig
+++ b/river/XwaylandOverrideRedirect.zig
@@ -176,7 +176,7 @@ fn handleUnmap(listener: *wl.Listener(void)) void {
}
}
- server.root.applyPending();
+ server.wm.applyPending();
}
fn handleSetGeometry(listener: *wl.Listener(void)) void {
diff --git a/river/XwaylandWindow.zig b/river/XwaylandWindow.zig
index 85751d2..7e1e487 100644
--- a/river/XwaylandWindow.zig
+++ b/river/XwaylandWindow.zig
@@ -229,7 +229,7 @@ fn handleRequestConfigure(
// Allow xwayland windows to set their own dimensions (but not position) if floating
xwayland_window.window.pending.box.width = event.width;
xwayland_window.window.pending.box.height = event.height;
- server.root.applyPending();
+ server.wm.applyPending();
}
fn handleSetOverrideRedirect(listener: *wl.Listener(void)) void {
@@ -272,7 +272,7 @@ fn handleSetDecorations(listener: *wl.Listener(void)) void {
if (window.pending.ssd != ssd) {
window.pending.ssd = ssd;
- server.root.applyPending();
+ server.wm.applyPending();
}
}
@@ -280,7 +280,7 @@ fn handleRequestFullscreen(listener: *wl.Listener(void)) void {
const xwayland_window: *XwaylandWindow = @fieldParentPtr("request_fullscreen", listener);
if (xwayland_window.window.pending.fullscreen != xwayland_window.xwayland_surface.fullscreen) {
xwayland_window.window.pending.fullscreen = xwayland_window.xwayland_surface.fullscreen;
- server.root.applyPending();
+ server.wm.applyPending();
}
}