Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
river: implement new rwm update sequence
protocol/river-window-management-v1.xml | 9 +-
river/Cursor.zig | 8 +-
river/LockManager.zig | 2 +-
river/Output.zig | 34 +--
river/OutputManager.zig | 62 ++--
river/PointerBinding.zig | 35 ++-
river/Seat.zig | 219 +++++++-------
river/ShellSurface.zig | 36 +--
river/Window.zig | 507 +++++++++++++++++---------------
river/WindowManager.zig | 286 +++++++++---------
river/WmNode.zig | 67 +++--
river/XdgToplevel.zig | 90 +++---
river/XkbBinding.zig | 48 +--
river/XwaylandOverrideRedirect.zig | 2 +-
river/XwaylandWindow.zig | 10 +-
rivercompat/Window.zig | 3 +
rivercompat/WindowManager.zig | 9 +-
17 files changed, 733 insertions(+), 694 deletions(-)
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index 4bcb773..427c0d2 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -91,7 +91,9 @@
</description>
<enum name="error">
- <entry name="role" value="0"
+ <entry name="update_sequence_order" value="0"
+ sumary="request violates update sequence ordering"/>
+ <entry name="role" value="1"
summary="given wl_surface already has a role"/>
</enum>
@@ -988,8 +990,9 @@
position. For example, this event is necessary to communicate the
results of interactive resize to the window manager client.
- This event will be followed by an update_windowing_start event after all
- other new state has been sent by the server.
+ This event is sent during update sequences after the
+ update_windowing_finish request and before the update_rendering_start
+ event.
</description>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 67d22a3..fec0416 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -483,12 +483,12 @@ pub fn processAxis(cursor: *Cursor, event: *const wlr.Pointer.event.Axis) void {
fn interact(cursor: Cursor, result: Scene.AtResult) void {
switch (result.data) {
.window => |window| {
- cursor.seat.pending.interaction = .{ .window = window };
- server.wm.dirtyPending();
+ cursor.seat.windowing_scheduled.interaction = .{ .window = window };
+ server.wm.dirtyWindowing();
},
.shell_surface => |shell_surface| {
- cursor.seat.pending.interaction = .{ .shell_surface = shell_surface };
- server.wm.dirtyPending();
+ cursor.seat.windowing_scheduled.interaction = .{ .shell_surface = shell_surface };
+ server.wm.dirtyWindowing();
},
.lock_surface => |lock_surface| {
assert(server.lock_manager.state != .unlocked);
diff --git a/river/LockManager.zig b/river/LockManager.zig
index b955681..184d836 100644
--- a/river/LockManager.zig
+++ b/river/LockManager.zig
@@ -203,7 +203,7 @@ fn handleUnlock(listener: *wl.Listener(void)) void {
handleDestroy(&manager.destroy);
- server.wm.dirtyPending();
+ server.wm.dirtyWindowing();
}
fn handleDestroy(listener: *wl.Listener(void)) void {
diff --git a/river/Output.zig b/river/Output.zig
index 1c46e98..4ab3d63 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -158,9 +158,9 @@ gamma_dirty: bool = false,
/// Root.outputs
link: wl.list.Link,
-/// Pending state to be sent to the wm in the next update sequence.
-pending: State = .{},
-/// State sent to the wm in the latest update sequence.
+/// State to be sent to the wm in the next windowing update sequence.
+scheduled: State = .{},
+/// State sent to the wm in the latest windowing update sequence.
sent: State = .{},
link_sent: wl.list.Link,
/// State applied to the wlr_output and rendered.
@@ -207,12 +207,12 @@ pub fn create(wlr_output: *wlr.Output) !void {
wlr_output.events.frame.add(&output.frame);
wlr_output.events.present.add(&output.present);
- output.pending.state = .enabled;
+ output.scheduled.state = .enabled;
if (wlr_output.preferredMode()) |preferred_mode| {
- output.pending.mode = .{ .standard = preferred_mode };
+ output.scheduled.mode = .{ .standard = preferred_mode };
}
- server.wm.dirtyPending();
+ server.wm.dirtyWindowing();
}
fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
@@ -231,13 +231,13 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) v
output.wlr_output = null;
output.scene_output = null;
- output.pending.state = .destroying;
+ output.scheduled.state = .destroying;
- server.wm.dirtyPending();
+ server.wm.dirtyWindowing();
}
-pub fn sendDirty(output: *Output) void {
- switch (output.pending.state) {
+pub fn updateWindowingStart(output: *Output) void {
+ switch (output.scheduled.state) {
.enabled, .disabled_soft => {
if (server.wm.object) |wm_v1| {
const new = output.object == null;
@@ -255,7 +255,7 @@ pub fn sendDirty(output: *Output) void {
};
errdefer comptime unreachable;
- const pending = &output.pending;
+ const pending = &output.scheduled;
const sent = &output.sent;
if (new or pending.width() != sent.width() or pending.height() != sent.height()) {
@@ -266,10 +266,10 @@ pub fn sendDirty(output: *Output) void {
}
}
- output.sent = output.pending;
+ output.sent = output.scheduled;
output.link_sent.remove();
- server.wm.sent.outputs.append(output);
+ server.wm.windowing_sent.outputs.append(output);
},
.disabled_hard, .destroying => {
if (output.object) |output_v1| {
@@ -281,7 +281,7 @@ pub fn sendDirty(output: *Output) void {
output.link_sent.remove();
output.link_sent.init();
- if (output.pending.state == .destroying) {
+ if (output.scheduled.state == .destroying) {
util.gpa.destroy(output);
}
},
@@ -330,16 +330,16 @@ fn handleRequestState(listener: *wl.Listener(*wlr.Output.event.RequestState), ev
log.debug("backend requested new mode", .{});
if (event.state.mode) |mode| {
- output.pending.mode = .{ .standard = mode };
+ output.scheduled.mode = .{ .standard = mode };
} else {
- output.pending.mode = .{ .custom = .{
+ output.scheduled.mode = .{ .custom = .{
.width = event.state.custom_mode.width,
.height = event.state.custom_mode.height,
.refresh = event.state.custom_mode.refresh,
} };
}
- server.wm.dirtyPending();
+ server.wm.dirtyWindowing();
}
fn handleFrame(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
diff --git a/river/OutputManager.zig b/river/OutputManager.zig
index f982af4..10409d4 100644
--- a/river/OutputManager.zig
+++ b/river/OutputManager.zig
@@ -139,7 +139,7 @@ fn handleManagerApply(_: *wl.Listener(*wlr.OutputConfigurationV1), config: *wlr.
while (it.next()) |head| {
const output: *Output = @ptrFromInt(head.state.output.data);
- output.pending = .{
+ output.scheduled = .{
.state = if (head.state.enabled) .enabled else .disabled_hard,
.mode = blk: {
if (head.state.mode) |mode| {
@@ -160,13 +160,13 @@ fn handleManagerApply(_: *wl.Listener(*wlr.OutputConfigurationV1), config: *wlr.
};
}
- if (server.wm.pending.output_config) |old| {
+ if (server.wm.windowing_scheduled.output_config) |old| {
old.sendFailed();
old.destroy();
}
- server.wm.pending.output_config = config;
+ server.wm.windowing_scheduled.output_config = config;
- server.wm.dirtyPending();
+ server.wm.dirtyWindowing();
}
fn validateConfigCoordinates(config: *wlr.OutputConfigurationV1) bool {
@@ -199,17 +199,17 @@ fn handlePowerManagerSetMode(
event.output.name,
});
- switch (output.pending.state) {
+ switch (output.scheduled.state) {
.enabled => {
- if (event.mode == .off) output.pending.state = .disabled_soft else return;
+ if (event.mode == .off) output.scheduled.state = .disabled_soft else return;
},
.disabled_soft => {
- if (event.mode == .on) output.pending.state = .enabled else return;
+ if (event.mode == .on) output.scheduled.state = .enabled else return;
},
.disabled_hard, .destroying => unreachable,
}
- server.wm.dirtyPending();
+ server.wm.dirtyWindowing();
}
fn handleSetGamma(
@@ -232,12 +232,12 @@ pub fn autoLayout(om: *OutputManager) void {
{
var it = om.outputs.iterator(.forward);
while (it.next()) |output| {
- if (output.pending.auto_layout) continue;
+ if (output.scheduled.auto_layout) continue;
- const x = output.pending.x + output.pending.width();
+ const x = output.scheduled.x + output.scheduled.width();
if (x > rightmost_edge) {
rightmost_edge = x;
- row_y = output.pending.y;
+ row_y = output.scheduled.y;
}
}
}
@@ -245,11 +245,11 @@ pub fn autoLayout(om: *OutputManager) void {
{
var it = om.outputs.iterator(.forward);
while (it.next()) |output| {
- if (!output.pending.auto_layout) continue;
+ if (!output.scheduled.auto_layout) continue;
- output.pending.x = rightmost_edge;
- output.pending.y = row_y;
- rightmost_edge += output.pending.width();
+ output.scheduled.x = rightmost_edge;
+ output.scheduled.y = row_y;
+ rightmost_edge += output.scheduled.width();
}
}
}
@@ -258,7 +258,7 @@ pub fn commitOutputState(om: *OutputManager) void {
const wm = &server.wm;
{
- var it = wm.sent.outputs.iterator(.forward);
+ var it = wm.windowing_sent.outputs.iterator(.forward);
while (it.next()) |output| {
const wlr_output = output.wlr_output orelse continue;
switch (output.sent.state) {
@@ -279,7 +279,7 @@ pub fn commitOutputState(om: *OutputManager) void {
server.input_manager.reconfigureDevices();
const need_modeset = blk: {
- var it = wm.sent.outputs.iterator(.forward);
+ var it = wm.windowing_sent.outputs.iterator(.forward);
while (it.next()) |output| {
const wlr_output = output.wlr_output orelse continue;
@@ -313,7 +313,7 @@ pub fn commitOutputState(om: *OutputManager) void {
defer for (states.items) |*s| s.base.finish();
{
- var it = wm.sent.outputs.iterator(.forward);
+ var it = wm.windowing_sent.outputs.iterator(.forward);
while (it.next()) |output| {
const wlr_output = output.wlr_output orelse continue;
const state = states.addOne() catch {
@@ -336,20 +336,20 @@ pub fn commitOutputState(om: *OutputManager) void {
log.err("failed to prepare new output configuration", .{});
// TODO search for a working fallback
- if (wm.sent.output_config) |config| {
+ if (wm.windowing_sent.output_config) |config| {
config.sendFailed();
config.destroy();
- wm.sent.output_config = null;
+ wm.windowing_sent.output_config = null;
}
{
// Revert to last working state on failure
- var it = wm.sent.outputs.iterator(.forward);
+ var it = wm.windowing_sent.outputs.iterator(.forward);
while (it.next()) |output| {
- output.pending = output.current;
+ output.scheduled = output.current;
output.sent = output.current;
}
- wm.dirtyPending();
+ wm.dirtyWindowing();
}
return;
}
@@ -366,20 +366,20 @@ pub fn commitOutputState(om: *OutputManager) void {
if (!server.backend.commit(states.items)) {
log.err("failed to commit new output configuration", .{});
- if (wm.sent.output_config) |config| {
+ if (wm.windowing_sent.output_config) |config| {
config.sendFailed();
config.destroy();
- wm.sent.output_config = null;
+ wm.windowing_sent.output_config = null;
}
{
// Revert to last working state on failure
- var it = wm.sent.outputs.iterator(.forward);
+ var it = wm.windowing_sent.outputs.iterator(.forward);
while (it.next()) |output| {
- output.pending = output.current;
+ output.scheduled = output.current;
output.sent = output.current;
}
- wm.dirtyPending();
+ wm.dirtyWindowing();
}
return;
}
@@ -387,14 +387,14 @@ pub fn commitOutputState(om: *OutputManager) void {
swapchain_manager.apply();
}
- if (wm.sent.output_config) |config| {
+ if (wm.windowing_sent.output_config) |config| {
config.sendSucceeded();
config.destroy();
- wm.sent.output_config = null;
+ wm.windowing_sent.output_config = null;
}
{
- var it = wm.sent.outputs.iterator(.forward);
+ var it = wm.windowing_sent.outputs.iterator(.forward);
while (it.next()) |output| {
output.current = output.sent;
diff --git a/river/PointerBinding.zig b/river/PointerBinding.zig
index b7ee0fe..c90bf88 100644
--- a/river/PointerBinding.zig
+++ b/river/PointerBinding.zig
@@ -31,25 +31,22 @@ const Seat = @import("Seat.zig");
const log = std.log.scoped(.input);
-const WmState = struct {
- enabled: bool = false,
-};
-
seat: *Seat,
object: *river.PointerBindingV1,
button: u32,
modifiers: river.SeatV1.Modifiers,
-pending: struct {
+windowing_scheduled: struct {
state_change: enum {
none,
pressed,
released,
} = .none,
} = .{},
-uncommitted: WmState = .{},
-committed: WmState = .{},
+windowing_requested: struct {
+ enabled: bool = false,
+} = .{},
/// This bit of state is used to ensure that multiple simultaneous
/// presses across multiple keyboards do not cause multiple press
@@ -111,8 +108,14 @@ fn handleRequest(
assert(binding.object == pointer_binding_v1);
switch (request) {
.destroy => pointer_binding_v1.destroy(),
- .enable => binding.uncommitted.enabled = true,
- .disable => binding.uncommitted.enabled = false,
+ .enable => {
+ if (!server.wm.ensureWindowing()) return;
+ binding.windowing_requested.enabled = true;
+ },
+ .disable => {
+ if (!server.wm.ensureWindowing()) return;
+ binding.windowing_requested.enabled = false;
+ },
}
}
@@ -120,18 +123,18 @@ pub fn pressed(binding: *PointerBinding) void {
assert(!binding.sent_pressed);
// Input event processing should not continue after a press/release event
// until that event is sent to the window manager in an update and acked.
- assert(binding.pending.state_change == .none);
- binding.pending.state_change = .pressed;
- server.wm.dirtyPending();
+ assert(binding.windowing_scheduled.state_change == .none);
+ binding.windowing_scheduled.state_change = .pressed;
+ server.wm.dirtyWindowing();
}
pub fn released(binding: *PointerBinding) void {
assert(binding.sent_pressed);
// Input event processing should not continue after a press/release event
// until that event is sent to the window manager in an update and acked.
- assert(binding.pending.state_change == .none);
- binding.pending.state_change = .released;
- server.wm.dirtyPending();
+ assert(binding.windowing_scheduled.state_change == .none);
+ binding.windowing_scheduled.state_change = .released;
+ server.wm.dirtyWindowing();
}
pub fn match(
@@ -139,7 +142,7 @@ pub fn match(
button: u32,
modifiers: wlr.Keyboard.ModifierMask,
) bool {
- if (!binding.committed.enabled) return false;
+ if (!binding.windowing_requested.enabled) return false;
return button == binding.button and
@as(u32, @bitCast(modifiers)) == @as(u32, @bitCast(binding.modifiers));
diff --git a/river/Seat.zig b/river/Seat.zig
index 9907f2f..a9541a3 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -73,18 +73,6 @@ pub const Event = union(enum) {
pointer_pinch_end: wlr.Pointer.event.PinchEnd,
};
-pub const WmState = struct {
- focus: WmFocus = .none,
- op: union(enum) {
- none,
- //TODO start_serial: ?u32,
- start_pointer,
- end,
- } = .none,
- // TODO confine region
- // TODO pointer warp
-};
-
pub const WmFocus = union(enum) {
none,
window: *Window,
@@ -123,25 +111,33 @@ object: ?*river.SeatV1 = null,
event_queue: EventQueue = EventQueue.init(),
-/// State to be sent to the window manager client in the next update sequence.
-pending: struct {
+/// State to be sent to the wm in the next windowing update sequence.
+windowing_scheduled: struct {
/// The window entered/hovered by the pointer, if any
window: ?*Window = null,
/// The window clicked on, touched, etc.
interaction: WmFocus = .none,
} = .{},
-/// State sent to the window manager client in the latest update sequence.
-sent: struct {
+/// State sent to the wm in the latest windowing update sequence.
+windowing_sent: struct {
/// The window entered/hovered by the pointer, if any
window: ?*Window = null,
} = .{},
link_sent: wl.list.Link,
-/// State requested by the window manager client but not yet committed.
-uncommitted: WmState = .{},
-/// State requested by the window manager client and committed.
-committed: WmState = .{},
+/// Windowing state requested by the wm.
+windowing_requested: struct {
+ focus: WmFocus = .none,
+ op: union(enum) {
+ none,
+ //TODO start_serial: ?u32,
+ start_pointer,
+ end,
+ } = .none,
+ // TODO confine region
+ // TODO pointer warp
+} = .{},
xkb_bindings: wl.list.Head(XkbBinding, .link),
pointer_bindings: wl.list.Head(PointerBinding, .link),
@@ -152,15 +148,17 @@ cursor: Cursor,
op: ?struct {
// We always want to process as many input events as possible before sending configures
// and starting a transaction. Therefore, we set this flag if a seat operation modifies
- // pending state and check it at the end of Seat.processEvents() rather than calling
- // WindowManager.dirtyPending() directly in Seat.updateOp().
- dirty: bool = false,
+ // Window.pending state and check it at the end of Seat.processEvents() rather than sending
+ // configures directly in Seat.updateOp().
+ need_configures: bool = false,
input: enum {
pointer,
},
/// Coordinates of the cursor/touch point/etc. at the start of the operation.
start_x: i32,
start_y: i32,
+ x: i32,
+ y: i32,
} = null,
relay: InputRelay,
@@ -203,7 +201,7 @@ pub fn create(name: [*:0]const u8) !void {
server.input_manager.seats.append(seat);
seat.link_sent.init();
- server.wm.dirtyPending();
+ server.wm.dirtyWindowing();
seat.xkb_bindings.init();
seat.pointer_bindings.init();
@@ -228,10 +226,8 @@ pub fn destroy(seat: *Seat) void {
var it = server.wm.windows.iterator(.forward);
while (it.next()) |window| {
inline for (.{
- &window.uncommitted,
- &window.committed,
- &window.pending,
- &window.sent,
+ &window.windowing_requested,
+ window,
}) |state| {
switch (state.op) {
.none => {},
@@ -275,16 +271,12 @@ pub fn queueEvent(seat: *Seat, event: Event) void {
pub fn processEvents(seat: *Seat) void {
assert(server.wm.state == .idle);
- // Only process events while there is no pending state to be sent to the window manager
- // and no transaction in progress.
- //
+ // Only process events while there is no new windowing state to be sent to the window manager.
// The window manager might decide to change focus or redefine keyboard/pointer bindings
- // in response to the pending update, which can affect further processing of events.
- //
- // Allowing event processing while there is a transaction in progress would require keeping
- // track of additional state to differentiate pending state modified since the transaction
- // was started. I don't see an advantage to that additional complexity.
- while (server.wm.state == .idle and !server.wm.pending.dirty) {
+ // in response to the windowing update, which can affect further processing of events.
+ while (!server.wm.windowing_scheduled.dirty) {
+ assert(server.wm.state == .idle);
+
const event = seat.event_queue.readItem() orelse break;
const pg = server.input_manager.pointer_gestures;
@@ -307,16 +299,18 @@ pub fn processEvents(seat: *Seat) void {
.pointer_pinch_end => |ev| pg.sendPinchEnd(seat.wlr_seat, ev.time_msec, ev.cancelled),
}
}
+ assert(server.wm.state == .idle);
if (seat.op) |*op| {
- if (op.dirty) {
- op.dirty = false;
- server.wm.sendConfigures();
+ if (op.need_configures) {
+ op.need_configures = false;
+ server.wm.state = .update_windowing;
+ server.wm.updateWindowingFinish();
}
}
}
-pub fn sendDirty(seat: *Seat) void {
+pub fn updateWindowingStart(seat: *Seat) void {
if (seat.destroying) {
if (seat.object) |seat_v1| {
seat_v1.sendRemoved();
@@ -340,52 +334,52 @@ pub fn sendDirty(seat: *Seat) void {
wm_v1.sendSeat(seat_v1);
seat.link_sent.remove();
- server.wm.sent.seats.append(seat);
+ server.wm.windowing_sent.seats.append(seat);
break :blk seat_v1;
};
errdefer comptime unreachable;
if (new) {
- if (seat.pending.window) |window| {
+ if (seat.windowing_scheduled.window) |window| {
if (window.object) |window_v1| {
seat_v1.sendPointerEnter(window_v1);
- seat.sent.window = seat.pending.window;
+ seat.windowing_sent.window = seat.windowing_scheduled.window;
}
}
- } else if (seat.pending.window != seat.sent.window) {
- if (seat.sent.window) |window| {
+ } else if (seat.windowing_scheduled.window != seat.windowing_sent.window) {
+ if (seat.windowing_sent.window) |window| {
if (window.object) |window_v1| {
seat_v1.sendPointerLeave(window_v1);
- seat.sent.window = null;
+ seat.windowing_sent.window = null;
}
}
- if (seat.pending.window) |window| {
+ if (seat.windowing_scheduled.window) |window| {
if (window.object) |window_v1| {
seat_v1.sendPointerEnter(window_v1);
- seat.sent.window = window;
+ seat.windowing_sent.window = window;
}
}
}
- switch (seat.pending.interaction) {
+ switch (seat.windowing_scheduled.interaction) {
.none => {},
.window => |window| {
if (window.object) |window_v1| {
seat_v1.sendWindowInteraction(window_v1);
- seat.pending.interaction = .none;
+ seat.windowing_scheduled.interaction = .none;
}
},
.shell_surface => |shell_surface| {
seat_v1.sendShellSurfaceInteraction(shell_surface.object);
- seat.pending.interaction = .none;
+ seat.windowing_scheduled.interaction = .none;
},
}
{
var it = seat.xkb_bindings.iterator(.forward);
while (it.next()) |binding| {
- switch (binding.pending.state_change) {
+ switch (binding.windowing_scheduled.state_change) {
.none => {},
.pressed => {
assert(!binding.sent_pressed);
@@ -398,13 +392,13 @@ pub fn sendDirty(seat: *Seat) void {
binding.object.sendReleased();
},
}
- binding.pending.state_change = .none;
+ binding.windowing_scheduled.state_change = .none;
}
}
{
var it = seat.pointer_bindings.iterator(.forward);
while (it.next()) |binding| {
- switch (binding.pending.state_change) {
+ switch (binding.windowing_scheduled.state_change) {
.none => {},
.pressed => {
assert(!binding.sent_pressed);
@@ -417,7 +411,7 @@ pub fn sendDirty(seat: *Seat) void {
binding.object.sendReleased();
},
}
- binding.pending.state_change = .none;
+ binding.windowing_scheduled.state_change = .none;
}
}
}
@@ -448,35 +442,48 @@ fn handleRequest(
},
.focus_window => |args| {
+ if (!server.wm.ensureWindowing()) return;
const data = args.window.getUserData() orelse return;
const window: *Window = @ptrCast(@alignCast(data));
- seat.uncommitted.focus = .{ .window = window };
+ seat.windowing_requested.focus = .{ .window = window };
},
.focus_shell_surface => |args| {
+ if (!server.wm.ensureWindowing()) return;
const data = args.shell_surface.getUserData() orelse return;
const shell_surface: *ShellSurface = @ptrCast(@alignCast(data));
- seat.uncommitted.focus = .{ .shell_surface = shell_surface };
+ seat.windowing_requested.focus = .{ .shell_surface = shell_surface };
},
- .clear_focus => seat.uncommitted.focus = .none,
+ .clear_focus => seat.windowing_requested.focus = .none,
- .op_start_serial => {},
- .op_start_pointer => seat.uncommitted.op = .start_pointer,
+ .op_start_serial => {
+ if (!server.wm.ensureWindowing()) return;
+ // XXX TODO
+ },
+ .op_start_pointer => {
+ if (!server.wm.ensureWindowing()) return;
+ seat.windowing_requested.op = .start_pointer;
+ },
.op_add_move_window => |args| {
+ if (!server.wm.ensureWindowing()) return;
const data = args.window.getUserData() orelse return;
const window: *Window = @ptrCast(@alignCast(data));
- window.uncommitted.op = .{ .move = .{
+ window.windowing_requested.op = .{ .move = .{
.seat = seat,
} };
},
.op_add_resize_window => |args| {
+ if (!server.wm.ensureWindowing()) return;
const data = args.window.getUserData() orelse return;
const window: *Window = @ptrCast(@alignCast(data));
- window.uncommitted.op = .{ .resize = .{
+ window.windowing_requested.op = .{ .resize = .{
.seat = seat,
.edges = args.edges,
} };
},
- .op_end => seat.uncommitted.op = .end,
+ .op_end => {
+ if (!server.wm.ensureWindowing()) return;
+ seat.windowing_requested.op = .end;
+ },
.pointer_confine_to_region => {},
.pointer_warp => {},
@@ -512,34 +519,16 @@ fn handleRequest(
}
}
-pub fn commitWmState(seat: *Seat) void {
- {
- var it = seat.xkb_bindings.iterator(.forward);
- while (it.next()) |binding| {
- binding.committed = binding.uncommitted;
- }
- }
- {
- var it = seat.pointer_bindings.iterator(.forward);
- while (it.next()) |binding| {
- binding.committed = binding.uncommitted;
- }
- }
-
- seat.committed = seat.uncommitted;
- seat.uncommitted.op = .none;
-}
-
-pub fn applyCommitted(seat: *Seat) void {
+pub fn updateWindowingFinish(seat: *Seat) void {
if (server.lock_manager.state != .unlocked) return;
- switch (seat.committed.focus) {
+ switch (seat.windowing_requested.focus) {
.none => seat.focus(.none),
.window => |window| seat.focus(.{ .window = window }),
.shell_surface => |shell_surface| seat.focus(.{ .shell_surface = shell_surface }),
}
- switch (seat.committed.op) {
+ switch (seat.windowing_requested.op) {
.none => {},
.start_pointer => if (seat.op == null) {
log.debug("start seat op pointer", .{});
@@ -547,34 +536,36 @@ pub fn applyCommitted(seat: *Seat) void {
.input = .pointer,
.start_x = @intFromFloat(seat.cursor.wlr_cursor.x),
.start_y = @intFromFloat(seat.cursor.wlr_cursor.y),
+ .x = @intFromFloat(seat.cursor.wlr_cursor.x),
+ .y = @intFromFloat(seat.cursor.wlr_cursor.y),
};
seat.cursor.startOpPointer();
{
var it = server.wm.windows.iterator(.forward);
while (it.next()) |window| {
- switch (window.committed.op) {
+ switch (window.windowing_requested.op) {
.none => {},
.move => |data| {
if (data.seat == seat) {
- assert(window.pending.op == .none);
- window.pending.op = .{
+ assert(window.op == .none);
+ window.op = .{
.move = .{
.seat = seat,
- .start_x = window.wm_pending.box.x,
- .start_y = window.wm_pending.box.y,
+ .start_x = window.rendering_sent.box.x,
+ .start_y = window.rendering_sent.box.y,
},
};
}
},
.resize => |data| {
if (data.seat == seat) {
- assert(window.pending.op == .none);
- window.pending.op = .{
+ assert(window.op == .none);
+ window.op = .{
.resize = .{
.seat = seat,
.edges = data.edges,
- .start_box = window.wm_pending.box,
+ .start_box = window.rendering_sent.box,
},
};
}
@@ -593,11 +584,11 @@ pub fn applyCommitted(seat: *Seat) void {
{
var it = server.wm.windows.iterator(.forward);
while (it.next()) |window| {
- switch (window.pending.op) {
+ switch (window.op) {
.none => {},
inline .move, .resize => |data| {
if (data.seat == seat) {
- window.pending.op = .none;
+ window.op = .none;
}
},
}
@@ -605,6 +596,7 @@ pub fn applyCommitted(seat: *Seat) void {
}
},
}
+ seat.windowing_requested.op = .none;
}
pub fn focus(seat: *Seat, new_focus: Focus) void {
@@ -781,46 +773,45 @@ pub fn handleSwitchMapping(
}
pub fn updateOp(seat: *Seat, x: i32, y: i32) void {
- const op = seat.op.?;
+ const op = &seat.op.?;
- // Total dx/dy since operation start
- const dx = x - op.start_x;
- const dy = y - op.start_y;
+ op.x = x;
+ op.y = y;
+ // Position is not updated until the window has committed its new dimensions.
+ // The client may not commit exactly the dimensions we request and we need
+ // to know the actual committed dimensions to correctly place the top left
+ // corner in the case of a resize from the top or left edge.
{
var it = server.wm.windows.iterator(.forward);
while (it.next()) |window| {
- switch (window.pending.op) {
+ switch (window.op) {
.none => {},
.move => |data| {
if (data.seat != seat) continue;
- window.wm_pending.box.x = data.start_x + dx;
- window.wm_pending.box.y = data.start_y + dy;
-
- seat.op.?.dirty = true;
+ op.need_configures = true;
},
.resize => |data| {
if (data.seat != seat) continue;
- // For resize, position is not updated until the window has committed
- // its new dimensions. The client may not commit exactly the dimensions
- // we request and we need to know the actual committed dimensions to
- // correctly place the top left corner in the case of a resize from
- // the top or left edge.
+ // Total dx/dy since operation start
+ const dx = x - op.start_x;
+ const dy = y - op.start_y;
+
if (data.edges.left) {
- window.pending.width = @max(1, data.start_box.width - dx);
+ window.configure_scheduled.width = @max(1, data.start_box.width - dx);
} else if (data.edges.right) {
- window.pending.width = @max(1, data.start_box.width + dx);
+ window.configure_scheduled.width = @max(1, data.start_box.width + dx);
}
if (data.edges.top) {
- window.pending.height = @max(1, data.start_box.height - dy);
+ window.configure_scheduled.height = @max(1, data.start_box.height - dy);
} else if (data.edges.bottom) {
- window.pending.height = @max(1, data.start_box.height + dy);
+ window.configure_scheduled.height = @max(1, data.start_box.height + dy);
}
- seat.op.?.dirty = true;
+ op.need_configures = true;
},
}
}
diff --git a/river/ShellSurface.zig b/river/ShellSurface.zig
index 11d33f2..c77f45f 100644
--- a/river/ShellSurface.zig
+++ b/river/ShellSurface.zig
@@ -32,12 +32,6 @@ const WmNode = @import("WmNode.zig");
const log = std.log.scoped(.wm);
-const WmState = struct {
- x: i32 = 0,
- y: i32 = 0,
- sync_next_commit: bool = false,
-};
-
const role: wlr.Surface.Role = .{
.name = "river_scene_surface_v1",
.client_commit = clientCommit,
@@ -52,8 +46,11 @@ tree: *wlr.SceneTree,
surfaces: Scene.SaveableSurfaces,
node: WmNode,
-uncommitted: WmState = .{},
-committed: WmState = .{},
+rendering_requested: struct {
+ x: i32 = 0,
+ y: i32 = 0,
+ sync_next_commit: bool = false,
+} = .{},
pub fn create(
client: *wl.Client,
@@ -88,9 +85,8 @@ pub fn create(
.surfaces = surfaces,
.node = undefined,
};
-
shell_surface.node.init(.shell_surface);
- server.wm.uncommitted.render_list.append(&shell_surface.node);
+ server.wm.rendering_requested.list.append(&shell_surface.node);
shell_surface_v1.setHandler(*ShellSurface, handleRequest, handleDestroy, shell_surface);
}
@@ -124,7 +120,8 @@ fn handleRequest(
);
},
.sync_next_commit => {
- shell_surface.uncommitted.sync_next_commit = true;
+ if (!server.wm.ensureRendering()) return;
+ shell_surface.rendering_requested.sync_next_commit = true;
},
}
}
@@ -135,7 +132,7 @@ fn clientCommit(wlr_surface: *wlr.Surface) callconv(.C) void {
const shell_surface: *ShellSurface = @ptrCast(@alignCast(resource.getUserData()));
- if (shell_surface.uncommitted.sync_next_commit) {
+ if (shell_surface.rendering_requested.sync_next_commit) {
shell_surface.surfaces.save();
}
}
@@ -148,22 +145,19 @@ fn commit(wlr_surface: *wlr.Surface) callconv(.C) void {
}
}
-pub fn commitWmState(shell_surface: *ShellSurface) void {
- shell_surface.committed = shell_surface.uncommitted;
-
- if (shell_surface.uncommitted.sync_next_commit) {
- shell_surface.uncommitted.sync_next_commit = false;
+pub fn updateRenderingFinish(shell_surface: *ShellSurface) void {
+ const rendering_requested = &shell_surface.rendering_requested;
+ if (rendering_requested.sync_next_commit) {
+ rendering_requested.sync_next_commit = false;
if (!shell_surface.surfaces.saved.node.enabled) {
shell_surface.object.postError(.no_commit,
- \\no wl_surface.commit after sync_next_commit and before river_window_manager_v1.commit
+ \\no wl_surface.commit after sync_next_commit and before update_rendering_finish
);
}
}
-}
-pub fn commitTransaction(shell_surface: *ShellSurface) void {
shell_surface.surfaces.dropSaved();
- shell_surface.tree.node.setPosition(shell_surface.committed.x, shell_surface.committed.y);
+ shell_surface.tree.node.setPosition(rendering_requested.x, rendering_requested.y);
}
diff --git a/river/Window.zig b/river/Window.zig
index 4839e47..fe34006 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -66,10 +66,9 @@ pub const Border = struct {
a: u32 = 0,
};
-pub const State = struct {
+pub const Configure = struct {
width: ?u31 = null,
height: ?u31 = null,
- hidden: bool = false,
/// True if the window has keyboard focus from at least one seat.
activated: bool = false,
ssd: bool = false,
@@ -78,59 +77,12 @@ pub const State = struct {
capabilities: river.WindowV1.Capabilities = .{},
maximized: bool = false,
fullscreen: bool = false,
-
- op: union(enum) {
- none,
- move: struct {
- seat: *Seat,
- start_x: i32,
- start_y: i32,
- },
- resize: struct {
- seat: *Seat,
- edges: river.WindowV1.Edges = .{},
- start_box: wlr.Box,
- },
- } = .none,
-};
-
-pub const WmState = struct {
- position: ?struct {
- x: i32,
- y: i32,
- } = null,
- dimensions: ?struct {
- width: u31,
- height: u31,
- } = null,
- hidden: bool = false,
- ssd: bool = false,
- border: Border = .{},
- tiled: river.WindowV1.Edges = .{},
- capabilities: river.WindowV1.Capabilities = .{
- .window_menu = true,
- .maximize = true,
- .fullscreen = true,
- .minimize = true,
- },
- maximized: bool = false,
- fullscreen: bool = false, // XXX output
- close: bool = false,
- op: union(enum) {
- none,
- move: struct {
- seat: *Seat,
- },
- resize: struct {
- seat: *Seat,
- edges: river.WindowV1.Edges = .{},
- },
- } = .none,
+ resizing: bool = false,
};
/// The window management protocol object for this window
/// Created after the window is ready to be configured.
-/// Lifetime is managed through wm_pending.state
+/// Lifetime is managed through windowing_scheduled.state
object: ?*river.WindowV1 = null,
node: WmNode,
@@ -159,8 +111,8 @@ destroying: bool = false,
/// WindowManager.windows
link: wl.list.Link,
-/// State to be sent to the window manager client in the next update sequence.
-wm_pending: struct {
+/// State to be sent to the wm in the next windowing update sequence.
+windowing_scheduled: struct {
state: enum {
/// Indicates that there is currently no associated river_window_v1
/// object.
@@ -171,7 +123,6 @@ wm_pending: struct {
/// Indicates that the closed event will be sent in the next update sequence.
closing,
} = .init,
- box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
dimensions_hint: DimensionsHint = .{},
decoration_hint: river.WindowV1.DecorationHint = .only_supports_csd,
/// Set back to no_request at the end of each update sequence
@@ -185,25 +136,85 @@ wm_pending: struct {
dirty_title: bool = false,
} = .{},
-/// State sent to the window manager client in the latest update sequence.
+/// State sent to the wm in the latest windowing update sequence.
/// This state is only kept around in order to avoid sending redundant events
-/// to the window manager client.
-wm_sent: struct {
- position: ?struct { x: i32, y: i32 } = null,
- dimensions: ?struct { width: i32, height: i32 } = null,
+/// to the wm.
+windowing_sent: struct {
dimensions_hint: DimensionsHint = .{},
decoration_hint: river.WindowV1.DecorationHint = .only_supports_csd,
} = .{},
-/// State requested by the window manager client but not yet committed.
-uncommitted: WmState = .{},
-/// State requested by the window manager client and committed.
-committed: WmState = .{},
+/// Windowing state requested by the wm.
+windowing_requested: struct {
+ dimensions: ?struct {
+ width: u31,
+ height: u31,
+ } = null,
+ ssd: bool = false,
+ tiled: river.WindowV1.Edges = .{},
+ capabilities: river.WindowV1.Capabilities = .{
+ .window_menu = true,
+ .maximize = true,
+ .fullscreen = true,
+ .minimize = true,
+ },
+ maximized: bool = false,
+ fullscreen: bool = false, // XXX output
+ close: bool = false,
+ op: union(enum) {
+ none,
+ move: struct {
+ seat: *Seat,
+ },
+ resize: struct {
+ seat: *Seat,
+ edges: river.WindowV1.Edges = .{},
+ },
+ } = .none,
+} = .{},
/// State to be sent to the window in the next configure.
-pending: State = .{},
+configure_scheduled: Configure = .{},
/// State sent to the window in the latest configure.
-sent: State = .{},
+configure_sent: Configure = .{},
+
+/// State to be sent to the wm in the next rendering update sequence.
+rendering_scheduled: struct {
+ /// Dimensions committed by the window.
+ width: u31 = 0,
+ height: u31 = 0,
+ /// Send dimensions even if they are unchanged.
+ resend_dimensions: bool = false,
+} = .{},
+
+/// State sent to the wm in the latest rendering update sequence.
+rendering_sent: struct {
+ box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
+} = .{},
+
+/// Rendering state requested by the wm.
+rendering_requested: struct {
+ position: ?struct {
+ x: i32,
+ y: i32,
+ } = null,
+ hidden: bool = false,
+ border: Border = .{},
+} = .{},
+
+op: union(enum) {
+ none,
+ move: struct {
+ seat: *Seat,
+ start_x: i32,
+ start_y: i32,
+ },
+ resize: struct {
+ seat: *Seat,
+ edges: river.WindowV1.Edges = .{},
+ start_box: wlr.Box,
+ },
+} = .none,
pub fn create(impl: Impl) error{OutOfMemory}!*Window {
assert(impl != .none);
@@ -254,14 +265,14 @@ pub fn destroy(window: *Window, when: enum { lazy, assert }) void {
window.destroying = true;
- // We can't assert(window.wm_pending.state != .ready) since the client may
+ // We can't assert(window.windowing_scheduled.state != .ready) since the client may
// have exited after making its empty initial commit but before the surface
// is mapped.
- switch (window.wm_pending.state) {
+ switch (window.windowing_scheduled.state) {
.init => {},
.closing, .ready => {
- window.wm_pending.state = .closing;
- server.wm.dirtyPending();
+ window.windowing_scheduled.state = .closing;
+ server.wm.dirtyWindowing();
return;
},
}
@@ -296,77 +307,54 @@ pub fn destroy(window: *Window, when: enum { lazy, assert }) void {
}
pub fn setDimensionsHint(window: *Window, hint: DimensionsHint) void {
- window.wm_pending.dimensions_hint = hint;
- if (!meta.eql(window.wm_sent.dimensions_hint, hint)) {
- server.wm.dirtyPending();
+ window.windowing_scheduled.dimensions_hint = hint;
+ if (!meta.eql(window.windowing_sent.dimensions_hint, hint)) {
+ server.wm.dirtyWindowing();
}
}
-pub fn setDimensions(window: *Window, width: i32, height: i32) void {
- window.wm_pending.box.width = width;
- window.wm_pending.box.height = height;
-
- switch (window.sent.op) {
- .none, .move => {},
- .resize => |data| {
- assert(data.seat.op != null);
-
- if (data.edges.left) {
- window.wm_pending.box.x = data.start_box.x + data.start_box.width - width;
- } else if (data.edges.right) {
- window.wm_pending.box.x = data.start_box.x;
- }
-
- if (data.edges.top) {
- window.wm_pending.box.y = data.start_box.y + data.start_box.height - height;
- } else if (data.edges.bottom) {
- window.wm_pending.box.y = data.start_box.y;
- }
- },
- }
+pub fn setDimensions(window: *Window, width: u31, height: u31) void {
+ window.rendering_scheduled.width = width;
+ window.rendering_scheduled.height = height;
- 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)
+ if (window.rendering_scheduled.resend_dimensions or
+ window.rendering_scheduled.width != window.rendering_sent.box.width or
+ window.rendering_scheduled.height != window.rendering_sent.box.height)
{
- server.wm.dirtyPending();
+ server.wm.dirtyRendering();
}
}
pub fn setDecorationHint(window: *Window, hint: river.WindowV1.DecorationHint) void {
- window.wm_pending.decoration_hint = hint;
- if (hint != window.wm_sent.decoration_hint) {
- server.wm.dirtyPending();
+ window.windowing_scheduled.decoration_hint = hint;
+ if (hint != window.windowing_sent.decoration_hint) {
+ server.wm.dirtyWindowing();
}
}
pub fn setFullscreenRequested(window: *Window, fullscreen_requested: bool) void {
if (fullscreen_requested) {
- window.wm_pending.fullscreen_requested = .fullscreen;
+ window.windowing_scheduled.fullscreen_requested = .fullscreen;
} else {
- window.wm_pending.fullscreen_requested = .exit;
+ window.windowing_scheduled.fullscreen_requested = .exit;
}
- server.wm.dirtyPending();
+ server.wm.dirtyWindowing();
}
-/// Send dirty pending state as part of an in progress update sequence.
-pub fn sendDirty(window: *Window) void {
- switch (window.wm_pending.state) {
+/// Send dirty windowing state as part of a windowing update sequence.
+pub fn updateWindowingStart(window: *Window) void {
+ switch (window.windowing_scheduled.state) {
.init => {},
.closing => {
- window.wm_pending.state = .init;
window.initialized = false;
- window.uncommitted = .{};
- window.committed = .{};
+ window.windowing_scheduled.state = .init;
+ window.windowing_sent = .{};
+ window.windowing_requested = .{};
+ window.rendering_sent = .{};
+ window.rendering_requested = .{};
- window.node.link_uncommitted.remove();
- window.node.link_uncommitted.init();
- window.node.link_committed.remove();
- window.node.link_committed.init();
- window.node.link_inflight.remove();
- window.node.link_inflight.init();
+ window.node.link.remove();
+ window.node.link.init();
window.makeInert();
},
@@ -382,37 +370,16 @@ pub fn sendDirty(window: *Window) void {
window_v1.setHandler(*Window, handleRequest, handleDestroy, window);
wm_v1.sendWindow(window_v1);
- server.wm.uncommitted.render_list.append(&window.node);
+ server.wm.rendering_requested.list.append(&window.node);
break :blk window_v1;
};
errdefer comptime unreachable;
- const pending = &window.wm_pending;
- const sent = &window.wm_sent;
+ const pending = &window.windowing_scheduled;
+ const sent = &window.windowing_sent;
// XXX send all dirty pending state
- if (new or sent.position == null or
- pending.box.x != sent.position.?.x or pending.box.y != sent.position.?.y)
- {
- if (window.node.object) |node_v1| {
- node_v1.sendPosition(pending.box.x, pending.box.y);
- sent.position = .{
- .x = pending.box.x,
- .y = pending.box.y,
- };
- }
- }
- if (!pending.box.empty() and (new or sent.dimensions == null or
- pending.box.width != sent.dimensions.?.width or
- pending.box.height != sent.dimensions.?.height))
- {
- window_v1.sendDimensions(window.wm_pending.box.width, window.wm_pending.box.height);
- sent.dimensions = .{
- .width = pending.box.width,
- .height = pending.box.height,
- };
- }
if (new or !meta.eql(pending.dimensions_hint, sent.dimensions_hint)) {
window_v1.sendDimensionsHint(
pending.dimensions_hint.min_width,
@@ -423,7 +390,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.wm_pending.decoration_hint);
+ window_v1.sendDecorationHint(window.windowing_scheduled.decoration_hint);
sent.decoration_hint = pending.decoration_hint;
}
switch (pending.fullscreen_requested) {
@@ -475,13 +442,17 @@ fn handleRequest(
window: *Window,
) void {
assert(window.object == window_v1);
- const uncommitted = &window.uncommitted;
+ const windowing_requested = &window.windowing_requested;
+ const rendering_requested = &window.rendering_requested;
switch (request) {
.destroy => {
// XXX send protocol error
window_v1.destroy();
},
- .close => uncommitted.close = true,
+ .close => {
+ if (!server.wm.ensureWindowing()) return;
+ windowing_requested.close = true;
+ },
.get_node => |args| {
if (window.node.object != null) {
window_v1.postError(.node_exists, "window already has a node object");
@@ -490,23 +461,37 @@ fn handleRequest(
window.node.createObject(window_v1.getClient(), window_v1.getVersion(), args.id);
},
.propose_dimensions => |args| {
+ if (!server.wm.ensureWindowing()) return;
if (args.width < 0 or args.height < 0) {
// XXX send protocol error
}
- uncommitted.dimensions = .{
+ windowing_requested.dimensions = .{
.width = @intCast(args.width),
.height = @intCast(args.height),
};
},
- .hide => uncommitted.hidden = true,
- .show => uncommitted.hidden = false,
- .use_ssd => uncommitted.ssd = true,
- .use_csd => uncommitted.ssd = false,
+ .hide => {
+ if (!server.wm.ensureRendering()) return;
+ rendering_requested.hidden = true;
+ },
+ .show => {
+ if (!server.wm.ensureRendering()) return;
+ rendering_requested.hidden = false;
+ },
+ .use_ssd => {
+ if (!server.wm.ensureWindowing()) return;
+ windowing_requested.ssd = true;
+ },
+ .use_csd => {
+ if (!server.wm.ensureWindowing()) return;
+ windowing_requested.ssd = false;
+ },
.set_borders => |args| {
+ if (!server.wm.ensureRendering()) return;
if (args.width < 0) {
// XXX send protocol error
}
- uncommitted.border = .{
+ rendering_requested.border = .{
.edges = args.edges,
.width = @intCast(args.width),
.r = args.r,
@@ -515,97 +500,83 @@ fn handleRequest(
.a = args.a,
};
},
- .set_tiled => |args| uncommitted.tiled = args.edges,
+ .set_tiled => |args| {
+ if (!server.wm.ensureWindowing()) return;
+ windowing_requested.tiled = args.edges;
+ },
.get_decoration_surface => {}, // XXX support decoration surfaces
- .set_capabilities => |args| uncommitted.capabilities = args.caps,
- .inform_maximized => uncommitted.maximized = true,
- .inform_unmaximized => uncommitted.maximized = false,
- .fullscreen => uncommitted.fullscreen = true,
- .exit_fullscreen => uncommitted.fullscreen = false,
- }
-}
-
-pub fn commitWmState(window: *Window) void {
- if (!window.initialized and window.uncommitted.dimensions != null) {
- window.initialized = true;
+ .set_capabilities => |args| {
+ if (!server.wm.ensureWindowing()) return;
+ windowing_requested.capabilities = args.caps;
+ },
+ .inform_maximized => {
+ if (!server.wm.ensureWindowing()) return;
+ windowing_requested.maximized = true;
+ },
+ .inform_unmaximized => {
+ if (!server.wm.ensureWindowing()) return;
+ windowing_requested.maximized = false;
+ },
+ .fullscreen => {
+ if (!server.wm.ensureWindowing()) return;
+ windowing_requested.fullscreen = true;
+ },
+ .exit_fullscreen => {
+ if (!server.wm.ensureWindowing()) return;
+ windowing_requested.fullscreen = false;
+ },
}
-
- window.committed = .{
- .position = window.uncommitted.position orelse window.committed.position,
- .dimensions = window.uncommitted.dimensions orelse window.committed.dimensions,
- .hidden = window.uncommitted.hidden,
- .ssd = window.uncommitted.ssd,
- .border = window.uncommitted.border,
- .tiled = window.uncommitted.tiled,
- .capabilities = window.uncommitted.capabilities,
- .maximized = window.uncommitted.maximized,
- .fullscreen = window.uncommitted.fullscreen,
- .close = window.uncommitted.close,
- .op = window.uncommitted.op,
- };
- window.uncommitted.position = null;
- window.uncommitted.dimensions = null;
- window.uncommitted.op = .none;
}
-/// Applies committed state from the window manager client to the inflight state.
+/// Applies windowing state from the window manager client and sends a configure
+/// to the window if necessary.
/// Returns true if the configure should be waited for by the transaction system.
-pub fn configure(window: *Window) bool {
- if (!window.initialized) return false;
+pub fn updateWindowingFinish(window: *Window) bool {
+ const windowing_requested = &window.windowing_requested;
+
+ if (!window.initialized) {
+ if (windowing_requested.dimensions != null) {
+ window.initialized = true;
+ } else {
+ return false;
+ }
+ }
assert(!window.destroying);
- if (window.committed.close) {
+ window.configure_scheduled.ssd = windowing_requested.ssd;
+ window.configure_scheduled.tiled = windowing_requested.tiled;
+ window.configure_scheduled.capabilities = windowing_requested.capabilities;
+ window.configure_scheduled.maximized = windowing_requested.maximized;
+ window.configure_scheduled.fullscreen = windowing_requested.fullscreen;
+
+ if (windowing_requested.close) {
window.close();
}
- const activated = blk: {
- var it = server.wm.sent.seats.iterator(.forward);
+ {
+ window.configure_scheduled.activated = false;
+ var it = server.wm.windowing_sent.seats.iterator(.forward);
while (it.next()) |seat| {
- if (seat.committed.focus == .window and seat.committed.focus.window == window) {
- break :blk true;
+ if (seat.windowing_requested.focus == .window and
+ seat.windowing_requested.focus.window == window)
+ {
+ window.configure_scheduled.activated = true;
+ break;
}
}
- break :blk false;
- };
-
- const committed = &window.committed;
-
- window.pending = .{
- .width = window.pending.width,
- .height = window.pending.height,
- .hidden = committed.hidden,
- .activated = activated,
- .ssd = committed.ssd,
- .border = committed.border,
- .tiled = committed.tiled,
- .capabilities = committed.capabilities,
- .maximized = committed.maximized,
- .fullscreen = committed.fullscreen,
- .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) |position| {
- if (window.pending.op == .none) {
- window.wm_pending.box.x = position.x;
- window.wm_pending.box.y = position.y;
+ if (windowing_requested.dimensions) |dimensions| {
+ if (window.op == .none) {
+ window.configure_scheduled.width = dimensions.width;
+ window.configure_scheduled.height = dimensions.height;
}
-
- window.wm_sent.position = null;
- committed.position = null;
+ windowing_requested.dimensions = null;
+ window.rendering_scheduled.resend_dimensions = true;
}
- 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;
+ windowing_requested.op = .none;
const track_configure = switch (window.impl) {
.toplevel => |*toplevel| toplevel.configure(),
@@ -621,7 +592,7 @@ pub fn configure(window: *Window) bool {
return track_configure;
}
-pub fn commitTransaction(window: *Window) void {
+pub fn updateRenderingStart(window: *Window) void {
switch (window.impl) {
.toplevel => |*toplevel| {
switch (toplevel.configure_state) {
@@ -656,25 +627,85 @@ pub fn commitTransaction(window: *Window) void {
},
.timed_out, .timed_out_acked => unreachable,
}
- window.setDimensions(toplevel.geometry.width, toplevel.geometry.height);
+ window.rendering_scheduled.width = @intCast(toplevel.geometry.width);
+ window.rendering_scheduled.height = @intCast(toplevel.geometry.height);
},
.xwayland => |xwindow| {
- window.setDimensions(xwindow.xsurface.width, xwindow.xsurface.height);
+ window.rendering_scheduled.width = xwindow.xsurface.width;
+ window.rendering_scheduled.height = xwindow.xsurface.height;
},
.none => {},
}
- window.updateSceneState();
+ const sent = &window.rendering_sent;
+ var scheduled_box: wlr.Box = .{
+ .x = sent.box.x,
+ .y = sent.box.y,
+ .width = window.rendering_scheduled.width,
+ .height = window.rendering_scheduled.height,
+ };
+
+ switch (window.op) {
+ .none => {},
+ .move => |data| {
+ const seat_op = &data.seat.op.?;
+ const dx = seat_op.x - seat_op.start_x;
+ const dy = seat_op.y - seat_op.start_y;
+ scheduled_box.x = data.start_x + dx;
+ scheduled_box.y = data.start_y + dy;
+ log.debug("set x/y for move to {} {}", .{ scheduled_box.x, scheduled_box.y });
+ },
+ .resize => |data| {
+ assert(data.seat.op != null);
+ if (data.edges.left) {
+ scheduled_box.x = data.start_box.x + data.start_box.width - scheduled_box.width;
+ } else if (data.edges.right) {
+ scheduled_box.x = data.start_box.x;
+ }
+ if (data.edges.top) {
+ scheduled_box.y = data.start_box.y + data.start_box.height - scheduled_box.height;
+ } else if (data.edges.bottom) {
+ scheduled_box.y = data.start_box.y;
+ }
+ },
+ }
+
+ if (scheduled_box.x != sent.box.x or scheduled_box.y != sent.box.y) {
+ if (window.node.object) |node_v1| {
+ node_v1.sendPosition(scheduled_box.x, scheduled_box.y);
+ }
+ }
+ if (window.rendering_scheduled.resend_dimensions or
+ scheduled_box.width != sent.box.width or scheduled_box.height != sent.box.height)
+ {
+ if (window.object) |window_v1| {
+ window_v1.sendDimensions(scheduled_box.width, scheduled_box.height);
+ window.rendering_scheduled.resend_dimensions = false;
+ }
+ }
+ sent.box = scheduled_box;
}
-pub fn updateSceneState(window: *Window) void {
- const box = &window.wm_pending.box;
+pub fn updateRenderingFinish(window: *Window) void {
+ window.tree.node.setEnabled(!window.rendering_requested.hidden);
+ window.popup_tree.node.setEnabled(!window.rendering_requested.hidden);
+
+ const box = &window.rendering_sent.box;
+
+ if (window.rendering_requested.position) |position| {
+ if (window.op == .none) {
+ box.x = position.x;
+ box.y = position.y;
+ }
+ window.rendering_requested.position = null;
+ }
+
window.tree.node.setPosition(box.x, box.y);
window.popup_tree.node.setPosition(box.x, box.y);
// f32 cannot represent all u32 values exactly, therefore we must initially use f64
// (which can) and then cast to f32, potentially losing precision.
- const border = &window.sent.border;
+ const border = &window.rendering_requested.border;
const color: [4]f32 = .{
@floatCast(@as(f64, @floatFromInt(border.r)) / math.maxInt(u32)),
@floatCast(@as(f64, @floatFromInt(border.g)) / math.maxInt(u32)),
@@ -805,17 +836,17 @@ pub fn unmap(window: *Window) void {
assert(window.mapped and !window.destroying);
window.mapped = false;
- assert(window.wm_pending.state != .closing);
- window.wm_pending.state = .closing;
- server.wm.dirtyPending();
+ assert(window.windowing_scheduled.state != .closing);
+ window.windowing_scheduled.state = .closing;
+ server.wm.dirtyWindowing();
}
pub fn notifyTitle(window: *Window) void {
- window.wm_pending.dirty_title = true;
- server.wm.dirtyPending();
+ window.windowing_scheduled.dirty_title = true;
+ server.wm.dirtyWindowing();
}
pub fn notifyAppId(window: *Window) void {
- window.wm_pending.dirty_app_id = true;
- server.wm.dirtyPending();
+ window.windowing_scheduled.dirty_app_id = true;
+ server.wm.dirtyWindowing();
}
diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index a6f83ac..3f4c064 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -42,52 +42,41 @@ object: ?*river.WindowManagerV1 = null,
state: union(enum) {
idle,
- /// An update event was sent to the window manager but has not yet been acked.
- /// Value is the update serial
- update_sent: u32,
- /// An update event was sent to the window manager and has been acked but not yet committed.
- update_acked,
+ /// Waiting on the window manager client to finish the windowing phase of the update sequence.
+ update_windowing,
/// The number of configures sent that have not yet been acked
inflight_configures: u32,
+ /// Waiting on the window manager client to finish the rendering phase of the update sequence.
+ update_rendering,
} = .idle,
windows: wl.list.Head(Window, .link),
-/// Pending state to be sent to the wm in the next update sequence.
-pending: struct {
- /// Pending state has been modified since the last update event sent to the wm.
+/// Windowing state to be sent to the wm in the next windowing update sequence.
+windowing_scheduled: struct {
+ /// Windowing state has been modified since the last windowing update sequence.
dirty: bool = false,
output_config: ?*wlr.OutputConfigurationV1 = null,
} = .{},
/// State sent to the wm in the latest update sequence.
-sent: struct {
+windowing_sent: struct {
outputs: wl.list.Head(Output, .link_sent),
output_config: ?*wlr.OutputConfigurationV1 = null,
seats: wl.list.Head(Seat, .link_sent),
},
-/// State sent by the wm but not yet committed with a commit request.
-uncommitted: struct {
- /// The list is in rendering order, the last node in the list is rendered on top.
- render_list: wl.list.Head(WmNode, .link_uncommitted),
-},
-
-/// State sent by the wm and committed with a commit request.
-committed: struct {
- // The wm has committed state since state was last sent to windows.
+/// Rendering state to be sent to the wm in the next rendering update sequence.
+rendering_scheduled: struct {
+ /// Rendering state has been modified since the last rendering update sequence.
dirty: bool = false,
- /// The list is in rendering order, the last node in the list is rendered on top.
- render_list: wl.list.Head(WmNode, .link_committed),
-},
+} = .{},
-/// State committed by the wm that has been sent to windows as part of the
-/// current transaction.
-inflight: struct {
- /// The list is in rendering order, the last node in the list is rendered on top.
- render_list: wl.list.Head(WmNode, .link_inflight),
+/// The list is in rendering order, the last node in the list is rendered on top.
+rendering_requested: struct {
+ list: wl.list.Head(WmNode, .link),
},
dirty_idle: ?*wl.EventSource = null,
@@ -102,27 +91,19 @@ pub fn init(wm: *WindowManager) !void {
wm.* = .{
.global = try wl.Global.create(server.wl_server, river.WindowManagerV1, 1, *WindowManager, wm, bind),
.windows = undefined,
- .sent = .{
+ .windowing_sent = .{
.outputs = undefined,
.seats = undefined,
},
- .uncommitted = .{
- .render_list = undefined,
- },
- .committed = .{
- .render_list = undefined,
- },
- .inflight = .{
- .render_list = undefined,
+ .rendering_requested = .{
+ .list = undefined,
},
.timeout = timeout,
};
wm.windows.init();
- wm.sent.outputs.init();
- wm.sent.seats.init();
- wm.uncommitted.render_list.init();
- wm.committed.render_list.init();
- wm.inflight.render_list.init();
+ wm.windowing_sent.outputs.init();
+ wm.windowing_sent.seats.init();
+ wm.rendering_requested.list.init();
server.wl_server.addDestroyListener(&wm.server_destroy);
}
@@ -149,7 +130,7 @@ fn bind(client: *wl.Client, wm: *WindowManager, version: u32, id: u32) void {
wm.object = object;
object.setHandler(*WindowManager, handleRequest, handleDestroy, wm);
- wm.dirtyPending();
+ wm.dirtyWindowing();
}
fn handleRequestInert(
@@ -180,43 +161,25 @@ fn handleRequest(
// XXX send protocol error
wm_v1.destroy();
},
- .ack_update => |args| {
- switch (wm.state) {
- .update_sent => |serial| {
- if (args.serial == serial) {
- wm.state = .update_acked;
- }
- },
- .idle, .update_acked, .inflight_configures => {},
+ .update_windowing_finish => {
+ if (wm.state != .update_windowing) {
+ wm_v1.postError(.update_sequence_order,
+ \\update_windowing_finish request does not match update_windowing_start
+ );
+ return;
}
+ wm.updateWindowingFinish();
},
- .commit => {
- {
- var it = wm.uncommitted.render_list.iterator(.forward);
- while (it.next()) |node| {
- node.link_committed.remove();
- wm.committed.render_list.append(node);
- switch (node.get()) {
- .window => |window| window.commitWmState(),
- .shell_surface => |shell_surface| shell_surface.commitWmState(),
- }
- }
- }
-
- {
- var it = wm.sent.seats.iterator(.forward);
- while (it.next()) |seat| seat.commitWmState();
- }
-
- wm.committed.dirty = true;
- switch (wm.state) {
- .idle, .update_acked => {
- wm.cancelTimeoutTimer();
- wm.sendConfigures();
- },
- .update_sent, .inflight_configures => {},
+ .update_rendering_finish => {
+ if (wm.state != .update_rendering) {
+ wm_v1.postError(.update_sequence_order,
+ \\update_rendering_finish request does not match update_rendering_start
+ );
+ return;
}
+ wm.updateRenderingFinish();
},
+ .update_mark_dirty => wm.dirtyWindowing(),
.get_shell_surface => |args| {
const surface = wlr.Surface.fromWlSurface(args.surface);
ShellSurface.create(
@@ -233,93 +196,125 @@ fn handleRequest(
}
}
-pub fn dirtyPending(wm: *WindowManager) void {
- wm.pending.dirty = true;
+pub fn ensureWindowing(wm: *WindowManager) bool {
+ switch (wm.state) {
+ .update_windowing => return true,
+ .idle, .inflight_configures, .update_rendering => {
+ if (wm.object) |wm_v1| {
+ wm_v1.postError(.update_sequence_order, "invalid modification of windowing state");
+ }
+ return false;
+ },
+ }
+}
+
+pub fn ensureRendering(wm: *WindowManager) bool {
+ switch (wm.state) {
+ .update_windowing, .inflight_configures, .update_rendering => return true,
+ .idle => {
+ if (wm.object) |wm_v1| {
+ wm_v1.postError(.update_sequence_order, "invalid modification of rendering state");
+ }
+ return false;
+ },
+ }
+}
+
+pub fn dirtyWindowing(wm: *WindowManager) void {
+ wm.windowing_scheduled.dirty = true;
if (wm.dirty_idle == null) {
const event_loop = server.wl_server.getEventLoop();
- wm.dirty_idle = event_loop.addIdle(*WindowManager, handleDirtyPending, wm) catch {
+ wm.dirty_idle = event_loop.addIdle(*WindowManager, dirtyIdle, wm) catch {
log.err("out of memory", .{});
return;
};
}
}
-fn handleDirtyPending(wm: *WindowManager) void {
- assert(wm.pending.dirty);
+pub fn dirtyRendering(wm: *WindowManager) void {
+ wm.rendering_scheduled.dirty = true;
+
+ if (wm.dirty_idle == null) {
+ const event_loop = server.wl_server.getEventLoop();
+ wm.dirty_idle = event_loop.addIdle(*WindowManager, dirtyIdle, wm) catch {
+ log.err("out of memory", .{});
+ return;
+ };
+ }
+}
+
+fn dirtyIdle(wm: *WindowManager) void {
+ assert(wm.windowing_scheduled.dirty or wm.rendering_scheduled.dirty);
wm.dirty_idle = null;
switch (wm.state) {
.idle => {
- assert(!wm.committed.dirty);
- wm.sendUpdate();
+ if (wm.rendering_scheduled.dirty) {
+ wm.updateRenderingStart();
+ } else {
+ wm.updateWindowingStart();
+ }
},
- .update_sent, .update_acked, .inflight_configures => {},
+ .update_windowing, .inflight_configures, .update_rendering => {},
}
}
-fn sendUpdate(wm: *WindowManager) void {
+fn updateWindowingStart(wm: *WindowManager) void {
assert(wm.state == .idle);
- assert(wm.pending.dirty);
+ assert(wm.windowing_scheduled.dirty);
- log.debug("sending update to window manager", .{});
+ log.debug("update windowing start", .{});
server.om.autoLayout();
{
var it = server.om.outputs.safeIterator(.forward);
- while (it.next()) |output| output.sendDirty();
+ while (it.next()) |output| output.updateWindowingStart();
}
- assert(wm.sent.output_config == null);
- wm.sent.output_config = wm.pending.output_config;
- wm.pending.output_config = null;
+ assert(wm.windowing_sent.output_config == null);
+ wm.windowing_sent.output_config = wm.windowing_scheduled.output_config;
+ wm.windowing_scheduled.output_config = null;
{
var it = wm.windows.safeIterator(.forward);
- while (it.next()) |window| window.sendDirty();
+ while (it.next()) |window| window.updateWindowingStart();
}
{
var it = server.input_manager.seats.safeIterator(.forward);
- while (it.next()) |seat| seat.sendDirty();
+ while (it.next()) |seat| seat.updateWindowingStart();
}
- wm.pending.dirty = false;
+ wm.windowing_scheduled.dirty = false;
+ wm.state = .update_windowing;
if (wm.object) |wm_v1| {
- const serial = server.wl_server.nextSerial();
- wm_v1.sendUpdate(serial);
- wm.state = .{ .update_sent = serial };
-
- wm.startTimeoutTimer();
+ // TODO kill the WM on a very long timeout?
+ wm_v1.sendUpdateWindowingStart();
} else {
- wm.sendConfigures();
+ wm.updateWindowingFinish();
}
}
-pub fn sendConfigures(wm: *WindowManager) void {
- switch (wm.state) {
- .idle, .update_acked => {},
- .update_sent, .inflight_configures => unreachable,
- }
+pub fn updateWindowingFinish(wm: *WindowManager) void {
+ assert(wm.state == .update_windowing);
- wm.committed.dirty = false;
+ log.debug("update windowing finish", .{});
{
- // Order is important here, Seat.applyCommitted() must be called
- // before configures are sent.
- var it = wm.sent.seats.iterator(.forward);
- while (it.next()) |seat| seat.applyCommitted();
+ // Order is important here, Seat.updateWindowingFinish() must be called
+ // before Window.updateWindowingFinish() are sent.
+ var it = wm.windowing_sent.seats.iterator(.forward);
+ while (it.next()) |seat| seat.updateWindowingFinish();
}
wm.state = .{ .inflight_configures = 0 };
{
- var it = wm.committed.render_list.iterator(.forward);
+ var it = wm.rendering_requested.list.iterator(.forward);
while (it.next()) |node| {
- node.link_inflight.remove();
- wm.inflight.render_list.append(node);
switch (node.get()) {
.window => |window| {
- if (window.configure()) {
+ if (window.updateWindowingFinish()) {
wm.state.inflight_configures += 1;
}
},
@@ -328,12 +323,12 @@ pub fn sendConfigures(wm: *WindowManager) void {
}
}
- log.debug("started transaction with {} configure(s)", .{wm.state.inflight_configures});
+ log.debug("sent {} tracked configure(s)", .{wm.state.inflight_configures});
if (wm.state.inflight_configures > 0) {
wm.startTimeoutTimer();
} else {
- wm.commitTransaction();
+ wm.updateRenderingStart();
}
}
@@ -351,10 +346,10 @@ fn cancelTimeoutTimer(wm: *WindowManager) void {
fn handleTimeout(wm: *WindowManager) c_int {
log.err("timeout occurred, some imperfect frames may be shown", .{});
- assert(wm.state != .idle);
+ assert(wm.state.inflight_configures > 0);
+ wm.state.inflight_configures = 0;
- wm.state = .{ .inflight_configures = 0 };
- wm.commitTransaction();
+ wm.updateRenderingStart();
return 0;
}
@@ -363,19 +358,43 @@ pub fn notifyConfigured(wm: *WindowManager) void {
wm.state.inflight_configures -= 1;
if (wm.state.inflight_configures == 0) {
wm.cancelTimeoutTimer();
- wm.commitTransaction();
+ wm.updateRenderingStart();
+ }
+}
+
+fn updateRenderingStart(wm: *WindowManager) void {
+ assert(wm.state == .idle or wm.state.inflight_configures == 0);
+
+ log.debug("update rendering start", .{});
+
+ {
+ var it = wm.rendering_requested.list.iterator(.forward);
+ while (it.next()) |node| {
+ switch (node.get()) {
+ .window => |window| window.updateRenderingStart(),
+ .shell_surface => {},
+ }
+ }
+ }
+
+ wm.state = .update_rendering;
+ wm.rendering_scheduled.dirty = false;
+
+ if (wm.object) |wm_v1| {
+ // TODO kill the WM on a very long timeout?
+ wm_v1.sendUpdateRenderingStart();
+ } else {
+ wm.updateRenderingFinish();
}
}
-/// 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.state.inflight_configures == 0);
+/// Finish the update sequence and drop stashed buffers. This means that
+/// the next frame drawn will be the post-transaction state.
+fn updateRenderingFinish(wm: *WindowManager) void {
+ assert(wm.state == .update_rendering);
wm.state = .idle;
- log.debug("commiting transaction", .{});
+ log.debug("update rendering finish", .{});
{
var it = wm.windows.safeIterator(.forward);
@@ -386,20 +405,17 @@ fn commitTransaction(wm: *WindowManager) void {
}
{
- var it = wm.inflight.render_list.iterator(.forward);
+ var it = wm.rendering_requested.list.iterator(.forward);
while (it.next()) |node| {
switch (node.get()) {
.window => |window| {
- window.commitTransaction();
+ window.updateRenderingFinish();
window.tree.node.reparent(server.scene.layers.wm);
window.tree.node.raiseToTop();
-
- window.tree.node.setEnabled(!window.pending.hidden);
- window.popup_tree.node.setEnabled(!window.pending.hidden);
},
.shell_surface => |shell_surface| {
- shell_surface.commitTransaction();
+ shell_surface.updateRenderingFinish();
shell_surface.tree.node.reparent(server.scene.layers.wm);
shell_surface.tree.node.raiseToTop();
@@ -419,10 +435,10 @@ fn commitTransaction(wm: *WindowManager) void {
log.debug("finished committing transaction", .{});
- if (wm.committed.dirty) {
- wm.sendConfigures();
- } else if (wm.pending.dirty) {
- wm.dirtyPending();
+ if (wm.rendering_scheduled.dirty) {
+ wm.dirtyRendering();
+ } else if (wm.windowing_scheduled.dirty) {
+ wm.dirtyWindowing();
} else {
server.input_manager.processEvents();
}
diff --git a/river/WmNode.zig b/river/WmNode.zig
index 7c8de0e..1120f6a 100644
--- a/river/WmNode.zig
+++ b/river/WmNode.zig
@@ -36,31 +36,21 @@ const Tag = @typeInfo(Type).Union.tag_type.?;
tag: Tag,
object: ?*river.NodeV1 = null,
-/// WindowManager.uncommitted.render_list
-link_uncommitted: wl.list.Link,
-/// WindowManager.committed.render_list
-link_committed: wl.list.Link,
-/// WindowManager.inflight.render_list
-link_inflight: wl.list.Link,
+/// WindowManager.rendering_requested.list
+link: wl.list.Link,
pub fn init(node: *WmNode, tag: Tag) void {
node.* = .{
.tag = tag,
- .link_uncommitted = undefined,
- .link_committed = undefined,
- .link_inflight = undefined,
+ .link = undefined,
};
- node.link_uncommitted.init();
- node.link_committed.init();
- node.link_inflight.init();
+ node.link.init();
}
pub fn deinit(node: *WmNode) void {
assert(node.object == null);
- node.link_uncommitted.remove();
- node.link_committed.remove();
- node.link_inflight.remove();
+ node.link.remove();
}
pub fn get(node: *WmNode) Type {
@@ -110,43 +100,52 @@ fn handleRequest(
.destroy => {
node_v1.destroy();
},
- .set_position => |args| switch (node.get()) {
- .window => |window| {
- window.uncommitted.position = .{
- .x = args.x,
- .y = args.y,
- };
- },
- .shell_surface => |shell_surface| {
- shell_surface.uncommitted.x = args.x;
- shell_surface.uncommitted.y = args.y;
- },
+ .set_position => |args| {
+ if (!server.wm.ensureRendering()) return;
+ switch (node.get()) {
+ .window => |window| {
+ window.rendering_requested.position = .{
+ .x = args.x,
+ .y = args.y,
+ };
+ },
+ .shell_surface => |shell_surface| {
+ shell_surface.rendering_requested.x = args.x;
+ shell_surface.rendering_requested.y = args.y;
+ },
+ }
},
.place_top => {
- node.link_uncommitted.remove();
- server.wm.uncommitted.render_list.append(node);
+ if (!server.wm.ensureRendering()) return;
+ node.link.remove();
+ server.wm.rendering_requested.list.append(node);
},
.place_bottom => {
- node.link_uncommitted.remove();
- server.wm.uncommitted.render_list.prepend(node);
+ if (!server.wm.ensureRendering()) return;
+ node.link.remove();
+ server.wm.rendering_requested.list.prepend(node);
},
.place_above => |args| {
+ if (!server.wm.ensureRendering()) return;
+
const other_data = args.other.getUserData() orelse return;
const other: *WmNode = @ptrCast(@alignCast(other_data));
if (other == node) return;
- node.link_uncommitted.remove();
- other.link_uncommitted.insert(&node.link_uncommitted);
+ node.link.remove();
+ other.link.insert(&node.link);
},
.place_below => |args| {
+ if (!server.wm.ensureRendering()) return;
+
const other_data = args.other.getUserData() orelse return;
const other: *WmNode = @ptrCast(@alignCast(other_data));
if (other == node) return;
- node.link_uncommitted.remove();
- other.link_uncommitted.prev.?.insert(&node.link_uncommitted);
+ node.link.remove();
+ other.link.prev.?.insert(&node.link);
},
}
}
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index ab34c3c..411b530 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -127,7 +127,7 @@ pub fn configure(toplevel: *XdgToplevel) bool {
.timed_out, .timed_out_acked, .committed => unreachable,
};
- const pending = &toplevel.window.pending;
+ const scheduled = &toplevel.window.configure_scheduled;
if (!toplevel.needsConfigure()) {
// If no new configure is required, continue to track a timed out configure
@@ -148,43 +148,43 @@ pub fn configure(toplevel: *XdgToplevel) bool {
const wlr_toplevel = toplevel.wlr_toplevel;
- _ = wlr_toplevel.setActivated(pending.activated);
+ _ = wlr_toplevel.setActivated(scheduled.activated);
_ = wlr_toplevel.setTiled(.{
- .top = pending.tiled.top,
- .bottom = pending.tiled.bottom,
- .left = pending.tiled.left,
- .right = pending.tiled.right,
+ .top = scheduled.tiled.top,
+ .bottom = scheduled.tiled.bottom,
+ .left = scheduled.tiled.left,
+ .right = scheduled.tiled.right,
});
_ = wlr_toplevel.setWmCapabilities(.{
- .window_menu = pending.capabilities.window_menu,
- .maximize = pending.capabilities.maximize,
- .fullscreen = pending.capabilities.fullscreen,
- .minimize = pending.capabilities.minimize,
+ .window_menu = scheduled.capabilities.window_menu,
+ .maximize = scheduled.capabilities.maximize,
+ .fullscreen = scheduled.capabilities.fullscreen,
+ .minimize = scheduled.capabilities.minimize,
});
- _ = wlr_toplevel.setMaximized(pending.maximized);
- _ = wlr_toplevel.setFullscreen(pending.fullscreen);
- _ = wlr_toplevel.setResizing(pending.op == .resize);
+ _ = wlr_toplevel.setMaximized(scheduled.maximized);
+ _ = wlr_toplevel.setFullscreen(scheduled.fullscreen);
+ _ = wlr_toplevel.setResizing(scheduled.resizing);
if (toplevel.decoration) |decoration| {
- _ = decoration.wlr_decoration.setMode(if (pending.ssd) .server_side else .client_side);
+ _ = decoration.wlr_decoration.setMode(if (scheduled.ssd) .server_side else .client_side);
}
- const width: u31 = pending.width orelse switch (toplevel.configure_state) {
+ const width: u31 = scheduled.width orelse switch (toplevel.configure_state) {
.idle => @intCast(toplevel.geometry.width),
- .timed_out, .timed_out_acked => toplevel.window.sent.width.?,
+ .timed_out, .timed_out_acked => toplevel.window.configure_sent.width.?,
.inflight, .acked, .committed => unreachable,
};
- const height: u31 = pending.height orelse switch (toplevel.configure_state) {
+ const height: u31 = scheduled.height orelse switch (toplevel.configure_state) {
.idle => @intCast(toplevel.geometry.height),
- .timed_out, .timed_out_acked => toplevel.window.sent.height.?,
+ .timed_out, .timed_out_acked => toplevel.window.configure_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.window.configure_sent = toplevel.window.configure_scheduled;
+ toplevel.window.configure_sent.width = width;
+ toplevel.window.configure_sent.height = height;
+ toplevel.window.configure_scheduled.width = null;
+ toplevel.window.configure_scheduled.height = null;
toplevel.configure_state = .{
.inflight = configure_serial,
@@ -194,18 +194,18 @@ pub fn configure(toplevel: *XdgToplevel) bool {
}
fn needsConfigure(toplevel: *XdgToplevel) bool {
- 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;
+ const scheduled = &toplevel.window.configure_scheduled;
+ const sent = &toplevel.window.configure_sent;
+
+ if (scheduled.width != null) return true;
+ if (scheduled.height != null) return true;
+ if (scheduled.activated != sent.activated) return true;
+ if (scheduled.ssd != sent.ssd) return true;
+ if (!std.meta.eql(scheduled.tiled, sent.tiled)) return true;
+ if (!std.meta.eql(scheduled.capabilities, sent.capabilities)) return true;
+ if (scheduled.maximized != sent.maximized) return true;
+ if (scheduled.fullscreen != sent.fullscreen) return true;
+ if ((scheduled.resizing) != (sent.resizing)) return true;
return false;
}
@@ -298,9 +298,9 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
});
if (toplevel.wlr_toplevel.base.initial_commit) {
- assert(window.wm_pending.state != .ready);
- window.wm_pending.state = .ready;
- server.wm.dirtyPending();
+ assert(window.windowing_scheduled.state != .ready);
+ window.windowing_scheduled.state = .ready;
+ server.wm.dirtyWindowing();
return;
}
@@ -322,13 +322,12 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
.{ old_geometry.width, old_geometry.height, toplevel.geometry.width, toplevel.geometry.height },
);
- window.setDimensions(toplevel.geometry.width, toplevel.geometry.height);
- window.updateSceneState();
+ window.setDimensions(@intCast(toplevel.geometry.width), @intCast(toplevel.geometry.height));
} else if (old_geometry.x != toplevel.geometry.x or
old_geometry.y != toplevel.geometry.y)
{
// We need to update the surface clip box to reflect the geometry change.
- window.updateSceneState();
+ // XXX actually update the clip box
}
},
// If the client has not yet acked our configure, we need to send a
@@ -339,7 +338,8 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
.acked, .timed_out_acked => {
toplevel.wlr_toplevel.base.getGeometry(&toplevel.geometry);
- window.setDimensions(toplevel.geometry.width, toplevel.geometry.height);
+ window.rendering_scheduled.width = @intCast(toplevel.geometry.width);
+ window.rendering_scheduled.height = @intCast(toplevel.geometry.height);
switch (toplevel.configure_state) {
.acked => {
@@ -348,7 +348,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
},
.timed_out_acked => {
toplevel.configure_state = .idle;
- window.updateSceneState();
+ server.wm.dirtyRendering();
},
else => unreachable,
}
@@ -371,7 +371,7 @@ fn handleRequestMove(
// Moving windows with touch or tablet tool is not yet supported.
if (seat.wlr_seat.validatePointerGrabSerial(null, event.serial)) {
- // XXX queue pointer_move_requested, dirtyPending()
+ // XXX queue pointer_move_requested, dirtyWindowing()
}
}
@@ -382,7 +382,7 @@ fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), ev
// Resizing windows with touch or tablet tool is not yet supported.
if (seat.wlr_seat.validatePointerGrabSerial(null, event.serial)) {
- // XXX queue pointer_resize_requested, dirtyPending()
+ // XXX queue pointer_resize_requested, dirtyWindowing()
}
}
diff --git a/river/XkbBinding.zig b/river/XkbBinding.zig
index f9644e0..4df4fa7 100644
--- a/river/XkbBinding.zig
+++ b/river/XkbBinding.zig
@@ -32,28 +32,25 @@ const Seat = @import("Seat.zig");
const log = std.log.scoped(.input);
-const WmState = struct {
- enabled: bool = false,
- // This is set for mappings with layout-pinning
- // If set, the layout with this index is always used to translate the given keycode
- layout: ?u32 = null,
-};
-
seat: *Seat,
object: *river.XkbBindingV1,
keysym: xkb.Keysym,
modifiers: river.SeatV1.Modifiers,
-pending: struct {
+windowing_scheduled: struct {
state_change: enum {
none,
pressed,
released,
} = .none,
} = .{},
-uncommitted: WmState = .{},
-committed: WmState = .{},
+windowing_requested: struct {
+ enabled: bool = false,
+ // This is set for mappings with layout-pinning
+ // If set, the layout with this index is always used to translate the given keycode
+ layout: ?u32 = null,
+} = .{},
/// This bit of state is used to ensure that multiple simultaneous
/// presses across multiple keyboards do not cause multiple press
@@ -125,9 +122,18 @@ fn handleRequest(
assert(binding.object == xkb_binding_v1);
switch (request) {
.destroy => xkb_binding_v1.destroy(),
- .set_layout_override => |args| binding.uncommitted.layout = args.layout,
- .enable => binding.uncommitted.enabled = true,
- .disable => binding.uncommitted.enabled = false,
+ .set_layout_override => |args| {
+ // XXX protocol error?
+ binding.windowing_requested.layout = args.layout;
+ },
+ .enable => {
+ if (!server.wm.ensureWindowing()) return;
+ binding.windowing_requested.enabled = true;
+ },
+ .disable => {
+ if (!server.wm.ensureWindowing()) return;
+ binding.windowing_requested.enabled = false;
+ },
}
}
@@ -135,18 +141,18 @@ pub fn pressed(binding: *XkbBinding) void {
assert(!binding.sent_pressed);
// Input event processing should not continue after a press/release event
// until that event is sent to the window manager in an update and acked.
- assert(binding.pending.state_change == .none);
- binding.pending.state_change = .pressed;
- server.wm.dirtyPending();
+ assert(binding.windowing_scheduled.state_change == .none);
+ binding.windowing_scheduled.state_change = .pressed;
+ server.wm.dirtyWindowing();
}
pub fn released(binding: *XkbBinding) void {
assert(binding.sent_pressed);
// Input event processing should not continue after a press/release event
// until that event is sent to the window manager in an update and acked.
- assert(binding.pending.state_change == .none);
- binding.pending.state_change = .released;
- server.wm.dirtyPending();
+ assert(binding.windowing_scheduled.state_change == .none);
+ binding.windowing_scheduled.state_change = .released;
+ server.wm.dirtyWindowing();
}
/// Compare binding with given keycode, modifiers and keyboard state
@@ -157,14 +163,14 @@ pub fn match(
xkb_state: *xkb.State,
method: enum { no_translate, translate },
) bool {
- if (!binding.committed.enabled) return false;
+ if (!binding.windowing_requested.enabled) return false;
const keymap = xkb_state.getKeymap();
// If the binding has no pinned layout, use the active layout.
// It doesn't matter if the index is out of range, since xkbcommon
// will fall back to the active layout if so.
- const layout = binding.committed.layout orelse xkb_state.keyGetLayout(keycode);
+ const layout = binding.windowing_requested.layout orelse xkb_state.keyGetLayout(keycode);
switch (method) {
.no_translate => {
diff --git a/river/XwaylandOverrideRedirect.zig b/river/XwaylandOverrideRedirect.zig
index 6a6ae1a..70ec3b1 100644
--- a/river/XwaylandOverrideRedirect.zig
+++ b/river/XwaylandOverrideRedirect.zig
@@ -180,7 +180,7 @@ fn handleUnmap(listener: *wl.Listener(void)) void {
}
}
- server.wm.dirtyPending();
+ server.wm.dirtyWindowing();
}
fn handleSetGeometry(listener: *wl.Listener(void)) void {
diff --git a/river/XwaylandWindow.zig b/river/XwaylandWindow.zig
index d52775a..7f46a17 100644
--- a/river/XwaylandWindow.zig
+++ b/river/XwaylandWindow.zig
@@ -99,10 +99,10 @@ pub fn configure(xwindow: *XwaylandWindow) bool {
// Sending a 0 width/height to X11 clients is invalid, so fake it
if (inflight.box.width == 0) {
- inflight.box.width = xwindow.window.pending.box.width;
+ inflight.box.width = xwindow.window.configure_scheduled.box.width;
}
if (inflight.box.height == 0) {
- inflight.box.height = xwindow.window.pending.box.height;
+ inflight.box.height = xwindow.window.configure_scheduled.box.height;
}
if (inflight.hidden != current.hidden) {
@@ -194,14 +194,14 @@ pub fn handleMap(listener: *wl.Listener(void)) void {
};
// XXX this seems like it should be deleted/moved to handleCommit()
- window.pending.box = .{
+ window.configure_scheduled.box = .{
.x = 0,
.y = 0,
.width = xwindow.xsurface.width,
.height = xwindow.xsurface.height,
};
- window.inflight.box = window.pending.box;
- window.current.box = window.pending.box;
+ window.inflight.box = window.configure_scheduled.box;
+ window.current.box = window.configure_scheduled.box;
window.map() catch {
log.err("out of memory", .{});
diff --git a/rivercompat/Window.zig b/rivercompat/Window.zig
index d25f8e2..c360091 100644
--- a/rivercompat/Window.zig
+++ b/rivercompat/Window.zig
@@ -106,6 +106,9 @@ pub fn updateWindowing(window: *Window, wm: *WindowManager) void {
seat.focus(window);
}
}
+
+ window.node_v1.setPosition(20, 20);
+ window.window_v1.proposeDimensions(400, 400);
}
window.pending = .{};
diff --git a/rivercompat/WindowManager.zig b/rivercompat/WindowManager.zig
index 83fac77..d863f93 100644
--- a/rivercompat/WindowManager.zig
+++ b/rivercompat/WindowManager.zig
@@ -73,7 +73,7 @@ fn handleEvent(wm_v1: *river.WindowManagerV1, event: river.WindowManagerV1.Event
},
.update_rendering_start => {
wm.updateRendering();
- wm_v1.updateWindowingFinish();
+ wm_v1.updateRenderingFinish();
},
.session_locked => {},
.session_unlocked => {},
@@ -95,16 +95,9 @@ fn updateWindowing(wm: *WindowManager) void {
}
}
{
- var x: i32 = 0;
- var y: i32 = 0;
var it = wm.windows.iterator(.forward);
while (it.next()) |window| {
window.updateWindowing(wm);
-
- window.node_v1.setPosition(x, y);
- window.window_v1.proposeDimensions(400, 400);
- x += 40;
- y += 40;
}
}
{