Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
protocol: implement river_window_v1.set_dimension_bounds
protocol/river-window-management-v1.xml | 21 ++++++
river/Window.zig | 113 +++++++++++++++++++++-----------
river/XdgToplevel.zig | 8 +++
3 files changed, 105 insertions(+), 37 deletions(-)
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index 23395e3..9add976 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -1080,6 +1080,27 @@
</description>
<arg name="identifier" type="string" summary="unique identifier"/>
</event>
+
+ <request name="set_dimension_bounds" since="4">
+ <description summary="recommend maximum dimensions to the window">
+ Recommend that the window keep its dimensions within a given
+ maximum width/height. This recommendation is only a hint and the window
+ may ignore it.
+
+ Setting the width and height to 0 indicates that there are no bounds
+ and is equivalent to having never made this request.
+
+ Setting width or height to a negative value is a protocol error.
+
+ The server should communicate this hint to an xdg_toplevel window with
+ the xdg_toplevel.configure_bounds event for example.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+ </description>
+ <arg name="max_width" type="int" summary="maximum width"/>
+ <arg name="max_height" type="int" summary="maximum height"/>
+ </request>
</interface>
<interface name="river_decoration_v1" version="4">
diff --git a/river/Window.zig b/river/Window.zig
index 2ed1627..72699db 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -28,6 +28,11 @@ const XwaylandWindow = @import("XwaylandWindow.zig");
const log = std.log.scoped(.wm);
+pub const Dimensions = struct {
+ width: u31,
+ height: u31,
+};
+
pub const DimensionsHint = struct {
min_width: u31 = 0,
max_width: u31 = 0,
@@ -61,7 +66,8 @@ pub const Border = struct {
/// Windowing state requested by the wm.
const WmRequested = struct {
- dimensions: ?struct { width: u31, height: u31 },
+ dimensions: ?Dimensions,
+ bounds: Dimensions,
ssd: bool,
tiled: river.WindowV1.Edges,
capabilities: river.WindowV1.Capabilities,
@@ -73,6 +79,7 @@ const WmRequested = struct {
pub const init: WmRequested = .{
.dimensions = null,
+ .bounds = .{ .width = 0, .height = 0 },
.ssd = false,
.tiled = .{},
.capabilities = .{
@@ -90,17 +97,30 @@ const WmRequested = struct {
};
pub const Configure = struct {
- width: ?u31 = null,
- height: ?u31 = null,
+ width: ?u31,
+ height: ?u31,
+ bounds: Dimensions,
/// True if the window has keyboard focus from at least one seat.
- activated: bool = false,
- ssd: bool = false,
- border: Border = .{},
- tiled: river.WindowV1.Edges = .{},
- capabilities: river.WindowV1.Capabilities = .{},
- maximized: bool = false,
- inform_fullscreen: bool = false,
- resizing: bool = false,
+ activated: bool,
+ ssd: bool,
+ tiled: river.WindowV1.Edges,
+ capabilities: river.WindowV1.Capabilities,
+ maximized: bool,
+ inform_fullscreen: bool,
+ resizing: bool,
+
+ pub const init: Configure = .{
+ .width = null,
+ .height = null,
+ .bounds = .{ .width = 0, .height = 0 },
+ .activated = false,
+ .ssd = false,
+ .tiled = .{},
+ .capabilities = .{},
+ .maximized = false,
+ .inform_fullscreen = false,
+ .resizing = false,
+ };
};
/// Rendering state requested by the wm.
@@ -216,9 +236,9 @@ wm_sent: struct {
wm_requested: WmRequested = .init,
/// State to be sent to the window in the next configure.
-configure_scheduled: Configure = .{},
+configure_scheduled: Configure = .init,
/// State sent to the window in the latest configure.
-configure_sent: Configure = .{},
+configure_sent: Configure = .init,
/// State to be sent to the wm in the next render sequence.
rendering_scheduled: struct {
@@ -690,6 +710,17 @@ fn handleRequest(
.height = args.height,
};
},
+ .set_dimension_bounds => |args| {
+ if (!server.wm.ensureWindowing()) return;
+ if (args.max_width < 0 or args.max_height < 0) {
+ window_v1.postError(.invalid_dimensions, "dimensions must be greater than or equal to 0 ");
+ return;
+ }
+ wm_requested.bounds = .{
+ .width = @intCast(args.max_width),
+ .height = @intCast(args.max_height),
+ };
+ },
}
}
@@ -718,45 +749,53 @@ pub fn manageFinish(window: *Window) bool {
.closing => return false,
}
- window.configure_scheduled.ssd = wm_requested.ssd;
- window.configure_scheduled.tiled = wm_requested.tiled;
- window.configure_scheduled.capabilities = wm_requested.capabilities;
- window.configure_scheduled.resizing = wm_requested.resizing;
- window.configure_scheduled.maximized = wm_requested.maximized;
- window.configure_scheduled.inform_fullscreen = wm_requested.inform_fullscreen;
-
if (wm_requested.close) {
window.close();
wm_requested.close = false;
}
- {
- window.configure_scheduled.activated = false;
+ const activated = blk: {
var it = server.wm.sent.seats.iterator(.forward);
while (it.next()) |seat| {
if (seat.focused == .window and seat.focused.window == window) {
- window.configure_scheduled.activated = true;
- break;
+ break :blk true;
}
}
- }
+ break :blk false;
+ };
- if (wm_requested.fullscreen) |output| {
- const width, const height = output.sent.dimensions();
- if (window.configure_sent.width != width or
- window.configure_sent.height != height)
- {
- window.configure_scheduled.width = width;
- window.configure_scheduled.height = height;
+ const width, const height = blk: {
+ if (wm_requested.fullscreen) |output| {
+ const width, const height = output.sent.dimensions();
+ if (window.configure_sent.width != width or
+ window.configure_sent.height != height)
+ {
+ window.configure_scheduled.width = width;
+ window.configure_scheduled.height = height;
+ window.rendering_scheduled.resend_dimensions = true;
+ break :blk .{ width, height };
+ }
+ } else if (wm_requested.dimensions) |dimensions| {
window.rendering_scheduled.resend_dimensions = true;
+ break :blk .{ dimensions.width, dimensions.height };
}
- } else if (wm_requested.dimensions) |dimensions| {
- window.configure_scheduled.width = dimensions.width;
- window.configure_scheduled.height = dimensions.height;
- window.rendering_scheduled.resend_dimensions = true;
- }
+ break :blk .{ null, null };
+ };
wm_requested.dimensions = null;
+ window.configure_scheduled = .{
+ .width = width,
+ .height = height,
+ .bounds = wm_requested.bounds,
+ .activated = activated,
+ .ssd = wm_requested.ssd,
+ .tiled = wm_requested.tiled,
+ .capabilities = wm_requested.capabilities,
+ .resizing = wm_requested.resizing,
+ .maximized = wm_requested.maximized,
+ .inform_fullscreen = wm_requested.inform_fullscreen,
+ };
+
const track_configure = switch (window.impl) {
.toplevel => |*toplevel| toplevel.configure(),
.xwayland => |*xwindow| xwindow.configure(),
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 7b27a33..11cb497 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -120,6 +120,7 @@ pub fn configure(toplevel: *XdgToplevel) bool {
};
const scheduled = &toplevel.window.configure_scheduled;
+ const sent = &toplevel.window.configure_scheduled;
if (!toplevel.needsConfigure()) {
// If no new configure is required, continue to track a timed out configure
@@ -159,6 +160,11 @@ pub fn configure(toplevel: *XdgToplevel) bool {
if (toplevel.decoration) |decoration| {
_ = decoration.wlr_decoration.setMode(if (scheduled.ssd) .server_side else .client_side);
}
+ if (scheduled.bounds.width != sent.bounds.width or
+ scheduled.bounds.height != sent.bounds.height)
+ {
+ _ = wlr_toplevel.setBounds(scheduled.bounds.width, scheduled.bounds.height);
+ }
const width: u31 = scheduled.width orelse switch (toplevel.configure_state) {
.idle => @intCast(toplevel.geometry.width),
@@ -201,6 +207,8 @@ fn needsConfigure(toplevel: *XdgToplevel) bool {
if (scheduled.width != null and scheduled.width != sent.width) return true;
if (scheduled.height != null and scheduled.height != sent.height) return true;
+ if (scheduled.bounds.width != sent.bounds.width or
+ scheduled.bounds.height != sent.bounds.height) 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;