toplevel-capture: partial initial implementation

Missing:
- xwayland child windows
- xwayland unmanaged windows, e.g. popups / menus / ...
- xdg child window positioning
- xdg subsurfaces (test-case: mate-terminal settings listboxes)
- xdg popup positioning
This commit is contained in:
Consolatis 2025-07-06 01:04:55 +02:00
parent 379f06b1e8
commit facf3856cb
8 changed files with 100 additions and 0 deletions

View file

@ -189,6 +189,13 @@ struct server {
struct wlr_xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager;
struct wl_listener xdg_toplevel_icon_set_icon;
struct {
struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *manager;
struct {
struct wl_listener new_request;
} on;
} toplevel_capture;
/* front to back order */
struct wl_list views;
uint64_t next_view_creation_id;

View file

@ -177,6 +177,12 @@ struct view {
char *title;
char *app_id; /* WM_CLASS for xwayland windows */
struct {
struct wlr_scene *scene;
struct wlr_ext_image_capture_source_v1 *source;
struct wl_listener on_capture_source_destroy;
} capture;
bool mapped;
bool been_mapped;
uint64_t creation_id;
@ -318,6 +324,7 @@ struct xdg_toplevel_view {
/* Events unique to xdg-toplevel views */
struct wl_listener set_app_id;
struct wl_listener request_show_window_menu;
struct wl_listener set_parent;
struct wl_listener new_popup;
};

View file

@ -75,6 +75,9 @@ ext_foreign_toplevel_init(struct ext_foreign_toplevel *ext_toplevel,
return;
}
/* In support for ext-toplevel-capture */
ext_toplevel->handle->data = view;
/* Client side requests */
ext_toplevel->on.handle_destroy.notify = handle_handle_destroy;
wl_signal_add(&ext_toplevel->handle->events.destroy, &ext_toplevel->on.handle_destroy);

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include "config.h"
#include <assert.h>
#include <signal.h>
#include <string.h>
#include <sys/wait.h>
@ -428,6 +429,39 @@ handle_renderer_lost(struct wl_listener *listener, void *data)
wlr_renderer_destroy(old_renderer);
}
static void
handle_toplevel_capture_source_destroy(struct wl_listener *listener, void *data)
{
struct view *view = wl_container_of(listener, view, capture.on_capture_source_destroy);
assert(view->capture.source);
view->capture.source = NULL;
wl_list_remove(&listener->link);
wl_list_init(&listener->link);
}
static void
handle_toplevel_capture_request(struct wl_listener *listener, void *data)
{
struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request = data;
struct view *view = request->toplevel_handle->data;
assert(view);
wlr_log(WLR_DEBUG, "Capturing toplevel %s", view->app_id);
if (!view->capture.source) {
view->capture.source = wlr_ext_image_capture_source_v1_create_with_scene_node(
&view->capture.scene->tree.node, server.wl_event_loop,
server.allocator, server.renderer);
assert(view->capture.source);
view->capture.on_capture_source_destroy.notify =
handle_toplevel_capture_source_destroy;
wl_signal_add(&view->capture.source->events.destroy,
&view->capture.on_capture_source_destroy);
}
wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept(
request, view->capture.source);
}
void
server_init(void)
{
@ -666,6 +700,19 @@ server_init(void)
wlr_screencopy_manager_v1_create(server.wl_display);
wlr_ext_image_copy_capture_manager_v1_create(server.wl_display, 1);
wlr_ext_output_image_capture_source_manager_v1_create(server.wl_display, 1);
server.toplevel_capture.manager =
wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(
server.wl_display, 1);
if (server.toplevel_capture.manager) {
server.toplevel_capture.on.new_request.notify = handle_toplevel_capture_request;
wl_signal_add(&server.toplevel_capture.manager->events.new_request,
&server.toplevel_capture.on.new_request);
} else {
/* Allow safe removal on shutdown */
wl_list_init(&server.toplevel_capture.on.new_request.link);
}
wlr_data_control_manager_v1_create(server.wl_display);
wlr_ext_data_control_manager_v1_create(server.wl_display,
LAB_EXT_DATA_CONTROL_VERSION);
@ -800,6 +847,8 @@ server_finish(void)
server.drm_lease_request.notify = NULL;
}
wl_list_remove(&server.toplevel_capture.on.new_request.link);
wlr_backend_destroy(server.backend);
wlr_allocator_destroy(server.allocator);

View file

@ -2482,6 +2482,10 @@ view_init(struct view *view)
view->title = xstrdup("");
view->app_id = xstrdup("");
view->capture.scene = wlr_scene_create();
view->capture.scene->restack_xwayland_surfaces = false;
wl_list_init(&view->capture.on_capture_source_destroy.link);
}
void
@ -2503,6 +2507,9 @@ view_destroy(struct view *view)
wl_list_remove(&view->request_fullscreen.link);
wl_list_remove(&view->set_title.link);
wl_list_remove(&view->destroy.link);
wl_list_remove(&view->capture.on_capture_source_destroy.link);
wlr_scene_node_destroy(&view->capture.scene->tree.node);
if (view->foreign_toplevel) {
foreign_toplevel_destroy(view->foreign_toplevel);

View file

@ -166,4 +166,6 @@ xdg_popup_create(struct view *view, struct wlr_xdg_popup *wlr_popup)
node_descriptor_create(wlr_popup->base->surface->data,
LAB_NODE_XDG_POPUP, view, /*data*/ NULL);
wlr_scene_xdg_surface_create(&view->capture.scene->tree, wlr_popup->base);
}

