Implement popup-style toplevels for menu editor on Wayland

The WPrefs menu editor creates standalone draggable windows for each
menu item via W_CreateTopView. On X11 these are override-redirect
windows. On Wayland they were becoming framed XDG toplevels with
taskbar entries, cluttering the desktop with phantom 'WINGs...' tabs.

Implementation:
1. New W_View flags.popup field, set by editmenu's standalone mode
2. wclient_wl: when realizing a popup view, set xdg_toplevel app_id
   to 'wings-popup' as a hint to the compositor
3. wmaker compositor: detect 'wings-popup' app_id in toplevel_map,
   set borderless + skip-taskbar + skip-switchpanel flags, then call
   wWindowConfigureBorders to rebuild the frame without decorations
4. New W_EventBackend->window_begin_move vtable method:
   - X11: returns False (caller runs manual drag loop)
   - Wayland: calls xdg_toplevel_move, returns True (compositor drives drag)
5. editmenu's dragMenu calls window_begin_move first; on Wayland the
   compositor handles the entire drag, so the manual loop is skipped
This commit is contained in:
Window Maker 2026-05-24 17:58:58 +02:00
parent e01dedb833
commit 04e6b348b2
6 changed files with 92 additions and 8 deletions

View File

@ -495,6 +495,10 @@ typedef struct W_EventBackend {
int *x, int *y,
unsigned int *width, unsigned int *height);
void (*move_window)(void *ectx, WNativeWindow window, int x, int y);
/* Begin interactive window move (Wayland: xdg_toplevel_move).
* Returns True if the backend will handle the move, False if the
* caller should run its own drag loop (X11). */
Bool (*window_begin_move)(void *ectx, WNativeWindow window);
WNativeWindow (*translate_coordinates)(void *ectx,
WNativeWindow src, WNativeWindow dst,
int src_x, int src_y,
@ -1024,6 +1028,7 @@ struct W_View {
unsigned int root:1; /* is the root window */
unsigned int mapWhenRealized:1; /* map the view when it's realized */
unsigned int alreadyDead:1; /* view was freed */
unsigned int popup:1; /* popup-style toplevel (no frame, no taskbar) */
unsigned int dontCompressMotion:1; /* motion notify event compress */
unsigned int notifySizeChanged:1;

View File

@ -450,8 +450,13 @@ wl_client_toplevel_realize(struct W_View *view, int width, int height)
/* Default app_id; the application can override via xdg_toplevel_set_app_id.
* WPrefs and friends ought to call WMSetApplicationIconImage / set_title;
* we'll plumb that next. */
xdg_toplevel_set_app_id(top->xdg_toplevel, "WINGs");
xdg_toplevel_set_title(top->xdg_toplevel, "WINGs Application");
if (view->flags.popup) {
xdg_toplevel_set_app_id(top->xdg_toplevel, "wings-popup");
xdg_toplevel_set_title(top->xdg_toplevel, "");
} else {
xdg_toplevel_set_app_id(top->xdg_toplevel, "WINGs");
xdg_toplevel_set_title(top->xdg_toplevel, "WINGs Application");
}
/* Initial commit with no buffer so the compositor sends the first
* configure. We attach a real buffer in the configure handler. */
@ -510,6 +515,25 @@ W_WLClientSetMinMaxSize(W_View *view)
wl_surface_commit(top->surface);
}
Bool
W_WLClientBeginMove(WNativeWindow window)
{
extern uint32_t wlc_ptr_button_serial;
extern W_View *wlc_ptr_grab;
extern unsigned int wlc_ptr_button_state;
WLClientCtx *ctx = g_wlclient_ctx;
WLClientToplevel *top = wlc_top_find(window);
if (!ctx || !ctx->seat || !top || !top->xdg_toplevel) return False;
if (wlc_ptr_button_serial == 0) return False;
xdg_toplevel_move(top->xdg_toplevel, ctx->seat, wlc_ptr_button_serial);
/* Release implicit grab — the compositor now owns the input until
* the user releases the button. */
wlc_ptr_grab = NULL;
/* Clear pressed state so widgets don't think the button is still down */
wlc_ptr_button_state = 0;
return True;
}
/* ------------------------------------------------------------------ */
/* Input: wl_pointer / wl_keyboard listeners */
/* ------------------------------------------------------------------ */
@ -540,11 +564,12 @@ wlc_xkb_mod_mask(struct xkb_state *state)
/* Pointer state */
static double wlc_ptr_x, wlc_ptr_y;
static uint32_t wlc_ptr_focus_serial;
uint32_t wlc_ptr_button_serial; /* serial of most recent button press */
static WLClientToplevel *wlc_ptr_focused;
static unsigned int wlc_ptr_button_state;
unsigned int wlc_ptr_button_state;
static unsigned int wlc_ptr_mods;
static W_View *wlc_ptr_child_focus;
static W_View *wlc_ptr_grab; /* implicit grab: deliver events here while button held */
W_View *wlc_ptr_grab; /* implicit grab: deliver events here while button held */
/*
* Hit-test: find the deepest child view at (x,y) relative to the toplevel,
@ -706,9 +731,11 @@ static void
pointer_handle_button(void *data, struct wl_pointer *p, uint32_t serial,
uint32_t time, uint32_t button, uint32_t state)
{
(void)p; (void)serial;
(void)p;
WLClientCtx *ctx = data;
if (!wlc_ptr_focused) return;
if (state == WL_POINTER_BUTTON_STATE_PRESSED)
wlc_ptr_button_serial = serial;
/* Linux input event codes -> X-style button numbers. */
unsigned int wmb;

View File

@ -219,6 +219,16 @@ static Bool wl_get_window_geometry(void *ectx, WNativeWindow w,
static void wl_move_window(void *ectx, WNativeWindow w, int x, int y)
{ (void)ectx; (void)w; (void)x; (void)y; }
/* Implemented in wclient_wl.c — calls xdg_toplevel_move on the client
* connection so the compositor handles the drag. */
extern Bool W_WLClientBeginMove(WNativeWindow window);
static Bool wl_window_begin_move(void *ectx, WNativeWindow window)
{
(void)ectx;
return W_WLClientBeginMove(window);
}
static WNativeWindow wl_translate_coordinates(void *ectx,
WNativeWindow src, WNativeWindow dst, int sx, int sy, int *dx, int *dy)
{ (void)ectx; (void)src; (void)dst; (void)sx; (void)sy;
@ -317,6 +327,7 @@ const W_EventBackend W_EventBackendWL = {
.query_pointer = wl_query_pointer,
.get_window_geometry = wl_get_window_geometry,
.move_window = wl_move_window,
.window_begin_move = wl_window_begin_move,
.translate_coordinates = wl_translate_coordinates,
.get_root_window = wl_get_root_window,
.query_tree = wl_query_tree,

View File

@ -435,6 +435,14 @@ x11_move_window(void *ectx, WNativeWindow window, int x, int y)
XMoveWindow(ctx->dpy, (Window)window, x, y);
}
static Bool
x11_window_begin_move(void *ectx, WNativeWindow window)
{
(void)ectx; (void)window;
/* X11 uses manual drag loop with grab_pointer; no backend assist. */
return False;
}
static WNativeWindow
x11_translate_coordinates(void *ectx, WNativeWindow src, WNativeWindow dst,
int src_x, int src_y, int *dst_x, int *dst_y)
@ -759,6 +767,7 @@ const W_EventBackend W_EventBackendX11 = {
.query_pointer = x11_query_pointer,
.get_window_geometry = x11_get_window_geometry,
.move_window = x11_move_window,
.window_begin_move = x11_window_begin_move,
.translate_coordinates = x11_translate_coordinates,
.get_root_window = x11_get_root_window,
.query_tree = x11_query_tree,

View File

@ -383,6 +383,8 @@ static WEditMenu *makeEditMenu(WMScreen * scr, WMWidget * parent, const char *ti
} else {
mPtr->view = W_CreateTopView(scr);
mPtr->flags.standalone = 1;
if (mPtr->view)
mPtr->view->flags.popup = 1;
}
if (!mPtr->view) {
wfree(mPtr);
@ -1050,6 +1052,17 @@ static void dragMenu(WEditMenu * menu)
unsigned blau;
WNativeWindow blaw;
if (menu->parent)
WTearOffEditMenu(menu->parent, menu);
/* Wayland: ask compositor to drive the move, then return. X11:
* fall through to manual drag loop. */
if (scr->eventBackend->window_begin_move
&& scr->eventBackend->window_begin_move(scr->eventBackendCtx,
W_VIEW_DRAWABLE(menu->view))) {
return;
}
scr->eventBackend->get_window_geometry(scr->eventBackendCtx,
W_VIEW_DRAWABLE(menu->view), &dx, &dy, &blau, &blau);
@ -1060,9 +1073,6 @@ static void dragMenu(WEditMenu * menu)
scr->eventBackend->grab_pointer(scr->eventBackendCtx, scr->rootWin, scr->defaultCursor);
if (menu->parent)
WTearOffEditMenu(menu->parent, menu);
while (!done) {
WMNextEvent(scr, &ev);

View File

@ -1138,12 +1138,34 @@ handle_toplevel_map(struct wl_listener *listener, void *data)
view->xdg_surface->toplevel->app_id ? view->xdg_surface->toplevel->app_id : "(none)",
view->xdg_surface->toplevel->title ? view->xdg_surface->toplevel->title : "(none)");
/* Detect popup-style WINGs windows (menu editor items, etc.) — these
* need borderless presentation, no taskbar entry, and free positioning. */
const char *app_id_check = view->xdg_surface->toplevel->app_id;
int is_popup = (app_id_check && strcmp(app_id_check, "wings-popup") == 0);
/* Phase 39/41: register the surface as a managed window. */
{
WScreen *scr = wScreenWithNumber(0);
if (scr) {
WWindow *wwin = wManageWindow(scr, view->id);
if (wwin) {
if (is_popup) {
/* Set borderless + skip-taskbar attributes */
wwin->client_flags.no_border = 1;
wwin->client_flags.no_titlebar = 1;
wwin->client_flags.no_closable = 1;
wwin->client_flags.no_miniaturizable = 1;
wwin->client_flags.no_resizable = 1;
wwin->client_flags.no_close_button = 1;
wwin->client_flags.no_miniaturize_button = 1;
wwin->client_flags.no_resizebar = 1;
wwin->client_flags.skip_window_list = 1;
wwin->client_flags.skip_switchpanel = 1;
wwin->user_flags = wwin->client_flags;
/* Rebuild frame without decorations */
extern void wWindowConfigureBorders(WWindow *wwin);
wWindowConfigureBorders(wwin);
}
int pid = wl_client_get_pid(view->id);
/* Phase 62: focus delivered by wManageWindow via wSetFocusTo
* (auto_focus=1 set in wl_screen_create_internals); the