summaryrefslogtreecommitdiff
path: root/capplets/common/theme-thumbnail.c
diff options
context:
space:
mode:
Diffstat (limited to 'capplets/common/theme-thumbnail.c')
-rw-r--r--capplets/common/theme-thumbnail.c1144
1 files changed, 1144 insertions, 0 deletions
diff --git a/capplets/common/theme-thumbnail.c b/capplets/common/theme-thumbnail.c
new file mode 100644
index 00000000..4afa4454
--- /dev/null
+++ b/capplets/common/theme-thumbnail.c
@@ -0,0 +1,1144 @@
+#include <config.h>
+#include <unistd.h>
+#include <string.h>
+#include <marco-private/util.h>
+#include <marco-private/theme.h>
+#include <marco-private/theme-parser.h>
+#include <marco-private/preview-widget.h>
+#include <signal.h>
+#include <errno.h>
+#include <math.h>
+
+/* We have to #undef this as marco #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_MARCO "marco"
+#define THUMBNAIL_TYPE_ICON "icon"
+
+#define META_THUMBNAIL_SIZE 128
+#define GTK_THUMBNAIL_SIZE 96
+#define MARCO_THUMBNAIL_WIDTH 120
+#define MARCO_THUMBNAIL_HEIGHT 60
+
+/* This draw the thumbnail of gtk
+ */
+static GdkPixmap* draw_window_on_pixbuf(GtkWidget* widget)
+{
+ GdkVisual* visual;
+ GdkPixmap* pixmap;
+ GtkStyle* style;
+ GdkScreen* screen = gdk_screen_get_default();
+ GdkWindow* window;
+ gint width, height;
+
+ gtk_widget_ensure_style(widget);
+
+ style = gtk_widget_get_style(widget);
+
+ g_assert(style);
+ g_assert(style->font_desc);
+
+ gtk_window_get_size(GTK_WINDOW(widget), &width, &height);
+
+ visual = gtk_widget_get_visual(widget);
+ pixmap = gdk_pixmap_new(NULL, width, height, visual->depth);
+ gdk_drawable_set_colormap(GDK_DRAWABLE(pixmap), gtk_widget_get_colormap(widget));
+
+ window = gtk_widget_get_window(widget);
+
+ /* This is a hack for the default resize grip on Ubuntu.
+ * Once again, thank you Ubuntu.
+ *
+ * We need to add a --enable-ubuntu for this.
+ */
+ #ifdef UBUNTU
+ gtk_window_set_has_resize_grip(GTK_WINDOW(widget), FALSE);
+ #endif
+
+ gdk_window_redirect_to_drawable(window, pixmap, 0, 0, 0, 0, width, height);
+ gdk_window_set_override_redirect(window, TRUE);
+ gtk_window_move(GTK_WINDOW(widget), gdk_screen_get_width(screen), gdk_screen_get_height(screen));
+ gtk_widget_show(widget);
+
+ gdk_window_process_updates(window, TRUE);
+ gtk_widget_hide(widget);
+
+ return pixmap;
+}
+
+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;
+ GtkIconInfo *folder_icon_info;
+ gchar *example_icon_name;
+ const gchar *icon_names[5];
+ gint i;
+
+ icon_theme = gtk_icon_theme_new ();
+ gtk_icon_theme_set_custom_theme (icon_theme, icon_theme_name);
+
+ i = 0;
+ /* 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)
+ icon_names[i++] = example_icon_name;
+ icon_names[i++] = "x-directory-normal";
+ icon_names[i++] = "mate-fs-directory";
+ icon_names[i++] = "folder";
+ icon_names[i++] = NULL;
+
+ folder_icon_info = gtk_icon_theme_choose_icon (icon_theme, icon_names, 48, GTK_ICON_LOOKUP_FORCE_SIZE);
+ if (folder_icon_info != NULL)
+ {
+ folder_icon = gtk_icon_info_load_icon (folder_icon_info, NULL);
+ gtk_icon_info_free (folder_icon_info);
+ }
+
+ g_object_unref (icon_theme);
+ g_free (example_icon_name);
+
+ /* 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);
+ }
+
+ return folder_icon;
+}
+
+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;
+ GtkAllocation vbox_allocation;
+ GdkPixmap *pixmap;
+ 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);
+
+ 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);
+
+ pixmap = draw_window_on_pixbuf (window);
+
+ 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);
+
+ gtk_widget_get_allocation (vbox, &vbox_allocation);
+
+ /* 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);
+ g_object_unref (pixmap);
+
+ 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;
+ 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_get_child (GTK_BIN (stock_button)));
+ gtk_widget_realize (checkbox);
+ gtk_widget_realize (radio);
+ gtk_widget_map (stock_button);
+ gtk_widget_map (gtk_bin_get_child (GTK_BIN (stock_button)));
+ 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);
+
+ gtk_window_get_size (GTK_WINDOW (window), &width, &height);
+
+ pixmap = draw_window_on_pixbuf (window);
+
+ 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);
+ g_object_unref (pixmap);
+
+ return retval;
+}
+
+static GdkPixbuf *
+create_marco_theme_pixbuf (ThemeThumbnailData *theme_thumbnail_data)
+{
+ GtkWidget *window, *preview, *dummy;
+ MetaFrameFlags flags;
+ MetaTheme *theme;
+ GtkRequisition requisition;
+ GtkAllocation allocation;
+ 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) MARCO_THUMBNAIL_WIDTH * 1.2, (int) MARCO_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) MARCO_THUMBNAIL_WIDTH * 1.2;
+ allocation.height = (int) MARCO_THUMBNAIL_HEIGHT * 1.2;
+ gtk_widget_size_allocate (window, &allocation);
+ gtk_widget_size_request (window, &requisition);
+
+ pixmap = draw_window_on_pixbuf (window);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, (int) MARCO_THUMBNAIL_WIDTH * 1.2, (int) MARCO_THUMBNAIL_HEIGHT * 1.2);
+ gdk_pixbuf_get_from_drawable (pixbuf, pixmap, NULL, 0, 0, 0, 0, (int) MARCO_THUMBNAIL_WIDTH * 1.2, (int) MARCO_THUMBNAIL_HEIGHT * 1.2);
+
+ region = meta_preview_get_clip_region (META_PREVIEW (preview),
+ MARCO_THUMBNAIL_WIDTH * 1.2, MARCO_THUMBNAIL_HEIGHT * 1.2);
+ pixbuf_apply_mask_region (pixbuf, region);
+ gdk_region_destroy (region);
+
+
+ retval = gdk_pixbuf_scale_simple (pixbuf,
+ MARCO_THUMBNAIL_WIDTH,
+ MARCO_THUMBNAIL_HEIGHT,
+ GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+
+ gtk_widget_destroy (window);
+ meta_theme_free (theme);
+ g_object_unref (pixmap);
+
+ 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;
+ 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)
+ {
+ GdkPixbuf *pixbuf = NULL;
+ gint i, rowstride;
+ guchar *pixels;
+ gint width, height;
+ const gchar *type = (const gchar *) 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_MARCO))
+ pixbuf = create_marco_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 = rowstride = 0;
+ pixels = NULL;
+ } 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 ((MateThemeMetaInfo *) item->theme_info,
+ item->func,
+ item->user_data,
+ item->destroy);
+ else if (!strcmp (item->thumbnail_type, THUMBNAIL_TYPE_GTK))
+ generate_gtk_theme_thumbnail_async ((MateThemeInfo *) item->theme_info,
+ item->func,
+ item->user_data,
+ item->destroy);
+ else if (!strcmp (item->thumbnail_type, THUMBNAIL_TYPE_MARCO))
+ generate_marco_theme_thumbnail_async ((MateThemeInfo *) item->theme_info,
+ item->func,
+ item->user_data,
+ item->destroy);
+ else if (!strcmp (item->thumbnail_type, THUMBNAIL_TYPE_ICON))
+ generate_icon_theme_thumbnail_async ((MateThemeIconInfo *) 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 *marco_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 (marco_theme_name)
+ write (pipe_to_factory_fd[1], marco_theme_name, strlen (marco_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 *marco_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,
+ marco_theme_name,
+ icon_theme_name,
+ application_font);
+
+ return read_pixbuf ();
+}
+
+GdkPixbuf *
+generate_meta_theme_thumbnail (MateThemeMetaInfo *theme_info)
+{
+ return generate_theme_thumbnail (THUMBNAIL_TYPE_META,
+ theme_info->gtk_theme_name,
+ theme_info->gtk_color_scheme,
+ theme_info->marco_theme_name,
+ theme_info->icon_theme_name,
+ theme_info->application_font);
+}
+
+GdkPixbuf *
+generate_gtk_theme_thumbnail (MateThemeInfo *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_marco_theme_thumbnail (MateThemeInfo *theme_info)
+{
+ return generate_theme_thumbnail (THUMBNAIL_TYPE_MARCO,
+ NULL,
+ NULL,
+ theme_info->name,
+ NULL,
+ NULL);
+}
+
+GdkPixbuf *
+generate_icon_theme_thumbnail (MateThemeIconInfo *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* marco_theme_name, gchar* icon_theme_name, gchar* application_font, ThemeThumbnailFunc func, gpointer user_data, GDestroyNotify destroy)
+{
+ if (async_data.set)
+ {
+ ThemeQueueItem* 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, marco_theme_name, icon_theme_name, application_font);
+}
+
+void
+generate_meta_theme_thumbnail_async (MateThemeMetaInfo *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->marco_theme_name,
+ theme_info->icon_theme_name,
+ theme_info->application_font,
+ func, user_data, destroy);
+}
+
+void generate_gtk_theme_thumbnail_async (MateThemeInfo* theme_info, ThemeThumbnailFunc func, gpointer user_data, GDestroyNotify destroy)
+{
+ gchar* 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_marco_theme_thumbnail_async (MateThemeInfo *theme_info,
+ ThemeThumbnailFunc func,
+ gpointer user_data,
+ GDestroyNotify destroy)
+{
+ generate_theme_thumbnail_async (theme_info,
+ theme_info->name,
+ THUMBNAIL_TYPE_MARCO,
+ NULL,
+ NULL,
+ theme_info->name,
+ NULL,
+ NULL,
+ func, user_data, destroy);
+}
+
+void
+generate_icon_theme_thumbnail_async (MateThemeIconInfo *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 ();
+}