1 /*
2  * ROX-Filer, filer for the ROX desktop project
3  * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
4  *
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.
9  *
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.
14  *
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
18  */
19 
20 /* pixmaps.c - code for handling pixbufs (despite the name!) */
21 
22 #include "config.h"
23 #define PIXMAPS_C
24 
25 /* Remove pixmaps from the cache when they haven't been accessed for
26  * this period of time (seconds).
27  */
28 
29 #define PIXMAP_PURGE_TIME 1200
30 #define PIXMAP_THUMB_SIZE  128
31 #define PIXMAP_THUMB_TOO_OLD_TIME  5
32 
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>
40 
41 #include <gtk/gtk.h>
42 
43 #include "global.h"
44 
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"
57 
58 GFSCache *pixmap_cache = NULL;
59 GFSCache *desktop_icon_cache = NULL;
60 
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 "            "};
78 
79 MaskedPixmap *im_error;
80 MaskedPixmap *im_unknown;
81 
82 MaskedPixmap *im_appdir;
83 
84 MaskedPixmap *im_dirs;
85 
86 GtkIconSize mount_icon_size = -1;
87 
88 typedef struct _ChildThumbnail ChildThumbnail;
89 
90 /* There is one of these for each active child process */
91 struct _ChildThumbnail {
92 	gchar	 *path;
93 	GFunc	 callback;
94 	gpointer data;
95 };
96 
97 static const char *stocks[] = {
98 	ROX_STOCK_SHOW_DETAILS,
99 	ROX_STOCK_SHOW_HIDDEN,
100 	ROX_STOCK_SELECT,
101 	ROX_STOCK_MOUNT,
102 	ROX_STOCK_MOUNTED,
103 	ROX_STOCK_SYMLINK,
104 	ROX_STOCK_XATTR,
105 };
106 
107 /* Static prototypes */
108 
109 static void load_default_pixmaps(void);
110 static gint purge(gpointer data);
111 static MaskedPixmap *image_from_file(const char *path);
112 static MaskedPixmap *image_from_desktop_file(const char *path);
113 static MaskedPixmap *get_bad_image(void);
114 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h);
115 static GdkPixbuf *get_thumbnail_for(const char *path);
116 static void thumbnail_child_done(ChildThumbnail *info);
117 static void child_create_thumbnail(const gchar *path, MIME_type *type);
118 static GList *thumbs_purge_cache(Option *option, xmlNode *node, guchar *label);
119 static gchar *thumbnail_path(const gchar *path);
120 static gchar *thumbnail_program(MIME_type *type);
121 static GdkPixbuf *extract_tiff_thumbnail(const gchar *path);
122 
123 /****************************************************************
124  *			EXTERNAL INTERFACE			*
125  ****************************************************************/
126 
pixmaps_init(void)127 void pixmaps_init(void)
128 {
129 	GtkIconFactory *factory;
130 	int i;
131 
132 	gtk_widget_push_colormap(gdk_rgb_get_colormap());
133 
134 	pixmap_cache = g_fscache_new((GFSLoadFunc) image_from_file, NULL, NULL);
135 	desktop_icon_cache = g_fscache_new((GFSLoadFunc) image_from_desktop_file, NULL, NULL);
136 
137 	g_timeout_add(10000, purge, NULL);
138 
139 	factory = gtk_icon_factory_new();
140 	for (i = 0; i < G_N_ELEMENTS(stocks); i++)
141 	{
142 		GdkPixbuf *pixbuf;
143 		GError *error = NULL;
144 		gchar *path;
145 		GtkIconSet *iset;
146 		const gchar *name = stocks[i];
147 
148 		path = g_strconcat(app_dir, "/images/", name, ".png", NULL);
149 		pixbuf = gdk_pixbuf_new_from_file(path, &error);
150 		if (!pixbuf)
151 		{
152 			g_warning("%s", error->message);
153 			g_error_free(error);
154 			pixbuf = gdk_pixbuf_new_from_xpm_data(bad_xpm);
155 		}
156 		g_free(path);
157 
158 		iset = gtk_icon_set_new_from_pixbuf(pixbuf);
159 		g_object_unref(G_OBJECT(pixbuf));
160 		gtk_icon_factory_add(factory, name, iset);
161 		gtk_icon_set_unref(iset);
162 	}
163 	gtk_icon_factory_add_default(factory);
164 
165 	mount_icon_size = gtk_icon_size_register("rox-mount-size", 14, 14);
166 
167 	load_default_pixmaps();
168 
169 	option_register_widget("thumbs-purge-cache", thumbs_purge_cache);
170 }
171 
172 /* Load image <appdir>/images/name.png.
173  * Always returns with a valid image.
174  */
load_pixmap(const char * name)175 MaskedPixmap *load_pixmap(const char *name)
176 {
177 	guchar *path;
178 	MaskedPixmap *retval;
179 
180 	path = g_strconcat(app_dir, "/images/", name, ".png", NULL);
181 	retval = image_from_file(path);
182 	g_free(path);
183 
184 	if (!retval)
185 		retval = get_bad_image();
186 
187 	return retval;
188 }
189 
190 /* Create a MaskedPixmap from a GTK stock ID. Always returns
191  * a valid image.
192  */
mp_from_stock(const char * stock_id,int size)193 static MaskedPixmap *mp_from_stock(const char *stock_id, int size)
194 {
195 	GtkIconSet *icon_set;
196 	GdkPixbuf  *pixbuf;
197 	MaskedPixmap *retval;
198 
199 	/*icon_set = gtk_icon_factory_lookup_default(stock_id);*/
200 	icon_set = gtk_style_lookup_icon_set(gtk_widget_get_default_style(),
201 					     stock_id);
202 	if (!icon_set)
203 		return get_bad_image();
204 
205 	pixbuf = gtk_icon_set_render_icon(icon_set,
206                                      gtk_widget_get_default_style(), /* Gtk bug */
207                                      GTK_TEXT_DIR_LTR,
208                                      GTK_STATE_NORMAL,
209                                      size,
210                                      NULL,
211                                      NULL);
212 	retval = masked_pixmap_new(pixbuf);
213 	gdk_pixbuf_unref(pixbuf);
214 
215 	return retval;
216 }
217 
pixmap_make_huge(MaskedPixmap * mp)218 void pixmap_make_huge(MaskedPixmap *mp)
219 {
220 	if (mp->huge_pixbuf)
221 		return;
222 
223 	g_return_if_fail(mp->src_pixbuf != NULL);
224 
225 	/* Limit to small size now, otherwise they get scaled up in mixed mode.
226 	 * Also looked ugly.
227 	 */
228 	mp->huge_pixbuf = scale_pixbuf_up(mp->src_pixbuf,
229 					  SMALL_WIDTH, SMALL_HEIGHT);
230 
231 	if (!mp->huge_pixbuf)
232 	{
233 		mp->huge_pixbuf = mp->src_pixbuf;
234 		g_object_ref(mp->huge_pixbuf);
235 	}
236 
237 	mp->huge_width = gdk_pixbuf_get_width(mp->huge_pixbuf);
238 	mp->huge_height = gdk_pixbuf_get_height(mp->huge_pixbuf);
239 }
240 
pixmap_make_small(MaskedPixmap * mp)241 void pixmap_make_small(MaskedPixmap *mp)
242 {
243 	if (mp->sm_pixbuf)
244 		return;
245 
246 	g_return_if_fail(mp->src_pixbuf != NULL);
247 
248 	mp->sm_pixbuf = scale_pixbuf(mp->src_pixbuf, SMALL_WIDTH, SMALL_HEIGHT);
249 
250 	if (!mp->sm_pixbuf)
251 	{
252 		mp->sm_pixbuf = mp->src_pixbuf;
253 		g_object_ref(mp->sm_pixbuf);
254 	}
255 
256 	mp->sm_width = gdk_pixbuf_get_width(mp->sm_pixbuf);
257 	mp->sm_height = gdk_pixbuf_get_height(mp->sm_pixbuf);
258 }
259 
260 /* Load image 'path' in the background and insert into pixmap_cache.
261  * Call callback(data, path) when done (path is NULL => error).
262  * If the image is already uptodate, or being created already, calls the
263  * callback right away.
264  */
pixmap_background_thumb(const gchar * path,GFunc callback,gpointer data)265 void pixmap_background_thumb(const gchar *path, GFunc callback, gpointer data)
266 {
267 	gboolean	found;
268 	MaskedPixmap	*image;
269 	pid_t		child;
270 	ChildThumbnail	*info;
271 	MIME_type       *type;
272 	gchar		*thumb_prog;
273 
274 	image = pixmap_try_thumb(path, TRUE);
275 
276 	if (image)
277 	{
278 		/* Thumbnail loaded */
279 		callback(data, path);
280 		return;
281 	}
282 
283 	/* Is it currently being created? */
284 	image = g_fscache_lookup_full(pixmap_cache, path,
285 					FSCACHE_LOOKUP_ONLY_NEW, &found);
286 
287 	if (found)
288 	{
289 		/* Thumbnail is known, or being created */
290 		if (image)
291 			g_object_unref(image);
292 		callback(data, image? path: NULL);
293 		return;
294 	}
295 
296 	/* Not in memory, nor in the thumbnails directory.  We need to
297 	 * generate it */
298 
299 	type = type_from_path(path);
300 	if (!type)
301 		type = text_plain;
302 
303 	/* Add an entry, set to NULL, so no-one else tries to load this
304 	 * image.
305 	 */
306 	g_fscache_insert(pixmap_cache, path, NULL, TRUE);
307 
308 	thumb_prog = thumbnail_program(type);
309 
310 	/* Only attempt to load 'images' types ourselves */
311 	if (thumb_prog == NULL && strcmp(type->media_type, "image") != 0)
312 	{
313 		callback(data, NULL);
314 		return;		/* Don't know how to handle this type */
315 	}
316 
317 	child = fork();
318 
319 	if (child == -1)
320 	{
321 		g_free(thumb_prog);
322 		delayed_error("fork(): %s", g_strerror(errno));
323 		callback(data, NULL);
324 		return;
325 	}
326 
327 	if (child == 0)
328 	{
329 		/* We are the child process.  (We are sloppy with freeing
330 		 memory, but since we go away very quickly, that's ok.) */
331 		if (thumb_prog)
332 		{
333 			DirItem *item;
334 
335 			item = diritem_new(g_basename(thumb_prog));
336 
337 			diritem_restat(thumb_prog, item, NULL);
338 			if (item->flags & ITEM_FLAG_APPDIR)
339 				thumb_prog = g_strconcat(thumb_prog, "/AppRun",
340 						       NULL);
341 
342 			execl(thumb_prog, thumb_prog, path,
343 			      thumbnail_path(path),
344 			      g_strdup_printf("%d", PIXMAP_THUMB_SIZE),
345 			      NULL);
346 			_exit(1);
347 		}
348 
349 		child_create_thumbnail(path, type);
350 		_exit(0);
351 	}
352 
353 	g_free(thumb_prog);
354 
355 	info = g_new(ChildThumbnail, 1);
356 	info->path = g_strdup(path);
357 	info->callback = callback;
358 	info->data = data;
359 	on_child_death(child, (CallbackFn) thumbnail_child_done, info);
360 }
361 
362 /*
363  * Return the thumbnail for a file, only if available.  If the
364  * can_load flags is set this includes loading from the cache, otherwise
365  * only if already in memory
366  */
pixmap_try_thumb(const gchar * path,gboolean can_load)367 MaskedPixmap *pixmap_try_thumb(const gchar *path, gboolean can_load)
368 {
369 	gboolean	found;
370 	MaskedPixmap	*image;
371 	GdkPixbuf	*pixbuf;
372 
373 	image = g_fscache_lookup_full(pixmap_cache, path,
374 					FSCACHE_LOOKUP_ONLY_NEW, &found);
375 
376 	if (found)
377 	{
378 		/* Thumbnail is known, or being created */
379 		if (image)
380 			return image;
381 	}
382 
383 	if(!can_load)
384 		return NULL;
385 
386 	pixbuf = get_thumbnail_for(path);
387 
388 	if (!pixbuf)
389 	{
390 		struct stat info1, info2;
391 		char *dir;
392 
393 		/* Skip zero-byte files. They're either empty, or
394 		 * special (may cause us to hang, e.g. /proc/kmsg). */
395 		if (mc_stat(path, &info1) == 0 && info1.st_size == 0) {
396 			return NULL;
397 		}
398 
399 		dir = g_path_get_dirname(path);
400 
401 		/* If the image itself is in ~/.thumbnails, load it now
402 		 * (ie, don't create thumbnails for thumbnails!).
403 		 */
404 		if (mc_stat(dir, &info1) != 0)
405 		{
406 			g_free(dir);
407 			return NULL;
408 		}
409 		g_free(dir);
410 
411 		if (mc_stat(make_path(home_dir, ".thumbnails/normal"),
412 			    &info2) == 0 &&
413 			    info1.st_dev == info2.st_dev &&
414 			    info1.st_ino == info2.st_ino)
415 		{
416 			pixbuf = rox_pixbuf_new_from_file_at_scale(path,
417 					PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE,
418 								   TRUE, NULL);
419 			if (!pixbuf)
420 			{
421 				return NULL;
422 			}
423 		}
424 	}
425 
426 	if (pixbuf)
427 	{
428 		MaskedPixmap *image;
429 
430 		image = masked_pixmap_new(pixbuf);
431 		gdk_pixbuf_unref(pixbuf);
432 		g_fscache_insert(pixmap_cache, path, image, TRUE);
433 		return image;
434 	}
435 
436 	return NULL;
437 }
438 
439 /****************************************************************
440  *			INTERNAL FUNCTIONS			*
441  ****************************************************************/
442 
443 /* Create a thumbnail file for this image */
save_thumbnail(const char * pathname,GdkPixbuf * full)444 static void save_thumbnail(const char *pathname, GdkPixbuf *full)
445 {
446 	struct stat info;
447 	gchar *path;
448 	int original_width, original_height;
449 	GString *to;
450 	char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
451 	mode_t old_mask;
452 	int name_len;
453 	GdkPixbuf *thumb;
454 
455 	thumb = scale_pixbuf(full, PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE);
456 
457 	original_width = gdk_pixbuf_get_width(full);
458 	original_height = gdk_pixbuf_get_height(full);
459 
460 	if (mc_stat(pathname, &info) != 0)
461 		return;
462 
463 	swidth = g_strdup_printf("%d", original_width);
464 	sheight = g_strdup_printf("%d", original_height);
465 	ssize = g_strdup_printf("%" SIZE_FMT, info.st_size);
466 	smtime = g_strdup_printf("%ld", (long) info.st_mtime);
467 
468 	path = pathdup(pathname);
469 	uri = g_filename_to_uri(path, NULL, NULL);
470 	if (!uri)
471 	        uri = g_strconcat("file://", path, NULL);
472 	md5 = md5_hash(uri);
473 	g_free(path);
474 
475 	to = g_string_new(home_dir);
476 	g_string_append(to, "/.thumbnails");
477 	mkdir(to->str, 0700);
478 	g_string_append(to, "/normal/");
479 	mkdir(to->str, 0700);
480 	g_string_append(to, md5);
481 	name_len = to->len + 4; /* Truncate to this length when renaming */
482 	g_string_append_printf(to, ".png.ROX-Filer-%ld", (long) getpid());
483 
484 	g_free(md5);
485 
486 	old_mask = umask(0077);
487 	gdk_pixbuf_save(thumb, to->str, "png", NULL,
488 			"tEXt::Thumb::Image::Width", swidth,
489 			"tEXt::Thumb::Image::Height", sheight,
490 			"tEXt::Thumb::Size", ssize,
491 			"tEXt::Thumb::MTime", smtime,
492 			"tEXt::Thumb::URI", uri,
493 			"tEXt::Software", PROJECT,
494 			NULL);
495 	umask(old_mask);
496 
497 	/* We create the file ###.png.ROX-Filer-PID and rename it to avoid
498 	 * a race condition if two programs create the same thumb at
499 	 * once.
500 	 */
501 	{
502 		gchar *final;
503 
504 		final = g_strndup(to->str, name_len);
505 		if (rename(to->str, final))
506 			g_warning("Failed to rename '%s' to '%s': %s",
507 				  to->str, final, g_strerror(errno));
508 		g_free(final);
509 	}
510 
511 	g_string_free(to, TRUE);
512 	g_free(swidth);
513 	g_free(sheight);
514 	g_free(ssize);
515 	g_free(smtime);
516 	g_free(uri);
517 }
518 
thumbnail_path(const char * path)519 static gchar *thumbnail_path(const char *path)
520 {
521 	gchar *uri, *md5;
522 	GString *to;
523 	gchar *ans;
524 
525 	uri = g_filename_to_uri(path, NULL, NULL);
526 	if(!uri)
527 	       uri = g_strconcat("file://", path, NULL);
528 	md5 = md5_hash(uri);
529 
530 	to = g_string_new(home_dir);
531 	g_string_append(to, "/.thumbnails");
532 	mkdir(to->str, 0700);
533 	g_string_append(to, "/normal/");
534 	mkdir(to->str, 0700);
535 	g_string_append(to, md5);
536 	g_string_append(to, ".png");
537 
538 	g_free(md5);
539 	g_free(uri);
540 
541 	ans=to->str;
542 	g_string_free(to, FALSE);
543 
544 	return ans;
545 }
546 
547 /* Return a program to create thumbnails for files of this type.
548  * NULL to try to make it ourself (using gdk).
549  * g_free the result.
550  */
thumbnail_program(MIME_type * type)551 static gchar *thumbnail_program(MIME_type *type)
552 {
553 	gchar *leaf;
554 	gchar *path;
555 
556 	if (!type)
557 		return NULL;
558 
559 	leaf = g_strconcat(type->media_type, "_", type->subtype, NULL);
560 	path = choices_find_xdg_path_load(leaf, "MIME-thumb", SITE);
561 	g_free(leaf);
562 	if (path)
563 	{
564 		return path;
565 	}
566 
567 	path = choices_find_xdg_path_load(type->media_type, "MIME-thumb",
568 					  SITE);
569 
570 	return path;
571 }
572 
573 /* Called in a subprocess. Load path and create the thumbnail
574  * file. Parent will notice when we die.
575  */
child_create_thumbnail(const gchar * path,MIME_type * type)576 static void child_create_thumbnail(const gchar *path, MIME_type *type)
577 {
578 	GdkPixbuf *image=NULL;
579 
580         if(strcmp(type->subtype, "jpeg")==0)
581             image=extract_tiff_thumbnail(path);
582 
583 	if(!image)
584             image = rox_pixbuf_new_from_file_at_scale(path,
585 			PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE, TRUE, NULL);
586 
587 	if (image)
588 		save_thumbnail(path, image);
589 
590 	/* (no need to unref, as we're about to exit) */
591 }
592 
593 /* Called when the child process exits */
thumbnail_child_done(ChildThumbnail * info)594 static void thumbnail_child_done(ChildThumbnail *info)
595 {
596 	GdkPixbuf *thumb;
597 
598 	thumb = get_thumbnail_for(info->path);
599 
600 	if (thumb)
601 	{
602 		MaskedPixmap *image;
603 
604 		image = masked_pixmap_new(thumb);
605 		g_object_unref(thumb);
606 
607 		g_fscache_insert(pixmap_cache, info->path, image, FALSE);
608 		g_object_unref(image);
609 
610 		info->callback(info->data, info->path);
611 	}
612 	else
613 		info->callback(info->data, NULL);
614 
615 	g_free(info->path);
616 	g_free(info);
617 }
618 
619 /* Check if we have an up-to-date thumbnail for this image.
620  * If so, return it. Otherwise, returns NULL.
621  */
get_thumbnail_for(const char * pathname)622 static GdkPixbuf *get_thumbnail_for(const char *pathname)
623 {
624 	GdkPixbuf *thumb = NULL;
625 	char *thumb_path, *md5, *uri, *path;
626 	const char *ssize, *smtime;
627 	struct stat info;
628 	time_t ttime, now;
629 
630 	path = pathdup(pathname);
631 	uri = g_filename_to_uri(path, NULL, NULL);
632 	if(!uri)
633 	        uri = g_strconcat("file://", path, NULL);
634 	md5 = md5_hash(uri);
635 	g_free(uri);
636 
637 	thumb_path = g_strdup_printf("%s/.thumbnails/normal/%s.png",
638 					home_dir, md5);
639 	g_free(md5);
640 
641 	thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
642 	if (!thumb)
643 		goto err;
644 
645 	/* Note that these don't need freeing... */
646 	ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
647 	/* This is optional, so don't flag an error if it is missing */
648 
649 	smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
650 	if (!smtime)
651 		goto err;
652 
653 	if (mc_stat(path, &info) != 0)
654 		goto err;
655 
656 	ttime=(time_t) atol(smtime);
657 	time(&now);
658 	if (info.st_mtime != ttime && now>ttime+PIXMAP_THUMB_TOO_OLD_TIME)
659 		goto err;
660 
661 	if (ssize && info.st_size < atol(ssize))
662 		goto err;
663 
664 	goto out;
665 err:
666 	if (thumb)
667 		gdk_pixbuf_unref(thumb);
668 	thumb = NULL;
669 out:
670 	g_free(path);
671 	g_free(thumb_path);
672 	return thumb;
673 }
674 
675 /* Load the image 'path' and return a pointer to the resulting
676  * MaskedPixmap. NULL on failure.
677  * Doesn't check for thumbnails (this is for small icons).
678  */
image_from_file(const char * path)679 static MaskedPixmap *image_from_file(const char *path)
680 {
681 	GdkPixbuf	*pixbuf;
682 	MaskedPixmap	*image;
683 	GError		*error = NULL;
684 
685 	pixbuf = gdk_pixbuf_new_from_file(path, &error);
686 	if (!pixbuf)
687 	{
688 		g_warning("%s\n", error->message);
689 		g_error_free(error);
690 		return NULL;
691 	}
692 
693 	image = masked_pixmap_new(pixbuf);
694 
695 	gdk_pixbuf_unref(pixbuf);
696 
697 	return image;
698 }
699 
700 /* Load this icon named by this .desktop file from the current theme.
701  * NULL on failure.
702  */
image_from_desktop_file(const char * path)703 static MaskedPixmap *image_from_desktop_file(const char *path)
704 {
705 	GError *error = NULL;
706 	MaskedPixmap *image = NULL;
707 	char *icon = NULL;
708 
709 	icon = get_value_from_desktop_file(path,
710 					"Desktop Entry", "Icon", &error);
711 	if (error)
712 	{
713 		g_warning("Failed to parse .desktop file '%s':\n%s",
714 				path, error->message);
715 		goto err;
716 	}
717 	if (!icon)
718 		goto err;
719 
720 	if (icon[0] == '/')
721 		image = image_from_file(icon);
722 	else
723 	{
724 		GdkPixbuf *pixbuf;
725 		int tmp_fd;
726 		char *extension;
727 
728 		/* For some unknown reason, some icon names have extensions.
729 		 * Remove them.
730 		 */
731 		extension = strrchr(icon, '.');
732 		if (extension && (strcmp(extension, ".png") == 0
733 						|| strcmp(extension, ".xpm") == 0
734 						|| strcmp(extension, ".svg") == 0))
735 		{
736 			*extension = '\0';
737 		}
738 
739 		/* SVG reader is very noisy, so redirect stderr to stdout */
740 		tmp_fd = dup(2);
741 		dup2(1, 2);
742 		pixbuf = theme_load_icon(icon, HUGE_WIDTH, 0, NULL);
743 		dup2(tmp_fd, 2);
744 		close(tmp_fd);
745 
746 		if (pixbuf == NULL)
747 			goto err;	/* Might just not be in the theme */
748 
749 		image = masked_pixmap_new(pixbuf);
750 		g_object_unref(pixbuf);
751 	}
752 err:
753 	if (error != NULL)
754 		g_error_free(error);
755 	if (icon != NULL)
756 		g_free(icon);
757 	return image;
758 }
759 
760 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
761  * If src is small enough, then ref it and return that.
762  */
scale_pixbuf(GdkPixbuf * src,int max_w,int max_h)763 GdkPixbuf *scale_pixbuf(GdkPixbuf *src, int max_w, int max_h)
764 {
765 	int	w, h;
766 
767 	w = gdk_pixbuf_get_width(src);
768 	h = gdk_pixbuf_get_height(src);
769 
770 	if (w <= max_w && h <= max_h)
771 	{
772 		gdk_pixbuf_ref(src);
773 		return src;
774 	}
775 	else
776 	{
777 		float scale_x = ((float) w) / max_w;
778 		float scale_y = ((float) h) / max_h;
779 		float scale = MAX(scale_x, scale_y);
780 		int dest_w = w / scale;
781 		int dest_h = h / scale;
782 
783 		return gdk_pixbuf_scale_simple(src,
784 						MAX(dest_w, 1),
785 						MAX(dest_h, 1),
786 						GDK_INTERP_BILINEAR);
787 	}
788 }
789 
790 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
791  * If src is that size or bigger, then ref it and return that.
792  */
scale_pixbuf_up(GdkPixbuf * src,int max_w,int max_h)793 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h)
794 {
795 	int	w, h;
796 
797 	w = gdk_pixbuf_get_width(src);
798 	h = gdk_pixbuf_get_height(src);
799 
800 	if (w == 0 || h == 0 || w >= max_w || h >= max_h)
801 	{
802 		gdk_pixbuf_ref(src);
803 		return src;
804 	}
805 	else
806 	{
807 		float scale_x = max_w / ((float) w);
808 		float scale_y = max_h / ((float) h);
809 		float scale = MIN(scale_x, scale_y);
810 
811 		return gdk_pixbuf_scale_simple(src,
812 						w * scale,
813 						h * scale,
814 						GDK_INTERP_BILINEAR);
815 	}
816 }
817 
818 /* Return a pointer to the (static) bad image. The ref counter will ensure
819  * that the image is never freed.
820  */
get_bad_image(void)821 static MaskedPixmap *get_bad_image(void)
822 {
823 	GdkPixbuf *bad;
824 	MaskedPixmap *mp;
825 
826 	bad = gdk_pixbuf_new_from_xpm_data(bad_xpm);
827 	mp = masked_pixmap_new(bad);
828 	gdk_pixbuf_unref(bad);
829 
830 	return mp;
831 }
832 
833 /* Called now and then to clear out old pixmaps */
purge(gpointer data)834 static gint purge(gpointer data)
835 {
836 	g_fscache_purge(pixmap_cache, PIXMAP_PURGE_TIME);
837 
838 	return TRUE;
839 }
840 
841 static gpointer parent_class;
842 
masked_pixmap_finialize(GObject * object)843 static void masked_pixmap_finialize(GObject *object)
844 {
845 	MaskedPixmap *mp = (MaskedPixmap *) object;
846 
847 	if (mp->src_pixbuf)
848 	{
849 		g_object_unref(mp->src_pixbuf);
850 		mp->src_pixbuf = NULL;
851 	}
852 
853 	if (mp->huge_pixbuf)
854 	{
855 		g_object_unref(mp->huge_pixbuf);
856 		mp->huge_pixbuf = NULL;
857 	}
858 	if (mp->pixbuf)
859 	{
860 		g_object_unref(mp->pixbuf);
861 		mp->pixbuf = NULL;
862 	}
863 
864 	if (mp->sm_pixbuf)
865 	{
866 		g_object_unref(mp->sm_pixbuf);
867 		mp->sm_pixbuf = NULL;
868 	}
869 
870 	G_OBJECT_CLASS(parent_class)->finalize(object);
871 }
872 
masked_pixmap_class_init(gpointer gclass,gpointer data)873 static void masked_pixmap_class_init(gpointer gclass, gpointer data)
874 {
875 	GObjectClass *object = (GObjectClass *) gclass;
876 
877 	parent_class = g_type_class_peek_parent(gclass);
878 
879 	object->finalize = masked_pixmap_finialize;
880 }
881 
masked_pixmap_init(GTypeInstance * object,gpointer gclass)882 static void masked_pixmap_init(GTypeInstance *object, gpointer gclass)
883 {
884 	MaskedPixmap *mp = (MaskedPixmap *) object;
885 
886 	mp->src_pixbuf = NULL;
887 
888 	mp->huge_pixbuf = NULL;
889 	mp->huge_width = -1;
890 	mp->huge_height = -1;
891 
892 	mp->pixbuf = NULL;
893 	mp->width = -1;
894 	mp->height = -1;
895 
896 	mp->sm_pixbuf = NULL;
897 	mp->sm_width = -1;
898 	mp->sm_height = -1;
899 }
900 
masked_pixmap_get_type(void)901 static GType masked_pixmap_get_type(void)
902 {
903 	static GType type = 0;
904 
905 	if (!type)
906 	{
907 		static const GTypeInfo info =
908 		{
909 			sizeof (MaskedPixmapClass),
910 			NULL,			/* base_init */
911 			NULL,			/* base_finalise */
912 			masked_pixmap_class_init,
913 			NULL,			/* class_finalise */
914 			NULL,			/* class_data */
915 			sizeof(MaskedPixmap),
916 			0,			/* n_preallocs */
917 			masked_pixmap_init
918 		};
919 
920 		type = g_type_register_static(G_TYPE_OBJECT, "MaskedPixmap",
921 					      &info, 0);
922 	}
923 
924 	return type;
925 }
926 
masked_pixmap_new(GdkPixbuf * full_size)927 MaskedPixmap *masked_pixmap_new(GdkPixbuf *full_size)
928 {
929 	MaskedPixmap *mp;
930 	GdkPixbuf	*src_pixbuf, *normal_pixbuf;
931 
932 	g_return_val_if_fail(full_size != NULL, NULL);
933 
934 	src_pixbuf = scale_pixbuf(full_size, HUGE_WIDTH, HUGE_HEIGHT);
935 	g_return_val_if_fail(src_pixbuf != NULL, NULL);
936 
937 	normal_pixbuf = scale_pixbuf(src_pixbuf, ICON_WIDTH, ICON_HEIGHT);
938 	g_return_val_if_fail(normal_pixbuf != NULL, NULL);
939 
940 	mp = g_object_new(masked_pixmap_get_type(), NULL);
941 
942 	mp->src_pixbuf = src_pixbuf;
943 
944 	mp->pixbuf = normal_pixbuf;
945 	mp->width = gdk_pixbuf_get_width(normal_pixbuf);
946 	mp->height = gdk_pixbuf_get_height(normal_pixbuf);
947 
948 	return mp;
949 }
950 
951 /* Load all the standard pixmaps. Also sets the default window icon. */
load_default_pixmaps(void)952 static void load_default_pixmaps(void)
953 {
954 	GdkPixbuf *pixbuf;
955 	GError *error = NULL;
956 
957 	im_error = mp_from_stock(GTK_STOCK_DIALOG_WARNING,
958 				 GTK_ICON_SIZE_DIALOG);
959 	im_unknown = mp_from_stock(GTK_STOCK_DIALOG_QUESTION,
960 				   GTK_ICON_SIZE_DIALOG);
961 
962 	im_dirs = load_pixmap("dirs");
963 	im_appdir = load_pixmap("application");
964 
965 	pixbuf = gdk_pixbuf_new_from_file(
966 			make_path(app_dir, ".DirIcon"), &error);
967 	if (pixbuf)
968 	{
969 		GList *icon_list;
970 
971 		icon_list = g_list_append(NULL, pixbuf);
972 		gtk_window_set_default_icon_list(icon_list);
973 		g_list_free(icon_list);
974 
975 		g_object_unref(G_OBJECT(pixbuf));
976 	}
977 	else
978 	{
979 		g_warning("%s\n", error->message);
980 		g_error_free(error);
981 	}
982 }
983 
984 /* Also purges memory cache */
purge_disk_cache(GtkWidget * button,gpointer data)985 static void purge_disk_cache(GtkWidget *button, gpointer data)
986 {
987 	char *path;
988 	GList *list = NULL;
989 	DIR *dir;
990 	struct dirent *ent;
991 
992 	g_fscache_purge(pixmap_cache, 0);
993 
994 	path = g_strconcat(home_dir, "/.thumbnails/normal/", NULL);
995 
996 	dir = opendir(path);
997 	if (!dir)
998 	{
999 		report_error(_("Can't delete thumbnails in %s:\n%s"),
1000 				path, g_strerror(errno));
1001 		goto out;
1002 	}
1003 
1004 	while ((ent = readdir(dir)))
1005 	{
1006 		if (ent->d_name[0] == '.')
1007 			continue;
1008 		list = g_list_prepend(list,
1009 				      g_strconcat(path, ent->d_name, NULL));
1010 	}
1011 
1012 	closedir(dir);
1013 
1014 	if (list)
1015 	{
1016 		action_delete(list);
1017 		destroy_glist(&list);
1018 	}
1019 	else
1020 		info_message(_("There are no thumbnails to delete"));
1021 out:
1022 	g_free(path);
1023 }
1024 
thumbs_purge_cache(Option * option,xmlNode * node,guchar * label)1025 static GList *thumbs_purge_cache(Option *option, xmlNode *node, guchar *label)
1026 {
1027 	GtkWidget *button, *align;
1028 
1029 	g_return_val_if_fail(option == NULL, NULL);
1030 
1031 	align = gtk_alignment_new(0, 0.5, 0, 0);
1032 	button = button_new_mixed(GTK_STOCK_CLEAR,
1033 				  _("Purge thumbnails disk cache"));
1034 	gtk_container_add(GTK_CONTAINER(align), button);
1035 	g_signal_connect(button, "clicked", G_CALLBACK(purge_disk_cache), NULL);
1036 
1037 	return g_list_append(NULL, align);
1038 }
1039 
1040 /* Exif reading.
1041  * Based on Thierry Bousch's public domain exifdump.py.
1042  */
1043 
1044 #define JPEG_FORMAT        0x201
1045 #define JPEG_FORMAT_LENGTH 0x202
1046 
1047 /*
1048  * Extract n-byte integer in Motorola (big-endian) format
1049  */
s2n_motorola(const unsigned char * p,int len)1050 static inline long long s2n_motorola(const unsigned char *p, int len)
1051 {
1052     long long a=0;
1053     int i;
1054 
1055     for(i=0; i<len; i++)
1056         a=(a<<8) | (int)(p[i]);
1057 
1058     return a;
1059 }
1060 
1061 /*
1062  * Extract n-byte integer in Intel (little-endian) format
1063  */
s2n_intel(const unsigned char * p,int len)1064 static inline long long s2n_intel(const unsigned char *p, int len)
1065 {
1066     long long a=0;
1067     int i;
1068 
1069     for(i=0; i<len; i++)
1070         a=a | (((int) p[i]) << (i*8));
1071 
1072     return a;
1073 }
1074 
1075 /*
1076  * Extract n-byte integer from data
1077  */
s2n(const unsigned char * dat,int off,int len,char format)1078 static int s2n(const unsigned char *dat, int off, int len, char format)
1079 {
1080     const unsigned char *p=dat+off;
1081 
1082     switch(format) {
1083     case 'I':
1084         return s2n_intel(p, len);
1085 
1086     case 'M':
1087         return s2n_motorola(p, len);
1088     }
1089 
1090     return 0;
1091 }
1092 
1093 /*
1094  * Load header of JPEG/Exif file and attempt to extract the embedded
1095  * thumbnail.  Return NULL on failure.
1096  */
extract_tiff_thumbnail(const gchar * path)1097 static GdkPixbuf *extract_tiff_thumbnail(const gchar *path)
1098 {
1099     FILE *in;
1100     unsigned char header[256];
1101     int i, n;
1102     int length;
1103     unsigned char *data;
1104     char format;
1105     int ifd, entries;
1106     int thumb=0, tlength=0;
1107     GdkPixbuf *buf=NULL;
1108 
1109     in=fopen(path, "rb");
1110     if(!in) {
1111         return NULL;
1112     }
1113 
1114     /* Check for Exif format */
1115     n=fread(header, 1, 12, in);
1116     if(n!=12 || strncmp((char *) header, "\377\330\377\341", 4)!=0 ||
1117        strncmp((char *)header+6, "Exif", 4)!=0) {
1118         fclose(in);
1119         return NULL;
1120     }
1121 
1122     /* Read header */
1123     length=header[4]*256+header[5];
1124     data=g_new(unsigned char, length);
1125     n=fread(data, 1, length, in);
1126     fclose(in);   /* File no longer needed */
1127     if(n!=length) {
1128         g_free(data);
1129         return NULL;
1130     }
1131 
1132     /* Big or little endian (as 'M' or 'I') */
1133     format=data[0];
1134 
1135     /* Skip over main section */
1136     ifd=s2n(data, 4, 4, format);
1137     entries=s2n(data, ifd, 2, format);
1138 
1139     /* Second section contains data on thumbnail */
1140     ifd=s2n(data, ifd+2+12*entries, 4, format);
1141     entries=s2n(data, ifd, 2, format);
1142 
1143     /* Loop over the entries */
1144     for(i=0; i<entries; i++) {
1145         int entry=ifd+2+12*i;
1146         int tag=s2n(data, entry, 2, format);
1147         int type=s2n(data, entry+2, 2, format);
1148         int count, offset;
1149 
1150         count=s2n(data, entry+4, 4, format);
1151         offset=entry+8;
1152 
1153         if(type==4) {
1154             int val=(int) s2n(data, offset, 4, format);
1155 
1156             /* Only interested in two entries, the location of the thumbnail
1157                and its size */
1158             switch(tag) {
1159             case JPEG_FORMAT: thumb=val; break;
1160             case JPEG_FORMAT_LENGTH: tlength=val; break;
1161             }
1162         }
1163     }
1164 
1165     if(thumb && tlength) {
1166         GError *err=NULL;
1167         GdkPixbufLoader *loader;
1168 
1169         /* Don't read outside the header (some files have incorrect data) */
1170         if(thumb+tlength>length)
1171             tlength=length-thumb;
1172 
1173         loader=gdk_pixbuf_loader_new();
1174         gdk_pixbuf_loader_write(loader, data+thumb, tlength, &err);
1175         if(err) {
1176             g_error_free(err);
1177             return NULL;
1178         }
1179 
1180         gdk_pixbuf_loader_close(loader, &err);
1181         if(err) {
1182             g_error_free(err);
1183             return NULL;
1184         }
1185 
1186         buf=gdk_pixbuf_loader_get_pixbuf(loader);
1187         g_object_ref(buf);      /* Ref the image before we unref the loader */
1188         g_object_unref(loader);
1189     }
1190 
1191     g_free(data);
1192 
1193     return buf;
1194 }
1195 
1196