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

commita51f3bea4451f9b9363e797d5e591d5509f93ac8
parent9947981035
authorIsaac Freund <[email protected]>
date2026-04-29 14:47
XdgPopup: fix positioning to respect parent geometry

It turns out that wlr_xdg_popup_unconstrain_from_box() is bad, and there
is a better, lower-level API that doesn't require us to use coordinates
in the root of the popup tree's coordinate space.

 river/LayerSurface.zig |  1 -
 river/XdgPopup.zig     | 31 +++++++++++++------------------
 river/XdgToplevel.zig  |  1 -
 3 files changed, 13 insertions(+), 20 deletions(-)

diff --git a/river/LayerSurface.zig b/river/LayerSurface.zig
index 9d29b12..9394c0e 100644
--- a/river/LayerSurface.zig
+++ b/river/LayerSurface.zig
@@ -172,7 +172,6 @@ fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.Xdg
     XdgPopup.create(
         wlr_xdg_popup,
         layer_surface.popup_tree,
-        layer_surface.popup_tree,
         null,
     ) catch {
         wlr_xdg_popup.resource.postNoMemory();
diff --git a/river/XdgPopup.zig b/river/XdgPopup.zig
index a1ff7cc..7212187 100644
--- a/river/XdgPopup.zig
+++ b/river/XdgPopup.zig
@@ -16,9 +16,6 @@ const SceneNodeData = @import("SceneNodeData.zig");
 const log = std.log.scoped(.xdg_popup);
 
 wlr_popup: *wlr.XdgPopup,
-/// The root of the surface tree, i.e. the Window's popup_tree
-root: *wlr.SceneTree,
-
 tree: *wlr.SceneTree,
 capture_tree: ?*wlr.SceneTree = null,
 
@@ -30,7 +27,6 @@ reposition: wl.Listener(void) = .init(handleReposition),
 // TODO check if popup is set_reactive and reposition on parent movement.
 pub fn create(
     wlr_popup: *wlr.XdgPopup,
-    root: *wlr.SceneTree,
     parent: *wlr.SceneTree,
     capture_parent: ?*wlr.SceneTree,
 ) error{OutOfMemory}!void {
@@ -39,7 +35,6 @@ pub fn create(
 
     xdg_popup.* = .{
         .wlr_popup = wlr_popup,
-        .root = root,
         .tree = try parent.createSceneXdgSurface(wlr_popup.base),
     };
     if (capture_parent) |p| {
@@ -76,7 +71,6 @@ fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_popup: *wlr.XdgPopu
 
     XdgPopup.create(
         wlr_popup,
-        xdg_popup.root,
         xdg_popup.tree,
         xdg_popup.capture_tree,
     ) catch {
@@ -87,21 +81,22 @@ fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_popup: *wlr.XdgPopu
 
 fn handleReposition(listener: *wl.Listener(void)) void {
     const xdg_popup: *XdgPopup = @fieldParentPtr("reposition", listener);
+    const wlr_popup = xdg_popup.wlr_popup;
 
-    var root_lx: c_int = undefined;
-    var root_ly: c_int = undefined;
-    _ = xdg_popup.root.node.coords(&root_lx, &root_ly);
+    var parent_lx: c_int = undefined;
+    var parent_ly: c_int = undefined;
+    _ = xdg_popup.tree.node.parent.?.node.coords(&parent_lx, &parent_ly);
 
-    var anchor = xdg_popup.wlr_popup.scheduled.rules.anchor_rect;
-    anchor.x += root_lx;
-    anchor.y += root_ly;
+    var anchor = wlr_popup.scheduled.rules.anchor_rect;
+    anchor.x += parent_lx;
+    anchor.y += parent_ly;
     const wlr_output = server.om.maxOverlapOutput(&anchor) orelse return;
 
-    var box: wlr.Box = undefined;
-    server.om.output_layout.getBox(wlr_output, &box);
-
-    box.x -= root_lx;
-    box.y -= root_ly;
+    var constraint: wlr.Box = undefined;
+    server.om.output_layout.getBox(wlr_output, &constraint);
+    constraint.x -= parent_lx;
+    constraint.y -= parent_ly;
 
-    xdg_popup.wlr_popup.unconstrainFromBox(&box);
+    wlr_popup.scheduled.rules.unconstrainBox(&constraint, &wlr_popup.scheduled.geometry);
+    _ = wlr_popup.base.scheduleConfigure();
 }
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 3eba31c..a426939 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -293,7 +293,6 @@ fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.Xdg
     XdgPopup.create(
         wlr_xdg_popup,
         toplevel.window.popup_tree,
-        toplevel.window.popup_tree,
         &toplevel.window.capture_scene.tree,
     ) catch {
         wlr_xdg_popup.resource.postNoMemory();