#include <config.h>
#include <unistd.h>
#include <string.h>
#include <metacity-private/util.h>
#include <metacity-private/theme.h>
#include <metacity-private/theme-parser.h>
#include <metacity-private/preview-widget.h>
#include <signal.h>
#include <errno.h>
#include <math.h>

/* We have to #undef this as metacity #defines these. */
#undef _
#undef N_

#include <glib.h>

#include "theme-thumbnail.h"
#include "gtkrc-utils.h"
#include "capplet-util.h"

typedef struct
{
  gboolean set;
  gint thumbnail_width;
  gint thumbnail_height;
  GByteArray *data;
  gchar *theme_name;
  ThemeThumbnailFunc func;
  gpointer user_data;
  GDestroyNotify destroy;
  GIOChannel *channel;
  guint watch_id;
} ThemeThumbnailAsyncData;


static ThemeThumbnailAsyncData async_data;

/* Protocol */

/* Our protocol is pretty simple.  The parent process will write several strings
 * (separated by a '\000'). They are the widget theme, the wm theme, the icon
 * theme, etc.  Then, it will wait for the child to write back the data.  The
 * parent expects ICON_SIZE_WIDTH * ICON_SIZE_HEIGHT * 4 bytes of information.
 * After that, the child is ready for the next theme to render.
 */

enum
{
  READY_FOR_THEME,
  READING_TYPE,
  READING_CONTROL_THEME_NAME,
  READING_GTK_COLOR_SCHEME,
  READING_WM_THEME_NAME,
  READING_ICON_THEME_NAME,
  READING_APPLICATION_FONT,
  WRITING_PIXBUF_DATA
};

typedef struct
{
  gint status;
  GByteArray *type;
  GByteArray *control_theme_name;
  GByteArray *gtk_color_scheme;
  GByteArray *wm_theme_name;
  GByteArray *icon_theme_name;
  GByteArray *application_font;
} ThemeThumbnailData;

typedef struct
{
  gchar *thumbnail_type;
  gpointer theme_info;
  ThemeThumbnailFunc func;
  gpointer user_data;
  GDestroyNotify destroy;
} ThemeQueueItem;

static GList *theme_queue = NULL;

static int pipe_to_factory_fd[2];
static int pipe_from_factory_fd[2];

#define THUMBNAIL_TYPE_META     "meta"
#define THUMBNAIL_TYPE_GTK      "gtk"
#define THUMBNAIL_TYPE_METACITY "metacity"
#define THUMBNAIL_TYPE_ICON     "icon"

#define META_THUMBNAIL_SIZE       128
#define GTK_THUMBNAIL_SIZE         96
#define METACITY_THUMBNAIL_WIDTH  120
#define METACITY_THUMBNAIL_HEIGHT  60
#define ICON_THUMBNAIL_SIZE        48


static void
fake_expose_widget (GtkWidget *widget,
                    GdkPixmap *pixmap,
                    GdkRectangle *area)
{
  GdkWindow *tmp_window;
  GdkEventExpose event;

  event.type = GDK_EXPOSE;
  event.window = pixmap;
  event.send_event = FALSE;
  event.area = area ? *area : widget->allocation;
  event.region = NULL;
  event.count = 0;

  tmp_window = widget->window;
  widget->window = pixmap;
  gtk_widget_send_expose (widget, (GdkEvent *) &event);
  widget->window = tmp_window;
}

static void
hbox_foreach (GtkWidget *widget,
              gpointer   data)
{
  if (GTK_WIDGET_VISIBLE (widget)) {
    gtk_widget_realize (widget);
    gtk_widget_map (widget);
    gtk_widget_ensure_style (widget);
    fake_expose_widget (widget, (GdkPixmap *) data, NULL);
  }
}

static void
pixbuf_apply_mask_region (GdkPixbuf *pixbuf, GdkRegion *region)
{
  gint nchannels, rowstride, w, h;
  guchar *pixels, *p;

  g_return_if_fail (pixbuf);
  g_return_if_fail (region);

  nchannels = gdk_pixbuf_get_n_channels (pixbuf);
  rowstride = gdk_pixbuf_get_rowstride (pixbuf);
  pixels = gdk_pixbuf_get_pixels (pixbuf);


  /* we need an alpha channel ... */
  if (!gdk_pixbuf_get_has_alpha (pixbuf) || nchannels != 4)
    return;

  for (w = 0; w < gdk_pixbuf_get_width (pixbuf); ++w)
    for (h = 0; h < gdk_pixbuf_get_height (pixbuf); ++h)
    {
      if (!gdk_region_point_in (region, w, h))
      {
        p = pixels + h * rowstride + w * nchannels;
        if (G_BYTE_ORDER == G_BIG_ENDIAN)
          p[0] = 0x0;
        else
          p[3] = 0x0;
      }
    }

}

