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
+