git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitb3b470341d9d82c62f76ea57c725d966e42eb401
parenta14c865999
authorIsaac Freund <[email protected]>
date2024-12-29 16:52
rwm: add and implement river_window_v1.dimensions_hint

 protocol/river-window-management-v1.xml | 23 ++++++++++++++++++++++-
 river/Window.zig                        | 33 ++++++++++++++++++++++++---------
 river/XdgToplevel.zig                   | 17 +++++++----------
 rivercompat/Window.zig                  |  1 +
 4 files changed, 54 insertions(+), 20 deletions(-)

diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index 1b320fa..2268a7b 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -206,7 +206,6 @@
       a river_window_manager_v1.commit request.
 
       TODO:
-        - size hints (including unset state)
         - window cropping
     </description>
 
@@ -260,6 +259,28 @@
       <arg name="id" type="new_id" interface="river_node_v1"/>
     </request>
 
+    <event name="dimensions_hint">
+      <description summary="the window's preferred min/max dimensions">
+        This event informs the window manager of the window's preferred min/max
+        dimensions. These preferences are a hint, and the window manager is free
+        to propose dimensions outside of these bounds.
+
+        All min/max width/height values must be strictly greater than or equal
+        to 0. A value of 0 indicates that the window has no preference for that
+        value.
+
+        The min_width/min_height must be strictly less than or equal to the
+        max_width/max_height.
+
+        This event is double-buffered state and will be followed by a
+        river_window_manager_v1.update event.
+      </description>
+      <arg name="min_width" type="int"/>
+      <arg name="min_height" type="int"/>
+      <arg name="max_width" type="int"/>
+      <arg name="max_height" type="int"/>
+    </event>
+
     <event name="dimensions">
       <description summary="window dimensions">
         This event indicates the dimensions of the window in the compositor's
diff --git a/river/Window.zig b/river/Window.zig
index b9622fa..462ebfb 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -20,6 +20,7 @@ const build_options = @import("build_options");
 const std = @import("std");
 const assert = std.debug.assert;
 const math = std.math;
+const meta = std.meta;
 const posix = std.posix;
 const wlr = @import("wlroots");
 const wl = @import("wayland").server.wl;
@@ -38,11 +39,11 @@ const XwaylandWindow = @import("XwaylandWindow.zig");
 
 const log = std.log.scoped(.wm);
 
-pub const Constraints = struct {
-    min_width: u31 = 1,
-    max_width: u31 = math.maxInt(u31),
-    min_height: u31 = 1,
-    max_height: u31 = math.maxInt(u31),
+pub const DimensionsHint = struct {
+    min_width: u31 = 0,
+    max_width: u31 = 0,
+    min_height: u31 = 0,
+    max_height: u31 = 0,
 };
 
 const Impl = union(enum) {
@@ -123,9 +124,6 @@ saved_surface_tree: *wlr.SceneTree,
 borders: [4]*wlr.SceneRect,
 popup_tree: *wlr.SceneTree,
 
-/// Bounds on the width/height of the window, set by the toplevel/xwindow implementation.
-constraints: Constraints = .{},
-
 /// Set to true once the window manager client has made its first commit
 /// proposing dimensions for a new river_window_v1 object.
 initialized: bool = false,
@@ -146,6 +144,7 @@ pending: struct {
         /// Indicates that the closed event will be sent in the next update sequence.
         closing,
     } = .init,
+    dimensions_hint: DimensionsHint = .{},
     box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
     decoration_hint: river.WindowV1.DecorationHint = .only_supports_csd,
     /// Set back to no_request at the end of each update sequence
@@ -161,6 +160,7 @@ pending: struct {
 /// This state is only kept around in order to avoid sending redundant events
 /// to the window manager client.
 sent: struct {
+    dimensions_hint: DimensionsHint = .{},
     box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
     decoration_hint: river.WindowV1.DecorationHint = .only_supports_csd,
 } = .{},
@@ -279,6 +279,13 @@ pub fn closing(window: *Window) void {
     window.dirtyPending();
 }
 
+pub fn setDimensionsHint(window: *Window, hint: DimensionsHint) void {
+    window.pending.dimensions_hint = hint;
+    if (!meta.eql(window.sent.dimensions_hint, hint)) {
+        window.dirtyPending();
+    }
+}
+
 pub fn setDimensions(window: *Window, width: i32, height: i32) void {
     window.pending.box.width = width;
     window.pending.box.height = height;
@@ -357,7 +364,15 @@ pub fn sendDirty(window: *Window) void {
             const sent = &window.sent;
 
             // XXX send all dirty pending state
-            log.debug("XXXXXXXXXXXXXX pending {any} sent {any}", .{ pending.box, sent.box });
+            if (new or !meta.eql(pending.dimensions_hint, sent.dimensions_hint)) {
+                window_v1.sendDimensionsHint(
+                    pending.dimensions_hint.min_width,
+                    pending.dimensions_hint.min_height,
+                    pending.dimensions_hint.max_width,
+                    pending.dimensions_hint.max_height,
+                );
+                sent.dimensions_hint = pending.dimensions_hint;
+            }
             if ((new or pending.box.width != sent.box.width or
                 pending.box.height != sent.box.height) and !pending.box.empty())
             {
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index db36d11..840eed7 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -298,6 +298,13 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
     const toplevel: *XdgToplevel = @fieldParentPtr("commit", listener);
     const window = toplevel.window;
 
+    window.setDimensionsHint(.{
+        .min_width = @intCast(toplevel.wlr_toplevel.current.min_width),
+        .min_height = @intCast(toplevel.wlr_toplevel.current.min_height),
+        .max_width = @intCast(toplevel.wlr_toplevel.current.max_width),
+        .max_height = @intCast(toplevel.wlr_toplevel.current.max_height),
+    });
+
     if (toplevel.wlr_toplevel.base.initial_commit) {
         window.ready();
         return;
@@ -307,16 +314,6 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
         return;
     }
 
-    {
-        const state = &toplevel.wlr_toplevel.current;
-        window.constraints = .{
-            .min_width = @max(state.min_width, 1),
-            .max_width = if (state.max_width > 0) @intCast(state.max_width) else math.maxInt(u31),
-            .min_height = @max(state.min_height, 1),
-            .max_height = if (state.max_height > 0) @intCast(state.max_height) else math.maxInt(u31),
-        };
-    }
-
     switch (toplevel.configure_state) {
         .idle, .committed, .timed_out => {
             const old_geometry = toplevel.geometry;
diff --git a/rivercompat/Window.zig b/rivercompat/Window.zig
index a5659e3..dc3df1f 100644
--- a/rivercompat/Window.zig
+++ b/rivercompat/Window.zig
@@ -63,6 +63,7 @@ fn handleEvent(window_v1: *river.WindowV1, event: river.WindowV1.Event, window:
 
             gpa.destroy(window);
         },
+        .dimensions_hint => {},
         .dimensions => {},
         .app_id => {},
         .title => {},