commit 0b9f5bc
hovercats
·
2026-03-10 11:13:13 +0000 UTC
parent 0141787
swc: port patches from neuswc, so that we can build hevel etc without adding neuswc as an additional package
22 files changed,
+4525,
-2
+1,
-0
1@@ -183,6 +183,7 @@
2 [submodule "pkg/swc/src"]
3 path = pkg/swc/src
4 url = https://github.com/michaelforney/swc
5+ ignore = all
6 [submodule "pkg/utf8proc/src"]
7 path = pkg/utf8proc/src
8 url = https://github.com/JuliaLang/utf8proc
+9,
-0
1@@ -30,6 +30,12 @@ waylandproto('protocol/server-decoration.xml', {
2 code='server-decoration-protocol.c',
3 })
4
5+waylandproto('protocol/swc_wallpaper.xml', {
6+ client='include/swc_wallpaper-client-protocol.h',
7+ server='include/swc_wallpaper-server-protocol.h',
8+ code='swc_wallpaper-protocol.c',
9+})
10+
11 pkg.hdrs = {
12 copy('$outdir/include', '$srcdir/libswc', {'swc.h'}),
13 '$outdir/include/server-decoration-server-protocol.h',
14@@ -50,6 +56,7 @@ build('convert_font', '$outdir/cursor/cursor_data.h', {'$srcdir/cursor/cursor.pc
15
16 pkg.deps = {
17 '$gendir/headers',
18+ '$outdir/include/swc_wallpaper-server-protocol.h',
19 '$outdir/wayland-drm-server-protocol.h',
20 '$outdir/cursor/cursor_data.h',
21 'pkg/fontconfig/headers',
22@@ -95,6 +102,7 @@ lib('libswc.a', [[
23 swc.c
24 util.c
25 view.c
26+ wallpaper.c
27 wayland_buffer.c
28 window.c
29 xdg_decoration.c
30@@ -103,6 +111,7 @@ lib('libswc.a', [[
31 launch/protocol.c.o
32 server-decoration-protocol.c.o
33 swc-protocol.c.o
34+ swc_wallpaper-protocol.c.o
35 $builddir/(
36 pkg/libinput/libinput.a.d
37 pkg/libxkbcommon/libxkbcommon.a
1@@ -0,0 +1,283 @@
2+From ca2cd556f5832eb1d10785a77c564dc4ef65e931 Mon Sep 17 00:00:00 2001
3+From: shrub <maybeshrub@gmail.com>
4+Date: Mon, 15 Dec 2025 19:06:24 +0000
5+Subject: [PATCH] add helper functions
6+
7+- a overlay helper so compositor's can draw arbitrary boxes
8+- swc_pointer_send_button helper, for compositor's that intercept button
9+ events
10+- swc_cursor_position helper, for tracking cursor position
11+---
12+ libswc/compositor.c | 89 +++++++++++++++++++++++++++++++++++++++++++++
13+ libswc/pointer.c | 21 +++++++++++
14+ libswc/swc.c | 19 ++++++++++
15+ libswc/swc.h | 32 ++++++++++++++++
16+ libswc/xdg_shell.c | 9 ++++-
17+ 5 files changed, 168 insertions(+), 2 deletions(-)
18+
19+diff --git a/libswc/compositor.c b/libswc/compositor.c
20+index 543615c..66f962a 100644
21+--- a/libswc/compositor.c
22++++ b/libswc/compositor.c
23+@@ -84,6 +84,14 @@ static struct {
24+ struct wl_global *global;
25+ } compositor;
26+
27++static struct {
28++ bool active;
29++ int32_t x, y;
30++ uint32_t width, height;
31++ uint32_t color;
32++ uint32_t border_width;
33++} overlay;
34++
35+ struct swc_compositor swc_compositor = {
36+ .pointer_handler = &pointer_handler,
37+ };
38+@@ -216,6 +224,7 @@ static void
39+ renderer_repaint(struct target *target, pixman_region32_t *damage, pixman_region32_t *base_damage, struct wl_list *views)
40+ {
41+ struct compositor_view *view;
42++ const struct swc_rectangle *target_geom = &target->view->geometry;
43+
44+ DEBUG("Rendering to target { x: %d, y: %d, w: %u, h: %u }\n",
45+ target->view->geometry.x, target->view->geometry.y,
46+@@ -234,6 +243,42 @@ renderer_repaint(struct target *target, pixman_region32_t *damage, pixman_region
47+ repaint_view(target, view, damage);
48+ }
49+
50++ if (overlay.active && overlay.border_width > 0) {
51++ int32_t x = overlay.x - target_geom->x;
52++ int32_t y = overlay.y - target_geom->y;
53++ uint32_t w = overlay.width, h = overlay.height, bw = overlay.border_width;
54++ int32_t tx = (int32_t)target_geom->width;
55++ int32_t ty = (int32_t)target_geom->height;
56++
57++ /* draw box as 4 rectangles with wld */
58++ #define CLAMP_LOW(v, lo) ((v) < (lo) ? (lo) : (v))
59++ #define CLAMP_HIGH(v, hi) ((v) > (hi) ? (hi) : (v))
60++ #define DRAW_CLIPPED(rx, ry, rw, rh) do { \
61++ int32_t _x1 = CLAMP_LOW((rx), 0); \
62++ int32_t _y1 = CLAMP_LOW((ry), 0); \
63++ int32_t _x2 = CLAMP_HIGH((rx) + (int32_t)(rw), tx); \
64++ int32_t _y2 = CLAMP_HIGH((ry) + (int32_t)(rh), ty); \
65++ if (_x2 > _x1 && _y2 > _y1) \
66++ wld_fill_rectangle(swc.drm->renderer, overlay.color, _x1, _y1, (uint32_t)(_x2 - _x1), (uint32_t)(_y2 - _y1)); \
67++ } while (0)
68++
69++ if (w > 0 && h > 0) {
70++ if (bw > w)
71++ bw = w;
72++ if (bw > h)
73++ bw = h;
74++
75++ DRAW_CLIPPED(x, y, (int32_t)w, (int32_t)bw); /* top */
76++ DRAW_CLIPPED(x, y + (int32_t)h - (int32_t)bw, (int32_t)w, (int32_t)bw); /* bottom */
77++ DRAW_CLIPPED(x, y, (int32_t)bw, (int32_t)h); /* left */
78++ DRAW_CLIPPED(x + (int32_t)w - (int32_t)bw, y, (int32_t)bw, (int32_t)h); /* right */
79++ }
80++
81++ #undef DRAW_CLIPPED
82++ #undef CLAMP_HIGH
83++ #undef CLAMP_LOW
84++ }
85++
86+ wld_flush(swc.drm->renderer);
87+ }
88+
89+@@ -345,6 +390,50 @@ schedule_updates(uint32_t screens)
90+ compositor.scheduled_updates |= screens;
91+ }
92+
93++static void
94++overlay_damage_region(int32_t x, int32_t y, uint32_t width, uint32_t height, uint32_t border_width)
95++{
96++ (void)border_width;
97++ pixman_region32_union_rect(&compositor.damage, &compositor.damage, x, y, width, height);
98++}
99++
100++EXPORT void
101++swc_overlay_set_box(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color, uint32_t border_width)
102++{
103++ int32_t x = x1 < x2 ? x1 : x2;
104++ int32_t y = y1 < y2 ? y1 : y2;
105++ uint32_t width = (uint32_t)abs(x2 - x1);
106++ uint32_t height = (uint32_t)abs(y2 - y1);
107++
108++ if (border_width == 0)
109++ border_width = 1;
110++
111++ if (overlay.active)
112++ overlay_damage_region(overlay.x, overlay.y, overlay.width, overlay.height, overlay.border_width);
113++
114++ overlay.active = true;
115++ overlay.x = x;
116++ overlay.y = y;
117++ overlay.width = width;
118++ overlay.height = height;
119++ overlay.color = color;
120++ overlay.border_width = border_width;
121++
122++ overlay_damage_region(overlay.x, overlay.y, overlay.width, overlay.height, overlay.border_width);
123++ schedule_updates(-1);
124++}
125++
126++EXPORT void
127++swc_overlay_clear(void)
128++{
129++ if (!overlay.active)
130++ return;
131++
132++ overlay_damage_region(overlay.x, overlay.y, overlay.width, overlay.height, overlay.border_width);
133++ overlay.active = false;
134++ schedule_updates(-1);
135++}
136++
137+ static bool
138+ update(struct view *base)
139+ {
140+diff --git a/libswc/pointer.c b/libswc/pointer.c
141+index 7e4d24d..184186f 100644
142+--- a/libswc/pointer.c
143++++ b/libswc/pointer.c
144+@@ -27,6 +27,7 @@
145+ #include "internal.h"
146+ #include "plane.h"
147+ #include "screen.h"
148++#include "seat.h"
149+ #include "shm.h"
150+ #include "surface.h"
151+ #include "util.h"
152+@@ -36,6 +37,26 @@
153+ #include <stdio.h>
154+ #include <wld/wld.h>
155+
156++EXPORT void
157++swc_pointer_send_button(uint32_t time, uint32_t button, uint32_t state)
158++{
159++ struct pointer *pointer = swc.seat ? swc.seat->pointer : NULL;
160++ struct wl_resource *resource;
161++ uint32_t serial;
162++
163++ if (!pointer || wl_list_empty(&pointer->focus.active))
164++ return;
165++
166++ serial = wl_display_next_serial(swc.display);
167++ wl_resource_for_each (resource, &pointer->focus.active)
168++ wl_pointer_send_button(resource, serial, time, button, state);
169++ wl_resource_for_each (resource, &pointer->focus.active) {
170++ if (wl_resource_get_version(resource) >= WL_POINTER_FRAME_SINCE_VERSION)
171++ wl_pointer_send_frame(resource);
172++ }
173++ pointer->client_axis_source = -1;
174++}
175++
176+ static void
177+ enter(struct input_focus_handler *handler, struct wl_list *resources, struct compositor_view *view)
178+ {
179+diff --git a/libswc/swc.c b/libswc/swc.c
180+index 0fca9f9..8814c13 100644
181+--- a/libswc/swc.c
182++++ b/libswc/swc.c
183+@@ -108,6 +108,25 @@ swc_deactivate(void)
184+ swc.manager->deactivate();
185+ }
186+
187++EXPORT bool
188++swc_cursor_position(int32_t *x, int32_t *y)
189++{
190++ if (x)
191++ *x = 0;
192++ if (y)
193++ *y = 0;
194++
195++ if (!swc.seat || !swc.seat->pointer)
196++ return false;
197++
198++ if (x)
199++ *x = swc.seat->pointer->x;
200++ if (y)
201++ *y = swc.seat->pointer->y;
202++
203++ return true;
204++}
205++
206+ EXPORT bool
207+ swc_initialize(struct wl_display *display, struct wl_event_loop *event_loop, const struct swc_manager *manager)
208+ {
209+diff --git a/libswc/swc.h b/libswc/swc.h
210+index cb0f7eb..d199e3c 100644
211+--- a/libswc/swc.h
212++++ b/libswc/swc.h
213+@@ -35,6 +35,38 @@ struct libinput_device;
214+ struct wl_display;
215+ struct wl_event_loop;
216+
217++/**
218++ * gett the current cursor position
219++ *
220++ * The returned coordinates are in compositor-global space, in wl_fixed_t
221++ * (24.8) fixed-point units, but exposed as raw int32_t to avoid needing
222++ * wayland headers
223++ *
224++ */
225++bool swc_cursor_position(int32_t *x, int32_t *y);
226++
227++/**
228++ * Send a pointer button event to the currently focused client.
229++ *
230++ * This is intended for window managers which intercept button events (for
231++ * example for mouse chords) but want normal clicks to still reach clients.
232++ */
233++void swc_pointer_send_button(uint32_t time, uint32_t button, uint32_t state);
234++
235++/**
236++ * draw [or update] a simple box overlay
237++ *
238++ * box is defined by two diagonally opposite corners in compositor-global
239++ * coordinates. this draws only the border. Call swc_overlay_clear() to remove
240++ * it
241++ */
242++void swc_overlay_set_box(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color, uint32_t border_width);
243++
244++/**
245++ * Clear the current overlay, if any.
246++ */
247++void swc_overlay_clear(void);
248++
249+ /* Rectangles {{{ */
250+
251+ struct swc_rectangle {
252+diff --git a/libswc/xdg_shell.c b/libswc/xdg_shell.c
253+index 97ad5fe..33a7f9c 100644
254+--- a/libswc/xdg_shell.c
255++++ b/libswc/xdg_shell.c
256+@@ -286,18 +286,23 @@ static void
257+ focus(struct window *window)
258+ {
259+ struct xdg_toplevel *toplevel = wl_container_of(window, toplevel, window);
260++ uint32_t width = window->view->base.geometry.width;
261++ uint32_t height = window->view->base.geometry.height;
262+
263+ add_state(toplevel, XDG_TOPLEVEL_STATE_ACTIVATED);
264+- send_configure(toplevel, -1, -1);
265++ /* dont send 0x0 on focus change */
266++ send_configure(toplevel, width ? (int32_t)width : -1, height ? (int32_t)height : -1);
267+ }
268+
269+ static void
270+ unfocus(struct window *window)
271+ {
272+ struct xdg_toplevel *toplevel = wl_container_of(window, toplevel, window);
273++ uint32_t width = window->view->base.geometry.width;
274++ uint32_t height = window->view->base.geometry.height;
275+
276+ remove_state(toplevel, XDG_TOPLEVEL_STATE_ACTIVATED);
277+- send_configure(toplevel, -1, -1);
278++ send_configure(toplevel, width ? (int32_t)width : -1, height ? (int32_t)height : -1);
279+ }
280+
281+ static void
282+--
283+2.49.0
284+
1@@ -0,0 +1,170 @@
2+From ab5eb634b9e10346b5c184ae3422eeda1c67b91a Mon Sep 17 00:00:00 2001
3+From: shrub <maybeshrub@gmail.com>
4+Date: Mon, 15 Dec 2025 21:55:21 +0000
5+Subject: [PATCH] z-ordering compositor.c : click to raise window to top. uses
6+ internal helpers to find the window under the pointer and raise it on button
7+ press.
8+
9+---
10+ libswc/compositor.c | 113 +++++++++++++++++++++++++++++++++++++-------
11+ 1 file changed, 97 insertions(+), 16 deletions(-)
12+
13+diff --git a/libswc/compositor.c b/libswc/compositor.c
14+index 66f962a..2808426 100644
15+--- a/libswc/compositor.c
16++++ b/libswc/compositor.c
17+@@ -63,10 +63,12 @@ struct target {
18+ };
19+
20+ static bool handle_motion(struct pointer_handler *handler, uint32_t time, wl_fixed_t x, wl_fixed_t y);
21++static bool handle_button(struct pointer_handler *handler, uint32_t time, struct button *button, uint32_t state);
22+ static void perform_update(void *data);
23+
24+ static struct pointer_handler pointer_handler = {
25+ .motion = handle_motion,
26++ .button = handle_button,
27+ };
28+
29+ static struct {
30+@@ -523,6 +525,78 @@ static const struct view_impl view_impl = {
31+ .move = move,
32+ };
33+
34++static struct compositor_view *
35++view_at(int32_t x, int32_t y)
36++{
37++ struct compositor_view *view;
38++ struct swc_rectangle *geom;
39++
40++ wl_list_for_each (view, &compositor.views, link) {
41++ if (!view->visible)
42++ continue;
43++
44++ geom = &view->base.geometry;
45++ if (!rectangle_contains_point(geom, x, y))
46++ continue;
47++
48++ if (pixman_region32_contains_point(&view->surface->state.input,
49++ x - geom->x, y - geom->y, NULL))
50++ {
51++ return view;
52++ }
53++ }
54++
55++ return NULL;
56++}
57++
58++static struct compositor_view *
59++window_view(struct compositor_view *view)
60++{
61++ while (view && !view->window && view->parent && view->parent != view)
62++ view = view->parent;
63++ return (view && view->window) ? view : NULL;
64++}
65++
66++static void
67++raise_window(struct compositor_view *view)
68++{
69++ struct compositor_view *other, *top_window;
70++ struct wl_list *insert_after;
71++ uint32_t screens;
72++
73++ view = window_view(view);
74++ if (!view || !view->visible)
75++ return;
76++
77++ top_window = NULL;
78++ insert_after = &compositor.views;
79++ wl_list_for_each (other, &compositor.views, link) {
80++ if (!other->visible)
81++ continue;
82++
83++ if (other->window) {
84++ top_window = other;
85++ break;
86++ }
87++ insert_after = &other->link;
88++ }
89++
90++ if (view == top_window)
91++ return;
92++
93++ screens = view->base.screens;
94++
95++ wl_list_remove(&view->link);
96++ wl_list_insert(insert_after, &view->link);
97++
98++ view->border.damaged = true;
99++ pixman_region32_union_rect(&compositor.damage, &compositor.damage,
100++ view->extents.x1, view->extents.y1,
101++ (uint32_t)(view->extents.x2 - view->extents.x1),
102++ (uint32_t)(view->extents.y2 - view->extents.y1));
103++ schedule_updates(screens);
104++}
105++
106+ struct compositor_view *
107+ compositor_create_view(struct surface *surface)
108+ {
109+@@ -575,7 +649,7 @@ compositor_view(struct view *view)
110+ void
111+ compositor_view_set_parent(struct compositor_view *view, struct compositor_view *parent)
112+ {
113+- view->parent = view;
114++ view->parent = parent;
115+
116+ if (parent->visible)
117+ compositor_view_show(view);
118+@@ -789,28 +863,35 @@ perform_update(void *data)
119+ bool
120+ handle_motion(struct pointer_handler *handler, uint32_t time, wl_fixed_t fx, wl_fixed_t fy)
121+ {
122+- struct compositor_view *view;
123+- bool found = false;
124+ int32_t x = wl_fixed_to_int(fx), y = wl_fixed_to_int(fy);
125+- struct swc_rectangle *geom;
126+
127+ /* If buttons are pressed, don't change pointer focus. */
128+ if (swc.seat->pointer->buttons.size > 0)
129+ return false;
130+
131+- wl_list_for_each (view, &compositor.views, link) {
132+- if (!view->visible)
133+- continue;
134+- geom = &view->base.geometry;
135+- if (rectangle_contains_point(geom, x, y)) {
136+- if (pixman_region32_contains_point(&view->surface->state.input, x - geom->x, y - geom->y, NULL)) {
137+- found = true;
138+- break;
139+- }
140+- }
141+- }
142++ struct compositor_view *view = view_at(x, y);
143++
144++ pointer_set_focus(swc.seat->pointer, view);
145++
146++ return false;
147++}
148++
149++static bool
150++handle_button(struct pointer_handler *handler, uint32_t time, struct button *button, uint32_t state)
151++{
152++ (void)handler;
153++ (void)time;
154++ (void)button;
155++
156++ if (state != WL_POINTER_BUTTON_STATE_PRESSED)
157++ return false;
158++
159++ int32_t x = wl_fixed_to_int(swc.seat->pointer->x);
160++ int32_t y = wl_fixed_to_int(swc.seat->pointer->y);
161++ struct compositor_view *view = view_at(x, y);
162+
163+- pointer_set_focus(swc.seat->pointer, found ? view : NULL);
164++ pointer_set_focus(swc.seat->pointer, view);
165++ raise_window(view);
166+
167+ return false;
168+ }
169+--
170+2.49.0
171+
1@@ -0,0 +1,59 @@
2+From b6c18cfa63ea8cef39e1bfb728cfdac4fd858c68 Mon Sep 17 00:00:00 2001
3+From: shrub <maybeshrub@gmail.com>
4+Date: Mon, 15 Dec 2025 22:06:23 +0000
5+Subject: [PATCH] swc_window_at new public api, returns the topmost window at
6+ some compositor global pair of coordinates
7+
8+---
9+ libswc/compositor.c | 9 +++++++++
10+ libswc/swc.h | 7 +++++++
11+ 2 files changed, 16 insertions(+)
12+
13+diff --git a/libswc/compositor.c b/libswc/compositor.c
14+index 2808426..24554ee 100644
15+--- a/libswc/compositor.c
16++++ b/libswc/compositor.c
17+@@ -43,6 +43,7 @@
18+ #include "surface.h"
19+ #include "util.h"
20+ #include "view.h"
21++#include "window.h"
22+
23+ #include <errno.h>
24+ #include <stdlib.h>
25+@@ -597,6 +598,14 @@ raise_window(struct compositor_view *view)
26+ schedule_updates(screens);
27+ }
28+
29++EXPORT struct swc_window *
30++swc_window_at(int32_t x, int32_t y)
31++{
32++ struct compositor_view *view = window_view(view_at(x, y));
33++
34++ return view ? &view->window->base : NULL;
35++}
36++
37+ struct compositor_view *
38+ compositor_create_view(struct surface *surface)
39+ {
40+diff --git a/libswc/swc.h b/libswc/swc.h
41+index d199e3c..caf966c 100644
42+--- a/libswc/swc.h
43++++ b/libswc/swc.h
44+@@ -298,6 +298,13 @@ void swc_window_begin_resize(struct swc_window *window, uint32_t edges);
45+ */
46+ void swc_window_end_resize(struct swc_window *window);
47+
48++/**
49++ * returns the topmost window at any given compositor global coordinates
50++ *
51++ * returns null if there is no window at that point
52++ */
53++struct swc_window *swc_window_at(int32_t x, int32_t y);
54++
55+ /* }}} */
56+
57+ /* Bindings {{{ */
58+--
59+2.49.0
60+
1@@ -0,0 +1,264 @@
2+From 54a1db20744eab817a392cccff8ea79fe4b34de3 Mon Sep 17 00:00:00 2001
3+From: dalem <lain@dalem.foo>
4+Date: Tue, 16 Dec 2025 02:01:10 +0100
5+Subject: [PATCH] add support for inner and outer window borders, lovingly
6+ known as 'double borders'
7+
8+---
9+ libswc/compositor.c | 90 +++++++++++++++++++++++++++++++--------------
10+ libswc/compositor.h | 17 ++++++---
11+ libswc/swc.h | 4 +-
12+ libswc/window.c | 8 ++--
13+ 4 files changed, 83 insertions(+), 36 deletions(-)
14+
15+diff --git a/libswc/compositor.c b/libswc/compositor.c
16+index 24554ee..5f45a16 100644
17+--- a/libswc/compositor.c
18++++ b/libswc/compositor.c
19+@@ -212,15 +212,38 @@ repaint_view(struct target *target, struct compositor_view *view, pixman_region3
20+ wld_copy_region(swc.drm->renderer, view->buffer, geom->x - target_geom->x, geom->y - target_geom->y, &view_damage);
21+ }
22+
23+- pixman_region32_fini(&view_damage);
24++ pixman_region32_t in_rect;
25++ pixman_region32_init_rect(&in_rect,
26++ geom->x - view->border.inwidth,
27++ geom->y - view->border.inwidth,
28++ geom->width + (2 * view->border.inwidth),
29++ geom->height + (2 * view->border.inwidth));
30++
31++ pixman_region32_t out_border;
32++ pixman_region32_init(&out_border);
33++ pixman_region32_subtract(&out_border, &border_damage, &in_rect);
34++
35++ pixman_region32_t in_border;
36++ pixman_region32_init(&in_border);
37++ pixman_region32_subtract(&in_border, &in_rect, &view_region);
38++
39+
40+ /* Draw border */
41+- if (pixman_region32_not_empty(&border_damage)) {
42+- pixman_region32_translate(&border_damage, -target_geom->x, -target_geom->y);
43+- wld_fill_region(swc.drm->renderer, view->border.color, &border_damage);
44++ if (view->border.outwidth > 0 && pixman_region32_not_empty(&out_border)) {
45++ pixman_region32_translate(&out_border, -target_geom->x, -target_geom->y);
46++ wld_fill_region(swc.drm->renderer, view->border.outcolor, &out_border);
47++ }
48++
49++ if (view->border.inwidth > 0 && pixman_region32_not_empty(&in_border)) {
50++ pixman_region32_translate(&in_border, -target_geom->x, -target_geom->y);
51++ wld_fill_region(swc.drm->renderer, view->border.incolor, &in_border);
52+ }
53+
54+ pixman_region32_fini(&border_damage);
55++ pixman_region32_fini(&in_rect);
56++ pixman_region32_fini(&out_border);
57++ pixman_region32_fini(&in_border);
58++
59+ }
60+
61+ static void
62+@@ -361,21 +384,26 @@ static void
63+ damage_view(struct compositor_view *view)
64+ {
65+ damage_below_view(view);
66+- view->border.damaged = true;
67+-}
68++ view->border.damaged_border1 = true;
69++ view->border.damaged_border2 = true;
70+
71++}
72+ static void
73+ update_extents(struct compositor_view *view)
74+ {
75+- view->extents.x1 = view->base.geometry.x - view->border.width;
76+- view->extents.y1 = view->base.geometry.y - view->border.width;
77+- view->extents.x2 = view->base.geometry.x + view->base.geometry.width + view->border.width;
78+- view->extents.y2 = view->base.geometry.y + view->base.geometry.height + view->border.width;
79++ uint32_t total_border = view->border.outwidth + view->border.inwidth;
80++
81++ view->extents.x1 = view->base.geometry.x - total_border;
82++ view->extents.y1 = view->base.geometry.y - total_border;
83++ view->extents.x2 = view->base.geometry.x + view->base.geometry.width + total_border;
84++ view->extents.y2 = view->base.geometry.y + view->base.geometry.height + total_border;
85+
86+ /* Damage border. */
87+- view->border.damaged = true;
88++ view->border.damaged_border1 = true;
89++ view->border.damaged_border2 = true;
90+ }
91+
92++
93+ static void
94+ schedule_updates(uint32_t screens)
95+ {
96+@@ -590,7 +618,7 @@ raise_window(struct compositor_view *view)
97+ wl_list_remove(&view->link);
98+ wl_list_insert(insert_after, &view->link);
99+
100+- view->border.damaged = true;
101++ view->border.damaged_border1 = true;
102+ pixman_region32_union_rect(&compositor.damage, &compositor.damage,
103+ view->extents.x1, view->extents.y1,
104+ (uint32_t)(view->extents.x2 - view->extents.x1),
105+@@ -626,9 +654,12 @@ compositor_create_view(struct surface *surface)
106+ view->extents.y1 = 0;
107+ view->extents.x2 = 0;
108+ view->extents.y2 = 0;
109+- view->border.width = 0;
110+- view->border.color = 0x000000;
111+- view->border.damaged = false;
112++ view->border.outwidth = 0;
113++ view->border.outcolor = 0x000000;
114++ view->border.damaged_border1 = false;
115++ view->border.inwidth = 0;
116++ view->border.incolor = 0x000000;
117++ view->border.damaged_border2 = false;
118+ pixman_region32_init(&view->clip);
119+ wl_signal_init(&view->destroy_signal);
120+ surface_set_view(surface, &view->base);
121+@@ -711,13 +742,16 @@ compositor_view_hide(struct compositor_view *view)
122+ }
123+
124+ void
125+-compositor_view_set_border_width(struct compositor_view *view, uint32_t width)
126++compositor_view_set_border_width(struct compositor_view *view, uint32_t outwidth, uint32_t inwidth)
127+ {
128+- if (view->border.width == width)
129++ if (view->border.outwidth == outwidth && view->border.inwidth == inwidth)
130+ return;
131+
132+- view->border.width = width;
133+- view->border.damaged = true;
134++ view->border.outwidth = outwidth;
135++ view->border.damaged_border1 = true;
136++
137++ view->border.inwidth = inwidth;
138++ view->border.damaged_border2 = true;
139+
140+ /* XXX: Damage above surface for transparent surfaces? */
141+
142+@@ -726,19 +760,22 @@ compositor_view_set_border_width(struct compositor_view *view, uint32_t width)
143+ }
144+
145+ void
146+-compositor_view_set_border_color(struct compositor_view *view, uint32_t color)
147++compositor_view_set_border_color(struct compositor_view *view, uint32_t outcolor, uint32_t incolor)
148+ {
149+- if (view->border.color == color)
150++ if (view->border.outcolor == outcolor && view->border.incolor == incolor)
151+ return;
152+
153+- view->border.color = color;
154+- view->border.damaged = true;
155++ view->border.outcolor = outcolor;
156++ view->border.damaged_border1 = true;
157++
158++ view->border.incolor = incolor;
159++ view->border.damaged_border2 = true;
160++
161+
162+ /* XXX: Damage above surface for transparent surfaces? */
163+
164+ update(&view->base);
165+ }
166+-
167+ /* }}} */
168+
169+ static void
170+@@ -781,7 +818,8 @@ calculate_damage(void)
171+ pixman_region32_clear(surface_damage);
172+ }
173+
174+- if (view->border.damaged) {
175++ /* redraw entire thingy if either */
176++ if (view->border.damaged_border1 || view->border.damaged_border2) {
177+ pixman_region32_t border_region, view_region;
178+
179+ pixman_region32_init_with_extents(&border_region, &view->extents);
180+@@ -793,8 +831,6 @@ calculate_damage(void)
181+
182+ pixman_region32_fini(&border_region);
183+ pixman_region32_fini(&view_region);
184+-
185+- view->border.damaged = false;
186+ }
187+ }
188+
189+diff --git a/libswc/compositor.h b/libswc/compositor.h
190+index 9844bd9..9046f0b 100644
191+--- a/libswc/compositor.h
192++++ b/libswc/compositor.h
193+@@ -64,9 +64,16 @@ struct compositor_view {
194+ pixman_region32_t clip;
195+
196+ struct {
197+- uint32_t width;
198+- uint32_t color;
199+- bool damaged;
200++ uint32_t outwidth;
201++ uint32_t outcolor;
202++
203++ bool damaged_border1;
204++
205++ /* sir, a second border has hit the compositor! */
206++ uint32_t inwidth;
207++ uint32_t incolor;
208++
209++ bool damaged_border2;
210+ } border;
211+
212+ struct wl_list link;
213+@@ -87,7 +94,7 @@ void compositor_view_set_parent(struct compositor_view *view, struct compositor_
214+ void compositor_view_show(struct compositor_view *view);
215+ void compositor_view_hide(struct compositor_view *view);
216+
217+-void compositor_view_set_border_color(struct compositor_view *view, uint32_t color);
218+-void compositor_view_set_border_width(struct compositor_view *view, uint32_t width);
219++void compositor_view_set_border_color(struct compositor_view *view, uint32_t outcolor, uint32_t incolor);
220++void compositor_view_set_border_width(struct compositor_view *view, uint32_t outwidth, uint32_t inwidth);
221+
222+ #endif
223+diff --git a/libswc/swc.h b/libswc/swc.h
224+index caf966c..24c8c3b 100644
225+--- a/libswc/swc.h
226++++ b/libswc/swc.h
227+@@ -267,8 +267,10 @@ void swc_window_set_geometry(struct swc_window *window, const struct swc_rectang
228+ *
229+ * NOTE: The window's geometry remains unchanged, and should be updated if a
230+ * fixed top-left corner of the border is desired.
231++ *
232++ * info from dalem: unsure how much double borders break!
233+ */
234+-void swc_window_set_border(struct swc_window *window, uint32_t color, uint32_t width);
235++void swc_window_set_border(struct swc_window *window, uint32_t inner_border_color, uint32_t inner_border_width, uint32_t outer_border_color, uint32_t outer_border_width);
236+
237+ /**
238+ * Begin an interactive move of the specified window.
239+diff --git a/libswc/window.c b/libswc/window.c
240+index 7f7255e..6e5a235 100644
241+--- a/libswc/window.c
242++++ b/libswc/window.c
243+@@ -249,14 +249,16 @@ swc_window_set_geometry(struct swc_window *window, const struct swc_rectangle *g
244+ }
245+
246+ EXPORT void
247+-swc_window_set_border(struct swc_window *window, uint32_t border_color, uint32_t border_width)
248++swc_window_set_border(struct swc_window *window, uint32_t inner_border_color, uint32_t inner_border_width,
249++ uint32_t outer_border_color, uint32_t outer_border_width)
250+ {
251+ struct compositor_view *view = INTERNAL(window)->view;
252+
253+- compositor_view_set_border_color(view, border_color);
254+- compositor_view_set_border_width(view, border_width);
255++ compositor_view_set_border_color(view, outer_border_color, inner_border_color);
256++ compositor_view_set_border_width(view, outer_border_width, inner_border_width);
257+ }
258+
259++
260+ EXPORT void
261+ swc_window_begin_move(struct swc_window *window)
262+ {
263+--
264+2.49.0
265+
1@@ -0,0 +1,383 @@
2+From c527d1aeb3199c92dccd973b97c388f44035e929 Mon Sep 17 00:00:00 2001
3+From: shrub <maybeshrub@gmail.com>
4+Date: Tue, 16 Dec 2025 01:03:48 +0000
5+Subject: [PATCH] scroll/axis bindings this adds bindings for the pointer axis.
6+ it also adds a swc_pointer_send-axis helper for compositors that want to
7+ sometimes intercept axis events. various window stacking helpers were also
8+ added
9+
10+---
11+ libswc/bindings.c | 66 +++++++++++++++++++++++++++++++-
12+ libswc/compositor.c | 92 +++++++++++++++++++++++++++++++++++++++++++++
13+ libswc/pointer.c | 37 ++++++++++++++++++
14+ libswc/swc.h | 32 ++++++++++++++++
15+ libswc/window.c | 12 ++++++
16+ 5 files changed, 238 insertions(+), 1 deletion(-)
17+
18+diff --git a/libswc/bindings.c b/libswc/bindings.c
19+index 32c2aa1..2894632 100644
20+--- a/libswc/bindings.c
21++++ b/libswc/bindings.c
22+@@ -40,6 +40,13 @@ struct binding {
23+ void *data;
24+ };
25+
26++struct axis_binding {
27++ uint32_t axis;
28++ uint32_t modifiers;
29++ swc_axis_binding_handler handler;
30++ void *data;
31++};
32++
33+ static bool handle_key(struct keyboard *keyboard, uint32_t time, struct key *key, uint32_t state);
34+
35+ static struct keyboard_handler key_binding_handler = {
36+@@ -47,12 +54,15 @@ static struct keyboard_handler key_binding_handler = {
37+ };
38+
39+ static bool handle_button(struct pointer_handler *handler, uint32_t time, struct button *button, uint32_t state);
40++static bool handle_axis(struct pointer_handler *handler, uint32_t time, enum wl_pointer_axis axis,
41++ enum wl_pointer_axis_source source, wl_fixed_t value, int value120);
42+
43+ static struct pointer_handler button_binding_handler = {
44+ .button = handle_button,
45++ .axis = handle_axis,
46+ };
47+
48+-static struct wl_array key_bindings, button_bindings;
49++static struct wl_array key_bindings, button_bindings, axis_bindings;
50+
51+ const struct swc_bindings swc_bindings = {
52+ .keyboard_handler = &key_binding_handler,
53+@@ -107,6 +117,19 @@ find_button_binding(uint32_t modifiers, uint32_t value)
54+ return find_binding(&button_bindings, modifiers, value);
55+ }
56+
57++static struct axis_binding *
58++find_axis_binding(uint32_t modifiers, uint32_t axis)
59++{
60++ struct axis_binding *binding;
61++
62++ wl_array_for_each (binding, &axis_bindings) {
63++ if (binding->axis == axis && (binding->modifiers == modifiers || binding->modifiers == SWC_MOD_ANY))
64++ return binding;
65++ }
66++
67++ return NULL;
68++}
69++
70+ static bool
71+ handle_binding(uint32_t time, struct press *press, uint32_t state, struct binding *(*find_binding)(uint32_t, uint32_t))
72+ {
73+@@ -140,11 +163,35 @@ handle_button(struct pointer_handler *handler, uint32_t time, struct button *but
74+ return handle_binding(time, &button->press, state, &find_button_binding);
75+ }
76+
77++bool
78++handle_axis(struct pointer_handler *handler, uint32_t time, enum wl_pointer_axis axis,
79++ enum wl_pointer_axis_source source, wl_fixed_t value, int value120)
80++{
81++ (void)handler;
82++ (void)source;
83++
84++ struct axis_binding *binding = find_axis_binding(swc.seat->keyboard->modifiers, axis);
85++ int32_t delta120 = value120;
86++
87++ if (!binding)
88++ return false;
89++
90++ if (!delta120 && value) {
91++ delta120 = (int32_t)(wl_fixed_to_double(value) * 120.0);
92++ if (!delta120)
93++ delta120 = value > 0 ? 1 : -1;
94++ }
95++
96++ binding->handler(binding->data, time, axis, delta120);
97++ return true;
98++}
99++
100+ bool
101+ bindings_initialize(void)
102+ {
103+ wl_array_init(&key_bindings);
104+ wl_array_init(&button_bindings);
105++ wl_array_init(&axis_bindings);
106+
107+ return true;
108+ }
109+@@ -154,6 +201,7 @@ bindings_finalize(void)
110+ {
111+ wl_array_release(&key_bindings);
112+ wl_array_release(&button_bindings);
113++ wl_array_release(&axis_bindings);
114+ }
115+
116+ EXPORT int
117+@@ -183,3 +231,19 @@ swc_add_binding(enum swc_binding_type type, uint32_t modifiers, uint32_t value,
118+
119+ return 0;
120+ }
121++
122++EXPORT int
123++swc_add_axis_binding(uint32_t modifiers, uint32_t axis, swc_axis_binding_handler handler, void *data)
124++{
125++ struct axis_binding *binding;
126++
127++ if (!(binding = wl_array_add(&axis_bindings, sizeof(*binding))))
128++ return -ENOMEM;
129++
130++ binding->axis = axis;
131++ binding->modifiers = modifiers;
132++ binding->handler = handler;
133++ binding->data = data;
134++
135++ return 0;
136++}
137+diff --git a/libswc/compositor.c b/libswc/compositor.c
138+index 5f45a16..ed56b7f 100644
139+--- a/libswc/compositor.c
140++++ b/libswc/compositor.c
141+@@ -634,6 +634,98 @@ swc_window_at(int32_t x, int32_t y)
142+ return view ? &view->window->base : NULL;
143+ }
144+
145++static struct compositor_view *
146++view_for_window(struct swc_window *base)
147++{
148++ struct window *window;
149++
150++ if (!base)
151++ return NULL;
152++
153++ window = (struct window *)base;
154++ return window->view;
155++}
156++
157++static struct compositor_view *
158++prev_window_view(struct compositor_view *view)
159++{
160++ struct wl_list *link;
161++ struct compositor_view *other;
162++
163++ for (link = view->link.prev; link != &compositor.views; link = link->prev) {
164++ other = wl_container_of(link, other, link);
165++
166++ if (other->visible && other->window)
167++ return other;
168++ }
169++
170++ return NULL;
171++}
172++
173++static struct compositor_view *
174++next_window_view(struct compositor_view *view)
175++{
176++ struct wl_list *link;
177++ struct compositor_view *other;
178++
179++ for (link = view->link.next; link != &compositor.views; link = link->next) {
180++ other = wl_container_of(link, other, link);
181++
182++ if (other->visible && other->window)
183++ return other;
184++ }
185++
186++ return NULL;
187++}
188++
189++static void
190++damage_views(struct compositor_view *a, struct compositor_view *b)
191++{
192++ uint32_t screens = a->base.screens | (b ? b->base.screens : 0);
193++
194++ a->border.damaged = true;
195++ pixman_region32_union_rect(&compositor.damage, &compositor.damage,
196++ a->extents.x1, a->extents.y1,
197++ (uint32_t)(a->extents.x2 - a->extents.x1),
198++ (uint32_t)(a->extents.y2 - a->extents.y1));
199++
200++ if (b) {
201++ b->border.damaged = true;
202++ pixman_region32_union_rect(&compositor.damage, &compositor.damage,
203++ b->extents.x1, b->extents.y1,
204++ (uint32_t)(b->extents.x2 - b->extents.x1),
205++ (uint32_t)(b->extents.y2 - b->extents.y1));
206++ }
207++
208++ schedule_updates(screens);
209++}
210++
211++EXPORT void
212++swc_window_stack(struct swc_window *window, int32_t direction)
213++{
214++ struct compositor_view *view = view_for_window(window);
215++ struct compositor_view *other = NULL;
216++
217++ if (!view || !view->visible || direction == 0)
218++ return;
219++
220++ if (direction < 0) {
221++ other = prev_window_view(view);
222++ if (!other)
223++ return;
224++ wl_list_remove(&view->link);
225++ wl_list_insert(other->link.prev, &view->link);
226++ } else {
227++ other = next_window_view(view);
228++ if (!other)
229++ return;
230++ wl_list_remove(&view->link);
231++ wl_list_insert(&other->link, &view->link);
232++ }
233++
234++ damage_views(view, other);
235++}
236++
237+ struct compositor_view *
238+ compositor_create_view(struct surface *surface)
239+ {
240+diff --git a/libswc/pointer.c b/libswc/pointer.c
241+index 184186f..cb2582c 100644
242+--- a/libswc/pointer.c
243++++ b/libswc/pointer.c
244+@@ -57,6 +57,43 @@ swc_pointer_send_button(uint32_t time, uint32_t button, uint32_t state)
245+ pointer->client_axis_source = -1;
246+ }
247+
248++EXPORT void
249++swc_pointer_send_axis(uint32_t time, uint32_t axis, int32_t value120)
250++{
251++ struct pointer *pointer = swc.seat ? swc.seat->pointer : NULL;
252++ struct wl_resource *resource;
253++ wl_fixed_t value;
254++
255++ if (!pointer || wl_list_empty(&pointer->focus.active))
256++ return;
257++
258++ value = wl_fixed_from_double((double)value120 / 120.0);
259++
260++ wl_resource_for_each (resource, &pointer->focus.active) {
261++ int ver = wl_resource_get_version(resource);
262++
263++ if (ver >= WL_POINTER_AXIS_SOURCE_SINCE_VERSION)
264++ wl_pointer_send_axis_source(resource, WL_POINTER_AXIS_SOURCE_WHEEL);
265++ if (value120) {
266++ if (ver >= WL_POINTER_AXIS_VALUE120_SINCE_VERSION)
267++ wl_pointer_send_axis_value120(resource, axis, value120);
268++ else if (ver >= WL_POINTER_AXIS_DISCRETE_SINCE_VERSION)
269++ wl_pointer_send_axis_discrete(resource, axis, value120 / 120);
270++ }
271++
272++ if (value)
273++ wl_pointer_send_axis(resource, time, axis, value);
274++ else if (ver >= WL_POINTER_AXIS_STOP_SINCE_VERSION)
275++ wl_pointer_send_axis_stop(resource, time, axis);
276++ }
277++
278++ wl_resource_for_each (resource, &pointer->focus.active) {
279++ if (wl_resource_get_version(resource) >= WL_POINTER_FRAME_SINCE_VERSION)
280++ wl_pointer_send_frame(resource);
281++ }
282++ pointer->client_axis_source = -1;
283++}
284++
285+ static void
286+ enter(struct input_focus_handler *handler, struct wl_list *resources, struct compositor_view *view)
287+ {
288+diff --git a/libswc/swc.h b/libswc/swc.h
289+index 24c8c3b..2aa06c8 100644
290+--- a/libswc/swc.h
291++++ b/libswc/swc.h
292+@@ -53,6 +53,16 @@ bool swc_cursor_position(int32_t *x, int32_t *y);
293+ */
294+ void swc_pointer_send_button(uint32_t time, uint32_t button, uint32_t state);
295+
296++/**
297++ * Send a pointer axis event to the currently focused client.
298++ *
299++ * This is intended for window managers which intercept axis events (for
300++ * example for mouse chords) but want normal scrolling to still reach clients.
301++ *
302++ * value120 uses the wl_pointer "120 units" convention.
303++ */
304++void swc_pointer_send_axis(uint32_t time, uint32_t axis, int32_t value120);
305++
306+ /**
307+ * draw [or update] a simple box overlay
308+ *
309+@@ -262,6 +272,11 @@ void swc_window_set_size(struct swc_window *window, uint32_t width, uint32_t hei
310+ */
311+ void swc_window_set_geometry(struct swc_window *window, const struct swc_rectangle *geometry);
312+
313++/**
314++ * Get the window's current geometry in compositor-global coordinates.
315++ */
316++bool swc_window_get_geometry(const struct swc_window *window, struct swc_rectangle *geometry);
317++
318+ /**
319+ * Set the window's border color and width.
320+ *
321+@@ -307,6 +322,14 @@ void swc_window_end_resize(struct swc_window *window);
322+ */
323+ struct swc_window *swc_window_at(int32_t x, int32_t y);
324+
325++/**
326++ * move a window in the stacking order by one step
327++ *
328++ * direction < 0 moves the window towards the front (higher)
329++ * direction > 0 moves the window towards the back (lower)
330++ */
331++void swc_window_stack(struct swc_window *window, int32_t direction);
332++
333+ /* }}} */
334+
335+ /* Bindings {{{ */
336+@@ -325,6 +348,7 @@ enum swc_binding_type {
337+ };
338+
339+ typedef void (*swc_binding_handler)(void *data, uint32_t time, uint32_t value, uint32_t state);
340++typedef void (*swc_axis_binding_handler)(void *data, uint32_t time, uint32_t axis, int32_t value120);
341+
342+ /**
343+ * Register a new input binding.
344+@@ -333,6 +357,14 @@ typedef void (*swc_binding_handler)(void *data, uint32_t time, uint32_t value, u
345+ */
346+ int swc_add_binding(enum swc_binding_type type, uint32_t modifiers, uint32_t value, swc_binding_handler handler, void *data);
347+
348++/**
349++ * register a new pointer axis binding
350++ *
351++ * this will intercept axis events from clients; use swc_pointer_send_axis()
352++ * from the handler to forward events when appropriate
353++ */
354++int swc_add_axis_binding(uint32_t modifiers, uint32_t axis, swc_axis_binding_handler handler, void *data);
355++
356+ /* }}} */
357+
358+ /**
359+diff --git a/libswc/window.c b/libswc/window.c
360+index 6e5a235..b1fdeb9 100644
361+--- a/libswc/window.c
362++++ b/libswc/window.c
363+@@ -248,6 +248,18 @@ swc_window_set_geometry(struct swc_window *window, const struct swc_rectangle *g
364+ swc_window_set_position(window, geometry->x, geometry->y);
365+ }
366+
367++EXPORT bool
368++swc_window_get_geometry(const struct swc_window *base, struct swc_rectangle *geometry)
369++{
370++ struct window *window = INTERNAL((struct swc_window *)base);
371++
372++ if (!window || !geometry)
373++ return false;
374++
375++ *geometry = window->view->base.geometry;
376++ return true;
377++}
378++
379+ EXPORT void
380+ swc_window_set_border(struct swc_window *window, uint32_t inner_border_color, uint32_t inner_border_width,
381+ uint32_t outer_border_color, uint32_t outer_border_width)
382+--
383+2.49.0
384+
1@@ -0,0 +1,215 @@
2+From e2aa9440d9db7422d3e671a2dec839936c424a0b Mon Sep 17 00:00:00 2001
3+From: shrub <maybeshrub@gmail.com>
4+Date: Tue, 16 Dec 2025 02:28:16 +0000
5+Subject: [PATCH] fix how border damage is calculated | ignore null axis
6+ handlers
7+
8+---
9+ libswc/bindings.c | 8 ++--
10+ libswc/compositor.c | 93 ++++++++++++++++++++++++++++++++-------------
11+ 2 files changed, 71 insertions(+), 30 deletions(-)
12+
13+diff --git a/libswc/bindings.c b/libswc/bindings.c
14+index 2894632..21e2cf7 100644
15+--- a/libswc/bindings.c
16++++ b/libswc/bindings.c
17+@@ -134,9 +134,10 @@ static bool
18+ handle_binding(uint32_t time, struct press *press, uint32_t state, struct binding *(*find_binding)(uint32_t, uint32_t))
19+ {
20+ struct binding *binding;
21++ uint32_t modifiers = swc.seat && swc.seat->keyboard ? swc.seat->keyboard->modifiers : 0;
22+
23+ if (state) {
24+- binding = find_binding(swc.seat->keyboard->modifiers, press->value);
25++ binding = find_binding(modifiers, press->value);
26+
27+ if (!binding)
28+ return false;
29+@@ -170,10 +171,11 @@ handle_axis(struct pointer_handler *handler, uint32_t time, enum wl_pointer_axis
30+ (void)handler;
31+ (void)source;
32+
33+- struct axis_binding *binding = find_axis_binding(swc.seat->keyboard->modifiers, axis);
34++ uint32_t modifiers = swc.seat && swc.seat->keyboard ? swc.seat->keyboard->modifiers : 0;
35++ struct axis_binding *binding = find_axis_binding(modifiers, axis);
36+ int32_t delta120 = value120;
37+
38+- if (!binding)
39++ if (!binding || !binding->handler)
40+ return false;
41+
42+ if (!delta120 && value) {
43+diff --git a/libswc/compositor.c b/libswc/compositor.c
44+index ed56b7f..263da39 100644
45+--- a/libswc/compositor.c
46++++ b/libswc/compositor.c
47+@@ -49,10 +49,33 @@
48+ #include <stdlib.h>
49+ #include <stdio.h>
50+ #include <assert.h>
51++#include <limits.h>
52+ #include <wld/wld.h>
53+ #include <wld/drm.h>
54+ #include <xkbcommon/xkbcommon-keysyms.h>
55+
56++static inline int32_t
57++clamp_i32(int64_t v)
58++{
59++ if (v > INT32_MAX)
60++ return INT32_MAX;
61++ if (v < INT32_MIN)
62++ return INT32_MIN;
63++ return (int32_t)v;
64++}
65++
66++static inline uint32_t
67++span_u32(int32_t a, int32_t b)
68++{
69++ int64_t d = (int64_t)b - (int64_t)a;
70++
71++ if (d <= 0)
72++ return 0;
73++ if (d > UINT32_MAX)
74++ return UINT32_MAX;
75++ return (uint32_t)d;
76++}
77++
78+ struct target {
79+ struct wld_surface *surface;
80+ struct wld_buffer *next_buffer, *current_buffer;
81+@@ -205,13 +228,13 @@ repaint_view(struct target *target, struct compositor_view *view, pixman_region3
82+ pixman_region32_subtract(&border_damage, &view_damage, &view_region);
83+ pixman_region32_intersect(&view_damage, &view_damage, &view_region);
84+
85+- pixman_region32_fini(&view_region);
86+-
87+ if (pixman_region32_not_empty(&view_damage)) {
88+ pixman_region32_translate(&view_damage, -geom->x, -geom->y);
89+ wld_copy_region(swc.drm->renderer, view->buffer, geom->x - target_geom->x, geom->y - target_geom->y, &view_damage);
90+ }
91+
92++ pixman_region32_fini(&view_damage);
93++
94+ pixman_region32_t in_rect;
95+ pixman_region32_init_rect(&in_rect,
96+ geom->x - view->border.inwidth,
97+@@ -226,7 +249,9 @@ repaint_view(struct target *target, struct compositor_view *view, pixman_region3
98+ pixman_region32_t in_border;
99+ pixman_region32_init(&in_border);
100+ pixman_region32_subtract(&in_border, &in_rect, &view_region);
101++ pixman_region32_intersect(&in_border, &in_border, &border_damage);
102+
103++ pixman_region32_fini(&view_region);
104+
105+ /* Draw border */
106+ if (view->border.outwidth > 0 && pixman_region32_not_empty(&out_border)) {
107+@@ -386,17 +411,26 @@ damage_view(struct compositor_view *view)
108+ damage_below_view(view);
109+ view->border.damaged_border1 = true;
110+ view->border.damaged_border2 = true;
111+-
112+ }
113++
114+ static void
115+ update_extents(struct compositor_view *view)
116+ {
117+- uint32_t total_border = view->border.outwidth + view->border.inwidth;
118+-
119+- view->extents.x1 = view->base.geometry.x - total_border;
120+- view->extents.y1 = view->base.geometry.y - total_border;
121+- view->extents.x2 = view->base.geometry.x + view->base.geometry.width + total_border;
122+- view->extents.y2 = view->base.geometry.y + view->base.geometry.height + total_border;
123++ int64_t total_border = (int64_t)view->border.outwidth + (int64_t)view->border.inwidth;
124++ int64_t x = view->base.geometry.x;
125++ int64_t y = view->base.geometry.y;
126++ int64_t w = view->base.geometry.width;
127++ int64_t h = view->base.geometry.height;
128++
129++ view->extents.x1 = clamp_i32(x - total_border);
130++ view->extents.y1 = clamp_i32(y - total_border);
131++ view->extents.x2 = clamp_i32(x + w + total_border);
132++ view->extents.y2 = clamp_i32(y + h + total_border);
133++
134++ if (view->extents.x2 < view->extents.x1)
135++ view->extents.x2 = view->extents.x1;
136++ if (view->extents.y2 < view->extents.y1)
137++ view->extents.y2 = view->extents.y1;
138+
139+ /* Damage border. */
140+ view->border.damaged_border1 = true;
141+@@ -621,8 +655,8 @@ raise_window(struct compositor_view *view)
142+ view->border.damaged_border1 = true;
143+ pixman_region32_union_rect(&compositor.damage, &compositor.damage,
144+ view->extents.x1, view->extents.y1,
145+- (uint32_t)(view->extents.x2 - view->extents.x1),
146+- (uint32_t)(view->extents.y2 - view->extents.y1));
147++ span_u32(view->extents.x1, view->extents.x2),
148++ span_u32(view->extents.y1, view->extents.y2));
149+ schedule_updates(screens);
150+ }
151+
152+@@ -683,18 +717,20 @@ damage_views(struct compositor_view *a, struct compositor_view *b)
153+ {
154+ uint32_t screens = a->base.screens | (b ? b->base.screens : 0);
155+
156+- a->border.damaged = true;
157++ a->border.damaged_border1 = true;
158++ a->border.damaged_border2 = true;
159+ pixman_region32_union_rect(&compositor.damage, &compositor.damage,
160+ a->extents.x1, a->extents.y1,
161+- (uint32_t)(a->extents.x2 - a->extents.x1),
162+- (uint32_t)(a->extents.y2 - a->extents.y1));
163++ span_u32(a->extents.x1, a->extents.x2),
164++ span_u32(a->extents.y1, a->extents.y2));
165+
166+ if (b) {
167+- b->border.damaged = true;
168++ b->border.damaged_border1 = true;
169++ b->border.damaged_border2 = true;
170+ pixman_region32_union_rect(&compositor.damage, &compositor.damage,
171+ b->extents.x1, b->extents.y1,
172+- (uint32_t)(b->extents.x2 - b->extents.x1),
173+- (uint32_t)(b->extents.y2 - b->extents.y1));
174++ span_u32(b->extents.x1, b->extents.x2),
175++ span_u32(b->extents.y1, b->extents.y2));
176+ }
177+
178+ schedule_updates(screens);
179+@@ -910,21 +946,24 @@ calculate_damage(void)
180+ pixman_region32_clear(surface_damage);
181+ }
182+
183+- /* redraw entire thingy if either */
184+- if (view->border.damaged_border1 || view->border.damaged_border2) {
185+- pixman_region32_t border_region, view_region;
186++ /* redraw entire thingy if either */
187++ if (view->border.damaged_border1 || view->border.damaged_border2) {
188++ pixman_region32_t border_region, view_region;
189++
190++ pixman_region32_init_with_extents(&border_region, &view->extents);
191++ pixman_region32_init_rect(&view_region, geom->x, geom->y, geom->width, geom->height);
192+
193+- pixman_region32_init_with_extents(&border_region, &view->extents);
194+- pixman_region32_init_rect(&view_region, geom->x, geom->y, geom->width, geom->height);
195++ pixman_region32_subtract(&border_region, &border_region, &view_region);
196+
197+- pixman_region32_subtract(&border_region, &border_region, &view_region);
198++ pixman_region32_union(&compositor.damage, &compositor.damage, &border_region);
199+
200+- pixman_region32_union(&compositor.damage, &compositor.damage, &border_region);
201++ pixman_region32_fini(&border_region);
202++ pixman_region32_fini(&view_region);
203+
204+- pixman_region32_fini(&border_region);
205+- pixman_region32_fini(&view_region);
206++ view->border.damaged_border1 = false;
207++ view->border.damaged_border2 = false;
208++ }
209+ }
210+- }
211+
212+ pixman_region32_fini(&surface_opaque);
213+ }
214+--
215+2.49.0
216+
+134,
-0
1@@ -0,0 +1,134 @@
2+From 6c4cfa23f28c67ae588423bdb2f3842848972ac7 Mon Sep 17 00:00:00 2001
3+From: uint <abhinav.prsai@gmail.com>
4+Date: Sun, 15 Feb 2026 12:40:13 +0000
5+Subject: [PATCH] add motion throttle
6+
7+fixes random crashes from overloading the server with
8+events. window managers are unaffected as there is a
9+def_motion_throttle_ms=60 and should even fix some
10+---
11+ libswc/swc.h | 1 +
12+ libswc/window.c | 27 +++++++++++++++++++++++++++
13+ libswc/window.h | 2 ++
14+ 3 files changed, 30 insertions(+)
15+
16+diff --git a/libswc/swc.h b/libswc/swc.h
17+index 2aa06c8..3776929 100644
18+--- a/libswc/swc.h
19++++ b/libswc/swc.h
20+@@ -192,6 +192,7 @@ struct swc_window {
21+ char *app_id;
22+
23+ struct swc_window *parent;
24++ uint32_t motion_throttle_ms;
25+ };
26+
27+ /**
28+diff --git a/libswc/window.c b/libswc/window.c
29+index b1fdeb9..bfbbbbd 100644
30+--- a/libswc/window.c
31++++ b/libswc/window.c
32+@@ -36,8 +36,23 @@
33+
34+ #define INTERNAL(w) ((struct window *)(w))
35+
36++static const uint32_t def_motion_throttle_ms = 16;
37++
38+ static const struct swc_window_handler null_handler;
39+
40++static bool
41++should_throttle_motion(uint32_t throttle_ms, uint32_t *last_time, uint32_t time)
42++{
43++ if (!throttle_ms)
44++ return false;
45++
46++ if (*last_time && time - *last_time < throttle_ms)
47++ return true;
48++
49++ *last_time = time;
50++ return false;
51++}
52++
53+ static void
54+ handle_window_enter(struct wl_listener *listener, void *data)
55+ {
56+@@ -299,6 +314,10 @@ static bool
57+ move_motion(struct pointer_handler *handler, uint32_t time, wl_fixed_t fx, wl_fixed_t fy)
58+ {
59+ struct window *window = wl_container_of(handler, window, move.interaction.handler);
60++
61++ if (should_throttle_motion(window->base.motion_throttle_ms, &window->move.last_time, time))
62++ return true;
63++
64+ int32_t x = wl_fixed_to_int(fx) + window->move.offset.x,
65+ y = wl_fixed_to_int(fy) + window->move.offset.y;
66+
67+@@ -313,6 +332,9 @@ resize_motion(struct pointer_handler *handler, uint32_t time, wl_fixed_t fx, wl_
68+ const struct swc_rectangle *geometry = &window->view->base.geometry;
69+ uint32_t width = geometry->width, height = geometry->height;
70+
71++ if (should_throttle_motion(window->base.motion_throttle_ms, &window->resize.last_time, time))
72++ return true;
73++
74+ if (window->resize.edges & SWC_WINDOW_EDGE_LEFT)
75+ width -= wl_fixed_to_int(fx) + window->resize.offset.x - geometry->x;
76+ else if (window->resize.edges & SWC_WINDOW_EDGE_RIGHT)
77+@@ -389,9 +411,11 @@ window_initialize(struct window *window, const struct window_impl *impl, struct
78+ window->handler = &null_handler;
79+ window->view_handler.impl = &view_handler_impl;
80+ window->view->window = window;
81++ window->base.motion_throttle_ms = def_motion_throttle_ms;
82+ window->managed = false;
83+ window->mode = WINDOW_MODE_STACKED;
84+ window->move.pending = false;
85++ window->move.last_time = 0;
86+ window->move.interaction.active = false;
87+ window->move.interaction.handler = (struct pointer_handler){
88+ .motion = move_motion,
89+@@ -405,6 +429,7 @@ window_initialize(struct window *window, const struct window_impl *impl, struct
90+ .motion = resize_motion,
91+ .button = handle_button,
92+ };
93++ window->resize.last_time = 0;
94+
95+ wl_list_insert(&window->view->base.handlers, &window->view_handler.link);
96+
97+@@ -491,6 +516,7 @@ window_begin_move(struct window *window, struct button *button)
98+ py = wl_fixed_to_int(swc.seat->pointer->y);
99+
100+ begin_interaction(&window->move.interaction, button);
101++ window->move.last_time = 0;
102+ window->move.offset.x = geometry->x - px;
103+ window->move.offset.y = geometry->y - py;
104+ }
105+@@ -509,6 +535,7 @@ window_begin_resize(struct window *window, uint32_t edges, struct button *button
106+ py = wl_fixed_to_int(swc.seat->pointer->y);
107+
108+ begin_interaction(&window->resize.interaction, button);
109++ window->resize.last_time = 0;
110+
111+ if (!edges) {
112+ edges |= (px < geometry->x + geometry->width / 2) ? SWC_WINDOW_EDGE_LEFT : SWC_WINDOW_EDGE_RIGHT;
113+diff --git a/libswc/window.h b/libswc/window.h
114+index 08c9d36..d6684a8 100644
115+--- a/libswc/window.h
116++++ b/libswc/window.h
117+@@ -61,6 +61,7 @@ struct window {
118+
119+ bool pending;
120+ int32_t x, y;
121++ uint32_t last_time;
122+ } move;
123+
124+ struct {
125+@@ -69,6 +70,7 @@ struct window {
126+ int32_t x, y;
127+ } offset;
128+ uint32_t edges;
129++ uint32_t last_time;
130+ } resize;
131+
132+ struct {
133+--
134+2.49.0
135+
1@@ -0,0 +1,99 @@
2+From af4e4ebba3d7fd5840c200c26e339246242bbee7 Mon Sep 17 00:00:00 2001
3+From: uint <abhinav.prsai@gmail.com>
4+Date: Sun, 15 Feb 2026 14:24:18 +0000
5+Subject: [PATCH] add min/max window width/height
6+
7+fixes swc wm dying if resize value is too large
8+(unsigned wrapping back)
9+---
10+ libswc/swc.h | 4 ++++
11+ libswc/window.c | 37 +++++++++++++++++++++++++++++++++++++
12+ 2 files changed, 41 insertions(+)
13+
14+diff --git a/libswc/swc.h b/libswc/swc.h
15+index 3776929..dde7ef0 100644
16+--- a/libswc/swc.h
17++++ b/libswc/swc.h
18+@@ -193,6 +193,10 @@ struct swc_window {
19+
20+ struct swc_window *parent;
21+ uint32_t motion_throttle_ms;
22++ uint32_t min_width;
23++ uint32_t min_height;
24++ uint32_t max_width;
25++ uint32_t max_height;
26+ };
27+
28+ /**
29+diff --git a/libswc/window.c b/libswc/window.c
30+index bfbbbbd..60432ae 100644
31+--- a/libswc/window.c
32++++ b/libswc/window.c
33+@@ -53,6 +53,36 @@ should_throttle_motion(uint32_t throttle_ms, uint32_t *last_time, uint32_t time)
34+ return false;
35+ }
36+
37++static uint32_t
38++clamp_dimension(int32_t value, uint32_t min, uint32_t max)
39++{
40++ if (value < 0)
41++ value = 0;
42++
43++ if (min && value < min)
44++ value = min;
45++
46++ if (max) {
47++ if (min && max < min)
48++ max = min;
49++
50++ if (value > max)
51++ value = max;
52++ }
53++
54++ if (value > UINT32_MAX)
55++ value = UINT32_MAX;
56++
57++ return value;
58++}
59++
60++static void
61++clamp_window_size(const struct window *window, uint32_t *width, uint32_t *height)
62++{
63++ *width = clamp_dimension(*width, window->base.min_width, window->base.max_width);
64++ *height = clamp_dimension(*height, window->base.min_height, window->base.max_height);
65++}
66++
67+ static void
68+ handle_window_enter(struct wl_listener *listener, void *data)
69+ {
70+@@ -241,6 +271,8 @@ swc_window_set_size(struct swc_window *base, uint32_t width, uint32_t height)
71+ struct window *window = INTERNAL(base);
72+ struct swc_rectangle *geom = &window->view->base.geometry;
73+
74++ clamp_window_size(window, &width, &height);
75++
76+ if ((window->configure.pending && width == window->configure.width && height == window->configure.height)
77+ || (!window->configure.pending && width == geom->width && height == geom->height))
78+ {
79+@@ -345,6 +377,7 @@ resize_motion(struct pointer_handler *handler, uint32_t time, wl_fixed_t fx, wl_
80+ else if (window->resize.edges & SWC_WINDOW_EDGE_BOTTOM)
81+ height = wl_fixed_to_int(fy) + window->resize.offset.y - geometry->y;
82+
83++ clamp_window_size(window, &width, &height);
84+ window->impl->configure(window, width, height);
85+
86+ return true;
87+@@ -412,6 +445,10 @@ window_initialize(struct window *window, const struct window_impl *impl, struct
88+ window->view_handler.impl = &view_handler_impl;
89+ window->view->window = window;
90+ window->base.motion_throttle_ms = def_motion_throttle_ms;
91++ window->base.min_width = 0;
92++ window->base.min_height = 0;
93++ window->base.max_width = 0;
94++ window->base.max_height = 0;
95+ window->managed = false;
96+ window->mode = WINDOW_MODE_STACKED;
97+ window->move.pending = false;
98+--
99+2.49.0
100+
1@@ -0,0 +1,232 @@
2+From b89f8f1c30e8d92e33d977ebd8a014281aa4d340 Mon Sep 17 00:00:00 2001
3+From: shrub <maybeshrub@gmail.com>
4+Date: Wed, 17 Dec 2025 02:04:55 +0000
5+Subject: [PATCH] custom cursors add support for custom cursor images from
6+ ARGB. there are two modes, client-managed cursor and compositor cursor
7+ override. api has been added for switcing modes and changing cursor.
8+
9+---
10+ libswc/pointer.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++-
11+ libswc/swc.h | 43 +++++++++++++++++
12+ 2 files changed, 161 insertions(+), 1 deletion(-)
13+
14+diff --git a/libswc/pointer.c b/libswc/pointer.c
15+index cb2582c..cff6bb6 100644
16+--- a/libswc/pointer.c
17++++ b/libswc/pointer.c
18+@@ -35,8 +35,19 @@
19+
20+ #include <assert.h>
21+ #include <stdio.h>
22++#include <string.h>
23+ #include <wld/wld.h>
24+
25++static enum swc_cursor_kind cursor_override = SWC_CURSOR_DEFAULT;
26++static enum swc_cursor_mode cursor_mode = SWC_CURSOR_MODE_CLIENT;
27++
28++static struct {
29++ const uint32_t *data;
30++ uint32_t width, height;
31++ int32_t hotspot_x, hotspot_y;
32++ bool active;
33++} cursor_images[6];
34++
35+ EXPORT void
36+ swc_pointer_send_button(uint32_t time, uint32_t button, uint32_t state)
37+ {
38+@@ -205,13 +216,114 @@ update_cursor(struct pointer *pointer)
39+ view_move(&pointer->cursor.view, x, y);
40+ }
41+
42++static void
43++drop_client_cursor_surface(struct pointer *pointer)
44++{
45++ if (!pointer || !pointer->cursor.surface)
46++ return;
47++ surface_set_view(pointer->cursor.surface, NULL);
48++ wl_list_remove(&pointer->cursor.destroy_listener.link);
49++ pointer->cursor.surface = NULL;
50++}
51++
52++static void
53++apply_cursor_override(struct pointer *pointer)
54++{
55++ if (!pointer || pointer->cursor.surface)
56++ return;
57++
58++ pointer_set_cursor(pointer, cursor_left_ptr);
59++}
60++
61++EXPORT void
62++swc_set_cursor(enum swc_cursor_kind kind)
63++{
64++ struct pointer *pointer = swc.seat ? swc.seat->pointer : NULL;
65++
66++ cursor_override = kind;
67++
68++ drop_client_cursor_surface(pointer);
69++
70++ apply_cursor_override(pointer);
71++}
72++
73++EXPORT void
74++swc_set_cursor_mode(enum swc_cursor_mode mode)
75++{
76++ struct pointer *pointer = swc.seat ? swc.seat->pointer : NULL;
77++
78++ cursor_mode = mode;
79++ if (cursor_mode == SWC_CURSOR_MODE_COMPOSITOR)
80++ drop_client_cursor_surface(pointer);
81++ apply_cursor_override(pointer);
82++}
83++
84++EXPORT void
85++swc_set_cursor_image(enum swc_cursor_kind kind,
86++ const uint32_t *argb8888,
87++ uint32_t width, uint32_t height,
88++ int32_t hotspot_x, int32_t hotspot_y)
89++{
90++ struct pointer *pointer = swc.seat ? swc.seat->pointer : NULL;
91++
92++ if (kind < 0 || kind >= (int)ARRAY_LENGTH(cursor_images))
93++ return;
94++ if (!argb8888 || width == 0 || height == 0)
95++ return;
96++
97++ cursor_images[kind].data = argb8888;
98++ cursor_images[kind].width = width;
99++ cursor_images[kind].height = height;
100++ cursor_images[kind].hotspot_x = hotspot_x;
101++ cursor_images[kind].hotspot_y = hotspot_y;
102++ cursor_images[kind].active = true;
103++
104++ if (cursor_mode == SWC_CURSOR_MODE_COMPOSITOR)
105++ drop_client_cursor_surface(pointer);
106++ apply_cursor_override(pointer);
107++}
108++
109++EXPORT void
110++swc_clear_cursor_image(enum swc_cursor_kind kind)
111++{
112++ struct pointer *pointer = swc.seat ? swc.seat->pointer : NULL;
113++
114++ if (kind < 0 || kind >= (int)ARRAY_LENGTH(cursor_images))
115++ return;
116++
117++ cursor_images[kind].active = false;
118++ cursor_images[kind].data = NULL;
119++
120++ apply_cursor_override(pointer);
121++}
122++
123+ void
124+ pointer_set_cursor(struct pointer *pointer, uint32_t id)
125+ {
126+ struct cursor *cursor = &cursor_metadata[id];
127++ const uint32_t *data = cursor_data;
128+ union wld_object object = { .ptr = &cursor_data[cursor->offset] };
129+ struct wld_buffer *buffer;
130+
131++ if (id == cursor_left_ptr) {
132++ enum swc_cursor_kind kind = cursor_override;
133++ if (kind < 0 || kind >= (int)ARRAY_LENGTH(cursor_images))
134++ kind = SWC_CURSOR_DEFAULT;
135++
136++ if (cursor_images[kind].active) {
137++ static struct cursor custom_cursor;
138++ custom_cursor.width = (int)cursor_images[kind].width;
139++ custom_cursor.height = (int)cursor_images[kind].height;
140++ custom_cursor.hotspot_x = (int)cursor_images[kind].hotspot_x;
141++ custom_cursor.hotspot_y = (int)cursor_images[kind].hotspot_y;
142++ custom_cursor.offset = 0;
143++
144++ cursor = &custom_cursor;
145++ data = cursor_images[kind].data;
146++ object.ptr = (void *)data;
147++ }
148++ }
149++
150+ if (pointer->cursor.internal_buffer)
151+ wld_buffer_unreference(pointer->cursor.internal_buffer);
152+ if (pointer->cursor.surface) {
153+@@ -315,7 +427,6 @@ pointer_initialize(struct pointer *pointer)
154+ {
155+ struct screen *screen = wl_container_of(swc.screens.next, screen, link);
156+ struct swc_rectangle *geom = &screen->base.geometry;
157+-
158+ /* Center cursor in the geometry of the first screen. */
159+ screen = wl_container_of(swc.screens.next, screen, link);
160+ pointer->x = wl_fixed_from_int(geom->x + geom->width / 2);
161+@@ -407,9 +518,15 @@ set_cursor(struct wl_client *client, struct wl_resource *resource,
162+ struct pointer *pointer = wl_resource_get_user_data(resource);
163+ struct surface *surface;
164+
165++ (void)serial;
166++
167+ if (client != pointer->focus.client)
168+ return;
169+
170++ /* If forcing compositor cursor, ignore client cursor surfaces. */
171++ if (cursor_mode == SWC_CURSOR_MODE_COMPOSITOR || cursor_override != SWC_CURSOR_DEFAULT)
172++ return;
173++
174+ if (pointer->cursor.surface) {
175+ surface_set_view(pointer->cursor.surface, NULL);
176+ wl_list_remove(&pointer->cursor.destroy_listener.link);
177+diff --git a/libswc/swc.h b/libswc/swc.h
178+index dde7ef0..29638d9 100644
179+--- a/libswc/swc.h
180++++ b/libswc/swc.h
181+@@ -63,6 +63,49 @@ void swc_pointer_send_button(uint32_t time, uint32_t button, uint32_t state);
182+ */
183+ void swc_pointer_send_axis(uint32_t time, uint32_t axis, int32_t value120);
184+
185++/* Cursor control (compositor-internal cursor) */
186++enum swc_cursor_kind {
187++ SWC_CURSOR_DEFAULT = 0,
188++ SWC_CURSOR_BOX = 1,
189++ SWC_CURSOR_CROSS = 2,
190++ SWC_CURSOR_SIGHT = 3,
191++ SWC_CURSOR_UP = 4,
192++ SWC_CURSOR_DOWN = 5,
193++};
194++
195++enum swc_cursor_mode {
196++ /* Allow clients to set their own cursors (I-beam, resize, etc). */
197++ SWC_CURSOR_MODE_CLIENT = 0,
198++ /* Force compositor cursor; ignore client wl_pointer.set_cursor. */
199++ SWC_CURSOR_MODE_COMPOSITOR = 1,
200++};
201++
202++/**
203++ * override the compositor's internal cursor
204++ *
205++ * this is intended for window managers to show mode cursors (move/resize/select) like the ones in hevel
206++ * If a client has set its own cursor surface, swc may ignore the override.
207++ */
208++void swc_set_cursor(enum swc_cursor_kind kind);
209++
210++/**
211++ * control whether client cursor surfaces are honored
212++ */
213++void swc_set_cursor_mode(enum swc_cursor_mode mode);
214++
215++/**
216++ * set a custom argb8888 cursor image for a given kind
217++ *
218++ * `argb8888` is a pointer to `width*height` pixels in ARGB8888 order.
219++ * the caller has to keep the pixel memory alive for as long as it may be used
220++ */
221++void swc_set_cursor_image(enum swc_cursor_kind kind,
222++ const uint32_t *argb8888,
223++ uint32_t width, uint32_t height,
224++ int32_t hotspot_x, int32_t hotspot_y);
225++
226++void swc_clear_cursor_image(enum swc_cursor_kind kind);
227++
228+ /**
229+ * draw [or update] a simple box overlay
230+ *
231+--
232+2.49.0
233+
1@@ -0,0 +1,85 @@
2+From d7cc5eb216965f3a9ed14542d25f557aab6042d1 Mon Sep 17 00:00:00 2001
3+From: shrub <maybeshrub@gmail.com>
4+Date: Sat, 10 Jan 2026 00:20:19 +0000
5+Subject: [PATCH] swc_window_get_pid add api to query a window's client pid
6+
7+---
8+ libswc/swc.h | 8 ++++++++
9+ libswc/window.c | 25 +++++++++++++++++++++++++
10+ 2 files changed, 33 insertions(+)
11+
12+diff --git a/libswc/swc.h b/libswc/swc.h
13+index 29638d9..f3bd453 100644
14+--- a/libswc/swc.h
15++++ b/libswc/swc.h
16+@@ -26,6 +26,7 @@
17+
18+ #include <stdbool.h>
19+ #include <stdint.h>
20++#include <sys/types.h>
21+
22+ #ifdef __cplusplus
23+ extern "C" {
24+@@ -325,6 +326,13 @@ void swc_window_set_geometry(struct swc_window *window, const struct swc_rectang
25+ */
26+ bool swc_window_get_geometry(const struct swc_window *window, struct swc_rectangle *geometry);
27+
28++/**
29++ * Get the pid of the client that owns this window
30++ *
31++ * returns pid, or 0 if unavailable
32++ */
33++pid_t swc_window_get_pid(struct swc_window *window);
34++
35+ /**
36+ * Set the window's border color and width.
37+ *
38+diff --git a/libswc/window.c b/libswc/window.c
39+index 60432ae..3e3a714 100644
40+--- a/libswc/window.c
41++++ b/libswc/window.c
42+@@ -27,12 +27,14 @@
43+ #include "internal.h"
44+ #include "keyboard.h"
45+ #include "seat.h"
46++#include "surface.h"
47+ #include "swc.h"
48+ #include "util.h"
49+ #include "view.h"
50+
51+ #include <stdlib.h>
52+ #include <string.h>
53++#include <sys/types.h>
54+
55+ #define INTERNAL(w) ((struct window *)(w))
56+
57+@@ -583,3 +585,26 @@ window_begin_resize(struct window *window, uint32_t edges, struct button *button
58+ window->resize.offset.y = geometry->y - py + ((edges & SWC_WINDOW_EDGE_BOTTOM) ? geometry->height : 0);
59+ window->resize.edges = edges;
60+ }
61++
62++EXPORT pid_t
63++swc_window_get_pid(struct swc_window *base)
64++{
65++ struct window *window = INTERNAL(base);
66++ struct surface *surface;
67++ struct wl_client *client;
68++ pid_t pid;
69++ uid_t uid;
70++ gid_t gid;
71++
72++ if (!window || !window->view || !window->view->surface)
73++ return 0;
74++
75++ surface = window->view->surface;
76++ if (!surface->resource)
77++ return 0;
78++
79++ client = wl_resource_get_client(surface->resource);
80++ wl_client_get_credentials(client, &pid, &uid, &gid);
81++
82++ return pid;
83++}
84+--
85+2.49.0
86+
1@@ -0,0 +1,333 @@
2+From 6d3e048dfc11209f32891ef9fc39586b83eff974 Mon Sep 17 00:00:00 2001
3+From: shrub <maybeshrub@gmail.com>
4+Date: Tue, 20 Jan 2026 22:31:55 +0000
5+Subject: [PATCH] zoom! swc_set_zoom and get_zoom api added zoom does a
6+ fullscreen damage and renders a zoomed scene into a shm buffer, with pixman
7+
8+---
9+ libswc/compositor.c | 249 ++++++++++++++++++++++++++++++++++++++++++--
10+ libswc/swc.h | 13 +++
11+ 2 files changed, 254 insertions(+), 8 deletions(-)
12+
13+diff --git a/libswc/compositor.c b/libswc/compositor.c
14+index 263da39..0f978d9 100644
15+--- a/libswc/compositor.c
16++++ b/libswc/compositor.c
17+@@ -108,6 +108,10 @@ static struct {
18+
19+ bool updating;
20+ struct wl_global *global;
21++
22++ /* zoom level (1.0 = normal, >1 = zoomed in, <1 = zoomed out) */
23++ float zoom;
24++ struct wld_buffer *zoom_buffer;
25+ } compositor;
26+
27+ static struct {
28+@@ -452,6 +456,17 @@ schedule_updates(uint32_t screens)
29+ screens |= screen_mask(screen);
30+ }
31+
32++ /* when zoomed, force full screen damage since actual area differs from world coords */
33++ if (compositor.zoom != 1.0f) {
34++ struct screen *screen;
35++ wl_list_for_each (screen, &swc.screens, link) {
36++ pixman_region32_union_rect(&compositor.damage, &compositor.damage,
37++ screen->base.geometry.x, screen->base.geometry.y,
38++ screen->base.geometry.width, screen->base.geometry.height);
39++ screens |= screen_mask(screen);
40++ }
41++ }
42++
43+ compositor.scheduled_updates |= screens;
44+ }
45+
46+@@ -499,6 +514,202 @@ swc_overlay_clear(void)
47+ schedule_updates(-1);
48+ }
49+
50++EXPORT void
51++swc_set_zoom(float level)
52++{
53++ if (level < 0.1f)
54++ level = 0.1f;
55++ if (level > 10.0f)
56++ level = 10.0f;
57++
58++ if (compositor.zoom != level) {
59++ compositor.zoom = level;
60++ /* damage entire screen to force full repaint */
61++ schedule_updates(-1);
62++ }
63++}
64++
65++EXPORT float
66++swc_get_zoom(void)
67++{
68++ return compositor.zoom;
69++}
70++
71++static pixman_format_code_t
72++wld_to_pixman_format(enum wld_format format)
73++{
74++ switch (format) {
75++ case WLD_FORMAT_XRGB8888:
76++ return PIXMAN_x8r8g8b8;
77++ case WLD_FORMAT_ARGB8888:
78++ return PIXMAN_a8r8g8b8;
79++ default:
80++ return PIXMAN_x8r8g8b8;
81++ }
82++}
83++
84++/* render zoomed view to shm wallpaper unscaled, windows scaled */
85++static struct wld_buffer *
86++render_zoomed_to_shm(struct screen *screen, float zoom)
87++{
88++ uint32_t width = screen->base.geometry.width;
89++ uint32_t height = screen->base.geometry.height;
90++ int32_t screen_x = screen->base.geometry.x;
91++ int32_t screen_y = screen->base.geometry.y;
92++ int32_t cx = screen_x + width / 2;
93++ int32_t cy = screen_y + height / 2;
94++ struct compositor_view *view;
95++
96++ struct wld_buffer *buffer = wld_create_buffer(swc.shm->context,
97++ width, height, WLD_FORMAT_XRGB8888, WLD_FLAG_MAP);
98++ if (!buffer)
99++ return NULL;
100++
101++ if (!wld_set_target_buffer(swc.shm->renderer, buffer)) {
102++ wld_buffer_unreference(buffer);
103++ return NULL;
104++ }
105++
106++ pixman_region32_t full;
107++ pixman_region32_init_rect(&full, 0, 0, width, height);
108++ if (wallbuf)
109++ wld_copy_region(swc.shm->renderer, wallbuf, 0, 0, &full);
110++ else
111++ wld_fill_region(swc.shm->renderer, bgcolor, &full);
112++ pixman_region32_fini(&full);
113++ wld_flush(swc.shm->renderer);
114++
115++ if (!wld_map(buffer)) {
116++ wld_buffer_unreference(buffer);
117++ return NULL;
118++ }
119++
120++ pixman_image_t *dst_img = pixman_image_create_bits(
121++ wld_to_pixman_format(buffer->format),
122++ buffer->width, buffer->height,
123++ buffer->map, buffer->pitch);
124++
125++ if (!dst_img) {
126++ wld_unmap(buffer);
127++ wld_buffer_unreference(buffer);
128++ return NULL;
129++ }
130++
131++ /* render each view with scaling */
132++ wl_list_for_each_reverse(view, &compositor.views, link) {
133++ struct wld_buffer *src = view->buffer;
134++ const struct swc_rectangle *geom = &view->base.geometry;
135++
136++ if (!src)
137++ continue;
138++
139++ if (!(wld_capabilities(swc.shm->renderer, src) & WLD_CAPABILITY_READ))
140++ src = view->base.buffer;
141++ if (!src)
142++ continue;
143++
144++ /* maths zoom position and size */
145++ float zoomed_x = (geom->x - cx) * zoom + width / 2.0f;
146++ float zoomed_y = (geom->y - cy) * zoom + height / 2.0f;
147++ float zoomed_w = geom->width * zoom;
148++ float zoomed_h = geom->height * zoom;
149++
150++ float border_out = view->border.outwidth * zoom;
151++ float border_in = view->border.inwidth * zoom;
152++ float total_border = border_out + border_in;
153++
154++ if (zoomed_x + zoomed_w + total_border < 0 || zoomed_x - total_border >= (int32_t)width ||
155++ zoomed_y + zoomed_h + total_border < 0 || zoomed_y - total_border >= (int32_t)height)
156++ continue;
157++
158++ if (view->border.outwidth > 0 && border_out >= 1) {
159++ int32_t bx = (int32_t)(zoomed_x - total_border);
160++ int32_t by = (int32_t)(zoomed_y - total_border);
161++ int32_t bw = (int32_t)(zoomed_w + 2 * total_border);
162++ int32_t bh = (int32_t)(zoomed_h + 2 * total_border);
163++ int32_t bo = (int32_t)border_out;
164++
165++ pixman_color_t color = {
166++ .red = ((view->border.outcolor >> 16) & 0xff) * 257,
167++ .green = ((view->border.outcolor >> 8) & 0xff) * 257,
168++ .blue = (view->border.outcolor & 0xff) * 257,
169++ .alpha = 0xffff
170++ };
171++ pixman_image_t *fill = pixman_image_create_solid_fill(&color);
172++ if (fill) {
173++ pixman_image_composite32(PIXMAN_OP_OVER, fill, NULL, dst_img,
174++ 0, 0, 0, 0, bx, by, bw, bo);
175++ pixman_image_composite32(PIXMAN_OP_OVER, fill, NULL, dst_img,
176++ 0, 0, 0, 0, bx, by + bh - bo, bw, bo);
177++ pixman_image_composite32(PIXMAN_OP_OVER, fill, NULL, dst_img,
178++ 0, 0, 0, 0, bx, by + bo, bo, bh - 2 * bo);
179++ pixman_image_composite32(PIXMAN_OP_OVER, fill, NULL, dst_img,
180++ 0, 0, 0, 0, bx + bw - bo, by + bo, bo, bh - 2 * bo);
181++ pixman_image_unref(fill);
182++ }
183++ }
184++
185++ if (view->border.inwidth > 0 && border_in >= 1) {
186++ int32_t bx = (int32_t)(zoomed_x - border_in);
187++ int32_t by = (int32_t)(zoomed_y - border_in);
188++ int32_t bw = (int32_t)(zoomed_w + 2 * border_in);
189++ int32_t bh = (int32_t)(zoomed_h + 2 * border_in);
190++ int32_t bi = (int32_t)border_in;
191++
192++ pixman_color_t color = {
193++ .red = ((view->border.incolor >> 16) & 0xff) * 257,
194++ .green = ((view->border.incolor >> 8) & 0xff) * 257,
195++ .blue = (view->border.incolor & 0xff) * 257,
196++ .alpha = 0xffff
197++ };
198++ pixman_image_t *fill = pixman_image_create_solid_fill(&color);
199++ if (fill) {
200++ pixman_image_composite32(PIXMAN_OP_OVER, fill, NULL, dst_img,
201++ 0, 0, 0, 0, bx, by, bw, bi);
202++ pixman_image_composite32(PIXMAN_OP_OVER, fill, NULL, dst_img,
203++ 0, 0, 0, 0, bx, by + bh - bi, bw, bi);
204++ pixman_image_composite32(PIXMAN_OP_OVER, fill, NULL, dst_img,
205++ 0, 0, 0, 0, bx, by + bi, bi, bh - 2 * bi);
206++ pixman_image_composite32(PIXMAN_OP_OVER, fill, NULL, dst_img,
207++ 0, 0, 0, 0, bx + bw - bi, by + bi, bi, bh - 2 * bi);
208++ pixman_image_unref(fill);
209++ }
210++ }
211++
212++ if (!wld_map(src))
213++ continue;
214++
215++ pixman_image_t *src_img = pixman_image_create_bits(
216++ wld_to_pixman_format(src->format),
217++ src->width, src->height,
218++ src->map, src->pitch);
219++
220++ if (src_img) {
221++ pixman_transform_t transform;
222++ pixman_transform_init_identity(&transform);
223++ pixman_fixed_t scale = pixman_double_to_fixed(1.0 / zoom);
224++ pixman_transform_scale(&transform, NULL, scale, scale);
225++ pixman_image_set_transform(src_img, &transform);
226++ pixman_image_set_filter(src_img, PIXMAN_FILTER_BILINEAR, NULL, 0);
227++
228++ pixman_image_composite32(PIXMAN_OP_OVER,
229++ src_img, NULL, dst_img,
230++ 0, 0, 0, 0,
231++ (int32_t)zoomed_x, (int32_t)zoomed_y,
232++ (int32_t)(zoomed_w + 1), (int32_t)(zoomed_h + 1));
233++
234++ pixman_image_unref(src_img);
235++ }
236++
237++ wld_unmap(src);
238++ }
239++
240++ pixman_image_unref(dst_img);
241++ wld_unmap(buffer);
242++
243++ return buffer;
244++}
245++
246+ static bool
247+ update(struct view *base)
248+ {
249+@@ -992,14 +1203,32 @@ update_screen(struct screen *screen)
250+ return;
251+ }
252+
253+- pixman_region32_t base_damage;
254+- pixman_region32_copy(&damage, total_damage);
255+- pixman_region32_translate(&damage, geom->x, geom->y);
256+- pixman_region32_init(&base_damage);
257+- pixman_region32_subtract(&base_damage, &damage, &compositor.opaque);
258+- renderer_repaint(target, &damage, &base_damage, &compositor.views);
259+- pixman_region32_fini(&damage);
260+- pixman_region32_fini(&base_damage);
261++ /* check if zoom */
262++ if (compositor.zoom != 1.0f) {
263++ pixman_region32_fini(&damage);
264++
265++ struct wld_buffer *zoomed = render_zoomed_to_shm(screen, compositor.zoom);
266++ if (!zoomed)
267++ return;
268++
269++ pixman_region32_t full;
270++ pixman_region32_init_rect(&full, 0, 0, geom->width, geom->height);
271++ wld_set_target_surface(swc.drm->renderer, target->surface);
272++ wld_copy_region(swc.drm->renderer, zoomed, 0, 0, &full);
273++ wld_flush(swc.drm->renderer);
274++ pixman_region32_fini(&full);
275++
276++ wld_buffer_unreference(zoomed);
277++ } else {
278++ pixman_region32_t base_damage;
279++ pixman_region32_copy(&damage, total_damage);
280++ pixman_region32_translate(&damage, geom->x, geom->y);
281++ pixman_region32_init(&base_damage);
282++ pixman_region32_subtract(&base_damage, &damage, &compositor.opaque);
283++ renderer_repaint(target, &damage, &base_damage, &compositor.views);
284++ pixman_region32_fini(&damage);
285++ pixman_region32_fini(&base_damage);
286++ }
287+
288+ switch (target_swap_buffers(target)) {
289+ case -EACCES:
290+@@ -1158,6 +1387,8 @@ compositor_initialize(void)
291+ compositor.scheduled_updates = 0;
292+ compositor.pending_flips = 0;
293+ compositor.updating = false;
294++ compositor.zoom = 1.0f;
295++ compositor.zoom_buffer = NULL;
296+ pixman_region32_init(&compositor.damage);
297+ pixman_region32_init(&compositor.opaque);
298+ wl_list_init(&compositor.views);
299+@@ -1181,6 +1412,8 @@ compositor_initialize(void)
300+ void
301+ compositor_finalize(void)
302+ {
303++ if (compositor.zoom_buffer)
304++ wld_buffer_unreference(compositor.zoom_buffer);
305+ pixman_region32_fini(&compositor.damage);
306+ pixman_region32_fini(&compositor.opaque);
307+ wl_global_destroy(compositor.global);
308+diff --git a/libswc/swc.h b/libswc/swc.h
309+index f3bd453..505391d 100644
310+--- a/libswc/swc.h
311++++ b/libswc/swc.h
312+@@ -121,6 +121,19 @@ void swc_overlay_set_box(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_
313+ */
314+ void swc_overlay_clear(void);
315+
316++/**
317++ * Set the compositor zoom level.
318++ *
319++ * 1.0 = normal, >1.0 = zoomed in, <1.0 = zoomed out
320++ * Uses software (pixman) scaling.
321++ */
322++void swc_set_zoom(float level);
323++
324++/**
325++ * Get the current zoom level.
326++ */
327++float swc_get_zoom(void);
328++
329+ /* Rectangles {{{ */
330+
331+ struct swc_rectangle {
332+--
333+2.49.0
334+
1@@ -0,0 +1,62 @@
2+From 3a2c4c613b15106497ea464b2ee613a30ca2f112 Mon Sep 17 00:00:00 2001
3+From: dalem <lain@dalem.foo>
4+Date: Tue, 27 Jan 2026 17:36:27 +0100
5+Subject: [PATCH] add fullscreen support
6+
7+---
8+ libswc/window.c | 21 ++++++++++++++++-----
9+ libswc/window.h | 5 +++++
10+ 2 files changed, 21 insertions(+), 5 deletions(-)
11+
12+diff --git a/libswc/window.c b/libswc/window.c
13+index 3e3a714..6322502 100644
14+--- a/libswc/window.c
15++++ b/libswc/window.c
16+@@ -239,12 +239,23 @@ EXPORT void
17+ swc_window_set_fullscreen(struct swc_window *base, struct swc_screen *screen)
18+ {
19+ struct window *window = INTERNAL(base);
20++ struct swc_rectangle geom;
21++ swc_window_get_geometry(base, &geom);
22++
23++ if (window->mode != WINDOW_MODE_FULLSCREEN) {
24++ window->prev.geom = geom;
25++ window->prev.mode = window->mode;
26++ swc_window_set_geometry(base, &screen->usable_geometry);
27++
28++ if (window->impl->set_mode)
29++ window->impl->set_mode(window, WINDOW_MODE_FULLSCREEN);
30++ window->mode = WINDOW_MODE_FULLSCREEN;
31++ }
32+
33+- /* TODO: Implement fullscreen windows. */
34+-
35+- if (window->impl->set_mode)
36+- window->impl->set_mode(window, WINDOW_MODE_FULLSCREEN);
37+- window->mode = WINDOW_MODE_FULLSCREEN;
38++ else {
39++ swc_window_set_geometry(base, &window->prev.geom);
40++ window->mode = window->prev.mode;
41++ }
42+ }
43+
44+ EXPORT void
45+diff --git a/libswc/window.h b/libswc/window.h
46+index d6684a8..71a597f 100644
47+--- a/libswc/window.h
48++++ b/libswc/window.h
49+@@ -52,6 +52,11 @@ struct window {
50+ struct view_handler view_handler;
51+ bool managed;
52+ unsigned mode;
53++
54++ struct {
55++ struct swc_rectangle geom;
56++ unsigned mode;
57++ } prev;
58+
59+ struct {
60+ struct window_pointer_interaction interaction;
61+--
62+2.49.0
63+
1@@ -0,0 +1,124 @@
2+From 73322f31c68b0ef38e16534b80aae31a75ef712c Mon Sep 17 00:00:00 2001
3+From: dalem <lain@dalem.foo>
4+Date: Sat, 20 Dec 2025 21:09:41 +0100
5+Subject: [PATCH] add wallpaper support.
6+
7+---
8+ libswc/compositor.c | 8 ++++++--
9+ libswc/swc.h | 23 ++++++++++++++++++++++
10+ libswc/wallpaper.c | 48 +++++++++++++++++++++++++++++++++++++++++++++
11+ 3 files changed, 77 insertions(+), 2 deletions(-)
12+ create mode 100644 libswc/wallpaper.c
13+
14+diff --git a/libswc/compositor.c b/libswc/compositor.c
15+index 0f978d9..d70377d 100644
16+--- a/libswc/compositor.c
17++++ b/libswc/compositor.c
18+@@ -287,10 +287,14 @@ renderer_repaint(struct target *target, pixman_region32_t *damage, pixman_region
19+
20+ wld_set_target_surface(swc.drm->renderer, target->surface);
21+
22+- /* Paint base damage black. */
23+ if (pixman_region32_not_empty(base_damage)) {
24+ pixman_region32_translate(base_damage, -target->view->geometry.x, -target->view->geometry.y);
25+- wld_fill_region(swc.drm->renderer, 0xff000000, base_damage);
26++
27++ if(wallbuf)
28++ wld_copy_region(swc.drm->renderer, wallbuf, 0, 0, base_damage);
29++
30++ else
31++ wld_fill_region(swc.drm->renderer, bgcolor, base_damage);
32+ }
33+
34+ wl_list_for_each_reverse (view, views, link) {
35+diff --git a/libswc/swc.h b/libswc/swc.h
36+index 505391d..a23e556 100644
37+--- a/libswc/swc.h
38++++ b/libswc/swc.h
39+@@ -436,6 +436,29 @@ int swc_add_axis_binding(uint32_t modifiers, uint32_t axis, swc_axis_binding_han
40+
41+ /* }}} */
42+
43++/* Wallpaper {{{ */
44++
45++extern unsigned char *wallpaper;
46++extern struct wld_buffer *wallbuf;
47++
48++/**
49++ * Set wallpaper to image from fs path.
50++ * TODO: scaling, tiling, maybe diff image for each screen
51++ */
52++
53++void swc_wallpaper_init(char* path);
54++
55++/**
56++ * Set wallpaper to a single color
57++ * pretty much ignored if wallpaper is set to image
58++ * defaults to black
59++ */
60++
61++extern uint32_t bgcolor;
62++void swc_wallpaper_color_set(uint32_t color);
63++
64++/* }}} */
65++
66+ /**
67+ * This is a user-provided structure that swc will use to notify the display
68+ * server of new windows, screens and input devices.
69+diff --git a/libswc/wallpaper.c b/libswc/wallpaper.c
70+new file mode 100644
71+index 0000000..53f8a0c
72+--- /dev/null
73++++ b/libswc/wallpaper.c
74+@@ -0,0 +1,48 @@
75++#include <pixman.h>
76++#include <wld/wld.h>
77++
78++#define STB_IMAGE_IMPLEMENTATION
79++#define STBI_NO_HDR
80++#include "../stb/stb_image.h"
81++
82++#include "swc.h"
83++#include "internal.h"
84++#include "drm.h"
85++#include "util.h"
86++#include "shm.h"
87++
88++unsigned char *wallpaper = NULL;
89++struct wld_buffer *wallbuf = NULL;
90++
91++uint32_t bgcolor = 0xff000000;
92++
93++EXPORT void
94++swc_wallpaper_init(char* path)
95++{
96++ int width, height, chan;
97++
98++ wallpaper = stbi_load(path, &width, &height, &chan, 4);
99++
100++ /* swap color channels to be compatible */
101++ for(int i = 0; i < width * height; i++) {
102++ unsigned char r = wallpaper[i*4];
103++ wallpaper[i*4] = wallpaper[(i*4)+2];
104++ wallpaper[(i*4)+2] = r;
105++ }
106++
107++ union wld_object obj;
108++ obj.ptr = (uint32_t*)wallpaper;
109++
110++ wallbuf = wld_import_buffer(swc.shm->context,
111++ WLD_OBJECT_DATA,
112++ obj,
113++ width, height,
114++ WLD_FORMAT_ARGB8888,
115++ width * 4);
116++}
117++
118++EXPORT void
119++swc_wallpaper_color_set(uint32_t color)
120++{
121++ bgcolor = color;
122++}
123+--
124+2.49.0
125+
1@@ -0,0 +1,68 @@
2+From 5be9ee4cbc06aefed8fe11360df8cc9d045c3587 Mon Sep 17 00:00:00 2001
3+From: shrub <maybeshrub@gmail.com>
4+Date: Sat, 20 Dec 2025 20:40:26 +0000
5+Subject: [PATCH] this adds wallpaper scaling to the initial wallpaper
6+ implementation, so the wallpaper scales to screen size.
7+
8+---
9+ libswc/wallpaper.c | 31 ++++++++++++++++++++++++++++++-
10+ 1 file changed, 30 insertions(+), 1 deletion(-)
11+
12+diff --git a/libswc/wallpaper.c b/libswc/wallpaper.c
13+index 53f8a0c..ad6e21f 100644
14+--- a/libswc/wallpaper.c
15++++ b/libswc/wallpaper.c
16+@@ -5,11 +5,15 @@
17+ #define STBI_NO_HDR
18+ #include "../stb/stb_image.h"
19+
20++#define STB_IMAGE_RESIZE_IMPLEMENTATION
21++#include "../stb/stb_image_resize2.h"
22++
23+ #include "swc.h"
24+ #include "internal.h"
25+ #include "drm.h"
26+ #include "util.h"
27+ #include "shm.h"
28++#include "screen.h"
29+
30+ unsigned char *wallpaper = NULL;
31+ struct wld_buffer *wallbuf = NULL;
32+@@ -20,8 +24,33 @@ EXPORT void
33+ swc_wallpaper_init(char* path)
34+ {
35+ int width, height, chan;
36++ unsigned char *loaded;
37++ struct screen *screen;
38++ int target_width = 0, target_height = 0;
39++
40++ loaded = stbi_load(path, &width, &height, &chan, 4);
41++ if (!loaded)
42++ return;
43+
44+- wallpaper = stbi_load(path, &width, &height, &chan, 4);
45++ /* get screen dimensions */
46++ wl_list_for_each(screen, &swc.screens, link) {
47++ target_width = screen->base.geometry.width;
48++ target_height = screen->base.geometry.height;
49++ break;
50++ }
51++
52++ /* If we have a screen and dimensions wrong scale */
53++ if (target_width > 0 && target_height > 0 &&
54++ (width != target_width || height != target_height)) {
55++ wallpaper = stbir_resize_uint8_srgb(loaded, width, height, 0,
56++ NULL, target_width, target_height, 0,
57++ STBIR_RGBA);
58++ stbi_image_free(loaded);
59++ width = target_width;
60++ height = target_height;
61++ } else {
62++ wallpaper = loaded;
63++ }
64+
65+ /* swap color channels to be compatible */
66+ for(int i = 0; i < width * height; i++) {
67+--
68+2.49.0
69+
1@@ -0,0 +1,44 @@
2+From 108719f480ad292252721ac3c90b75081e1152e5 Mon Sep 17 00:00:00 2001
3+From: dalem <lain@dalem.foo>
4+Date: Sun, 8 Feb 2026 03:33:43 +0100
5+Subject: [PATCH] Rename swc_wallpaper_init to swc_wallpaper_set
6+
7+this change makes it appear as more consistent
8+---
9+ libswc/swc.h | 4 ++--
10+ libswc/wallpaper.c | 2 +-
11+ 2 files changed, 3 insertions(+), 3 deletions(-)
12+
13+diff --git a/libswc/swc.h b/libswc/swc.h
14+index a23e556..033bdd4 100644
15+--- a/libswc/swc.h
16++++ b/libswc/swc.h
17+@@ -443,10 +443,10 @@ extern struct wld_buffer *wallbuf;
18+
19+ /**
20+ * Set wallpaper to image from fs path.
21+- * TODO: scaling, tiling, maybe diff image for each screen
22++ * TODO: tiling, maybe diff image for each screen
23+ */
24+
25+-void swc_wallpaper_init(char* path);
26++void swc_wallpaper_set(char* path);
27+
28+ /**
29+ * Set wallpaper to a single color
30+diff --git a/libswc/wallpaper.c b/libswc/wallpaper.c
31+index ad6e21f..7618bf0 100644
32+--- a/libswc/wallpaper.c
33++++ b/libswc/wallpaper.c
34+@@ -21,7 +21,7 @@ struct wld_buffer *wallbuf = NULL;
35+ uint32_t bgcolor = 0xff000000;
36+
37+ EXPORT void
38+-swc_wallpaper_init(char* path)
39++swc_wallpaper_set(char* path)
40+ {
41+ int width, height, chan;
42+ unsigned char *loaded;
43+--
44+2.49.0
45+
1@@ -0,0 +1,678 @@
2+From c04d6b835bc440cb56babfc63e45c705c1723820 Mon Sep 17 00:00:00 2001
3+From: shrub <maybeshrub@gmail.com>
4+Date: Sat, 31 Jan 2026 21:34:22 +0000
5+Subject: [PATCH] initial subsurface implementation add basic wl_subsurface
6+ support with naive patrent child tracking, subsurface positioning, parent
7+ clipping, synchronized subsurface commits, stacking, and so on. TLDR, it gets
8+ firefox rendering, but it's not very good and crashed often.
9+
10+---
11+ libswc/compositor.c | 69 +++++++++++++--
12+ libswc/compositor.h | 2 +
13+ libswc/subcompositor.c | 13 ++-
14+ libswc/subsurface.c | 190 +++++++++++++++++++++++++++++++++++++++--
15+ libswc/subsurface.h | 17 +++-
16+ libswc/surface.c | 38 ++++++++-
17+ libswc/surface.h | 9 ++
18+ libswc/window.c | 10 ++-
19+ libswc/xdg_shell.c | 19 +++++
20+ 9 files changed, 346 insertions(+), 21 deletions(-)
21+
22+diff --git a/libswc/compositor.c b/libswc/compositor.c
23+index d70377d..d2321b9 100644
24+--- a/libswc/compositor.c
25++++ b/libswc/compositor.c
26+@@ -219,11 +219,18 @@ repaint_view(struct target *target, struct compositor_view *view, pixman_region3
27+ {
28+ pixman_region32_t view_region, view_damage, border_damage;
29+ const struct swc_rectangle *geom = &view->base.geometry, *target_geom = &target->view->geometry;
30++ bool has_parent = view->parent && view->parent != view;
31++ pixman_region32_t parent_region;
32+
33+ if (!view->base.buffer)
34+ return;
35+
36+ pixman_region32_init_rect(&view_region, geom->x, geom->y, geom->width, geom->height);
37++ if (has_parent) {
38++ const struct swc_rectangle *parent_geom = &view->parent->base.geometry;
39++ pixman_region32_init_rect(&parent_region, parent_geom->x, parent_geom->y,
40++ parent_geom->width, parent_geom->height);
41++ }
42+ pixman_region32_init_with_extents(&view_damage, &view->extents);
43+ pixman_region32_init(&border_damage);
44+
45+@@ -231,9 +238,13 @@ repaint_view(struct target *target, struct compositor_view *view, pixman_region3
46+ pixman_region32_subtract(&view_damage, &view_damage, &view->clip);
47+ pixman_region32_subtract(&border_damage, &view_damage, &view_region);
48+ pixman_region32_intersect(&view_damage, &view_damage, &view_region);
49++ if (has_parent) {
50++ pixman_region32_intersect(&view_damage, &view_damage, &parent_region);
51++ pixman_region32_intersect(&border_damage, &border_damage, &parent_region);
52++ }
53+
54+ if (pixman_region32_not_empty(&view_damage)) {
55+- pixman_region32_translate(&view_damage, -geom->x, -geom->y);
56++ pixman_region32_translate(&view_damage, -geom->x + view->buffer_offset_x, -geom->y + view->buffer_offset_y);
57+ wld_copy_region(swc.drm->renderer, view->buffer, geom->x - target_geom->x, geom->y - target_geom->y, &view_damage);
58+ }
59+
60+@@ -272,6 +283,8 @@ repaint_view(struct target *target, struct compositor_view *view, pixman_region3
61+ pixman_region32_fini(&in_rect);
62+ pixman_region32_fini(&out_border);
63+ pixman_region32_fini(&in_border);
64++ if (has_parent)
65++ pixman_region32_fini(&parent_region);
66+
67+ }
68+
69+@@ -731,8 +744,11 @@ static int
70+ attach(struct view *base, struct wld_buffer *buffer)
71+ {
72+ struct compositor_view *view = (void *)base;
73++ struct surface *surface = view->surface;
74+ pixman_box32_t old_extents;
75+ pixman_region32_t old, new, both;
76++ uint32_t new_width = buffer ? buffer->width : 0;
77++ uint32_t new_height = buffer ? buffer->height : 0;
78+ int ret;
79+
80+ if ((ret = renderer_attach(view, buffer)) < 0)
81+@@ -742,7 +758,18 @@ attach(struct view *base, struct wld_buffer *buffer)
82+ * visible on. */
83+ update(&view->base);
84+
85+- if (view_set_size_from_buffer(&view->base, buffer)) {
86++ view->buffer_offset_x = 0;
87++ view->buffer_offset_y = 0;
88++ if (surface && surface->has_window_geometry && buffer) {
89++ if (surface->window_width > 0 && surface->window_height > 0) {
90++ new_width = (uint32_t)surface->window_width;
91++ new_height = (uint32_t)surface->window_height;
92++ view->buffer_offset_x = surface->window_x;
93++ view->buffer_offset_y = surface->window_y;
94++ }
95++ }
96++
97++ if (view_set_size(&view->base, new_width, new_height)) {
98+ /* The view was resized. */
99+ old_extents = view->extents;
100+ update_extents(view);
101+@@ -818,7 +845,8 @@ view_at(int32_t x, int32_t y)
102+ continue;
103+
104+ if (pixman_region32_contains_point(&view->surface->state.input,
105+- x - geom->x, y - geom->y, NULL))
106++ x - geom->x + view->buffer_offset_x,
107++ y - geom->y + view->buffer_offset_y, NULL))
108+ {
109+ return view;
110+ }
111+@@ -992,6 +1020,8 @@ compositor_create_view(struct surface *surface)
112+ view->buffer = NULL;
113+ view->window = NULL;
114+ view->parent = NULL;
115++ view->buffer_offset_x = 0;
116++ view->buffer_offset_y = 0;
117+ view->visible = false;
118+ view->extents.x1 = 0;
119+ view->extents.y1 = 0;
120+@@ -1137,13 +1167,28 @@ calculate_damage(void)
121+ continue;
122+
123+ geom = &view->base.geometry;
124++ bool has_parent = view->parent && view->parent != view;
125++ pixman_region32_t view_region;
126++ pixman_region32_t parent_region;
127++
128++ pixman_region32_init_rect(&view_region, geom->x, geom->y, geom->width, geom->height);
129++ if (has_parent) {
130++ const struct swc_rectangle *parent_geom = &view->parent->base.geometry;
131++ pixman_region32_init_rect(&parent_region, parent_geom->x, parent_geom->y,
132++ parent_geom->width, parent_geom->height);
133++ }
134+
135+ /* Clip the surface by the opaque region covering it. */
136+ pixman_region32_copy(&view->clip, &compositor.opaque);
137+
138+ /* Translate the opaque region to global coordinates. */
139+ pixman_region32_copy(&surface_opaque, &view->surface->state.opaque);
140+- pixman_region32_translate(&surface_opaque, geom->x, geom->y);
141++ pixman_region32_translate(&surface_opaque,
142++ geom->x - view->buffer_offset_x,
143++ geom->y - view->buffer_offset_y);
144++ pixman_region32_intersect(&surface_opaque, &surface_opaque, &view_region);
145++ if (has_parent)
146++ pixman_region32_intersect(&surface_opaque, &surface_opaque, &parent_region);
147+
148+ /* Add the surface's opaque region to the accumulated opaque region. */
149+ pixman_region32_union(&compositor.opaque, &compositor.opaque, &surface_opaque);
150+@@ -1154,7 +1199,11 @@ calculate_damage(void)
151+ renderer_flush_view(view);
152+
153+ /* Translate surface damage to global coordinates. */
154+- pixman_region32_translate(surface_damage, geom->x, geom->y);
155++ pixman_region32_translate(surface_damage,
156++ geom->x - view->buffer_offset_x,
157++ geom->y - view->buffer_offset_y);
158++ if (has_parent)
159++ pixman_region32_intersect(surface_damage, surface_damage, &parent_region);
160+
161+ /* Add the surface damage to the compositor damage. */
162+ pixman_region32_union(&compositor.damage, &compositor.damage, surface_damage);
163+@@ -1163,22 +1212,24 @@ calculate_damage(void)
164+
165+ /* redraw entire thingy if either */
166+ if (view->border.damaged_border1 || view->border.damaged_border2) {
167+- pixman_region32_t border_region, view_region;
168++ pixman_region32_t border_region;
169+
170+ pixman_region32_init_with_extents(&border_region, &view->extents);
171+- pixman_region32_init_rect(&view_region, geom->x, geom->y, geom->width, geom->height);
172+
173+ pixman_region32_subtract(&border_region, &border_region, &view_region);
174+
175+ pixman_region32_union(&compositor.damage, &compositor.damage, &border_region);
176+
177+ pixman_region32_fini(&border_region);
178+- pixman_region32_fini(&view_region);
179+
180+ view->border.damaged_border1 = false;
181+ view->border.damaged_border2 = false;
182+ }
183+- }
184++
185++ pixman_region32_fini(&view_region);
186++ if (has_parent)
187++ pixman_region32_fini(&parent_region);
188++ }
189+
190+ pixman_region32_fini(&surface_opaque);
191+ }
192+diff --git a/libswc/compositor.h b/libswc/compositor.h
193+index 9046f0b..e079d92 100644
194+--- a/libswc/compositor.h
195++++ b/libswc/compositor.h
196+@@ -52,6 +52,8 @@ struct compositor_view {
197+ struct wld_buffer *buffer;
198+ struct window *window;
199+ struct compositor_view *parent;
200++ int32_t buffer_offset_x;
201++ int32_t buffer_offset_y;
202+
203+ /* Whether or not the view is visible (mapped). */
204+ bool visible;
205+diff --git a/libswc/subcompositor.c b/libswc/subcompositor.c
206+index 5d1da45..1211f83 100644
207+--- a/libswc/subcompositor.c
208++++ b/libswc/subcompositor.c
209+@@ -25,6 +25,7 @@
210+ #include "internal.h"
211+ #include "subcompositor.h"
212+ #include "subsurface.h"
213++#include "surface.h"
214+ #include "util.h"
215+
216+ static void
217+@@ -32,13 +33,23 @@ get_subsurface(struct wl_client *client, struct wl_resource *resource,
218+ uint32_t id, struct wl_resource *surface_resource, struct wl_resource *parent_resource)
219+ {
220+ struct subsurface *subsurface;
221++ struct surface *surface = wl_resource_get_user_data(surface_resource);
222++ struct surface *parent = wl_resource_get_user_data(parent_resource);
223+
224+- subsurface = subsurface_new(client, wl_resource_get_version(resource), id);
225++ if (!surface || !parent || surface == parent)
226++ return;
227++
228++ if (surface->subsurface)
229++ return;
230++
231++ subsurface = subsurface_new(client, wl_resource_get_version(resource), id, surface, parent);
232+
233+ if (!subsurface) {
234+ wl_resource_post_no_memory(resource);
235+ return;
236+ }
237++
238++ surface->subsurface = subsurface;
239+ }
240+
241+ static const struct wl_subcompositor_interface subcompositor_impl = {
242+diff --git a/libswc/subsurface.c b/libswc/subsurface.c
243+index 6f68b2c..e9b7d7c 100644
244+--- a/libswc/subsurface.c
245++++ b/libswc/subsurface.c
246+@@ -22,39 +22,144 @@
247+ */
248+
249+ #include "subsurface.h"
250++#include "compositor.h"
251++#include "surface.h"
252+ #include "util.h"
253++#include "view.h"
254+
255+ #include <stdlib.h>
256+ #include <wayland-server.h>
257+
258++static void
259++subsurface_update_position(struct subsurface *subsurface)
260++{
261++ struct compositor_view *parent_view;
262++ struct compositor_view *view;
263++
264++ if (!subsurface->surface || !subsurface->parent)
265++ return;
266++
267++ view = compositor_view(subsurface->surface->view);
268++ parent_view = compositor_view(subsurface->parent->view);
269++ if (!view || !parent_view)
270++ return;
271++
272++ view_move(&view->base,
273++ parent_view->base.geometry.x + subsurface->x - parent_view->buffer_offset_x,
274++ parent_view->base.geometry.y + subsurface->y - parent_view->buffer_offset_y);
275++}
276++
277++static void
278++handle_parent_view_move(struct view_handler *handler)
279++{
280++ struct subsurface *subsurface = wl_container_of(handler, subsurface, parent_view_handler);
281++ subsurface_update_position(subsurface);
282++}
283++
284++static const struct view_handler_impl parent_view_handler_impl = {
285++ .move = handle_parent_view_move,
286++};
287++
288++static void
289++handle_surface_destroy(struct wl_listener *listener, void *data)
290++{
291++ (void)data;
292++ struct subsurface *subsurface = wl_container_of(listener, subsurface, surface_destroy_listener);
293++ if (subsurface->resource)
294++ wl_resource_destroy(subsurface->resource);
295++}
296++
297++static void
298++handle_parent_destroy(struct wl_listener *listener, void *data)
299++{
300++ (void)data;
301++ struct subsurface *subsurface = wl_container_of(listener, subsurface, parent_destroy_listener);
302++ struct compositor_view *view = NULL;
303++
304++ if (subsurface->surface && subsurface->surface->view)
305++ view = compositor_view(subsurface->surface->view);
306++
307++ if (view) {
308++ view->parent = NULL;
309++ compositor_view_hide(view);
310++ }
311++
312++ if (!wl_list_empty(&subsurface->parent_view_handler.link)) {
313++ wl_list_remove(&subsurface->parent_view_handler.link);
314++ wl_list_init(&subsurface->parent_view_handler.link);
315++ }
316++
317++ if (!wl_list_empty(&subsurface->link)) {
318++ wl_list_remove(&subsurface->link);
319++ wl_list_init(&subsurface->link);
320++ }
321++
322++ subsurface->parent = NULL;
323++}
324++
325+ static void
326+ set_position(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y)
327+ {
328+- /* TODO: Implement. */
329++ (void)client;
330++ struct subsurface *subsurface = wl_resource_get_user_data(resource);
331++
332++ subsurface->x = x;
333++ subsurface->y = y;
334++ subsurface_update_position(subsurface);
335+ }
336+
337+ static void
338+ place_above(struct wl_client *client, struct wl_resource *resource, struct wl_resource *sibling_resource)
339+ {
340+- /* TODO: Implement. */
341++ (void)client;
342++ struct subsurface *subsurface = wl_resource_get_user_data(resource);
343++ struct surface *sibling_surface = wl_resource_get_user_data(sibling_resource);
344++ struct compositor_view *view = compositor_view(subsurface->surface->view);
345++ struct compositor_view *sibling_view = compositor_view(sibling_surface->view);
346++
347++ if (!view || !sibling_view || view == sibling_view)
348++ return;
349++
350++ if (view->parent != sibling_view->parent)
351++ return;
352++
353++ wl_list_remove(&view->link);
354++ wl_list_insert(sibling_view->link.prev, &view->link);
355+ }
356+
357+ static void
358+ place_below(struct wl_client *client, struct wl_resource *resource, struct wl_resource *sibling_resource)
359+ {
360+- /* TODO: Implement. */
361++ (void)client;
362++ struct subsurface *subsurface = wl_resource_get_user_data(resource);
363++ struct surface *sibling_surface = wl_resource_get_user_data(sibling_resource);
364++ struct compositor_view *view = compositor_view(subsurface->surface->view);
365++ struct compositor_view *sibling_view = compositor_view(sibling_surface->view);
366++
367++ if (!view || !sibling_view || view == sibling_view)
368++ return;
369++
370++ if (view->parent != sibling_view->parent)
371++ return;
372++
373++ wl_list_remove(&view->link);
374++ wl_list_insert(&sibling_view->link, &view->link);
375+ }
376+
377+ static void
378+ set_sync(struct wl_client *client, struct wl_resource *resource)
379+ {
380+- /* TODO: Implement. */
381++ (void)client;
382++ struct subsurface *subsurface = wl_resource_get_user_data(resource);
383++ subsurface->sync = true;
384+ }
385+
386+ static void
387+ set_desync(struct wl_client *client, struct wl_resource *resource)
388+ {
389+- /* TODO: Implement. */
390++ (void)client;
391++ struct subsurface *subsurface = wl_resource_get_user_data(resource);
392++ subsurface->sync = false;
393+ }
394+
395+ static const struct wl_subsurface_interface subsurface_impl = {
396+@@ -70,13 +175,47 @@ static void
397+ subsurface_destroy(struct wl_resource *resource)
398+ {
399+ struct subsurface *subsurface = wl_resource_get_user_data(resource);
400++
401++ if (subsurface->surface) {
402++ if (subsurface->surface->subsurface == subsurface)
403++ subsurface->surface->subsurface = NULL;
404++ }
405++
406++ if (!wl_list_empty(&subsurface->parent_destroy_listener.link)) {
407++ wl_list_remove(&subsurface->parent_destroy_listener.link);
408++ wl_list_init(&subsurface->parent_destroy_listener.link);
409++ }
410++ if (!wl_list_empty(&subsurface->surface_destroy_listener.link)) {
411++ wl_list_remove(&subsurface->surface_destroy_listener.link);
412++ wl_list_init(&subsurface->surface_destroy_listener.link);
413++ }
414++
415++ if (!wl_list_empty(&subsurface->parent_view_handler.link)) {
416++ wl_list_remove(&subsurface->parent_view_handler.link);
417++ wl_list_init(&subsurface->parent_view_handler.link);
418++ }
419++
420++ if (!wl_list_empty(&subsurface->link)) {
421++ wl_list_remove(&subsurface->link);
422++ wl_list_init(&subsurface->link);
423++ }
424++
425++ if (subsurface->surface && subsurface->surface->view) {
426++ struct compositor_view *view = compositor_view(subsurface->surface->view);
427++ if (view && !view->window)
428++ compositor_view_destroy(view);
429++ }
430++
431+ free(subsurface);
432+ }
433+
434+ struct subsurface *
435+-subsurface_new(struct wl_client *client, uint32_t version, uint32_t id)
436++subsurface_new(struct wl_client *client, uint32_t version, uint32_t id,
437++ struct surface *surface, struct surface *parent)
438+ {
439+ struct subsurface *subsurface;
440++ struct compositor_view *parent_view;
441++ struct compositor_view *view;
442+
443+ if (!(subsurface = malloc(sizeof(*subsurface))))
444+ goto error0;
445+@@ -88,8 +227,47 @@ subsurface_new(struct wl_client *client, uint32_t version, uint32_t id)
446+
447+ wl_resource_set_implementation(subsurface->resource, &subsurface_impl, subsurface, &subsurface_destroy);
448+
449++ subsurface->surface = surface;
450++ subsurface->parent = parent;
451++ subsurface->x = 0;
452++ subsurface->y = 0;
453++ subsurface->sync = true;
454++ subsurface->pending = false;
455++
456++ subsurface->parent_view_handler.impl = &parent_view_handler_impl;
457++ wl_list_init(&subsurface->parent_view_handler.link);
458++ wl_list_init(&subsurface->surface_destroy_listener.link);
459++ wl_list_init(&subsurface->parent_destroy_listener.link);
460++ wl_list_init(&subsurface->link);
461++
462++ if (!surface->view)
463++ compositor_create_view(surface);
464++ if (!parent->view)
465++ compositor_create_view(parent);
466++
467++ parent_view = compositor_view(parent->view);
468++ view = compositor_view(surface->view);
469++ if (!parent_view || !view)
470++ goto error2;
471++
472++ compositor_view_set_parent(view, parent_view);
473++ wl_list_remove(&view->link);
474++ wl_list_insert(parent_view->link.prev, &view->link);
475++
476++ wl_list_insert(&parent_view->base.handlers, &subsurface->parent_view_handler.link);
477++ subsurface_update_position(subsurface);
478++ wl_list_insert(&parent->subsurfaces, &subsurface->link);
479++
480++ subsurface->surface_destroy_listener.notify = handle_surface_destroy;
481++ wl_resource_add_destroy_listener(surface->resource, &subsurface->surface_destroy_listener);
482++ subsurface->parent_destroy_listener.notify = handle_parent_destroy;
483++ wl_resource_add_destroy_listener(parent->resource, &subsurface->parent_destroy_listener);
484++
485+ return subsurface;
486+
487++error2:
488++ wl_resource_destroy(subsurface->resource);
489++ return NULL;
490+ error1:
491+ free(subsurface);
492+ error0:
493+diff --git a/libswc/subsurface.h b/libswc/subsurface.h
494+index a78ed69..c747530 100644
495+--- a/libswc/subsurface.h
496++++ b/libswc/subsurface.h
497+@@ -24,14 +24,29 @@
498+ #ifndef SWC_SUBSURFACE_H
499+ #define SWC_SUBSURFACE_H
500+
501++#include "view.h"
502++
503++#include <stdbool.h>
504+ #include <stdint.h>
505++#include <wayland-server.h>
506+
507+ struct wl_client;
508++struct surface;
509+
510+ struct subsurface {
511+ struct wl_resource *resource;
512++ struct surface *surface;
513++ struct surface *parent;
514++ struct view_handler parent_view_handler;
515++ struct wl_listener surface_destroy_listener;
516++ struct wl_listener parent_destroy_listener;
517++ struct wl_list link;
518++ int32_t x, y;
519++ bool sync;
520++ bool pending;
521+ };
522+
523+-struct subsurface *subsurface_new(struct wl_client *client, uint32_t version, uint32_t id);
524++struct subsurface *subsurface_new(struct wl_client *client, uint32_t version, uint32_t id,
525++ struct surface *surface, struct surface *parent);
526+
527+ #endif
528+diff --git a/libswc/surface.c b/libswc/surface.c
529+index 19a36ec..9332054 100644
530+--- a/libswc/surface.c
531++++ b/libswc/surface.c
532+@@ -27,6 +27,7 @@
533+ #include "output.h"
534+ #include "region.h"
535+ #include "screen.h"
536++#include "subsurface.h"
537+ #include "util.h"
538+ #include "view.h"
539+ #include "wayland_buffer.h"
540+@@ -218,9 +219,8 @@ trim_region(pixman_region32_t *region, struct wld_buffer *buffer)
541+ }
542+
543+ static void
544+-commit(struct wl_client *client, struct wl_resource *resource)
545++surface_apply_pending(struct surface *surface, bool flush_children)
546+ {
547+- struct surface *surface = wl_resource_get_user_data(resource);
548+ struct wld_buffer *buffer;
549+
550+ /* Attach */
551+@@ -263,6 +263,32 @@ commit(struct wl_client *client, struct wl_resource *resource)
552+ }
553+
554+ surface->pending.commit = 0;
555++
556++ if (surface->subsurface)
557++ surface->subsurface->pending = false;
558++
559++ if (flush_children) {
560++ struct subsurface *child;
561++ wl_list_for_each (child, &surface->subsurfaces, link) {
562++ if (!child->sync || !child->pending)
563++ continue;
564++ if (child->surface)
565++ surface_apply_pending(child->surface, true);
566++ }
567++ }
568++}
569++
570++static void
571++commit(struct wl_client *client, struct wl_resource *resource)
572++{
573++ struct surface *surface = wl_resource_get_user_data(resource);
574++
575++ if (surface->subsurface && surface->subsurface->sync) {
576++ surface->subsurface->pending = true;
577++ return;
578++ }
579++
580++ surface_apply_pending(surface, true);
581+ }
582+
583+ static void
584+@@ -339,6 +365,14 @@ surface_new(struct wl_client *client, uint32_t version, uint32_t id)
585+ surface->pending.commit = 0;
586+ surface->view = NULL;
587+ surface->view_handler.impl = &view_handler_impl;
588++ surface->subsurface = NULL;
589++ wl_list_init(&surface->subsurfaces);
590++ surface->has_window_geometry = false;
591++ surface->window_x = 0;
592++ surface->window_y = 0;
593++ surface->window_width = 0;
594++ surface->window_height = 0;
595++ surface->window_geometry_applied = false;
596+
597+ state_initialize(&surface->state);
598+ state_initialize(&surface->pending.state);
599+diff --git a/libswc/surface.h b/libswc/surface.h
600+index 039ed4c..5eb751c 100644
601+--- a/libswc/surface.h
602++++ b/libswc/surface.h
603+@@ -29,6 +29,8 @@
604+ #include <pixman.h>
605+ #include <wayland-server.h>
606+
607++struct subsurface;
608++
609+ enum {
610+ SURFACE_COMMIT_ATTACH = (1 << 0),
611+ SURFACE_COMMIT_DAMAGE = (1 << 1),
612+@@ -67,6 +69,13 @@ struct surface {
613+
614+ struct view *view;
615+ struct view_handler view_handler;
616++
617++ struct subsurface *subsurface;
618++ struct wl_list subsurfaces;
619++ bool has_window_geometry;
620++ int32_t window_x, window_y;
621++ int32_t window_width, window_height;
622++ bool window_geometry_applied;
623+ };
624+
625+ struct surface *surface_new(struct wl_client *client, uint32_t version, uint32_t id);
626+diff --git a/libswc/window.c b/libswc/window.c
627+index 6322502..728243d 100644
628+--- a/libswc/window.c
629++++ b/libswc/window.c
630+@@ -450,8 +450,14 @@ window_initialize(struct window *window, const struct window_impl *impl, struct
631+ window->base.app_id = NULL;
632+ window->base.parent = NULL;
633+
634+- if (!(window->view = compositor_create_view(surface)))
635+- return false;
636++ if (surface->view) {
637++ window->view = compositor_view(surface->view);
638++ if (!window->view || window->view->window)
639++ return false;
640++ } else {
641++ if (!(window->view = compositor_create_view(surface)))
642++ return false;
643++ }
644+
645+ window->impl = impl;
646+ window->handler = &null_handler;
647+diff --git a/libswc/xdg_shell.c b/libswc/xdg_shell.c
648+index 33a7f9c..d1cf081 100644
649+--- a/libswc/xdg_shell.c
650++++ b/libswc/xdg_shell.c
651+@@ -591,6 +591,25 @@ ack_configure(struct wl_client *client, struct wl_resource *resource, uint32_t s
652+ static void
653+ set_window_geometry(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
654+ {
655++ (void)client;
656++ struct xdg_surface *xdg_surface = wl_resource_get_user_data(resource);
657++ struct surface *surface = xdg_surface->surface;
658++
659++ if (width <= 0 || height <= 0) {
660++ surface->has_window_geometry = false;
661++ return;
662++ }
663++
664++ surface->has_window_geometry = true;
665++ surface->window_x = x;
666++ surface->window_y = y;
667++ surface->window_width = width;
668++ surface->window_height = height;
669++ if (!surface->window_geometry_applied && surface->view && (x != 0 || y != 0)) {
670++ struct swc_rectangle *geom = &surface->view->geometry;
671++ view_move(surface->view, geom->x - x, geom->y - y);
672++ surface->window_geometry_applied = true;
673++ }
674+ }
675+
676+ static const struct xdg_surface_interface xdg_surface_impl = {
677+--
678+2.49.0
679+
1@@ -0,0 +1,850 @@
2+From 13a2200f15510e837ce2631592856f4f0a0ede2b Mon Sep 17 00:00:00 2001
3+From: shrub <maybeshrub@gmail.com>
4+Date: Tue, 10 Feb 2026 21:34:58 +0000
5+Subject: [PATCH] fix subsurfaces, so they can actually work fix subsurface
6+ buffer rendering and do damage calculations properly, calculate pointer
7+ position and translate it properly, and do better subusrfcae comittis and
8+ restacking.
9+
10+---
11+ libswc/compositor.c | 148 ++++++++++++++++---------
12+ libswc/compositor.h | 1 +
13+ libswc/pointer.c | 15 ++-
14+ libswc/subcompositor.c | 25 ++++-
15+ libswc/subsurface.c | 243 +++++++++++++++++++++++++++++++++++++----
16+ libswc/subsurface.h | 9 ++
17+ libswc/surface.c | 17 ++-
18+ libswc/surface.h | 5 +
19+ 8 files changed, 383 insertions(+), 80 deletions(-)
20+
21+diff --git a/libswc/compositor.c b/libswc/compositor.c
22+index d2321b9..4875af8 100644
23+--- a/libswc/compositor.c
24++++ b/libswc/compositor.c
25+@@ -41,6 +41,7 @@
26+ #include "seat.h"
27+ #include "shm.h"
28+ #include "surface.h"
29++#include "subsurface.h"
30+ #include "util.h"
31+ #include "view.h"
32+ #include "window.h"
33+@@ -217,38 +218,50 @@ error0:
34+ static void
35+ repaint_view(struct target *target, struct compositor_view *view, pixman_region32_t *damage)
36+ {
37+- pixman_region32_t view_region, view_damage, border_damage;
38++ pixman_region32_t geom_region, buffer_region, border_region, view_damage, buffer_damage, border_damage;
39+ const struct swc_rectangle *geom = &view->base.geometry, *target_geom = &target->view->geometry;
40+- bool has_parent = view->parent && view->parent != view;
41+- pixman_region32_t parent_region;
42++ int32_t buf_x, buf_y;
43++ uint32_t buf_w, buf_h;
44++ int64_t total_border;
45+
46+ if (!view->base.buffer)
47+ return;
48+
49+- pixman_region32_init_rect(&view_region, geom->x, geom->y, geom->width, geom->height);
50+- if (has_parent) {
51+- const struct swc_rectangle *parent_geom = &view->parent->base.geometry;
52+- pixman_region32_init_rect(&parent_region, parent_geom->x, parent_geom->y,
53+- parent_geom->width, parent_geom->height);
54++ buf_w = view->base.buffer->width;
55++ buf_h = view->base.buffer->height;
56++ buf_x = geom->x - view->buffer_offset_x;
57++ buf_y = geom->y - view->buffer_offset_y;
58++
59++ total_border = (int64_t)view->border.outwidth + (int64_t)view->border.inwidth;
60++ pixman_region32_init_rect(&geom_region, geom->x, geom->y, geom->width, geom->height);
61++ if (view->window) {
62++ pixman_region32_init_rect(&buffer_region, geom->x, geom->y, geom->width, geom->height);
63++ } else {
64++ pixman_region32_init_rect(&buffer_region, buf_x, buf_y, buf_w, buf_h);
65+ }
66++ pixman_region32_init_rect(&border_region,
67++ geom->x - (int32_t)total_border,
68++ geom->y - (int32_t)total_border,
69++ geom->width + (uint32_t)(2 * total_border),
70++ geom->height + (uint32_t)(2 * total_border));
71++ pixman_region32_subtract(&border_region, &border_region, &geom_region);
72+ pixman_region32_init_with_extents(&view_damage, &view->extents);
73++ pixman_region32_init(&buffer_damage);
74+ pixman_region32_init(&border_damage);
75+
76+ pixman_region32_intersect(&view_damage, &view_damage, damage);
77+ pixman_region32_subtract(&view_damage, &view_damage, &view->clip);
78+- pixman_region32_subtract(&border_damage, &view_damage, &view_region);
79+- pixman_region32_intersect(&view_damage, &view_damage, &view_region);
80+- if (has_parent) {
81+- pixman_region32_intersect(&view_damage, &view_damage, &parent_region);
82+- pixman_region32_intersect(&border_damage, &border_damage, &parent_region);
83+- }
84++ pixman_region32_intersect(&border_damage, &view_damage, &border_region);
85++ pixman_region32_intersect(&buffer_damage, &view_damage, &buffer_region);
86+
87+- if (pixman_region32_not_empty(&view_damage)) {
88+- pixman_region32_translate(&view_damage, -geom->x + view->buffer_offset_x, -geom->y + view->buffer_offset_y);
89+- wld_copy_region(swc.drm->renderer, view->buffer, geom->x - target_geom->x, geom->y - target_geom->y, &view_damage);
90++ if (pixman_region32_not_empty(&buffer_damage)) {
91++ pixman_region32_translate(&buffer_damage, -geom->x + view->buffer_offset_x, -geom->y + view->buffer_offset_y);
92++ wld_copy_region(swc.drm->renderer, view->buffer,
93++ buf_x - target_geom->x, buf_y - target_geom->y, &buffer_damage);
94+ }
95+
96+ pixman_region32_fini(&view_damage);
97++ pixman_region32_fini(&buffer_damage);
98+
99+ pixman_region32_t in_rect;
100+ pixman_region32_init_rect(&in_rect,
101+@@ -263,10 +276,12 @@ repaint_view(struct target *target, struct compositor_view *view, pixman_region3
102+
103+ pixman_region32_t in_border;
104+ pixman_region32_init(&in_border);
105+- pixman_region32_subtract(&in_border, &in_rect, &view_region);
106++ pixman_region32_subtract(&in_border, &in_rect, &geom_region);
107+ pixman_region32_intersect(&in_border, &in_border, &border_damage);
108+
109+- pixman_region32_fini(&view_region);
110++ pixman_region32_fini(&geom_region);
111++ pixman_region32_fini(&buffer_region);
112++ pixman_region32_fini(&border_region);
113+
114+ /* Draw border */
115+ if (view->border.outwidth > 0 && pixman_region32_not_empty(&out_border)) {
116+@@ -283,8 +298,6 @@ repaint_view(struct target *target, struct compositor_view *view, pixman_region3
117+ pixman_region32_fini(&in_rect);
118+ pixman_region32_fini(&out_border);
119+ pixman_region32_fini(&in_border);
120+- if (has_parent)
121+- pixman_region32_fini(&parent_region);
122+
123+ }
124+
125+@@ -409,8 +422,7 @@ renderer_flush_view(struct compositor_view *view)
126+ /* Surface Views {{{ */
127+
128+ /**
129+- * Adds the region below a view to the compositor's damaged region,
130+- * taking into account its clip region.
131++ * Adds the region below a view to the compositor's damaged region.
132+ */
133+ static void
134+ damage_below_view(struct compositor_view *view)
135+@@ -418,7 +430,6 @@ damage_below_view(struct compositor_view *view)
136+ pixman_region32_t damage_below;
137+
138+ pixman_region32_init_with_extents(&damage_below, &view->extents);
139+- pixman_region32_subtract(&damage_below, &damage_below, &view->clip);
140+ pixman_region32_union(&compositor.damage, &compositor.damage, &damage_below);
141+ pixman_region32_fini(&damage_below);
142+ }
143+@@ -438,15 +449,25 @@ static void
144+ update_extents(struct compositor_view *view)
145+ {
146+ int64_t total_border = (int64_t)view->border.outwidth + (int64_t)view->border.inwidth;
147+- int64_t x = view->base.geometry.x;
148+- int64_t y = view->base.geometry.y;
149+- int64_t w = view->base.geometry.width;
150+- int64_t h = view->base.geometry.height;
151+-
152+- view->extents.x1 = clamp_i32(x - total_border);
153+- view->extents.y1 = clamp_i32(y - total_border);
154+- view->extents.x2 = clamp_i32(x + w + total_border);
155+- view->extents.y2 = clamp_i32(y + h + total_border);
156++ int64_t geom_x = view->base.geometry.x;
157++ int64_t geom_y = view->base.geometry.y;
158++ int64_t geom_w = view->base.geometry.width;
159++ int64_t geom_h = view->base.geometry.height;
160++
161++ int64_t border_x1 = geom_x - total_border;
162++ int64_t border_y1 = geom_y - total_border;
163++ int64_t border_x2 = geom_x + geom_w + total_border;
164++ int64_t border_y2 = geom_y + geom_h + total_border;
165++
166++ int64_t buffer_x1 = geom_x - view->buffer_offset_x;
167++ int64_t buffer_y1 = geom_y - view->buffer_offset_y;
168++ int64_t buffer_x2 = buffer_x1 + (view->base.buffer ? view->base.buffer->width : (uint32_t)geom_w);
169++ int64_t buffer_y2 = buffer_y1 + (view->base.buffer ? view->base.buffer->height : (uint32_t)geom_h);
170++
171++ view->extents.x1 = clamp_i32(MIN(border_x1, buffer_x1));
172++ view->extents.y1 = clamp_i32(MIN(border_y1, buffer_y1));
173++ view->extents.x2 = clamp_i32(MAX(border_x2, buffer_x2));
174++ view->extents.y2 = clamp_i32(MAX(border_y2, buffer_y2));
175+
176+ if (view->extents.x2 < view->extents.x1)
177+ view->extents.x2 = view->extents.x1;
178+@@ -775,15 +796,13 @@ attach(struct view *base, struct wld_buffer *buffer)
179+ update_extents(view);
180+
181+ if (view->visible) {
182+- /* Damage the region that was newly uncovered
183+- * or covered, minus the clip region. */
184++ /* Damage the region that was newly uncovered or covered. */
185+ pixman_region32_init_with_extents(&old, &old_extents);
186+ pixman_region32_init_with_extents(&new, &view->extents);
187+ pixman_region32_init(&both);
188+ pixman_region32_intersect(&both, &old, &new);
189+ pixman_region32_union(&new, &old, &new);
190+ pixman_region32_subtract(&new, &new, &both);
191+- pixman_region32_subtract(&new, &new, &view->clip);
192+ pixman_region32_union(&compositor.damage, &compositor.damage, &new);
193+ pixman_region32_fini(&old);
194+ pixman_region32_fini(&new);
195+@@ -835,14 +854,26 @@ view_at(int32_t x, int32_t y)
196+ {
197+ struct compositor_view *view;
198+ struct swc_rectangle *geom;
199++ struct swc_rectangle buffer_geom;
200+
201+ wl_list_for_each (view, &compositor.views, link) {
202+ if (!view->visible)
203+ continue;
204+
205+ geom = &view->base.geometry;
206+- if (!rectangle_contains_point(geom, x, y))
207++ if (view->window) {
208++ if (!rectangle_contains_point(geom, x, y))
209++ continue;
210++ } else if (view->base.buffer) {
211++ buffer_geom.x = geom->x - view->buffer_offset_x;
212++ buffer_geom.y = geom->y - view->buffer_offset_y;
213++ buffer_geom.width = view->base.buffer->width;
214++ buffer_geom.height = view->base.buffer->height;
215++ if (!rectangle_contains_point(&buffer_geom, x, y))
216++ continue;
217++ } else if (!rectangle_contains_point(geom, x, y)) {
218+ continue;
219++ }
220+
221+ if (pixman_region32_contains_point(&view->surface->state.input,
222+ x - geom->x + view->buffer_offset_x,
223+@@ -1070,14 +1101,42 @@ compositor_view_set_parent(struct compositor_view *view, struct compositor_view
224+ compositor_view_hide(view);
225+ }
226+
227++void
228++compositor_view_restack(struct compositor_view *view, struct compositor_view *sibling, bool above)
229++{
230++ if (!view || !sibling || view == sibling)
231++ return;
232++
233++ if (above) {
234++ if (view->link.next == &sibling->link)
235++ return;
236++ wl_list_remove(&view->link);
237++ wl_list_insert(sibling->link.prev, &view->link);
238++ } else {
239++ if (view->link.prev == &sibling->link)
240++ return;
241++ wl_list_remove(&view->link);
242++ wl_list_insert(&sibling->link, &view->link);
243++ }
244++
245++ damage_views(view, sibling);
246++}
247++
248+ void
249+ compositor_view_show(struct compositor_view *view)
250+ {
251+ struct compositor_view *other;
252++ struct subsurface *subsurface;
253+
254+ if (view->visible)
255+ return;
256+
257++ subsurface = view->surface ? view->surface->subsurface : NULL;
258++ if (subsurface) {
259++ if (!subsurface->added || !view->surface->state.buffer)
260++ return;
261++ }
262++
263+ view->visible = true;
264+ view_update_screens(&view->base);
265+
266+@@ -1167,16 +1226,9 @@ calculate_damage(void)
267+ continue;
268+
269+ geom = &view->base.geometry;
270+- bool has_parent = view->parent && view->parent != view;
271+ pixman_region32_t view_region;
272+- pixman_region32_t parent_region;
273+
274+ pixman_region32_init_rect(&view_region, geom->x, geom->y, geom->width, geom->height);
275+- if (has_parent) {
276+- const struct swc_rectangle *parent_geom = &view->parent->base.geometry;
277+- pixman_region32_init_rect(&parent_region, parent_geom->x, parent_geom->y,
278+- parent_geom->width, parent_geom->height);
279+- }
280+
281+ /* Clip the surface by the opaque region covering it. */
282+ pixman_region32_copy(&view->clip, &compositor.opaque);
283+@@ -1187,8 +1239,6 @@ calculate_damage(void)
284+ geom->x - view->buffer_offset_x,
285+ geom->y - view->buffer_offset_y);
286+ pixman_region32_intersect(&surface_opaque, &surface_opaque, &view_region);
287+- if (has_parent)
288+- pixman_region32_intersect(&surface_opaque, &surface_opaque, &parent_region);
289+
290+ /* Add the surface's opaque region to the accumulated opaque region. */
291+ pixman_region32_union(&compositor.opaque, &compositor.opaque, &surface_opaque);
292+@@ -1202,8 +1252,6 @@ calculate_damage(void)
293+ pixman_region32_translate(surface_damage,
294+ geom->x - view->buffer_offset_x,
295+ geom->y - view->buffer_offset_y);
296+- if (has_parent)
297+- pixman_region32_intersect(surface_damage, surface_damage, &parent_region);
298+
299+ /* Add the surface damage to the compositor damage. */
300+ pixman_region32_union(&compositor.damage, &compositor.damage, surface_damage);
301+@@ -1226,9 +1274,7 @@ calculate_damage(void)
302+ view->border.damaged_border2 = false;
303+ }
304+
305+- pixman_region32_fini(&view_region);
306+- if (has_parent)
307+- pixman_region32_fini(&parent_region);
308++ pixman_region32_fini(&view_region);
309+ }
310+
311+ pixman_region32_fini(&surface_opaque);
312+diff --git a/libswc/compositor.h b/libswc/compositor.h
313+index e079d92..529c360 100644
314+--- a/libswc/compositor.h
315++++ b/libswc/compositor.h
316+@@ -92,6 +92,7 @@ void compositor_view_destroy(struct compositor_view *view);
317+ struct compositor_view *compositor_view(struct view *view);
318+
319+ void compositor_view_set_parent(struct compositor_view *view, struct compositor_view *parent);
320++void compositor_view_restack(struct compositor_view *view, struct compositor_view *sibling, bool above);
321+
322+ void compositor_view_show(struct compositor_view *view);
323+ void compositor_view_hide(struct compositor_view *view);
324+diff --git a/libswc/pointer.c b/libswc/pointer.c
325+index cff6bb6..8b8896f 100644
326+--- a/libswc/pointer.c
327++++ b/libswc/pointer.c
328+@@ -112,14 +112,18 @@ enter(struct input_focus_handler *handler, struct wl_list *resources, struct com
329+ struct wl_resource *resource;
330+ uint32_t serial;
331+ wl_fixed_t surface_x, surface_y;
332++ int32_t origin_x, origin_y;
333+
334+ if (wl_list_empty(resources)) {
335+ pointer_set_cursor(pointer, cursor_left_ptr);
336+ return;
337+ }
338+ serial = wl_display_next_serial(swc.display);
339+- surface_x = pointer->x - wl_fixed_from_int(view->base.geometry.x);
340+- surface_y = pointer->y - wl_fixed_from_int(view->base.geometry.y);
341++ /* do based on buffer origin, holy fuck */
342++ origin_x = view->base.geometry.x - view->buffer_offset_x;
343++ origin_y = view->base.geometry.y - view->buffer_offset_y;
344++ surface_x = pointer->x - wl_fixed_from_int(origin_x);
345++ surface_y = pointer->y - wl_fixed_from_int(origin_y);
346+ wl_resource_for_each (resource, resources)
347+ wl_pointer_send_enter(resource, serial, view->surface->resource, surface_x, surface_y);
348+ }
349+@@ -349,12 +353,15 @@ client_handle_motion(struct pointer_handler *handler, uint32_t time, wl_fixed_t
350+ struct pointer *pointer = wl_container_of(handler, pointer, client_handler);
351+ struct wl_resource *resource;
352+ wl_fixed_t sx, sy;
353++ int32_t origin_x, origin_y;
354+
355+ if (wl_list_empty(&pointer->focus.active))
356+ return false;
357+
358+- sx = x - wl_fixed_from_int(pointer->focus.view->base.geometry.x);
359+- sy = y - wl_fixed_from_int(pointer->focus.view->base.geometry.y);
360++ origin_x = pointer->focus.view->base.geometry.x - pointer->focus.view->buffer_offset_x;
361++ origin_y = pointer->focus.view->base.geometry.y - pointer->focus.view->buffer_offset_y;
362++ sx = x - wl_fixed_from_int(origin_x);
363++ sy = y - wl_fixed_from_int(origin_y);
364+ wl_resource_for_each (resource, &pointer->focus.active)
365+ wl_pointer_send_motion(resource, time, sx, sy);
366+ return true;
367+diff --git a/libswc/subcompositor.c b/libswc/subcompositor.c
368+index 1211f83..2dad2bc 100644
369+--- a/libswc/subcompositor.c
370++++ b/libswc/subcompositor.c
371+@@ -28,6 +28,18 @@
372+ #include "surface.h"
373+ #include "util.h"
374+
375++static bool
376++is_descendant_of(struct surface *ancestor, struct surface *surface)
377++{
378++ while (surface && surface->subsurface) {
379++ surface = surface->subsurface->parent;
380++ if (surface == ancestor)
381++ return true;
382++ }
383++
384++ return false;
385++}
386++
387+ static void
388+ get_subsurface(struct wl_client *client, struct wl_resource *resource,
389+ uint32_t id, struct wl_resource *surface_resource, struct wl_resource *parent_resource)
390+@@ -36,11 +48,20 @@ get_subsurface(struct wl_client *client, struct wl_resource *resource,
391+ struct surface *surface = wl_resource_get_user_data(surface_resource);
392+ struct surface *parent = wl_resource_get_user_data(parent_resource);
393+
394+- if (!surface || !parent || surface == parent)
395++ if (!surface || !parent) {
396++ wl_resource_post_error(resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, "invalid surface");
397+ return;
398++ }
399+
400+- if (surface->subsurface)
401++ if (surface == parent || is_descendant_of(surface, parent)) {
402++ wl_resource_post_error(resource, WL_SUBCOMPOSITOR_ERROR_BAD_PARENT, "invalid parent surface");
403+ return;
404++ }
405++
406++ if (surface->subsurface) {
407++ wl_resource_post_error(resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, "surface already has a subsurface role");
408++ return;
409++ }
410+
411+ subsurface = subsurface_new(client, wl_resource_get_version(resource), id, surface, parent);
412+
413+diff --git a/libswc/subsurface.c b/libswc/subsurface.c
414+index e9b7d7c..f8aa66c 100644
415+--- a/libswc/subsurface.c
416++++ b/libswc/subsurface.c
417+@@ -30,6 +30,20 @@
418+ #include <stdlib.h>
419+ #include <wayland-server.h>
420+
421++bool
422++subsurface_is_synchronized(const struct subsurface *subsurface)
423++{
424++ while (subsurface) {
425++ if (subsurface->sync)
426++ return true;
427++ if (!subsurface->parent)
428++ return false;
429++ subsurface = subsurface->parent->subsurface;
430++ }
431++
432++ return false;
433++}
434++
435+ static void
436+ subsurface_update_position(struct subsurface *subsurface)
437+ {
438+@@ -50,16 +64,99 @@ subsurface_update_position(struct subsurface *subsurface)
439+ }
440+
441+ static void
442+-handle_parent_view_move(struct view_handler *handler)
443++list_remove_if_linked(struct wl_list *link)
444++{
445++ if (!wl_list_empty(link)) {
446++ wl_list_remove(link);
447++ wl_list_init(link);
448++ }
449++}
450++
451++void
452++subsurface_update_visibility(struct subsurface *subsurface)
453++{
454++ struct compositor_view *view;
455++ struct compositor_view *parent_view;
456++
457++ if (!subsurface || !subsurface->surface || !subsurface->parent)
458++ return;
459++
460++ view = compositor_view(subsurface->surface->view);
461++ parent_view = compositor_view(subsurface->parent->view);
462++ if (!view || !parent_view)
463++ return;
464++
465++ if (subsurface->added && parent_view->visible && subsurface->surface->state.buffer)
466++ compositor_view_show(view);
467++ else
468++ compositor_view_hide(view);
469++}
470++
471++static void
472++handle_parent_view_change(struct view_handler *handler)
473+ {
474+ struct subsurface *subsurface = wl_container_of(handler, subsurface, parent_view_handler);
475+ subsurface_update_position(subsurface);
476+ }
477+
478++static void
479++handle_parent_view_resize(struct view_handler *handler, uint32_t old_width, uint32_t old_height)
480++{
481++ (void)old_width;
482++ (void)old_height;
483++ handle_parent_view_change(handler);
484++}
485++
486+ static const struct view_handler_impl parent_view_handler_impl = {
487+- .move = handle_parent_view_move,
488++ .attach = handle_parent_view_change,
489++ .move = handle_parent_view_change,
490++ .resize = handle_parent_view_resize,
491+ };
492+
493++static struct subsurface *
494++subsurface_find_sibling(struct subsurface *subsurface, struct surface *surface)
495++{
496++ struct surface *parent = subsurface->parent;
497++ struct subsurface *sibling;
498++
499++ if (!parent)
500++ return NULL;
501++
502++ wl_list_for_each (sibling, &parent->pending.state.subsurfaces_below, pending_link) {
503++ if (sibling->surface == surface && sibling != subsurface)
504++ return sibling;
505++ }
506++
507++ wl_list_for_each (sibling, &parent->pending.state.subsurfaces_above, pending_link) {
508++ if (sibling->surface == surface && sibling != subsurface)
509++ return sibling;
510++ }
511++
512++ return NULL;
513++}
514++
515++static bool
516++is_valid_sibling(struct subsurface *subsurface, struct surface *sibling_surface,
517++ struct subsurface **sibling_subsurface)
518++{
519++ struct subsurface *sibling;
520++
521++ if (!subsurface->parent || !sibling_surface || sibling_surface == subsurface->surface)
522++ return false;
523++
524++ if (sibling_surface == subsurface->parent) {
525++ *sibling_subsurface = NULL;
526++ return true;
527++ }
528++
529++ sibling = subsurface_find_sibling(subsurface, sibling_surface);
530++ if (!sibling)
531++ return false;
532++
533++ *sibling_subsurface = sibling;
534++ return true;
535++}
536++
537+ static void
538+ handle_surface_destroy(struct wl_listener *listener, void *data)
539+ {
540+@@ -94,6 +191,9 @@ handle_parent_destroy(struct wl_listener *listener, void *data)
541+ wl_list_init(&subsurface->link);
542+ }
543+
544++ list_remove_if_linked(&subsurface->pending_link);
545++ list_remove_if_linked(&subsurface->current_link);
546++
547+ subsurface->parent = NULL;
548+ }
549+
550+@@ -103,9 +203,9 @@ set_position(struct wl_client *client, struct wl_resource *resource, int32_t x,
551+ (void)client;
552+ struct subsurface *subsurface = wl_resource_get_user_data(resource);
553+
554+- subsurface->x = x;
555+- subsurface->y = y;
556+- subsurface_update_position(subsurface);
557++ subsurface->pending_x = x;
558++ subsurface->pending_y = y;
559++ subsurface->pending_position = true;
560+ }
561+
562+ static void
563+@@ -114,17 +214,20 @@ place_above(struct wl_client *client, struct wl_resource *resource, struct wl_re
564+ (void)client;
565+ struct subsurface *subsurface = wl_resource_get_user_data(resource);
566+ struct surface *sibling_surface = wl_resource_get_user_data(sibling_resource);
567+- struct compositor_view *view = compositor_view(subsurface->surface->view);
568+- struct compositor_view *sibling_view = compositor_view(sibling_surface->view);
569++ struct subsurface *sibling_subsurface;
570+
571+- if (!view || !sibling_view || view == sibling_view)
572+- return;
573+-
574+- if (view->parent != sibling_view->parent)
575++ if (!is_valid_sibling(subsurface, sibling_surface, &sibling_subsurface)) {
576++ wl_resource_post_error(resource, WL_SUBSURFACE_ERROR_BAD_SURFACE, "invalid sibling surface");
577+ return;
578++ }
579+
580+- wl_list_remove(&view->link);
581+- wl_list_insert(sibling_view->link.prev, &view->link);
582++ if (!sibling_subsurface) {
583++ wl_list_remove(&subsurface->pending_link);
584++ wl_list_insert(&subsurface->parent->pending.state.subsurfaces_above, &subsurface->pending_link);
585++ } else {
586++ wl_list_remove(&subsurface->pending_link);
587++ wl_list_insert(&sibling_subsurface->pending_link, &subsurface->pending_link);
588++ }
589+ }
590+
591+ static void
592+@@ -133,17 +236,20 @@ place_below(struct wl_client *client, struct wl_resource *resource, struct wl_re
593+ (void)client;
594+ struct subsurface *subsurface = wl_resource_get_user_data(resource);
595+ struct surface *sibling_surface = wl_resource_get_user_data(sibling_resource);
596+- struct compositor_view *view = compositor_view(subsurface->surface->view);
597+- struct compositor_view *sibling_view = compositor_view(sibling_surface->view);
598++ struct subsurface *sibling_subsurface;
599+
600+- if (!view || !sibling_view || view == sibling_view)
601+- return;
602+-
603+- if (view->parent != sibling_view->parent)
604++ if (!is_valid_sibling(subsurface, sibling_surface, &sibling_subsurface)) {
605++ wl_resource_post_error(resource, WL_SUBSURFACE_ERROR_BAD_SURFACE, "invalid sibling surface");
606+ return;
607++ }
608+
609+- wl_list_remove(&view->link);
610+- wl_list_insert(&sibling_view->link, &view->link);
611++ if (!sibling_subsurface) {
612++ wl_list_remove(&subsurface->pending_link);
613++ wl_list_insert(subsurface->parent->pending.state.subsurfaces_below.prev, &subsurface->pending_link);
614++ } else {
615++ wl_list_remove(&subsurface->pending_link);
616++ wl_list_insert(sibling_subsurface->pending_link.prev, &subsurface->pending_link);
617++ }
618+ }
619+
620+ static void
621+@@ -159,7 +265,91 @@ set_desync(struct wl_client *client, struct wl_resource *resource)
622+ {
623+ (void)client;
624+ struct subsurface *subsurface = wl_resource_get_user_data(resource);
625++ bool synchronized = subsurface_is_synchronized(subsurface);
626++
627+ subsurface->sync = false;
628++
629++ if (synchronized
630++ && !subsurface_is_synchronized(subsurface)
631++ && subsurface->pending
632++ && subsurface->surface)
633++ {
634++ surface_commit_pending(subsurface->surface);
635++ }
636++}
637++
638++void
639++subsurface_parent_commit(struct surface *parent)
640++{
641++ struct subsurface *child;
642++ struct compositor_view *parent_view;
643++ struct compositor_view *reference;
644++ struct compositor_view *child_view;
645++
646++ if (!parent)
647++ return;
648++
649++ wl_list_for_each (child, &parent->subsurfaces, link)
650++ list_remove_if_linked(&child->current_link);
651++
652++ wl_list_init(&parent->state.subsurfaces_below);
653++ wl_list_init(&parent->state.subsurfaces_above);
654++
655++ wl_list_for_each (child, &parent->pending.state.subsurfaces_below, pending_link)
656++ wl_list_insert(parent->state.subsurfaces_below.prev, &child->current_link);
657++
658++ wl_list_for_each (child, &parent->pending.state.subsurfaces_above, pending_link)
659++ wl_list_insert(parent->state.subsurfaces_above.prev, &child->current_link);
660++
661++ parent_view = parent->view ? compositor_view(parent->view) : NULL;
662++ if (parent_view) {
663++ reference = parent_view;
664++ wl_list_for_each_reverse (child, &parent->state.subsurfaces_below, current_link) {
665++ if (!child->surface || !child->surface->view)
666++ continue;
667++
668++ child_view = compositor_view(child->surface->view);
669++ if (!child_view)
670++ continue;
671++
672++ compositor_view_restack(child_view, reference, false);
673++ reference = child_view;
674++ }
675++
676++ reference = parent_view;
677++ wl_list_for_each (child, &parent->state.subsurfaces_above, current_link) {
678++ if (!child->surface || !child->surface->view)
679++ continue;
680++
681++ child_view = compositor_view(child->surface->view);
682++ if (!child_view)
683++ continue;
684++
685++ compositor_view_restack(child_view, reference, true);
686++ reference = child_view;
687++ }
688++ }
689++
690++ wl_list_for_each (child, &parent->subsurfaces, link) {
691++ if (!child->pending_position)
692++ continue;
693++
694++ child->x = child->pending_x;
695++ child->y = child->pending_y;
696++ child->pending_position = false;
697++ subsurface_update_position(child);
698++ }
699++
700++ wl_list_for_each (child, &parent->state.subsurfaces_below, current_link) {
701++ if (!child->added)
702++ child->added = true;
703++ subsurface_update_visibility(child);
704++ }
705++ wl_list_for_each (child, &parent->state.subsurfaces_above, current_link) {
706++ if (!child->added)
707++ child->added = true;
708++ subsurface_update_visibility(child);
709++ }
710+ }
711+
712+ static const struct wl_subsurface_interface subsurface_impl = {
713+@@ -200,6 +390,9 @@ subsurface_destroy(struct wl_resource *resource)
714+ wl_list_init(&subsurface->link);
715+ }
716+
717++ list_remove_if_linked(&subsurface->pending_link);
718++ list_remove_if_linked(&subsurface->current_link);
719++
720+ if (subsurface->surface && subsurface->surface->view) {
721+ struct compositor_view *view = compositor_view(subsurface->surface->view);
722+ if (view && !view->window)
723+@@ -231,14 +424,20 @@ subsurface_new(struct wl_client *client, uint32_t version, uint32_t id,
724+ subsurface->parent = parent;
725+ subsurface->x = 0;
726+ subsurface->y = 0;
727++ subsurface->pending_x = 0;
728++ subsurface->pending_y = 0;
729++ subsurface->pending_position = false;
730+ subsurface->sync = true;
731+ subsurface->pending = false;
732++ subsurface->added = false;
733+
734+ subsurface->parent_view_handler.impl = &parent_view_handler_impl;
735+ wl_list_init(&subsurface->parent_view_handler.link);
736+ wl_list_init(&subsurface->surface_destroy_listener.link);
737+ wl_list_init(&subsurface->parent_destroy_listener.link);
738+ wl_list_init(&subsurface->link);
739++ wl_list_init(&subsurface->pending_link);
740++ wl_list_init(&subsurface->current_link);
741+
742+ if (!surface->view)
743+ compositor_create_view(surface);
744+@@ -257,6 +456,8 @@ subsurface_new(struct wl_client *client, uint32_t version, uint32_t id,
745+ wl_list_insert(&parent_view->base.handlers, &subsurface->parent_view_handler.link);
746+ subsurface_update_position(subsurface);
747+ wl_list_insert(&parent->subsurfaces, &subsurface->link);
748++ wl_list_insert(parent->pending.state.subsurfaces_above.prev, &subsurface->pending_link);
749++ subsurface_update_visibility(subsurface);
750+
751+ subsurface->surface_destroy_listener.notify = handle_surface_destroy;
752+ wl_resource_add_destroy_listener(surface->resource, &subsurface->surface_destroy_listener);
753+diff --git a/libswc/subsurface.h b/libswc/subsurface.h
754+index c747530..a4eafdf 100644
755+--- a/libswc/subsurface.h
756++++ b/libswc/subsurface.h
757+@@ -41,11 +41,20 @@ struct subsurface {
758+ struct wl_listener surface_destroy_listener;
759+ struct wl_listener parent_destroy_listener;
760+ struct wl_list link;
761++ struct wl_list pending_link;
762++ struct wl_list current_link;
763+ int32_t x, y;
764++ int32_t pending_x, pending_y;
765++ bool pending_position;
766+ bool sync;
767+ bool pending;
768++ bool added;
769+ };
770+
771++bool subsurface_is_synchronized(const struct subsurface *subsurface);
772++void subsurface_update_visibility(struct subsurface *subsurface);
773++void subsurface_parent_commit(struct surface *parent);
774++
775+ struct subsurface *subsurface_new(struct wl_client *client, uint32_t version, uint32_t id,
776+ struct surface *surface, struct surface *parent);
777+
778+diff --git a/libswc/surface.c b/libswc/surface.c
779+index 9332054..0e84f00 100644
780+--- a/libswc/surface.c
781++++ b/libswc/surface.c
782+@@ -59,6 +59,8 @@ state_initialize(struct surface_state *state)
783+ pixman_region32_init_with_extents(&state->input, &infinite_extents);
784+
785+ wl_list_init(&state->frame_callbacks);
786++ wl_list_init(&state->subsurfaces_below);
787++ wl_list_init(&state->subsurfaces_above);
788+ }
789+
790+ static void
791+@@ -267,10 +269,15 @@ surface_apply_pending(struct surface *surface, bool flush_children)
792+ if (surface->subsurface)
793+ surface->subsurface->pending = false;
794+
795++ if (surface->subsurface)
796++ subsurface_update_visibility(surface->subsurface);
797++
798++ subsurface_parent_commit(surface);
799++
800+ if (flush_children) {
801+ struct subsurface *child;
802+ wl_list_for_each (child, &surface->subsurfaces, link) {
803+- if (!child->sync || !child->pending)
804++ if (!child->pending || !subsurface_is_synchronized(child))
805+ continue;
806+ if (child->surface)
807+ surface_apply_pending(child->surface, true);
808+@@ -283,7 +290,7 @@ commit(struct wl_client *client, struct wl_resource *resource)
809+ {
810+ struct surface *surface = wl_resource_get_user_data(resource);
811+
812+- if (surface->subsurface && surface->subsurface->sync) {
813++ if (surface->subsurface && subsurface_is_synchronized(surface->subsurface)) {
814+ surface->subsurface->pending = true;
815+ return;
816+ }
817+@@ -402,3 +409,9 @@ surface_set_view(struct surface *surface, struct view *view)
818+ view_update(view);
819+ }
820+ }
821++
822++void
823++surface_commit_pending(struct surface *surface)
824++{
825++ surface_apply_pending(surface, true);
826++}
827+diff --git a/libswc/surface.h b/libswc/surface.h
828+index 5eb751c..0e8c4e5 100644
829+--- a/libswc/surface.h
830++++ b/libswc/surface.h
831+@@ -54,6 +54,10 @@ struct surface_state {
832+ pixman_region32_t input;
833+
834+ struct wl_list frame_callbacks;
835++
836++ /* subsurface order; double-buffered with surface state. */
837++ struct wl_list subsurfaces_below;
838++ struct wl_list subsurfaces_above;
839+ };
840+
841+ struct surface {
842+@@ -80,5 +84,6 @@ struct surface {
843+
844+ struct surface *surface_new(struct wl_client *client, uint32_t version, uint32_t id);
845+ void surface_set_view(struct surface *surface, struct view *view);
846++void surface_commit_pending(struct surface *surface);
847+
848+ #endif
849+--
850+2.49.0
851+
1@@ -0,0 +1,430 @@
2+From 40e72dcbb90be5d92615aea647810a56dd0c878a Mon Sep 17 00:00:00 2001
3+From: uint <abhinav.prsai@gmail.com>
4+Date: Wed, 18 Feb 2026 06:07:03 +0000
5+Subject: [PATCH] add wallpaper protocol with separate screen buffers support
6+
7+---
8+ libswc/compositor.c | 39 ++++++++--
9+ libswc/compositor.h | 1 +
10+ libswc/internal.h | 1 +
11+ libswc/swc.c | 1 +
12+ libswc/swc.h | 17 +++--
13+ libswc/wallpaper.c | 147 +++++++++++++++++++++++--------------
14+ libswc/wallpaper.h | 12 +++
15+ protocol/swc_wallpaper.xml | 19 +++++
16+ 8 files changed, 170 insertions(+), 67 deletions(-)
17+ create mode 100644 libswc/wallpaper.h
18+ create mode 100644 protocol/swc_wallpaper.xml
19+
20+diff --git a/libswc/compositor.c b/libswc/compositor.c
21+index 4875af8..cb7863b 100644
22+--- a/libswc/compositor.c
23++++ b/libswc/compositor.c
24+@@ -44,6 +44,7 @@
25+ #include "subsurface.h"
26+ #include "util.h"
27+ #include "view.h"
28++#include "wallpaper.h"
29+ #include "window.h"
30+
31+ #include <errno.h>
32+@@ -109,6 +110,7 @@ static struct {
33+
34+ bool updating;
35+ struct wl_global *global;
36++ bool initialized;
37+
38+ /* zoom level (1.0 = normal, >1 = zoomed in, <1 = zoomed out) */
39+ float zoom;
40+@@ -302,7 +304,7 @@ repaint_view(struct target *target, struct compositor_view *view, pixman_region3
41+ }
42+
43+ static void
44+-renderer_repaint(struct target *target, pixman_region32_t *damage, pixman_region32_t *base_damage, struct wl_list *views)
45++renderer_repaint(struct target *target, pixman_region32_t *damage, pixman_region32_t *base_damage, struct wl_list *views, struct screen *screen)
46+ {
47+ struct compositor_view *view;
48+ const struct swc_rectangle *target_geom = &target->view->geometry;
49+@@ -314,10 +316,12 @@ renderer_repaint(struct target *target, pixman_region32_t *damage, pixman_region
50+ wld_set_target_surface(swc.drm->renderer, target->surface);
51+
52+ if (pixman_region32_not_empty(base_damage)) {
53++ struct wld_buffer *background = swc_wallpaper_buffer_for_screen(screen);
54++
55+ pixman_region32_translate(base_damage, -target->view->geometry.x, -target->view->geometry.y);
56+
57+- if(wallbuf)
58+- wld_copy_region(swc.drm->renderer, wallbuf, 0, 0, base_damage);
59++ if (background)
60++ wld_copy_region(swc.drm->renderer, background, 0, 0, base_damage);
61+
62+ else
63+ wld_fill_region(swc.drm->renderer, bgcolor, base_damage);
64+@@ -508,6 +512,23 @@ schedule_updates(uint32_t screens)
65+ compositor.scheduled_updates |= screens;
66+ }
67+
68++void
69++compositor_damage_all(void)
70++{
71++ struct screen *screen;
72++
73++ if (!compositor.initialized)
74++ return;
75++
76++ wl_list_for_each (screen, &swc.screens, link) {
77++ pixman_region32_union_rect(&compositor.damage, &compositor.damage,
78++ screen->base.geometry.x, screen->base.geometry.y,
79++ screen->base.geometry.width, screen->base.geometry.height);
80++ }
81++
82++ schedule_updates(-1);
83++}
84++
85+ static void
86+ overlay_damage_region(int32_t x, int32_t y, uint32_t width, uint32_t height, uint32_t border_width)
87+ {
88+@@ -597,6 +618,7 @@ render_zoomed_to_shm(struct screen *screen, float zoom)
89+ int32_t cx = screen_x + width / 2;
90+ int32_t cy = screen_y + height / 2;
91+ struct compositor_view *view;
92++ struct wld_buffer *background;
93+
94+ struct wld_buffer *buffer = wld_create_buffer(swc.shm->context,
95+ width, height, WLD_FORMAT_XRGB8888, WLD_FLAG_MAP);
96+@@ -610,8 +632,9 @@ render_zoomed_to_shm(struct screen *screen, float zoom)
97+
98+ pixman_region32_t full;
99+ pixman_region32_init_rect(&full, 0, 0, width, height);
100+- if (wallbuf)
101+- wld_copy_region(swc.shm->renderer, wallbuf, 0, 0, &full);
102++ background = swc_wallpaper_buffer_for_screen(screen);
103++ if (background)
104++ wld_copy_region(swc.shm->renderer, background, 0, 0, &full);
105+ else
106+ wld_fill_region(swc.shm->renderer, bgcolor, &full);
107+ pixman_region32_fini(&full);
108+@@ -1326,7 +1349,7 @@ update_screen(struct screen *screen)
109+ pixman_region32_translate(&damage, geom->x, geom->y);
110+ pixman_region32_init(&base_damage);
111+ pixman_region32_subtract(&base_damage, &damage, &compositor.opaque);
112+- renderer_repaint(target, &damage, &base_damage, &compositor.views);
113++ renderer_repaint(target, &damage, &base_damage, &compositor.views, screen);
114+ pixman_region32_fini(&damage);
115+ pixman_region32_fini(&base_damage);
116+ }
117+@@ -1507,12 +1530,16 @@ compositor_initialize(void)
118+ for (keysym = XKB_KEY_XF86Switch_VT_1; keysym <= XKB_KEY_XF86Switch_VT_12; ++keysym)
119+ swc_add_binding(SWC_BINDING_KEY, SWC_MOD_ANY, keysym, &handle_switch_vt, NULL);
120+
121++ compositor.initialized = true;
122++
123+ return true;
124+ }
125+
126+ void
127+ compositor_finalize(void)
128+ {
129++ compositor.initialized = false;
130++
131+ if (compositor.zoom_buffer)
132+ wld_buffer_unreference(compositor.zoom_buffer);
133+ pixman_region32_fini(&compositor.damage);
134+diff --git a/libswc/compositor.h b/libswc/compositor.h
135+index 529c360..6c938ac 100644
136+--- a/libswc/compositor.h
137++++ b/libswc/compositor.h
138+@@ -45,6 +45,7 @@ struct swc_compositor {
139+
140+ bool compositor_initialize(void);
141+ void compositor_finalize(void);
142++void compositor_damage_all(void);
143+
144+ struct compositor_view {
145+ struct view base;
146+diff --git a/libswc/internal.h b/libswc/internal.h
147+index 8efba6d..6d2b0a1 100644
148+--- a/libswc/internal.h
149++++ b/libswc/internal.h
150+@@ -50,6 +50,7 @@ struct swc {
151+ struct wl_global *panel_manager;
152+ struct wl_global *shell;
153+ struct wl_global *subcompositor;
154++ struct wl_global *wallpaper_manager;
155+ struct wl_global *xdg_decoration_manager;
156+ struct wl_global *xdg_shell;
157+
158+diff --git a/libswc/swc.c b/libswc/swc.c
159+index 8814c13..1035ef3 100644
160+--- a/libswc/swc.c
161++++ b/libswc/swc.c
162+@@ -39,6 +39,7 @@
163+ #include "shm.h"
164+ #include "subcompositor.h"
165+ #include "util.h"
166++#include "wallpaper.h"
167+ #include "window.h"
168+ #include "xdg_decoration.h"
169+ #include "xdg_shell.h"
170+diff --git a/libswc/swc.h b/libswc/swc.h
171+index 033bdd4..f2a8ae1 100644
172+--- a/libswc/swc.h
173++++ b/libswc/swc.h
174+@@ -35,6 +35,7 @@ extern "C" {
175+ struct libinput_device;
176+ struct wl_display;
177+ struct wl_event_loop;
178++struct wld_buffer;
179+
180+ /**
181+ * gett the current cursor position
182+@@ -438,19 +439,21 @@ int swc_add_axis_binding(uint32_t modifiers, uint32_t axis, swc_axis_binding_han
183+
184+ /* Wallpaper {{{ */
185+
186+-extern unsigned char *wallpaper;
187+-extern struct wld_buffer *wallbuf;
188+-
189+ /**
190+- * Set wallpaper to image from fs path.
191+- * TODO: tiling, maybe diff image for each screen
192++ * Set fallback wallpaper buffer for all screens that dom't have an override.
193+ */
194++void swc_wallpaper_set_buffer(struct wld_buffer *buffer);
195+
196+-void swc_wallpaper_set(char* path);
197++/**
198++ * Set wallpaper buffer for specified screen id.
199++ *
200++ * Passing NULL clears the override for that screen.
201++ */
202++void swc_wallpaper_set_buffer_for_screen(uint8_t screen_id, struct wld_buffer *buffer);
203+
204+ /**
205+ * Set wallpaper to a single color
206+- * pretty much ignored if wallpaper is set to image
207++ * used when no wallpaper buffer is set
208+ * defaults to black
209+ */
210+
211+diff --git a/libswc/wallpaper.c b/libswc/wallpaper.c
212+index 7618bf0..deeb5fd 100644
213+--- a/libswc/wallpaper.c
214++++ b/libswc/wallpaper.c
215+@@ -1,77 +1,116 @@
216+-#include <pixman.h>
217+ #include <wld/wld.h>
218+
219+-#define STB_IMAGE_IMPLEMENTATION
220+-#define STBI_NO_HDR
221+-#include "../stb/stb_image.h"
222+-
223+-#define STB_IMAGE_RESIZE_IMPLEMENTATION
224+-#include "../stb/stb_image_resize2.h"
225+-
226+ #include "swc.h"
227+-#include "internal.h"
228+-#include "drm.h"
229+-#include "util.h"
230+-#include "shm.h"
231++#include "compositor.h"
232+ #include "screen.h"
233++#include "util.h"
234++#include "wayland_buffer.h"
235++#include "wallpaper.h"
236++#include "swc_wallpaper-server-protocol.h"
237+
238+-unsigned char *wallpaper = NULL;
239+-struct wld_buffer *wallbuf = NULL;
240++#define MAX_WALLPAPER_SCREENS 32
241+
242++static struct wld_buffer *wallbuf = NULL;
243++static struct wld_buffer *screen_wallbuf[MAX_WALLPAPER_SCREENS];
244+ uint32_t bgcolor = 0xff000000;
245+
246++static void
247++set_buffer_slot(struct wld_buffer **slot, struct wld_buffer *buffer)
248++{
249++ if (buffer)
250++ wld_buffer_reference(buffer);
251++ if (*slot)
252++ wld_buffer_unreference(*slot);
253++
254++ *slot = buffer;
255++}
256++
257++struct wld_buffer *
258++swc_wallpaper_buffer_for_screen(struct screen *screen)
259++{
260++ if (screen
261++ && screen->id < ARRAY_LENGTH(screen_wallbuf)
262++ && screen_wallbuf[screen->id])
263++ return screen_wallbuf[screen->id];
264++
265++ return wallbuf;
266++}
267++
268+ EXPORT void
269+-swc_wallpaper_set(char* path)
270++swc_wallpaper_set_buffer(struct wld_buffer *buffer)
271+ {
272+- int width, height, chan;
273+- unsigned char *loaded;
274+- struct screen *screen;
275+- int target_width = 0, target_height = 0;
276++ set_buffer_slot(&wallbuf, buffer);
277++ compositor_damage_all();
278++}
279+
280+- loaded = stbi_load(path, &width, &height, &chan, 4);
281+- if (!loaded)
282++EXPORT void
283++swc_wallpaper_set_buffer_for_screen(uint8_t screen_id, struct wld_buffer *buffer)
284++{
285++ if (screen_id >= ARRAY_LENGTH(screen_wallbuf))
286+ return;
287+
288+- /* get screen dimensions */
289+- wl_list_for_each(screen, &swc.screens, link) {
290+- target_width = screen->base.geometry.width;
291+- target_height = screen->base.geometry.height;
292+- break;
293+- }
294++ set_buffer_slot(&screen_wallbuf[screen_id], buffer);
295++ compositor_damage_all();
296++}
297++
298++EXPORT void
299++swc_wallpaper_color_set(uint32_t color)
300++{
301++ bgcolor = color;
302++ compositor_damage_all();
303++}
304++
305++static void
306++set_buffer(struct wl_client *client, struct wl_resource *resource,
307++ int32_t screen_id, struct wl_resource *buffer_resource)
308++{
309++ struct wld_buffer *buffer = NULL;
310+
311+- /* If we have a screen and dimensions wrong scale */
312+- if (target_width > 0 && target_height > 0 &&
313+- (width != target_width || height != target_height)) {
314+- wallpaper = stbir_resize_uint8_srgb(loaded, width, height, 0,
315+- NULL, target_width, target_height, 0,
316+- STBIR_RGBA);
317+- stbi_image_free(loaded);
318+- width = target_width;
319+- height = target_height;
320+- } else {
321+- wallpaper = loaded;
322++ (void)client;
323++
324++ if (buffer_resource) {
325++ buffer = wayland_buffer_get(buffer_resource);
326++ if (!buffer) {
327++ wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT,
328++ "buffer is not a wl_buffer");
329++ return;
330++ }
331+ }
332+
333+- /* swap color channels to be compatible */
334+- for(int i = 0; i < width * height; i++) {
335+- unsigned char r = wallpaper[i*4];
336+- wallpaper[i*4] = wallpaper[(i*4)+2];
337+- wallpaper[(i*4)+2] = r;
338++ if (screen_id == -1) {
339++ swc_wallpaper_set_buffer(buffer);
340++ return;
341+ }
342+
343+- union wld_object obj;
344+- obj.ptr = (uint32_t*)wallpaper;
345++ if (screen_id < 0 || screen_id >= ARRAY_LENGTH(screen_wallbuf))
346++ return;
347+
348+- wallbuf = wld_import_buffer(swc.shm->context,
349+- WLD_OBJECT_DATA,
350+- obj,
351+- width, height,
352+- WLD_FORMAT_ARGB8888,
353+- width * 4);
354++ swc_wallpaper_set_buffer_for_screen((uint8_t)screen_id, buffer);
355+ }
356+
357+-EXPORT void
358+-swc_wallpaper_color_set(uint32_t color)
359++static const struct swc_wallpaper_interface wallpaper_impl = {
360++ .destroy = destroy_resource,
361++ .set_buffer = set_buffer,
362++};
363++
364++static void
365++bind_wallpaper(struct wl_client *client, void *data, uint32_t version, uint32_t id)
366+ {
367+- bgcolor = color;
368++ (void)data;
369++
370++ struct wl_resource *resource;
371++
372++ resource = wl_resource_create(client, &swc_wallpaper_interface, version, id);
373++ if (!resource) {
374++ wl_client_post_no_memory(client);
375++ return;
376++ }
377++
378++ wl_resource_set_implementation(resource, &wallpaper_impl, NULL, NULL);
379++}
380++
381++struct wl_global *
382++swc_wallpaper_manager_create(struct wl_display *display)
383++{
384++ return wl_global_create(display, &swc_wallpaper_interface, 1, NULL, bind_wallpaper);
385+ }
386+diff --git a/libswc/wallpaper.h b/libswc/wallpaper.h
387+new file mode 100644
388+index 0000000..8c4d834
389+--- /dev/null
390++++ b/libswc/wallpaper.h
391+@@ -0,0 +1,12 @@
392++#ifndef SWC_WALLPAPER_H
393++#define SWC_WALLPAPER_H
394++
395++struct wl_display;
396++struct wl_global;
397++struct wld_buffer;
398++struct screen;
399++
400++struct wl_global *swc_wallpaper_manager_create(struct wl_display *display);
401++struct wld_buffer *swc_wallpaper_buffer_for_screen(struct screen *screen);
402++
403++#endif
404+diff --git a/protocol/swc_wallpaper.xml b/protocol/swc_wallpaper.xml
405+new file mode 100644
406+index 0000000..dd7479a
407+--- /dev/null
408++++ b/protocol/swc_wallpaper.xml
409+@@ -0,0 +1,19 @@
410++<?xml version="1.0" encoding="UTF-8"?>
411++<protocol name="swc_wallpaper">
412++ <interface name="swc_wallpaper" version="1">
413++ <description summary="set compositor wallpaper">
414++ Allows clients to set wallpaper buffers.
415++ </description>
416++
417++ <request name="destroy" type="destructor" />
418++
419++ <request name="set_buffer">
420++ <description summary="set or clear wallpaper buffer">
421++ screen_id=-1 targets global fallback wallpaper.
422++ Any other screen_id targets that id.
423++ </description>
424++ <arg name="screen_id" type="int" />
425++ <arg name="buffer" type="object" interface="wl_buffer" allow-null="true" />
426++ </request>
427++ </interface>
428++</protocol>
429+--
430+2.49.0
431+
+1,
-1
1@@ -1 +1 @@
2-Subproject commit 19893a5ca3239082b904a04fdb9ac7c3a1fb62e2
3+Subproject commit 9a20014528f6bb61333a404b81136d918595ee22
+1,
-1
1@@ -1 +1 @@
2-19893a5ca3 r0
3+9a20014 r3