Don't allow SetIcon/UnsetIcon SOAP calls to (un)set icons set by the user.
[rox-filer/dt.git] / ROX-Filer / src / pixmaps.c
blobc68b364e958b35662ffc4bf8c940adfa6b5600f5
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* pixmaps.c - code for handling pixbufs (despite the name!) */
22 #include "config.h"
23 #define PIXMAPS_C
25 /* Remove pixmaps from the cache when they haven't been accessed for
26 * this period of time (seconds).
29 #define PIXMAP_PURGE_TIME 1200
30 #define PIXMAP_THUMB_SIZE 128
31 #define PIXMAP_THUMB_TOO_OLD_TIME 5
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <string.h>
41 #include <gtk/gtk.h>
43 #include "global.h"
45 #include "fscache.h"
46 #include "support.h"
47 #include "gui_support.h"
48 #include "pixmaps.h"
49 #include "main.h"
50 #include "filer.h"
51 #include "dir.h"
52 #include "diritem.h"
53 #include "choices.h"
54 #include "options.h"
55 #include "action.h"
56 #include "type.h"
58 GFSCache *pixmap_cache = NULL;
59 GFSCache *desktop_icon_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;
86 MaskedPixmap *im_xattr;
88 MaskedPixmap *im_dirs;
90 typedef struct _ChildThumbnail ChildThumbnail;
92 /* There is one of these for each active child process */
93 struct _ChildThumbnail {
94 gchar *path;
95 GFunc callback;
96 gpointer data;
99 static const char *stocks[] = {
100 ROX_STOCK_SHOW_DETAILS,
101 ROX_STOCK_SHOW_HIDDEN,
102 ROX_STOCK_SELECT,
103 ROX_STOCK_MOUNT,
104 ROX_STOCK_MOUNTED,
105 ROX_STOCK_SYMLINK,
106 ROX_STOCK_XATTR,
109 static GtkIconSize mount_icon_size = -1;
111 /* Static prototypes */
113 static void load_default_pixmaps(void);
114 static gint purge(gpointer data);
115 static MaskedPixmap *image_from_file(const char *path);
116 static MaskedPixmap *image_from_desktop_file(const char *path);
117 static MaskedPixmap *get_bad_image(void);
118 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h);
119 static GdkPixbuf *get_thumbnail_for(const char *path);
120 static void thumbnail_child_done(ChildThumbnail *info);
121 static void child_create_thumbnail(const gchar *path, MIME_type *type);
122 static GList *thumbs_purge_cache(Option *option, xmlNode *node, guchar *label);
123 static gchar *thumbnail_path(const gchar *path);
124 static gchar *thumbnail_program(MIME_type *type);
125 static GdkPixbuf *extract_tiff_thumbnail(const gchar *path);
127 /****************************************************************
128 * EXTERNAL INTERFACE *
129 ****************************************************************/
131 void pixmaps_init(void)
133 GtkIconFactory *factory;
134 int i;
136 gtk_widget_push_colormap(gdk_rgb_get_colormap());
138 pixmap_cache = g_fscache_new((GFSLoadFunc) image_from_file, NULL, NULL);
139 desktop_icon_cache = g_fscache_new((GFSLoadFunc) image_from_desktop_file, NULL, NULL);
141 g_timeout_add(10000, purge, NULL);
143 factory = gtk_icon_factory_new();
144 for (i = 0; i < G_N_ELEMENTS(stocks); i++)
146 GdkPixbuf *pixbuf;
147 GError *error = NULL;
148 gchar *path;
149 GtkIconSet *iset;
150 const gchar *name = stocks[i];
152 path = g_strconcat(app_dir, "/images/", name, ".png", NULL);
153 pixbuf = gdk_pixbuf_new_from_file(path, &error);
154 if (!pixbuf)
156 g_warning("%s", error->message);
157 g_error_free(error);
158 pixbuf = gdk_pixbuf_new_from_xpm_data(bad_xpm);
160 g_free(path);
162 iset = gtk_icon_set_new_from_pixbuf(pixbuf);
163 g_object_unref(G_OBJECT(pixbuf));
164 gtk_icon_factory_add(factory, name, iset);
165 gtk_icon_set_unref(iset);
167 gtk_icon_factory_add_default(factory);
169 mount_icon_size = gtk_icon_size_register("rox-mount-size", 14, 14);
171 load_default_pixmaps();
173 option_register_widget("thumbs-purge-cache", thumbs_purge_cache);
176 /* Load image <appdir>/images/name.png.
177 * Always returns with a valid image.
179 MaskedPixmap *load_pixmap(const char *name)
181 guchar *path;
182 MaskedPixmap *retval;
184 path = g_strconcat(app_dir, "/images/", name, ".png", NULL);
185 retval = image_from_file(path);
186 g_free(path);
188 if (!retval)
189 retval = get_bad_image();
191 return retval;
194 /* Create a MaskedPixmap from a GTK stock ID. Always returns
195 * a valid image.
197 static MaskedPixmap *mp_from_stock(const char *stock_id, int size)
199 GtkIconSet *icon_set;
200 GdkPixbuf *pixbuf;
201 MaskedPixmap *retval;
203 icon_set = gtk_icon_factory_lookup_default(stock_id);
204 if (!icon_set)
205 return get_bad_image();
207 pixbuf = gtk_icon_set_render_icon(icon_set,
208 gtk_widget_get_default_style(), /* Gtk bug */
209 GTK_TEXT_DIR_LTR,
210 GTK_STATE_NORMAL,
211 size,
212 NULL,
213 NULL);
214 retval = masked_pixmap_new(pixbuf);
215 gdk_pixbuf_unref(pixbuf);
217 return retval;
220 void pixmap_make_huge(MaskedPixmap *mp)
222 if (mp->huge_pixbuf)
223 return;
225 g_return_if_fail(mp->src_pixbuf != NULL);
227 /* Limit to small size now, otherwise they get scaled up in mixed mode.
228 * Also looked ugly.
230 mp->huge_pixbuf = scale_pixbuf_up(mp->src_pixbuf,
231 SMALL_WIDTH, SMALL_HEIGHT);
233 if (!mp->huge_pixbuf)
235 mp->huge_pixbuf = mp->src_pixbuf;
236 g_object_ref(mp->huge_pixbuf);
239 mp->huge_width = gdk_pixbuf_get_width(mp->huge_pixbuf);
240 mp->huge_height = gdk_pixbuf_get_height(mp->huge_pixbuf);
243 void pixmap_make_small(MaskedPixmap *mp)
245 if (mp->sm_pixbuf)
246 return;
248 g_return_if_fail(mp->src_pixbuf != NULL);
250 mp->sm_pixbuf = scale_pixbuf(mp->src_pixbuf, SMALL_WIDTH, SMALL_HEIGHT);
252 if (!mp->sm_pixbuf)
254 mp->sm_pixbuf = mp->src_pixbuf;
255 g_object_ref(mp->sm_pixbuf);
258 mp->sm_width = gdk_pixbuf_get_width(mp->sm_pixbuf);
259 mp->sm_height = gdk_pixbuf_get_height(mp->sm_pixbuf);
262 /* Load image 'path' in the background and insert into pixmap_cache.
263 * Call callback(data, path) when done (path is NULL => error).
264 * If the image is already uptodate, or being created already, calls the
265 * callback right away.
267 void pixmap_background_thumb(const gchar *path, GFunc callback, gpointer data)
269 gboolean found;
270 MaskedPixmap *image;
271 GdkPixbuf *pixbuf;
272 pid_t child;
273 ChildThumbnail *info;
274 MIME_type *type;
275 gchar *thumb_prog;
278 image = g_fscache_lookup_full(pixmap_cache, path,
279 FSCACHE_LOOKUP_ONLY_NEW, &found);
281 if (found)
283 /* Thumbnail is known, or being created */
284 if (image)
285 g_object_unref(image);
286 callback(data, NULL);
287 return;
290 g_return_if_fail(image == NULL);
292 pixbuf = get_thumbnail_for(path);
294 if (!pixbuf)
296 struct stat info1, info2;
297 char *dir;
299 // Skip zero-byte files. They're either empty, or special (may cause
300 // us to hang, e.g. /proc/kmsg).
301 if (mc_stat(path, &info1) == 0 && info1.st_size == 0) {
302 callback(data, NULL);
303 return;
306 dir = g_path_get_dirname(path);
308 /* If the image itself is in ~/.thumbnails, load it now
309 * (ie, don't create thumbnails for thumbnails!).
311 if (mc_stat(dir, &info1) != 0)
313 callback(data, NULL);
314 g_free(dir);
315 return;
317 g_free(dir);
319 if (mc_stat(make_path(home_dir, ".thumbnails/normal"),
320 &info2) == 0 &&
321 info1.st_dev == info2.st_dev &&
322 info1.st_ino == info2.st_ino)
324 pixbuf = rox_pixbuf_new_from_file_at_scale(path,
325 PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE, TRUE, NULL);
326 if (!pixbuf)
328 g_fscache_insert(pixmap_cache,
329 path, NULL, TRUE);
330 callback(data, NULL);
331 return;
336 if (pixbuf)
338 MaskedPixmap *image;
340 image = masked_pixmap_new(pixbuf);
341 gdk_pixbuf_unref(pixbuf);
342 g_fscache_insert(pixmap_cache, path, image, TRUE);
343 callback(data, (gchar *) path);
344 g_object_unref(G_OBJECT(image));
345 return;
348 type = type_from_path(path);
349 if (!type)
350 type = text_plain;
352 /* Add an entry, set to NULL, so no-one else tries to load this
353 * image.
355 g_fscache_insert(pixmap_cache, path, NULL, TRUE);
357 thumb_prog = thumbnail_program(type);
359 /* Only attempt to load 'images' types ourselves */
360 if (thumb_prog == NULL && strcmp(type->media_type, "image") != 0)
362 callback(data, NULL);
363 return; /* Don't know how to handle this type */
366 child = fork();
368 if (child == -1)
370 g_free(thumb_prog);
371 delayed_error("fork(): %s", g_strerror(errno));
372 callback(data, NULL);
373 return;
376 if (child == 0)
378 /* We are the child process. (We are sloppy with freeing
379 memory, but since we go away very quickly, that's ok.) */
380 if (thumb_prog)
382 DirItem *item;
384 item = diritem_new(g_basename(thumb_prog));
386 diritem_restat(thumb_prog, item, NULL);
387 if (item->flags & ITEM_FLAG_APPDIR)
388 thumb_prog = g_strconcat(thumb_prog, "/AppRun",
389 NULL);
391 execl(thumb_prog, thumb_prog, path,
392 thumbnail_path(path),
393 g_strdup_printf("%d", PIXMAP_THUMB_SIZE),
394 NULL);
395 _exit(1);
398 child_create_thumbnail(path, type);
399 _exit(0);
402 g_free(thumb_prog);
404 info = g_new(ChildThumbnail, 1);
405 info->path = g_strdup(path);
406 info->callback = callback;
407 info->data = data;
408 on_child_death(child, (CallbackFn) thumbnail_child_done, info);
411 /****************************************************************
412 * INTERNAL FUNCTIONS *
413 ****************************************************************/
415 /* Create a thumbnail file for this image */
416 static void save_thumbnail(const char *pathname, GdkPixbuf *full)
418 struct stat info;
419 gchar *path;
420 int original_width, original_height;
421 GString *to;
422 char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
423 mode_t old_mask;
424 int name_len;
425 GdkPixbuf *thumb;
427 thumb = scale_pixbuf(full, PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE);
429 original_width = gdk_pixbuf_get_width(full);
430 original_height = gdk_pixbuf_get_height(full);
432 if (mc_stat(pathname, &info) != 0)
433 return;
435 swidth = g_strdup_printf("%d", original_width);
436 sheight = g_strdup_printf("%d", original_height);
437 ssize = g_strdup_printf("%" SIZE_FMT, info.st_size);
438 smtime = g_strdup_printf("%ld", (long) info.st_mtime);
440 path = pathdup(pathname);
441 uri = g_filename_to_uri(path, NULL, NULL);
442 if (!uri)
443 uri = g_strconcat("file://", path, NULL);
444 md5 = md5_hash(uri);
445 g_free(path);
447 to = g_string_new(home_dir);
448 g_string_append(to, "/.thumbnails");
449 mkdir(to->str, 0700);
450 g_string_append(to, "/normal/");
451 mkdir(to->str, 0700);
452 g_string_append(to, md5);
453 name_len = to->len + 4; /* Truncate to this length when renaming */
454 g_string_append_printf(to, ".png.ROX-Filer-%ld", (long) getpid());
456 g_free(md5);
458 old_mask = umask(0077);
459 gdk_pixbuf_save(thumb, to->str, "png", NULL,
460 "tEXt::Thumb::Image::Width", swidth,
461 "tEXt::Thumb::Image::Height", sheight,
462 "tEXt::Thumb::Size", ssize,
463 "tEXt::Thumb::MTime", smtime,
464 "tEXt::Thumb::URI", uri,
465 "tEXt::Software", PROJECT,
466 NULL);
467 umask(old_mask);
469 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
470 * a race condition if two programs create the same thumb at
471 * once.
474 gchar *final;
476 final = g_strndup(to->str, name_len);
477 if (rename(to->str, final))
478 g_warning("Failed to rename '%s' to '%s': %s",
479 to->str, final, g_strerror(errno));
480 g_free(final);
483 g_string_free(to, TRUE);
484 g_free(swidth);
485 g_free(sheight);
486 g_free(ssize);
487 g_free(smtime);
488 g_free(uri);
491 static gchar *thumbnail_path(const char *path)
493 gchar *uri, *md5;
494 GString *to;
495 gchar *ans;
497 uri = g_filename_to_uri(path, NULL, NULL);
498 if(!uri)
499 uri = g_strconcat("file://", path, NULL);
500 md5 = md5_hash(uri);
502 to = g_string_new(home_dir);
503 g_string_append(to, "/.thumbnails");
504 mkdir(to->str, 0700);
505 g_string_append(to, "/normal/");
506 mkdir(to->str, 0700);
507 g_string_append(to, md5);
508 g_string_append(to, ".png");
510 g_free(md5);
511 g_free(uri);
513 ans=to->str;
514 g_string_free(to, FALSE);
516 return ans;
519 /* Return a program to create thumbnails for files of this type.
520 * NULL to try to make it ourself (using gdk).
521 * g_free the result.
523 static gchar *thumbnail_program(MIME_type *type)
525 gchar *leaf;
526 gchar *path;
528 if (!type)
529 return NULL;
531 leaf = g_strconcat(type->media_type, "_", type->subtype, NULL);
532 path = choices_find_xdg_path_load(leaf, "MIME-thumb", SITE);
533 g_free(leaf);
534 if (path)
536 return path;
539 path = choices_find_xdg_path_load(type->media_type, "MIME-thumb",
540 SITE);
542 return path;
545 /* Called in a subprocess. Load path and create the thumbnail
546 * file. Parent will notice when we die.
548 static void child_create_thumbnail(const gchar *path, MIME_type *type)
550 GdkPixbuf *image=NULL;
552 if(strcmp(type->subtype, "jpeg")==0)
553 image=extract_tiff_thumbnail(path);
555 if(!image)
556 image = rox_pixbuf_new_from_file_at_scale(path,
557 PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE, TRUE, NULL);
559 if (image)
560 save_thumbnail(path, image);
562 /* (no need to unref, as we're about to exit) */
565 /* Called when the child process exits */
566 static void thumbnail_child_done(ChildThumbnail *info)
568 GdkPixbuf *thumb;
570 thumb = get_thumbnail_for(info->path);
572 if (thumb)
574 MaskedPixmap *image;
576 image = masked_pixmap_new(thumb);
577 g_object_unref(thumb);
579 g_fscache_insert(pixmap_cache, info->path, image, FALSE);
580 g_object_unref(image);
582 info->callback(info->data, info->path);
584 else
585 info->callback(info->data, NULL);
587 g_free(info->path);
588 g_free(info);
591 /* Check if we have an up-to-date thumbnail for this image.
592 * If so, return it. Otherwise, returns NULL.
594 static GdkPixbuf *get_thumbnail_for(const char *pathname)
596 GdkPixbuf *thumb = NULL;
597 char *thumb_path, *md5, *uri, *path;
598 const char *ssize, *smtime;
599 struct stat info;
600 time_t ttime, now;
602 path = pathdup(pathname);
603 uri = g_filename_to_uri(path, NULL, NULL);
604 if(!uri)
605 uri = g_strconcat("file://", path, NULL);
606 md5 = md5_hash(uri);
607 g_free(uri);
609 thumb_path = g_strdup_printf("%s/.thumbnails/normal/%s.png",
610 home_dir, md5);
611 g_free(md5);
613 thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
614 if (!thumb)
615 goto err;
617 /* Note that these don't need freeing... */
618 ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
619 /* This is optional, so don't flag an error if it is missing */
621 smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
622 if (!smtime)
623 goto err;
625 if (mc_stat(path, &info) != 0)
626 goto err;
628 ttime=(time_t) atol(smtime);
629 time(&now);
630 if (info.st_mtime != ttime && now>ttime+PIXMAP_THUMB_TOO_OLD_TIME)
631 goto err;
633 if (ssize && info.st_size < atol(ssize))
634 goto err;
636 goto out;
637 err:
638 if (thumb)
639 gdk_pixbuf_unref(thumb);
640 thumb = NULL;
641 out:
642 g_free(path);
643 g_free(thumb_path);
644 return thumb;
647 /* Load the image 'path' and return a pointer to the resulting
648 * MaskedPixmap. NULL on failure.
649 * Doesn't check for thumbnails (this is for small icons).
651 static MaskedPixmap *image_from_file(const char *path)
653 GdkPixbuf *pixbuf;
654 MaskedPixmap *image;
655 GError *error = NULL;
657 pixbuf = gdk_pixbuf_new_from_file(path, &error);
658 if (!pixbuf)
660 g_warning("%s\n", error->message);
661 g_error_free(error);
662 return NULL;
665 image = masked_pixmap_new(pixbuf);
667 gdk_pixbuf_unref(pixbuf);
669 return image;
672 /* Load this icon named by this .desktop file from the current theme.
673 * NULL on failure.
675 static MaskedPixmap *image_from_desktop_file(const char *path)
677 GError *error = NULL;
678 MaskedPixmap *image = NULL;
679 char *icon = NULL;
681 icon = get_value_from_desktop_file(path,
682 "Desktop Entry", "Icon", &error);
683 if (error)
685 g_warning("Failed to parse .desktop file '%s':\n%s",
686 path, error->message);
687 goto err;
689 if (!icon)
690 goto err;
692 if (icon[0] == '/')
693 image = image_from_file(icon);
694 else
696 GdkPixbuf *pixbuf;
697 int tmp_fd;
698 char *extension;
700 /* For some unknown reason, some icon names have extensions.
701 * Remove them.
703 extension = strrchr(icon, '.');
704 if (extension && (strcmp(extension, ".png") == 0
705 || strcmp(extension, ".xpm") == 0
706 || strcmp(extension, ".svg") == 0))
708 *extension = '\0';
711 /* SVG reader is very noisy, so redirect stderr to stdout */
712 tmp_fd = dup(2);
713 dup2(1, 2);
714 pixbuf = gtk_icon_theme_load_icon(icon_theme, icon, HUGE_WIDTH,
715 0, NULL);
716 dup2(tmp_fd, 2);
717 close(tmp_fd);
719 if (pixbuf == NULL)
720 goto err; /* Might just not be in the theme */
722 image = masked_pixmap_new(pixbuf);
723 g_object_unref(pixbuf);
725 err:
726 if (error != NULL)
727 g_error_free(error);
728 if (icon != NULL)
729 g_free(icon);
730 return image;
733 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
734 * If src is small enough, then ref it and return that.
736 GdkPixbuf *scale_pixbuf(GdkPixbuf *src, int max_w, int max_h)
738 int w, h;
740 w = gdk_pixbuf_get_width(src);
741 h = gdk_pixbuf_get_height(src);
743 if (w <= max_w && h <= max_h)
745 gdk_pixbuf_ref(src);
746 return src;
748 else
750 float scale_x = ((float) w) / max_w;
751 float scale_y = ((float) h) / max_h;
752 float scale = MAX(scale_x, scale_y);
753 int dest_w = w / scale;
754 int dest_h = h / scale;
756 return gdk_pixbuf_scale_simple(src,
757 MAX(dest_w, 1),
758 MAX(dest_h, 1),
759 GDK_INTERP_BILINEAR);
763 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
764 * If src is that size or bigger, then ref it and return that.
766 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h)
768 int w, h;
770 w = gdk_pixbuf_get_width(src);
771 h = gdk_pixbuf_get_height(src);
773 if (w == 0 || h == 0 || w >= max_w || h >= max_h)
775 gdk_pixbuf_ref(src);
776 return src;
778 else
780 float scale_x = max_w / ((float) w);
781 float scale_y = max_h / ((float) h);
782 float scale = MIN(scale_x, scale_y);
784 return gdk_pixbuf_scale_simple(src,
785 w * scale,
786 h * scale,
787 GDK_INTERP_BILINEAR);
791 /* Return a pointer to the (static) bad image. The ref counter will ensure
792 * that the image is never freed.
794 static MaskedPixmap *get_bad_image(void)
796 GdkPixbuf *bad;
797 MaskedPixmap *mp;
799 bad = gdk_pixbuf_new_from_xpm_data(bad_xpm);
800 mp = masked_pixmap_new(bad);
801 gdk_pixbuf_unref(bad);
803 return mp;
806 /* Called now and then to clear out old pixmaps */
807 static gint purge(gpointer data)
809 g_fscache_purge(pixmap_cache, PIXMAP_PURGE_TIME);
811 return TRUE;
814 static gpointer parent_class;
816 static void masked_pixmap_finialize(GObject *object)
818 MaskedPixmap *mp = (MaskedPixmap *) object;
820 if (mp->src_pixbuf)
822 g_object_unref(mp->src_pixbuf);
823 mp->src_pixbuf = NULL;
826 if (mp->huge_pixbuf)
828 g_object_unref(mp->huge_pixbuf);
829 mp->huge_pixbuf = NULL;
831 if (mp->pixbuf)
833 g_object_unref(mp->pixbuf);
834 mp->pixbuf = NULL;
837 if (mp->sm_pixbuf)
839 g_object_unref(mp->sm_pixbuf);
840 mp->sm_pixbuf = NULL;
843 G_OBJECT_CLASS(parent_class)->finalize(object);
846 static void masked_pixmap_class_init(gpointer gclass, gpointer data)
848 GObjectClass *object = (GObjectClass *) gclass;
850 parent_class = g_type_class_peek_parent(gclass);
852 object->finalize = masked_pixmap_finialize;
855 static void masked_pixmap_init(GTypeInstance *object, gpointer gclass)
857 MaskedPixmap *mp = (MaskedPixmap *) object;
859 mp->src_pixbuf = NULL;
861 mp->huge_pixbuf = NULL;
862 mp->huge_width = -1;
863 mp->huge_height = -1;
865 mp->pixbuf = NULL;
866 mp->width = -1;
867 mp->height = -1;
869 mp->sm_pixbuf = NULL;
870 mp->sm_width = -1;
871 mp->sm_height = -1;
874 static GType masked_pixmap_get_type(void)
876 static GType type = 0;
878 if (!type)
880 static const GTypeInfo info =
882 sizeof (MaskedPixmapClass),
883 NULL, /* base_init */
884 NULL, /* base_finalise */
885 masked_pixmap_class_init,
886 NULL, /* class_finalise */
887 NULL, /* class_data */
888 sizeof(MaskedPixmap),
889 0, /* n_preallocs */
890 masked_pixmap_init
893 type = g_type_register_static(G_TYPE_OBJECT, "MaskedPixmap",
894 &info, 0);
897 return type;
900 MaskedPixmap *masked_pixmap_new(GdkPixbuf *full_size)
902 MaskedPixmap *mp;
903 GdkPixbuf *src_pixbuf, *normal_pixbuf;
905 g_return_val_if_fail(full_size != NULL, NULL);
907 src_pixbuf = scale_pixbuf(full_size, HUGE_WIDTH, HUGE_HEIGHT);
908 g_return_val_if_fail(src_pixbuf != NULL, NULL);
910 normal_pixbuf = scale_pixbuf(src_pixbuf, ICON_WIDTH, ICON_HEIGHT);
911 g_return_val_if_fail(normal_pixbuf != NULL, NULL);
913 mp = g_object_new(masked_pixmap_get_type(), NULL);
915 mp->src_pixbuf = src_pixbuf;
917 mp->pixbuf = normal_pixbuf;
918 mp->width = gdk_pixbuf_get_width(normal_pixbuf);
919 mp->height = gdk_pixbuf_get_height(normal_pixbuf);
921 return mp;
924 /* Load all the standard pixmaps. Also sets the default window icon. */
925 static void load_default_pixmaps(void)
927 GdkPixbuf *pixbuf;
928 GError *error = NULL;
930 im_error = mp_from_stock(GTK_STOCK_DIALOG_WARNING,
931 GTK_ICON_SIZE_DIALOG);
932 im_unknown = mp_from_stock(GTK_STOCK_DIALOG_QUESTION,
933 GTK_ICON_SIZE_DIALOG);
935 im_symlink = mp_from_stock(ROX_STOCK_SYMLINK, mount_icon_size);
936 im_unmounted = mp_from_stock(ROX_STOCK_MOUNT, mount_icon_size);
937 im_mounted = mp_from_stock(ROX_STOCK_MOUNTED, mount_icon_size);
938 im_xattr = mp_from_stock(ROX_STOCK_XATTR, mount_icon_size);
940 im_dirs = load_pixmap("dirs");
941 im_appdir = load_pixmap("application");
943 pixbuf = gdk_pixbuf_new_from_file(
944 make_path(app_dir, ".DirIcon"), &error);
945 if (pixbuf)
947 GList *icon_list;
949 icon_list = g_list_append(NULL, pixbuf);
950 gtk_window_set_default_icon_list(icon_list);
951 g_list_free(icon_list);
953 g_object_unref(G_OBJECT(pixbuf));
955 else
957 g_warning("%s\n", error->message);
958 g_error_free(error);
962 /* Also purges memory cache */
963 static void purge_disk_cache(GtkWidget *button, gpointer data)
965 char *path;
966 GList *list = NULL;
967 DIR *dir;
968 struct dirent *ent;
970 g_fscache_purge(pixmap_cache, 0);
972 path = g_strconcat(home_dir, "/.thumbnails/normal/", NULL);
974 dir = opendir(path);
975 if (!dir)
977 report_error(_("Can't delete thumbnails in %s:\n%s"),
978 path, g_strerror(errno));
979 goto out;
982 while ((ent = readdir(dir)))
984 if (ent->d_name[0] == '.')
985 continue;
986 list = g_list_prepend(list,
987 g_strconcat(path, ent->d_name, NULL));
990 closedir(dir);
992 if (list)
994 action_delete(list);
995 destroy_glist(&list);
997 else
998 info_message(_("There are no thumbnails to delete"));
999 out:
1000 g_free(path);
1003 static GList *thumbs_purge_cache(Option *option, xmlNode *node, guchar *label)
1005 GtkWidget *button, *align;
1007 g_return_val_if_fail(option == NULL, NULL);
1009 align = gtk_alignment_new(0, 0.5, 0, 0);
1010 button = button_new_mixed(GTK_STOCK_CLEAR,
1011 _("Purge thumbnails disk cache"));
1012 gtk_container_add(GTK_CONTAINER(align), button);
1013 g_signal_connect(button, "clicked", G_CALLBACK(purge_disk_cache), NULL);
1015 return g_list_append(NULL, align);
1018 /* Exif reading.
1019 * Based on Thierry Bousch's public domain exifdump.py.
1022 #define JPEG_FORMAT 0x201
1023 #define JPEG_FORMAT_LENGTH 0x202
1026 * Extract n-byte integer in Motorola (big-endian) format
1028 static inline long long s2n_motorola(const unsigned char *p, int len)
1030 long long a=0;
1031 int i;
1033 for(i=0; i<len; i++)
1034 a=(a<<8) | (int)(p[i]);
1036 return a;
1040 * Extract n-byte integer in Intel (little-endian) format
1042 static inline long long s2n_intel(const unsigned char *p, int len)
1044 long long a=0;
1045 int i;
1047 for(i=0; i<len; i++)
1048 a=a | (((int) p[i]) << (i*8));
1050 return a;
1054 * Extract n-byte integer from data
1056 static int s2n(const unsigned char *dat, int off, int len, char format)
1058 const unsigned char *p=dat+off;
1060 switch(format) {
1061 case 'I':
1062 return s2n_intel(p, len);
1064 case 'M':
1065 return s2n_motorola(p, len);
1068 return 0;
1072 * Load header of JPEG/Exif file and attempt to extract the embedded
1073 * thumbnail. Return NULL on failure.
1075 static GdkPixbuf *extract_tiff_thumbnail(const gchar *path)
1077 FILE *in;
1078 unsigned char header[256];
1079 int i, n;
1080 int length;
1081 unsigned char *data;
1082 char format;
1083 int ifd, entries;
1084 int thumb=0, tlength=0;
1085 GdkPixbuf *buf=NULL;
1087 in=fopen(path, "rb");
1088 if(!in) {
1089 return NULL;
1092 /* Check for Exif format */
1093 n=fread(header, 1, 12, in);
1094 if(n!=12 || strncmp((char *) header, "\377\330\377\341", 4)!=0 ||
1095 strncmp((char *)header+6, "Exif", 4)!=0) {
1096 fclose(in);
1097 return NULL;
1100 /* Read header */
1101 length=header[4]*256+header[5];
1102 data=g_new(unsigned char, length);
1103 n=fread(data, 1, length, in);
1104 fclose(in); /* File no longer needed */
1105 if(n!=length) {
1106 g_free(data);
1107 return NULL;
1110 /* Big or little endian (as 'M' or 'I') */
1111 format=data[0];
1113 /* Skip over main section */
1114 ifd=s2n(data, 4, 4, format);
1115 entries=s2n(data, ifd, 2, format);
1117 /* Second section contains data on thumbnail */
1118 ifd=s2n(data, ifd+2+12*entries, 4, format);
1119 entries=s2n(data, ifd, 2, format);
1121 /* Loop over the entries */
1122 for(i=0; i<entries; i++) {
1123 int entry=ifd+2+12*i;
1124 int tag=s2n(data, entry, 2, format);
1125 int type=s2n(data, entry+2, 2, format);
1126 int count, offset;
1128 count=s2n(data, entry+4, 4, format);
1129 offset=entry+8;
1131 if(type==4) {
1132 int val=(int) s2n(data, offset, 4, format);
1134 /* Only interested in two entries, the location of the thumbnail
1135 and its size */
1136 switch(tag) {
1137 case JPEG_FORMAT: thumb=val; break;
1138 case JPEG_FORMAT_LENGTH: tlength=val; break;
1143 if(thumb && tlength) {
1144 GError *err=NULL;
1145 GdkPixbufLoader *loader;
1147 /* Don't read outside the header (some files have incorrect data) */
1148 if(thumb+tlength>length)
1149 tlength=length-thumb;
1151 loader=gdk_pixbuf_loader_new();
1152 gdk_pixbuf_loader_write(loader, data+thumb, tlength, &err);
1153 if(err) {
1154 g_error_free(err);
1155 return NULL;
1158 gdk_pixbuf_loader_close(loader, &err);
1159 if(err) {
1160 g_error_free(err);
1161 return NULL;
1164 buf=gdk_pixbuf_loader_get_pixbuf(loader);
1165 g_object_ref(buf); /* Ref the image before we unref the loader */
1166 g_object_unref(loader);
1169 g_free(data);
1171 return buf;