Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
WindowManager: start structuring state
river/Cursor.zig | 4 +-
river/ForeignToplevelHandle.zig | 11 ++---
river/Seat.zig | 7 ++-
river/Server.zig | 9 +---
river/Window.zig | 100 ++++++++++++++++++----------------------
river/WindowManager.zig | 58 +++++++++++++++--------
river/XdgDecoration.zig | 11 +++--
river/XdgToplevel.zig | 31 ++++---------
river/XwaylandWindow.zig | 31 +++++++++----
9 files changed, 133 insertions(+), 129 deletions(-)
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 5d66fea..85f49fa 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -851,7 +851,7 @@ fn processMotion(cursor: *Cursor, device: *wlr.InputDevice, time: u32, delta_x:
data.delta_x = dx - @trunc(dx);
data.delta_y = dy - @trunc(dy);
- data.window.pending.move(@intFromFloat(dx), @intFromFloat(dy));
+ // XXX move window
server.wm.applyPending();
},
@@ -864,6 +864,8 @@ fn processMotion(cursor: *Cursor, device: *wlr.InputDevice, time: u32, delta_x:
data.x += @intFromFloat(dx);
data.y += @intFromFloat(dy);
+ if (true) return; // XXX resize window
+
// Modify width/height of the pending box, taking constraints into account
// The x/y coordinates of the window will be adjusted as needed in Window.resizeCommit()
// based on the dimensions actually committed by the client.
diff --git a/river/ForeignToplevelHandle.zig b/river/ForeignToplevelHandle.zig
index 1bd5deb..fb424d3 100644
--- a/river/ForeignToplevelHandle.zig
+++ b/river/ForeignToplevelHandle.zig
@@ -90,14 +90,13 @@ fn handleForeignActivate(
}
fn handleForeignFullscreen(
- listener: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen),
- event: *wlr.ForeignToplevelHandleV1.event.Fullscreen,
+ _: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen),
+ _: *wlr.ForeignToplevelHandleV1.event.Fullscreen,
) void {
- const handle: *ForeignToplevelHandle = @fieldParentPtr("foreign_fullscreen", listener);
- const window: *Window = @fieldParentPtr("foreign_toplevel_handle", handle);
+ //const handle: *ForeignToplevelHandle = @fieldParentPtr("foreign_fullscreen", listener);
+ //const window: *Window = @fieldParentPtr("foreign_toplevel_handle", handle);
- window.pending.fullscreen = event.fullscreen;
- server.wm.applyPending();
+ // XXX Can I just delete this protocol?
}
fn handleForeignClose(
diff --git a/river/Seat.zig b/river/Seat.zig
index 921a86d..8426990 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -172,7 +172,7 @@ pub fn setFocusRaw(seat: *Seat, new_focus: FocusTarget) void {
// First clear the current focus
switch (seat.focused) {
.window => |window| {
- window.pending.focus -= 1;
+ //window.pending.focus -= 1; XXX update focus to send activated state
window.destroyPopups();
},
.layer => |layer_surface| {
@@ -183,10 +183,9 @@ pub fn setFocusRaw(seat: *Seat, new_focus: FocusTarget) void {
// Set the new focus
switch (new_focus) {
- .window => |target_window| {
+ .window => |_| {
assert(server.lock_manager.state != .locked);
- target_window.pending.focus += 1;
- target_window.pending.urgent = false;
+ //target_window.pending.focus += 1; XXX update focus to send activated state
},
.layer => assert(server.lock_manager.state != .locked),
.lock_surface => assert(server.lock_manager.state != .unlocked),
diff --git a/river/Server.zig b/river/Server.zig
index 7e15659..620c19c 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -406,17 +406,12 @@ fn handleNewXwaylandSurface(_: *wl.Listener(*wlr.XwaylandSurface), xwayland_surf
}
fn handleRequestActivate(
- listener: *wl.Listener(*wlr.XdgActivationV1.event.RequestActivate),
+ _: *wl.Listener(*wlr.XdgActivationV1.event.RequestActivate),
event: *wlr.XdgActivationV1.event.RequestActivate,
) void {
- const server: *Server = @fieldParentPtr("request_activate", listener);
-
const node_data = SceneNodeData.fromSurface(event.surface) orelse return;
switch (node_data.data) {
- .window => |window| if (window.pending.focus == 0) {
- window.pending.urgent = true;
- server.wm.applyPending();
- },
+ .window => |_| {}, // XXX
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 3a327b7..0f67d8f 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -23,6 +23,7 @@ const math = std.math;
const posix = std.posix;
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
+const river = @import("wayland").server.river;
const server = &@import("main.zig").server;
const util = @import("util.zig");
@@ -65,48 +66,20 @@ pub const State = struct {
urgent: bool = false,
ssd: bool = false,
resizing: bool = false,
-
- /// Modify the x/y of the given state by delta_x/delta_y, clamping to the
- /// bounds of the output.
- pub fn move(state: *State, delta_x: i32, delta_y: i32) void {
- const border_width = if (state.ssd) server.config.border_width else 0;
-
- const output_width = math.maxInt(i32);
- const output_height = math.maxInt(i32);
-
- const max_x = output_width - state.box.width - border_width;
- state.box.x += delta_x;
- state.box.x = @max(state.box.x, border_width);
- state.box.x = @min(state.box.x, max_x);
- state.box.x = @max(state.box.x, 0);
-
- const max_y = output_height - state.box.height - border_width;
- state.box.y += delta_y;
- state.box.y = @max(state.box.y, border_width);
- state.box.y = @min(state.box.y, max_y);
- state.box.y = @max(state.box.y, 0);
- }
-
- pub fn clampToOutput(state: *State) void {
- const output = state.output orelse return;
-
- var output_width: i32 = undefined;
- var output_height: i32 = undefined;
- output.wlr_output.effectiveResolution(&output_width, &output_height);
-
- const border_width = if (state.ssd) server.config.border_width else 0;
- state.box.width = @min(state.box.width, output_width - (2 * border_width));
- state.box.height = @min(state.box.height, output_height - (2 * border_width));
-
- state.move(0, 0);
- }
};
+/// The window management protocol object for this window
+/// If the window is unmapped the "closed" event is sent to the current object.
+/// If the window is remapped a new object is created and sent to the window manager.
+object: ?*river.WindowV1,
+
/// The implementation of this window
impl: Impl,
-/// Link for Root.windows
+/// Link for WindowManager.windows
link: wl.list.Link,
+/// Link for WindowManager.pending_to_wm.new_windows
+link_new: wl.list.Link,
tree: *wlr.SceneTree,
surface_tree: *wlr.SceneTree,
@@ -125,20 +98,22 @@ inflight_transaction: bool = false,
/// transaction completes. See Window.destroy()
destroying: bool = false,
-/// The state of the window that is directly acted upon/modified through user input.
-///
-/// Pending state will be copied to the inflight state and communicated to clients
-/// 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 WindowManager.applyPending() must be called
-/// before yielding back to the event loop.
-pending: State = .{},
-pending_render_list_link: wl.list.Link,
-
-/// The state most recently sent to the layout generator and clients.
-/// This state is immutable until all clients have replied and the transaction
-/// is completed, at which point this inflight state is copied to current.
+pending: struct {
+ box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
+
+ decoration_hint: river.WindowV1.DecorationHint = .only_supports_csd,
+ /// TODO output hint
+ fullscreen_requested: bool = false,
+
+ resizing: bool = false,
+} = .{},
+
+uncommitted: struct {} = .{},
+uncommitted_render_list_link: wl.list.Link,
+
+committed: struct {} = .{},
+committed_render_list_link: wl.list.Link,
+
inflight: State = .{},
inflight_render_list_link: wl.list.Link,
@@ -153,6 +128,13 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
const window = try util.gpa.create(Window);
errdefer util.gpa.destroy(window);
+ const object = blk: {
+ const wm_v1 = server.wm.object orelse break :blk null;
+ break :blk river.WindowV1.create(wm_v1.getClient(), wm_v1.getVersion(), 0) catch
+ return error.OutOfMemory;
+ };
+ errdefer if (object) |o| o.destroy();
+
const tree = try server.root.hidden_tree.createSceneTree();
errdefer tree.node.destroy();
@@ -160,8 +142,10 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
errdefer popup_tree.node.destroy();
window.* = .{
+ .object = object,
.impl = impl,
.link = undefined,
+ .link_new = undefined,
.tree = tree,
.surface_tree = try tree.createSceneTree(),
.saved_surface_tree = try tree.createSceneTree(),
@@ -173,13 +157,17 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
},
.popup_tree = popup_tree,
- .pending_render_list_link = undefined,
+ .uncommitted_render_list_link = undefined,
+ .committed_render_list_link = undefined,
.inflight_render_list_link = undefined,
};
server.wm.windows.prepend(window);
- server.wm.pending.render_list.prepend(window);
- server.wm.inflight.render_list.prepend(window);
+ server.wm.pending.new_windows.prepend(window);
+
+ window.uncommitted_render_list_link.init();
+ window.committed_render_list_link.init();
+ window.inflight_render_list_link.init();
window.tree.node.setEnabled(false);
window.popup_tree.node.setEnabled(false);
@@ -208,9 +196,13 @@ pub fn destroy(window: *Window, when: enum { lazy, assert }) void {
window.popup_tree.node.destroy();
window.link.remove();
- window.pending_render_list_link.remove();
+ window.link_new.remove();
+ window.uncommitted_render_list_link.remove();
+ window.committed_render_list_link.remove();
window.inflight_render_list_link.remove();
+ // XXX destroy object?
+
util.gpa.destroy(window);
} else {
switch (when) {
diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index bc9be75..91f9e72 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -31,18 +31,28 @@ const log = std.log.scoped(.wm);
global: *wl.Global,
server_destroy: wl.Listener(*wl.Server) = wl.Listener(*wl.Server).init(handleServerDestroy),
-/// The active window manager, if any.
-wm_v1: ?*river.WindowManagerV1 = null,
+/// The protocol object of the active window manager, if any.
+object: ?*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 state from windows to be sent to the wm in the next update sequence.
pending: struct {
- render_list: wl.list.Head(Window, .pending_render_list_link),
+ new_windows: wl.list.Head(Window, .link_new),
},
-/// Committed state from the window manager that is part of the current transaction.
+/// State sent by the wm but not yet committed with a commit request.
+uncommitted: struct {
+ render_list: wl.list.Head(Window, .uncommitted_render_list_link),
+},
+
+/// State sent by the wm and committed with a commit request.
+committed: struct {
+ render_list: wl.list.Head(Window, .committed_render_list_link),
+},
+
+/// State committed by the wm that has been sent to windows as part of the
+/// current transaction.
inflight: struct {
render_list: wl.list.Head(Window, .inflight_render_list_link),
},
@@ -63,6 +73,12 @@ pub fn init(wm: *WindowManager) !void {
.global = try wl.Global.create(server.wl_server, river.WindowManagerV1, 1, *WindowManager, wm, bind),
.windows = undefined,
.pending = .{
+ .new_windows = undefined,
+ },
+ .uncommitted = .{
+ .render_list = undefined,
+ },
+ .committed = .{
.render_list = undefined,
},
.inflight = .{
@@ -71,6 +87,10 @@ pub fn init(wm: *WindowManager) !void {
.transaction_timeout = transaction_timeout,
};
wm.windows.init();
+ wm.pending.new_windows.init();
+ wm.uncommitted.render_list.init();
+ wm.committed.render_list.init();
+ wm.inflight.render_list.init();
server.wl_server.addDestroyListener(&wm.server_destroy);
}
@@ -83,41 +103,41 @@ fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
}
fn bind(client: *wl.Client, wm: *WindowManager, version: u32, id: u32) void {
- const wm_v1 = river.WindowManagerV1.create(client, version, id) catch {
+ const object = river.WindowManagerV1.create(client, version, id) catch {
client.postNoMemory();
log.err("out of memory", .{});
return;
};
- if (wm.wm_v1 != null) {
- wm_v1.sendUnavailable();
- wm_v1.setHandler(?*anyopaque, handleRequestInert, null, null);
+ if (wm.object != null) {
+ object.sendUnavailable();
+ object.setHandler(?*anyopaque, handleRequestInert, null, null);
return;
}
- wm.wm_v1 = wm_v1;
- wm_v1.setHandler(*WindowManager, handleRequest, null, wm);
+ wm.object = object;
+ object.setHandler(*WindowManager, handleRequest, null, wm);
}
fn handleRequestInert(
- wm_v1: *river.WindowManagerV1,
+ object: *river.WindowManagerV1,
request: river.WindowManagerV1.Request,
_: ?*anyopaque,
) void {
- if (request == .destroy) wm_v1.destroy();
+ if (request == .destroy) object.destroy();
}
fn handleRequest(
- wm_v1: *river.WindowManagerV1,
+ object: *river.WindowManagerV1,
request: river.WindowManagerV1.Request,
wm: *WindowManager,
) void {
- assert(wm.wm_v1 == wm_v1);
+ assert(wm.object == object);
switch (request) {
.stop => {
- wm.wm_v1 = null;
- wm_v1.sendFinished();
- wm_v1.setHandler(?*anyopaque, handleRequestInert, null, null);
+ wm.object = null;
+ object.sendFinished();
+ object.setHandler(?*anyopaque, handleRequestInert, null, null);
},
.destroy => {
// XXX send protocol error
diff --git a/river/XdgDecoration.zig b/river/XdgDecoration.zig
index 909eba1..840b9c2 100644
--- a/river/XdgDecoration.zig
+++ b/river/XdgDecoration.zig
@@ -75,10 +75,11 @@ fn handleRequestMode(
const toplevel: *XdgToplevel = @ptrFromInt(decoration.wlr_decoration.toplevel.base.data);
const window = toplevel.window;
- const ssd = true;
-
- if (window.pending.ssd != ssd) {
- window.pending.ssd = ssd;
- server.wm.applyPending();
+ switch (decoration.wlr_decoration.requested_mode) {
+ .none => window.pending.decoration_hint = .no_preference,
+ .client_side => window.pending.decoration_hint = .prefers_csd,
+ .server_side => window.pending.decoration_hint = .prefers_ssd,
}
+
+ server.wm.applyPending();
}
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index c70b130..427451d 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -243,7 +243,7 @@ fn handleMap(listener: *wl.Listener(void)) void {
window.inflight.box = window.pending.box;
window.current.box = window.pending.box;
- toplevel.window.pending.fullscreen = toplevel.wlr_toplevel.requested.fullscreen;
+ toplevel.window.pending.fullscreen_requested = toplevel.wlr_toplevel.requested.fullscreen;
window.map() catch {
log.err("out of memory", .{});
@@ -298,12 +298,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
if (toplevel.wlr_toplevel.base.initial_commit) {
_ = toplevel.wlr_toplevel.setWmCapabilities(.{ .fullscreen = true });
- if (toplevel.decoration) |decoration| {
- const ssd = true;
- _ = decoration.wlr_decoration.setMode(if (ssd) .server_side else .client_side);
- toplevel.window.pending.ssd = ssd;
- }
-
+ // XXX I think this is where we actually want to send the new window event.
return;
}
@@ -398,8 +393,8 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
/// for now, perhaps it should be denied in some cases in the future.
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;
+ if (toplevel.window.pending.fullscreen_requested != toplevel.wlr_toplevel.requested.fullscreen) {
+ toplevel.window.pending.fullscreen_requested = toplevel.wlr_toplevel.requested.fullscreen;
server.wm.applyPending();
}
}
@@ -409,33 +404,23 @@ fn handleRequestMove(
event: *wlr.XdgToplevel.event.Move,
) void {
const toplevel: *XdgToplevel = @fieldParentPtr("request_move", listener);
+ _ = toplevel;
const seat: *Seat = @ptrFromInt(event.seat.seat.data);
- const window = toplevel.window;
-
- if (window.pending.fullscreen) return;
// Moving windows with touch or tablet tool is not yet supported.
if (seat.wlr_seat.validatePointerGrabSerial(null, event.serial)) {
- switch (seat.cursor.mode) {
- .passthrough, .down => seat.cursor.startMove(window),
- .move, .resize => {},
- }
+ // XXX queue pointer_move_requested, applyPending()
}
}
fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), event: *wlr.XdgToplevel.event.Resize) void {
const toplevel: *XdgToplevel = @fieldParentPtr("request_resize", listener);
+ _ = toplevel;
const seat: *Seat = @ptrFromInt(event.seat.seat.data);
- const window = toplevel.window;
-
- if (window.pending.fullscreen) return;
// Resizing windows with touch or tablet tool is not yet supported.
if (seat.wlr_seat.validatePointerGrabSerial(null, event.serial)) {
- switch (seat.cursor.mode) {
- .passthrough, .down => seat.cursor.startResize(window, event.edges),
- .move, .resize => {},
- }
+ // XXX queue pointer_resize_requested, applyPending()
}
}
diff --git a/river/XwaylandWindow.zig b/river/XwaylandWindow.zig
index 7e1e487..a234534 100644
--- a/river/XwaylandWindow.zig
+++ b/river/XwaylandWindow.zig
@@ -182,10 +182,15 @@ pub fn handleMap(listener: *wl.Listener(void)) void {
window.inflight.box = window.pending.box;
window.current.box = window.pending.box;
- // This will be overwritten in Window.map() if the window is matched by a rule.
- window.pending.ssd = !xwayland_surface.decorations.no_border;
+ if (xwayland_surface.decorations.no_border or
+ xwayland_surface.decorations.no_title)
+ {
+ window.pending.decoration_hint = .prefers_csd;
+ } else {
+ window.pending.decoration_hint = .prefers_ssd;
+ }
- window.pending.fullscreen = xwayland_surface.fullscreen;
+ window.pending.fullscreen_requested = xwayland_surface.fullscreen;
window.map() catch {
log.err("out of memory", .{});
@@ -268,18 +273,24 @@ fn handleSetDecorations(listener: *wl.Listener(void)) void {
const xwayland_window: *XwaylandWindow = @fieldParentPtr("set_decorations", listener);
const window = xwayland_window.window;
- const ssd = !xwayland_window.xwayland_surface.decorations.no_border;
-
- if (window.pending.ssd != ssd) {
- window.pending.ssd = ssd;
- server.wm.applyPending();
+ if (xwayland_window.xwayland_surface.decorations.no_border or
+ xwayland_window.xwayland_surface.decorations.no_title)
+ {
+ window.pending.decoration_hint = .prefers_csd;
+ } else {
+ window.pending.decoration_hint = .prefers_ssd;
}
+
+ server.wm.applyPending();
}
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;
+ const window = xwayland_window.window;
+ const xwayland_surface = xwayland_window.xwayland_surface;
+
+ if (window.pending.fullscreen_requested != xwayland_surface.fullscreen) {
+ window.pending.fullscreen_requested = xwayland_surface.fullscreen;
server.wm.applyPending();
}
}