4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2005, 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)
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
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!) */
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
32 #define PIXMAP_THUMB_SIZE 128
33 #define PIXMAP_THUMB_TOO_OLD_TIME 5
39 #include <sys/types.h>
49 #include "gui_support.h"
60 GFSCache
*pixmap_cache
= NULL
;
61 GFSCache
*desktop_icon_cache
= NULL
;
63 static const char * bad_xpm
[] = {
81 MaskedPixmap
*im_error
;
82 MaskedPixmap
*im_unknown
;
83 MaskedPixmap
*im_symlink
;
85 MaskedPixmap
*im_unmounted
;
86 MaskedPixmap
*im_mounted
;
87 MaskedPixmap
*im_appdir
;
88 MaskedPixmap
*im_xattr
;
90 MaskedPixmap
*im_dirs
;
92 typedef struct _ChildThumbnail ChildThumbnail
;
94 /* There is one of these for each active child process */
95 struct _ChildThumbnail
{
101 static const char *stocks
[] = {
102 ROX_STOCK_SHOW_DETAILS
,
103 ROX_STOCK_SHOW_HIDDEN
,
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
);
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
);
126 /****************************************************************
127 * EXTERNAL INTERFACE *
128 ****************************************************************/
130 void pixmaps_init(void)
132 GtkIconFactory
*factory
;
135 gtk_widget_push_colormap(gdk_rgb_get_colormap());
137 pixmap_cache
= g_fscache_new((GFSLoadFunc
) image_from_file
, NULL
, NULL
);
138 desktop_icon_cache
= g_fscache_new((GFSLoadFunc
) image_from_desktop_file
, NULL
, NULL
);
140 g_timeout_add(10000, purge
, NULL
);
142 factory
= gtk_icon_factory_new();
143 for (i
= 0; i
< G_N_ELEMENTS(stocks
); i
++)
146 GError
*error
= NULL
;
149 const gchar
*name
= stocks
[i
];
151 path
= g_strconcat(app_dir
, "/images/", name
, ".png", NULL
);
152 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
155 g_warning("%s", error
->message
);
157 pixbuf
= gdk_pixbuf_new_from_xpm_data(bad_xpm
);
161 iset
= gtk_icon_set_new_from_pixbuf(pixbuf
);
162 g_object_unref(G_OBJECT(pixbuf
));
163 gtk_icon_factory_add(factory
, name
, iset
);
164 gtk_icon_set_unref(iset
);
166 gtk_icon_factory_add_default(factory
);
168 mount_icon_size
= gtk_icon_size_register("rox-mount-size", 14, 14);
170 load_default_pixmaps();
172 option_register_widget("thumbs-purge-cache", thumbs_purge_cache
);
175 /* Load image <appdir>/images/name.png.
176 * Always returns with a valid image.
178 MaskedPixmap
*load_pixmap(const char *name
)
181 MaskedPixmap
*retval
;
183 path
= g_strconcat(app_dir
, "/images/", name
, ".png", NULL
);
184 retval
= image_from_file(path
);
188 retval
= get_bad_image();
193 /* Create a MaskedPixmap from a GTK stock ID. Always returns
196 static MaskedPixmap
*mp_from_stock(const char *stock_id
, int size
)
198 GtkIconSet
*icon_set
;
200 MaskedPixmap
*retval
;
202 icon_set
= gtk_icon_factory_lookup_default(stock_id
);
204 return get_bad_image();
206 pixbuf
= gtk_icon_set_render_icon(icon_set
,
207 gtk_widget_get_default_style(), /* Gtk bug */
213 retval
= masked_pixmap_new(pixbuf
);
214 gdk_pixbuf_unref(pixbuf
);
219 void pixmap_make_huge(MaskedPixmap
*mp
)
224 g_return_if_fail(mp
->src_pixbuf
!= NULL
);
226 /* Limit to small size now, otherwise they get scaled up in mixed mode.
229 mp
->huge_pixbuf
= scale_pixbuf_up(mp
->src_pixbuf
,
230 SMALL_WIDTH
, SMALL_HEIGHT
);
232 if (!mp
->huge_pixbuf
)
234 mp
->huge_pixbuf
= mp
->src_pixbuf
;
235 g_object_ref(mp
->huge_pixbuf
);
238 mp
->huge_width
= gdk_pixbuf_get_width(mp
->huge_pixbuf
);
239 mp
->huge_height
= gdk_pixbuf_get_height(mp
->huge_pixbuf
);
242 void pixmap_make_small(MaskedPixmap
*mp
)
247 g_return_if_fail(mp
->src_pixbuf
!= NULL
);
249 mp
->sm_pixbuf
= scale_pixbuf(mp
->src_pixbuf
, SMALL_WIDTH
, SMALL_HEIGHT
);
253 mp
->sm_pixbuf
= mp
->src_pixbuf
;
254 g_object_ref(mp
->sm_pixbuf
);
257 mp
->sm_width
= gdk_pixbuf_get_width(mp
->sm_pixbuf
);
258 mp
->sm_height
= gdk_pixbuf_get_height(mp
->sm_pixbuf
);
261 /* Load image 'path' in the background and insert into pixmap_cache.
262 * Call callback(data, path) when done (path is NULL => error).
263 * If the image is already uptodate, or being created already, calls the
264 * callback right away.
266 void pixmap_background_thumb(const gchar
*path
, GFunc callback
, gpointer data
)
272 ChildThumbnail
*info
;
277 image
= g_fscache_lookup_full(pixmap_cache
, path
,
278 FSCACHE_LOOKUP_ONLY_NEW
, &found
);
282 /* Thumbnail is known, or being created */
284 g_object_unref(image
);
285 callback(data
, NULL
);
289 g_return_if_fail(image
== NULL
);
291 pixbuf
= get_thumbnail_for(path
);
295 struct stat info1
, info2
;
298 dir
= g_path_get_dirname(path
);
300 /* If the image itself is in ~/.thumbnails, load it now
301 * (ie, don't create thumbnails for thumbnails!).
303 if (mc_stat(dir
, &info1
) != 0)
305 callback(data
, NULL
);
311 if (mc_stat(make_path(home_dir
, ".thumbnails/normal"),
313 info1
.st_dev
== info2
.st_dev
&&
314 info1
.st_ino
== info2
.st_ino
)
316 pixbuf
= rox_pixbuf_new_from_file_at_scale(path
,
317 PIXMAP_THUMB_SIZE
, PIXMAP_THUMB_SIZE
, TRUE
, NULL
);
320 g_fscache_insert(pixmap_cache
,
322 callback(data
, NULL
);
332 image
= masked_pixmap_new(pixbuf
);
333 gdk_pixbuf_unref(pixbuf
);
334 g_fscache_insert(pixmap_cache
, path
, image
, TRUE
);
335 callback(data
, (gchar
*) path
);
336 g_object_unref(G_OBJECT(image
));
340 type
= type_from_path(path
);
344 /* Add an entry, set to NULL, so no-one else tries to load this
347 g_fscache_insert(pixmap_cache
, path
, NULL
, TRUE
);
349 thumb_prog
= thumbnail_program(type
);
351 /* Only attempt to load 'images' types ourselves */
352 if (thumb_prog
== NULL
&& strcmp(type
->media_type
, "image") != 0)
354 callback(data
, NULL
);
355 return; /* Don't know how to handle this type */
363 delayed_error("fork(): %s", g_strerror(errno
));
364 callback(data
, NULL
);
370 /* We are the child process. (We are sloppy with freeing
371 memory, but since we go away very quickly, that's ok.) */
376 item
= diritem_new(g_basename(thumb_prog
));
378 diritem_restat(thumb_prog
, item
, NULL
);
379 if (item
->flags
& ITEM_FLAG_APPDIR
)
380 thumb_prog
= g_strconcat(thumb_prog
, "/AppRun",
383 execl(thumb_prog
, thumb_prog
, path
,
384 thumbnail_path(path
),
385 g_strdup_printf("%d", PIXMAP_THUMB_SIZE
),
390 child_create_thumbnail(path
);
396 info
= g_new(ChildThumbnail
, 1);
397 info
->path
= g_strdup(path
);
398 info
->callback
= callback
;
400 on_child_death(child
, (CallbackFn
) thumbnail_child_done
, info
);
403 /****************************************************************
404 * INTERNAL FUNCTIONS *
405 ****************************************************************/
407 /* Create a thumbnail file for this image */
408 static void save_thumbnail(const char *pathname
, GdkPixbuf
*full
)
412 int original_width
, original_height
;
414 char *md5
, *swidth
, *sheight
, *ssize
, *smtime
, *uri
;
419 thumb
= scale_pixbuf(full
, PIXMAP_THUMB_SIZE
, PIXMAP_THUMB_SIZE
);
421 original_width
= gdk_pixbuf_get_width(full
);
422 original_height
= gdk_pixbuf_get_height(full
);
424 if (mc_stat(pathname
, &info
) != 0)
427 swidth
= g_strdup_printf("%d", original_width
);
428 sheight
= g_strdup_printf("%d", original_height
);
429 ssize
= g_strdup_printf("%" SIZE_FMT
, info
.st_size
);
430 smtime
= g_strdup_printf("%ld", (long) info
.st_mtime
);
432 path
= pathdup(pathname
);
433 uri
= g_filename_to_uri(path
, NULL
, NULL
);
435 uri
= g_strconcat("file://", path
, NULL
);
439 to
= g_string_new(home_dir
);
440 g_string_append(to
, "/.thumbnails");
441 mkdir(to
->str
, 0700);
442 g_string_append(to
, "/normal/");
443 mkdir(to
->str
, 0700);
444 g_string_append(to
, md5
);
445 name_len
= to
->len
+ 4; /* Truncate to this length when renaming */
446 g_string_append_printf(to
, ".png.ROX-Filer-%ld", (long) getpid());
450 old_mask
= umask(0077);
451 gdk_pixbuf_save(thumb
, to
->str
, "png", NULL
,
452 "tEXt::Thumb::Image::Width", swidth
,
453 "tEXt::Thumb::Image::Height", sheight
,
454 "tEXt::Thumb::Size", ssize
,
455 "tEXt::Thumb::MTime", smtime
,
456 "tEXt::Thumb::URI", uri
,
457 "tEXt::Software", PROJECT
,
461 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
462 * a race condition if two programs create the same thumb at
468 final
= g_strndup(to
->str
, name_len
);
469 if (rename(to
->str
, final
))
470 g_warning("Failed to rename '%s' to '%s': %s",
471 to
->str
, final
, g_strerror(errno
));
475 g_string_free(to
, TRUE
);
483 static gchar
*thumbnail_path(const char *path
)
489 uri
= g_filename_to_uri(path
, NULL
, NULL
);
491 uri
= g_strconcat("file://", path
, NULL
);
494 to
= g_string_new(home_dir
);
495 g_string_append(to
, "/.thumbnails");
496 mkdir(to
->str
, 0700);
497 g_string_append(to
, "/normal/");
498 mkdir(to
->str
, 0700);
499 g_string_append(to
, md5
);
500 g_string_append(to
, ".png");
506 g_string_free(to
, FALSE
);
511 /* Return a program to create thumbnails for files of this type.
512 * NULL to try to make it ourself (using gdk).
515 static gchar
*thumbnail_program(MIME_type
*type
)
523 leaf
= g_strconcat(type
->media_type
, "_", type
->subtype
, NULL
);
524 path
= choices_find_xdg_path_load(leaf
, "MIME-thumb", SITE
);
531 path
= choices_find_xdg_path_load(type
->media_type
, "MIME-thumb",
537 /* Called in a subprocess. Load path and create the thumbnail
538 * file. Parent will notice when we die.
540 static void child_create_thumbnail(const gchar
*path
)
544 image
= rox_pixbuf_new_from_file_at_scale(path
,
545 PIXMAP_THUMB_SIZE
, PIXMAP_THUMB_SIZE
, TRUE
, NULL
);
548 save_thumbnail(path
, image
);
550 /* (no need to unref, as we're about to exit) */
553 /* Called when the child process exits */
554 static void thumbnail_child_done(ChildThumbnail
*info
)
558 thumb
= get_thumbnail_for(info
->path
);
564 image
= masked_pixmap_new(thumb
);
565 g_object_unref(thumb
);
567 g_fscache_insert(pixmap_cache
, info
->path
, image
, FALSE
);
568 g_object_unref(image
);
570 info
->callback(info
->data
, info
->path
);
573 info
->callback(info
->data
, NULL
);
579 /* Check if we have an up-to-date thumbnail for this image.
580 * If so, return it. Otherwise, returns NULL.
582 static GdkPixbuf
*get_thumbnail_for(const char *pathname
)
584 GdkPixbuf
*thumb
= NULL
;
585 char *thumb_path
, *md5
, *uri
, *path
;
586 const char *ssize
, *smtime
;
590 path
= pathdup(pathname
);
591 uri
= g_filename_to_uri(path
, NULL
, NULL
);
593 uri
= g_strconcat("file://", path
, NULL
);
597 thumb_path
= g_strdup_printf("%s/.thumbnails/normal/%s.png",
601 thumb
= gdk_pixbuf_new_from_file(thumb_path
, NULL
);
605 /* Note that these don't need freeing... */
606 ssize
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::Size");
610 smtime
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::MTime");
614 if (mc_stat(path
, &info
) != 0)
617 ttime
=(time_t) atol(smtime
);
619 if (info
.st_mtime
!= ttime
&& now
>ttime
+PIXMAP_THUMB_TOO_OLD_TIME
)
622 if (info
.st_size
< atol(ssize
))
628 gdk_pixbuf_unref(thumb
);
636 /* Load the image 'path' and return a pointer to the resulting
637 * MaskedPixmap. NULL on failure.
638 * Doesn't check for thumbnails (this is for small icons).
640 static MaskedPixmap
*image_from_file(const char *path
)
644 GError
*error
= NULL
;
646 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
649 g_warning("%s\n", error
->message
);
654 image
= masked_pixmap_new(pixbuf
);
656 gdk_pixbuf_unref(pixbuf
);
661 /* Load this icon named by this .desktop file from the current theme.
664 static MaskedPixmap
*image_from_desktop_file(const char *path
)
666 GError
*error
= NULL
;
667 MaskedPixmap
*image
= NULL
;
670 icon
= get_value_from_desktop_file(path
,
671 "Desktop Entry", "Icon", &error
);
674 g_warning("Failed to parse .desktop file '%s':\n%s",
675 path
, error
->message
);
682 image
= image_from_file(icon
);
688 /* SVG reader is very noisy, so redirect stderr to stdout */
691 pixbuf
= gtk_icon_theme_load_icon(icon_theme
, icon
, HUGE_WIDTH
,
697 goto err
; /* Might just not be in the theme */
699 image
= masked_pixmap_new(pixbuf
);
700 g_object_unref(pixbuf
);
710 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
711 * If src is small enough, then ref it and return that.
713 GdkPixbuf
*scale_pixbuf(GdkPixbuf
*src
, int max_w
, int max_h
)
717 w
= gdk_pixbuf_get_width(src
);
718 h
= gdk_pixbuf_get_height(src
);
720 if (w
<= max_w
&& h
<= max_h
)
727 float scale_x
= ((float) w
) / max_w
;
728 float scale_y
= ((float) h
) / max_h
;
729 float scale
= MAX(scale_x
, scale_y
);
730 int dest_w
= w
/ scale
;
731 int dest_h
= h
/ scale
;
733 return gdk_pixbuf_scale_simple(src
,
736 GDK_INTERP_BILINEAR
);
740 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
741 * If src is that size or bigger, then ref it and return that.
743 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
)
747 w
= gdk_pixbuf_get_width(src
);
748 h
= gdk_pixbuf_get_height(src
);
750 if (w
== 0 || h
== 0 || w
>= max_w
|| h
>= max_h
)
757 float scale_x
= max_w
/ ((float) w
);
758 float scale_y
= max_h
/ ((float) h
);
759 float scale
= MIN(scale_x
, scale_y
);
761 return gdk_pixbuf_scale_simple(src
,
764 GDK_INTERP_BILINEAR
);
768 /* Return a pointer to the (static) bad image. The ref counter will ensure
769 * that the image is never freed.
771 static MaskedPixmap
*get_bad_image(void)
776 bad
= gdk_pixbuf_new_from_xpm_data(bad_xpm
);
777 mp
= masked_pixmap_new(bad
);
778 gdk_pixbuf_unref(bad
);
783 /* Called now and then to clear out old pixmaps */
784 static gint
purge(gpointer data
)
786 g_fscache_purge(pixmap_cache
, PIXMAP_PURGE_TIME
);
791 static gpointer parent_class
;
793 static void masked_pixmap_finialize(GObject
*object
)
795 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
799 g_object_unref(mp
->src_pixbuf
);
800 mp
->src_pixbuf
= NULL
;
805 g_object_unref(mp
->huge_pixbuf
);
806 mp
->huge_pixbuf
= NULL
;
810 g_object_unref(mp
->pixbuf
);
816 g_object_unref(mp
->sm_pixbuf
);
817 mp
->sm_pixbuf
= NULL
;
820 G_OBJECT_CLASS(parent_class
)->finalize(object
);
823 static void masked_pixmap_class_init(gpointer gclass
, gpointer data
)
825 GObjectClass
*object
= (GObjectClass
*) gclass
;
827 parent_class
= g_type_class_peek_parent(gclass
);
829 object
->finalize
= masked_pixmap_finialize
;
832 static void masked_pixmap_init(GTypeInstance
*object
, gpointer gclass
)
834 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
836 mp
->src_pixbuf
= NULL
;
838 mp
->huge_pixbuf
= NULL
;
840 mp
->huge_height
= -1;
846 mp
->sm_pixbuf
= NULL
;
851 static GType
masked_pixmap_get_type(void)
853 static GType type
= 0;
857 static const GTypeInfo info
=
859 sizeof (MaskedPixmapClass
),
860 NULL
, /* base_init */
861 NULL
, /* base_finalise */
862 masked_pixmap_class_init
,
863 NULL
, /* class_finalise */
864 NULL
, /* class_data */
865 sizeof(MaskedPixmap
),
870 type
= g_type_register_static(G_TYPE_OBJECT
, "MaskedPixmap",
877 MaskedPixmap
*masked_pixmap_new(GdkPixbuf
*full_size
)
880 GdkPixbuf
*src_pixbuf
, *normal_pixbuf
;
882 g_return_val_if_fail(full_size
!= NULL
, NULL
);
884 src_pixbuf
= scale_pixbuf(full_size
, HUGE_WIDTH
, HUGE_HEIGHT
);
885 g_return_val_if_fail(src_pixbuf
!= NULL
, NULL
);
887 normal_pixbuf
= scale_pixbuf(src_pixbuf
, ICON_WIDTH
, ICON_HEIGHT
);
888 g_return_val_if_fail(normal_pixbuf
!= NULL
, NULL
);
890 mp
= g_object_new(masked_pixmap_get_type(), NULL
);
892 mp
->src_pixbuf
= src_pixbuf
;
894 mp
->pixbuf
= normal_pixbuf
;
895 mp
->width
= gdk_pixbuf_get_width(normal_pixbuf
);
896 mp
->height
= gdk_pixbuf_get_height(normal_pixbuf
);
901 /* Load all the standard pixmaps. Also sets the default window icon. */
902 static void load_default_pixmaps(void)
905 GError
*error
= NULL
;
907 im_error
= mp_from_stock(GTK_STOCK_DIALOG_WARNING
,
908 GTK_ICON_SIZE_DIALOG
);
909 im_unknown
= mp_from_stock(GTK_STOCK_DIALOG_QUESTION
,
910 GTK_ICON_SIZE_DIALOG
);
911 im_symlink
= load_pixmap("symlink");
913 im_unmounted
= mp_from_stock(ROX_STOCK_MOUNT
, mount_icon_size
);
914 im_mounted
= mp_from_stock(ROX_STOCK_MOUNTED
, mount_icon_size
);
915 im_appdir
= load_pixmap("application");
916 im_xattr
= load_pixmap("rox-xattr");
918 im_dirs
= load_pixmap("dirs");
920 pixbuf
= gdk_pixbuf_new_from_file(
921 make_path(app_dir
, ".DirIcon"), &error
);
926 icon_list
= g_list_append(NULL
, pixbuf
);
927 gtk_window_set_default_icon_list(icon_list
);
928 g_list_free(icon_list
);
930 g_object_unref(G_OBJECT(pixbuf
));
934 g_warning("%s\n", error
->message
);
939 /* Also purges memory cache */
940 static void purge_disk_cache(GtkWidget
*button
, gpointer data
)
947 g_fscache_purge(pixmap_cache
, 0);
949 path
= g_strconcat(home_dir
, "/.thumbnails/normal/", NULL
);
954 report_error(_("Can't delete thumbnails in %s:\n%s"),
955 path
, g_strerror(errno
));
959 while ((ent
= readdir(dir
)))
961 if (ent
->d_name
[0] == '.')
963 list
= g_list_prepend(list
,
964 g_strconcat(path
, ent
->d_name
, NULL
));
972 destroy_glist(&list
);
975 info_message(_("There are no thumbnails to delete"));
980 static GList
*thumbs_purge_cache(Option
*option
, xmlNode
*node
, guchar
*label
)
982 GtkWidget
*button
, *align
;
984 g_return_val_if_fail(option
== NULL
, NULL
);
986 align
= gtk_alignment_new(0, 0.5, 0, 0);
987 button
= button_new_mixed(GTK_STOCK_CLEAR
,
988 _("Purge thumbnails disk cache"));
989 gtk_container_add(GTK_CONTAINER(align
), button
);
990 g_signal_connect(button
, "clicked", G_CALLBACK(purge_disk_cache
), NULL
);
992 return g_list_append(NULL
, align
);