From 053ed4e6b318a3042167f643c24ef6fcfec30c95 Mon Sep 17 00:00:00 2001 From: Window Maker Date: Tue, 2 Jun 2026 16:05:18 +0200 Subject: [PATCH] decompose wl_backend.c: extract wl_xdg.c --- src/Makefile.am | 1 + src/backend/wayland/wl_backend.c | 558 +---------------------------- src/backend/wayland/wl_xdg.c | 591 +++++++++++++++++++++++++++++++ 3 files changed, 594 insertions(+), 556 deletions(-) create mode 100644 src/backend/wayland/wl_xdg.c diff --git a/src/Makefile.am b/src/Makefile.am index 25048d3..aa49f39 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -190,6 +190,7 @@ wmaker_SOURCES += \ backend/wayland/wl_grab.c \ backend/wayland/wl_keyboard.c \ backend/wayland/wl_popup.c \ + backend/wayland/wl_xdg.c \ backend/wayland/wl_xwayland.c \ backend/wayland/wl_dock.c \ backend/wayland/wl_input_fwd.c diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 7d95eed..41a1263 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -338,6 +338,8 @@ static void handle_virtual_keyboard_new(struct wl_listener *listener, void *data static void handle_xdg_activation_request(struct wl_listener *listener, void *data); /* Phase 36: layer shell new_surface handler. */ static void handle_layer_shell_new_surface(struct wl_listener *listener, void *data); +extern void handle_xdg_new_toplevel(struct wl_listener *listener, void *data); +extern void handle_new_toplevel_decoration(struct wl_listener *listener, void *data); /* Phase 37: virtual pointer handler. */ static void handle_virtual_pointer_new(struct wl_listener *listener, void *data); /* Phase 17: XWayland new surface forward declaration. */ @@ -360,562 +362,6 @@ extern void wl_grab_begin_resize(struct wl_toplevel_view *view, uint32_t edges); extern void wl_grab_end(void); extern const char *resize_edges_to_cursor(uint32_t edges); -/* Phase 41: look up the view for a given synthetic Window ID. */ -struct wl_toplevel_view * -wl_find_view_by_id(WNativeWindow id) -{ - struct wl_toplevel_view *v; - wl_list_for_each(v, &wl_state.toplevel_list, link) { - if (v->id == id) - return v; - } - return NULL; -} - -/* Phase 65: look up the view for a given wlr_xdg_surface pointer. */ -struct wl_toplevel_view * -wl_find_view_by_xdg_surface(struct wlr_xdg_surface *surf) -{ - struct wl_toplevel_view *v; - wl_list_for_each(v, &wl_state.toplevel_list, link) { - if (v->xdg_surface == surf) - return v; - } - return NULL; -} - -/* - * Phase 46: XDG toplevel title changed — update wwin->wm_instance. - */ -static void -handle_toplevel_title(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, title_listener); - (void)data; - const char *new_title = view->xdg_surface->toplevel->title; - - WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); - if (!desc || desc->parent_type != WCLASS_WINDOW) - return; - WWindow *wwin = (WWindow *)desc->parent; - - if (wwin->wm_instance) - wfree(wwin->wm_instance); - wwin->wm_instance = new_title ? strdup(new_title) : NULL; - - if (wwin->wm_class) - wfree(wwin->wm_class); - const char *app_id = (view->xdg_surface->toplevel->app_id) - ? view->xdg_surface->toplevel->app_id : "wayland-client"; - wwin->wm_class = strdup(app_id); - - wWindowUpdateName(wwin, new_title); -} - -/* - * Phase 23: set the cursor image by name from the loaded xcursor theme. - * No-op if cursor or xcursor_mgr was not initialised. - */ -void -wl_set_cursor_image(const char *name) -{ - if (!wl_state.cursor || !wl_state.xcursor_mgr) - return; - wlr_cursor_set_xcursor(wl_state.cursor, wl_state.xcursor_mgr, name); -} - - - -/* - * Phase 15/22: xdg_toplevel request_move — client requests interactive move. - * Phase 22: starts a compositor-side move grab. - */ -static void -handle_toplevel_request_move(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_move_listener); - (void)data; - wl_grab_begin_move(view); -} - -/* - * Phase 15/22: xdg_toplevel request_resize — client requests interactive resize. - * Phase 22: starts a compositor-side resize grab. - */ -static void -handle_toplevel_request_resize(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_resize_listener); - struct wlr_xdg_toplevel_resize_event *ev = data; - wl_grab_begin_resize(view, ev ? ev->edges : WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT); -} - -/* - * Phase 16: xdg_toplevel request_maximize — toggle maximize state. - */ -static void -handle_toplevel_request_maximize(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_maximize_listener); - (void)data; - WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); - if (!desc || desc->parent_type != WCLASS_WINDOW) - return; - WWindow *wwin = (WWindow *)desc->parent; - if (wwin->flags.maximized) - wUnmaximizeWindow(wwin); - else - wMaximizeWindow(wwin, MAX_VERTICAL | MAX_HORIZONTAL, wGetHeadForWindow(wwin)); -} - -/* - * Phase 16: xdg_toplevel request_fullscreen. - */ -static void -handle_toplevel_request_fullscreen(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_fullscreen_listener); - (void)data; - WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); - if (!desc || desc->parent_type != WCLASS_WINDOW) - return; - WWindow *wwin = (WWindow *)desc->parent; - if (!wwin->flags.fullscreen) - wFullscreenWindow(wwin); -} - -/* - * Phase 16: xdg_toplevel request_minimize — iconify the window. - */ -static void -handle_toplevel_request_minimize(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_minimize_listener); - (void)data; - WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); - if (!desc || desc->parent_type != WCLASS_WINDOW) - return; - WWindow *wwin = (WWindow *)desc->parent; - if (!wwin->flags.miniaturized) - wIconifyWindow(wwin); -} - -/* - * Phase 16: xdg_toplevel set_app_id — update wm_class. - */ -static void -handle_toplevel_set_app_id(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, set_app_id_listener); - (void)data; - WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); - if (!desc || desc->parent_type != WCLASS_WINDOW) - return; - WWindow *wwin = (WWindow *)desc->parent; - const char *app_id = view->xdg_surface->toplevel->app_id; - if (!app_id) - return; - if (wwin->wm_class) - wfree(wwin->wm_class); - wwin->wm_class = strdup(app_id); -} - -/* - * Phase 38: toplevel map — surface is ready to be managed. - */ -void -handle_toplevel_scene_destroy(struct wl_listener *listener, void *data) -{ - (void)data; - struct wl_toplevel_view *view = - VIEW_FROM_LISTENER(listener, scene_destroy_listener); - /* wlroots is destroying view->scene_node (typically because its - * parent tree — the WMaker frame's fb->tree — is being destroyed - * as part of unmanage). NULL the pointer so the later - * handle_toplevel_destroy doesn't try to redestroy it. */ - view->scene_node = NULL; - wl_list_remove(&view->scene_destroy_listener.link); -} - -void -view_attach_scene_destroy_listener(struct wl_toplevel_view *view) -{ - if (!view || !view->scene_node) return; - view->scene_destroy_listener.notify = handle_toplevel_scene_destroy; - wl_signal_add(&view->scene_node->node.events.destroy, - &view->scene_destroy_listener); -} - -static void -handle_toplevel_map(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, map_listener); - (void)data; - - wwarning("wl_backend: toplevel mapped id=0x%lx app_id=%s title=%s", - view->id, - 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); - /* If the client requested a position via wmaker-popup-v1, - * apply it now that the frame is set up. */ - if (view->popup) - wmaker_popup_apply(view); - } - 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 - * direct wl_focus_set() call is no longer needed here. */ - - /* Phase 18: add XDG surface to the scene graph. - * - * ORDERING: wManageWindow (above) calls wl_client_reparent - * which sets view->frame_id but cannot reparent the scene - * node because it does not exist yet. We create it here - * and immediately do the deferred reparent if frame_id is - * already set, so the client surface ends up inside - * fb->tree and moves with it during stacking operations. - * Without this the client surface would sit as a flat - * sibling of fb->tree in WL_LAYER_NORMAL and CommitStacking - * raises would leave it behind the frame decorations of - * subsequently-opened windows. */ - if (wl_state.scene && !view->scene_node) { - struct wlr_scene_tree *_vp = wl_state.level_trees[WL_LAYER_NORMAL] - ? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree; - view->scene_node = wlr_scene_subsurface_tree_create( - _vp, view->xdg_surface->surface); - if (view->scene_node) { - view_attach_scene_destroy_listener(view); - /* Deferred reparent: move client surface under its - * frame grouping tree now that the node exists. */ - if (view->frame_id) { - struct wl_frame_buf *_pfb = frame_buf_find(view->frame_id); - if (_pfb && _pfb->tree) - wlr_scene_node_reparent(&view->scene_node->node, - _pfb->tree); - /* Position at relative offset within frame tree */ - wlr_scene_node_set_position(&view->scene_node->node, - view->frame_rel_x, view->frame_rel_y); - } else { - wlr_scene_node_set_position(&view->scene_node->node, - view->x, view->y); - } - } - } - - /* Phase 13: warp virtual pointer to the centre of the - * newly managed window so pointer focus is immediately - * delivered to its surface. The warp calls - * wl_update_pointer_focus() internally. */ - { - int cx = view->x + wwin->client.width / 2; - int cy = view->y + wwin->client.height / 2; - wl_pointer_warp_to(cx, cy); - } - - /* Phase 35: create a foreign toplevel handle so taskbars - * and docks (e.g. wlr-taskbar, waybar) can enumerate and - * control this window. */ - if (wl_state.foreign_toplevel_mgr && !view->ft_handle) { - view->ft_handle = - wlr_foreign_toplevel_handle_v1_create( - wl_state.foreign_toplevel_mgr); - if (view->ft_handle) { - const char *title = wwin->wm_class ? wwin->wm_class : ""; - const char *app_id = wwin->wm_instance ? wwin->wm_instance : ""; - wlr_foreign_toplevel_handle_v1_set_title(view->ft_handle, title); - wlr_foreign_toplevel_handle_v1_set_app_id(view->ft_handle, app_id); - if (wl_state.output) - wlr_foreign_toplevel_handle_v1_output_enter( - view->ft_handle, wl_state.output); - wlr_foreign_toplevel_handle_v1_set_activated(view->ft_handle, true); - } - } - - /* Phase 30+: add to dock icon list */ - wm_dock_app_add(view); - } - } - } -} - -/* - * Phase 40: toplevel unmap — surface hidden or destroyed; unmanage the window. - */ -static void -handle_toplevel_unmap(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, unmap_listener); - (void)data; - - wwarning("wl_backend: toplevel unmapped id=0x%lx", view->id); - - /* Phase 35: destroy the foreign toplevel handle so taskbars stop - * showing this window in their list. */ - if (view->ft_handle) { - wlr_foreign_toplevel_handle_v1_destroy(view->ft_handle); - view->ft_handle = NULL; - } - - /* Phase 30+: remove from dock icon list */ - wm_dock_app_remove(view->id); - - /* Phase 40: remove the WWindow from the WM's managed list. */ - WScreen *scr = wScreenWithNumber(0); - if (scr) { - WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); - if (desc && desc->parent_type == WCLASS_WINDOW) { - WWindow *wwin = (WWindow *)desc->parent; - wUnmanageWindow(wwin, False, True); - } - } -} - -/* - * Phase 38: toplevel destroy — free the view. - */ -static void -handle_toplevel_destroy(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, destroy_listener); - (void)data; - - /* Remove all listeners registered with xdg_surface / xdg_toplevel signals. - * Failure to remove a listener before freeing the view leaves a dangling - * pointer in wlroots' signal list; when wlroots later emits or cleans up - * those signals (e.g. during wl_display_destroy) it corrupts the heap. */ - wl_list_remove(&view->map_listener.link); - wl_list_remove(&view->unmap_listener.link); - wl_list_remove(&view->destroy_listener.link); - wl_list_remove(&view->title_listener.link); - wl_list_remove(&view->request_move_listener.link); - wl_list_remove(&view->request_resize_listener.link); - wl_list_remove(&view->request_maximize_listener.link); - wl_list_remove(&view->request_fullscreen_listener.link); - wl_list_remove(&view->request_minimize_listener.link); - wl_list_remove(&view->set_app_id_listener.link); - wl_list_remove(&view->commit_listener.link); - wl_list_remove(&view->surface_commit_listener.link); - wl_list_remove(&view->link); - /* Phase 18: remove scene node. */ - if (view->scene_node) { - /* Remove our destroy listener BEFORE destroying so the listener - * doesn't fire and try to remove itself from a list we're - * already cleaning up. */ - wl_list_remove(&view->scene_destroy_listener.link); - wlr_scene_node_destroy(&view->scene_node->node); - view->scene_node = NULL; - } - wl_state.managed_surface_count--; - if (view->wm_state) wfree(view->wm_state); /* Phase 56 */ - if (view->wm_command) { /* Phase 57 */ - int ci; - for (ci = 0; ci < view->wm_command_n; ci++) - wfree(view->wm_command[ci]); - wfree(view->wm_command); - } - wfree(view); -} - -/* - * Phase 64: new_toplevel_decoration listener. - * Respond with CLIENT_SIDE so clients draw their own decorations. - * Must defer until the surface is initialized (wlroots 0.19). - */ -struct wl_deferred_decoration { - struct wl_listener commit_listener; - struct wlr_xdg_toplevel_decoration_v1 *deco; -}; - -static void -handle_deferred_decoration_commit(struct wl_listener *listener, void *data) -{ - struct wl_deferred_decoration *dd = - wl_container_of(listener, dd, commit_listener); - struct wlr_xdg_surface *surface = dd->deco->toplevel->base; - (void)data; - - if (!surface->initial_commit) - return; - - wl_list_remove(&dd->commit_listener.link); - wlr_xdg_toplevel_decoration_v1_set_mode(dd->deco, - WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); - wfree(dd); -} - -static void -handle_new_toplevel_decoration(struct wl_listener *listener, void *data) -{ - struct wlr_xdg_toplevel_decoration_v1 *deco = data; - (void)listener; - - struct wlr_xdg_surface *surface = deco->toplevel->base; - if (surface->initialized) { - wlr_xdg_toplevel_decoration_v1_set_mode(deco, - WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); - } else { - struct wl_deferred_decoration *dd = wmalloc(sizeof(*dd)); - dd->deco = deco; - dd->commit_listener.notify = handle_deferred_decoration_commit; - wl_signal_add(&surface->surface->events.commit, &dd->commit_listener); - } -} - - - -/* - * handle_xdg_toplevel_initial_commit -- wlroots 0.19 requires that the - * initial configure is sent in response to the client's first (empty) commit, - * not at new_surface time. The surface is only "initialized" after this - * commit, so schedule_configure is safe here. - */ -static void -handle_xdg_toplevel_initial_commit(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = - wl_container_of(listener, view, commit_listener); - (void)data; - - if (!view->xdg_surface || !view->xdg_surface->initial_commit) - return; - - /* Only need this once — remove ourselves. */ - wl_list_remove(&view->commit_listener.link); - wl_list_init(&view->commit_listener.link); - - /* Send initial configure so the client enters the configure/ack cycle. - * Size 0,0 means "choose your own size" per the XDG spec. */ - wlr_xdg_toplevel_set_activated(view->xdg_surface->toplevel, true); - wlr_xdg_toplevel_set_size(view->xdg_surface->toplevel, 0, 0); - wlr_xdg_surface_schedule_configure(view->xdg_surface); -} - -static void -handle_toplevel_commit(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = - wl_container_of(listener, view, surface_commit_listener); - (void)data; - - /* Ensure client scene_node is parented under frame tree */ - if (view->scene_node && view->frame_id) { - struct wl_frame_buf *ffb = frame_buf_find(view->frame_id); - if (ffb && ffb->tree && view->scene_node->node.parent != ffb->tree) { - wlr_scene_node_reparent(&view->scene_node->node, ffb->tree); - wlr_scene_node_set_position(&view->scene_node->node, - view->frame_rel_x, view->frame_rel_y); - } - } - - /* When the client commits a buffer at a new size (e.g. after interactive - * resize), update the WM frame to match. */ - if (view->xdg_surface && view->xdg_surface->surface && view->frame_id) { - int cw = view->xdg_surface->surface->current.width; - int ch = view->xdg_surface->surface->current.height; - if (cw > 0 && ch > 0) { - WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); - if (desc && desc->parent_type == WCLASS_WINDOW) { - WWindow *wwin = (WWindow *)desc->parent; - if (wwin->frame && - (cw != wwin->client.width || ch != wwin->client.height)) { - wwin->client.width = cw; - wwin->client.height = ch; - int h = ch + wwin->frame->top_width + wwin->frame->bottom_width; - wFrameWindowResize(wwin->frame, cw, h); - } - } - } - } - - if (wl_state.output) - wlr_output_schedule_frame(wl_state.output); -} - -/* - * Phase 36 / 38: new XDG toplevel listener (wlroots 0.19+). - * Fires when the client calls get_toplevel on an xdg_surface. - * Allocates a wl_toplevel_view, assigns a synthetic Window ID, - * wires lifecycle listeners, and sets up the initial commit handler. - */ -static void -handle_xdg_new_toplevel(struct wl_listener *listener, void *data) -{ - struct wlr_xdg_toplevel *toplevel = data; - struct wlr_xdg_surface *xdg_surface = toplevel->base; - struct wl_toplevel_view *view; - (void)listener; - - view = wmalloc(sizeof(*view)); - memset(view, 0, sizeof(*view)); - view->xdg_surface = xdg_surface; - view->id = wl_state.next_synthetic_id++; - - view->map_listener.notify = handle_toplevel_map; - view->unmap_listener.notify = handle_toplevel_unmap; - view->destroy_listener.notify = handle_toplevel_destroy; - view->title_listener.notify = handle_toplevel_title; - view->request_move_listener.notify = handle_toplevel_request_move; - view->request_resize_listener.notify = handle_toplevel_request_resize; - view->request_maximize_listener.notify = handle_toplevel_request_maximize; - view->request_fullscreen_listener.notify = handle_toplevel_request_fullscreen; - view->request_minimize_listener.notify = handle_toplevel_request_minimize; - view->set_app_id_listener.notify = handle_toplevel_set_app_id; - - wl_signal_add(&xdg_surface->surface->events.map, &view->map_listener); - wl_signal_add(&xdg_surface->surface->events.unmap, &view->unmap_listener); - wl_signal_add(&toplevel->events.destroy, &view->destroy_listener); - wl_signal_add(&toplevel->events.set_title, &view->title_listener); - wl_signal_add(&toplevel->events.request_move, &view->request_move_listener); - wl_signal_add(&toplevel->events.request_resize, &view->request_resize_listener); - wl_signal_add(&toplevel->events.request_maximize, &view->request_maximize_listener); - wl_signal_add(&toplevel->events.request_fullscreen, &view->request_fullscreen_listener); - wl_signal_add(&toplevel->events.request_minimize, &view->request_minimize_listener); - wl_signal_add(&toplevel->events.set_app_id, &view->set_app_id_listener); - - /* wlroots 0.19: send initial configure on the client's first commit, - * when the surface becomes initialized. */ - view->commit_listener.notify = handle_xdg_toplevel_initial_commit; - wl_signal_add(&xdg_surface->surface->events.commit, &view->commit_listener); - - view->surface_commit_listener.notify = handle_toplevel_commit; - wl_signal_add(&xdg_surface->surface->events.commit, &view->surface_commit_listener); - - wl_list_insert(&wl_state.toplevel_list, &view->link); - wl_state.managed_surface_count++; - - wwarning("wl_backend: new XDG toplevel id=0x%lx app_id=%s title=%s", - view->id, - toplevel->app_id ? toplevel->app_id : "(none)", - toplevel->title ? toplevel->title : "(none)"); -} diff --git a/src/backend/wayland/wl_xdg.c b/src/backend/wayland/wl_xdg.c new file mode 100644 index 0000000..645d95a --- /dev/null +++ b/src/backend/wayland/wl_xdg.c @@ -0,0 +1,591 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../screen.h" +#include "window.h" +#include "wl_types.h" +#include "../../actions.h" +#include "../../xinerama.h" +#include "../../framewin.h" +#include + +/* Forward declarations for functions defined in other wl_*.c files */ +void view_attach_scene_destroy_listener(struct wl_toplevel_view *view); +void wl_pointer_warp_to(int x, int y); +void wm_dock_app_add(struct wl_toplevel_view *v); +int wl_client_get_pid(WNativeWindow win); +void wmaker_popup_apply(struct wl_toplevel_view *view); +void wl_grab_begin_move(struct wl_toplevel_view *view); +void wl_grab_begin_resize(struct wl_toplevel_view *view, uint32_t edges); +void wm_dock_app_remove(WNativeWindow id); + + +#define VIEW_FROM_LISTENER(ptr, field) ((struct wl_toplevel_view *)((char *)(ptr) - offsetof(struct wl_toplevel_view, field))) + +/* Phase 41: look up the view for a given synthetic Window ID. */ +struct wl_toplevel_view * +wl_find_view_by_id(WNativeWindow id) +{ + struct wl_toplevel_view *v; + wl_list_for_each(v, &wl_state.toplevel_list, link) { + if (v->id == id) + return v; + } + return NULL; +} + +/* Phase 65: look up the view for a given wlr_xdg_surface pointer. */ +struct wl_toplevel_view * +wl_find_view_by_xdg_surface(struct wlr_xdg_surface *surf) +{ + struct wl_toplevel_view *v; + wl_list_for_each(v, &wl_state.toplevel_list, link) { + if (v->xdg_surface == surf) + return v; + } + return NULL; +} + +/* + * Phase 46: XDG toplevel title changed — update wwin->wm_instance. + */ +static void +handle_toplevel_title(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, title_listener); + (void)data; + const char *new_title = view->xdg_surface->toplevel->title; + + WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (!desc || desc->parent_type != WCLASS_WINDOW) + return; + WWindow *wwin = (WWindow *)desc->parent; + + if (wwin->wm_instance) + wfree(wwin->wm_instance); + wwin->wm_instance = new_title ? strdup(new_title) : NULL; + + if (wwin->wm_class) + wfree(wwin->wm_class); + const char *app_id = (view->xdg_surface->toplevel->app_id) + ? view->xdg_surface->toplevel->app_id : "wayland-client"; + wwin->wm_class = strdup(app_id); + + wWindowUpdateName(wwin, new_title); +} + +/* + * Phase 23: set the cursor image by name from the loaded xcursor theme. + * No-op if cursor or xcursor_mgr was not initialised. + */ +void +wl_set_cursor_image(const char *name) +{ + if (!wl_state.cursor || !wl_state.xcursor_mgr) + return; + wlr_cursor_set_xcursor(wl_state.cursor, wl_state.xcursor_mgr, name); +} + + + +/* + * Phase 15/22: xdg_toplevel request_move — client requests interactive move. + * Phase 22: starts a compositor-side move grab. + */ +static void +handle_toplevel_request_move(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_move_listener); + (void)data; + wl_grab_begin_move(view); +} + +/* + * Phase 15/22: xdg_toplevel request_resize — client requests interactive resize. + * Phase 22: starts a compositor-side resize grab. + */ +static void +handle_toplevel_request_resize(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_resize_listener); + struct wlr_xdg_toplevel_resize_event *ev = data; + wl_grab_begin_resize(view, ev ? ev->edges : WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT); +} + +/* + * Phase 16: xdg_toplevel request_maximize — toggle maximize state. + */ +static void +handle_toplevel_request_maximize(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_maximize_listener); + (void)data; + WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (!desc || desc->parent_type != WCLASS_WINDOW) + return; + WWindow *wwin = (WWindow *)desc->parent; + if (wwin->flags.maximized) + wUnmaximizeWindow(wwin); + else + wMaximizeWindow(wwin, MAX_VERTICAL | MAX_HORIZONTAL, wGetHeadForWindow(wwin)); +} + +/* + * Phase 16: xdg_toplevel request_fullscreen. + */ +static void +handle_toplevel_request_fullscreen(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_fullscreen_listener); + (void)data; + WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (!desc || desc->parent_type != WCLASS_WINDOW) + return; + WWindow *wwin = (WWindow *)desc->parent; + if (!wwin->flags.fullscreen) + wFullscreenWindow(wwin); +} + +/* + * Phase 16: xdg_toplevel request_minimize — iconify the window. + */ +static void +handle_toplevel_request_minimize(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_minimize_listener); + (void)data; + WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (!desc || desc->parent_type != WCLASS_WINDOW) + return; + WWindow *wwin = (WWindow *)desc->parent; + if (!wwin->flags.miniaturized) + wIconifyWindow(wwin); +} + +/* + * Phase 16: xdg_toplevel set_app_id — update wm_class. + */ +static void +handle_toplevel_set_app_id(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, set_app_id_listener); + (void)data; + WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (!desc || desc->parent_type != WCLASS_WINDOW) + return; + WWindow *wwin = (WWindow *)desc->parent; + const char *app_id = view->xdg_surface->toplevel->app_id; + if (!app_id) + return; + if (wwin->wm_class) + wfree(wwin->wm_class); + wwin->wm_class = strdup(app_id); +} + +/* + * Phase 38: toplevel map — surface is ready to be managed. + */ +void +handle_toplevel_scene_destroy(struct wl_listener *listener, void *data) +{ + (void)data; + struct wl_toplevel_view *view = + VIEW_FROM_LISTENER(listener, scene_destroy_listener); + /* wlroots is destroying view->scene_node (typically because its + * parent tree — the WMaker frame's fb->tree — is being destroyed + * as part of unmanage). NULL the pointer so the later + * handle_toplevel_destroy doesn't try to redestroy it. */ + view->scene_node = NULL; + wl_list_remove(&view->scene_destroy_listener.link); +} + +void +view_attach_scene_destroy_listener(struct wl_toplevel_view *view) +{ + if (!view || !view->scene_node) return; + view->scene_destroy_listener.notify = handle_toplevel_scene_destroy; + wl_signal_add(&view->scene_node->node.events.destroy, + &view->scene_destroy_listener); +} + +static void +handle_toplevel_map(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, map_listener); + (void)data; + + wwarning("wl_backend: toplevel mapped id=0x%lx app_id=%s title=%s", + view->id, + 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); + /* If the client requested a position via wmaker-popup-v1, + * apply it now that the frame is set up. */ + if (view->popup) + wmaker_popup_apply(view); + } + 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 + * direct wl_focus_set() call is no longer needed here. */ + + /* Phase 18: add XDG surface to the scene graph. + * + * ORDERING: wManageWindow (above) calls wl_client_reparent + * which sets view->frame_id but cannot reparent the scene + * node because it does not exist yet. We create it here + * and immediately do the deferred reparent if frame_id is + * already set, so the client surface ends up inside + * fb->tree and moves with it during stacking operations. + * Without this the client surface would sit as a flat + * sibling of fb->tree in WL_LAYER_NORMAL and CommitStacking + * raises would leave it behind the frame decorations of + * subsequently-opened windows. */ + if (wl_state.scene && !view->scene_node) { + struct wlr_scene_tree *_vp = wl_state.level_trees[WL_LAYER_NORMAL] + ? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree; + view->scene_node = wlr_scene_subsurface_tree_create( + _vp, view->xdg_surface->surface); + if (view->scene_node) { + view_attach_scene_destroy_listener(view); + /* Deferred reparent: move client surface under its + * frame grouping tree now that the node exists. */ + if (view->frame_id) { + struct wl_frame_buf *_pfb = frame_buf_find(view->frame_id); + if (_pfb && _pfb->tree) + wlr_scene_node_reparent(&view->scene_node->node, + _pfb->tree); + /* Position at relative offset within frame tree */ + wlr_scene_node_set_position(&view->scene_node->node, + view->frame_rel_x, view->frame_rel_y); + } else { + wlr_scene_node_set_position(&view->scene_node->node, + view->x, view->y); + } + } + } + + /* Phase 13: warp virtual pointer to the centre of the + * newly managed window so pointer focus is immediately + * delivered to its surface. The warp calls + * wl_update_pointer_focus() internally. */ + { + int cx = view->x + wwin->client.width / 2; + int cy = view->y + wwin->client.height / 2; + wl_pointer_warp_to(cx, cy); + } + + /* Phase 35: create a foreign toplevel handle so taskbars + * and docks (e.g. wlr-taskbar, waybar) can enumerate and + * control this window. */ + if (wl_state.foreign_toplevel_mgr && !view->ft_handle) { + view->ft_handle = + wlr_foreign_toplevel_handle_v1_create( + wl_state.foreign_toplevel_mgr); + if (view->ft_handle) { + const char *title = wwin->wm_class ? wwin->wm_class : ""; + const char *app_id = wwin->wm_instance ? wwin->wm_instance : ""; + wlr_foreign_toplevel_handle_v1_set_title(view->ft_handle, title); + wlr_foreign_toplevel_handle_v1_set_app_id(view->ft_handle, app_id); + if (wl_state.output) + wlr_foreign_toplevel_handle_v1_output_enter( + view->ft_handle, wl_state.output); + wlr_foreign_toplevel_handle_v1_set_activated(view->ft_handle, true); + } + } + + /* Phase 30+: add to dock icon list */ + wm_dock_app_add(view); + } + } + } +} + +/* + * Phase 40: toplevel unmap — surface hidden or destroyed; unmanage the window. + */ +static void +handle_toplevel_unmap(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, unmap_listener); + (void)data; + + wwarning("wl_backend: toplevel unmapped id=0x%lx", view->id); + + /* Phase 35: destroy the foreign toplevel handle so taskbars stop + * showing this window in their list. */ + if (view->ft_handle) { + wlr_foreign_toplevel_handle_v1_destroy(view->ft_handle); + view->ft_handle = NULL; + } + + /* Phase 30+: remove from dock icon list */ + wm_dock_app_remove(view->id); + + /* Phase 40: remove the WWindow from the WM's managed list. */ + WScreen *scr = wScreenWithNumber(0); + if (scr) { + WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (desc && desc->parent_type == WCLASS_WINDOW) { + WWindow *wwin = (WWindow *)desc->parent; + wUnmanageWindow(wwin, False, True); + } + } +} + +/* + * Phase 38: toplevel destroy — free the view. + */ +static void +handle_toplevel_destroy(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, destroy_listener); + (void)data; + + /* Remove all listeners registered with xdg_surface / xdg_toplevel signals. + * Failure to remove a listener before freeing the view leaves a dangling + * pointer in wlroots' signal list; when wlroots later emits or cleans up + * those signals (e.g. during wl_display_destroy) it corrupts the heap. */ + wl_list_remove(&view->map_listener.link); + wl_list_remove(&view->unmap_listener.link); + wl_list_remove(&view->destroy_listener.link); + wl_list_remove(&view->title_listener.link); + wl_list_remove(&view->request_move_listener.link); + wl_list_remove(&view->request_resize_listener.link); + wl_list_remove(&view->request_maximize_listener.link); + wl_list_remove(&view->request_fullscreen_listener.link); + wl_list_remove(&view->request_minimize_listener.link); + wl_list_remove(&view->set_app_id_listener.link); + wl_list_remove(&view->commit_listener.link); + wl_list_remove(&view->surface_commit_listener.link); + wl_list_remove(&view->link); + /* Phase 18: remove scene node. */ + if (view->scene_node) { + /* Remove our destroy listener BEFORE destroying so the listener + * doesn't fire and try to remove itself from a list we're + * already cleaning up. */ + wl_list_remove(&view->scene_destroy_listener.link); + wlr_scene_node_destroy(&view->scene_node->node); + view->scene_node = NULL; + } + wl_state.managed_surface_count--; + if (view->wm_state) wfree(view->wm_state); /* Phase 56 */ + if (view->wm_command) { /* Phase 57 */ + int ci; + for (ci = 0; ci < view->wm_command_n; ci++) + wfree(view->wm_command[ci]); + wfree(view->wm_command); + } + wfree(view); +} + +/* + * Phase 64: new_toplevel_decoration listener. + * Respond with CLIENT_SIDE so clients draw their own decorations. + * Must defer until the surface is initialized (wlroots 0.19). + */ +struct wl_deferred_decoration { + struct wl_listener commit_listener; + struct wlr_xdg_toplevel_decoration_v1 *deco; +}; + +static void +handle_deferred_decoration_commit(struct wl_listener *listener, void *data) +{ + struct wl_deferred_decoration *dd = + wl_container_of(listener, dd, commit_listener); + struct wlr_xdg_surface *surface = dd->deco->toplevel->base; + (void)data; + + if (!surface->initial_commit) + return; + + wl_list_remove(&dd->commit_listener.link); + wlr_xdg_toplevel_decoration_v1_set_mode(dd->deco, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + wfree(dd); +} + +void +handle_new_toplevel_decoration(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_toplevel_decoration_v1 *deco = data; + (void)listener; + + struct wlr_xdg_surface *surface = deco->toplevel->base; + if (surface->initialized) { + wlr_xdg_toplevel_decoration_v1_set_mode(deco, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + } else { + struct wl_deferred_decoration *dd = wmalloc(sizeof(*dd)); + dd->deco = deco; + dd->commit_listener.notify = handle_deferred_decoration_commit; + wl_signal_add(&surface->surface->events.commit, &dd->commit_listener); + } +} + + + +/* + * handle_xdg_toplevel_initial_commit -- wlroots 0.19 requires that the + * initial configure is sent in response to the client's first (empty) commit, + * not at new_surface time. The surface is only "initialized" after this + * commit, so schedule_configure is safe here. + */ +static void +handle_xdg_toplevel_initial_commit(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = + wl_container_of(listener, view, commit_listener); + (void)data; + + if (!view->xdg_surface || !view->xdg_surface->initial_commit) + return; + + /* Only need this once — remove ourselves. */ + wl_list_remove(&view->commit_listener.link); + wl_list_init(&view->commit_listener.link); + + /* Send initial configure so the client enters the configure/ack cycle. + * Size 0,0 means "choose your own size" per the XDG spec. */ + wlr_xdg_toplevel_set_activated(view->xdg_surface->toplevel, true); + wlr_xdg_toplevel_set_size(view->xdg_surface->toplevel, 0, 0); + wlr_xdg_surface_schedule_configure(view->xdg_surface); +} + +static void +handle_toplevel_commit(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = + wl_container_of(listener, view, surface_commit_listener); + (void)data; + + /* Ensure client scene_node is parented under frame tree */ + if (view->scene_node && view->frame_id) { + struct wl_frame_buf *ffb = frame_buf_find(view->frame_id); + if (ffb && ffb->tree && view->scene_node->node.parent != ffb->tree) { + wlr_scene_node_reparent(&view->scene_node->node, ffb->tree); + wlr_scene_node_set_position(&view->scene_node->node, + view->frame_rel_x, view->frame_rel_y); + } + } + + /* When the client commits a buffer at a new size (e.g. after interactive + * resize), update the WM frame to match. */ + if (view->xdg_surface && view->xdg_surface->surface && view->frame_id) { + int cw = view->xdg_surface->surface->current.width; + int ch = view->xdg_surface->surface->current.height; + if (cw > 0 && ch > 0) { + WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (desc && desc->parent_type == WCLASS_WINDOW) { + WWindow *wwin = (WWindow *)desc->parent; + if (wwin->frame && + (cw != wwin->client.width || ch != wwin->client.height)) { + wwin->client.width = cw; + wwin->client.height = ch; + int h = ch + wwin->frame->top_width + wwin->frame->bottom_width; + wFrameWindowResize(wwin->frame, cw, h); + } + } + } + } + + if (wl_state.output) + wlr_output_schedule_frame(wl_state.output); +} + +/* + * Phase 36 / 38: new XDG toplevel listener (wlroots 0.19+). + * Fires when the client calls get_toplevel on an xdg_surface. + * Allocates a wl_toplevel_view, assigns a synthetic Window ID, + * wires lifecycle listeners, and sets up the initial commit handler. + */ +void +handle_xdg_new_toplevel(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_toplevel *toplevel = data; + struct wlr_xdg_surface *xdg_surface = toplevel->base; + struct wl_toplevel_view *view; + (void)listener; + + view = wmalloc(sizeof(*view)); + memset(view, 0, sizeof(*view)); + view->xdg_surface = xdg_surface; + view->id = wl_state.next_synthetic_id++; + + view->map_listener.notify = handle_toplevel_map; + view->unmap_listener.notify = handle_toplevel_unmap; + view->destroy_listener.notify = handle_toplevel_destroy; + view->title_listener.notify = handle_toplevel_title; + view->request_move_listener.notify = handle_toplevel_request_move; + view->request_resize_listener.notify = handle_toplevel_request_resize; + view->request_maximize_listener.notify = handle_toplevel_request_maximize; + view->request_fullscreen_listener.notify = handle_toplevel_request_fullscreen; + view->request_minimize_listener.notify = handle_toplevel_request_minimize; + view->set_app_id_listener.notify = handle_toplevel_set_app_id; + + wl_signal_add(&xdg_surface->surface->events.map, &view->map_listener); + wl_signal_add(&xdg_surface->surface->events.unmap, &view->unmap_listener); + wl_signal_add(&toplevel->events.destroy, &view->destroy_listener); + wl_signal_add(&toplevel->events.set_title, &view->title_listener); + wl_signal_add(&toplevel->events.request_move, &view->request_move_listener); + wl_signal_add(&toplevel->events.request_resize, &view->request_resize_listener); + wl_signal_add(&toplevel->events.request_maximize, &view->request_maximize_listener); + wl_signal_add(&toplevel->events.request_fullscreen, &view->request_fullscreen_listener); + wl_signal_add(&toplevel->events.request_minimize, &view->request_minimize_listener); + wl_signal_add(&toplevel->events.set_app_id, &view->set_app_id_listener); + + /* wlroots 0.19: send initial configure on the client's first commit, + * when the surface becomes initialized. */ + view->commit_listener.notify = handle_xdg_toplevel_initial_commit; + wl_signal_add(&xdg_surface->surface->events.commit, &view->commit_listener); + + view->surface_commit_listener.notify = handle_toplevel_commit; + wl_signal_add(&xdg_surface->surface->events.commit, &view->surface_commit_listener); + + wl_list_insert(&wl_state.toplevel_list, &view->link); + wl_state.managed_surface_count++; + + wwarning("wl_backend: new XDG toplevel id=0x%lx app_id=%s title=%s", + view->id, + toplevel->app_id ? toplevel->app_id : "(none)", + toplevel->title ? toplevel->title : "(none)"); +}