clients/screenshot: Add support for weston output capture
[src/agl-compositor.git] / clients / screenshot.c
1 /*
2  * Copyright 2024 Collabora, Ltd.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  */
23
24 #include "config.h"
25
26 #include <stdint.h>
27 #include <stdbool.h>
28 #include <errno.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <fcntl.h>
33 #include <unistd.h>
34 #include <limits.h>
35 #include <sys/param.h>
36 #include <sys/mman.h>
37 #include <pixman.h>
38 #include <cairo.h>
39 #include <getopt.h>
40 #include <assert.h>
41
42 #include <wayland-client.h>
43 #include "weston-output-capture-client-protocol.h"
44 #include "shared/os-compatibility.h"
45 #include "shared/xalloc.h"
46 #include "shared/file-util.h"
47 #include "shared/pixel-formats.h"
48 #include "shared/helpers.h"
49 #include "shared/string-helpers.h"
50
51 static int opts = 0x0;
52
53 #define OPT_SCREENSHOT_OUTPUT           1
54 #define OPT_SHOW_ALL_OUTPUTS            2
55 #define OPT_SCREENSHOT_ALL_OUTPUTS      3
56
57 struct screenshooter_app {
58         struct wl_display *display;
59         struct wl_registry *registry;
60         struct wl_shm *shm;
61         struct weston_capture_v1 *capture_factory;
62
63         struct wl_list output_list; /* struct screenshooter_output::link */
64
65         bool retry;
66         bool failed;
67         int waitcount;
68 };
69
70 struct screenshooter_buffer {
71         size_t len;
72         void *data;
73         struct wl_buffer *wl_buffer;
74         pixman_image_t *image;
75 };
76
77 struct screenshooter_output {
78         struct screenshooter_app *app;
79         struct wl_list link; /* struct screenshooter_app::output_list */
80
81         struct wl_output *wl_output;
82         int offset_x, offset_y;
83         char *name;
84
85         struct weston_capture_source_v1 *source;
86
87         int buffer_width;
88         int buffer_height;
89         const struct pixel_format_info *fmt;
90         struct screenshooter_buffer *buffer;
91 };
92
93 struct buffer_size {
94         int width, height;
95
96         int min_x, min_y;
97         int max_x, max_y;
98 };
99
100 static struct screenshooter_buffer *
101 screenshot_create_shm_buffer(struct screenshooter_app *app,
102                              size_t width, size_t height,
103                              const struct pixel_format_info *fmt)
104 {
105         struct screenshooter_buffer *buffer;
106         struct wl_shm_pool *pool;
107         int fd;
108         size_t bytes_pp;
109         size_t stride;
110
111         assert(width > 0);
112         assert(height > 0);
113         assert(fmt && fmt->bpp > 0);
114         assert(fmt->pixman_format);
115
116         buffer = xzalloc(sizeof *buffer);
117
118         bytes_pp = fmt->bpp / 8;
119         stride = width * bytes_pp;
120         buffer->len = stride * height;
121
122         assert(width == stride / bytes_pp);
123         assert(height == buffer->len / stride);
124
125         fd = os_create_anonymous_file(buffer->len);
126         if (fd < 0) {
127                 fprintf(stderr, "creating a buffer file for %zd B failed: %s\n",
128                         buffer->len, strerror(errno));
129                 free(buffer);
130                 return NULL;
131         }
132
133         buffer->data = mmap(NULL, buffer->len, PROT_READ | PROT_WRITE,
134                             MAP_SHARED, fd, 0);
135         if (buffer->data == MAP_FAILED) {
136                 fprintf(stderr, "mmap failed: %s\n", strerror(errno));
137                 close(fd);
138                 free(buffer);
139                 return NULL;
140         }
141
142         pool = wl_shm_create_pool(app->shm, fd, buffer->len);
143         close(fd);
144         buffer->wl_buffer =
145                 wl_shm_pool_create_buffer(pool, 0, width, height, stride,
146                                           pixel_format_get_shm_format(fmt));
147         wl_shm_pool_destroy(pool);
148
149         buffer->image = pixman_image_create_bits(fmt->pixman_format,
150                                                  width, height,
151                                                  buffer->data, stride);
152         if (!buffer->image) {
153                 fprintf(stderr, "Failed to create buffer image!\n");
154                 close(fd);
155                 free(buffer);
156                 return NULL;
157         }
158
159         return buffer;
160 }
161
162 static void
163 screenshooter_buffer_destroy(struct screenshooter_buffer *buffer)
164 {
165         if (!buffer)
166                 return;
167
168         pixman_image_unref(buffer->image);
169         munmap(buffer->data, buffer->len);
170         wl_buffer_destroy(buffer->wl_buffer);
171         free(buffer);
172 }
173
174 static void
175 capture_source_handle_format(void *data,
176                              struct weston_capture_source_v1 *proxy,
177                              uint32_t drm_format)
178 {
179         struct screenshooter_output *output = data;
180
181         assert(output->source == proxy);
182
183         output->fmt = pixel_format_get_info(drm_format);
184 }
185
186 static void
187 capture_source_handle_size(void *data,
188                            struct weston_capture_source_v1 *proxy,
189                            int32_t width, int32_t height)
190 {
191         struct screenshooter_output *output = data;
192
193         assert(width > 0);
194         assert(height > 0);
195
196         output->buffer_width = width;
197         output->buffer_height = height;
198 }
199
200 static void
201 capture_source_handle_complete(void *data,
202                                struct weston_capture_source_v1 *proxy)
203 {
204         struct screenshooter_output *output = data;
205
206         output->app->waitcount--;
207 }
208
209 static void
210 capture_source_handle_retry(void *data,
211                             struct weston_capture_source_v1 *proxy)
212 {
213         struct screenshooter_output *output = data;
214
215         output->app->waitcount--;
216         output->app->retry = true;
217 }
218
219 static void
220 capture_source_handle_failed(void *data,
221                              struct weston_capture_source_v1 *proxy,
222                              const char *msg)
223 {
224         struct screenshooter_output *output = data;
225
226         output->app->waitcount--;
227         output->app->failed = true;
228
229         if (msg)
230                 fprintf(stderr, "Output capture error: %s\n", msg);
231 }
232
233 static const struct weston_capture_source_v1_listener capture_source_handlers = {
234         .format = capture_source_handle_format,
235         .size = capture_source_handle_size,
236         .complete = capture_source_handle_complete,
237         .retry = capture_source_handle_retry,
238         .failed = capture_source_handle_failed,
239 };
240
241 static void
242 display_handle_geometry(void *data,
243                 struct wl_output *wl_output,
244                 int x, int y,
245                 int physical_width,
246                 int physical_height,
247                 int subpixel,
248                 const char *make,
249                 const char *model,
250                 int32_t transform)
251 {
252 }
253
254 static void
255 display_handle_mode(void *data,
256                 struct wl_output *wl_output,
257                 uint32_t flags,
258                 int width,
259                 int height,
260                 int refresh)
261 {
262 }
263
264 static void
265 display_handle_scale(void *data,
266                 struct wl_output *wl_output,
267                 int scale)
268 {
269 }
270
271 static void
272 display_handle_name(void *data, struct wl_output *wl_output, const char *name)
273 {
274         struct screenshooter_output *output = data;
275         output->name = strdup(name);
276 }
277
278 static void
279 display_handle_description(void *data, struct wl_output *wl_output, const char *desc)
280 {
281 }
282
283 static void
284 display_handle_done(void *data,
285                 struct wl_output *wl_output)
286 {
287 }
288
289
290 static const struct wl_output_listener output_listener = {
291         display_handle_geometry,
292         display_handle_mode,
293         display_handle_done,
294         display_handle_scale,
295         display_handle_name,
296         display_handle_description,
297 };
298
299 static void
300 create_output(struct screenshooter_app *app, uint32_t output_name, uint32_t version)
301 {
302         struct screenshooter_output *output;
303
304         version = MIN(version, 4);
305         output = xzalloc(sizeof *output);
306         output->app = app;
307         output->wl_output = wl_registry_bind(app->registry, output_name,
308                                              &wl_output_interface, version);
309         if (!output->wl_output) {
310                 fprintf(stderr, "Failed to get bind output!\n");
311                 exit(EXIT_FAILURE);
312         }
313
314         wl_output_add_listener(output->wl_output, &output_listener, output);
315
316         output->source = weston_capture_v1_create(app->capture_factory,
317                                                   output->wl_output,
318                                                   WESTON_CAPTURE_V1_SOURCE_FRAMEBUFFER);
319         if (!output->source) {
320                 fprintf(stderr, "Failed to get a capture source!\n");
321                 exit(EXIT_FAILURE);
322         }
323         weston_capture_source_v1_add_listener(output->source,
324                                               &capture_source_handlers, output);
325
326         wl_list_insert(&app->output_list, &output->link);
327 }
328
329 static void
330 destroy_output(struct screenshooter_output *output)
331 {
332         weston_capture_source_v1_destroy(output->source);
333
334         if (wl_output_get_version(output->wl_output) >= WL_OUTPUT_RELEASE_SINCE_VERSION)
335                 wl_output_release(output->wl_output);
336         else
337                 wl_output_destroy(output->wl_output);
338
339         screenshooter_buffer_destroy(output->buffer);
340         wl_list_remove(&output->link);
341         free(output->name);
342         free(output);
343 }
344
345 static void
346 handle_global(void *data, struct wl_registry *registry,
347               uint32_t name, const char *interface, uint32_t version)
348 {
349         struct screenshooter_app *app = data;
350
351         if (strcmp(interface, wl_output_interface.name) == 0) {
352                 create_output(app, name, version);
353         } else if (strcmp(interface, wl_shm_interface.name) == 0) {
354                 app->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
355                 /*
356                  * Not listening for format advertisements,
357                  * weston_capture_source_v1.format event tells us what to use.
358                  */
359         } else if (strcmp(interface, weston_capture_v1_interface.name) == 0) {
360                 app->capture_factory = wl_registry_bind(registry, name,
361                                                         &weston_capture_v1_interface,
362                                                         1);
363         }
364 }
365
366 static void
367 handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
368 {
369         /* Dynamic output removals will just fail the respective shot. */
370 }
371
372 static const struct wl_registry_listener registry_listener = {
373         handle_global,
374         handle_global_remove
375 };
376
377 static void
378 screenshooter_output_capture(struct screenshooter_output *output)
379 {
380         screenshooter_buffer_destroy(output->buffer);
381         output->buffer = screenshot_create_shm_buffer(output->app,
382                                                       output->buffer_width,
383                                                       output->buffer_height,
384                                                       output->fmt);
385         if (!output->buffer) {
386                 fprintf(stderr, "Failed to create output buffer\n");
387                 exit(EXIT_FAILURE);
388         }
389
390         weston_capture_source_v1_capture(output->source,
391                                          output->buffer->wl_buffer);
392         output->app->waitcount++;
393 }
394
395 static void
396 screenshot_write_png_per_output(const struct buffer_size *buff_size,
397                                 struct screenshooter_output *output,
398                                 const char *fn)
399 {
400         pixman_image_t *shot;
401         cairo_surface_t *surface;
402         FILE *fp;
403         char filepath[PATH_MAX];
404         char *filename_to_write;
405
406         shot = pixman_image_create_bits(PIXMAN_a8r8g8b8,
407                                         buff_size->width, buff_size->height,
408                                         NULL, 0);
409         if (!shot) {
410                 fprintf(stderr, "Failed to create shot\n");
411                 exit(EXIT_FAILURE);
412         }
413
414
415         pixman_image_composite32(PIXMAN_OP_SRC,
416                                  output->buffer->image, /* src */
417                                  NULL, /* mask */
418                                  shot, /* dest */
419                                  0, 0, /* src x,y */
420                                  0, 0, /* mask x,y */
421                                  output->offset_x, output->offset_y, /* dst x,y */
422                                  output->buffer_width, output->buffer_height);
423
424         surface = cairo_image_surface_create_for_data((void *)pixman_image_get_data(shot),
425                                                       CAIRO_FORMAT_ARGB32,
426                                                       pixman_image_get_width(shot),
427                                                       pixman_image_get_height(shot),
428                                                       pixman_image_get_stride(shot));
429         if (fn)
430                 str_printf(&filename_to_write, "agl-screenshot-%s-", fn);
431         else
432                 str_printf(&filename_to_write, "agl-screenshot-");
433
434         fp = file_create_dated(getenv("XDG_PICTURES_DIR"), filename_to_write,
435                                ".png", filepath, sizeof(filepath));
436         if (fp) {
437                 fclose(fp);
438                 cairo_surface_write_to_png(surface, filepath);
439         }
440         cairo_surface_destroy(surface);
441         pixman_image_unref(shot);
442 }
443
444 static void
445 screenshot_set_buffer_size_per_output(struct buffer_size *buff_size,
446                                       struct screenshooter_output *output)
447 {
448         buff_size->min_x = MIN(buff_size->min_x, output->offset_x);
449         buff_size->min_y = MIN(buff_size->min_y, output->offset_y);
450         buff_size->max_x = MAX(buff_size->max_x, output->offset_x + output->buffer_width);
451         buff_size->max_y = MAX(buff_size->max_y, output->offset_y + output->buffer_height);
452
453         buff_size->width = buff_size->max_x - buff_size->min_x;
454         buff_size->height = buff_size->max_y - buff_size->min_y;
455 }
456
457 static void
458 screenshot_compute_output_offset(int *pos, struct screenshooter_output *sh_output)
459 {
460         sh_output->offset_x = *pos;
461         *pos += sh_output->buffer_width;
462 }
463
464 static struct screenshooter_output *
465 agl_shooter_search_for_output(const char *output_name,
466                               struct screenshooter_app *app)
467 {
468         struct screenshooter_output *found_output = NULL;
469         struct screenshooter_output *output;
470
471         if (!output_name)
472                 return found_output;
473
474         wl_list_for_each(output, &app->output_list, link) {
475                 if (output->name && strcmp(output->name, output_name) == 0) {
476                         found_output = output;
477                         break;
478                 }
479         }
480
481         return found_output;
482 }
483
484 static char *
485 agl_shooter_search_get_output_name(struct screenshooter_output *sh_output)
486 {
487         struct screenshooter_app *app;
488         struct screenshooter_output *output;
489
490         if (!sh_output)
491                 return NULL;
492
493         app = sh_output->app;
494
495         wl_list_for_each(output, &app->output_list, link) {
496                 if (output == sh_output) {
497                         return output->name;
498                 }
499         }
500
501         return NULL;
502 }
503
504 static void
505 agl_shooter_display_all_outputs(struct screenshooter_app *app)
506 {
507         struct screenshooter_output *output;
508
509         wl_list_for_each(output, &app->output_list, link) {
510                 fprintf(stdout, "Output '%s'\n", output->name);
511         }
512 }
513
514 static void
515 agl_shooter_screenshot_output(struct screenshooter_output *output, int *pos)
516 {
517         struct buffer_size buff_size = {};
518         struct screenshooter_app *app = output->app;
519         char *output_name;
520
521         do {
522                 app->retry = false;
523                 screenshooter_output_capture(output);
524
525                 while (app->waitcount > 0 && !app->failed) {
526                         if (wl_display_dispatch(app->display) < 0)
527                                 app->failed = true;
528                         assert(app->waitcount >= 0);
529                 }
530         } while (app->retry && !app->failed);
531
532         if (!app->failed) {
533                 screenshot_compute_output_offset(pos, output);
534                 screenshot_set_buffer_size_per_output(&buff_size, output);
535
536                 output_name = agl_shooter_search_get_output_name(output);
537                 assert(output_name);
538                 screenshot_write_png_per_output(&buff_size, output, output_name);
539         } else {
540                 fprintf(stderr, "Error: screenshot or protocol failure\n");
541         }
542 }
543
544
545 static void
546 agl_shooter_screenshot_all_outputs(struct screenshooter_app *app)
547 {
548         struct screenshooter_output *output;
549         int pos = 0;
550
551         wl_list_for_each(output, &app->output_list, link)
552                 agl_shooter_screenshot_output(output, &pos);
553 }
554
555 static void
556 print_usage_and_exit(void)
557 {
558         fprintf(stderr, "./agl-screenshooter [-o OUTPUT_NAME] [-l] [-a]\n");
559
560         fprintf(stderr, "\t-o OUTPUT_NAME -- take a screenshot of the output "
561                                 "specified by OUTPUT_NAME\n");
562         fprintf(stderr, "\t-a  -- take a screenshot of all the outputs found\n");
563         fprintf(stderr, "\t-l  -- list all the outputs found\n");
564         exit(EXIT_FAILURE);
565 }
566
567 int
568 main(int argc, char *argv[])
569 {
570         struct wl_display *display;
571         struct screenshooter_output *output;
572         struct screenshooter_output *sh_output = NULL;
573         struct screenshooter_output *tmp_output;
574         struct screenshooter_app app = {};
575
576         int c, option_index;
577         char *output_name = NULL;
578         int pos = 0;
579
580         wl_list_init(&app.output_list);
581
582         static struct option long_options[] = {
583                 {"output",      required_argument, 0,  'o' },
584                 {"list",        required_argument, 0,  'l' },
585                 {"all",         required_argument, 0,  'a' },
586                 {"help",        no_argument      , 0,  'h' },
587                 {0, 0, 0, 0}
588         };
589
590         while ((c = getopt_long(argc, argv, "o:lah",
591                                 long_options, &option_index)) != -1) {
592                 switch (c) {
593                 case 'o':
594                         output_name = optarg;
595                         opts |= (1 << OPT_SCREENSHOT_OUTPUT);
596                         break;
597                 case 'l':
598                         opts |= (1 << OPT_SHOW_ALL_OUTPUTS);
599                         break;
600                 case 'a':
601                         opts |= (1 << OPT_SCREENSHOT_ALL_OUTPUTS);
602                         break;
603                 default:
604                         print_usage_and_exit();
605                 }
606         }
607
608         display = wl_display_connect(NULL);
609         if (display == NULL) {
610                 fprintf(stderr, "failed to create display: %s\n",
611                         strerror(errno));
612                 return -1;
613         }
614
615         app.display = display;
616         app.registry = wl_display_get_registry(display);
617         wl_registry_add_listener(app.registry, &registry_listener, &app);
618
619         /* Process wl_registry advertisements */
620         wl_display_roundtrip(display);
621
622         if (!app.shm) {
623                 fprintf(stderr, "Error: display does not support wl_shm\n");
624                 return -1;
625         }
626         if (!app.capture_factory) {
627                 fprintf(stderr, "Error: display does not support weston_capture_v1\n");
628                 return -1;
629         }
630
631         /* Process initial events for wl_output and weston_capture_source_v1 */
632         wl_display_roundtrip(display);
633
634         if (opts & (1 << OPT_SHOW_ALL_OUTPUTS)) {
635                 agl_shooter_display_all_outputs(&app);
636                 return EXIT_SUCCESS;
637         }
638
639         if (opts & (1 << OPT_SCREENSHOT_ALL_OUTPUTS)) {
640                 agl_shooter_screenshot_all_outputs(&app);
641                 return EXIT_SUCCESS;
642         }
643
644         sh_output = NULL;
645         if (output_name)
646                 sh_output = agl_shooter_search_for_output(output_name, &app);
647
648         if (!sh_output && (opts & (1 << OPT_SCREENSHOT_OUTPUT))) {
649                 fprintf(stderr, "Could not find an output matching '%s'\n",
650                                 output_name);
651                 return EXIT_FAILURE;
652         }
653
654         /* if we're still here just pick the first one available
655          * and use that. Still useful in case we are run without
656          * any args whatsoever */
657         if (!sh_output)
658                 sh_output = container_of(app.output_list.next,
659                                          struct screenshooter_output, link);
660
661         /* take a screenshot only of that specific output */
662         agl_shooter_screenshot_output(sh_output, &pos);
663
664         wl_list_for_each_safe(output, tmp_output, &app.output_list, link)
665                 destroy_output(output);
666
667         weston_capture_v1_destroy(app.capture_factory);
668         wl_shm_destroy(app.shm);
669         wl_registry_destroy(app.registry);
670         wl_display_disconnect(display);
671
672         return 0;
673 }