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

commit8c2fbc971e97ca39ad5f86e2df191d785905dc48
parent376fe5a6fc
authorIsaac Freund <[email protected]>
date2025-07-14 10:53
XdgPopup: fix TODO panic

Actually (re)position popups rather than crashing.

 river/WindowManager.zig |  1 +
 river/XdgPopup.zig      | 53 +++++++++++++++++++++++++++++--------------------
 2 files changed, 33 insertions(+), 21 deletions(-)

diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index c8abd94..4c1e74b 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -431,6 +431,7 @@ fn renderFinish(wm: *WindowManager) void {
                 .window => |window| {
                     window.renderFinish();
                     window.tree.node.reparent(server.scene.layers.wm);
+                    window.popup_tree.node.reparent(server.scene.layers.popups);
                     if (window.wm_requested.fullscreen != null) {
                         found_fullscreen = true;
                         window.tree.node.raiseToTop();
diff --git a/river/XdgPopup.zig b/river/XdgPopup.zig
index 833cd41..3026347 100644
--- a/river/XdgPopup.zig
+++ b/river/XdgPopup.zig
@@ -28,7 +28,7 @@ const SceneNodeData = @import("SceneNodeData.zig");
 
 const log = std.log.scoped(.xdg_popup);
 
-wlr_xdg_popup: *wlr.XdgPopup,
+wlr_popup: *wlr.XdgPopup,
 /// The root of the surface tree, i.e. the Window's popup_tree
 root: *wlr.SceneTree,
 
@@ -41,7 +41,7 @@ reposition: wl.Listener(void) = .init(handleReposition),
 
 // TODO check if popup is set_reactive and reposition on parent movement.
 pub fn create(
-    wlr_xdg_popup: *wlr.XdgPopup,
+    wlr_popup: *wlr.XdgPopup,
     root: *wlr.SceneTree,
     parent: *wlr.SceneTree,
 ) error{OutOfMemory}!void {
@@ -49,15 +49,15 @@ pub fn create(
     errdefer util.gpa.destroy(xdg_popup);
 
     xdg_popup.* = .{
-        .wlr_xdg_popup = wlr_xdg_popup,
+        .wlr_popup = wlr_popup,
         .root = root,
-        .tree = try parent.createSceneXdgSurface(wlr_xdg_popup.base),
+        .tree = try parent.createSceneXdgSurface(wlr_popup.base),
     };
 
-    wlr_xdg_popup.events.destroy.add(&xdg_popup.destroy);
-    wlr_xdg_popup.base.surface.events.commit.add(&xdg_popup.commit);
-    wlr_xdg_popup.base.events.new_popup.add(&xdg_popup.new_popup);
-    wlr_xdg_popup.events.reposition.add(&xdg_popup.reposition);
+    wlr_popup.events.destroy.add(&xdg_popup.destroy);
+    wlr_popup.base.surface.events.commit.add(&xdg_popup.commit);
+    wlr_popup.base.events.new_popup.add(&xdg_popup.new_popup);
+    wlr_popup.events.reposition.add(&xdg_popup.reposition);
 }
 
 fn handleDestroy(listener: *wl.Listener(void)) void {
@@ -74,20 +74,20 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
 fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
     const xdg_popup: *XdgPopup = @fieldParentPtr("commit", listener);
 
-    if (xdg_popup.wlr_xdg_popup.base.initial_commit) {
+    if (xdg_popup.wlr_popup.base.initial_commit) {
         handleReposition(&xdg_popup.reposition);
     }
 }
 
-fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
+fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_popup: *wlr.XdgPopup) void {
     const xdg_popup: *XdgPopup = @fieldParentPtr("new_popup", listener);
 
     XdgPopup.create(
-        wlr_xdg_popup,
+        wlr_popup,
         xdg_popup.root,
         xdg_popup.tree,
     ) catch {
-        wlr_xdg_popup.resource.postNoMemory();
+        wlr_popup.resource.postNoMemory();
         return;
     };
 }
@@ -95,20 +95,31 @@ fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.Xdg
 fn handleReposition(listener: *wl.Listener(void)) void {
     const xdg_popup: *XdgPopup = @fieldParentPtr("reposition", listener);
 
-    const output = switch (SceneNodeData.fromNode(&xdg_popup.root.node).?.data) {
-        .window => |_| @panic("TODO"),
-        else => unreachable,
-    };
-
-    var box: wlr.Box = undefined;
-    server.om.output_layout.getBox(output.wlr_output, &box);
-
     var root_lx: c_int = undefined;
     var root_ly: c_int = undefined;
     _ = xdg_popup.root.node.coords(&root_lx, &root_ly);
 
+    const wlr_output = switch (SceneNodeData.fromNode(&xdg_popup.root.node).?.data) {
+        .window => |_| blk: {
+            var lx: f64 = undefined;
+            var ly: f64 = undefined;
+            server.om.output_layout.closestPoint(
+                null,
+                @as(f64, @floatFromInt(root_lx + xdg_popup.wlr_popup.scheduled.geometry.x)),
+                @as(f64, @floatFromInt(root_ly + xdg_popup.wlr_popup.scheduled.geometry.y)),
+                &lx,
+                &ly,
+            );
+            break :blk server.om.output_layout.outputAt(lx, ly).?;
+        },
+        .shell_surface, .lock_surface, .override_redirect => unreachable,
+    };
+
+    var box: wlr.Box = undefined;
+    server.om.output_layout.getBox(wlr_output, &box);
+
     box.x -= root_lx;
     box.y -= root_ly;
 
-    xdg_popup.wlr_xdg_popup.unconstrainFromBox(&box);
+    xdg_popup.wlr_popup.unconstrainFromBox(&box);
 }