static GdkPixbuf *
create_folder_icon (char *icon_theme_name)
{
  GtkIconTheme *icon_theme;
  GdkPixbuf *folder_icon = NULL, *retval;
  GtkIconInfo *folder_icon_info;
  const gchar *filename;
  gchar *example_icon_name;

  icon_theme = gtk_icon_theme_new ();
  gtk_icon_theme_set_custom_theme (icon_theme, icon_theme_name);

  folder_icon_info = NULL;
  /* Get the Example icon name in the theme if specified */
  example_icon_name = gtk_icon_theme_get_example_icon_name (icon_theme);
  if (example_icon_name != NULL)
    folder_icon_info = gtk_icon_theme_lookup_icon (icon_theme, example_icon_name, 48, GTK_ICON_LOOKUP_FORCE_SVG);
  g_free (example_icon_name);

  /* If an Example is not specified, fall back to using the folder icons in
     the order of Icon Nameing Spec, "gnome-fs-directory", and "folder" */
  if (folder_icon_info == NULL)
    folder_icon_info = gtk_icon_theme_lookup_icon (icon_theme, "x-directory-normal", 48, GTK_ICON_LOOKUP_FORCE_SVG);
  if (folder_icon_info == NULL)
    folder_icon_info = gtk_icon_theme_lookup_icon (icon_theme, "gnome-fs-directory", 48, GTK_ICON_LOOKUP_FORCE_SVG);
  if (folder_icon_info == NULL)
    folder_icon_info = gtk_icon_theme_lookup_icon (icon_theme, "folder", 48, GTK_ICON_LOOKUP_FORCE_SVG);

  g_object_unref (icon_theme);

  if (folder_icon_info != NULL)
  {
    filename = gtk_icon_info_get_filename (folder_icon_info);

    if (filename != NULL)
    {
      folder_icon = gdk_pixbuf_new_from_file (filename, NULL);
    }

    gtk_icon_info_free (folder_icon_info);
  }

  /* render the icon to the thumbnail */
  if (folder_icon == NULL)
  {
    GtkWidget *dummy;
    dummy = gtk_label_new ("");

    folder_icon = gtk_widget_render_icon (dummy,
                                          GTK_STOCK_MISSING_IMAGE,
                                          GTK_ICON_SIZE_DIALOG,
                                          NULL);

    gtk_widget_destroy (dummy);
  }

  /* Some icons don't come back at the requested dimensions, so we need to scale
   * them. The width is usually the largest dimension for icons that come at
   * irregular sizes, so use this to calculate the scale factor
   */
  if (gdk_pixbuf_get_width (folder_icon) != ICON_THUMBNAIL_SIZE) {
    int width, height;
    gdouble scale;

    scale = ((double) ICON_THUMBNAIL_SIZE) / gdk_pixbuf_get_width (folder_icon);
    width = ICON_THUMBNAIL_SIZE;
    height = scale * gdk_pixbuf_get_height (folder_icon);

    retval = gdk_pixbuf_scale_simple (folder_icon, width, height, GDK_INTERP_BILINEAR);
    g_object_unref (folder_icon);
  } else {
    retval = folder_icon;
  }

  return retval;
}

static GdkPixbuf *
create_meta_theme_pixbuf (ThemeThumbnailData *theme_thumbnail_data)
{
  GtkWidget *window;
  GtkWidget *preview;
  GtkWidget *vbox;
  GtkWidget *align;
  GtkWidget *box;
  GtkWidget *stock_button;
  GtkWidget *checkbox;
  GtkWidget *radio;

  GtkRequisition requisition;
  GtkAllocation allocation;
  GdkPixmap *pixmap;
  GdkVisual *visual;
  MetaFrameFlags flags;
  MetaTheme *theme;
  GdkPixbuf *pixbuf, *icon;
  int icon_width, icon_height;
  GdkRegion *region;

  g_object_set (gtk_settings_get_default (),
    "gtk-theme-name", (char *) theme_thumbnail_data->control_theme_name->data,
    "gtk-font-name", (char *) theme_thumbnail_data->application_font->data,
    "gtk-icon-theme-name", (char *) theme_thumbnail_data->icon_theme_name->data,
    "gtk-color-scheme", (char *) theme_thumbnail_data->gtk_color_scheme->data,
    NULL);

  theme = meta_theme_load ((char *) theme_thumbnail_data->wm_theme_name->data, NULL);
  if (theme == NULL)
    return NULL;

  /* Represent the icon theme */
  icon = create_folder_icon ((char *) theme_thumbnail_data->icon_theme_name->data);
  icon_width = gdk_pixbuf_get_width (icon);
  icon_height = gdk_pixbuf_get_height (icon);

  /* Create a fake window */
  flags = META_FRAME_ALLOWS_DELETE |
          META_FRAME_ALLOWS_MENU |
          META_FRAME_ALLOWS_MINIMIZE |
          META_FRAME_ALLOWS_MAXIMIZE |
          META_FRAME_ALLOWS_VERTICAL_RESIZE |
          META_FRAME_ALLOWS_HORIZONTAL_RESIZE |
          META_FRAME_HAS_FOCUS |
          META_FRAME_ALLOWS_SHADE |
          META_FRAME_ALLOWS_MOVE;

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  preview = meta_preview_new ();
  gtk_container_add (GTK_CONTAINER (window), preview);
  gtk_widget_realize (window);
  gtk_widget_realize (preview);
  vbox = gtk_vbox_new (FALSE, 6);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
  gtk_container_add (GTK_CONTAINER (preview), vbox);
  align = gtk_alignment_new (0, 0, 0.0, 0.0);
  gtk_box_pack_start (GTK_BOX (vbox), align, FALSE, FALSE, 0);
  stock_button = gtk_button_new_from_stock (GTK_STOCK_OPEN);
  gtk_container_add (GTK_CONTAINER (align), stock_button);
  box = gtk_hbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 0);
  checkbox = gtk_check_button_new ();
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbox), TRUE);
  gtk_box_pack_start (GTK_BOX (box), checkbox, FALSE, FALSE, 0);
  radio = gtk_radio_button_new (NULL);
  gtk_box_pack_start (GTK_BOX (box), radio, FALSE, FALSE, 0);

  gtk_widget_show_all (preview);
  gtk_widget_realize (stock_button);
  gtk_widget_realize (GTK_BIN (stock_button)->child);
  gtk_widget_realize (checkbox);
  gtk_widget_realize (radio);
  gtk_widget_map (stock_button);
  gtk_widget_map (GTK_BIN (stock_button)->child);
  gtk_widget_map (checkbox);
  gtk_widget_map (radio);

  meta_preview_set_frame_flags (META_PREVIEW (preview), flags);
  meta_preview_set_theme (META_PREVIEW (preview), theme);
  meta_preview_set_title (META_PREVIEW (preview), "");

  gtk_window_set_default_size (GTK_WINDOW (window), META_THUMBNAIL_SIZE, META_THUMBNAIL_SIZE);

  gtk_widget_size_request (window, &requisition);
  allocation.x = 0;
  allocation.y = 0;
  allocation.width = META_THUMBNAIL_SIZE;
  allocation.height = META_THUMBNAIL_SIZE;
  gtk_widget_size_allocate (window, &allocation);
  gtk_widget_size_request (window, &requisition);

  /* Create a pixmap */
  visual = gtk_widget_get_visual (window);
  pixmap = gdk_pixmap_new (NULL, META_THUMBNAIL_SIZE, META_THUMBNAIL_SIZE, visual->depth);
  gdk_drawable_set_colormap (GDK_DRAWABLE (pixmap), gtk_widget_get_colormap (window));

  /* Draw the window */
  gtk_widget_ensure_style (window);
  g_assert (window->style);
  g_assert (window->style->font_desc);

  fake_expose_widget (window, pixmap, NULL);
  fake_expose_widget (preview, pixmap, NULL);
  /* we call this again here because the preview sometimes draws into the area
   * of the contents, see http://bugzilla.gnome.org/show_bug.cgi?id=351389 */
  fake_expose_widget (window, pixmap, &vbox->allocation);
  fake_expose_widget (stock_button, pixmap, NULL);
  gtk_container_foreach (GTK_CONTAINER (GTK_BIN (GTK_BIN (stock_button)->child)->child),
                         hbox_foreach,
                         pixmap);
  fake_expose_widget (GTK_BIN (stock_button)->child, pixmap, NULL);
  fake_expose_widget (checkbox, pixmap, NULL);
  fake_expose_widget (radio, pixmap, NULL);

  pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, META_THUMBNAIL_SIZE, META_THUMBNAIL_SIZE);
  gdk_pixbuf_get_from_drawable (pixbuf, pixmap, NULL, 0, 0, 0, 0, META_THUMBNAIL_SIZE, META_THUMBNAIL_SIZE);

  /* Add the icon theme to the pixbuf */
  gdk_pixbuf_composite (icon, pixbuf,
                        vbox->allocation.x + vbox->allocation.width - icon_width - 5,
                        vbox->allocation.y + vbox->allocation.height - icon_height - 5,
                        icon_width, icon_height,
                        vbox->allocation.x + vbox->allocation.width - icon_width - 5,
                        vbox->allocation.y + vbox->allocation.height - icon_height - 5,
                        1.0, 1.0, GDK_INTERP_BILINEAR, 255);
  region = meta_preview_get_clip_region (META_PREVIEW (preview),
      META_THUMBNAIL_SIZE, META_THUMBNAIL_SIZE);
  pixbuf_apply_mask_region (pixbuf, region);
  gdk_region_destroy (region);

  g_object_unref (icon);
  gtk_widget_destroy (window);
  meta_theme_free (theme);

  return pixbuf;
}

