Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
WindowManager: send configures
protocol/river-window-management-v1.xml | 3 ++
river/Window.zig | 81 +++++++++++++++++++++------------
river/WindowManager.zig | 18 ++------
river/XdgToplevel.zig | 63 +++++++++++++++++--------
river/XwaylandWindow.zig | 49 ++++++++++++--------
5 files changed, 134 insertions(+), 80 deletions(-)
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index 58f5b9d..d990c53 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -387,6 +387,9 @@
Tell the client to use client side decoration and draw its own title
bar, borders, etc.
+ This is the default if neither this request nor the use_ssd request is
+ ever made.
+
This request is double-buffered state and will not be applied until the
next river_window_manager_v1.commit request.
</description>
diff --git a/river/Window.zig b/river/Window.zig
index f3bcdeb..1c10342 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -56,16 +56,27 @@ const Impl = union(enum) {
none,
};
+pub const Border = struct {
+ edges: river.WindowV1.Edges = .{},
+ width: u31 = 0,
+ r: u32 = 0,
+ b: u32 = 0,
+ g: u32 = 0,
+ a: u32 = 0,
+};
+
pub const State = struct {
/// The output-relative coordinates of the window and dimensions requested by river.
box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
-
+ hidden: bool = false,
/// Number of seats currently focusing the window
focus: u32 = 0,
-
- fullscreen: bool = false,
- urgent: bool = false,
ssd: bool = false,
+ border: Border = .{},
+ tiled: river.WindowV1.Edges = .{},
+ capabilities: river.WindowV1.Capabilities = .{},
+ maximized: bool = false,
+ fullscreen: bool = false,
resizing: bool = false,
};
@@ -77,19 +88,8 @@ pub const WmState = struct {
height: u31,
} = null,
hidden: bool = false,
- decoration_choice: enum {
- none,
- csd,
- ssd,
- } = .none,
- border: struct {
- edges: river.WindowV1.Edges = .{},
- width: u31 = 0,
- r: u32 = 0,
- b: u32 = 0,
- g: u32 = 0,
- a: u32 = 0,
- } = .{},
+ ssd: bool = false,
+ border: Border = .{},
tiled: river.WindowV1.Edges = .{},
capabilities: river.WindowV1.Capabilities = .{
.window_menu = true,
@@ -130,8 +130,6 @@ constraints: Constraints = .{},
/// proposing dimensions for a new river_window_v1 object.
initialized: bool = false,
mapped: bool = false,
-/// This is true if the Window is involved in the currently inflight transaction.
-inflight_transaction: bool = false,
/// This indicates that the window should be destroyed when the current
/// transaction completes. See Window.destroy()
destroying: bool = false,
@@ -402,8 +400,8 @@ fn handleRequest(
},
.hide => uncommitted.hidden = true,
.show => uncommitted.hidden = false,
- .use_csd => uncommitted.decoration_choice = .csd,
- .use_ssd => uncommitted.decoration_choice = .ssd,
+ .use_ssd => uncommitted.ssd = true,
+ .use_csd => uncommitted.ssd = false,
.set_borders => |args| {
if (args.width < 0) {
// XXX send protocol error
@@ -437,7 +435,7 @@ pub fn commitWmState(window: *Window) void {
.y = window.uncommitted.y,
.proposed = window.uncommitted.proposed orelse window.committed.proposed,
.hidden = window.uncommitted.hidden,
- .decoration_choice = window.uncommitted.decoration_choice,
+ .ssd = window.uncommitted.ssd,
.border = window.uncommitted.border,
.tiled = window.uncommitted.tiled,
.capabilities = window.uncommitted.capabilities,
@@ -481,9 +479,6 @@ pub fn resizeUpdatePosition(window: *Window, width: i32, height: i32) void {
}
pub fn commitTransaction(window: *Window) void {
- assert(window.inflight_transaction);
- window.inflight_transaction = false;
-
window.foreign_toplevel_handle.update();
window.dropSavedSurfaceTree();
@@ -604,14 +599,44 @@ pub fn updateSceneState(window: *Window) void {
}
}
+/// Applies committed state from the window manager client to the inflight state.
/// Returns true if the configure should be waited for by the transaction system.
pub fn configure(window: *Window) bool {
+ if (!window.initialized) return false;
+
assert(window.mapped and !window.destroying);
- switch (window.impl) {
- .toplevel => |*toplevel| return toplevel.configure(),
- .xwayland => |*xwindow| return xwindow.configure(),
+
+ const committed = &window.committed;
+ window.inflight = .{
+ .box = .{
+ .x = committed.x,
+ .y = committed.y,
+ .width = if (committed.proposed) |p| p.width else window.pending.box.width,
+ .height = if (committed.proposed) |p| p.height else window.pending.box.height,
+ },
+ .hidden = committed.hidden,
+ .focus = 0, // XXX
+ .ssd = committed.ssd,
+ .border = committed.border,
+ .tiled = committed.tiled,
+ .capabilities = committed.capabilities,
+ .maximized = committed.maximized,
+ .fullscreen = committed.fullscreen,
+ .resizing = false, // XXX
+ };
+
+ const track_configure = switch (window.impl) {
+ .toplevel => |*toplevel| toplevel.configure(),
+ .xwayland => |*xwindow| xwindow.configure(),
.none => unreachable,
+ };
+
+ if (track_configure) {
+ window.saveSurfaceTree();
+ window.sendFrameDone();
}
+
+ return track_configure;
}
/// Returns null if the window is currently being destroyed and no longer has
diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index db752de..acb2326 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -169,6 +169,7 @@ fn handleRequest(
{
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(),
@@ -243,29 +244,18 @@ fn sendConfigures(wm: *WindowManager) void {
.update_sent, .inflight_configures => unreachable,
}
assert(wm.committed.dirty);
-
- // XXX apply committed to inflight
wm.committed.dirty = false;
wm.state = .{ .inflight_configures = 0 };
-
{
- var it = wm.inflight.render_list.iterator(.forward);
+ var it = wm.committed.render_list.iterator(.forward);
while (it.next()) |node| {
+ node.link_inflight.remove();
+ wm.inflight.render_list.append(node);
switch (node.get()) {
.window => |window| {
- assert(!window.inflight_transaction);
- window.inflight_transaction = true;
-
- // This can happen if a window is unmapped while a layout demand including it
- // is inflight If a window has been unmapped, don't send it a configure.
- if (!window.mapped) continue;
-
if (window.configure()) {
wm.state.inflight_configures += 1;
-
- window.saveSurfaceTree();
- window.sendFrameDone();
}
},
}
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 3b6eacc..7025dca 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -130,16 +130,7 @@ pub fn configure(toplevel: *XdgToplevel) bool {
const inflight = &toplevel.window.inflight;
const current = &toplevel.window.current;
- // We avoid a special case for newly mapped windows which we have not yet
- // configured by setting the current width/height to the initial width/height
- // of the window in handleMap().
- if (inflight.box.width == current.box.width and
- inflight.box.height == current.box.height and
- (inflight.focus != 0) == (current.focus != 0) and
- inflight.fullscreen == current.fullscreen and
- inflight.ssd == current.ssd and
- inflight.resizing == current.resizing)
- {
+ if (!toplevel.needsConfigure()) {
// If no new configure is required, continue to track a timed out configure
// from the previous transaction if any.
switch (toplevel.configure_state) {
@@ -159,30 +150,36 @@ pub fn configure(toplevel: *XdgToplevel) bool {
const wlr_toplevel = toplevel.wlr_toplevel;
_ = wlr_toplevel.setActivated(inflight.focus != 0);
+ _ = wlr_toplevel.setTiled(.{
+ .top = inflight.tiled.top,
+ .bottom = inflight.tiled.bottom,
+ .left = inflight.tiled.left,
+ .right = inflight.tiled.right,
+ });
+ _ = wlr_toplevel.setWmCapabilities(.{
+ .window_menu = inflight.capabilities.window_menu,
+ .maximize = inflight.capabilities.maximize,
+ .fullscreen = inflight.capabilities.fullscreen,
+ .minimize = inflight.capabilities.minimize,
+ });
+ _ = wlr_toplevel.setMaximized(inflight.maximized);
_ = wlr_toplevel.setFullscreen(inflight.fullscreen);
_ = wlr_toplevel.setResizing(inflight.resizing);
- // TODO
- if (true) {
- _ = wlr_toplevel.setTiled(.{ .top = false, .bottom = false, .left = false, .right = false });
- } else {
- _ = wlr_toplevel.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true });
- }
-
if (toplevel.decoration) |decoration| {
_ = decoration.wlr_decoration.setMode(if (inflight.ssd) .server_side else .client_side);
}
// We need to call this wlroots function even if the inflight dimensions
// match the current dimensions in order to prevent wlroots internal state
- // from getting out of sync in the case where a client has resized ittoplevel.
+ // from getting out of sync in the case where a client has resized the toplevel.
const configure_serial = wlr_toplevel.setSize(inflight.box.width, inflight.box.height);
// Only track configures with the transaction system if they affect the dimensions of the window.
// If the configure state is not idle this means we are currently tracking a timed out
// configure from a previous transaction and should instead track the newly sent configure.
- if (inflight.box.width == current.box.width and
- inflight.box.height == current.box.height and
+ if (inflight.box.width != 0 and inflight.box.width == current.box.width and
+ inflight.box.height != 0 and inflight.box.height == current.box.height and
toplevel.configure_state == .idle)
{
return false;
@@ -195,6 +192,32 @@ pub fn configure(toplevel: *XdgToplevel) bool {
return true;
}
+fn needsConfigure(toplevel: *XdgToplevel) bool {
+ const inflight = &toplevel.window.inflight;
+ const current = &toplevel.window.current;
+
+ // Never send configures to hidden windows.
+ // If transitioning from hidden to not-hidden, send a configure.
+ if (inflight.hidden) return false;
+ if (current.hidden) return true;
+
+ if (inflight.box.width == 0 or inflight.box.width != current.box.width or
+ inflight.box.height == 0 or inflight.box.height != current.box.height)
+ {
+ return true;
+ }
+
+ if ((inflight.focus != 0) != (current.focus != 0)) return true;
+ if (inflight.ssd != current.ssd) return true;
+ if (!std.meta.eql(inflight.tiled, current.tiled)) return true;
+ if (!std.meta.eql(inflight.capabilities, current.capabilities)) return true;
+ if (inflight.maximized != current.maximized) return true;
+ if (inflight.fullscreen != current.fullscreen) return true;
+ if (inflight.resizing != current.resizing) return true;
+
+ return false;
+}
+
pub fn destroyPopups(toplevel: XdgToplevel) void {
var it = toplevel.wlr_toplevel.base.popups.safeIterator(.forward);
while (it.next()) |wlr_xdg_popup| wlr_xdg_popup.destroy();
diff --git a/river/XwaylandWindow.zig b/river/XwaylandWindow.zig
index 34dba95..b04f041 100644
--- a/river/XwaylandWindow.zig
+++ b/river/XwaylandWindow.zig
@@ -88,30 +88,44 @@ pub fn create(xsurface: *wlr.XwaylandSurface) error{OutOfMemory}!void {
}
/// Always returns false as we do not care about frame perfection for Xwayland windows.
-pub fn configure(xwindow: XwaylandWindow) bool {
+pub fn configure(xwindow: *XwaylandWindow) bool {
const inflight = &xwindow.window.inflight;
const current = &xwindow.window.current;
- if (xwindow.xsurface.x == inflight.box.x and
- xwindow.xsurface.y == inflight.box.y and
- xwindow.xsurface.width == inflight.box.width and
- xwindow.xsurface.height == inflight.box.height and
- (inflight.focus != 0) == (current.focus != 0))
- // TODO fullscreen
- {
- return false;
+ // 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;
+ }
+ if (inflight.box.height == 0) {
+ inflight.box.height = xwindow.window.pending.box.height;
}
- xwindow.xsurface.configure(
- math.lossyCast(i16, inflight.box.x),
- math.lossyCast(i16, inflight.box.y),
- math.lossyCast(u16, inflight.box.width),
- math.lossyCast(u16, inflight.box.height),
- );
+ if (inflight.hidden != current.hidden) {
+ xwindow.xsurface.setWithdrawn(inflight.hidden);
+ }
- xwindow.setActivated(inflight.focus != 0);
+ if (inflight.box.x != current.box.x or
+ inflight.box.y != current.box.y or
+ inflight.box.width != current.box.width or
+ inflight.box.height != current.box.height)
+ {
+ xwindow.xsurface.configure(
+ math.lossyCast(i16, inflight.box.x),
+ math.lossyCast(i16, inflight.box.y),
+ math.lossyCast(u16, inflight.box.width),
+ math.lossyCast(u16, inflight.box.height),
+ );
+ }
- if (false) xwindow.xsurface.setFullscreen();
+ if ((inflight.focus != 0) != (current.focus != 0)) {
+ xwindow.setActivated(inflight.focus != 0);
+ }
+ if (inflight.maximized != current.maximized) {
+ xwindow.xsurface.setFullscreen(inflight.maximized);
+ }
+ if (inflight.fullscreen != current.fullscreen) {
+ xwindow.xsurface.setFullscreen(inflight.fullscreen);
+ }
return false;
}
@@ -215,7 +229,6 @@ fn handleRequestConfigure(
return;
}
- // Allow xwayland windows to set their own dimensions (but not position) if floating
xwindow.window.pending.box.width = event.width;
xwindow.window.pending.box.height = event.height;
server.wm.dirtyPending();