r1996: Cope slightly better with invalid filenames in various places (reported by
[rox-filer.git] / ROX-Filer / src / pixmaps.c
blob72c09ef3b8fd982ffcb8f95bd38d57831bc7ad2b
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* pixmaps.c - code for handling pixbufs (despite the name!) */
24 #include "config.h"
25 #define PIXMAPS_C
27 /* Remove pixmaps from the cache when they haven't been accessed for
28 * this period of time (seconds).
31 #define PIXMAP_PURGE_TIME 1200
33 /* Image files smaller than this are loaded in the main process.
34 * Larger images are sent to a sub-process.
35 * If this is too small, then looking inside ~/.thumbnails will
36 * cause nasty effects ;-)
38 #define SMALL_IMAGE_THRESHOLD 50000
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
47 #include <gtk/gtk.h>
49 #include "global.h"
51 #include "fscache.h"
52 #include "support.h"
53 #include "gui_support.h"
54 #include "pixmaps.h"
55 #include "main.h"
56 #include "filer.h"
57 #include "dir.h"
59 GFSCache *pixmap_cache = NULL;
61 static const char * bad_xpm[] = {
62 "12 12 3 1",
63 " c #000000000000",
64 ". c #FFFF00000000",
65 "x c #FFFFFFFFFFFF",
66 " ",
67 " ..xxxxxx.. ",
68 " ...xxxx... ",
69 " x...xx...x ",
70 " xx......xx ",
71 " xxx....xxx ",
72 " xxx....xxx ",
73 " xx......xx ",
74 " x...xx...x ",
75 " ...xxxx... ",
76 " ..xxxxxx.. ",
77 " "};
79 MaskedPixmap *im_error;
80 MaskedPixmap *im_unknown;
81 MaskedPixmap *im_symlink;
83 MaskedPixmap *im_unmounted;
84 MaskedPixmap *im_mounted;
85 MaskedPixmap *im_appdir;
87 MaskedPixmap *im_dirs;
89 typedef struct _ChildThumbnail ChildThumbnail;
91 /* There is one of these for each active child process */
92 struct _ChildThumbnail {
93 gchar *path;
94 GFunc callback;
95 gpointer data;
98 /* Static prototypes */
100 static void load_default_pixmaps(void);
101 static gint purge(gpointer data);
102 static MaskedPixmap *image_from_file(const char *path);
103 static MaskedPixmap *get_bad_image(void);
104 static GdkPixbuf *scale_pixbuf(GdkPixbuf *src, int max_w, int max_h);
105 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h);
106 static GdkPixbuf *get_thumbnail_for(const char *path);
107 static void thumbnail_child_done(ChildThumbnail *info);
108 static void child_create_thumbnail(const gchar *path);
109 static GdkPixbuf *create_spotlight_pixbuf(GdkPixbuf *src, guint32 color,
110 guchar alpha);
112 /****************************************************************
113 * EXTERNAL INTERFACE *
114 ****************************************************************/
116 void pixmaps_init(void)
118 gtk_widget_push_colormap(gdk_rgb_get_colormap());
120 pixmap_cache = g_fscache_new((GFSLoadFunc) image_from_file, NULL, NULL);
122 gtk_timeout_add(10000, purge, NULL);
124 load_default_pixmaps();
127 /* Load image <appdir>/images/name.png.
128 * Always returns with a valid image.
130 MaskedPixmap *load_pixmap(const char *name)
132 guchar *path;
133 MaskedPixmap *retval;
135 path = g_strconcat(app_dir, "/images/", name, ".png", NULL);
136 retval = image_from_file(path);
137 g_free(path);
139 if (!retval)
140 retval = get_bad_image();
142 return retval;
145 /* Create a MaskedPixmap from a GTK stock ID. Always returns
146 * a valid image.
148 static MaskedPixmap *mp_from_stock(const char *stock_id)
150 GtkIconSet *icon_set;
151 GdkPixbuf *pixbuf;
152 MaskedPixmap *retval;
154 icon_set = gtk_icon_factory_lookup_default(stock_id);
155 if (!icon_set)
156 return get_bad_image();
158 pixbuf = gtk_icon_set_render_icon(icon_set,
159 gtk_widget_get_default_style(), /* Gtk bug */
160 GTK_TEXT_DIR_LTR,
161 GTK_STATE_NORMAL,
162 GTK_ICON_SIZE_DIALOG,
163 NULL,
164 NULL);
165 retval = masked_pixmap_new(pixbuf);
166 gdk_pixbuf_unref(pixbuf);
168 return retval;
171 void pixmap_make_huge(MaskedPixmap *mp)
173 if (mp->huge_pixbuf)
174 return;
176 g_return_if_fail(mp->src_pixbuf != NULL);
178 mp->huge_pixbuf = scale_pixbuf_up(mp->src_pixbuf,
179 HUGE_WIDTH * 0.7,
180 HUGE_HEIGHT * 0.7);
182 if (!mp->huge_pixbuf)
184 mp->huge_pixbuf = mp->src_pixbuf;
185 g_object_ref(mp->huge_pixbuf);
188 mp->huge_pixbuf_lit = create_spotlight_pixbuf(mp->huge_pixbuf,
189 0x000099, 128);
190 mp->huge_width = gdk_pixbuf_get_width(mp->huge_pixbuf);
191 mp->huge_height = gdk_pixbuf_get_height(mp->huge_pixbuf);
194 void pixmap_make_small(MaskedPixmap *mp)
196 if (mp->sm_pixbuf)
197 return;
199 g_return_if_fail(mp->src_pixbuf != NULL);
201 mp->sm_pixbuf = scale_pixbuf(mp->src_pixbuf, SMALL_WIDTH, SMALL_HEIGHT);
203 if (!mp->sm_pixbuf)
205 mp->sm_pixbuf = mp->src_pixbuf;
206 g_object_ref(mp->sm_pixbuf);
209 mp->sm_pixbuf_lit = create_spotlight_pixbuf(mp->sm_pixbuf,
210 0x000099, 128);
212 mp->sm_width = gdk_pixbuf_get_width(mp->sm_pixbuf);
213 mp->sm_height = gdk_pixbuf_get_height(mp->sm_pixbuf);
216 /* Load image 'path' in the background and insert into pixmap_cache.
217 * Call callback(data, path) when done (path is NULL => error).
218 * If the image is already uptodate, or being created already, calls the
219 * callback right away.
221 void pixmap_background_thumb(const gchar *path, GFunc callback, gpointer data)
223 gboolean found;
224 MaskedPixmap *image;
225 GdkPixbuf *pixbuf;
226 pid_t child;
227 ChildThumbnail *info;
229 image = g_fscache_lookup_full(pixmap_cache, path,
230 FSCACHE_LOOKUP_ONLY_NEW, &found);
232 if (found)
234 /* Thumbnail is known, or being created */
235 g_object_unref(image);
236 callback(data, NULL);
237 return;
240 g_return_if_fail(image == NULL);
242 pixbuf = get_thumbnail_for(path);
244 if (!pixbuf)
246 struct stat finfo;
248 /* If the image is small, load it now */
249 if (mc_stat(path, &finfo) != 0)
251 callback(data, NULL);
252 return;
255 if (finfo.st_size < SMALL_IMAGE_THRESHOLD)
257 pixbuf = gdk_pixbuf_new_from_file(path, NULL);
258 if (!pixbuf)
260 g_fscache_insert(pixmap_cache,
261 path, NULL, TRUE);
262 callback(data, NULL);
263 return;
268 if (pixbuf)
270 MaskedPixmap *image;
272 image = masked_pixmap_new(pixbuf);
273 gdk_pixbuf_unref(pixbuf);
274 g_fscache_insert(pixmap_cache, path, image, TRUE);
275 callback(data, (gchar *) path);
276 return;
279 /* Add an entry, set to NULL, so no-one else tries to load this
280 * image.
282 g_fscache_insert(pixmap_cache, path, NULL, TRUE);
284 child = fork();
286 if (child == -1)
288 delayed_error("fork(): %s", g_strerror(errno));
289 callback(data, NULL);
290 return;
293 if (child == 0)
295 /* We are the child process */
296 child_create_thumbnail(path);
297 _exit(0);
300 info = g_new(ChildThumbnail, 1);
301 info->path = g_strdup(path);
302 info->callback = callback;
303 info->data = data;
304 on_child_death(child, (CallbackFn) thumbnail_child_done, info);
307 /****************************************************************
308 * INTERNAL FUNCTIONS *
309 ****************************************************************/
311 /* Create a thumbnail file for this image.
312 * XXX: Thumbnails should be deleted somewhere!
314 static void save_thumbnail(const char *pathname, GdkPixbuf *full)
316 struct stat info;
317 gchar *path;
318 int original_width, original_height;
319 GString *to;
320 char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
321 mode_t old_mask;
322 int name_len;
323 GdkPixbuf *thumb;
325 thumb = scale_pixbuf(full, HUGE_WIDTH, HUGE_HEIGHT);
327 original_width = gdk_pixbuf_get_width(full);
328 original_height = gdk_pixbuf_get_height(full);
330 if (mc_stat(pathname, &info) != 0)
331 return;
333 swidth = g_strdup_printf("%d", original_width);
334 sheight = g_strdup_printf("%d", original_height);
335 ssize = g_strdup_printf("%" SIZE_FMT, info.st_size);
336 smtime = g_strdup_printf("%ld", info.st_mtime);
338 path = pathdup(pathname);
339 uri = g_strconcat("file://", path, NULL);
340 md5 = md5_hash(uri);
341 g_free(path);
343 to = g_string_new(home_dir);
344 g_string_append(to, "/.thumbnails");
345 mkdir(to->str, 0700);
346 g_string_append(to, "/normal/");
347 mkdir(to->str, 0700);
348 g_string_append(to, md5);
349 name_len = to->len + 4; /* Truncate to this length when renaming */
350 g_string_sprintfa(to, ".png.ROX-Filer-%ld", (long) getpid());
352 g_free(md5);
354 old_mask = umask(0077);
355 gdk_pixbuf_save(thumb, to->str, "png", NULL,
356 "tEXt::Thumb::Image::Width", swidth,
357 "tEXt::Thumb::Image::Height", sheight,
358 "tEXt::Thumb::Size", ssize,
359 "tEXt::Thumb::MTime", smtime,
360 "tEXt::Thumb::URI", uri,
361 "tEXt::Software", PROJECT,
362 NULL);
363 umask(old_mask);
365 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
366 * a race condition if two programs create the same thumb at
367 * once.
370 gchar *final;
372 final = g_strndup(to->str, name_len);
373 if (rename(to->str, final))
374 g_warning("Failed to rename '%s' to '%s': %s",
375 to->str, final, g_strerror(errno));
376 g_free(final);
379 g_string_free(to, TRUE);
380 g_free(swidth);
381 g_free(sheight);
382 g_free(ssize);
383 g_free(smtime);
384 g_free(uri);
387 /* Called in a subprocess. Load path and create the thumbnail
388 * file. Parent will notice when we die.
390 static void child_create_thumbnail(const gchar *path)
392 GdkPixbuf *image;
394 image = gdk_pixbuf_new_from_file(path, NULL);
396 if (image)
397 save_thumbnail(path, image);
399 /* (no need to unref, as we're about to exit) */
402 /* Called when the child process exits */
403 static void thumbnail_child_done(ChildThumbnail *info)
405 GdkPixbuf *thumb;
407 thumb = get_thumbnail_for(info->path);
409 if (thumb)
411 MaskedPixmap *image;
413 image = masked_pixmap_new(thumb);
414 g_object_unref(thumb);
416 g_fscache_insert(pixmap_cache, info->path, image, FALSE);
417 g_object_unref(image);
419 info->callback(info->data, info->path);
421 else
422 info->callback(info->data, NULL);
424 g_free(info->path);
425 g_free(info);
428 /* Check if we have an up-to-date thumbnail for this image.
429 * If so, return it. Otherwise, returns NULL.
431 static GdkPixbuf *get_thumbnail_for(const char *pathname)
433 GdkPixbuf *thumb = NULL;
434 char *thumb_path, *md5, *uri, *path;
435 const char *ssize, *smtime;
436 struct stat info;
438 path = pathdup(pathname);
439 uri = g_strconcat("file://", path, NULL);
440 md5 = md5_hash(uri);
441 g_free(uri);
443 thumb_path = g_strdup_printf("%s/.thumbnails/normal/%s.png",
444 home_dir, md5);
445 g_free(md5);
447 thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
448 if (!thumb)
449 goto err;
451 /* Note that these don't need freeing... */
452 ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
453 if (!ssize)
454 goto err;
456 smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
457 if (!smtime)
458 goto err;
460 if (mc_stat(path, &info) != 0)
461 goto err;
463 if (info.st_mtime != atol(smtime) || info.st_size != atol(ssize))
464 goto err;
466 goto out;
467 err:
468 if (thumb)
469 gdk_pixbuf_unref(thumb);
470 thumb = NULL;
471 out:
472 g_free(path);
473 g_free(thumb_path);
474 return thumb;
477 /* Load the image 'path' and return a pointer to the resulting
478 * MaskedPixmap. NULL on failure.
479 * Doesn't check for thumbnails (this is for small icons).
481 static MaskedPixmap *image_from_file(const char *path)
483 GdkPixbuf *pixbuf;
484 MaskedPixmap *image;
485 GError *error = NULL;
487 pixbuf = gdk_pixbuf_new_from_file(path, &error);
488 if (!pixbuf)
490 g_print("%s\n", error ? error->message : _("Unknown error"));
491 g_error_free(error);
492 return NULL;
495 image = masked_pixmap_new(pixbuf);
497 gdk_pixbuf_unref(pixbuf);
499 return image;
502 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
503 * If src is small enough, then ref it and return that.
505 static GdkPixbuf *scale_pixbuf(GdkPixbuf *src, int max_w, int max_h)
507 int w, h;
509 w = gdk_pixbuf_get_width(src);
510 h = gdk_pixbuf_get_height(src);
512 if (w <= max_w && h <= max_h)
514 gdk_pixbuf_ref(src);
515 return src;
517 else
519 float scale_x = ((float) w) / max_w;
520 float scale_y = ((float) h) / max_h;
521 float scale = MAX(scale_x, scale_y);
522 int dest_w = w / scale;
523 int dest_h = h / scale;
525 return gdk_pixbuf_scale_simple(src,
526 MAX(dest_w, 1),
527 MAX(dest_h, 1),
528 GDK_INTERP_BILINEAR);
532 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
533 * If src is that size or bigger, then ref it and return that.
535 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h)
537 int w, h;
539 w = gdk_pixbuf_get_width(src);
540 h = gdk_pixbuf_get_height(src);
542 if (w == 0 || h == 0 || (w >= max_w && h >= max_h))
544 gdk_pixbuf_ref(src);
545 return src;
547 else
549 float scale_x = max_w / ((float) w);
550 float scale_y = max_h / ((float) h);
551 float scale = MIN(scale_x, scale_y);
553 return gdk_pixbuf_scale_simple(src,
554 w * scale,
555 h * scale,
556 GDK_INTERP_BILINEAR);
560 /* Return a pointer to the (static) bad image. The ref counter will ensure
561 * that the image is never freed.
563 static MaskedPixmap *get_bad_image(void)
565 GdkPixbuf *bad;
566 MaskedPixmap *mp;
568 bad = gdk_pixbuf_new_from_xpm_data(bad_xpm);
569 mp = masked_pixmap_new(bad);
570 gdk_pixbuf_unref(bad);
572 return mp;
575 /* Called now and then to clear out old pixmaps */
576 static gint purge(gpointer data)
578 g_fscache_purge(pixmap_cache, PIXMAP_PURGE_TIME);
580 return TRUE;
583 static gpointer parent_class;
585 static void masked_pixmap_finialize(GObject *object)
587 MaskedPixmap *mp = (MaskedPixmap *) object;
589 if (mp->src_pixbuf)
591 g_object_unref(mp->src_pixbuf);
592 mp->src_pixbuf = NULL;
595 if (mp->huge_pixbuf)
597 g_object_unref(mp->huge_pixbuf);
598 mp->huge_pixbuf = NULL;
600 if (mp->huge_pixbuf_lit)
602 g_object_unref(mp->huge_pixbuf_lit);
603 mp->huge_pixbuf_lit = NULL;
606 if (mp->pixbuf)
608 g_object_unref(mp->pixbuf);
609 mp->pixbuf = NULL;
611 if (mp->pixbuf_lit)
613 g_object_unref(mp->pixbuf_lit);
614 mp->pixbuf_lit = NULL;
617 if (mp->sm_pixbuf)
619 g_object_unref(mp->sm_pixbuf);
620 mp->sm_pixbuf = NULL;
622 if (mp->sm_pixbuf_lit)
624 g_object_unref(mp->sm_pixbuf_lit);
625 mp->sm_pixbuf_lit = NULL;
628 G_OBJECT_CLASS(parent_class)->finalize(object);
631 static void masked_pixmap_class_init(gpointer gclass, gpointer data)
633 GObjectClass *object = (GObjectClass *) gclass;
635 parent_class = g_type_class_peek_parent(gclass);
637 object->finalize = masked_pixmap_finialize;
640 static void masked_pixmap_init(GTypeInstance *object, gpointer gclass)
642 MaskedPixmap *mp = (MaskedPixmap *) object;
644 mp->src_pixbuf = NULL;
646 mp->huge_pixbuf = NULL;
647 mp->huge_pixbuf_lit = NULL;
648 mp->huge_width = -1;
649 mp->huge_height = -1;
651 mp->pixbuf = NULL;
652 mp->pixbuf_lit = NULL;
653 mp->width = -1;
654 mp->height = -1;
656 mp->sm_pixbuf = NULL;
657 mp->sm_pixbuf_lit = NULL;
658 mp->sm_width = -1;
659 mp->sm_height = -1;
662 static GType masked_pixmap_get_type(void)
664 static GType type = 0;
666 if (!type)
668 static const GTypeInfo info =
670 sizeof (MaskedPixmapClass),
671 NULL, /* base_init */
672 NULL, /* base_finalise */
673 masked_pixmap_class_init,
674 NULL, /* class_finalise */
675 NULL, /* class_data */
676 sizeof(MaskedPixmap),
677 0, /* n_preallocs */
678 masked_pixmap_init
681 type = g_type_register_static(G_TYPE_OBJECT, "MaskedPixmap",
682 &info, 0);
685 return type;
688 MaskedPixmap *masked_pixmap_new(GdkPixbuf *full_size)
690 MaskedPixmap *mp;
691 GdkPixbuf *src_pixbuf, *normal_pixbuf;
693 g_return_val_if_fail(full_size != NULL, NULL);
695 src_pixbuf = scale_pixbuf(full_size, HUGE_WIDTH, HUGE_HEIGHT);
696 g_return_val_if_fail(src_pixbuf != NULL, NULL);
698 normal_pixbuf = scale_pixbuf(src_pixbuf, ICON_WIDTH, ICON_HEIGHT);
699 g_return_val_if_fail(normal_pixbuf != NULL, NULL);
701 mp = g_object_new(masked_pixmap_get_type(), NULL);
703 mp->src_pixbuf = src_pixbuf;
705 mp->pixbuf = normal_pixbuf;
706 mp->pixbuf_lit = create_spotlight_pixbuf(normal_pixbuf, 0x000099, 128);
707 mp->width = gdk_pixbuf_get_width(normal_pixbuf);
708 mp->height = gdk_pixbuf_get_height(normal_pixbuf);
710 return mp;
713 /* Stolen from eel...and modified to colourize the pixbuf.
714 * 'alpha' is the transparency of 'color' (0xRRGGBB):
715 * 0 = fully opaque, 255 = fully transparent.
717 static GdkPixbuf *create_spotlight_pixbuf(GdkPixbuf *src,
718 guint32 color,
719 guchar alpha)
721 GdkPixbuf *dest;
722 int i, j;
723 int width, height, has_alpha, src_row_stride, dst_row_stride;
724 guchar *target_pixels, *original_pixels;
725 guchar *pixsrc, *pixdest;
726 guchar r, g, b;
727 gint n_channels;
729 n_channels = gdk_pixbuf_get_n_channels(src);
730 has_alpha = gdk_pixbuf_get_has_alpha(src);
731 width = gdk_pixbuf_get_width(src);
732 height = gdk_pixbuf_get_height(src);
734 g_return_val_if_fail(gdk_pixbuf_get_colorspace(src) ==
735 GDK_COLORSPACE_RGB, NULL);
736 g_return_val_if_fail((!has_alpha && n_channels == 3) ||
737 (has_alpha && n_channels == 4), NULL);
738 g_return_val_if_fail(gdk_pixbuf_get_bits_per_sample(src) == 8, NULL);
740 dest = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(src), has_alpha,
741 gdk_pixbuf_get_bits_per_sample(src),
742 width, height);
744 dst_row_stride = gdk_pixbuf_get_rowstride(dest);
745 src_row_stride = gdk_pixbuf_get_rowstride(src);
746 target_pixels = gdk_pixbuf_get_pixels(dest);
747 original_pixels = gdk_pixbuf_get_pixels(src);
749 r = (color & 0xff0000) >> 16;
750 g = (color & 0xff00) >> 8;
751 b = color & 0xff;
753 for (i = 0; i < height; i++)
755 gint tmp;
757 pixdest = target_pixels + i * dst_row_stride;
758 pixsrc = original_pixels + i * src_row_stride;
759 for (j = 0; j < width; j++)
761 tmp = (*pixsrc++ * alpha + r * (255 - alpha)) / 255;
762 *pixdest++ = (guchar) MIN(255, tmp);
763 tmp = (*pixsrc++ * alpha + g * (255 - alpha)) / 255;
764 *pixdest++ = (guchar) MIN(255, tmp);
765 tmp = (*pixsrc++ * alpha + b * (255 - alpha)) / 255;
766 *pixdest++ = (guchar) MIN(255, tmp);
767 if (has_alpha)
768 *pixdest++ = *pixsrc++;
772 return dest;
775 /* Load all the standard pixmaps. Also sets the default window icon. */
776 static void load_default_pixmaps(void)
778 GdkPixbuf *pixbuf;
779 GError *error = NULL;
781 im_error = mp_from_stock(GTK_STOCK_DIALOG_WARNING);
782 im_unknown = mp_from_stock(GTK_STOCK_DIALOG_QUESTION);
783 im_symlink = load_pixmap("symlink");
785 im_unmounted = load_pixmap("mount");
786 im_mounted = load_pixmap("mounted");
787 im_appdir = load_pixmap("application");
789 im_dirs = load_pixmap("dirs");
791 pixbuf = gdk_pixbuf_new_from_file(
792 make_path(app_dir, ".DirIcon")->str, &error);
793 if (pixbuf)
795 GList *icon_list;
797 icon_list = g_list_append(NULL, pixbuf);
798 gtk_window_set_default_icon_list(icon_list);
799 g_list_free(icon_list);
801 g_object_unref(G_OBJECT(pixbuf));
803 else
805 g_print("%s\n", error ? error->message : _("Unknown error"));
806 g_error_free(error);