static GdkPixbuf *
create_gtk_theme_pixbuf (ThemeThumbnailData *theme_thumbnail_data)
{
  GtkSettings *settings;
  GtkWidget *window, *vbox, *box, *stock_button, *checkbox, *radio;
  GtkRequisition requisition;
  GtkAllocation allocation;
  GdkVisual *visual;
  GdkPixmap *pixmap;
  GdkPixbuf *pixbuf, *retval;
  gint width, height;

  settings = gtk_settings_get_default ();
  g_object_set (settings, "gtk-theme-name", (char *) theme_thumbnail_data->control_theme_name->data,
			  "gtk-color-scheme", (char *) theme_thumbnail_data->gtk_color_scheme->data,
 			  NULL);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_add (GTK_CONTAINER (window), vbox);
  box = gtk_hbox_new (FALSE, 6);
  gtk_container_set_border_width (GTK_CONTAINER (box), 6);
  gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 0);
  stock_button = gtk_button_new_from_stock (GTK_STOCK_OPEN);
  gtk_box_pack_start (GTK_BOX (box), stock_button, FALSE, FALSE, 0);
  checkbox = gtk_check_button_new ();
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbox), TRUE);
  gtk_box_pack_start (GTK_BOX (box), checkbox, FALSE, FALSE, 0);
  radio = gtk_radio_button_new_from_widget (NULL);
  gtk_box_pack_start (GTK_BOX (box), radio, FALSE, FALSE, 0);

  gtk_widget_show_all (vbox);
  gtk_widget_realize (stock_button);
  gtk_widget_realize (GTK_BIN (stock_button)->child);
  gtk_widget_realize (checkbox);
  gtk_widget_realize (radio);
  gtk_widget_map (stock_button);
  gtk_widget_map (GTK_BIN (stock_button)->child);
  gtk_widget_map (checkbox);
  gtk_widget_map (radio);

  gtk_widget_size_request (window, &requisition);
  allocation.x = 0;
  allocation.y = 0;
  allocation.width = requisition.width;
  allocation.height = requisition.height;
  gtk_widget_size_allocate (window, &allocation);
  gtk_widget_size_request (window, &requisition);

  /* Draw the window */
  gtk_widget_ensure_style (window);
  g_assert (window->style);
  g_assert (window->style->font_desc);

  gtk_window_get_size (GTK_WINDOW (window), &width, &height);

  visual = gtk_widget_get_visual (window);
  pixmap = gdk_pixmap_new (NULL, width, height, visual->depth);
  gdk_drawable_set_colormap (GDK_DRAWABLE (pixmap), gtk_widget_get_colormap (window));

  fake_expose_widget (window, pixmap, NULL);
  fake_expose_widget (stock_button, pixmap, NULL);
  gtk_container_foreach (GTK_CONTAINER (GTK_BIN (GTK_BIN (stock_button)->child)->child),
       hbox_foreach,
       pixmap);
  fake_expose_widget (GTK_BIN (stock_button)->child, pixmap, NULL);
  fake_expose_widget (checkbox, pixmap, NULL);
  fake_expose_widget (radio, pixmap, NULL);

  pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height);
  gdk_pixbuf_get_from_drawable (pixbuf, pixmap, NULL, 0, 0, 0, 0, width, height);

  retval = gdk_pixbuf_scale_simple (pixbuf,
                                    GTK_THUMBNAIL_SIZE,
                                    (int) GTK_THUMBNAIL_SIZE * (((double) height) / ((double) width)),
                                    GDK_INTERP_BILINEAR);
  g_object_unref (pixbuf);
  gtk_widget_destroy (window);

  return retval;
}

