gnome-control-center/capplets/common/theme-thumbnail.c
Jens Granseuer 10e93c23ad unref the thumbnail image after passing it to the callback. Callers will
2008-04-20  Jens Granseuer  <jensgr@gmx.net>

	* theme-thumbnail.c: (message_from_child): unref the thumbnail image
	after passing it to the callback. Callers will have to ref it if they
	want to keep it until after the callback has returned

2008-04-20  Jens Granseuer  <jensgr@gmx.net>

	* appearance-style.c: (update_thumbnail_in_treeview):
	* appearance-themes.c: (theme_thumbnail_update),
	(theme_thumbnail_generate): adapt to modified refcounting in the
	thumbnailer; thumbnails don't need to be unref'ed unless we explicitly
	retain a reference

svn path=/trunk/; revision=8661
2008-04-20 10:29:49 +00:00

1244 lines
40 KiB
C

#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 <libgnomeui/gnome-icon-theme.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 ();
}