View file

@ -30,6 +30,8 @@
#define LAB_XDG_SHELL_VERSION 6
#define CONFIGURE_TIMEOUT_MS 100
static struct view *xdg_toplevel_view_get_root(struct view *view);
static struct xdg_toplevel_view *
xdg_toplevel_view_from_view(struct view *view)
{
@ -463,6 +465,7 @@ handle_destroy(struct wl_listener *listener, void *data)
/* Remove xdg-shell view specific listeners */
wl_list_remove(&xdg_toplevel_view->set_app_id.link);
wl_list_remove(&xdg_toplevel_view->request_show_window_menu.link);
wl_list_remove(&xdg_toplevel_view->set_parent.link);
wl_list_remove(&xdg_toplevel_view->new_popup.link);
wl_list_remove(&view->commit.link);
@ -566,6 +569,23 @@ handle_request_show_window_menu(struct wl_listener *listener, void *data)
menu_open_root(menu, cursor->x, cursor->y);
}
static void
handle_set_parent(struct wl_listener *listener, void *data)
{
struct xdg_toplevel_view *xdg_toplevel_view = wl_container_of(
listener, xdg_toplevel_view, set_parent);
struct view *view = &xdg_toplevel_view->base;
struct view *view_root = xdg_toplevel_view_get_root(view);
if (view_root == view) {
return;
}
struct wlr_scene_node *node, *tmp;
wl_list_for_each_safe(node, tmp, &view->capture.scene->tree.children, link) {
wlr_log(WLR_INFO, "moving capture scene node to view_root");
wlr_scene_node_reparent(node, &view_root->capture.scene->tree);
}
}
static void
handle_set_title(struct wl_listener *listener, void *data)
{
@ -1024,6 +1044,9 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data)
mappable_connect(&view->mappable, xdg_surface->surface,
handle_map, handle_unmap);
struct view *root_view = xdg_toplevel_view_get_root(view);
wlr_scene_xdg_surface_create(&root_view->capture.scene->tree, xdg_surface);
struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel;
CONNECT_SIGNAL(toplevel, view, destroy);
CONNECT_SIGNAL(toplevel, view, request_move);
@ -1037,6 +1060,7 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data)
/* Events specific to XDG toplevel views */
CONNECT_SIGNAL(toplevel, xdg_toplevel_view, set_app_id);
CONNECT_SIGNAL(toplevel, xdg_toplevel_view, request_show_window_menu);
CONNECT_SIGNAL(toplevel, xdg_toplevel_view, set_parent);
CONNECT_SIGNAL(xdg_surface, xdg_toplevel_view, new_popup);
wl_list_insert(&server.views, &view->link);

View file

@ -783,6 +783,7 @@ handle_map(struct wl_listener *listener, void *data)
view->content_tree = wlr_scene_subsurface_tree_create(
view->scene_tree, view->surface);
die_if_null(view->content_tree);
wlr_scene_subsurface_tree_create(&view->capture.scene->tree, view->surface);
}
wlr_scene_node_set_enabled(&view->content_tree->node, !view->shaded);