static GdkPixbuf *
create_metacity_theme_pixbuf (ThemeThumbnailData *theme_thumbnail_data)
{
  GtkWidget *window, *preview, *dummy;
  MetaFrameFlags flags;
  MetaTheme *theme;
  GtkRequisition requisition;
  GtkAllocation allocation;
  GdkVisual *visual;
  GdkPixmap *pixmap;
  GdkPixbuf *pixbuf, *retval;
  GdkRegion *region;

  theme = meta_theme_load ((char *) theme_thumbnail_data->wm_theme_name->data, NULL);
  if (theme == NULL)
    return NULL;

  flags = META_FRAME_ALLOWS_DELETE |
          META_FRAME_ALLOWS_MENU |
          META_FRAME_ALLOWS_MINIMIZE |
          META_FRAME_ALLOWS_MAXIMIZE |
          META_FRAME_ALLOWS_VERTICAL_RESIZE |
          META_FRAME_ALLOWS_HORIZONTAL_RESIZE |
          META_FRAME_HAS_FOCUS |
          META_FRAME_ALLOWS_SHADE |
          META_FRAME_ALLOWS_MOVE;

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_default_size (GTK_WINDOW (window), (int) METACITY_THUMBNAIL_WIDTH * 1.2, (int) METACITY_THUMBNAIL_HEIGHT * 1.2);

  preview = meta_preview_new ();
  meta_preview_set_frame_flags (META_PREVIEW (preview), flags);
  meta_preview_set_theme (META_PREVIEW (preview), theme);
  meta_preview_set_title (META_PREVIEW (preview), "");
  gtk_container_add (GTK_CONTAINER (window), preview);

  dummy = gtk_label_new ("");
  gtk_container_add (GTK_CONTAINER (preview), dummy);

  gtk_widget_realize (window);
  gtk_widget_realize (preview);
  gtk_widget_realize (dummy);
  gtk_widget_show_all (preview);
  gtk_widget_map (dummy);

  gtk_widget_size_request (window, &requisition);
  allocation.x = 0;
  allocation.y = 0;
  allocation.width = (int) METACITY_THUMBNAIL_WIDTH * 1.2;
  allocation.height = (int) METACITY_THUMBNAIL_HEIGHT * 1.2;
  gtk_widget_size_allocate (window, &allocation);
  gtk_widget_size_request (window, &requisition);

  /* Draw the window */
  gtk_widget_ensure_style (window);
  g_assert (window->style);
  g_assert (window->style->font_desc);

  /* Create a pixmap */
  visual = gtk_widget_get_visual (window);
  pixmap = gdk_pixmap_new (NULL, (int) METACITY_THUMBNAIL_WIDTH * 1.2, (int) METACITY_THUMBNAIL_HEIGHT * 1.2, visual->depth);
  gdk_drawable_set_colormap (GDK_DRAWABLE (pixmap), gtk_widget_get_colormap (window));

  fake_expose_widget (window, pixmap, NULL);
  fake_expose_widget (preview, pixmap, NULL);
  fake_expose_widget (window, pixmap, &dummy->allocation);

  pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, (int) METACITY_THUMBNAIL_WIDTH * 1.2, (int) METACITY_THUMBNAIL_HEIGHT * 1.2);
  gdk_pixbuf_get_from_drawable (pixbuf, pixmap, NULL, 0, 0, 0, 0, (int) METACITY_THUMBNAIL_WIDTH * 1.2, (int) METACITY_THUMBNAIL_HEIGHT * 1.2);

  region = meta_preview_get_clip_region (META_PREVIEW (preview),
      METACITY_THUMBNAIL_WIDTH * 1.2, METACITY_THUMBNAIL_HEIGHT * 1.2);
  pixbuf_apply_mask_region (pixbuf, region);
  gdk_region_destroy (region);


  retval = gdk_pixbuf_scale_simple (pixbuf,
                                    METACITY_THUMBNAIL_WIDTH,
                                    METACITY_THUMBNAIL_HEIGHT,
                                    GDK_INTERP_BILINEAR);
  g_object_unref (pixbuf);

  gtk_widget_destroy (window);
  meta_theme_free (theme);
  return retval;
}

static GdkPixbuf *
create_icon_theme_pixbuf (ThemeThumbnailData *theme_thumbnail_data)
{
  return create_folder_icon ((char *) theme_thumbnail_data->icon_theme_name->data);
}


