452 lines
12 KiB
C
452 lines
12 KiB
C
/*
|
|
* Copyright © 2008 Kristian Høgsberg
|
|
* Copyright 2022 Collabora, Ltd.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
#include <sys/param.h>
|
|
#include <sys/mman.h>
|
|
#include <pixman.h>
|
|
#include <cairo.h>
|
|
#include <assert.h>
|
|
|
|
#include <wayland-client.h>
|
|
#include "weston-output-capture-client-protocol.h"
|
|
#include "shared/os-compatibility.h"
|
|
#include "shared/xalloc.h"
|
|
#include "shared/file-util.h"
|
|
#include "pixel-formats.h"
|
|
|
|
struct screenshooter_app {
|
|
struct wl_registry *registry;
|
|
struct wl_shm *shm;
|
|
struct weston_capture_v1 *capture_factory;
|
|
|
|
struct wl_list output_list; /* struct screenshooter_output::link */
|
|
|
|
bool retry;
|
|
bool failed;
|
|
int waitcount;
|
|
};
|
|
|
|
struct screenshooter_buffer {
|
|
size_t len;
|
|
void *data;
|
|
struct wl_buffer *wl_buffer;
|
|
pixman_image_t *image;
|
|
};
|
|
|
|
struct screenshooter_output {
|
|
struct screenshooter_app *app;
|
|
struct wl_list link; /* struct screenshooter_app::output_list */
|
|
|
|
struct wl_output *wl_output;
|
|
int offset_x, offset_y;
|
|
|
|
struct weston_capture_source_v1 *source;
|
|
|
|
int buffer_width;
|
|
int buffer_height;
|
|
const struct pixel_format_info *fmt;
|
|
struct screenshooter_buffer *buffer;
|
|
};
|
|
|
|
struct buffer_size {
|
|
int width, height;
|
|
|
|
int min_x, min_y;
|
|
int max_x, max_y;
|
|
};
|
|
|
|
static struct screenshooter_buffer *
|
|
screenshot_create_shm_buffer(struct screenshooter_app *app,
|
|
size_t width, size_t height,
|
|
const struct pixel_format_info *fmt)
|
|
{
|
|
struct screenshooter_buffer *buffer;
|
|
struct wl_shm_pool *pool;
|
|
int fd;
|
|
size_t bytes_pp;
|
|
size_t stride;
|
|
|
|
assert(width > 0);
|
|
assert(height > 0);
|
|
assert(fmt && fmt->bpp > 0);
|
|
assert(fmt->pixman_format);
|
|
|
|
buffer = xzalloc(sizeof *buffer);
|
|
|
|
bytes_pp = fmt->bpp / 8;
|
|
stride = width * bytes_pp;
|
|
buffer->len = stride * height;
|
|
|
|
assert(width == stride / bytes_pp);
|
|
assert(height == buffer->len / stride);
|
|
|
|
fd = os_create_anonymous_file(buffer->len);
|
|
if (fd < 0) {
|
|
fprintf(stderr, "creating a buffer file for %zd B failed: %s\n",
|
|
buffer->len, strerror(errno));
|
|
free(buffer);
|
|
return NULL;
|
|
}
|
|
|
|
buffer->data = mmap(NULL, buffer->len, PROT_READ | PROT_WRITE,
|
|
MAP_SHARED, fd, 0);
|
|
if (buffer->data == MAP_FAILED) {
|
|
fprintf(stderr, "mmap failed: %s\n", strerror(errno));
|
|
close(fd);
|
|
free(buffer);
|
|
return NULL;
|
|
}
|
|
|
|
pool = wl_shm_create_pool(app->shm, fd, buffer->len);
|
|
close(fd);
|
|
buffer->wl_buffer =
|
|
wl_shm_pool_create_buffer(pool, 0, width, height, stride,
|
|
pixel_format_get_shm_format(fmt));
|
|
wl_shm_pool_destroy(pool);
|
|
|
|
buffer->image = pixman_image_create_bits(fmt->pixman_format,
|
|
width, height,
|
|
buffer->data, stride);
|
|
abort_oom_if_null(buffer->image);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static void
|
|
screenshooter_buffer_destroy(struct screenshooter_buffer *buffer)
|
|
{
|
|
if (!buffer)
|
|
return;
|
|
|
|
pixman_image_unref(buffer->image);
|
|
munmap(buffer->data, buffer->len);
|
|
wl_buffer_destroy(buffer->wl_buffer);
|
|
free(buffer);
|
|
}
|
|
|
|
static void
|
|
capture_source_handle_format(void *data,
|
|
struct weston_capture_source_v1 *proxy,
|
|
uint32_t drm_format)
|
|
{
|
|
struct screenshooter_output *output = data;
|
|
|
|
assert(output->source == proxy);
|
|
|
|
output->fmt = pixel_format_get_info(drm_format);
|
|
}
|
|
|
|
static void
|
|
capture_source_handle_size(void *data,
|
|
struct weston_capture_source_v1 *proxy,
|
|
int32_t width, int32_t height)
|
|
{
|
|
struct screenshooter_output *output = data;
|
|
|
|
assert(width > 0);
|
|
assert(height > 0);
|
|
|
|
output->buffer_width = width;
|
|
output->buffer_height = height;
|
|
}
|
|
|
|
static void
|
|
capture_source_handle_complete(void *data,
|
|
struct weston_capture_source_v1 *proxy)
|
|
{
|
|
struct screenshooter_output *output = data;
|
|
|
|
output->app->waitcount--;
|
|
}
|
|
|
|
static void
|
|
capture_source_handle_retry(void *data,
|
|
struct weston_capture_source_v1 *proxy)
|
|
{
|
|
struct screenshooter_output *output = data;
|
|
|
|
output->app->waitcount--;
|
|
output->app->retry = true;
|
|
}
|
|
|
|
static void
|
|
capture_source_handle_failed(void *data,
|
|
struct weston_capture_source_v1 *proxy,
|
|
const char *msg)
|
|
{
|
|
struct screenshooter_output *output = data;
|
|
|
|
output->app->waitcount--;
|
|
output->app->failed = true;
|
|
|
|
if (msg)
|
|
fprintf(stderr, "Output capture error: %s\n", msg);
|
|
}
|
|
|
|
static const struct weston_capture_source_v1_listener capture_source_handlers = {
|
|
.format = capture_source_handle_format,
|
|
.size = capture_source_handle_size,
|
|
.complete = capture_source_handle_complete,
|
|
.retry = capture_source_handle_retry,
|
|
.failed = capture_source_handle_failed,
|
|
};
|
|
|
|
static void
|
|
create_output(struct screenshooter_app *app, uint32_t output_name, uint32_t version)
|
|
{
|
|
struct screenshooter_output *output;
|
|
|
|
version = MIN(version, 4);
|
|
output = xzalloc(sizeof *output);
|
|
output->app = app;
|
|
output->wl_output = wl_registry_bind(app->registry, output_name,
|
|
&wl_output_interface, version);
|
|
abort_oom_if_null(output->wl_output);
|
|
|
|
output->source = weston_capture_v1_create(app->capture_factory,
|
|
output->wl_output,
|
|
WESTON_CAPTURE_V1_SOURCE_FRAMEBUFFER);
|
|
abort_oom_if_null(output->source);
|
|
weston_capture_source_v1_add_listener(output->source,
|
|
&capture_source_handlers, output);
|
|
|
|
wl_list_insert(&app->output_list, &output->link);
|
|
}
|
|
|
|
static void
|
|
destroy_output(struct screenshooter_output *output)
|
|
{
|
|
weston_capture_source_v1_destroy(output->source);
|
|
|
|
if (wl_output_get_version(output->wl_output) >= WL_OUTPUT_RELEASE_SINCE_VERSION)
|
|
wl_output_release(output->wl_output);
|
|
else
|
|
wl_output_destroy(output->wl_output);
|
|
|
|
screenshooter_buffer_destroy(output->buffer);
|
|
wl_list_remove(&output->link);
|
|
free(output);
|
|
}
|
|
|
|
static void
|
|
handle_global(void *data, struct wl_registry *registry,
|
|
uint32_t name, const char *interface, uint32_t version)
|
|
{
|
|
struct screenshooter_app *app = data;
|
|
|
|
if (strcmp(interface, wl_output_interface.name) == 0) {
|
|
create_output(app, name, version);
|
|
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
|
app->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
|
|
/*
|
|
* Not listening for format advertisements,
|
|
* weston_capture_source_v1.format event tells us what to use.
|
|
*/
|
|
} else if (strcmp(interface, weston_capture_v1_interface.name) == 0) {
|
|
app->capture_factory = wl_registry_bind(registry, name,
|
|
&weston_capture_v1_interface,
|
|
1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
|
|
{
|
|
/* Dynamic output removals will just fail the respective shot. */
|
|
}
|
|
|
|
static const struct wl_registry_listener registry_listener = {
|
|
handle_global,
|
|
handle_global_remove
|
|
};
|
|
|
|
static void
|
|
screenshooter_output_capture(struct screenshooter_output *output)
|
|
{
|
|
screenshooter_buffer_destroy(output->buffer);
|
|
output->buffer = screenshot_create_shm_buffer(output->app,
|
|
output->buffer_width,
|
|
output->buffer_height,
|
|
output->fmt);
|
|
abort_oom_if_null(output->buffer);
|
|
|
|
weston_capture_source_v1_capture(output->source,
|
|
output->buffer->wl_buffer);
|
|
output->app->waitcount++;
|
|
}
|
|
|
|
static void
|
|
screenshot_write_png(const struct buffer_size *buff_size,
|
|
struct wl_list *output_list)
|
|
{
|
|
pixman_image_t *shot;
|
|
cairo_surface_t *surface;
|
|
struct screenshooter_output *output;
|
|
FILE *fp;
|
|
char filepath[PATH_MAX];
|
|
|
|
shot = pixman_image_create_bits(PIXMAN_a8r8g8b8,
|
|
buff_size->width, buff_size->height,
|
|
NULL, 0);
|
|
abort_oom_if_null(shot);
|
|
|
|
wl_list_for_each(output, output_list, link) {
|
|
pixman_image_composite32(PIXMAN_OP_SRC,
|
|
output->buffer->image, /* src */
|
|
NULL, /* mask */
|
|
shot, /* dest */
|
|
0, 0, /* src x,y */
|
|
0, 0, /* mask x,y */
|
|
output->offset_x, output->offset_y, /* dst x,y */
|
|
output->buffer_width, output->buffer_height);
|
|
}
|
|
|
|
surface = cairo_image_surface_create_for_data((void *)pixman_image_get_data(shot),
|
|
CAIRO_FORMAT_ARGB32,
|
|
pixman_image_get_width(shot),
|
|
pixman_image_get_height(shot),
|
|
pixman_image_get_stride(shot));
|
|
|
|
fp = file_create_dated(getenv("XDG_PICTURES_DIR"), "wayland-screenshot-",
|
|
".png", filepath, sizeof(filepath));
|
|
if (fp) {
|
|
fclose (fp);
|
|
cairo_surface_write_to_png(surface, filepath);
|
|
}
|
|
cairo_surface_destroy(surface);
|
|
pixman_image_unref(shot);
|
|
}
|
|
|
|
static int
|
|
screenshot_set_buffer_size(struct buffer_size *buff_size,
|
|
struct wl_list *output_list)
|
|
{
|
|
struct screenshooter_output *output;
|
|
buff_size->min_x = buff_size->min_y = INT_MAX;
|
|
buff_size->max_x = buff_size->max_y = INT_MIN;
|
|
int position = 0;
|
|
|
|
wl_list_for_each_reverse(output, output_list, link) {
|
|
output->offset_x = position;
|
|
position += output->buffer_width;
|
|
}
|
|
|
|
wl_list_for_each(output, output_list, link) {
|
|
buff_size->min_x = MIN(buff_size->min_x, output->offset_x);
|
|
buff_size->min_y = MIN(buff_size->min_y, output->offset_y);
|
|
buff_size->max_x =
|
|
MAX(buff_size->max_x, output->offset_x + output->buffer_width);
|
|
buff_size->max_y =
|
|
MAX(buff_size->max_y, output->offset_y + output->buffer_height);
|
|
}
|
|
|
|
if (buff_size->max_x <= buff_size->min_x ||
|
|
buff_size->max_y <= buff_size->min_y)
|
|
return -1;
|
|
|
|
buff_size->width = buff_size->max_x - buff_size->min_x;
|
|
buff_size->height = buff_size->max_y - buff_size->min_y;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
struct wl_display *display;
|
|
struct screenshooter_output *output;
|
|
struct screenshooter_output *tmp_output;
|
|
struct buffer_size buff_size = {};
|
|
struct screenshooter_app app = {};
|
|
|
|
wl_list_init(&app.output_list);
|
|
|
|
display = wl_display_connect(NULL);
|
|
if (display == NULL) {
|
|
fprintf(stderr, "failed to create display: %s\n",
|
|
strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
app.registry = wl_display_get_registry(display);
|
|
wl_registry_add_listener(app.registry, ®istry_listener, &app);
|
|
|
|
/* Process wl_registry advertisements */
|
|
wl_display_roundtrip(display);
|
|
|
|
if (!app.shm) {
|
|
fprintf(stderr, "Error: display does not support wl_shm\n");
|
|
return -1;
|
|
}
|
|
if (!app.capture_factory) {
|
|
fprintf(stderr, "Error: display does not support weston_capture_v1\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Process initial events for wl_output and weston_capture_source_v1 */
|
|
wl_display_roundtrip(display);
|
|
|
|
do {
|
|
app.retry = false;
|
|
|
|
wl_list_for_each(output, &app.output_list, link)
|
|
screenshooter_output_capture(output);
|
|
|
|
while (app.waitcount > 0 && !app.failed) {
|
|
if (wl_display_dispatch(display) < 0)
|
|
app.failed = true;
|
|
assert(app.waitcount >= 0);
|
|
}
|
|
} while (app.retry && !app.failed);
|
|
|
|
if (!app.failed) {
|
|
if (screenshot_set_buffer_size(&buff_size, &app.output_list) < 0)
|
|
return -1;
|
|
screenshot_write_png(&buff_size, &app.output_list);
|
|
} else {
|
|
fprintf(stderr, "Error: screenshot or protocol failure\n");
|
|
}
|
|
|
|
wl_list_for_each_safe(output, tmp_output, &app.output_list, link)
|
|
destroy_output(output);
|
|
|
|
weston_capture_v1_destroy(app.capture_factory);
|
|
wl_shm_destroy(app.shm);
|
|
wl_registry_destroy(app.registry);
|
|
wl_display_disconnect(display);
|
|
|
|
return 0;
|
|
}
|