summaryrefslogtreecommitdiff
path: root/util/cairo-sphinx
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2009-08-18 10:55:54 +0100
committerChris Wilson <chris@chris-wilson.co.uk>2009-08-29 17:07:34 +0100
commit6e0b3be903a6c3ad948de95f490cff92430429a6 (patch)
treea4eddf60c33692885fb140eb93839569fcc990fb /util/cairo-sphinx
parentc980affce05590f5f52273ba340463f00773c776 (diff)
Add cairo-sphinx utility for regression analysis
sphinx is an alternate version of the current cairo-test-trace. It's purpose is to replay a live application (which may just be a replay of a trace) against a backend and its reference. The improvement over the original cairo-test-trace is that the reference backend may be from an older version of cairo.
Diffstat (limited to 'util/cairo-sphinx')
-rw-r--r--util/cairo-sphinx/.gitignore1
-rw-r--r--util/cairo-sphinx/Makefile.am40
-rw-r--r--util/cairo-sphinx/fdr.c260
-rw-r--r--util/cairo-sphinx/sphinx.c1525
4 files changed, 1826 insertions, 0 deletions
diff --git a/util/cairo-sphinx/.gitignore b/util/cairo-sphinx/.gitignore
new file mode 100644
index 00000000..56ecd5de
--- /dev/null
+++ b/util/cairo-sphinx/.gitignore
@@ -0,0 +1 @@
+cairo-sphinx
diff --git a/util/cairo-sphinx/Makefile.am b/util/cairo-sphinx/Makefile.am
new file mode 100644
index 00000000..845888c7
--- /dev/null
+++ b/util/cairo-sphinx/Makefile.am
@@ -0,0 +1,40 @@
+cairolibdir = $(libdir)/cairo
+
+cairolib_LTLIBRARIES = cairo-sphinx.la
+bin_PROGRAMS = cairo-sphinx
+
+AM_CPPFLAGS = -I$(top_srcdir)/src \
+ -I$(top_builddir)/src \
+ -I$(top_srcdir)/boilerplate \
+ -I$(top_srcdir)/util/cairo-script
+
+cairo_sphinx_la_SOURCES = fdr.c
+cairo_sphinx_la_CPPFLAGS = $(AM_CPPFLAGS)
+cairo_sphinx_la_CFLAGS = $(CAIRO_CFLAGS)
+cairo_sphinx_la_LDFLAGS = -module -no-undefined
+cairo_sphinx_la_LIBADD = -ldl
+
+cairo_sphinx_SOURCES = sphinx.c
+cairo_sphinx_CPPFLAGS = $(AM_CPPFLAGS) -DLIBDIR="\"$(cairolibdir)\""
+cairo_sphinx_CFLAGS = $(CAIRO_CFLAGS) $(glib_CFLAGS)
+cairo_sphinx_LDADD = \
+ $(top_builddir)/util/cairo-script/libcairo-script-interpreter.la \
+ $(top_builddir)/boilerplate/libcairoboilerplate.la \
+ $(top_builddir)/src/libcairo.la \
+ $(glib_LIBS) \
+ $(CAIRO_LDADD) \
+ -lrt
+cairo_sphinx_DEPENDENCIES = \
+ $(top_builddir)/util/cairo-script/libcairo-script-interpreter.la \
+ $(top_builddir)/boilerplate/libcairoboilerplate.la \
+ $(top_builddir)/src/libcairo.la
+
+# Install rules to rebuild the libraries and add explicit dependencies
+$(top_builddir)/boilerplate/libcairoboilerplate.la: $(top_builddir)/src/libcairo.la
+ cd $(top_builddir)/boilerplate && $(MAKE) $(AM_MAKEFLAGS) libcairoboilerplate.la
+
+$(top_builddir)/src/libcairo.la:
+ cd $(top_builddir)/src && $(MAKE) $(AM_MAKEFLAGS) libcairo.la
+
+$(top_builddir)/util/cairo-script/libcairo-script-interpreter.la: $(top_builddir)/src/libcairo.la
+ cd $(top_builddir)/util/cairo-script && $(MAKE) $(AM_MAKEFLAGS) libcairo-script-interpreter.la
diff --git a/util/cairo-sphinx/fdr.c b/util/cairo-sphinx/fdr.c
new file mode 100644
index 00000000..993baba3
--- /dev/null
+++ b/util/cairo-sphinx/fdr.c
@@ -0,0 +1,260 @@
+/* cairo-fdr - a 'flight data recorder', a black box, for cairo
+ *
+ * Copyright © 2009 Chris Wilson
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <cairo.h>
+#include <cairo-script.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <dlfcn.h>
+
+static void *_dlhandle = RTLD_NEXT;
+#define DLCALL(name, args...) ({ \
+ static typeof (&name) name##_real; \
+ if (name##_real == NULL) { \
+ name##_real = dlsym (_dlhandle, #name); \
+ if (name##_real == NULL && _dlhandle == RTLD_NEXT) { \
+ _dlhandle = dlopen ("libcairo.so", RTLD_LAZY); \
+ name##_real = dlsym (_dlhandle, #name); \
+ assert (name##_real != NULL); \
+ } \
+ } \
+ (*name##_real) (args); \
+})
+
+static cairo_script_context_t *fdr_context;
+static const cairo_user_data_key_t fdr_key;
+
+static void
+fdr_get_extents (cairo_surface_t *surface,
+ cairo_rectangle_t *extents)
+{
+ cairo_t *cr;
+
+ cr = DLCALL (cairo_create, surface);
+ DLCALL (cairo_clip_extents, cr,
+ &extents->x, &extents->y, &extents->width, &extents->height);
+ DLCALL (cairo_destroy, cr);
+
+ extents->width -= extents->x;
+ extents->height -= extents->y;
+}
+
+static void
+fdr_surface_destroy (void *surface)
+{
+ DLCALL (cairo_surface_destroy, surface);
+}
+
+static void
+fdr_surface_reference (void *surface)
+{
+ DLCALL (cairo_surface_reference, surface);
+}
+
+static cairo_surface_t *
+fdr_surface_get_tee (cairo_surface_t *surface)
+{
+ return DLCALL (cairo_surface_get_user_data, surface, &fdr_key);
+}
+
+static cairo_surface_t *
+fdr_tee_surface_index (cairo_surface_t *surface, int index)
+{
+ return DLCALL (cairo_tee_surface_index, surface, index);
+}
+
+static cairo_status_t
+fdr_write (void *closure, const unsigned char *data, unsigned int len)
+{
+ int fd = (int) (intptr_t) closure;
+ while (len) {
+ int ret = write (fd, data, len);
+ if (ret < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ continue;
+ default:
+ return CAIRO_STATUS_WRITE_ERROR;
+ }
+ } else if (ret == 0) {
+ return CAIRO_STATUS_WRITE_ERROR;
+ } else {
+ data += ret;
+ len -= ret;
+ }
+ }
+ return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_t *
+cairo_create (cairo_surface_t *surface)
+{
+ cairo_surface_t *tee;
+
+ tee = fdr_surface_get_tee (surface);
+ if (tee == NULL) {
+ cairo_surface_t *script;
+ cairo_rectangle_t extents;
+ cairo_content_t content;
+
+ if (fdr_context == NULL) {
+ const char *env = getenv ("CAIRO_SPHINX_FD");
+ int fd = env ? atoi (env) : 1;
+ fdr_context = DLCALL (cairo_script_context_create_for_stream,
+ fdr_write, (void *) (intptr_t) fd);
+ }
+
+ fdr_get_extents (surface, &extents);
+ content = DLCALL (cairo_surface_get_content, surface);
+
+ tee = DLCALL (cairo_tee_surface_create, surface);
+ script = DLCALL (cairo_script_surface_create,
+ fdr_context, content, extents.width, extents.height);
+ DLCALL (cairo_tee_surface_append, tee, script);
+
+ DLCALL (cairo_surface_set_user_data, surface,
+ &fdr_key, tee, fdr_surface_destroy);
+ }
+
+ return DLCALL (cairo_create, tee);
+}
+
+static void
+fdr_remove_tee (cairo_surface_t *surface)
+{
+ fdr_surface_reference (surface);
+ DLCALL (cairo_surface_set_user_data, surface, &fdr_key, NULL, NULL);
+ fdr_surface_destroy (surface);
+}
+
+void
+cairo_destroy (cairo_t *cr)
+{
+ cairo_surface_t *tee;
+
+ tee = DLCALL (cairo_get_target, cr);
+ DLCALL (cairo_destroy, cr);
+
+ if (DLCALL (cairo_surface_get_reference_count, tee) == 1)
+ fdr_remove_tee (fdr_tee_surface_index (tee, 0));
+}
+
+void
+cairo_pattern_destroy (cairo_pattern_t *pattern)
+{
+ if (DLCALL (cairo_pattern_get_type, pattern) == CAIRO_PATTERN_TYPE_SURFACE) {
+ cairo_surface_t *surface;
+
+ if (DLCALL (cairo_pattern_get_surface, pattern, &surface) == CAIRO_STATUS_SUCCESS &&
+ DLCALL (cairo_surface_get_type, surface) == CAIRO_SURFACE_TYPE_TEE &&
+ DLCALL (cairo_surface_get_reference_count, surface) == 2)
+ {
+ fdr_remove_tee (fdr_tee_surface_index (surface, 0));
+ }
+ }
+
+ DLCALL (cairo_pattern_destroy, pattern);
+}
+
+cairo_surface_t *
+cairo_get_target (cairo_t *cr)
+{
+ cairo_surface_t *tee;
+
+ tee = DLCALL (cairo_get_target, cr);
+ return fdr_tee_surface_index (tee, 0);
+}
+
+cairo_surface_t *
+cairo_get_group_target (cairo_t *cr)
+{
+ cairo_surface_t *tee;
+
+ tee = DLCALL (cairo_get_group_target, cr);
+ return fdr_tee_surface_index (tee, 0);
+}
+
+cairo_pattern_t *
+cairo_pattern_create_for_surface (cairo_surface_t *surface)
+{
+ cairo_surface_t *tee;
+
+ tee = fdr_surface_get_tee (surface);
+ if (tee != NULL)
+ surface = tee;
+
+ return DLCALL (cairo_pattern_create_for_surface, surface);
+}
+
+cairo_status_t
+cairo_pattern_get_surface (cairo_pattern_t *pattern,
+ cairo_surface_t **surface)
+{
+ cairo_status_t status;
+ cairo_surface_t *tee;
+
+ status = DLCALL (cairo_pattern_get_surface, pattern, surface);
+ if (status != CAIRO_STATUS_SUCCESS)
+ return status;
+
+ tee = fdr_surface_get_tee (*surface);
+ if (tee != NULL)
+ *surface = tee;
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+void
+cairo_set_source_surface (cairo_t *cr,
+ cairo_surface_t *surface,
+ double x, double y)
+{
+ cairo_surface_t *tee;
+
+ tee = fdr_surface_get_tee (surface);
+ if (tee != NULL)
+ surface = tee;
+
+ DLCALL (cairo_set_source_surface, cr, surface, x, y);
+}
+
+cairo_surface_t *
+cairo_surface_create_similar (cairo_surface_t *surface,
+ cairo_content_t content,
+ int width, int height)
+{
+ cairo_surface_t *tee;
+
+ tee = fdr_surface_get_tee (surface);
+ if (tee != NULL)
+ surface = tee;
+
+ return DLCALL (cairo_surface_create_similar,
+ surface, content, width, height);
+}
diff --git a/util/cairo-sphinx/sphinx.c b/util/cairo-sphinx/sphinx.c
new file mode 100644
index 00000000..d6ccfc74
--- /dev/null
+++ b/util/cairo-sphinx/sphinx.c
@@ -0,0 +1,1525 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <errno.h>
+#include <assert.h>
+#include <pthread.h>
+
+#include <cairo.h>
+#include <cairo-script.h>
+#include <cairo-script-interpreter.h>
+#include <cairo-boilerplate.h>
+
+#include <glib.h> /* for checksumming */
+
+#define DATA_SIZE (256 << 20)
+#define SHM_PATH_XXX "/shmem-cairo-sphinx"
+
+struct client {
+ int sk;
+ const cairo_boilerplate_target_t *target;
+ cairo_surface_t *surface;
+ void *base;
+
+ cairo_script_interpreter_t *csi;
+ struct context_closure {
+ struct context_closure *next;
+ unsigned long id;
+ cairo_t *context;
+ cairo_surface_t *surface;
+ cairo_surface_t *original;
+ } *contexts;
+
+ unsigned long context_id;
+};
+
+struct surface_tag {
+ long width, height;
+};
+static const cairo_user_data_key_t surface_tag;
+
+static int
+client_socket (const char *socket_path);
+
+static int
+writen (int fd, const void *ptr, int len)
+{
+#if 1
+ const uint8_t *data = ptr;
+ while (len) {
+ int ret = write (fd, data, len);
+ if (ret < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ continue;
+ default:
+ return FALSE;
+ }
+ } else if (ret == 0) {
+ return FALSE;
+ } else {
+ data += ret;
+ len -= ret;
+ }
+ }
+ return TRUE;
+#else
+ int ret = send (fd, ptr, len, 0);
+ return ret == len;
+#endif
+}
+
+static int
+readn (int fd, void *ptr, int len)
+{
+#if 0
+ uint8_t *data = ptr;
+ while (len) {
+ int ret = read (fd, data, len);
+ if (ret < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ continue;
+ default:
+ return FALSE;
+ }
+ } else if (ret == 0) {
+ return FALSE;
+ } else {
+ data += ret;
+ len -= ret;
+ }
+ }
+ return TRUE;
+#else
+ int ret = recv (fd, ptr, len, MSG_WAITALL);
+ return ret == len;
+#endif
+}
+static int
+open_devnull_to_fd (int want_fd, int flags)
+{
+ int error;
+ int got_fd;
+
+ close (want_fd);
+
+ got_fd = open("/dev/null", flags | O_CREAT, 0700);
+ if (got_fd == -1)
+ return -1;
+
+ error = dup2 (got_fd, want_fd);
+ close (got_fd);
+
+ return error;
+}
+
+static int
+daemonize (void)
+{
+ void (*oldhup) (int);
+
+ /* Let the parent go. */
+ switch (fork ()) {
+ case -1: return -1;
+ case 0: break;
+ default: _exit (0);
+ }
+
+ /* Become session leader. */
+ if (setsid () == -1)
+ return -1;
+
+ /* Refork to yield session leadership. */
+ oldhup = signal (SIGHUP, SIG_IGN);
+
+ switch (fork ()) { /* refork to yield session leadership. */
+ case -1: return -1;
+ case 0: break;
+ default: _exit (0);
+ }
+
+ signal (SIGHUP, oldhup);
+
+ /* Establish stdio. */
+ if (open_devnull_to_fd (0, O_RDONLY) == -1)
+ return -1;
+ if (open_devnull_to_fd (1, O_WRONLY | O_APPEND) == -1)
+ return -1;
+ if (dup2 (1, 2) == -1)
+ return -1;
+
+ return 0;
+}
+
+static int
+server_socket (const char *socket_path)
+{
+ long flags;
+ struct sockaddr_un addr;
+ int sk;
+
+ unlink (socket_path);
+
+ sk = socket (PF_UNIX, SOCK_STREAM, 0);
+ if (sk == -1)
+ return -1;
+
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strcpy (addr.sun_path, socket_path);
+ if (bind (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1) {
+ close (sk);
+ return -1;
+ }
+
+ flags = fcntl (sk, F_GETFL);
+ if (flags == -1 || fcntl (sk, F_SETFL, flags | O_NONBLOCK) == -1) {
+ close (sk);
+ return -1;
+ }
+
+ if (listen (sk, 5) == -1) {
+ close (sk);
+ return -1;
+ }
+
+ return sk;
+}
+
+static int
+readline (int fd, char *line, int max)
+{
+ int len = 0;
+ do {
+ int ret = read (fd, &line[len], 1);
+ if (ret <= 0)
+ return -1;
+ } while (line[len] != '\n' && ++len < max);
+ line[len] = '\0';
+ return len;
+}
+
+struct clients {
+ int count, size;
+ int complete;
+
+ cairo_surface_t *meta;
+ unsigned long serial;
+
+ struct client_info {
+ int sk;
+ int trace;
+ unsigned long image_serial;
+ cairo_surface_t *image;
+ char *name;
+ char *target;
+ char *reference;
+
+ uint8_t *out_buf;
+ int out_len;
+ int out_size;
+ } *clients;
+ const char *shm_path;
+ unsigned long offset;
+ uint8_t *base;
+};
+
+static void *
+clients_shm (const char *shm_path)
+{
+ void *base;
+ int fd;
+
+ shm_unlink (shm_path);
+ fd = shm_open (shm_path, O_RDWR | O_EXCL | O_CREAT, 0777);
+ if (fd == -1)
+ return MAP_FAILED;
+
+ if (ftruncate (fd, DATA_SIZE) == -1) {
+ close (fd);
+ return MAP_FAILED;
+ }
+
+ base = mmap (NULL, DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ close (fd);
+
+ return base;
+}
+
+static int
+clients_init (struct clients *clients)
+{
+ clients->count = 0;
+ clients->complete = 0;
+ clients->size = 4;
+ clients->clients = xmalloc (clients->size * sizeof (struct client_info));
+
+ clients->shm_path = SHM_PATH_XXX;
+ clients->base = clients_shm (clients->shm_path);
+ if (clients->base == MAP_FAILED)
+ return -1;
+ clients->offset = 0;
+
+ clients->meta = NULL;
+ clients->serial = 0;
+
+ return 0;
+}
+
+static void
+clients_add_command (struct clients *clients, int fd, char *info)
+{
+ struct client_info *c;
+ char buf[1024];
+ int len;
+ char *str;
+
+ if (clients->count == clients->size) {
+ clients->size *= 2;
+ clients->clients = xrealloc (clients->clients,
+ clients->size * sizeof (struct client_info));
+ }
+
+ c = &clients->clients[clients->count++];
+ c->sk = fd;
+ c->trace = -1;
+ c->image_serial = 0;
+ c->image = NULL;
+ c->name = c->target = c->reference = NULL;
+
+ c->out_size = 8192;
+ c->out_buf = xmalloc (c->out_size);
+ c->out_len = 0;
+
+ str = strstr (info, "name=");
+ if (str != NULL) {
+ char *sp = strchr (str + 5, ' ');
+ int len;
+ if (sp)
+ len = sp - str - 5;
+ else
+ len = strlen (str + 5);
+ c->name = xmalloc (len + 1);
+ memcpy (c->name, str + 5, len);
+ c->name[len] = '\0';
+ }
+
+ str = strstr (info, "target=");
+ if (str != NULL) {
+ char *sp = strchr (str + 7, ' ');
+ int len;
+ if (sp)
+ len = sp - str - 7;
+ else
+ len = strlen (str + 7);
+ c->target = xmalloc (len + 1);
+ memcpy (c->target, str + 7, len);
+ c->target[len] = '\0';
+ }
+
+ str = strstr (info, "reference=");
+ if (str != NULL) {
+ char *sp = strchr (str + 10, ' ');
+ int len;
+ if (sp)
+ len = sp - str - 10;
+ else
+ len = strlen (str + 10);
+ c->reference = xmalloc (len + 1);
+ memcpy (c->reference, str + 10, len);
+ c->reference[len] = '\0';
+ }
+
+ len = sprintf (buf, "%s\n", clients->shm_path);
+ writen (fd, buf, len);
+}
+
+static void
+clients_add_trace (struct clients *clients, int fd, char *info)
+{
+ char *str, *sp;
+ char *name;
+ int i;
+
+ str = strstr (info, "name=");
+ assert (str != NULL);
+ sp = strchr (str + 5, ' ');
+ if (sp)
+ i = sp - str - 5;
+ else
+ i = strlen (str + 5);
+
+ name = xmalloc (i + 1);
+ memcpy (name, str + 5, i);
+ name[i] = '\0';
+
+ for (i = 0; i < clients->count; i++) {
+ struct client_info *c = &clients->clients[i];
+ if (strcmp (name, c->name) == 0) {
+ c->trace = fd;
+ break;
+ }
+ }
+
+ free (name);
+}
+
+static int
+clients_image (struct clients *clients, int fd, char *info)
+{
+ struct client_info *c = NULL;
+ int format, width, height, stride, size;
+ int i;
+
+ for (i = 0; i < clients->count; i++) {
+ if (clients->clients[i].sk == fd) {
+ c = &clients->clients[i];
+ break;
+ }
+ }
+
+ if (c == NULL)
+ return 0;
+
+ if (sscanf (info, "%lu %d %d %d %d",
+ &c->image_serial, &format, &width, &height, &stride) != 5)
+ {
+ return 0;
+ }
+
+ size = height * stride;
+ size = (size + 4095) & -4096;
+ assert (clients->offset + size <= DATA_SIZE);
+
+ c->image =
+ cairo_image_surface_create_for_data (clients->base + clients->offset,
+ format, width, height, stride);
+
+ if (! writen (fd, &clients->offset, sizeof (clients->offset)))
+ return 0;
+
+ clients->offset += size;
+
+ return 1;
+}
+
+static int
+u8_cmp (const void *A, const void *B)
+{
+ const uint8_t *a = A, *b = B;
+ return (int) *a - (int) *b;
+}
+
+static uint8_t
+median (uint8_t *values, int count)
+{
+ qsort (values, count, 1, u8_cmp);
+ return values[count/2];
+}
+
+static uint32_t
+get_pixel32 (int x, int y, const uint8_t *data, int stride)
+{
+ return ((uint32_t *)(data + y * stride))[x];
+}
+
+static uint8_t
+get_median_32 (int x, int y, int channel,
+ const uint8_t *data, int width, int height, int stride)
+{
+ uint8_t neighbourhood[25];
+ int cnt = 0;
+ int xx, yy;
+
+ for (yy = y - 2; yy <= y + 2; yy++) {
+ if (yy < 0)
+ continue;
+ if (yy >= height)
+ continue;
+
+ for (xx = x - 2; xx <= x + 2; xx++) {
+ if (xx < 0)
+ continue;
+ if (xx >= width)
+ continue;
+
+ neighbourhood[cnt++] = (get_pixel32 (xx, yy, data, stride) >> (channel*8)) & 0xff;
+ }
+ }
+
+ return median (neighbourhood, cnt);
+}
+
+static uint8_t
+get_pixel8 (int x, int y, const uint8_t *data, int stride)
+{
+ return data[y * stride + x];
+}
+
+static uint8_t
+get_median_8 (int x, int y, const uint8_t *data, int width, int height, int stride)
+{
+ uint8_t neighbourhood[25];
+ int cnt = 0;
+ int xx, yy;
+
+ for (yy = y - 2; yy <= y + 2; yy++) {
+ if (yy < 0)
+ continue;
+ if (yy >= height)
+ continue;
+
+ for (xx = x - 2; xx <= x + 2; xx++) {
+ if (xx < 0)
+ continue;
+ if (xx >= width)
+ continue;
+
+ neighbourhood[cnt++] = get_pixel8 (xx, yy, data, stride);
+ }
+ }
+
+ return median (neighbourhood, cnt);
+}
+
+static cairo_bool_t
+compare_images (cairo_surface_t *a,
+ cairo_surface_t *b)
+{
+ int width, height, stride;
+ const uint8_t *aa, *bb;
+ int x, y;
+
+ if (cairo_surface_status (a) || cairo_surface_status (b))
+ return FALSE;
+
+ if (cairo_surface_get_type (a) != cairo_surface_get_type (b))
+ return FALSE;
+
+ if (cairo_image_surface_get_format (a) != cairo_image_surface_get_format (b))
+ return FALSE;
+
+ if (cairo_image_surface_get_width (a) != cairo_image_surface_get_width (b))
+ return FALSE;
+
+ if (cairo_image_surface_get_height (a) != cairo_image_surface_get_height (b))
+ return FALSE;
+
+ if (cairo_image_surface_get_stride (a) != cairo_image_surface_get_stride (b))
+ return FALSE;
+
+
+ width = cairo_image_surface_get_width (a);
+ height = cairo_image_surface_get_height (a);
+ stride = cairo_image_surface_get_stride (a);
+
+ aa = cairo_image_surface_get_data (a);
+ bb = cairo_image_surface_get_data (b);
+ switch (cairo_image_surface_get_format (a)) {
+ case CAIRO_FORMAT_ARGB32:
+ for (y = 0; y < height; y++) {
+ const uint32_t *ua = (uint32_t *) aa;
+ const uint32_t *ub = (uint32_t *) bb;
+ for (x = 0; x < width; x++) {
+ if (ua[x] != ub[x]) {
+ int channel;
+
+ for (channel = 0; channel < 4; channel++) {
+ unsigned va, vb, diff;
+
+ va = (ua[x] >> (channel*8)) & 0xff;
+ vb = (ub[x] >> (channel*8)) & 0xff;
+ diff = abs (va - vb);
+ if (diff > 1) {
+ va = get_median_32 (x, y, channel, aa, width, height, stride);
+ vb = get_median_32 (x, y, channel, bb, width, height, stride);
+ diff = abs (va - vb);
+ if (diff > 1)
+ return FALSE;
+ }
+ }
+ }
+ }
+ aa += stride;
+ bb += stride;
+ }
+ break;
+
+ case CAIRO_FORMAT_RGB24:
+ for (y = 0; y < height; y++) {
+ const uint32_t *ua = (uint32_t *) aa;
+ const uint32_t *ub = (uint32_t *) bb;
+ for (x = 0; x < width; x++) {
+ if ((ua[x] & 0x00ffffff) != (ub[x] & 0x00ffffff)) {
+ int channel;
+
+ for (channel = 0; channel < 3; channel++) {
+ unsigned va, vb, diff;
+
+ va = (ua[x] >> (channel*8)) & 0xff;
+ vb = (ub[x] >> (channel*8)) & 0xff;
+ diff = abs (va - vb);
+ if (diff > 1) {
+ va = get_median_32 (x, y, channel, aa, width, height, stride);
+ vb = get_median_32 (x, y, channel, bb, width, height, stride);
+ diff = abs (va - vb);
+ if (diff > 1)
+ return FALSE;
+ }
+ }
+ }
+ }
+ aa += stride;
+ bb += stride;
+ }
+ break;
+
+ case CAIRO_FORMAT_A8:
+ for (y = 0; y < height; y++) {
+ for (x = 0; x < width; x++) {
+ if (aa[x] != bb[x]) {
+ unsigned diff = abs (aa[x] - bb[x]);
+ if (diff > 1) {
+ uint8_t va, vb;
+
+ va = get_median_8 (x, y, aa, width, height, stride);
+ vb = get_median_8 (x, y, bb, width, height, stride);
+ diff = abs (va - vb);
+ if (diff > 1)
+ return FALSE;
+ }
+
+ }
+ }
+ aa += stride;
+ bb += stride;
+ }
+ break;
+
+ case CAIRO_FORMAT_A1:
+ width /= 8;
+ for (y = 0; y < height; y++) {
+ if (memcmp (aa, bb, width))
+ return FALSE;
+ aa += stride;
+ bb += stride;
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+static int
+check_images (struct clients *clients)
+{
+ int i, j;
+
+ for (i = 0; i < clients->count; i++) {
+ struct client_info *c = &clients->clients[i];
+
+ if (c->reference == NULL)
+ continue;
+
+ for (j = 0; j < clients->count; j++) {
+ struct client_info *ref = &clients->clients[j];
+
+ if (strcmp (c->reference, ref->name))
+ continue;
+
+ if (! compare_images (c->image, ref->image))
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static gchar *
+checksum (const char *filename)
+{
+ gchar *str = NULL;
+ gchar *data;
+ gsize len;
+
+ if (g_file_get_contents (filename, &data, &len, NULL)) {
+ str = g_compute_checksum_for_data (G_CHECKSUM_SHA1, (guchar *) data, len);
+ g_free (data);
+ }
+
+ return str;
+}
+
+static void
+write_trace (struct clients *clients)
+{
+ cairo_script_context_t *ctx;
+ gchar *csum;
+ char buf[4096];
+ int i;
+
+ mkdir ("output", 0777);
+
+ ctx = cairo_script_context_create ("output/cairo-sphinx.trace");
+ cairo_script_from_meta_surface (ctx, clients->meta);
+ cairo_script_context_destroy (ctx);
+
+ csum = checksum ("output/cairo-sphinx.trace");
+
+ sprintf (buf, "output/%s.trace", csum);
+ if (! g_file_test (buf, G_FILE_TEST_EXISTS)) {
+ rename ("output/cairo-sphinx.trace", buf);
+
+ sprintf (buf, "output/%s.meta.png", csum);
+ cairo_surface_write_to_png (clients->meta, buf);
+
+ for (i = 0; i < clients->count; i++) {
+ struct client_info *c = &clients->clients[i];
+ if (c->image != NULL) {
+ sprintf (buf, "output/%s.%s.png", csum, c->name);
+ cairo_surface_write_to_png (c->image, buf);
+ }
+ }
+ }
+}
+
+static void
+clients_complete (struct clients *clients, int fd)
+{
+ int i;
+
+ for (i = 0; i < clients->count; i++) {
+ if (clients->clients[i].sk == fd) {
+ break;
+ }
+ }
+ if (i == clients->count)
+ return;
+
+ if (++clients->complete != clients->count)
+ return;
+
+ clients->offset = 0;
+ clients->complete = 0;
+
+ if (! check_images (clients))
+ write_trace (clients);
+
+ /* ack */
+ for (i = 0; i < clients->count; i++) {
+ struct client_info *c = &clients->clients[i];
+
+ cairo_surface_destroy (c->image);
+ c->image = NULL;
+
+ if (! writen (c->sk, &clients->serial, sizeof (clients->serial)))
+ continue;
+
+ c->image_serial = 0;
+ }
+
+ clients->meta = NULL;
+ clients->serial = 0;
+}
+
+static void
+clients_meta (struct clients *clients, int fd, char *info)
+{
+ sscanf (info, "%p %lu", &clients->meta, &clients->serial);
+ clients_complete (clients, fd);
+}
+
+static void
+clients_remove (struct clients *clients, int fd)
+{
+ int i, j;
+
+ for (i = 0; i < clients->count; i++) {
+ struct client_info *c = &clients->clients[i];
+ if (c->sk == fd) {
+ free (c->out_buf);
+ break;
+ }
+ }
+
+ for (j = i++; i < clients->count; i++)
+ clients->clients[j] = clients->clients[i];
+
+ clients->count = j;
+}
+
+static void
+clients_send_trace (struct clients *clients,
+ const char * const line, const int len)
+{
+ int i;
+
+ for (i = 0; i < clients->count; i++) {
+ struct client_info *c = &clients->clients[i];
+ int ret, rem = len;
+
+ if (c->trace == -1)
+ continue;
+
+ if (c->out_len) {
+ ret = write (c->trace, c->out_buf, c->out_len);
+ if (ret > 0) {
+ c->out_len -= ret;
+ if (c->out_len)
+ memmove (c->out_buf, c->out_buf + ret, c->out_len);
+ }
+ }
+
+ if (! c->out_len) {
+ ret = write (c->trace, line, rem);
+ if (ret > 0)
+ rem -= ret;
+ }
+
+ if (rem) {
+ if (c->out_len + rem > c->out_size) {
+ c->out_size *= 2;
+ c->out_buf = xrealloc (c->out_buf, c->out_size);
+ }
+
+ memcpy (c->out_buf + c->out_len, line, rem);
+ c->out_len += rem;
+ }
+ }
+}
+
+static void
+clients_fini (struct clients *clients)
+{
+ shm_unlink (clients->shm_path);
+ munmap (clients->base, DATA_SIZE);
+ free (clients->clients);
+}
+
+static int
+nonblocking (int fd)
+{
+ long flags;
+
+ flags = fcntl (fd, F_GETFL);
+ if (flags == -1)
+ return -1;
+
+ return fcntl (fd, F_SETFL, flags | O_NONBLOCK);
+}
+
+static void *
+request_image (struct client *c,
+ struct context_closure *closure,
+ cairo_format_t format,
+ int width, int height, int stride)
+{
+ char buf[1024];
+ unsigned long offset = -1;
+ int len;
+
+ assert (format != (cairo_format_t) -1);
+
+ len = sprintf (buf, ".image %lu %d %d %d %d\n",
+ closure->id, format, width, height, stride);
+ writen (c->sk, buf, len);
+
+ readn (c->sk, &offset, sizeof (offset));
+ if (offset == (unsigned long) -1)
+ return NULL;
+
+ return (uint8_t *) c->base + offset;
+}
+
+static cairo_format_t
+format_for_content (cairo_content_t content)
+{
+ switch (content) {
+ case CAIRO_CONTENT_ALPHA:
+ return CAIRO_FORMAT_A8;
+ case CAIRO_CONTENT_COLOR:
+ return CAIRO_FORMAT_RGB24;
+ default:
+ case CAIRO_CONTENT_COLOR_ALPHA:
+ return CAIRO_FORMAT_ARGB32;
+ }
+}
+
+static void
+get_surface_size (cairo_surface_t *surface,
+ int *width, int *height,
+ cairo_format_t *format)
+{
+ if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE) {
+ *width = cairo_image_surface_get_width (surface);
+ *height = cairo_image_surface_get_height (surface);
+ *format = cairo_image_surface_get_format (surface);
+ } else {
+ struct surface_tag *tag;
+
+ tag = cairo_surface_get_user_data (surface, &surface_tag);
+ if (tag != NULL) {
+ *width = tag->width;
+ *height = tag->height;
+ } else {
+ double x0, x1, y0, y1;
+ cairo_t *cr;
+
+ /* presumably created using cairo_surface_create_similar() */
+ cr = cairo_create (surface);
+ cairo_clip_extents (cr, &x0, &y0, &x1, &y1);
+ cairo_destroy (cr);
+
+ tag = xmalloc (sizeof (*tag));
+ *width = tag->width = ceil (x1 - x0);
+ *height = tag->height = ceil (y1 - y0);
+
+ if (cairo_surface_set_user_data (surface, &surface_tag, tag, free))
+ exit (-1);
+ }
+ }
+}
+
+
+static void
+send_surface (struct client *c,
+ struct context_closure *closure)
+{
+ cairo_surface_t *source = closure->surface;
+ cairo_surface_t *image;
+ cairo_format_t format = (cairo_format_t) -1;
+ cairo_t *cr;
+ int width, height, stride;
+ void *data;
+ unsigned long serial;
+
+ get_surface_size (source, &width, &height, &format);
+ if (format == (cairo_format_t) -1)
+ format = format_for_content (cairo_surface_get_content (source));
+
+ stride = cairo_format_stride_for_width (format, width);
+
+ data = request_image (c, closure, format, width, height, stride);
+ if (data == NULL)
+ exit (-1);
+
+ image = cairo_image_surface_create_for_data (data,
+ format,
+ width, height,
+ stride);
+ cr = cairo_create (image);
+ cairo_surface_destroy (image);
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_surface (cr, source, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ /* signal completion */
+ writen (c->sk, ".complete\n", strlen (".complete\n"));
+
+ /* wait for image check */
+ serial = 0;
+ readn (c->sk, &serial, sizeof (serial));
+ if (serial != closure->id)
+ exit (-1);
+}
+
+static void
+send_meta (struct client *c,
+ struct context_closure *closure)
+{
+ cairo_surface_t *source = closure->surface;
+ char buf[1024];
+ int len;
+ unsigned long serial;
+
+ assert (cairo_surface_get_type (source) == CAIRO_SURFACE_TYPE_META);
+ len = sprintf (buf, ".meta %p %lu\n", source, closure->id);
+ writen (c->sk, buf, len);
+
+ /* wait for image check */
+
+ serial = 0;
+ readn (c->sk, &serial, sizeof (serial));
+ if (serial != closure->id)
+ exit (-1);
+}
+
+static cairo_surface_t *
+_surface_create (void *closure,
+ cairo_content_t content,
+ double width, double height)
+{
+ struct client *c = closure;
+ cairo_surface_t *surface;
+
+ surface = cairo_surface_create_similar (c->surface,
+ content, width, height);
+ if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE) {
+ struct surface_tag *tag;
+
+ tag = xmalloc (sizeof (*tag));
+ tag->width = width;
+ tag->height = height;
+ if (cairo_surface_set_user_data (surface, &surface_tag, tag, free))
+ exit (-1);
+ }
+
+ return surface;
+}
+
+static cairo_t *
+_context_create (void *closure, cairo_surface_t *surface)
+{
+ struct client *c = closure;
+ struct context_closure *l;
+ cairo_bool_t foreign = FALSE;
+
+ l = xmalloc (sizeof (*l));
+ l->next = c->contexts;
+ l->surface = surface;
+ l->original = cairo_surface_reference (surface);
+ l->id = ++c->context_id;
+ if (l->id == 0)
+ l->id = ++c->context_id;
+ c->contexts = l;
+
+ /* record everything, including writes to images */
+ if (c->target == NULL) {
+ if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_META) {
+ cairo_format_t format;
+ int width, height;
+
+ get_surface_size (surface, &width, &height, &format);
+ l->surface = cairo_surface_create_similar (c->surface,
+ cairo_surface_get_content (surface),
+ width, height);
+ foreign = TRUE;
+ }
+ }
+
+ l->context = cairo_create (l->surface);
+ if (foreign) {
+ cairo_set_source_surface (l->context, surface, 0, 0);
+ cairo_paint (l->context);
+ }
+
+ return l->context;
+}
+
+static void
+_context_destroy (void *closure, void *ptr)
+{
+ struct client *c = closure;
+ struct context_closure *l, **prev = &c->contexts;
+
+ while ((l = *prev) != NULL) {
+ if (l->context == ptr) {
+ if (cairo_surface_status (l->surface) == CAIRO_STATUS_SUCCESS) {
+ if (c->target == NULL)
+ send_meta (c, l);
+ else
+ send_surface (c, l);
+ } else {
+ exit (-1);
+ }
+
+ cairo_surface_destroy (l->original);
+ *prev = l->next;
+ free (l);
+ return;
+ }
+ prev = &l->next;
+ }
+}
+
+static void *
+recorder (void *arg)
+{
+ struct client client;
+ const cairo_script_interpreter_hooks_t hooks = {
+ .closure = &client,
+ .surface_create = _surface_create,
+ .context_create = _context_create,
+ .context_destroy = _context_destroy,
+ };
+ char *buf;
+ int buf_size;
+ int len = 0, ret;
+ struct pollfd pfd;
+
+ client.target = NULL;
+ client.sk = client_socket ("/tmp/cairo-sphinx");
+ if (client.sk < 0)
+ return NULL;
+
+ buf_size = 65536;
+ buf = xmalloc (buf_size);
+
+ len = sprintf (buf, "client-command target=meta name=.recorder\n");
+ if (! writen (client.sk, buf, len))
+ return NULL;
+
+ /* drain the shm_path */
+ len = readline (client.sk, buf, buf_size);
+
+ pfd.fd = client_socket ("/tmp/cairo-sphinx");
+ if (pfd.fd < 0)
+ return NULL;
+
+ len = sprintf (buf, "client-trace name=.recorder\n");
+ if (! writen (pfd.fd, buf, len))
+ return NULL;
+
+ client.surface = cairo_meta_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
+ NULL);
+
+ client.context_id = 0;
+ client.csi = cairo_script_interpreter_create ();
+ cairo_script_interpreter_install_hooks (client.csi, &hooks);
+
+ nonblocking (pfd.fd);
+ pfd.events = POLLIN;
+ len = 0;
+ while (poll (&pfd, 1, -1) > 0) {
+ while ((ret = read (pfd.fd, buf + len, buf_size - len)) > 0) {
+ int end;
+
+ if (ret == buf_size - len) {
+ buf_size *= 2;
+ buf = xrealloc (buf, buf_size);
+ }
+ len += ret;
+
+ for (end = len; end > 0 && buf[--end] != '\n'; )
+ ;
+ if (end > 0) {
+ buf[end] = '\0';
+ cairo_script_interpreter_feed_string (client.csi, buf, end);
+
+ len -= end + 1;
+ if (len)
+ memmove (buf, buf + end + 1, len);
+ }
+ }
+ if (ret == 0)
+ break;
+ if (! (errno == EAGAIN || errno == EINTR))
+ break;
+ }
+
+ cairo_script_interpreter_finish (client.csi);
+ cairo_script_interpreter_destroy (client.csi);
+
+ cairo_surface_destroy (client.surface);
+ return NULL;
+}
+
+static int
+do_server (const char *path)
+{
+ pthread_t thread;
+ struct clients clients;
+ char line[4096];
+ struct pollfd *pfd;
+ int num_pfd, size_pfd;
+ int n, cnt, ret = 1;
+ int sk, source = -1;
+ int waiter = -1, waiter_count = 0;
+ int len;
+
+ signal (SIGPIPE, SIG_IGN);
+
+ if (clients_init (&clients) < 0) {
+ fprintf (stderr, "Failed to initialise clients structure\n");
+ return -1;
+ }
+
+ sk = server_socket (path);
+ if (sk < 0) {
+ fprintf (stderr, "Failed to create server socket\n");
+ return 1;
+ }
+
+ if (daemonize () < 0)
+ return 1;
+
+ if (pthread_create (&thread, NULL, recorder, NULL) < 0) {
+ fprintf (stderr, "Failed to create spawn recording thread\n");
+ return 1;
+ }
+
+ size_pfd = 4;
+ pfd = xmalloc (sizeof (*pfd) * size_pfd);
+ pfd[0].fd = sk;
+ pfd[0].events = POLLIN;
+ num_pfd = 1;
+
+ while ((cnt = poll (pfd, num_pfd, -1)) > 0) {
+ int have_source;
+
+ if (pfd[0].revents) {
+ while ((sk = accept (pfd[0].fd, NULL, NULL)) != -1) {
+ len = readline (sk, line, sizeof (line));
+ if (strcmp (line, "source") == 0) {
+
+ if (source != -1)
+ exit (1);
+
+ source = sk;
+ if (nonblocking (sk) < 0) {
+ close (sk);
+ continue;
+ }
+ } else if (strncmp (line, "client-command", 14) == 0) {
+ if (source == -1)
+ clients_add_command (&clients, sk, line);
+ } else if (strncmp (line, "client-trace", 12) == 0) {
+ if (source == -1) {
+ clients_add_trace (&clients, sk, line);
+ if (nonblocking (sk) < 0) {
+ close (sk);
+ continue;
+ }
+
+ if (clients.count == waiter_count) {
+ for (n = 1; n < num_pfd; n++) {
+ if (pfd[n].fd == waiter) {
+ pfd[n].fd = -1;
+ break;
+ }
+ }
+ close (waiter);
+ waiter_count = -1;
+ }
+ }
+ } else if (strncmp (line, "wait", 4) == 0) {
+ int count = atoi (line + 5) + 1;
+ if (clients.count == count) {
+ close (sk);
+ continue;
+ } else {
+ waiter = sk;
+ waiter_count = count;
+ }
+ }
+
+ if (num_pfd == size_pfd) {
+ size_pfd *= 2;
+ pfd = xrealloc (pfd, sizeof (*pfd) * size_pfd);
+ }
+
+ pfd[num_pfd].fd = sk;
+ pfd[num_pfd].events = POLLIN;
+ pfd[num_pfd].revents = 0;
+ num_pfd++;
+ }
+ cnt--;
+ }
+
+ have_source = 0;
+ for (n = 1; cnt && n < num_pfd; n++) {
+ if (! pfd[n].revents)
+ continue;
+ cnt--;
+
+ if (pfd[n].fd == -1)
+ continue;
+
+ if (source == pfd[n].fd) {
+ have_source = n;
+ } else {
+ len = readline (pfd[n].fd, line, sizeof (line));
+ if (len < 0) {
+ clients_remove (&clients, pfd[n].fd);
+ close (pfd[n].fd);
+ pfd[n].fd = -1;
+ continue;
+ }
+
+ if (strncmp (line, ".image", 6) == 0) {
+ if (! clients_image (&clients, pfd[n].fd, line + 7)) {
+ clients_remove (&clients, pfd[n].fd);
+ close (pfd[n].fd);
+ pfd[n].fd = -1;
+ continue;
+ }
+ } else if (strncmp (line, ".complete", 9) == 0) {
+ clients_complete (&clients, pfd[n].fd);
+ } else if (strncmp (line, ".meta", 5) == 0) {
+ clients_meta (&clients, pfd[n].fd, line + 6);
+ } else {
+ printf ("do_command (%s)\n", line);
+ }
+ }
+ }
+
+ if (have_source) {
+ do {
+ len = read (source, line, sizeof (line));
+ if (len > 0) {
+ clients_send_trace (&clients, line, len);
+ } else if (len == 0) {
+ close (source);
+ pfd[have_source].fd = source = -1;
+ goto done;
+ } else
+ break;
+ } while (1);
+ }
+
+ for (n = cnt = 1; n < num_pfd; n++) {
+ if (pfd[n].fd != -1) {
+ if (cnt != n)
+ pfd[cnt] = pfd[n];
+ cnt++;
+ }
+ }
+ num_pfd = cnt;
+ }
+
+done:
+ ret = 0;
+ for (n = 0; n < num_pfd; n++) {
+ if (pfd[n].fd != -1)
+ close (pfd[n].fd);
+ }
+ free (pfd);
+ clients_fini (&clients);
+
+ return ret;
+}
+
+static void *
+client_shm (const char *shm_path)
+{
+ void *base;
+ int fd;
+
+ fd = shm_open (shm_path, O_RDWR, 0);
+ if (fd == -1)
+ return MAP_FAILED;
+
+ base = mmap (NULL, DATA_SIZE,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_NORESERVE,
+ fd, 0);
+ close (fd);
+
+ return base;
+}
+
+static int
+client_socket (const char *socket_path)
+{
+ struct sockaddr_un addr;
+ int sk;
+
+ sk = socket (PF_UNIX, SOCK_STREAM, 0);
+ if (sk == -1)
+ return -1;
+
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strcpy (addr.sun_path, socket_path);
+
+ if (connect (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1)
+ return -1;
+
+ return sk;
+}
+
+static int
+do_client (int fd,
+ const char *target,
+ const char *name,
+ const char *reference,
+ cairo_content_t content)
+{
+ struct client client;
+ const cairo_script_interpreter_hooks_t hooks = {
+ .closure = &client,
+ .surface_create = _surface_create,
+ .context_create = _context_create,
+ .context_destroy = _context_destroy,
+ };
+ void *closure;
+ char *buf;
+ int buf_size;
+ int len = 0, ret;
+ struct pollfd pfd;
+
+ client.sk = fd;
+ client.target = cairo_boilerplate_get_target_by_name (target, content);
+ client.context_id = 0;
+
+ client.surface = client.target->create_surface (NULL, content, 1, 1, 1, 1,
+ CAIRO_BOILERPLATE_MODE_TEST,
+ 0, &closure);
+ if (client.surface == NULL) {
+ fprintf (stderr, "Failed to create target surface: %s.\n",
+ client.target->name);
+ return 1;
+ }
+
+ buf_size = 65536;
+ buf = xmalloc (buf_size);
+
+ if (reference != NULL) {
+ len = sprintf (buf,
+ "client-command name=%s target=%s reference=%s\n",
+ name, target, reference);
+ } else {
+ len = sprintf (buf,
+ "client-command name=%s target=%s\n",
+ name, target);
+ }
+ if (! writen (fd, buf, len))
+ return 1;
+
+ len = readline (fd, buf, buf_size);
+ client.base = client_shm (buf);
+ if (client.base == MAP_FAILED) {
+ fprintf (stderr, "Failed to map shared memory segment '%s'.\n", buf);
+ return 1;
+ }
+
+ if (daemonize () < 0)
+ return 1;
+
+ pfd.fd = client_socket ("/tmp/cairo-sphinx");
+ if (pfd.fd < 0)
+ return 1;
+
+ len = sprintf (buf, "client-trace name=%s\n", name);
+ if (! writen (pfd.fd, buf, len))
+ return 1;
+
+ client.csi = cairo_script_interpreter_create ();
+ cairo_script_interpreter_install_hooks (client.csi, &hooks);
+
+ nonblocking (pfd.fd);
+ pfd.events = POLLIN;
+ len = 0;
+ while (poll (&pfd, 1, -1) > 0) {
+ while ((ret = read (pfd.fd, buf + len, buf_size - len)) > 0) {
+ int end;
+
+ if (ret == buf_size - len) {
+ buf_size *= 2;
+ buf = xrealloc (buf, buf_size);
+ }
+ len += ret;
+
+ for (end = len; end > 0 && buf[--end] != '\n'; )
+ ;
+ if (end > 0) {
+ buf[end] = '\0';
+ cairo_script_interpreter_feed_string (client.csi, buf, end);
+
+ len -= end + 1;
+ if (len)
+ memmove (buf, buf + end + 1, len);
+ }
+ }
+ if (ret == 0)
+ break;
+ if (! (errno == EAGAIN || errno == EINTR))
+ break;
+ }
+
+ cairo_script_interpreter_finish (client.csi);
+ cairo_script_interpreter_destroy (client.csi);
+
+ cairo_surface_destroy (client.surface);
+ close (fd);
+
+ return 0;
+}
+
+static int
+do_exec (int fd, char **argv)
+{
+ char buf[4096];
+
+ if (*argv == NULL)
+ return 0;
+
+ snprintf (buf, sizeof (buf), "%s/cairo-trace.so", LIBDIR);
+ setenv ("LD_PRELOAD", buf, 1);
+
+ snprintf (buf, sizeof (buf), "0");
+ setenv ("CAIRO_TRACE_LINE_INFO", buf, 1);
+
+ snprintf (buf, sizeof (buf), "%d", fd);
+ setenv ("CAIRO_TRACE_FD", buf, 1);
+ putenv (buf);
+
+ return execvp (argv[0], argv);
+}
+
+static int
+do_wait (int fd)
+{
+ char buf;
+ int ret = read (fd, &buf, 1);
+ return ret != 0;
+}
+
+int
+main (int argc, char **argv)
+{
+ char buf[4096];
+ int len;
+ int fd;
+
+ if (argc == 1)
+ return do_server ("/tmp/cairo-sphinx");
+
+ fd = client_socket ("/tmp/cairo-sphinx");
+ if (fd < 0)
+ return 1;
+
+ if (strcmp (argv[1], "client") == 0) {
+ return do_client (fd, argv[2], argv[3], argv[4],
+ CAIRO_CONTENT_COLOR_ALPHA);
+ }
+
+ if (strcmp (argv[1], "wait") == 0) {
+ len = snprintf (buf, sizeof (buf), "wait %s\n", argv[2]);
+ if (! writen (fd, buf, len))
+ return 1;
+
+ return do_wait (fd);
+ }
+
+ if (strcmp (argv[1], "exec") == 0) {
+ len = snprintf (buf, sizeof (buf), "source\n");
+ if (! writen (fd, buf, len))
+ return 1;
+
+ return do_exec (fd, argv+2);
+ }
+
+ if (strcmp (argv[1], "replay") == 0) {
+ len = snprintf (buf, sizeof (buf), "replay %s\n", argv[2]);
+ return ! writen (fd, buf, len);
+ }
+
+ return 0;
+}