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

commit81841a651631834b55eaaa6d16f8e6fc44cc1621
parent8dadee026c
authorIsaac Freund <[email protected]>
date2025-08-05 00:03
Window: send river_window_v1.parent

 river/Window.zig      | 22 ++++++++++++++++++++++
 river/XdgToplevel.zig |  7 +++++++
 2 files changed, 29 insertions(+)

diff --git a/river/Window.zig b/river/Window.zig
index ed71001..50c5e52 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -172,6 +172,7 @@ wm_scheduled: struct {
 wm_sent: struct {
     dimensions_hint: DimensionsHint = .{},
     decoration_hint: river.WindowV1.DecorationHint = .only_supports_csd,
+    parent: ?Window.Ref = null,
 } = .{},
 
 /// Windowing state requested by the wm.
@@ -419,6 +420,16 @@ pub fn manageStart(window: *Window) void {
             }
             scheduled.minimize_requested = false;
 
+            if (window.getParent()) |parent| {
+                if (sent.parent == null or sent.parent.?.get() != parent) {
+                    window_v1.sendParent(parent.object);
+                    sent.parent = parent.ref;
+                }
+            } else if (sent.parent != null) {
+                window_v1.sendParent(null);
+                sent.parent = null;
+            }
+
             if (new or scheduled.dirty_app_id) {
                 window_v1.sendAppId(window.getAppId());
                 scheduled.dirty_app_id = false;
@@ -872,6 +883,17 @@ pub fn destroyPopups(window: Window) void {
     }
 }
 
+pub fn getParent(window: *Window) ?*Window {
+    switch (window.impl) {
+        .toplevel => |toplevel| {
+            const wlr_parent = toplevel.wlr_toplevel.parent orelse return null;
+            const parent: *XdgToplevel = @alignCast(@ptrCast(wlr_parent.base.data));
+            return parent.window;
+        },
+        .xwayland, .destroying => return null,
+    }
+}
+
 /// Return the current title of the window if any.
 pub fn getTitle(window: Window) ?[*:0]const u8 {
     return switch (window.impl) {
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 6ff71c0..a90d3ab 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -70,6 +70,7 @@ request_maximize: wl.Listener(void) = .init(handleRequestMaximize),
 request_minimize: wl.Listener(void) = .init(handleRequestMinimize),
 request_move: wl.Listener(*wlr.XdgToplevel.event.Move) = .init(handleRequestMove),
 request_resize: wl.Listener(*wlr.XdgToplevel.event.Resize) = .init(handleRequestResize),
+set_parent: wl.Listener(void) = .init(handleSetParent),
 set_title: wl.Listener(void) = .init(handleSetTitle),
 set_app_id: wl.Listener(void) = .init(handleSetAppId),
 
@@ -112,6 +113,7 @@ pub fn create(wlr_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void {
     wlr_toplevel.events.request_minimize.add(&toplevel.request_minimize);
     wlr_toplevel.events.request_move.add(&toplevel.request_move);
     wlr_toplevel.events.request_resize.add(&toplevel.request_resize);
+    wlr_toplevel.events.set_parent.add(&toplevel.set_parent);
     wlr_toplevel.events.set_title.add(&toplevel.set_title);
     wlr_toplevel.events.set_app_id.add(&toplevel.set_app_id);
 }
@@ -237,6 +239,7 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
     toplevel.request_minimize.link.remove();
     toplevel.request_move.link.remove();
     toplevel.request_resize.link.remove();
+    toplevel.set_parent.link.remove();
     toplevel.set_title.link.remove();
     toplevel.set_app_id.link.remove();
 
@@ -428,6 +431,10 @@ fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), ev
     }
 }
 
+fn handleSetParent(_: *wl.Listener(void)) void {
+    server.wm.dirtyWindowing();
+}
+
 /// Called when the client sets / updates its title
 fn handleSetTitle(listener: *wl.Listener(void)) void {
     const toplevel: *XdgToplevel = @fieldParentPtr("set_title", listener);