wmaker-popup-v1: add set_parent for parent-relative positioning

The previous version had set_position(x, y) in compositor-global
coordinates, but Wayland clients don't know their toplevel's screen
position — the editmenu and popup button code naturally compute
positions relative to the parent toplevel via accumulated view->pos.

Add set_parent(xdg_toplevel) request. When a parent is set,
set_position(x, y) is interpreted as offsets from the parent
toplevel's top-left corner.

Compositor side resolves the parent's wl_toplevel_view via
wlr_xdg_toplevel_from_resource → wl_find_view_by_xdg_surface,
then adds parent->x and parent->y to the requested offsets.

Client side (wclient_wl.c) tracks the first non-popup toplevel as
ctx->main_toplevel and calls set_parent on every popup wrapper to
anchor it to the application's main window.
This commit is contained in:
Window Maker 2026-05-24 18:37:48 +02:00
parent 1cf271932a
commit 27802e6d43
3 changed files with 71 additions and 13 deletions

View File

@ -66,6 +66,8 @@ typedef struct WLClientCtx {
struct xdg_wm_base *wm_base;
/* wmaker-popup-v1: client-driven popup positioning */
struct wmaker_popup_manager_v1 *wmaker_popup_mgr;
/* First non-popup toplevel — used as parent for popup positioning. */
struct xdg_toplevel *main_toplevel;
struct wl_pointer *pointer;
struct wl_keyboard *keyboard;
@ -466,6 +468,11 @@ wl_client_toplevel_realize(struct W_View *view, int width, int height)
wwarning("wclient_wl: wrapped popup toplevel id=0x%lx pos=(%d,%d) wrapper=%p",
(unsigned long)view->window, view->pos.x, view->pos.y,
(void*)top->wmaker_popup);
/* Anchor positioning to the application's main toplevel. */
if (top->wmaker_popup && ctx->main_toplevel) {
wmaker_popup_v1_set_parent(top->wmaker_popup,
ctx->main_toplevel);
}
/* Apply any position recorded in view->pos before realize. */
if (top->wmaker_popup && (view->pos.x || view->pos.y)) {
wmaker_popup_v1_set_position(top->wmaker_popup,
@ -477,6 +484,10 @@ wl_client_toplevel_realize(struct W_View *view, int width, int height)
} else {
xdg_toplevel_set_app_id(top->xdg_toplevel, "WINGs");
xdg_toplevel_set_title(top->xdg_toplevel, "WINGs Application");
/* Remember the first regular toplevel as the "main" window so
* subsequent popups can position relative to it. */
if (!ctx->main_toplevel)
ctx->main_toplevel = top->xdg_toplevel;
}
/* Initial commit with no buffer so the compositor sends the first

View File

@ -74,7 +74,10 @@
<interface name="wmaker_popup_v1" version="1">
<description summary="client-positioned popup toplevel">
Wrapper around an xdg_toplevel that lets the client drive screen
placement. Position is in compositor-global coordinates.
placement. Position is in coordinates relative to the parent
toplevel's top-left corner, or compositor-global if no parent is
set. The parent is set via set_parent() and is typically the
application's main toplevel (e.g., the WPrefs main window).
</description>
<enum name="error">
@ -90,14 +93,26 @@
</description>
</request>
<request name="set_position">
<description summary="set the screen position of the popup">
Place the popup's top-left corner at (x, y) in compositor-global
coordinates. Takes effect on the next surface commit if the
toplevel is not yet mapped, or immediately if already mapped.
<request name="set_parent">
<description summary="set the parent toplevel for relative positioning">
Subsequent set_position calls are interpreted as offsets from
the parent toplevel's top-left corner. Pass NULL to revert to
compositor-global coordinates.
</description>
<arg name="x" type="int" summary="screen x in compositor coordinates"/>
<arg name="y" type="int" summary="screen y in compositor coordinates"/>
<arg name="parent" type="object" interface="xdg_toplevel"
allow-null="true" summary="parent toplevel or null"/>
</request>
<request name="set_position">
<description summary="set the position of the popup">
Place the popup at (x, y). If a parent has been set via
set_parent, coordinates are relative to the parent's top-left
corner. Otherwise, coordinates are compositor-global.
Takes effect on the next surface commit if the toplevel is not
yet mapped, or immediately if already mapped.
</description>
<arg name="x" type="int" summary="x offset"/>
<arg name="y" type="int" summary="y offset"/>
</request>
</interface>
</protocol>

View File

@ -277,6 +277,7 @@ struct wl_toplevel_view {
/* wmaker-popup-v1: client-driven popup position (only valid if popup != NULL) */
struct wl_resource *popup; /* wmaker_popup_v1 resource or NULL */
int popup_x, popup_y;
struct wlr_xdg_toplevel *popup_parent; /* parent toplevel or NULL */
long *wm_state; /* Phase 56: saved WM state or NULL */
int wm_state_n; /* Phase 56: element count */
char **wm_command; /* Phase 57: WM_COMMAND argv or NULL */
@ -1390,6 +1391,23 @@ wmaker_popup_handle_destroy(struct wl_client *client,
wl_resource_destroy(resource);
}
static void
wmaker_popup_handle_set_parent(struct wl_client *client,
struct wl_resource *resource,
struct wl_resource *parent_resource)
{
(void)client;
struct wl_toplevel_view *view = wl_resource_get_user_data(resource);
if (!view) return;
if (parent_resource) {
view->popup_parent = wlr_xdg_toplevel_from_resource(parent_resource);
} else {
view->popup_parent = NULL;
}
wwarning("wmaker_popup: set_parent view=%p parent=%p", (void*)view,
(void*)view->popup_parent);
}
static void
wmaker_popup_handle_set_position(struct wl_client *client,
struct wl_resource *resource,
@ -1406,6 +1424,7 @@ wmaker_popup_handle_set_position(struct wl_client *client,
static const struct wmaker_popup_v1_interface wmaker_popup_impl = {
.destroy = wmaker_popup_handle_destroy,
.set_parent = wmaker_popup_handle_set_parent,
.set_position = wmaker_popup_handle_set_position,
};
@ -1422,14 +1441,27 @@ wmaker_popup_apply(struct wl_toplevel_view *view)
{
if (!view || !view->popup) return;
int dx = view->popup_x - view->x;
int dy = view->popup_y - view->y;
/* If a parent was set, translate the requested position into screen
* coordinates by adding the parent toplevel's current position. */
int target_x = view->popup_x;
int target_y = view->popup_y;
if (view->popup_parent) {
struct wl_toplevel_view *parent_view =
wl_find_view_by_xdg_surface(view->popup_parent->base);
if (parent_view) {
target_x += parent_view->x;
target_y += parent_view->y;
}
}
int dx = target_x - view->x;
int dy = target_y - view->y;
wwarning("wmaker_popup_apply: view=%p (%d,%d)->(%d,%d) frame_id=0x%lx scene_node=%p",
(void*)view, view->x, view->y, view->popup_x, view->popup_y,
(void*)view, view->x, view->y, target_x, target_y,
(unsigned long)view->frame_id, (void*)view->scene_node);
view->x = view->popup_x;
view->y = view->popup_y;
view->x = target_x;
view->y = target_y;
/* If a frame_buf wraps this view, move it via the WM's window-move
* path so the frame and its tree node update consistently. */