static void
handle_bytes (const gchar        *buffer,
              gint                bytes_read,
              ThemeThumbnailData *theme_thumbnail_data)
{
  const gchar *ptr;
  ptr = buffer;

  while (bytes_read > 0)
  {
    char *nil;

    switch (theme_thumbnail_data->status)
    {
      case READY_FOR_THEME:
        theme_thumbnail_data->status = READING_TYPE;
        /* fall through */
      case READING_TYPE:
        nil = memchr (ptr, '\000', bytes_read);
        if (nil == NULL)
        {
          g_byte_array_append (theme_thumbnail_data->type, ptr, bytes_read);
          bytes_read = 0;
        }
        else
        {
          g_byte_array_append (theme_thumbnail_data->type, ptr, nil - ptr + 1);
          bytes_read -= (nil - ptr + 1);
          ptr = nil + 1;
          theme_thumbnail_data->status = READING_CONTROL_THEME_NAME;
        }
        break;

      case READING_CONTROL_THEME_NAME:
        nil = memchr (ptr, '\000', bytes_read);
        if (nil == NULL)
        {
          g_byte_array_append (theme_thumbnail_data->control_theme_name, ptr, bytes_read);
          bytes_read = 0;
        }
        else
        {
          g_byte_array_append (theme_thumbnail_data->control_theme_name, ptr, nil - ptr + 1);
          bytes_read -= (nil - ptr + 1);
          ptr = nil + 1;
          theme_thumbnail_data->status = READING_GTK_COLOR_SCHEME;
        }
        break;

      case READING_GTK_COLOR_SCHEME:
        nil = memchr (ptr, '\000', bytes_read);
        if (nil == NULL)
        {
          g_byte_array_append (theme_thumbnail_data->gtk_color_scheme, ptr, bytes_read);
          bytes_read = 0;
        }
        else
        {
          g_byte_array_append (theme_thumbnail_data->gtk_color_scheme, ptr, nil - ptr + 1);
          bytes_read -= (nil - ptr + 1);
          ptr = nil + 1;
          theme_thumbnail_data->status = READING_WM_THEME_NAME;
        }
        break;

      case READING_WM_THEME_NAME:
        nil = memchr (ptr, '\000', bytes_read);
        if (nil == NULL)
        {
          g_byte_array_append (theme_thumbnail_data->wm_theme_name, ptr, bytes_read);
          bytes_read = 0;
        }
        else
        {
          g_byte_array_append (theme_thumbnail_data->wm_theme_name, ptr, nil - ptr + 1);
          bytes_read -= (nil - ptr + 1);
          ptr = nil + 1;
          theme_thumbnail_data->status = READING_ICON_THEME_NAME;
        }
        break;

      case READING_ICON_THEME_NAME:
        nil = memchr (ptr, '\000', bytes_read);
        if (nil == NULL)
        {
          g_byte_array_append (theme_thumbnail_data->icon_theme_name, ptr, bytes_read);
          bytes_read = 0;
        }
        else
        {
          g_byte_array_append (theme_thumbnail_data->icon_theme_name, ptr, nil - ptr + 1);
          bytes_read -= (nil - ptr + 1);
          ptr = nil + 1;
          theme_thumbnail_data->status = READING_APPLICATION_FONT;
        }
        break;

      case READING_APPLICATION_FONT:
        nil = memchr (ptr, '\000', bytes_read);
        if (nil == NULL)
        {
          g_byte_array_append (theme_thumbnail_data->application_font, ptr, bytes_read);
          bytes_read = 0;
        }
        else
        {
          g_byte_array_append (theme_thumbnail_data->application_font, ptr, nil - ptr + 1);
          bytes_read -= (nil - ptr + 1);
          ptr = nil + 1;
          theme_thumbnail_data->status = WRITING_PIXBUF_DATA;
        }
        break;

      default:
        g_assert_not_reached ();
    }
  }
}

static gboolean
message_from_capplet (GIOChannel   *source,
                      GIOCondition  condition,
                      gpointer      data)
{
  gchar buffer[1024];
  GIOStatus status;
  gsize bytes_read;
  GdkPixbuf *pixbuf;
  gint i, rowstride;
  guchar *pixels;
  ThemeThumbnailData *theme_thumbnail_data;

  theme_thumbnail_data = (ThemeThumbnailData *) data;
  status = g_io_channel_read_chars (source,
                                    buffer,
                                    1024,
                                    &bytes_read,
                                    NULL);

  switch (status)
  {
    case G_IO_STATUS_NORMAL:
      handle_bytes (buffer, bytes_read, theme_thumbnail_data);

      if (theme_thumbnail_data->status == WRITING_PIXBUF_DATA)
      {
        char *type;
        gint width, height;
        type = (char *) theme_thumbnail_data->type->data;

        if (!strcmp (type, THUMBNAIL_TYPE_META))
          pixbuf = create_meta_theme_pixbuf (theme_thumbnail_data);
        else if (!strcmp (type, THUMBNAIL_TYPE_GTK))
          pixbuf = create_gtk_theme_pixbuf (theme_thumbnail_data);
        else if (!strcmp (type, THUMBNAIL_TYPE_METACITY))
          pixbuf = create_metacity_theme_pixbuf (theme_thumbnail_data);
        else if (!strcmp (type, THUMBNAIL_TYPE_ICON))
          pixbuf = create_icon_theme_pixbuf (theme_thumbnail_data);
        else
          g_assert_not_reached ();

        if (pixbuf == NULL) {
          width = height = 0;
        } else {
          width = gdk_pixbuf_get_width (pixbuf);
          height = gdk_pixbuf_get_height (pixbuf);
          rowstride = gdk_pixbuf_get_rowstride (pixbuf);
          pixels = gdk_pixbuf_get_pixels (pixbuf);
        }

        /* Write the pixbuf's size */
        write (pipe_from_factory_fd[1], &width, sizeof (width));
        write (pipe_from_factory_fd[1], &height, sizeof (height));

        for (i = 0; i < height; i++)
        {
          write (pipe_from_factory_fd[1], pixels + rowstride * i, width * gdk_pixbuf_get_n_channels (pixbuf));
        }

        if (pixbuf)
          g_object_unref (pixbuf);
        g_byte_array_set_size (theme_thumbnail_data->type, 0);
        g_byte_array_set_size (theme_thumbnail_data->control_theme_name, 0);
        g_byte_array_set_size (theme_thumbnail_data->gtk_color_scheme, 0);
        g_byte_array_set_size (theme_thumbnail_data->wm_theme_name, 0);
        g_byte_array_set_size (theme_thumbnail_data->icon_theme_name, 0);
        g_byte_array_set_size (theme_thumbnail_data->application_font, 0);
        theme_thumbnail_data->status = READY_FOR_THEME;
      }
      return TRUE;

    case G_IO_STATUS_AGAIN:
      return TRUE;

    case G_IO_STATUS_EOF:
    case G_IO_STATUS_ERROR:
      _exit (0);

    default:
      g_assert_not_reached ();
    }

  return TRUE;
}

