/* * Copyright © 2003 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of Red Hat not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. Red Hat makes no representations about the * suitability of this software for any purpose. It is provided "as is" * without express or implied warranty. * * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Author: Owen Taylor, Red Hat, Inc. */ #include "config.h" #include #include #include #include #include #include #include #include #include "themefile.h" static void theme_rectangle_union (ThemeRectangle *src1, ThemeRectangle *src2, ThemeRectangle *dest) { int dest_x, dest_y; dest_x = MIN (src1->x, src2->x); dest_y = MIN (src1->y, src2->y); dest->width = MAX (src1->x + src1->width, src2->x + src2->width) - dest_x; dest->height = MAX (src1->y + src1->height, src2->y + src2->height) - dest_y; dest->x = dest_x; dest->y = dest_y; } static ThemeImage * theme_source_find_image (ThemeSource *source, int use) { GSList *tmp_list; for (tmp_list = source->images; tmp_list; tmp_list = tmp_list->next) { ThemeImage *image = tmp_list->data; if (image->use == use) return image; } return NULL; } static void theme_source_location_start (ThemeSource *source, ThemeLocation *location, int *start_x, int *start_y) { *start_x = (source->margin + location->column * (source->gridsize + source->spacing)); *start_y = (source->margin + location->row * (source->gridsize + source->spacing)); } static gboolean theme_source_location_bounds (ThemeSource *source, ThemeLocation *location, int frame_index, int threshold, gboolean location_relative, ThemeRectangle *out) { ThemeImage *image = theme_source_find_image (source, frame_index); int start_x, start_y; int i, j; const guchar *pixels; int rowstride; int n_channels; theme_source_location_start (source, location, &start_x, &start_y); rowstride = gdk_pixbuf_get_rowstride (image->image); n_channels = gdk_pixbuf_get_n_channels (image->image); if (n_channels == 3) { out->x = start_x; out->y = start_x; out->width = source->gridsize; out->height = source->gridsize; return TRUE; } else { int min_x = 0, max_x = 0, min_y = 0, max_y = 0; gboolean found = FALSE; pixels = gdk_pixbuf_get_pixels (image->image) + start_y * rowstride + start_x * 4; for (j = 0; j < source->gridsize; j++) { for (i = 0; i < source->gridsize; i++) { if (pixels[4*i + 3] > threshold) { if (found) { if (start_x + i < min_x) min_x = start_x + i; if (start_x + i >= max_x) max_x = start_x + i + 1; if (start_y + j < min_y) min_y = start_y + j; if (start_y + j >= max_y) max_y = start_y + j + 1; } else { min_x = start_x + i; max_x = start_x + i + 1; min_y = start_y + i; max_y = start_y + i + 1; found = TRUE; } } } pixels += rowstride; } if (found) { if (out) { out->x = location_relative ? min_x - start_x : min_x; out->y = location_relative ? min_y - start_y : min_y; out->width = max_x - min_x; out->height = max_y - min_y; } return TRUE; } else return FALSE; } } static GdkPixbuf * theme_source_get_pixbuf (ThemeSource *source, ThemeLocation *location, int frame_index, ThemeRectangle *bounds) { ThemeImage *image = theme_source_find_image (source, frame_index); GdkPixbuf *result, *tmp_pixbuf; tmp_pixbuf = gdk_pixbuf_new_subpixbuf (image->image, bounds->x, bounds->y, bounds->width, bounds->height); result = gdk_pixbuf_copy (tmp_pixbuf); g_object_unref (tmp_pixbuf); return result; } static gboolean theme_source_has_location (ThemeSource *source, ThemeLocation *location, int frame) { return theme_source_location_bounds (source, location, frame, 0, FALSE, NULL); } static gboolean cursor_find_hotspot (ThemeCursor *cursor, ThemeSource *source, int *hot_x, int *hot_y) { ThemeRectangle bounds; ThemeImage *image; /* Some debugging checks */ image = theme_source_find_image (source, THEME_SOURCE_USE_HOTSPOT); if (!image) { g_printerr ("Source at size %d doesn't have a hotspot image\n", source->size); return FALSE; } if (!gdk_pixbuf_get_has_alpha (image->image) || gdk_pixbuf_get_colorspace (image->image) != GDK_COLORSPACE_RGB) { g_printerr ("Invalid format for hotspot file\n"); return FALSE; } if (theme_source_location_bounds (source, &cursor->location, THEME_SOURCE_USE_HOTSPOT, 0x80, TRUE, &bounds)) { if (bounds.width > 1 || bounds.height > 1) { g_printerr ("Multiple hotspots for cursor %s at size %d\n", cursor->name, source->size); return FALSE; } *hot_x = bounds.x; *hot_y = bounds.y; return TRUE; } else { g_printerr ("Cannot find hotspot for cursor %s at size %d\n", cursor->name, source->size); return FALSE; } return TRUE; } static void cursor_fetch_pixbuf (ThemeCursor *cursor, ThemeFrame *frame, ThemeSource *source, int frame_index) { int start_x, start_y; ThemeRectangle bounds_rect; ThemeRectangle hotspot_rect; theme_source_location_start (source, &cursor->location, &start_x, &start_y); theme_source_location_bounds (source, &cursor->location, frame_index, 0, FALSE, &bounds_rect); hotspot_rect.x = start_x + frame->hot_x; hotspot_rect.y = start_y + frame->hot_y; hotspot_rect.width = 1; hotspot_rect.height = 1; theme_rectangle_union (&bounds_rect, &hotspot_rect, &bounds_rect); frame->image = theme_source_get_pixbuf (source, &cursor->location, frame_index, &bounds_rect); frame->hot_x -= bounds_rect.x - start_x; frame->hot_y -= bounds_rect.y - start_y; } static gboolean cursor_add_source (ThemeCursor *cursor, ThemeSource *source) { GSList *tmp_list; int hot_x, hot_y; int i; /* If the first frame is missing we silently treat * it as OK */ if (!theme_source_has_location (source, &cursor->location, 0)) return TRUE; if (!cursor_find_hotspot (cursor, source, &hot_x, &hot_y)) return FALSE; for (tmp_list = cursor->frame_configs, i = 0; tmp_list; tmp_list = tmp_list->next, i++) { ThemeFrameConfig *frame_config = tmp_list->data; ThemeFrame *frame; if (i != 0 && !theme_source_has_location (source, &cursor->location, i)) { g_printerr ("Frame %d missing for cursor '%s' at size %d\n", i, cursor->name, source->size); frame = g_new0 (ThemeFrame, 1); frame->size = -1; cursor->frames = g_slist_append (cursor->frames, frame); continue; } frame = g_new0 (ThemeFrame, 1); frame->size = source->size; frame->hot_x = hot_x; frame->hot_y = hot_y; frame->delay = frame_config->delay; cursor_fetch_pixbuf (cursor, frame, source, i); cursor->frames = g_slist_append (cursor->frames, frame); } return TRUE; } static void icon_fetch_pixbuf (ThemeIcon *icon, ThemeIconInstance *instance, ThemeSource *source) { ThemeRectangle bounds_rect; theme_source_location_start (source, &icon->location, &bounds_rect.x, &bounds_rect.y); bounds_rect.width = source->gridsize; bounds_rect.height = source->gridsize; instance->image = theme_source_get_pixbuf (source, &icon->location, 0, &bounds_rect); } static void icon_fetch_embedded_rect (ThemeIcon *icon, ThemeIconInstance *instance, ThemeSource *source) { ThemeRectangle bounds_rect; ThemeImage *image; image = theme_source_find_image (source, THEME_SOURCE_USE_EMBEDDED_RECT); if (!image) return; if (!gdk_pixbuf_get_has_alpha (image->image) || gdk_pixbuf_get_colorspace (image->image) != GDK_COLORSPACE_RGB) { g_printerr ("Invalid format for embedded rectangle file\n"); exit (1); } if (theme_source_location_bounds (source, &icon->location, THEME_SOURCE_USE_EMBEDDED_RECT, 0x80, TRUE, &bounds_rect)) { instance->embedded_rect = g_memdup (&bounds_rect, sizeof (bounds_rect)); } } static void icon_fetch_attach_points (ThemeIcon *icon, ThemeIconInstance *instance, ThemeSource *source) { ThemeImage *image; int start_x, start_y; int i, j; const guchar *pixels; int rowstride; theme_source_location_start (source, &icon->location, &start_x, &start_y); image = theme_source_find_image (source, THEME_SOURCE_USE_ATTACH_POINTS); if (!image) return; if (!gdk_pixbuf_get_has_alpha (image->image) || gdk_pixbuf_get_colorspace (image->image) != GDK_COLORSPACE_RGB) { g_printerr ("Invalid format for embedded rectangle file\n"); exit (1); } rowstride = gdk_pixbuf_get_rowstride (image->image); pixels = gdk_pixbuf_get_pixels (image->image) + start_y * rowstride + start_x * 4; for (j = 0; j < source->gridsize; j++) { for (i = 0; i < source->gridsize; i++) { if (pixels[4*i + 3] > 0x80) { ThemePoint *attach_point = g_new (ThemePoint, 1); attach_point->x = i; attach_point->y = j; instance->attach_points = g_slist_append (instance->attach_points, attach_point); } } pixels += rowstride; } } static gboolean icon_add_source (ThemeIcon *icon, ThemeSource *source) { ThemeIconInstance *instance; /* If the image is missing, we silently omit it */ if (!theme_source_has_location (source, &icon->location, 0)) return TRUE; instance = g_new0 (ThemeIconInstance, 1); instance->source = source; icon_fetch_pixbuf (icon, instance, source); icon_fetch_embedded_rect (icon, instance, source); icon_fetch_attach_points (icon, instance, source); icon->instances = g_slist_append (icon->instances, instance); return TRUE; } static gboolean theme_read_cursor (ThemeFile *theme, ThemeCursor *cursor) { GSList *tmp_list; for (tmp_list = theme->sources; tmp_list; tmp_list = tmp_list->next) { if (!cursor_add_source (cursor, tmp_list->data)) return FALSE; } return TRUE; } static gboolean theme_read_icon (ThemeFile *theme, ThemeIcon *icon) { GSList *tmp_list; for (tmp_list = theme->sources; tmp_list; tmp_list = tmp_list->next) { if (!icon_add_source (icon, tmp_list->data)) return FALSE; } return TRUE; } static gboolean ensure_directory (const char *directory) { if (strchr (directory, '/') != NULL) { char *dirname = g_path_get_dirname (directory); gboolean success = ensure_directory (dirname); g_free (dirname); if (!success) return FALSE; } if (!g_file_test (directory, G_FILE_TEST_IS_DIR) && mkdir (directory, 0755) < 0) { g_printerr ("Error creating output directory '%s': %s\n", directory, g_strerror (errno)); return FALSE; } return TRUE; } static void theme_write_cursor (ThemeFile *theme, ThemeCursor *cursor) { GSList *tmp_list; char *curdir; char *config_filename; char *command; FILE *config_file; GError *error = NULL; int status; int i; if (!ensure_directory ("cursors")) return; curdir = g_get_current_dir (); if (chdir ("cursors") < 0) { g_printerr ("Could not change to cursor directory: %s\n", g_strerror (errno)); return; } if (g_hash_table_lookup (theme->aliases, cursor->name)) { g_printerr ("Warning: cursor '%s' overridden by alias\n", cursor->name); goto out; } if (!cursor->frames) goto out; config_filename = g_strconcat (cursor->name, ".cfg", NULL); config_file = fopen (config_filename, "w"); if (!config_file) { g_printerr ("Cannot open config file '%s'\n", config_filename); g_free (config_filename); goto out; } for (tmp_list = cursor->frames, i = 0; tmp_list; tmp_list = tmp_list->next, i++) { ThemeFrame *frame = tmp_list->data; char *filename; if (frame->size == -1) continue; filename = g_strdup_printf ("%s-%d.png", cursor->name, i); if (gdk_pixbuf_save (frame->image, filename, "png", &error, NULL)) { if (frame->delay > 0) fprintf (config_file, "%d %d %d %s %d\n", frame->size, frame->hot_x, frame->hot_y, filename, frame->delay); else fprintf (config_file, "%d %d %d %s\n", frame->size, frame->hot_x, frame->hot_y, filename); } else { g_printerr ("Error saving image file: %s\n", error->message); g_error_free (error); } g_free (filename); } fclose (config_file); command = g_strdup_printf ("sh -c 'xcursorgen %s > %s'\n", config_filename, cursor->name); if (!g_spawn_command_line_sync (command, NULL, NULL, &status, &error)) { g_printerr ("Error running xcursorgen for %s: %s\n", cursor->name, error->message); g_error_free (error); } else if (status) { g_printerr ("Error running xcursorgen for %s\n", cursor->name); } else { /* Only delete temporary files if no error occurred */ unlink (config_filename); g_free (config_filename); for (tmp_list = cursor->frames, i = 0; tmp_list; tmp_list = tmp_list->next, i++) { char *filename; filename = g_strdup_printf ("%s-%d.png", cursor->name, i); unlink (filename); g_free (filename); } } out: chdir (curdir); } static char * theme_icon_output_dir (ThemeFile *theme, ThemeSource *source, const char *typedir_override, const char *icon_or_alias_name) { const char *typedir = typedir_override ? typedir_override : theme->typedir; if (!source->sizedir && !typedir) { g_printerr ("Both typedir and sizedir are empty for '%s'\n", icon_or_alias_name); return NULL; } if (source->sizedir) return g_build_filename (source->sizedir, typedir, NULL); else return g_strdup (typedir); } static void theme_write_icon_data_file (ThemeFile *theme, const char *output_dir, const char *name, ThemeIconInstance *instance, GSList *display_names) { char *data_filename; char *data_pathname; FILE *data_file; GSList *tmp_list; data_filename = g_strconcat (name, ".icon", NULL); data_pathname = g_build_filename (output_dir, data_filename, NULL); data_file = fopen (data_pathname, "w"); if (!data_file) { g_printerr ("Cannot open icon data file '%s'\n", data_pathname); exit (1); } fprintf (data_file, "# Generated by icon-slicer, do not edit\n"); fprintf (data_file, "[Icon Data]\n"); if (instance->embedded_rect) { fprintf (data_file, "EmbeddedTextRectangle=%d,%d,%d,%d\n", instance->embedded_rect->x, instance->embedded_rect->y, instance->embedded_rect->x + instance->embedded_rect->width, instance->embedded_rect->y + instance->embedded_rect->height); } if (instance->attach_points) { GString *attach_point_string = g_string_new (NULL); for (tmp_list = instance->attach_points; tmp_list; tmp_list = tmp_list->next) { ThemePoint *attach_point = tmp_list->data; g_string_append_printf (attach_point_string, "%d,%d", attach_point->x, attach_point->y); if (tmp_list->next) g_string_append (attach_point_string, "|"); } fprintf (data_file, "AttachPoints=%s\n", attach_point_string->str); g_string_free (attach_point_string, TRUE); } for (tmp_list = display_names; tmp_list; tmp_list = tmp_list->next) { ThemeDisplayName *display_name = tmp_list->data; if (display_name->lang) fprintf (data_file, "DisplayName[%s]=%s\n", display_name->lang, display_name->str); else fprintf (data_file, "DisplayName=%s\n", display_name->str); } fclose (data_file); g_free (data_filename); g_free (data_pathname); } static void theme_write_icon (ThemeFile *theme, ThemeIcon *icon) { GSList *tmp_list; GError *error = NULL; int i; if (g_hash_table_lookup (theme->aliases, icon->name)) { g_printerr ("Warning: icon '%s' overridden by alias\n", icon->name); return; } if (!icon->instances) return; for (tmp_list = icon->instances, i = 0; tmp_list; tmp_list = tmp_list->next, i++) { ThemeIconInstance *instance = tmp_list->data; char *output_dir, *filename, *pathname; output_dir = theme_icon_output_dir (theme, instance->source, icon->typedir, icon->name); if (!output_dir) exit (1); if (!ensure_directory (output_dir)) exit (1); filename = g_strconcat (icon->name, ".png", NULL); pathname = g_build_filename (output_dir, filename, NULL); if (!gdk_pixbuf_save (instance->image, pathname, "png", &error, NULL)) { g_printerr ("Error saving image file: %s\n", error->message); g_error_free (error); exit (1); } g_free (filename); g_free (pathname); if (instance->embedded_rect || instance->attach_points || icon->display_names) { theme_write_icon_data_file (theme, output_dir, icon->name, instance, icon->display_names); } g_free (output_dir); } } static const char * theme_check_alias (ThemeFile *theme, ThemeAlias *alias, ThemeAliasType *type, const char **target_typedir) { ThemeAlias *tortoise = alias; ThemeAlias *hare = alias; ThemeCursor *cursor_target; ThemeIcon *icon_target; /* Dereference, using tortoise-and-hare checking for circular aliases */ while (TRUE) { ThemeAlias *next; next = g_hash_table_lookup (theme->aliases, hare->target); if (!next) break; hare = next; if (hare == tortoise) goto found_loop; next = g_hash_table_lookup (theme->aliases, hare->target); if (!next) break; hare = next; if (hare == tortoise) goto found_loop; tortoise = g_hash_table_lookup (theme->aliases, tortoise->target); } /* Now check that the actual target exists and is sensible. */ cursor_target = g_hash_table_lookup (theme->cursors, hare->target); icon_target = g_hash_table_lookup (theme->icons, hare->target); if (cursor_target && icon_target) { g_printerr ("Alias '%s' points to '%s', which is both a cursor and an icon\n", alias->name, hare->target); return NULL; } if (!cursor_target && !icon_target) { g_printerr ("Alias '%s' points to '%s', which is not in the theme\n", alias->name, hare->target); return NULL; } if (cursor_target) { if (!cursor_target->frames) { g_printerr ("Alias '%s' points to cursor '%s', which is not present in any source\n", alias->name, hare->target); return NULL; } *type = THEME_ALIAS_CURSOR; *target_typedir = NULL; } if (icon_target) { if (!icon_target->instances) { g_printerr ("Alias '%s' points to icon '%s', which is not present in any source\n", alias->name, hare->target); return NULL; } *type = THEME_ALIAS_ICON; *target_typedir = icon_target->typedir; } return hare->target; found_loop: g_printerr ("Circular looop detected when dereferencing alias '%s'\n", alias->name); return NULL; } static char * relative_path (const char *directory, const char *base) { char **dir_comps = g_strsplit (directory, "/", -1); char **base_comps = g_strsplit (base, "/", -1); char **dirp, **basep; GString *result = g_string_new (NULL); dirp = dir_comps; basep = base_comps; while (*dirp && *basep && strcmp (*dirp, *basep) == 0) { dirp++; basep++; } while (*basep) { if (result->len) g_string_append_c (result, '/'); g_string_append (result, ".."); basep++; } while (*dirp) { if (result->len) g_string_append_c (result, '/'); g_string_append (result, *dirp); dirp++; } g_strfreev (dir_comps); g_strfreev (base_comps); if (result->len) return g_string_free (result, FALSE); else { g_string_free (result, TRUE); return NULL; } } static gboolean make_symlink (const char *directory, const char *alias, const char *target_directory, const char *target) { char *filename; char *target_filename; gboolean result = FALSE; char *relative_target_directory; if (!ensure_directory (directory)) return FALSE; relative_target_directory = relative_path (target_directory, directory); filename = g_build_filename (directory, alias, NULL); if (relative_target_directory) target_filename = g_build_filename (relative_target_directory, target, NULL); else target_filename = g_strdup (target); unlink (filename); if (symlink (target_filename, filename) < 0) g_printerr ("Error creating alias symlink from '%s' to '%s': %s\n", filename, target_filename, g_strerror (errno)); else result = TRUE; g_free (filename); g_free (relative_target_directory); g_free (target_filename); return result; } static void theme_write_alias (ThemeFile *theme, ThemeAlias *alias) { ThemeAliasType type; const char *target_typedir; const char *target = theme_check_alias (theme, alias, &type, &target_typedir); if (!target) exit (1); if (type == THEME_ALIAS_CURSOR) { if (!make_symlink ("cursors", alias->name, "cursors", target)) exit (1); } else { ThemeIcon *icon = g_hash_table_lookup (theme->icons, target); GSList *tmp_list; char *pngalias = g_strdup_printf ("%s.png", alias->name); char *pngtarget = g_strdup_printf ("%s.png", target); char *dataalias = g_strdup_printf ("%s.icon", alias->name); char *datatarget = g_strdup_printf ("%s.icon", target); for (tmp_list = icon->instances; tmp_list; tmp_list = tmp_list->next) { ThemeIconInstance *instance = tmp_list->data; char *output_dir; char *target_output_dir; output_dir = theme_icon_output_dir (theme, instance->source, alias->typedir, alias->name); if (!output_dir) exit (1); target_output_dir = theme_icon_output_dir (theme, instance->source, target_typedir, target); if (!output_dir) exit (1); if (!ensure_directory (output_dir)) exit (1); if (!make_symlink (output_dir, pngalias, target_output_dir, pngtarget)) exit (1); if (alias->display_names) theme_write_icon_data_file (theme, output_dir, alias->name, instance, alias->display_names); else if ((instance->embedded_rect || instance->attach_points || icon->display_names)) { if (!make_symlink (output_dir, dataalias, target_output_dir, datatarget)) exit (1); } g_free (output_dir); g_free (target_output_dir); } g_free (pngalias); g_free (pngtarget); g_free (dataalias); g_free (datatarget); } } static void write_cursor_foreach (gpointer key, gpointer value, gpointer data) { theme_write_cursor (data, value); } static void write_icon_foreach (gpointer key, gpointer value, gpointer data) { theme_write_icon (data, value); } static void write_alias_foreach (gpointer key, gpointer value, gpointer data) { theme_write_alias (data, value); } static gboolean theme_write (ThemeFile *theme, const char *output_dir) { char *curdir; if (!g_file_test (output_dir, G_FILE_TEST_IS_DIR)) { g_printerr ("Output directory '%s' does not exist\n", output_dir); return FALSE; } curdir = g_get_current_dir (); if (chdir (output_dir) < 0) { g_printerr ("Could not change to output directory '%s'\n", output_dir); return FALSE; } g_hash_table_foreach (theme->cursors, write_cursor_foreach, theme); g_hash_table_foreach (theme->icons, write_icon_foreach, theme); g_hash_table_foreach (theme->aliases, write_alias_foreach, theme); chdir (curdir); return TRUE; } void usage (void) { g_printerr ("Usage: cursorthemegen CONFIG_FILE OUTPUT_DIR\n"); exit (1); } static void read_cursor_foreach (gpointer key, gpointer value, gpointer data) { if (!theme_read_cursor (data, value)) exit (1); } static void read_icon_foreach (gpointer key, gpointer value, gpointer data) { if (!theme_read_icon (data, value)) exit (1); } static int want_my_version = FALSE; static const char *output_dir = NULL; static const char *image_dir = NULL; static const struct poptOption options_table[] = { { "version", 0, POPT_ARG_NONE, &want_my_version, 0, "output version of icon-slicer" }, { "output-dir", 0, POPT_ARG_STRING, &output_dir, 0, "directory into which to write output", "DIRECTORY" }, { "image-dir", 0, POPT_ARG_STRING, &image_dir, 0, "directory in which to find source images", "DIRECTORY" }, POPT_AUTOHELP { NULL, 0, 0, NULL, 0 } }; int main (int argc, char **argv) { poptContext opt_context; const char *arg; int result; g_type_init (); opt_context = poptGetContext ("xsri", argc, (const char **)argv, options_table, 0); result = poptGetNextOpt (opt_context); if (result != -1) { g_printerr ("xsri: %s: %s\n", poptBadOption(opt_context, POPT_BADOPTION_NOALIAS), poptStrerror(result)); return 1; } if (want_my_version) { printf ("icon-slicer version %s\n", PACKAGE_VERSION); return 0; } if (!output_dir) { g_printerr ("Output directory must be given with --output-dir\n"); poptPrintUsage (opt_context, stderr, 0); return 1; } arg = poptGetArg (opt_context); if (!arg) { poptPrintUsage (opt_context, stderr, 0); return 1; } while (arg) { ThemeFile *theme; theme = theme_file_read (arg, image_dir); if (!theme) return 1; g_hash_table_foreach (theme->cursors, read_cursor_foreach, theme); g_hash_table_foreach (theme->icons, read_icon_foreach, theme); if (!theme_write (theme, output_dir)) return 1; theme_file_free (theme); arg = poptGetArg (opt_context); } return 0; }