687 lines
27 KiB
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)");
|
|
}
|