From 312fe5f4453c3637ed8d8fd35609dc1e64aec19e Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 9 Feb 2015 11:23:48 +0200 Subject: [PATCH] surface-screenshot: a new manual test module Add a new Weston plugin under tests/ for manual testing of the surface-shooting API. The debug key binding 'h' triggers a surface shot from the surface that currently has the pointer focus. The shot is written in PAM format into a file. PAM format was chosen because it is dead-simple to write from scratch and can carry an RGBA format. Changes in v2: - check fprintf calls, fix a malloc without free - remove stride and format arguments from the API Signed-off-by: Pekka Paalanen v1 Tested-by: Nobuhiko Tanibata Reviewed-by: Daniel Stone --- Makefile.am | 17 +++ tests/surface-screenshot.c | 218 +++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 tests/surface-screenshot.c diff --git a/Makefile.am b/Makefile.am index 4e219e14..c509f6ec 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1104,6 +1104,23 @@ EXTRA_DIST += \ protocol/ivi-application.xml \ protocol/ivi-hmi-controller.xml +# +# manual test modules in tests subdirectory +# + +noinst_LTLIBRARIES += \ + surface-screenshot.la + +surface_screenshot_la_LIBADD = $(COMPOSITOR_LIBS) libshared.la +surface_screenshot_la_LDFLAGS = $(test_module_ldflags) +surface_screenshot_la_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) +surface_screenshot_la_SOURCES = tests/surface-screenshot.c + + +# +# Documentation +# + man_MANS = weston.1 weston.ini.5 if ENABLE_DRM_COMPOSITOR diff --git a/tests/surface-screenshot.c b/tests/surface-screenshot.c new file mode 100644 index 00000000..57ec9258 --- /dev/null +++ b/tests/surface-screenshot.c @@ -0,0 +1,218 @@ +/* + * Copyright © 2015 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "compositor.h" +#include "file-util.h" + +static char * +encode_PAM_comment_line(const char *comment) +{ + size_t len = strlen(comment); + char *str = malloc(len + 2); + char *dst = str; + const char *src = comment; + const char *end = src + len; + + *dst++ = '#'; + *dst++ = ' '; + for (; src < end; src++, dst++) { + if (*src == '\n' || !isprint(*src)) + *dst = '_'; + else + *dst = *src; + } + + return str; +} + +/* + * PAM image format: + * http://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format + * RGBA is in byte address order. + * + * ImageMagick 'convert' can convert a PAM image to a more common format. + * To view the image metadata: $ head -n7 image.pam + */ +static int +write_PAM_image_rgba(FILE *fp, int width, int height, + void *pixels, size_t size, const char *comment) +{ + char *str; + int ret; + + assert(size == (size_t)4 * width * height); + + ret = fprintf(fp, "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\n" + "TUPLTYPE RGB_ALPHA\n", width, height); + if (ret < 0) + return -1; + + if (comment) { + str = encode_PAM_comment_line(comment); + ret = fprintf(fp, "%s\n", str); + free(str); + + if (ret < 0) + return -1; + } + + ret = fprintf(fp, "ENDHDR\n"); + if (ret < 0) + return -1; + + if (fwrite(pixels, 1, size, fp) != size) + return -1; + + if (ferror(fp)) + return -1; + + return 0; +} + +static uint32_t +unmult(uint32_t c, uint32_t a) +{ + return (c * 255 + a / 2) / a; +} + +static void +unpremultiply_and_swap_a8b8g8r8_to_PAMrgba(void *pixels, size_t size) +{ + uint32_t *p = pixels; + uint32_t *end; + + for (end = p + size / 4; p < end; p++) { + uint32_t v = *p; + uint32_t a; + + a = (v & 0xff000000) >> 24; + if (a == 0) { + *p = 0; + } else { + uint8_t *dst = (uint8_t *)p; + + dst[0] = unmult((v & 0x000000ff) >> 0, a); + dst[1] = unmult((v & 0x0000ff00) >> 8, a); + dst[2] = unmult((v & 0x00ff0000) >> 16, a); + dst[3] = a; + } + } +} + +static void +trigger_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + const char *prefix = "surfaceshot-"; + const char *suffix = ".pam"; + char fname[1024]; + struct weston_surface *surface; + int width, height; + char desc[512]; + void *pixels; + const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ + size_t sz; + int ret; + FILE *fp; + + if (seat->pointer_device_count == 0 || + !seat->pointer || + !seat->pointer->focus) + return; + + surface = seat->pointer->focus->surface; + + weston_surface_get_content_size(surface, &width, &height); + + if (!surface->get_label || + surface->get_label(surface, desc, sizeof(desc)) < 0) + snprintf(desc, sizeof(desc), "(unknown)"); + + weston_log("surface screenshot of %p: '%s', %dx%d\n", + surface, desc, width, height); + + sz = width * bytespp * height; + if (sz == 0) { + weston_log("no content for %p\n", surface); + return; + } + + pixels = malloc(sz); + if (!pixels) { + weston_log("%s: failed to malloc %zu B\n", __func__, sz); + return; + } + + ret = weston_surface_copy_content(surface, pixels, sz, + 0, 0, width, height); + if (ret < 0) { + weston_log("shooting surface %p failed\n", surface); + goto out; + } + + unpremultiply_and_swap_a8b8g8r8_to_PAMrgba(pixels, sz); + + fp = file_create_dated(prefix, suffix, fname, sizeof(fname)); + if (!fp) { + const char *msg; + + switch (errno) { + case ETIME: + msg = "failure in datetime formatting"; + break; + default: + msg = strerror(errno); + } + + weston_log("Cannot open '%s*%s' for writing: %s\n", + prefix, suffix, msg); + goto out; + } + + ret = write_PAM_image_rgba(fp, width, height, pixels, sz, desc); + if (fclose(fp) != 0 || ret < 0) + weston_log("writing surface %p screenshot failed.\n", surface); + else + weston_log("successfully shot surface %p into '%s'\n", + surface, fname); + +out: + free(pixels); +} + +WL_EXPORT int +module_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + weston_compositor_add_debug_binding(ec, KEY_H, trigger_binding, ec); + + return 0; +}