decompose wl_backend.c: extract wl_xdg.c

This commit is contained in:
Window Maker 2026-06-02 16:05:18 +02:00
parent ac4aa4fe4d
commit 053ed4e6b3
3 changed files with 594 additions and 556 deletions

View File

@ -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

View File

@ -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)");
}

View File

@ -0,0 +1,591 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#include "../../screen.h"
#include "window.h"
#include "wl_types.h"
#include "../../actions.h"
#include "../../xinerama.h"
#include "../../framewin.h"
#include <WINGs/WINGsP.h>
/* 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)");
}