static void
generate_next_in_queue (void)
{
  ThemeQueueItem *item;

  if (theme_queue == NULL)
    return;

  item = theme_queue->data;
  theme_queue = g_list_delete_link (theme_queue, g_list_first (theme_queue));

  if (!strcmp (item->thumbnail_type, THUMBNAIL_TYPE_META))
    generate_meta_theme_thumbnail_async ((GnomeThemeMetaInfo *) item->theme_info,
                                         item->func,
                                         item->user_data,
                                         item->destroy);
  else if (!strcmp (item->thumbnail_type, THUMBNAIL_TYPE_GTK))
    generate_gtk_theme_thumbnail_async ((GnomeThemeInfo *) item->theme_info,
                                        item->func,
                                        item->user_data,
                                        item->destroy);
  else if (!strcmp (item->thumbnail_type, THUMBNAIL_TYPE_METACITY))
    generate_metacity_theme_thumbnail_async ((GnomeThemeInfo *) item->theme_info,
                                             item->func,
                                             item->user_data,
                                             item->destroy);
  else if (!strcmp (item->thumbnail_type, THUMBNAIL_TYPE_ICON))
    generate_icon_theme_thumbnail_async ((GnomeThemeIconInfo *) item->theme_info,
                                         item->func,
                                         item->user_data,
                                         item->destroy);

  g_free (item);
}

static gboolean
message_from_child (GIOChannel   *source,
                    GIOCondition  condition,
                    gpointer      data)
{
  gchar buffer[1024];
  GIOStatus status;
  gsize bytes_read;

  if (async_data.set == FALSE)
    return TRUE;

  if (condition == G_IO_HUP)
    return FALSE;

  status = g_io_channel_read_chars (source,
                                    buffer,
                                    1024,
                                    &bytes_read,
                                    NULL);
  switch (status)
  {
    case G_IO_STATUS_NORMAL:
      g_byte_array_append (async_data.data, (guchar *) buffer, bytes_read);

      if (async_data.thumbnail_width == -1 && async_data.data->len >= 2 * sizeof (gint))
      {
        async_data.thumbnail_width = *((gint *) async_data.data->data);
        async_data.thumbnail_height = *(((gint *) async_data.data->data) + 1);
        g_byte_array_remove_range (async_data.data, 0, 2 * sizeof (gint));
      }

      if (async_data.thumbnail_width >= 0 && async_data.data->len == async_data.thumbnail_width * async_data.thumbnail_height * 4)
      {
        GdkPixbuf *pixbuf = NULL;

        if (async_data.thumbnail_width > 0) {
          gchar *pixels;
          gint i, rowstride;

          pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, async_data.thumbnail_width, async_data.thumbnail_height);
          pixels = (gchar *) gdk_pixbuf_get_pixels (pixbuf);
          rowstride = gdk_pixbuf_get_rowstride (pixbuf);

          for (i = 0; i < async_data.thumbnail_height; ++i)
            memcpy (pixels + rowstride * i, async_data.data->data + 4 * async_data.thumbnail_width * i, async_data.thumbnail_width * 4);
        }

        /* callback function needs to ref the pixbuf if it wants to keep it */
        (* async_data.func) (pixbuf, async_data.theme_name, async_data.user_data);

        if (async_data.destroy)
          (* async_data.destroy) (async_data.user_data);

        if (pixbuf)
          g_object_unref (pixbuf);

        /* Clean up async_data */
        g_free (async_data.theme_name);
        g_source_remove (async_data.watch_id);
        g_io_channel_unref (async_data.channel);

        /* reset async_data */
        async_data.thumbnail_width = -1;
        async_data.thumbnail_height = -1;
        async_data.theme_name = NULL;
        async_data.channel = NULL;
        async_data.func = NULL;
        async_data.user_data = NULL;
        async_data.destroy = NULL;
        async_data.set = FALSE;
        g_byte_array_set_size (async_data.data, 0);

        generate_next_in_queue ();
      }
      return TRUE;

    case G_IO_STATUS_AGAIN:
      return TRUE;

    case G_IO_STATUS_EOF:
    case G_IO_STATUS_ERROR:
      return FALSE;

    default:
      g_assert_not_reached ();
  }

  return TRUE;
}

