commit 2e266a0
shrub
·
2026-02-26 21:38:29 +0000 UTC
parent c775040
change swcsnap protocol a new version of swcsnap that hands off more to the client, which is faster and makes screen recording possible. some people will tell you this is similar functionality to the already existing screencopy protocol. they might be right, but you should ignore them anyway.
7 files changed,
+899,
-122
M
Makefile
+20,
-1
1@@ -68,6 +68,8 @@ PACKAGES+= xcb xcb-composite xcb-ewmh xcb-icccm
2
3 PKG_CFLAGS!= ${PKG_CONFIG} --cflags ${PACKAGES}
4 PKG_LIBS!= ${PKG_CONFIG} --libs ${PACKAGES}
5+WAYLAND_CLIENT_CFLAGS!= ${PKG_CONFIG} --cflags wayland-client
6+WAYLAND_CLIENT_LIBS!= ${PKG_CONFIG} --libs wayland-client
7 WAYLAND_PROTOCOLS_DATADIR!= ${PKG_CONFIG} --variable=pkgdatadir wayland-protocols
8
9 CPPFLAGS+= -D_GNU_SOURCE
10@@ -267,6 +269,20 @@ libswc/libswc.a: libswc/libswc.o
11 @echo " AR $@"
12 @${AR} cru ${.TARGET} ${.ALLSRC}
13
14+EXTRA_CAPTURE_TOOLS= \
15+ extra/swcsnap
16+
17+.for ex in ${EXTRA_CAPTURE_TOOLS}
18+${ex:R}.o: ${ex}.c protocol/swc_snap-client-protocol.h
19+ @echo " CC $@"
20+ @${CC} ${CPPFLAGS} ${CFLAGS} ${WAYLAND_CLIENT_CFLAGS} -I . -I protocol \
21+ -c -o ${.TARGET} ${.IMPSRC}
22+.endfor
23+
24+extra/swcsnap: extra/swcsnap.o protocol/swc_snap-protocol.o
25+ @echo " CCLD $@"
26+ @${CC} ${LDFLAGS} -o ${.TARGET} ${.ALLSRC} ${WAYLAND_CLIENT_LIBS}
27+
28 swc.pc: swc.pc.in
29 @echo " GEN $@"
30 @sed -e 's:@VERSION@:${VERSION}:' \
31@@ -281,6 +297,7 @@ swc.pc: swc.pc.in
32 .PHONY: all build clean install
33 all: build
34 build: libswc/libswc.a launch/swc-launch cursor/cursor_data.h swc.pc
35+build: ${EXTRA_CAPTURE_TOOLS}
36
37 install: build
38 install -d ${DESTDIR}${BINDIR}
39@@ -289,6 +306,7 @@ install: build
40 install -d ${DESTDIR}${DATADIR}/swc
41 install -d ${DESTDIR}${PKGCONFIGDIR}
42 install -m 4755 launch/swc-launch ${DESTDIR}${BINDIR}/
43+ install -m 755 extra/swcsnap ${DESTDIR}${BINDIR}/
44 install -m 644 libswc/libswc.a ${DESTDIR}${LIBDIR}/
45 install -m 644 libswc/swc.h ${DESTDIR}${INCLUDEDIR}/
46 install -m 644 ${PROTO_GEN_H} ${DESTDIR}${INCLUDEDIR}/
47@@ -301,4 +319,5 @@ clean:
48 cursor/cursor_data.h cursor/convert_font cursor/convert_font.o \
49 launch/*.o launch/swc-launch \
50 libswc/*.o libswc/libswc-internal.o libswc/libswc.o libswc/libswc.a \
51- protocol/*.o
52+ protocol/*.o \
53+ extra/swcsnap.o extra/swcsnap
+544,
-0
1@@ -0,0 +1,544 @@
2+#define _POSIX_C_SOURCE 200809L
3+
4+#include <errno.h>
5+#include <fcntl.h>
6+#include <stdbool.h>
7+#include <stdint.h>
8+#include <stdio.h>
9+#include <stdlib.h>
10+#include <string.h>
11+#include <sys/mman.h>
12+#include <time.h>
13+#include <unistd.h>
14+
15+#include <wayland-client.h>
16+
17+#include "swc_snap-client-protocol.h"
18+
19+enum { CAPTURE_FLAGS = SWC_SNAP_FLAGS_OVERLAY_CURSOR };
20+
21+struct shm_buffer {
22+ struct wl_shm_pool *pool;
23+ struct wl_buffer *buffer;
24+ void *data;
25+ size_t size;
26+ int fd;
27+ int32_t width;
28+ int32_t height;
29+ int32_t stride;
30+};
31+
32+struct app {
33+ struct wl_display *display;
34+ struct wl_registry *registry;
35+ struct wl_shm *shm;
36+ struct swc_snap *snap;
37+ uint32_t width;
38+ uint32_t height;
39+ bool have_buffer_info;
40+ bool capture_ready;
41+ bool capture_failed;
42+ uint32_t failure_reason;
43+};
44+
45+static void
46+usage(FILE *stream, const char *argv0)
47+{
48+ fprintf(stream,
49+ "usage:\n"
50+ " %s image [output.ppm]\n"
51+ " %s record [-o output.raw|-] [-n frames] [-r fps]\n",
52+ argv0, argv0);
53+}
54+
55+static ssize_t
56+write_all(int fd, const void *data, size_t size)
57+{
58+ const uint8_t *p = data;
59+ size_t written = 0;
60+
61+ while (written < size) {
62+ ssize_t rc = write(fd, p + written, size - written);
63+ if (rc < 0) {
64+ if (errno == EINTR) {
65+ continue;
66+ }
67+ return -1;
68+ }
69+ written += (size_t)rc;
70+ }
71+
72+ return (ssize_t)written;
73+}
74+
75+static int
76+create_shm_file(size_t size)
77+{
78+ char path[] = "/tmp/swcsnap-XXXXXX";
79+ int fd = mkstemp(path);
80+
81+ if (fd < 0) {
82+ return -1;
83+ }
84+
85+ unlink(path);
86+
87+ if (ftruncate(fd, (off_t)size) < 0) {
88+ close(fd);
89+ return -1;
90+ }
91+
92+ return fd;
93+}
94+
95+static bool
96+shm_buffer_create(struct shm_buffer *buffer, struct wl_shm *shm,
97+ int32_t width, int32_t height, uint32_t format)
98+{
99+ size_t size;
100+
101+ memset(buffer, 0, sizeof(*buffer));
102+ buffer->fd = -1;
103+ buffer->width = width;
104+ buffer->height = height;
105+ buffer->stride = width * 4;
106+ size = (size_t)buffer->stride * buffer->height;
107+ buffer->size = size;
108+
109+ buffer->fd = create_shm_file(size);
110+ if (buffer->fd < 0) {
111+ return false;
112+ }
113+
114+ buffer->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,
115+ buffer->fd, 0);
116+ if (buffer->data == MAP_FAILED) {
117+ close(buffer->fd);
118+ buffer->fd = -1;
119+ buffer->data = NULL;
120+ return false;
121+ }
122+
123+ buffer->pool = wl_shm_create_pool(shm, buffer->fd, (int32_t)size);
124+ if (!buffer->pool) {
125+ munmap(buffer->data, size);
126+ close(buffer->fd);
127+ buffer->fd = -1;
128+ buffer->data = NULL;
129+ return false;
130+ }
131+
132+ buffer->buffer = wl_shm_pool_create_buffer(
133+ buffer->pool, 0, width, height, buffer->stride, format);
134+ if (!buffer->buffer) {
135+ wl_shm_pool_destroy(buffer->pool);
136+ munmap(buffer->data, size);
137+ close(buffer->fd);
138+ buffer->pool = NULL;
139+ buffer->fd = -1;
140+ buffer->data = NULL;
141+ return false;
142+ }
143+
144+ return true;
145+}
146+
147+static void
148+shm_buffer_destroy(struct shm_buffer *buffer)
149+{
150+ if (buffer->buffer) {
151+ wl_buffer_destroy(buffer->buffer);
152+ }
153+ if (buffer->pool) {
154+ wl_shm_pool_destroy(buffer->pool);
155+ }
156+ if (buffer->data) {
157+ munmap(buffer->data, buffer->size);
158+ }
159+ if (buffer->fd >= 0) {
160+ close(buffer->fd);
161+ }
162+ memset(buffer, 0, sizeof(*buffer));
163+ buffer->fd = -1;
164+}
165+
166+static bool
167+write_ppm(const char *path, const struct shm_buffer *buffer)
168+{
169+ FILE *f;
170+ uint8_t rgb[3];
171+
172+ f = fopen(path, "wb");
173+ if (!f) {
174+ perror("fopen");
175+ return false;
176+ }
177+
178+ if (fprintf(f, "P6\n%d %d\n255\n", buffer->width, buffer->height) < 0) {
179+ fclose(f);
180+ return false;
181+ }
182+
183+ for (int32_t y = 0; y < buffer->height; y++) {
184+ const uint8_t *row =
185+ (const uint8_t *)buffer->data + (size_t)y * buffer->stride;
186+ for (int32_t x = 0; x < buffer->width; x++) {
187+ const uint8_t *px = row + (size_t)x * 4;
188+
189+ rgb[0] = px[2];
190+ rgb[1] = px[1];
191+ rgb[2] = px[0];
192+ if (fwrite(rgb, 1, sizeof(rgb), f) != sizeof(rgb)) {
193+ fclose(f);
194+ return false;
195+ }
196+ }
197+ }
198+
199+ return fclose(f) == 0;
200+}
201+
202+static void
203+handle_snap_buffer(void *data, struct swc_snap *snap, uint32_t width,
204+ uint32_t height, uint32_t format)
205+{
206+ struct app *app = data;
207+ (void)snap;
208+
209+ app->width = width;
210+ app->height = height;
211+ app->have_buffer_info = true;
212+}
213+
214+static void
215+handle_snap_ready(void *data, struct swc_snap *snap, uint32_t tv_sec,
216+ uint32_t tv_nsec)
217+{
218+ struct app *app = data;
219+ (void)snap;
220+ (void)tv_sec;
221+ (void)tv_nsec;
222+
223+ app->capture_ready = true;
224+}
225+
226+static void
227+handle_snap_failed(void *data, struct swc_snap *snap, uint32_t reason)
228+{
229+ struct app *app = data;
230+ (void)snap;
231+
232+ app->capture_failed = true;
233+ app->failure_reason = reason;
234+}
235+
236+static const struct swc_snap_listener snap_listener = {
237+ .buffer = handle_snap_buffer,
238+ .ready = handle_snap_ready,
239+ .failed = handle_snap_failed,
240+};
241+
242+static void
243+handle_global(void *data, struct wl_registry *registry, uint32_t name,
244+ const char *interface, uint32_t version)
245+{
246+ struct app *app = data;
247+
248+ if (strcmp(interface, wl_shm_interface.name) == 0) {
249+ app->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
250+ return;
251+ }
252+
253+ if (strcmp(interface, "swc_snap") == 0) {
254+ uint32_t bind_version = version < 2 ? version : 2;
255+
256+ app->snap =
257+ wl_registry_bind(registry, name, &swc_snap_interface, bind_version);
258+ if (app->snap) {
259+ swc_snap_add_listener(app->snap, &snap_listener, app);
260+ }
261+ }
262+}
263+
264+static void
265+handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
266+{
267+ (void)data;
268+ (void)registry;
269+ (void)name;
270+}
271+
272+static const struct wl_registry_listener registry_listener = {
273+ .global = handle_global,
274+ .global_remove = handle_global_remove,
275+};
276+
277+static bool
278+app_connect(struct app *app)
279+{
280+ memset(app, 0, sizeof(*app));
281+
282+ app->display = wl_display_connect(NULL);
283+ if (!app->display) {
284+ fprintf(stderr, "swcsnap: failed to connect to Wayland display\n");
285+ return false;
286+ }
287+
288+ app->registry = wl_display_get_registry(app->display);
289+ wl_registry_add_listener(app->registry, ®istry_listener, app);
290+
291+ if (wl_display_roundtrip(app->display) < 0 ||
292+ wl_display_roundtrip(app->display) < 0) {
293+ fprintf(stderr, "swcsnap: roundtrip failed\n");
294+ return false;
295+ }
296+
297+ if (!app->shm || !app->snap || !app->have_buffer_info) {
298+ fprintf(stderr, "swcsnap: missing wl_shm/swc_snap metadata\n");
299+ return false;
300+ }
301+
302+ return true;
303+}
304+
305+static void
306+app_disconnect(struct app *app)
307+{
308+ if (app->snap) {
309+ swc_snap_destroy(app->snap);
310+ }
311+ if (app->shm) {
312+ wl_shm_destroy(app->shm);
313+ }
314+ if (app->registry) {
315+ wl_registry_destroy(app->registry);
316+ }
317+ if (app->display) {
318+ wl_display_disconnect(app->display);
319+ }
320+ memset(app, 0, sizeof(*app));
321+}
322+
323+static bool
324+capture_once(struct app *app, struct shm_buffer *buffer)
325+{
326+ app->capture_ready = false;
327+ app->capture_failed = false;
328+ app->failure_reason = 0;
329+
330+ swc_snap_capture(app->snap, buffer->buffer, CAPTURE_FLAGS);
331+ wl_display_flush(app->display);
332+
333+ while (!app->capture_ready && !app->capture_failed) {
334+ if (wl_display_dispatch(app->display) < 0) {
335+ return false;
336+ }
337+ }
338+
339+ return !app->capture_failed;
340+}
341+
342+static void
343+timespec_add_ns(struct timespec *ts, long ns)
344+{
345+ ts->tv_nsec += ns;
346+ while (ts->tv_nsec >= 1000000000L) {
347+ ts->tv_nsec -= 1000000000L;
348+ ts->tv_sec += 1;
349+ }
350+}
351+
352+static void
353+sleep_until(const struct timespec *deadline)
354+{
355+ int rc;
356+
357+ while ((rc = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, deadline,
358+ NULL)) != 0) {
359+ if (rc != EINTR) {
360+ return;
361+ }
362+ }
363+}
364+
365+static int
366+cmd_image(const char *prog, int argc, char **argv)
367+{
368+ struct app app;
369+ struct shm_buffer buffer = {.fd = -1};
370+ const char *output = "shot.ppm";
371+ int ret = 1;
372+
373+ if (argc > 2) {
374+ fprintf(stderr, "usage: %s image [output.ppm]\n", prog);
375+ return 1;
376+ }
377+ if (argc == 2) {
378+ output = argv[1];
379+ }
380+
381+ if (!app_connect(&app)) {
382+ goto out0;
383+ }
384+
385+ if (!shm_buffer_create(&buffer, app.shm, (int32_t)app.width,
386+ (int32_t)app.height, WL_SHM_FORMAT_ARGB8888)) {
387+ fprintf(stderr, "swcsnap: failed to allocate shm buffer\n");
388+ goto out1;
389+ }
390+
391+ if (!capture_once(&app, &buffer)) {
392+ fprintf(stderr, "swcsnap: capture failed (%u)\n", app.failure_reason);
393+ goto out2;
394+ }
395+
396+ if (!write_ppm(output, &buffer)) {
397+ fprintf(stderr, "swcsnap: failed to write %s\n", output);
398+ goto out2;
399+ }
400+
401+ ret = 0;
402+
403+out2:
404+ shm_buffer_destroy(&buffer);
405+out1:
406+ app_disconnect(&app);
407+out0:
408+ return ret;
409+}
410+
411+static int
412+cmd_record(const char *prog, int argc, char **argv)
413+{
414+ struct app app;
415+ struct shm_buffer buffer = {.fd = -1};
416+ unsigned fps = 60;
417+ unsigned frames = 600;
418+ const char *output_path = "-";
419+ int out_fd = STDOUT_FILENO;
420+ struct timespec deadline;
421+ long frame_ns;
422+ int ret = 1;
423+ int opt;
424+
425+ optind = 1;
426+ while ((opt = getopt(argc, argv, "o:n:r:")) != -1) {
427+ switch (opt) {
428+ case 'o':
429+ output_path = optarg;
430+ break;
431+ case 'n':
432+ frames = (unsigned)strtoul(optarg, NULL, 10);
433+ break;
434+ case 'r':
435+ fps = (unsigned)strtoul(optarg, NULL, 10);
436+ break;
437+ default:
438+ fprintf(stderr,
439+ "usage: %s record [-o output.raw|-] [-n frames] [-r fps]\n",
440+ prog);
441+ return 1;
442+ }
443+ }
444+
445+ if (optind != argc) {
446+ fprintf(stderr,
447+ "usage: %s record [-o output.raw|-] [-n frames] [-r fps]\n",
448+ prog);
449+ return 1;
450+ }
451+
452+ if (fps == 0) {
453+ fprintf(stderr, "swcsnap: fps must be > 0\n");
454+ return 1;
455+ }
456+
457+ if (strcmp(output_path, "-") != 0) {
458+ out_fd = open(output_path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
459+ if (out_fd < 0) {
460+ perror("open");
461+ return 1;
462+ }
463+ }
464+
465+ if (!app_connect(&app)) {
466+ goto out0;
467+ }
468+
469+ if (!shm_buffer_create(&buffer, app.shm, (int32_t)app.width,
470+ (int32_t)app.height, WL_SHM_FORMAT_ARGB8888)) {
471+ fprintf(stderr, "swcsnap: failed to allocate shm buffer\n");
472+ goto out1;
473+ }
474+
475+ fprintf(stderr, "swcsnap: rawvideo stream %ux%u bgra @ %u fps (%u frames)\n",
476+ app.width, app.height, fps, frames);
477+
478+ frame_ns = 1000000000L / (long)fps;
479+ clock_gettime(CLOCK_MONOTONIC, &deadline);
480+
481+ for (unsigned i = 0; i < frames;) {
482+ if ((uint32_t)buffer.width != app.width ||
483+ (uint32_t)buffer.height != app.height) {
484+ shm_buffer_destroy(&buffer);
485+ if (!shm_buffer_create(&buffer, app.shm, (int32_t)app.width,
486+ (int32_t)app.height,
487+ WL_SHM_FORMAT_ARGB8888)) {
488+ fprintf(stderr, "swcsnap: failed to reallocate shm buffer\n");
489+ goto out2;
490+ }
491+ fprintf(stderr, "swcsnap: resized to %ux%u\n", app.width,
492+ app.height);
493+ }
494+
495+ if (!capture_once(&app, &buffer)) {
496+ if (app.capture_failed &&
497+ app.failure_reason == SWC_SNAP_FAILURE_REASON_SIZE_MISMATCH) {
498+ continue;
499+ }
500+ fprintf(stderr, "swcsnap: capture failed (%u)\n",
501+ app.failure_reason);
502+ goto out2;
503+ }
504+
505+ if (write_all(out_fd, buffer.data, buffer.size) < 0) {
506+ perror("write");
507+ goto out2;
508+ }
509+
510+ i++;
511+ timespec_add_ns(&deadline, frame_ns);
512+ sleep_until(&deadline);
513+ }
514+
515+ ret = 0;
516+
517+out2:
518+ shm_buffer_destroy(&buffer);
519+out1:
520+ app_disconnect(&app);
521+out0:
522+ if (out_fd != STDOUT_FILENO && out_fd >= 0) {
523+ close(out_fd);
524+ }
525+ return ret;
526+}
527+
528+int
529+main(int argc, char **argv)
530+{
531+ if (argc < 2) {
532+ usage(stderr, argv[0]);
533+ return 1;
534+ }
535+
536+ if (strcmp(argv[1], "image") == 0) {
537+ return cmd_image(argv[0], argc - 1, argv + 1);
538+ }
539+ if (strcmp(argv[1], "record") == 0) {
540+ return cmd_record(argv[0], argc - 1, argv + 1);
541+ }
542+
543+ usage(stderr, argv[0]);
544+ return 1;
545+}
+56,
-43
1@@ -333,6 +333,54 @@ repaint_view(struct target *target, struct compositor_view *view,
2 pixman_region32_fini(&in_border);
3 }
4
5+static void
6+draw_overlays(struct wld_renderer *renderer, const struct swc_rectangle *target_geom)
7+{
8+ int32_t tx = (int32_t)target_geom->width;
9+ int32_t ty = (int32_t)target_geom->height;
10+
11+/* draw box as 4 rectangles with wld */
12+#define CLAMP_LOW(v, lo) ((v) < (lo) ? (lo) : (v))
13+#define CLAMP_HIGH(v, hi) ((v) > (hi) ? (hi) : (v))
14+#define DRAW_CLIPPED(rx, ry, rw, rh, clr) \
15+ do { \
16+ int32_t _x1 = CLAMP_LOW((rx), 0); \
17+ int32_t _y1 = CLAMP_LOW((ry), 0); \
18+ int32_t _x2 = CLAMP_HIGH((rx) + (int32_t)(rw), tx); \
19+ int32_t _y2 = CLAMP_HIGH((ry) + (int32_t)(rh), ty); \
20+ if (_x2 > _x1 && _y2 > _y1) \
21+ wld_fill_rectangle(renderer, (clr), _x1, _y1, \
22+ (uint32_t)(_x2 - _x1), (uint32_t)(_y2 - _y1)); \
23+ } while (0)
24+
25+ if (overlay.active && overlay.border_width > 0) {
26+ int32_t x = overlay.x - target_geom->x;
27+ int32_t y = overlay.y - target_geom->y;
28+ uint32_t w = overlay.width, h = overlay.height,
29+ bw = overlay.border_width;
30+
31+ if (w > 0 && h > 0) {
32+ if (bw > w) {
33+ bw = w;
34+ }
35+ if (bw > h) {
36+ bw = h;
37+ }
38+
39+ DRAW_CLIPPED(x, y, (int32_t)w, (int32_t)bw, overlay.color);
40+ DRAW_CLIPPED(x, y + (int32_t)h - (int32_t)bw, (int32_t)w,
41+ (int32_t)bw, overlay.color);
42+ DRAW_CLIPPED(x, y, (int32_t)bw, (int32_t)h, overlay.color);
43+ DRAW_CLIPPED(x + (int32_t)w - (int32_t)bw, y, (int32_t)bw,
44+ (int32_t)h, overlay.color);
45+ }
46+ }
47+
48+#undef DRAW_CLIPPED
49+#undef CLAMP_HIGH
50+#undef CLAMP_LOW
51+}
52+
53 static void
54 renderer_repaint(struct target *target, pixman_region32_t *damage,
55 pixman_region32_t *base_damage, struct wl_list *views,
56@@ -369,48 +417,7 @@ renderer_repaint(struct target *target, pixman_region32_t *damage,
57 }
58 }
59
60- if (overlay.active && overlay.border_width > 0) {
61- int32_t x = overlay.x - target_geom->x;
62- int32_t y = overlay.y - target_geom->y;
63- uint32_t w = overlay.width, h = overlay.height,
64- bw = overlay.border_width;
65- int32_t tx = (int32_t)target_geom->width;
66- int32_t ty = (int32_t)target_geom->height;
67-
68-/* draw box as 4 rectangles with wld */
69-#define CLAMP_LOW(v, lo) ((v) < (lo) ? (lo) : (v))
70-#define CLAMP_HIGH(v, hi) ((v) > (hi) ? (hi) : (v))
71-#define DRAW_CLIPPED(rx, ry, rw, rh) \
72- do { \
73- int32_t _x1 = CLAMP_LOW((rx), 0); \
74- int32_t _y1 = CLAMP_LOW((ry), 0); \
75- int32_t _x2 = CLAMP_HIGH((rx) + (int32_t)(rw), tx); \
76- int32_t _y2 = CLAMP_HIGH((ry) + (int32_t)(rh), ty); \
77- if (_x2 > _x1 && _y2 > _y1) \
78- wld_fill_rectangle(swc.drm->renderer, overlay.color, _x1, _y1, \
79- (uint32_t)(_x2 - _x1), (uint32_t)(_y2 - _y1)); \
80- } while (0)
81-
82- if (w > 0 && h > 0) {
83- if (bw > w) {
84- bw = w;
85- }
86- if (bw > h) {
87- bw = h;
88- }
89-
90- DRAW_CLIPPED(x, y, (int32_t)w, (int32_t)bw); /* top */
91- DRAW_CLIPPED(x, y + (int32_t)h - (int32_t)bw, (int32_t)w,
92- (int32_t)bw); /* bottom */
93- DRAW_CLIPPED(x, y, (int32_t)bw, (int32_t)h); /* left */
94- DRAW_CLIPPED(x + (int32_t)w - (int32_t)bw, y, (int32_t)bw,
95- (int32_t)h); /* right */
96- }
97-
98-#undef DRAW_CLIPPED
99-#undef CLAMP_HIGH
100-#undef CLAMP_LOW
101- }
102+ draw_overlays(swc.drm->renderer, target_geom);
103
104 wld_flush(swc.drm->renderer);
105 }
106@@ -701,7 +708,7 @@ render_zoomed_to_shm(struct screen *screen, float zoom)
107 struct wld_buffer *background;
108
109 struct wld_buffer *buffer = wld_create_buffer(
110- swc.shm->context, width, height, WLD_FORMAT_XRGB8888, WLD_FLAG_MAP);
111+ swc.shm->context, width, height, WLD_FORMAT_ARGB8888, WLD_FLAG_MAP);
112 if (!buffer) {
113 return NULL;
114 }
115@@ -1754,6 +1761,10 @@ compositor_get_buffer(struct screen *screen)
116 struct wld_buffer *
117 compositor_render_to_shm(struct screen *screen)
118 {
119+ if (compositor.zoom != 1.0f) {
120+ return render_zoomed_to_shm(screen, compositor.zoom);
121+ }
122+
123 uint32_t width = screen->base.geometry.width;
124 uint32_t height = screen->base.geometry.height;
125 struct wld_buffer *buffer;
126@@ -1871,6 +1882,8 @@ compositor_render_to_shm(struct screen *screen)
127 }
128 }
129
130+ draw_overlays(swc.shm->renderer, &screen->base.geometry);
131+
132 wld_flush(swc.shm->renderer);
133 pixman_region32_fini(®ion);
134 pixman_region32_fini(&damage);
+106,
-5
1@@ -46,6 +46,7 @@ struct pool {
2 void *data;
3 uint32_t size;
4 int fd;
5+ bool writable;
6 unsigned references;
7 };
8
9@@ -54,6 +55,41 @@ struct pool_reference {
10 struct pool *pool;
11 };
12
13+struct shm_buffer_record {
14+ struct wl_list link;
15+ struct wl_listener destroy_listener;
16+ struct wl_resource *resource;
17+ struct pool *pool;
18+ uint32_t offset;
19+ int32_t width;
20+ int32_t height;
21+ int32_t stride;
22+ uint32_t format;
23+};
24+
25+static struct wl_list shm_buffer_records;
26+static bool shm_buffer_records_initialized;
27+
28+static void
29+ensure_shm_buffer_records(void)
30+{
31+ if (!shm_buffer_records_initialized) {
32+ wl_list_init(&shm_buffer_records);
33+ shm_buffer_records_initialized = true;
34+ }
35+}
36+
37+static void
38+handle_shm_buffer_resource_destroy(struct wl_listener *listener, void *data)
39+{
40+ struct shm_buffer_record *record =
41+ wl_container_of(listener, record, destroy_listener);
42+ (void)data;
43+
44+ wl_list_remove(&record->link);
45+ free(record);
46+}
47+
48 static void *
49 swc_mremap(struct pool *pool, void *oldp, size_t oldsize, size_t newsize)
50 {
51@@ -121,6 +157,7 @@ create_buffer(struct wl_client *client, struct wl_resource *resource,
52 {
53 struct pool *pool = wl_resource_get_user_data(resource);
54 struct pool_reference *reference;
55+ struct shm_buffer_record *record = NULL;
56 struct wld_buffer *buffer;
57 struct wl_resource *buffer_resource;
58 union wld_object object;
59@@ -147,9 +184,25 @@ create_buffer(struct wl_client *client, struct wl_resource *resource,
60 goto error1;
61 }
62
63- if (!(reference = malloc(sizeof(*reference)))) {
64+ ensure_shm_buffer_records();
65+ record = malloc(sizeof(*record));
66+ if (!record) {
67 goto error2;
68 }
69+ record->resource = buffer_resource;
70+ record->pool = pool;
71+ record->offset = (uint32_t)offset;
72+ record->width = width;
73+ record->height = height;
74+ record->stride = stride;
75+ record->format = format;
76+ record->destroy_listener.notify = &handle_shm_buffer_resource_destroy;
77+ wl_resource_add_destroy_listener(buffer_resource, &record->destroy_listener);
78+ wl_list_insert(&shm_buffer_records, &record->link);
79+
80+ if (!(reference = malloc(sizeof(*reference)))) {
81+ goto error3;
82+ }
83
84 reference->pool = pool;
85 reference->destructor.destroy = &handle_buffer_destroy;
86@@ -158,6 +211,12 @@ create_buffer(struct wl_client *client, struct wl_resource *resource,
87
88 return;
89
90+error3:
91+ if (record) {
92+ wl_list_remove(&record->destroy_listener.link);
93+ wl_list_remove(&record->link);
94+ free(record);
95+ }
96 error2:
97 wl_resource_destroy(buffer_resource);
98 error1:
99@@ -230,11 +289,16 @@ create_pool(struct wl_client *client, struct wl_resource *resource, uint32_t id,
100 }
101 wl_resource_set_implementation(pool->resource, &shm_pool_impl, pool,
102 &destroy_pool_resource);
103- pool->data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
104+ pool->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
105+ pool->writable = true;
106 if (pool->data == MAP_FAILED) {
107- wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FD,
108- "mmap failed: %s", strerror(errno));
109- goto error2;
110+ pool->data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
111+ pool->writable = false;
112+ if (pool->data == MAP_FAILED) {
113+ wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FD,
114+ "mmap failed: %s", strerror(errno));
115+ goto error2;
116+ }
117 }
118 /* close(fd); */
119 pool->size = size;
120@@ -312,3 +376,40 @@ shm_destroy(struct swc_shm *shm)
121 wld_destroy_context(shm->context);
122 free(shm);
123 }
124+
125+bool
126+shm_buffer_get_info(struct wl_resource *resource, struct swc_shm_buffer_info *info)
127+{
128+ struct shm_buffer_record *record;
129+ uint64_t size;
130+
131+ if (!shm_buffer_records_initialized) {
132+ return false;
133+ }
134+
135+ wl_list_for_each(record, &shm_buffer_records, link)
136+ {
137+ if (record->resource != resource) {
138+ continue;
139+ }
140+
141+ if (record->width < 0 || record->height < 0 || record->stride < 0) {
142+ return false;
143+ }
144+
145+ size = (uint64_t)record->stride * (uint64_t)record->height;
146+ if ((uint64_t)record->offset + size > record->pool->size) {
147+ return false;
148+ }
149+
150+ info->data = (uint8_t *)record->pool->data + record->offset;
151+ info->width = record->width;
152+ info->height = record->height;
153+ info->stride = record->stride;
154+ info->format = record->format;
155+ info->writable = record->pool->writable;
156+ return true;
157+ }
158+
159+ return false;
160+}
+15,
-0
1@@ -24,7 +24,11 @@
2 #ifndef SWC_SHM_H
3 #define SWC_SHM_H
4
5+#include <stdbool.h>
6+#include <stdint.h>
7+
8 struct wl_display;
9+struct wl_resource;
10
11 struct swc_shm {
12 struct wl_global *global;
13@@ -32,9 +36,20 @@ struct swc_shm {
14 struct wld_renderer *renderer;
15 };
16
17+struct swc_shm_buffer_info {
18+ void *data;
19+ int32_t width;
20+ int32_t height;
21+ int32_t stride;
22+ uint32_t format;
23+ bool writable;
24+};
25+
26 struct swc_shm *
27 shm_create(struct wl_display *display);
28 void
29 shm_destroy(struct swc_shm *shm);
30+bool
31+shm_buffer_get_info(struct wl_resource *resource, struct swc_shm_buffer_info *info);
32
33 #endif
+92,
-67
1@@ -5,62 +5,15 @@
2 #include "screen.h"
3 #include "seat.h"
4 #include "shm.h"
5+#include "util.h"
6
7 #include "swc_snap-server-protocol.h"
8 #include <stdint.h>
9-#include <stdio.h>
10-#include <stdlib.h>
11 #include <string.h>
12-#include <unistd.h>
13+#include <time.h>
14 #include <wayland-server.h>
15 #include <wld/wld.h>
16
17-static unsigned char *ppm_rgb_buffer;
18-static size_t ppm_rgb_buffer_size;
19-
20-static void
21-ppm(int fd, const uint8_t *pixels, uint32_t width, uint32_t height,
22- uint32_t pitch)
23-{
24- FILE *f = fdopen(fd, "wb");
25- size_t rgb_size = (size_t)width * height * 3;
26-
27- if (!f) {
28- close(fd);
29- return;
30- }
31-
32- if (rgb_size > ppm_rgb_buffer_size) {
33- unsigned char *buffer = realloc(ppm_rgb_buffer, rgb_size);
34- if (!buffer) {
35- fclose(f);
36- return;
37- }
38-
39- ppm_rgb_buffer = buffer;
40- ppm_rgb_buffer_size = rgb_size;
41- }
42-
43- /* ppm header */
44- fprintf(f, "P6\n%u %u\n255\n", width, height);
45-
46- /* pixel data convert argb8888 to rgb) */
47- unsigned char *dst = ppm_rgb_buffer;
48- for (uint32_t y = 0; y < height; y++) {
49- const uint32_t *row = (const uint32_t *)(pixels + ((size_t)y * pitch));
50-
51- for (uint32_t x = 0; x < width; x++) {
52- uint32_t pixel = row[x];
53- *dst++ = (pixel >> 16) & 0xFF;
54- *dst++ = (pixel >> 8) & 0xFF;
55- *dst++ = pixel & 0xFF;
56- }
57- }
58-
59- fwrite(ppm_rgb_buffer, 1, rgb_size, f);
60- fclose(f);
61-}
62-
63 /* get cursor */
64 static void
65 cursor(uint8_t *dst, uint32_t dst_width, uint32_t dst_height,
66@@ -154,50 +107,114 @@ cursor(uint8_t *dst, uint32_t dst_width, uint32_t dst_height,
67 }
68
69 static void
70-capture(struct wl_client *client, struct wl_resource *resource, int32_t fd)
71+send_buffer_metadata(struct wl_resource *resource, uint32_t width,
72+ uint32_t height)
73+{
74+ swc_snap_send_buffer(resource, width, height,
75+ SWC_SNAP_PIXEL_FORMAT_ARGB8888);
76+}
77+
78+static bool
79+get_screen(struct screen **screen, uint32_t *width, uint32_t *height)
80+{
81+ struct screen *s;
82+
83+ if (wl_list_empty(&swc.screens)) {
84+ return false;
85+ }
86+
87+ s = wl_container_of(swc.screens.next, s, link);
88+ *screen = s;
89+ *width = s->base.geometry.width;
90+ *height = s->base.geometry.height;
91+ return true;
92+}
93+
94+static void
95+capture(struct wl_client *client, struct wl_resource *resource,
96+ struct wl_resource *buffer_resource, uint32_t flags)
97 {
98 struct screen *screen;
99+ struct swc_shm_buffer_info target;
100 struct wld_buffer *shm_buffer;
101- uint8_t *pixels;
102+ uint8_t *src_pixels;
103+ uint8_t *dst_pixels;
104 uint32_t width, height;
105+ uint32_t target_width, target_height, target_stride, target_format;
106+ uint32_t row_bytes;
107+ struct timespec ts;
108+ (void)client;
109
110- if (wl_list_empty(&swc.screens)) {
111- fprintf(stderr, "snap: no screens available\n");
112- close(fd);
113+ if (!get_screen(&screen, &width, &height)) {
114+ swc_snap_send_failed(resource, SWC_SNAP_FAILURE_REASON_NO_SCREEN);
115 return;
116 }
117
118- screen = wl_container_of(swc.screens.next, screen, link);
119- width = screen->base.geometry.width;
120- height = screen->base.geometry.height;
121+ send_buffer_metadata(resource, width, height);
122+
123+ if (!shm_buffer_get_info(buffer_resource, &target)) {
124+ swc_snap_send_failed(resource,
125+ SWC_SNAP_FAILURE_REASON_UNSUPPORTED_BUFFER);
126+ return;
127+ }
128+
129+ target_format = target.format;
130+ if (target_format != WL_SHM_FORMAT_XRGB8888 &&
131+ target_format != WL_SHM_FORMAT_ARGB8888) {
132+ swc_snap_send_failed(resource,
133+ SWC_SNAP_FAILURE_REASON_UNSUPPORTED_BUFFER);
134+ return;
135+ }
136+
137+ target_width = (uint32_t)target.width;
138+ target_height = (uint32_t)target.height;
139+ target_stride = (uint32_t)target.stride;
140+ row_bytes = width * 4;
141+
142+ if (target_width < width || target_height < height ||
143+ target_stride < row_bytes) {
144+ swc_snap_send_failed(resource, SWC_SNAP_FAILURE_REASON_SIZE_MISMATCH);
145+ return;
146+ }
147+ if (!target.writable) {
148+ swc_snap_send_failed(resource, SWC_SNAP_FAILURE_REASON_UNSUPPORTED_BUFFER);
149+ return;
150+ }
151
152- /* put compositor in shm*/
153 shm_buffer = compositor_render_to_shm(screen);
154 if (!shm_buffer) {
155- fprintf(stderr, "snap: failed to render to SHM\n");
156- close(fd);
157+ swc_snap_send_failed(resource, SWC_SNAP_FAILURE_REASON_INTERNAL);
158 return;
159 }
160
161- /* get pixel data from shm */
162 if (!wld_map(shm_buffer) || !shm_buffer->map) {
163- fprintf(stderr, "snap: failed to map buffer data\n");
164 wld_buffer_unreference(shm_buffer);
165- close(fd);
166+ swc_snap_send_failed(resource, SWC_SNAP_FAILURE_REASON_INTERNAL);
167 return;
168 }
169
170- pixels = shm_buffer->map;
171+ src_pixels = shm_buffer->map;
172+
173+ if (flags & SWC_SNAP_FLAGS_OVERLAY_CURSOR) {
174+ cursor(src_pixels, width, height, shm_buffer->pitch, screen);
175+ }
176
177- cursor(pixels, width, height, shm_buffer->pitch, screen);
178+ dst_pixels = target.data;
179
180- ppm(fd, pixels, width, height, shm_buffer->pitch);
181+ for (uint32_t y = 0; y < height; y++) {
182+ memcpy(dst_pixels + (size_t)y * target_stride,
183+ src_pixels + (size_t)y * shm_buffer->pitch, row_bytes);
184+ }
185
186 wld_unmap(shm_buffer);
187 wld_buffer_unreference(shm_buffer);
188+
189+ clock_gettime(CLOCK_MONOTONIC, &ts);
190+ swc_snap_send_ready(resource, (uint32_t)ts.tv_sec, (uint32_t)ts.tv_nsec);
191 }
192
193 static const struct swc_snap_interface snap_impl = {
194+ .destroy = destroy_resource,
195 .capture = capture,
196 };
197
198@@ -212,10 +229,18 @@ bind_snap(struct wl_client *client, void *data, uint32_t version, uint32_t id)
199 return;
200 }
201 wl_resource_set_implementation(resource, &snap_impl, NULL, NULL);
202+
203+ if (!wl_list_empty(&swc.screens)) {
204+ struct screen *screen;
205+
206+ screen = wl_container_of(swc.screens.next, screen, link);
207+ send_buffer_metadata(resource, screen->base.geometry.width,
208+ screen->base.geometry.height);
209+ }
210 }
211
212 struct wl_global *
213 snap_manager_create(struct wl_display *display)
214 {
215- return wl_global_create(display, &swc_snap_interface, 1, NULL, &bind_snap);
216+ return wl_global_create(display, &swc_snap_interface, 2, NULL, &bind_snap);
217 }
+66,
-6
1@@ -1,15 +1,75 @@
2 <?xml version="1.0" encoding="UTF-8"?>
3 <protocol name="swc_snap">
4- <interface name="swc_snap" version="1">
5- <description summary="simple screenshot to file descriptor">
6- provides a minimal screenshot capability.
7+ <interface name="swc_snap" version="2">
8+ <description summary="raw screen capture into client buffers">
9+ gives a screen capture capability where the client supplies a
10+ wl_shm buffer and the compositor fills it with a raw frame.
11 </description>
12
13+ <enum name="flags" bitfield="true">
14+ <entry name="overlay_cursor" value="1"
15+ summary="composite the current cursor into the frame"/>
16+ </enum>
17+
18+ <enum name="pixel_format">
19+ <entry name="xrgb8888" value="0"
20+ summary="WL_SHM_FORMAT_XRGB8888"/>
21+ <entry name="argb8888" value="1"
22+ summary="WL_SHM_FORMAT_ARGB8888"/>
23+ </enum>
24+
25+ <enum name="failure_reason">
26+ <entry name="no_screen" value="0"
27+ summary="no captureable screen is available"/>
28+ <entry name="unsupported_buffer" value="1"
29+ summary="buffer is not a wl_shm xrgb/argb8888 buffer"/>
30+ <entry name="size_mismatch" value="2"
31+ summary="buffer dimensions are too small for the screen"/>
32+ <entry name="internal" value="3"
33+ summary="compositor capture failed"/>
34+ </enum>
35+
36+ <request name="destroy" type="destructor">
37+ <description summary="destroy the capture object"/>
38+ </request>
39+
40 <request name="capture">
41- <description summary="capture screenshot to fd">
42- captures the current screen and writes it as ppm formay
43+ <description summary="capture a frame into a client buffer">
44+ captures the current primary screen into the supplied wl_shm
45+ buffer. The compositor writes raw pixels and reports ready or
46+ failed state
47 </description>
48- <arg name="fd" type="fd" summary="file descriptor to write ppm data"/>
49+ <arg name="buffer" type="object" interface="wl_buffer"
50+ summary="client-provided wl_shm buffer to fill"/>
51+ <arg name="flags" type="uint" summary="bitmask of swc_snap.flags"/>
52 </request>
53+
54+ <event name="buffer">
55+ <description summary="current capture frame metadata">
56+ describes the current frame geometry and pixel format expected
57+ by the capture stream
58+ </description>
59+ <arg name="width" type="uint"/>
60+ <arg name="height" type="uint"/>
61+ <arg name="format" type="uint"
62+ summary="swc_snap.pixel_format value"/>
63+ </event>
64+
65+ <event name="ready">
66+ <description summary="capture complete">
67+ the requested buffer has been filled and is ready for client
68+ </description>
69+ <arg name="tv_sec" type="uint" summary="CLOCK_MONOTONIC seconds"/>
70+ <arg name="tv_nsec" type="uint" summary="CLOCK_MONOTONIC nanoseconds"/>
71+ </event>
72+
73+ <event name="failed">
74+ <description summary="capture failed">
75+ the capture request failed, the object still usable and the
76+ client may retry with another buffer or after a mode change
77+ </description>
78+ <arg name="reason" type="uint"
79+ summary="swc_snap.failure_reason value"/>
80+ </event>
81 </interface>
82 </protocol>