windowmaker-wl/src/backend/wayland/wl_xdg.c

687 lines
27 KiB
C

#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 "../../application.h"
#include <WINGs/WINGsP.h>
/* Forward declarations for functions defined in other wl_*.c files */
extern void W_UnregisterBacking(unsigned long id);
extern W_View *W_GetViewForWindow(WNativeWindow id);
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)))
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;
}
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;
}
/*
* 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);
}
/*
* 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);
}
/*
* 22: xdg_toplevel request_move — client requests interactive move.
* 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);
}
/*
* 22: xdg_toplevel request_resize — client requests interactive resize.
* 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);
}
/*
* 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));
}
/*
* 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 (view->xdg_surface->toplevel->requested.fullscreen) {
if (!wwin->flags.fullscreen)
wFullscreenWindow(wwin);
} else {
if (wwin->flags.fullscreen)
wUnfullscreenWindow(wwin);
}
}
/*
* 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);
}
/*
* 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);
}
/*
* 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)");
/* WINGs/Wayland sets app_id="WINGs" for ALL windows (wclient_wl.c),
* not just dialogs. Only treat as "WINGs dialog" when we actually
* find a pre-created frame buffer from wl_client_reparent. Regular
* WINGs windows (WPrefs) must go through wManageWindow. */
const char *app_id_check = view->xdg_surface->toplevel->app_id;
int is_wings_dialog = 0;
int is_popup = (app_id_check && strcmp(app_id_check, "wings-popup") == 0);
if (app_id_check && strcmp(app_id_check, "WINGs") == 0) {
struct wl_frame_buf *_fb, *_next;
wl_list_for_each_safe(_fb, _next, &wl_state.frame_list, link) {
/* WINGs view windows and synthetic backing IDs both live in the
* 0x20000000+ range. A regular WINGs toplevel (like WPrefs) is
* already present in the view table by the time it maps, while a
* pre-created dialog backing from wl_client_reparent is not.
* Only steal the id for the latter case. */
if (_fb->id >= 0x20000000 && !W_GetViewForWindow(_fb->id)) {
/* Stale WINGs frame_buf from a previous session — clean it
* up if its scene buffer was auto-destroyed (parent tree was
* destroyed, cascade nulled the pointer) or if the parent
* frame no longer exists. */
if (!_fb->scene_buf ||
(_fb->parent_id && !frame_buf_find(_fb->parent_id))) {
W_UnregisterBacking(_fb->id);
wl_list_remove(&_fb->link);
wlr_buffer_drop(&_fb->base);
continue;
}
wwarning("wl_backend: WINGs dialog id 0x%lx", (unsigned long)_fb->id);
view->id = _fb->id;
view->frame_id = _fb->parent_id;
view->frame_rel_x = _fb->rel_x;
view->frame_rel_y = _fb->rel_y;
is_wings_dialog = 1;
break;
}
}
}
{
WScreen *scr = wScreenWithNumber(0);
WWindow *wwin = NULL;
if (scr) {
if (!is_wings_dialog) {
wwin = wManageWindow(scr, view->id);
if (wwin) {
if (is_popup) {
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;
extern void wWindowConfigureBorders(WWindow *wwin);
wWindowConfigureBorders(wwin);
if (view->popup)
wmaker_popup_apply(view);
}
int pid = wl_client_get_pid(view->id);
}
}
/* 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_xdg_surface_create(
_vp, view->xdg_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. */
struct wlr_box geo = view->xdg_surface->geometry;
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,
* adjusted for CSD geometry offset (shadows). */
wlr_scene_node_set_position(&view->scene_node->node,
view->frame_rel_x - geo.x,
view->frame_rel_y - geo.y);
} else {
wlr_scene_node_set_position(&view->scene_node->node,
view->x - geo.x,
view->y - geo.y);
}
}
}
/* 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. */
if (wwin) {
int cx = view->x + wwin->client.width / 2;
int cy = view->y + wwin->client.height / 2;
wl_pointer_warp_to(cx, cy);
}
/* create a foreign toplevel handle so taskbars
* and docks (e.g. wlr-taskbar, waybar) can enumerate and
* control this window. */
if (wwin && 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);
}
}
if (wwin)
wm_dock_app_add(view);
}
}
}
/*
* 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;
/* 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;
}
wm_dock_app_remove(view->id);
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;
WNativeWindow main_win = wwin->main_window;
wwin->flags.destroyed = 1;
/* Destroy app BEFORE unmanage so wApplicationDestroy can
* still access main_window_desc (which unmanage frees). */
WApplication *app = wApplicationOf(main_win);
if (app) {
app->refcount = 0;
wApplicationDestroy(app);
}
wUnmanageWindow(wwin, False, True);
}
}
}
/*
* 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);
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);
if (view->wm_command) {
int ci;
for (ci = 0; ci < view->wm_command_n; ci++)
wfree(view->wm_command[ci]);
wfree(view->wm_command);
}
/* Save frame_id before freeing view */
WNativeWindow frame_id = view->frame_id;
wfree(view);
/* Destroy the frame_buf for this window and any orphaned children.
* Without this, frame_bufs leak in wl_state.frame_list and on the
* next launch the WINGs dialog detection finds a stale 0x20000000+
* frame_buf, skips wManageWindow, and the window gets no titlebar. */
if (frame_id) {
struct wl_frame_buf *_fb, *_next;
wl_list_for_each_safe(_fb, _next, &wl_state.frame_list, link) {
if (_fb->id == frame_id || _fb->parent_id == frame_id) {
if (_fb->scene_buf)
wlr_scene_node_destroy(&_fb->scene_buf->node);
if (_fb->tree)
wlr_scene_node_destroy(&_fb->tree->node);
W_UnregisterBacking(_fb->id);
wl_list_remove(&_fb->link);
wlr_buffer_drop(&_fb->base);
}
}
}
}
/*
* 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 wl_listener destroy_listener;
struct wlr_xdg_toplevel_decoration_v1 *deco;
};
static void handle_deferred_decoration_destroy(struct wl_listener *listener, void *data);
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);
wl_list_remove(&dd->destroy_listener.link);
wlr_xdg_toplevel_decoration_v1_set_mode(dd->deco,
WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
wfree(dd);
}
static void
handle_deferred_decoration_destroy(struct wl_listener *listener, void *data)
{
struct wl_deferred_decoration *dd =
wl_container_of(listener, dd, destroy_listener);
(void)data;
wl_list_remove(&dd->commit_listener.link);
wl_list_remove(&dd->destroy_listener.link);
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);
dd->destroy_listener.notify = handle_deferred_decoration_destroy;
wl_signal_add(&deco->events.destroy, &dd->destroy_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);
}
}
/* Keep scene node offset in sync with CSD geometry (may change between commits) */
if (view->scene_node && view->xdg_surface) {
struct wlr_box geo = view->xdg_surface->geometry;
if (view->frame_id)
wlr_scene_node_set_position(&view->scene_node->node,
view->frame_rel_x - geo.x,
view->frame_rel_y - geo.y);
else
wlr_scene_node_set_position(&view->scene_node->node,
view->x - geo.x,
view->y - geo.y);
}
/* When the client commits a buffer at a new size (e.g. after interactive
* resize), update the WM frame to match. Use xdg geometry (excludes
* CSD shadows) when available, fall back to surface size. */
if (view->xdg_surface && view->xdg_surface->surface && view->frame_id) {
struct wlr_box geo = view->xdg_surface->geometry;
int cw = (geo.width > 0) ? geo.width : view->xdg_surface->surface->current.width;
int ch = (geo.height > 0) ? geo.height : 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);
}
/*
* 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)");
}