static void
send_thumbnail_request (gchar *thumbnail_type,
                        gchar *gtk_theme_name,
                        gchar *gtk_color_scheme,
                        gchar *metacity_theme_name,
                        gchar *icon_theme_name,
                        gchar *application_font)
{
  write (pipe_to_factory_fd[1], thumbnail_type, strlen (thumbnail_type) + 1);

  if (gtk_theme_name)
    write (pipe_to_factory_fd[1], gtk_theme_name, strlen (gtk_theme_name) + 1);
  else
    write (pipe_to_factory_fd[1], "", 1);

  if (gtk_color_scheme)
    write (pipe_to_factory_fd[1], gtk_color_scheme, strlen (gtk_color_scheme) + 1);
  else
    write (pipe_to_factory_fd[1], "", 1);

  if (metacity_theme_name)
    write (pipe_to_factory_fd[1], metacity_theme_name, strlen (metacity_theme_name) + 1);
  else
    write (pipe_to_factory_fd[1], "", 1);

  if (icon_theme_name)
    write (pipe_to_factory_fd[1], icon_theme_name, strlen (icon_theme_name) + 1);
  else
    write (pipe_to_factory_fd[1], "", 1);

  if (application_font)
    write (pipe_to_factory_fd[1], application_font, strlen (application_font) + 1);
  else
     write (pipe_to_factory_fd[1], "Sans 10", strlen ("Sans 10") + 1);

}

static GdkPixbuf *
read_pixbuf (void)
{
  gint bytes_read, i, j = 0;
  gint size[2];
  GdkPixbuf *pixbuf;
  gint rowstride;
  guchar *pixels;

  do
  {
    bytes_read = read (pipe_from_factory_fd[0], ((guint8*) size) + j, 2 * sizeof (gint));
    if (bytes_read == 0)
      goto eof;
    j += bytes_read;
  }
  while (j < 2 * sizeof (gint));

  if (size[0] <= 0 || size[1] <= 0)
    return NULL;

  pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, size[0], size[1]);
  rowstride = gdk_pixbuf_get_rowstride (pixbuf);
  pixels = gdk_pixbuf_get_pixels (pixbuf);

  for (i = 0; i < size[1]; i++)
  {
    j = 0;

    do
    {
      bytes_read = read (pipe_from_factory_fd[0], pixels + rowstride * i + j, size[0] * gdk_pixbuf_get_n_channels (pixbuf) - j);

      if (bytes_read > 0)
        j += bytes_read;
      else if (bytes_read == 0)
      {
        g_object_unref (pixbuf);
        goto eof;
      }
    }
    while (j < size[0] * gdk_pixbuf_get_n_channels (pixbuf));
  }

  return pixbuf;

eof:
  g_warning ("Received EOF while reading thumbnail");
  close (pipe_to_factory_fd[1]);
  pipe_to_factory_fd[1] = 0;
  close (pipe_from_factory_fd[0]);
  pipe_from_factory_fd[0] = 0;
  return NULL;
}

static GdkPixbuf *
generate_theme_thumbnail (gchar *thumbnail_type,
                          gchar *gtk_theme_name,
                          gchar *gtk_color_scheme,
                          gchar *metacity_theme_name,
                          gchar *icon_theme_name,
                          gchar *application_font)
{
  if (async_data.set || !pipe_to_factory_fd[1] || !pipe_from_factory_fd[0])
    return NULL;

  send_thumbnail_request (thumbnail_type,
                          gtk_theme_name,
                          gtk_color_scheme,
                          metacity_theme_name,
                          icon_theme_name,
                          application_font);

  return read_pixbuf ();
}

GdkPixbuf *
generate_meta_theme_thumbnail (GnomeThemeMetaInfo *theme_info)
{
  return generate_theme_thumbnail (THUMBNAIL_TYPE_META,
                                   theme_info->gtk_theme_name,
                                   theme_info->gtk_color_scheme,
                                   theme_info->metacity_theme_name,
                                   theme_info->icon_theme_name,
                                   theme_info->application_font);
}

GdkPixbuf *
generate_gtk_theme_thumbnail (GnomeThemeInfo *theme_info)
{
  gchar *scheme;

  scheme = gtkrc_get_color_scheme_for_theme (theme_info->name);

  return generate_theme_thumbnail (THUMBNAIL_TYPE_GTK,
                                   theme_info->name,
                                   scheme,
                                   NULL,
                                   NULL,
                                   NULL);
  g_free (scheme);
}

GdkPixbuf *
generate_metacity_theme_thumbnail (GnomeThemeInfo *theme_info)
{
  return generate_theme_thumbnail (THUMBNAIL_TYPE_METACITY,
                                   NULL,
                                   NULL,
                                   theme_info->name,
                                   NULL,
                                   NULL);
}

GdkPixbuf *
generate_icon_theme_thumbnail (GnomeThemeIconInfo *theme_info)
{
  return generate_theme_thumbnail (THUMBNAIL_TYPE_ICON,
                                   NULL,
                                   NULL,
                                   NULL,
                                   theme_info->name,
                                   NULL);
}

