diff --git a/include/common/macros.h b/include/common/macros.h index 25226261..f3ceee82 100644 --- a/include/common/macros.h +++ b/include/common/macros.h @@ -62,7 +62,11 @@ #define BOUNDED_INT(a) ((a) < INT_MAX && (a) > INT_MIN) #endif -#define LAB_WLR_VERSION_AT_LEAST(major, minor, micro) \ - (WLR_VERSION_NUM >= (((major) << 16) | ((minor) << 8) | (micro))) +#define _LAB_CALC_WLR_VERSION_NUM(major, minor, micro) (((major) << 16) | ((minor) << 8) | (micro)) + +#define LAB_WLR_VERSION_AT_LEAST(major, minor, micro) ( \ + server.wlr_version >= _LAB_CALC_WLR_VERSION_NUM(major, minor, micro)) + +#define LAB_WLR_VERSION_LOWER(major, minor, micro) (!LAB_WLR_VERSION_AT_LEAST(major, minor, micro)) #endif /* LABWC_MACROS_H */ diff --git a/include/labwc.h b/include/labwc.h index 511fdd65..bdaa7d30 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -148,6 +148,8 @@ struct seat { }; struct server { + uint32_t wlr_version; + struct wl_display *wl_display; struct wl_event_loop *wl_event_loop; /* Can be used for timer events */ struct wlr_renderer *renderer; @@ -187,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; diff --git a/include/view.h b/include/view.h index c2764f94..cc1731b5 100644 --- a/include/view.h +++ b/include/view.h @@ -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; }; diff --git a/src/foreign-toplevel/ext-foreign.c b/src/foreign-toplevel/ext-foreign.c index 78774433..1dfa1d96 100644 --- a/src/foreign-toplevel/ext-foreign.c +++ b/src/foreign-toplevel/ext-foreign.c @@ -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); diff --git a/src/main.c b/src/main.c index 373f4480..f36be071 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,7 @@ #include #include "common/fd-util.h" #include "common/font.h" +#include "common/macros.h" #include "common/spawn.h" #include "config/rcxml.h" #include "config/session.h" @@ -164,6 +165,12 @@ main(int argc, char *argv[]) char *primary_client = NULL; enum wlr_log_importance verbosity = WLR_ERROR; + server.wlr_version = _LAB_CALC_WLR_VERSION_NUM( + wlr_version_get_major(), + wlr_version_get_minor(), + wlr_version_get_micro() + ); + int c; while (1) { int index = 0; diff --git a/src/output.c b/src/output.c index aea35062..4042cc1c 100644 --- a/src/output.c +++ b/src/output.c @@ -51,6 +51,36 @@ #include #endif +/* + * Partial workaround for toplevel capture on wlroots 0.20.0 + * Note that even with this workaround the capture itself might still stall. + * + * See https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5315 + * + * TODO: Remove once we start tracking wlroots 0.21.x + * or labwc depends on wlroots >= 0.20.1 + */ + +#include + +static void +workaround_frame_done_iter(struct wlr_scene_buffer *buffer, int sx, int sy, void *data) +{ + if (!buffer->primary_output) { + /* Catches hidden views, e.g. completely covered or disabled */ + return; + } + + struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(buffer); + if (!scene_surface) { + return; + } + + wlr_surface_send_frame_done(scene_surface->surface, (struct timespec *)data); +} + +/* Workaround for toplevel capture end */ + bool output_get_tearing_allowance(struct output *output) { @@ -121,6 +151,29 @@ handle_output_frame(struct wl_listener *listener, void *data) struct timespec now = { 0 }; clock_gettime(CLOCK_MONOTONIC, &now); wlr_scene_output_send_frame_done(output->scene_output, &now); + + /* + * Workaround for toplevel capture on wlroots 0.20.0 + * + * TODO: Remove once we start tracking wlroots 0.21.x + * or labwc depends on wlroots >= 0.20.1 + */ + if (LAB_WLR_VERSION_LOWER(0, 20, 1)) { + struct view *view; + wl_list_for_each(view, &server.views, link) { + if (view->capture.source) { + if (!view_on_output(view, output)) { + continue; + } + /* + * view might still be covered / disabled, + * but the iterator takes care of that. + */ + wlr_scene_node_for_each_buffer(&view->content_tree->node, + workaround_frame_done_iter, &now); + } + } + } } static void diff --git a/src/server.c b/src/server.c index fd48efed..189f0d15 100644 --- a/src/server.c +++ b/src/server.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include "config.h" +#include #include #include #include @@ -428,6 +429,49 @@ 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); + + if (LAB_WLR_VERSION_LOWER(0, 20, 1)) { + /* + * Work around a memory leak in wlroots. + * See https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5328 + * + * TODO: remove once we start tracking wlroots 0.21.x or depend on >= 0.20.1 + */ + free(request); + } +} + void server_init(void) { @@ -666,6 +710,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 +857,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); diff --git a/src/view.c b/src/view.c index 1d43fcfa..4321285d 100644 --- a/src/view.c +++ b/src/view.c @@ -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); diff --git a/src/xdg-popup.c b/src/xdg-popup.c index 04f82f9e..6db5e501 100644 --- a/src/xdg-popup.c +++ b/src/xdg-popup.c @@ -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); } diff --git a/src/xdg.c b/src/xdg.c index 5092ab7a..d2749170 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -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); diff --git a/src/xwayland.c b/src/xwayland.c index 967364c5..5838d412 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -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);