diff --git a/patches/per-app-cast/README.md b/patches/per-app-cast/README.md new file mode 100644 index 0000000..099c4ba --- /dev/null +++ b/patches/per-app-cast/README.md @@ -0,0 +1,18 @@ +### Description +Adds per-window screen sharing aka toplevel capture via `ext-foreign-toplevel-image-capture-source-v1` based on the sway implementation
+XWayland clients work in basic testing but it should be considered rather experimental. There is some possible restacking edge-cases but I was not able to reproduce them yet
+Note that the captured surface is rendered a second time into its own scene, so there's a small GPU cost while a capture is active + +Targets the dwl **`wlroots-next`** branch (base commit `d41ecb745c`). + +### Requirements +| Requirement | Notes | +| --- | --- | +| `wlroots` ≥ 0.20 | mandatory | +| `xdg-desktop-portal-wlr` ≥ 0.8 | `chooser_type` has to be configured as `dmenu` | + +### Download +- [per-app-share-wlroots-next-d41ecb745c](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/per-app-cast/per-app-share-wlroots-next-d41ecb745c.patch) + +### Authors +- [skeetamine](https://codeberg.org/skeetamine) diff --git a/patches/per-app-cast/per-app-share-wlroots-next-d41ecb745c.patch b/patches/per-app-cast/per-app-share-wlroots-next-d41ecb745c.patch new file mode 100644 index 0000000..67df510 --- /dev/null +++ b/patches/per-app-cast/per-app-share-wlroots-next-d41ecb745c.patch @@ -0,0 +1,194 @@ +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 +