static void
generate_theme_thumbnail_async (gpointer            theme_info,
                                gchar              *theme_name,
                                gchar              *thumbnail_type,
                                gchar              *gtk_theme_name,
                                gchar              *gtk_color_scheme,
                                gchar              *metacity_theme_name,
                                gchar              *icon_theme_name,
                                gchar              *application_font,
                                ThemeThumbnailFunc  func,
                                gpointer            user_data,
                                GDestroyNotify      destroy)
{
  if (async_data.set)
  {
    ThemeQueueItem *item;

    item = g_new0 (ThemeQueueItem, 1);
    item->thumbnail_type = thumbnail_type;
    item->theme_info = theme_info;
    item->func = func;
    item->user_data = user_data;
    item->destroy = destroy;

    theme_queue = g_list_append (theme_queue, item);
    return;
  }

  if (!pipe_to_factory_fd[1] || !pipe_from_factory_fd[0])
  {
    (* func) (NULL, theme_name, user_data);

    if (destroy)
      (* destroy) (user_data);

    return;
  }

  if (async_data.channel == NULL)
  {
    async_data.channel = g_io_channel_unix_new (pipe_from_factory_fd[0]);
    g_io_channel_set_flags (async_data.channel, g_io_channel_get_flags (async_data.channel) | G_IO_FLAG_NONBLOCK, NULL);
    g_io_channel_set_encoding (async_data.channel, NULL, NULL);
    async_data.watch_id = g_io_add_watch (async_data.channel, G_IO_IN | G_IO_HUP, message_from_child, NULL);
  }

  async_data.set = TRUE;
  async_data.thumbnail_width = -1;
  async_data.thumbnail_height = -1;
  async_data.theme_name = g_strdup (theme_name);
  async_data.func = func;
  async_data.user_data = user_data;
  async_data.destroy = destroy;

  send_thumbnail_request (thumbnail_type,
                          gtk_theme_name,
                          gtk_color_scheme,
                          metacity_theme_name,
                          icon_theme_name,
                          application_font);
}

void
generate_meta_theme_thumbnail_async (GnomeThemeMetaInfo *theme_info,
                                     ThemeThumbnailFunc  func,
                                     gpointer            user_data,
                                     GDestroyNotify      destroy)
{
  generate_theme_thumbnail_async (theme_info,
                                         theme_info->name,
                                         THUMBNAIL_TYPE_META,
                                         theme_info->gtk_theme_name,
                                         theme_info->gtk_color_scheme,
                                         theme_info->metacity_theme_name,
                                         theme_info->icon_theme_name,
                                         theme_info->application_font,
                                         func, user_data, destroy);
}

void
generate_gtk_theme_thumbnail_async (GnomeThemeInfo *theme_info,
                                    ThemeThumbnailFunc  func,
                                    gpointer            user_data,
                                    GDestroyNotify      destroy)
{
  gchar *scheme;

  scheme = gtkrc_get_color_scheme_for_theme (theme_info->name);

  generate_theme_thumbnail_async (theme_info,
                                  theme_info->name,
                                  THUMBNAIL_TYPE_GTK,
                                  theme_info->name,
                                  scheme,
                                  NULL,
                                  NULL,
                                  NULL,
                                  func, user_data, destroy);
  g_free (scheme);
}

void
generate_metacity_theme_thumbnail_async (GnomeThemeInfo *theme_info,
                                         ThemeThumbnailFunc  func,
                                         gpointer            user_data,
                                         GDestroyNotify      destroy)
{
  generate_theme_thumbnail_async (theme_info,
                                         theme_info->name,
                                         THUMBNAIL_TYPE_METACITY,
                                         NULL,
                                         NULL,
                                         theme_info->name,
                                         NULL,
                                         NULL,
                                         func, user_data, destroy);
}

void
generate_icon_theme_thumbnail_async (GnomeThemeIconInfo *theme_info,
                                     ThemeThumbnailFunc  func,
                                     gpointer            user_data,
                                     GDestroyNotify      destroy)
{
  generate_theme_thumbnail_async (theme_info,
                                         theme_info->name,
                                         THUMBNAIL_TYPE_ICON,
                                         NULL,
                                         NULL,
                                         NULL,
                                         theme_info->name,
                                         NULL,
                                         func, user_data, destroy);
}

void
theme_thumbnail_factory_init (int argc, char *argv[])
{
#ifndef __APPLE__
  gint child_pid;
#endif

  pipe (pipe_to_factory_fd);
  pipe (pipe_from_factory_fd);

/* Apple's CoreFoundation classes must not be used from forked
 * processes. Since freetype (and thus GTK) uses them, we simply
 * disable the thumbnailer on MacOS for now. That means no thumbs
 * until the thumbnailing process is rewritten, but at least we won't
 * make apps crash. */
#ifndef __APPLE__
  child_pid = fork ();
  if (child_pid == 0)
  {
    ThemeThumbnailData data;
    GIOChannel *channel;

    /* Child */
    gtk_init (&argc, &argv);

    close (pipe_to_factory_fd[1]);
    pipe_to_factory_fd[1] = 0;
    close (pipe_from_factory_fd[0]);
    pipe_from_factory_fd[0] = 0;

    data.status = READY_FOR_THEME;
    data.type = g_byte_array_new ();
    data.control_theme_name = g_byte_array_new ();
    data.gtk_color_scheme = g_byte_array_new ();
    data.wm_theme_name = g_byte_array_new ();
    data.icon_theme_name = g_byte_array_new ();
    data.application_font = g_byte_array_new ();

    channel = g_io_channel_unix_new (pipe_to_factory_fd[0]);
    g_io_channel_set_flags (channel, g_io_channel_get_flags (channel) |
          G_IO_FLAG_NONBLOCK, NULL);
    g_io_channel_set_encoding (channel, NULL, NULL);
    g_io_add_watch (channel, G_IO_IN | G_IO_HUP, message_from_capplet, &data);
    g_io_channel_unref (channel);

    gtk_main ();
    _exit (0);
  }

  g_assert (child_pid > 0);

  /* Parent */
  close (pipe_to_factory_fd[0]);
  close (pipe_from_factory_fd[1]);
#endif /* __APPLE__ */

  async_data.set = FALSE;
  async_data.theme_name = NULL;
  async_data.data = g_byte_array_new ();
}
