Logo Search packages:      
Sourcecode: cairo version File versions  Download package

sphinx.c

#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 */

#ifndef CAIRO_HAS_REAL_PTHREAD
# error "cairo-sphinx needs real pthreads"
#endif

#define DATA_SIZE (256 << 20)
#define SHM_PATH_XXX "/shmem-cairo-sphinx"

00033 struct client {
    int sk;
    const cairo_boilerplate_target_t *target;
    cairo_surface_t *surface;
    void *base;

    cairo_script_interpreter_t *csi;
00040     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 ()) {
    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;
}

00218 struct clients {
    int count, size;
    int complete;

    cairo_surface_t *recording;
    unsigned long serial;

00225     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->recording = 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)
{
    /* XXX could use a fast median here if we cared */
    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;

    case CAIRO_FORMAT_INVALID:
    case CAIRO_FORMAT_RGB16_565: /* XXX */
      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_device_t *ctx;
    gchar *csum;
    char buf[4096];
    int i;

    mkdir ("output", 0777);

    ctx = cairo_script_create ("output/cairo-sphinx.trace");
    cairo_script_from_recording_surface (ctx, clients->recording);
    cairo_device_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.recording.png", csum);
      cairo_surface_write_to_png (clients->recording, 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->recording = NULL;
    clients->serial = 0;
}

static void
clients_recording (struct clients *clients, int fd, char *info)
{
    sscanf (info, "%p %lu", &clients->recording, &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_INVALID);

    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_INVALID;
    cairo_t *cr;
    int width, height, stride;
    void *data;
    unsigned long serial;

    get_surface_size (source, &width, &height, &format);
    if (format == CAIRO_FORMAT_INVALID)
      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_recording (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_RECORDING);
    len = sprintf (buf, ".recording %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,
             long uid)
{
    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_RECORDING) {
          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_recording (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=recording 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_recording_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, ".recording", 10) == 0) {
                clients_recording (&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;
}

Generated by  Doxygen 1.6.0   Back to index