From e0fe17a27625440a1a4ac8cfe91639a938589925 Mon Sep 17 00:00:00 2001 From: skeetamine Date: Fri, 19 Jun 2026 18:37:41 +0300 Subject: [PATCH] per-app-cast: capture individual toplevels Implement ext-foreign-toplevel-image-capture-source-v1 so portals can offer per-window screen sharing alongside per-output. Each toplevel is rendered into a dedicated offscreen scene so capture isolates the window regardless of tag/position. --- dwl.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/dwl.c b/dwl.c index 8101ffa..7f4d059 100644 --- a/dwl.c +++ b/dwl.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,7 @@ typedef struct { struct wlr_xwayland_surface *xwayland; } surface; struct wlr_xdg_toplevel_decoration_v1 *decoration; + struct wlr_ext_foreign_toplevel_handle_v1 *foreign_toplevel; struct wl_listener commit; struct wl_listener map; struct wl_listener maximize; @@ -243,6 +245,11 @@ typedef struct { struct wl_listener destroy; } SessionLock; +typedef struct { + struct wlr_scene *scene; + struct wl_listener source_destroy; +} CaptureScene; + /* function declarations */ static void applybounds(Client *c, struct wlr_box *bbox); static void applyrules(Client *c); @@ -252,6 +259,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, static void arrangelayers(Monitor *m); static void axisnotify(struct wl_listener *listener, void *data); static void buttonpress(struct wl_listener *listener, void *data); +static void capturescenedestroy(struct wl_listener *listener, void *data); static void chvt(const Arg *arg); static void checkidleinhibitor(struct wlr_surface *exclude); static void cleanup(void); @@ -292,6 +300,7 @@ static void focusstack(const Arg *arg); static Client *focustop(Monitor *m); static void fullscreennotify(struct wl_listener *listener, void *data); static void gpureset(struct wl_listener *listener, void *data); +static void handlecapturerequest(struct wl_listener *listener, void *data); static void handlesig(int signo); static void incnmaster(const Arg *arg); static void inputdevice(struct wl_listener *listener, void *data); @@ -399,6 +408,10 @@ static struct wlr_session_lock_manager_v1 *session_lock_mgr; static struct wlr_scene_rect *locked_bg; static struct wlr_session_lock_v1 *cur_lock; +static struct wlr_ext_foreign_toplevel_list_v1 *foreign_toplevel_list; +static struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *toplevel_capture_mgr; +static struct wl_listener foreign_toplevel_capture_request = {.notify = handlecapturerequest}; + static struct wlr_seat *seat; static KeyboardGroup *kb_group; static unsigned int cursor_mode; @@ -676,6 +689,15 @@ buttonpress(struct wl_listener *listener, void *data) event->time_msec, event->button, event->state); } +void +capturescenedestroy(struct wl_listener *listener, void *data) +{ + CaptureScene *cs = wl_container_of(listener, cs, source_destroy); + wl_list_remove(&cs->source_destroy.link); + wlr_scene_node_destroy(&cs->scene->tree.node); + free(cs); +} + void chvt(const Arg *arg) { @@ -786,6 +808,7 @@ cleanuplisteners(void) wl_list_remove(&request_start_drag.link); wl_list_remove(&start_drag.link); wl_list_remove(&new_session_lock.link); + wl_list_remove(&foreign_toplevel_capture_request.link); #ifdef XWAYLAND wl_list_remove(&new_xwayland_surface.link); wl_list_remove(&xwayland_ready.link); @@ -1561,6 +1584,41 @@ gpureset(struct wl_listener *listener, void *data) wlr_renderer_destroy(old_drw); } +void +handlecapturerequest(struct wl_listener *listener, void *data) +{ + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *req = data; + Client *c; + CaptureScene *cs; + struct wlr_ext_image_capture_source_v1 *source; + + wl_list_for_each(c, &clients, link) { + if (c->foreign_toplevel != req->toplevel_handle) + continue; + + cs = ecalloc(1, sizeof(*cs)); + cs->scene = wlr_scene_create(); + if (c->type == XDGShell) + wlr_scene_xdg_surface_create(&cs->scene->tree, c->surface.xdg); + else + wlr_scene_subsurface_tree_create(&cs->scene->tree, client_surface(c)); + + source = + wlr_ext_image_capture_source_v1_create_with_scene_node( + &cs->scene->tree.node, event_loop, alloc, drw); + + if (source) { + cs->source_destroy.notify = capturescenedestroy; + wl_signal_add(&source->events.destroy, &cs->source_destroy); + wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept(req, source); + } else { + wlr_scene_node_destroy(&cs->scene->tree.node); + free(cs); + } + return; + } +} + void handlesig(int signo) { @@ -1786,6 +1844,14 @@ mapnotify(struct wl_listener *listener, void *data) wl_list_insert(&clients, &c->link); wl_list_insert(&fstack, &c->flink); + { + struct wlr_ext_foreign_toplevel_handle_v1_state ft_state = { + .title = client_get_title(c), + .app_id = client_get_appid(c), + }; + c->foreign_toplevel = wlr_ext_foreign_toplevel_handle_v1_create(foreign_toplevel_list, &ft_state); + } + /* Set initial monitor, tags, floating status, and focus: * we always consider floating, clients that have parent and thus * we set the same tags and monitor as its parent. @@ -2532,6 +2598,10 @@ setup(void) wlr_presentation_create(dpy, backend, 2); wlr_alpha_modifier_v1_create(dpy); + foreign_toplevel_list = wlr_ext_foreign_toplevel_list_v1_create(dpy, 1); + toplevel_capture_mgr = wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(dpy, 1); + wl_signal_add(&toplevel_capture_mgr->events.new_request, &foreign_toplevel_capture_request); + /* Initializes the interface used to implement urgency hints */ activation = wlr_xdg_activation_v1_create(dpy); wl_signal_add(&activation->events.request_activate, &request_activate); @@ -2838,6 +2908,11 @@ unmapnotify(struct wl_listener *listener, void *data) wl_list_remove(&c->flink); } + if (c->foreign_toplevel) { + wlr_ext_foreign_toplevel_handle_v1_destroy(c->foreign_toplevel); + c->foreign_toplevel = NULL; + } + wlr_scene_node_destroy(&c->scene->node); printstatus(); motionnotify(0, NULL, 0, 0, 0, 0); @@ -2955,6 +3030,14 @@ updatetitle(struct wl_listener *listener, void *data) Client *c = wl_container_of(listener, c, set_title); if (c == focustop(c->mon)) printstatus(); + + if (c->foreign_toplevel) { + struct wlr_ext_foreign_toplevel_handle_v1_state ft_state = { + .title = client_get_title(c), + .app_id = client_get_appid(c), + }; + wlr_ext_foreign_toplevel_handle_v1_update_state(c->foreign_toplevel, &ft_state); + } } void -- 2.54.0