2
* Copyright © 2003 Red Hat, Inc.
4
* Permission to use, copy, modify, distribute, and sell this software and its
5
* documentation for any purpose is hereby granted without fee, provided that
6
* the above copyright notice appear in all copies and that both that
7
* copyright notice and this permission notice appear in supporting
8
* documentation, and that the name of Red Hat not be used in advertising or
9
* publicity pertaining to distribution of the software without specific,
10
* written prior permission. Red Hat makes no representations about the
11
* suitability of this software for any purpose. It is provided "as is"
12
* without express or implied warranty.
14
* RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
15
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT
16
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
18
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
19
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21
* Author: Owen Taylor, Red Hat, Inc.
29
#include <sys/types.h>
35
#include "themefile.h"
38
theme_rectangle_union (ThemeRectangle *src1,
44
dest_x = MIN (src1->x, src2->x);
45
dest_y = MIN (src1->y, src2->y);
46
dest->width = MAX (src1->x + src1->width, src2->x + src2->width) - dest_x;
47
dest->height = MAX (src1->y + src1->height, src2->y + src2->height) - dest_y;
53
theme_source_find_image (ThemeSource *source,
58
for (tmp_list = source->images; tmp_list; tmp_list = tmp_list->next)
60
ThemeImage *image = tmp_list->data;
62
if (image->use == use)
70
theme_source_location_start (ThemeSource *source,
71
ThemeLocation *location,
75
*start_x = (source->margin +
76
location->column * (source->gridsize + source->spacing));
78
*start_y = (source->margin +
79
location->row * (source->gridsize + source->spacing));
83
theme_source_location_bounds (ThemeSource *source,
84
ThemeLocation *location,
87
gboolean location_relative,
90
ThemeImage *image = theme_source_find_image (source, frame_index);
98
theme_source_location_start (source, location, &start_x, &start_y);
100
rowstride = gdk_pixbuf_get_rowstride (image->image);
101
n_channels = gdk_pixbuf_get_n_channels (image->image);
107
out->width = source->gridsize;
108
out->height = source->gridsize;
114
int min_x = 0, max_x = 0, min_y = 0, max_y = 0;
115
gboolean found = FALSE;
117
pixels = gdk_pixbuf_get_pixels (image->image) + start_y * rowstride + start_x * 4;
119
for (j = 0; j < source->gridsize; j++)
121
for (i = 0; i < source->gridsize; i++)
123
if (pixels[4*i + 3] > threshold)
127
if (start_x + i < min_x)
129
if (start_x + i >= max_x)
130
max_x = start_x + i + 1;
131
if (start_y + j < min_y)
133
if (start_y + j >= max_y)
134
max_y = start_y + j + 1;
139
max_x = start_x + i + 1;
141
max_y = start_y + i + 1;
155
out->x = location_relative ? min_x - start_x : min_x;
156
out->y = location_relative ? min_y - start_y : min_y;
157
out->width = max_x - min_x;
158
out->height = max_y - min_y;
169
theme_source_get_pixbuf (ThemeSource *source,
170
ThemeLocation *location,
172
ThemeRectangle *bounds)
174
ThemeImage *image = theme_source_find_image (source, frame_index);
175
GdkPixbuf *result, *tmp_pixbuf;
177
tmp_pixbuf = gdk_pixbuf_new_subpixbuf (image->image,
183
result = gdk_pixbuf_copy (tmp_pixbuf);
184
g_object_unref (tmp_pixbuf);
190
theme_source_has_location (ThemeSource *source,
191
ThemeLocation *location,
194
return theme_source_location_bounds (source, location, frame, 0, FALSE, NULL);
198
cursor_find_hotspot (ThemeCursor *cursor,
203
ThemeRectangle bounds;
206
/* Some debugging checks */
207
image = theme_source_find_image (source, THEME_SOURCE_USE_HOTSPOT);
210
g_printerr ("Source at size %d doesn't have a hotspot image\n",
215
if (!gdk_pixbuf_get_has_alpha (image->image) ||
216
gdk_pixbuf_get_colorspace (image->image) != GDK_COLORSPACE_RGB)
218
g_printerr ("Invalid format for hotspot file\n");
222
if (theme_source_location_bounds (source, &cursor->location,
223
THEME_SOURCE_USE_HOTSPOT,
224
0x80, TRUE, &bounds))
226
if (bounds.width > 1 || bounds.height > 1)
228
g_printerr ("Multiple hotspots for cursor %s at size %d\n",
229
cursor->name, source->size);
240
g_printerr ("Cannot find hotspot for cursor %s at size %d\n",
241
cursor->name, source->size);
249
cursor_fetch_pixbuf (ThemeCursor *cursor,
254
int start_x, start_y;
255
ThemeRectangle bounds_rect;
256
ThemeRectangle hotspot_rect;
258
theme_source_location_start (source, &cursor->location, &start_x, &start_y);
259
theme_source_location_bounds (source, &cursor->location, frame_index,
260
0, FALSE, &bounds_rect);
262
hotspot_rect.x = start_x + frame->hot_x;
263
hotspot_rect.y = start_y + frame->hot_y;
264
hotspot_rect.width = 1;
265
hotspot_rect.height = 1;
266
theme_rectangle_union (&bounds_rect, &hotspot_rect, &bounds_rect);
268
frame->image = theme_source_get_pixbuf (source, &cursor->location,
269
frame_index, &bounds_rect);
271
frame->hot_x -= bounds_rect.x - start_x;
272
frame->hot_y -= bounds_rect.y - start_y;
276
cursor_add_source (ThemeCursor *cursor,
283
/* If the first frame is missing we silently treat
286
if (!theme_source_has_location (source, &cursor->location, 0))
289
if (!cursor_find_hotspot (cursor, source, &hot_x, &hot_y))
292
for (tmp_list = cursor->frame_configs, i = 0;
294
tmp_list = tmp_list->next, i++)
296
ThemeFrameConfig *frame_config = tmp_list->data;
299
if (i != 0 && !theme_source_has_location (source, &cursor->location, i))
301
g_printerr ("Frame %d missing for cursor '%s' at size %d\n",
302
i, cursor->name, source->size);
303
frame = g_new0 (ThemeFrame, 1);
305
cursor->frames = g_slist_append (cursor->frames, frame);
309
frame = g_new0 (ThemeFrame, 1);
310
frame->size = source->size;
311
frame->hot_x = hot_x;
312
frame->hot_y = hot_y;
313
frame->delay = frame_config->delay;
315
cursor_fetch_pixbuf (cursor, frame, source, i);
317
cursor->frames = g_slist_append (cursor->frames, frame);
324
icon_fetch_pixbuf (ThemeIcon *icon,
325
ThemeIconInstance *instance,
328
ThemeRectangle bounds_rect;
330
theme_source_location_start (source, &icon->location,
331
&bounds_rect.x, &bounds_rect.y);
332
bounds_rect.width = source->gridsize;
333
bounds_rect.height = source->gridsize;
335
instance->image = theme_source_get_pixbuf (source, &icon->location,
340
icon_fetch_embedded_rect (ThemeIcon *icon,
341
ThemeIconInstance *instance,
344
ThemeRectangle bounds_rect;
347
image = theme_source_find_image (source, THEME_SOURCE_USE_EMBEDDED_RECT);
351
if (!gdk_pixbuf_get_has_alpha (image->image) ||
352
gdk_pixbuf_get_colorspace (image->image) != GDK_COLORSPACE_RGB)
354
g_printerr ("Invalid format for embedded rectangle file\n");
358
if (theme_source_location_bounds (source, &icon->location,
359
THEME_SOURCE_USE_EMBEDDED_RECT,
360
0x80, TRUE, &bounds_rect))
362
instance->embedded_rect = g_memdup (&bounds_rect, sizeof (bounds_rect));
367
icon_fetch_attach_points (ThemeIcon *icon,
368
ThemeIconInstance *instance,
373
int start_x, start_y;
376
const guchar *pixels;
379
theme_source_location_start (source, &icon->location, &start_x, &start_y);
380
image = theme_source_find_image (source, THEME_SOURCE_USE_ATTACH_POINTS);
384
if (!gdk_pixbuf_get_has_alpha (image->image) ||
385
gdk_pixbuf_get_colorspace (image->image) != GDK_COLORSPACE_RGB)
387
g_printerr ("Invalid format for embedded rectangle file\n");
391
rowstride = gdk_pixbuf_get_rowstride (image->image);
392
pixels = gdk_pixbuf_get_pixels (image->image) + start_y * rowstride + start_x * 4;
394
for (j = 0; j < source->gridsize; j++)
396
for (i = 0; i < source->gridsize; i++)
398
if (pixels[4*i + 3] > 0x80)
400
ThemePoint *attach_point = g_new (ThemePoint, 1);
404
instance->attach_points = g_slist_append (instance->attach_points, attach_point);
413
icon_add_source (ThemeIcon *icon,
416
ThemeIconInstance *instance;
418
/* If the image is missing, we silently omit it
420
if (!theme_source_has_location (source, &icon->location, 0))
423
instance = g_new0 (ThemeIconInstance, 1);
424
instance->source = source;
426
icon_fetch_pixbuf (icon, instance, source);
427
icon_fetch_embedded_rect (icon, instance, source);
428
icon_fetch_attach_points (icon, instance, source);
430
icon->instances = g_slist_append (icon->instances, instance);
436
theme_read_cursor (ThemeFile *theme,
441
for (tmp_list = theme->sources; tmp_list; tmp_list = tmp_list->next)
443
if (!cursor_add_source (cursor, tmp_list->data))
451
theme_read_icon (ThemeFile *theme,
456
for (tmp_list = theme->sources; tmp_list; tmp_list = tmp_list->next)
458
if (!icon_add_source (icon, tmp_list->data))
466
ensure_directory (const char *directory)
468
if (strchr (directory, '/') != NULL)
470
char *dirname = g_path_get_dirname (directory);
471
gboolean success = ensure_directory (dirname);
478
if (!g_file_test (directory, G_FILE_TEST_IS_DIR) &&
479
mkdir (directory, 0755) < 0)
481
g_printerr ("Error creating output directory '%s': %s\n",
482
directory, g_strerror (errno));
491
theme_write_cursor (ThemeFile *theme,
496
char *config_filename;
499
GError *error = NULL;
503
if (!ensure_directory ("cursors"))
506
curdir = g_get_current_dir ();
507
if (chdir ("cursors") < 0)
509
g_printerr ("Could not change to cursor directory: %s\n",
514
if (g_hash_table_lookup (theme->aliases, cursor->name))
516
g_printerr ("Warning: cursor '%s' overridden by alias\n", cursor->name);
523
config_filename = g_strconcat (cursor->name, ".cfg", NULL);
524
config_file = fopen (config_filename, "w");
528
g_printerr ("Cannot open config file '%s'\n", config_filename);
529
g_free (config_filename);
533
for (tmp_list = cursor->frames, i = 0; tmp_list; tmp_list = tmp_list->next, i++)
535
ThemeFrame *frame = tmp_list->data;
538
if (frame->size == -1)
540
filename = g_strdup_printf ("%s-%d.png", cursor->name, i);
541
if (gdk_pixbuf_save (frame->image, filename, "png", &error, NULL))
543
if (frame->delay > 0)
544
fprintf (config_file, "%d %d %d %s %d\n",
545
frame->size, frame->hot_x, frame->hot_y, filename, frame->delay);
547
fprintf (config_file, "%d %d %d %s\n",
548
frame->size, frame->hot_x, frame->hot_y, filename);
552
g_printerr ("Error saving image file: %s\n", error->message);
553
g_error_free (error);
558
fclose (config_file);
560
command = g_strdup_printf ("sh -c 'xcursorgen %s > %s'\n",
561
config_filename, cursor->name);
562
if (!g_spawn_command_line_sync (command, NULL, NULL, &status, &error))
564
g_printerr ("Error running xcursorgen for %s: %s\n",
565
cursor->name, error->message);
566
g_error_free (error);
570
g_printerr ("Error running xcursorgen for %s\n",
575
/* Only delete temporary files if no error occurred
577
unlink (config_filename);
578
g_free (config_filename);
580
for (tmp_list = cursor->frames, i = 0; tmp_list; tmp_list = tmp_list->next, i++)
584
filename = g_strdup_printf ("%s-%d.png", cursor->name, i);
595
theme_icon_output_dir (ThemeFile *theme,
597
const char *typedir_override,
598
const char *icon_or_alias_name)
600
const char *typedir = typedir_override ? typedir_override : theme->typedir;
602
if (!source->sizedir && !typedir)
604
g_printerr ("Both typedir and sizedir are empty for '%s'\n",
610
return g_build_filename (source->sizedir, typedir, NULL);
612
return g_strdup (typedir);
616
theme_write_icon_data_file (ThemeFile *theme,
617
const char *output_dir,
619
ThemeIconInstance *instance,
620
GSList *display_names)
627
data_filename = g_strconcat (name, ".icon", NULL);
628
data_pathname = g_build_filename (output_dir, data_filename, NULL);
630
data_file = fopen (data_pathname, "w");
633
g_printerr ("Cannot open icon data file '%s'\n",
638
fprintf (data_file, "# Generated by icon-slicer, do not edit\n");
639
fprintf (data_file, "[Icon Data]\n");
640
if (instance->embedded_rect)
642
fprintf (data_file, "EmbeddedTextRectangle=%d,%d,%d,%d\n",
643
instance->embedded_rect->x,
644
instance->embedded_rect->y,
645
instance->embedded_rect->x + instance->embedded_rect->width,
646
instance->embedded_rect->y + instance->embedded_rect->height);
648
if (instance->attach_points)
650
GString *attach_point_string = g_string_new (NULL);
652
for (tmp_list = instance->attach_points; tmp_list; tmp_list = tmp_list->next)
654
ThemePoint *attach_point = tmp_list->data;
655
g_string_append_printf (attach_point_string, "%d,%d",
656
attach_point->x, attach_point->y);
658
g_string_append (attach_point_string, "|");
661
fprintf (data_file, "AttachPoints=%s\n",
662
attach_point_string->str);
664
g_string_free (attach_point_string, TRUE);
666
for (tmp_list = display_names; tmp_list; tmp_list = tmp_list->next)
668
ThemeDisplayName *display_name = tmp_list->data;
670
if (display_name->lang)
671
fprintf (data_file, "DisplayName[%s]=%s\n",
672
display_name->lang, display_name->str);
674
fprintf (data_file, "DisplayName=%s\n",
680
g_free (data_filename);
681
g_free (data_pathname);
685
theme_write_icon (ThemeFile *theme,
689
GError *error = NULL;
692
if (g_hash_table_lookup (theme->aliases, icon->name))
694
g_printerr ("Warning: icon '%s' overridden by alias\n", icon->name);
698
if (!icon->instances)
701
for (tmp_list = icon->instances, i = 0; tmp_list; tmp_list = tmp_list->next, i++)
703
ThemeIconInstance *instance = tmp_list->data;
704
char *output_dir, *filename, *pathname;
706
output_dir = theme_icon_output_dir (theme, instance->source,
707
icon->typedir, icon->name);
711
if (!ensure_directory (output_dir))
714
filename = g_strconcat (icon->name, ".png", NULL);
715
pathname = g_build_filename (output_dir, filename, NULL);
716
if (!gdk_pixbuf_save (instance->image, pathname, "png", &error, NULL))
718
g_printerr ("Error saving image file: %s\n", error->message);
719
g_error_free (error);
725
if (instance->embedded_rect || instance->attach_points || icon->display_names)
727
theme_write_icon_data_file (theme, output_dir, icon->name,
728
instance, icon->display_names);
736
theme_check_alias (ThemeFile *theme,
738
ThemeAliasType *type,
739
const char **target_typedir)
741
ThemeAlias *tortoise = alias;
742
ThemeAlias *hare = alias;
743
ThemeCursor *cursor_target;
744
ThemeIcon *icon_target;
746
/* Dereference, using tortoise-and-hare checking for circular aliases
752
next = g_hash_table_lookup (theme->aliases, hare->target);
757
if (hare == tortoise)
760
next = g_hash_table_lookup (theme->aliases, hare->target);
765
if (hare == tortoise)
768
tortoise = g_hash_table_lookup (theme->aliases, tortoise->target);
771
/* Now check that the actual target exists and is sensible.
773
cursor_target = g_hash_table_lookup (theme->cursors, hare->target);
774
icon_target = g_hash_table_lookup (theme->icons, hare->target);
776
if (cursor_target && icon_target)
778
g_printerr ("Alias '%s' points to '%s', which is both a cursor and an icon\n",
779
alias->name, hare->target);
783
if (!cursor_target && !icon_target)
785
g_printerr ("Alias '%s' points to '%s', which is not in the theme\n",
786
alias->name, hare->target);
792
if (!cursor_target->frames)
794
g_printerr ("Alias '%s' points to cursor '%s', which is not present in any source\n",
795
alias->name, hare->target);
799
*type = THEME_ALIAS_CURSOR;
800
*target_typedir = NULL;
805
if (!icon_target->instances)
807
g_printerr ("Alias '%s' points to icon '%s', which is not present in any source\n",
808
alias->name, hare->target);
812
*type = THEME_ALIAS_ICON;
813
*target_typedir = icon_target->typedir;
819
g_printerr ("Circular looop detected when dereferencing alias '%s'\n",
825
relative_path (const char *directory,
828
char **dir_comps = g_strsplit (directory, "/", -1);
829
char **base_comps = g_strsplit (base, "/", -1);
830
char **dirp, **basep;
831
GString *result = g_string_new (NULL);
836
while (*dirp && *basep && strcmp (*dirp, *basep) == 0)
845
g_string_append_c (result, '/');
847
g_string_append (result, "..");
854
g_string_append_c (result, '/');
856
g_string_append (result, *dirp);
860
g_strfreev (dir_comps);
861
g_strfreev (base_comps);
864
return g_string_free (result, FALSE);
867
g_string_free (result, TRUE);
873
make_symlink (const char *directory,
875
const char *target_directory,
879
char *target_filename;
880
gboolean result = FALSE;
881
char *relative_target_directory;
883
if (!ensure_directory (directory))
886
relative_target_directory = relative_path (target_directory, directory);
888
filename = g_build_filename (directory, alias, NULL);
889
if (relative_target_directory)
890
target_filename = g_build_filename (relative_target_directory, target, NULL);
892
target_filename = g_strdup (target);
895
if (symlink (target_filename, filename) < 0)
896
g_printerr ("Error creating alias symlink from '%s' to '%s': %s\n",
897
filename, target_filename, g_strerror (errno));
902
g_free (relative_target_directory);
903
g_free (target_filename);
909
theme_write_alias (ThemeFile *theme,
913
const char *target_typedir;
914
const char *target = theme_check_alias (theme, alias, &type, &target_typedir);
918
if (type == THEME_ALIAS_CURSOR)
920
if (!make_symlink ("cursors", alias->name, "cursors", target))
925
ThemeIcon *icon = g_hash_table_lookup (theme->icons, target);
927
char *pngalias = g_strdup_printf ("%s.png", alias->name);
928
char *pngtarget = g_strdup_printf ("%s.png", target);
929
char *dataalias = g_strdup_printf ("%s.icon", alias->name);
930
char *datatarget = g_strdup_printf ("%s.icon", target);
932
for (tmp_list = icon->instances; tmp_list; tmp_list = tmp_list->next)
934
ThemeIconInstance *instance = tmp_list->data;
936
char *target_output_dir;
938
output_dir = theme_icon_output_dir (theme, instance->source,
939
alias->typedir, alias->name);
943
target_output_dir = theme_icon_output_dir (theme, instance->source,
944
target_typedir, target);
948
if (!ensure_directory (output_dir))
951
if (!make_symlink (output_dir, pngalias,
952
target_output_dir, pngtarget))
955
if (alias->display_names)
956
theme_write_icon_data_file (theme, output_dir, alias->name,
957
instance, alias->display_names);
958
else if ((instance->embedded_rect || instance->attach_points || icon->display_names))
960
if (!make_symlink (output_dir, dataalias,
961
target_output_dir, datatarget))
966
g_free (target_output_dir);
977
write_cursor_foreach (gpointer key,
981
theme_write_cursor (data, value);
985
write_icon_foreach (gpointer key,
989
theme_write_icon (data, value);
993
write_alias_foreach (gpointer key,
997
theme_write_alias (data, value);
1001
theme_write (ThemeFile *theme,
1002
const char *output_dir)
1006
if (!g_file_test (output_dir, G_FILE_TEST_IS_DIR))
1008
g_printerr ("Output directory '%s' does not exist\n", output_dir);
1012
curdir = g_get_current_dir ();
1013
if (chdir (output_dir) < 0)
1015
g_printerr ("Could not change to output directory '%s'\n", output_dir);
1019
g_hash_table_foreach (theme->cursors,
1020
write_cursor_foreach,
1023
g_hash_table_foreach (theme->icons,
1027
g_hash_table_foreach (theme->aliases,
1028
write_alias_foreach,
1039
g_printerr ("Usage: cursorthemegen CONFIG_FILE OUTPUT_DIR\n");
1044
read_cursor_foreach (gpointer key,
1048
if (!theme_read_cursor (data, value))
1053
read_icon_foreach (gpointer key,
1057
if (!theme_read_icon (data, value))
1061
static int want_my_version = FALSE;
1062
static const char *output_dir = NULL;
1063
static const char *image_dir = NULL;
1065
static const struct poptOption options_table[] = {
1066
{ "version", 0, POPT_ARG_NONE, &want_my_version, 0,
1067
"output version of icon-slicer" },
1068
{ "output-dir", 0, POPT_ARG_STRING, &output_dir, 0,
1069
"directory into which to write output", "DIRECTORY" },
1070
{ "image-dir", 0, POPT_ARG_STRING, &image_dir, 0,
1071
"directory in which to find source images", "DIRECTORY" },
1073
{ NULL, 0, 0, NULL, 0 }
1077
main (int argc, char **argv)
1079
poptContext opt_context;
1085
opt_context = poptGetContext ("xsri", argc, (const char **)argv,
1088
result = poptGetNextOpt (opt_context);
1091
g_printerr ("xsri: %s: %s\n",
1092
poptBadOption(opt_context, POPT_BADOPTION_NOALIAS),
1093
poptStrerror(result));
1097
if (want_my_version)
1099
printf ("icon-slicer version %s\n", PACKAGE_VERSION);
1105
g_printerr ("Output directory must be given with --output-dir\n");
1106
poptPrintUsage (opt_context, stderr, 0);
1110
arg = poptGetArg (opt_context);
1113
poptPrintUsage (opt_context, stderr, 0);
1121
theme = theme_file_read (arg, image_dir);
1125
g_hash_table_foreach (theme->cursors,
1126
read_cursor_foreach,
1129
g_hash_table_foreach (theme->icons,
1133
if (!theme_write (theme, output_dir))
1136
theme_file_free (theme);
1138
arg = poptGetArg (opt_context);