1/* GtkIconTheme - a loader for icon themes
2 * gtk-icon-theme.c Copyright (C) 2002, 2003 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include <sys/types.h>
21#include <sys/stat.h>
22#ifdef HAVE_UNISTD_H
23#include <unistd.h>
24#endif
25#include <string.h>
26#include <stdlib.h>
27#include <math.h>
28#include <glib.h>
29#include <glib/gstdio.h>
30
31#ifdef G_OS_WIN32
32#ifndef S_ISDIR
33#define S_ISDIR(mode) ((mode)&_S_IFDIR)
34#endif
35#define WIN32_LEAN_AND_MEAN
36#include <windows.h>
37#include <shellapi.h>
38#include "win32/gdkwin32.h"
39#endif /* G_OS_WIN32 */
40
41#include "gtkiconthemeprivate.h"
42#include "gtkcsspalettevalueprivate.h"
43#include "gtkcsscolorvalueprivate.h"
44#include "gtkdebug.h"
45#include "gtkiconcacheprivate.h"
46#include "gtkintl.h"
47#include "gtkmain.h"
48#include "gtkprivate.h"
49#include "gtksettingsprivate.h"
50#include "gtksnapshot.h"
51#include "gtkstylecontextprivate.h"
52#include "gtkstyleproviderprivate.h"
53#include "gtksymbolicpaintable.h"
54#include "gtkwidgetprivate.h"
55#include "gdkpixbufutilsprivate.h"
56#include "gdk/gdktextureprivate.h"
57#include "gdk/gdkprofilerprivate.h"
58
59#define GDK_ARRAY_ELEMENT_TYPE char *
60#define GDK_ARRAY_NULL_TERMINATED 1
61#define GDK_ARRAY_FREE_FUNC g_free
62#define GDK_ARRAY_TYPE_NAME GtkStrvBuilder
63#define GDK_ARRAY_NAME gtk_strv_builder
64#define GDK_ARRAY_PREALLOC 16
65#include "gdk/gdkarrayimpl.c"
66
67/**
68 * GtkIconTheme:
69 *
70 * `GtkIconTheme` provides a facility for loading themed icons.
71 *
72 * The main reason for using a name rather than simply providing a filename
73 * is to allow different icons to be used depending on what “icon theme” is
74 * selected by the user. The operation of icon themes on Linux and Unix
75 * follows the [Icon Theme Specification](http://www.freedesktop.org/Standards/icon-theme-spec)
76 * There is a fallback icon theme, named `hicolor`, where applications
77 * should install their icons, but additional icon themes can be installed
78 * as operating system vendors and users choose.
79 *
80 * In many cases, named themes are used indirectly, via [class@Gtk.Image]
81 * rather than directly, but looking up icons directly is also simple. The
82 * `GtkIconTheme` object acts as a database of all the icons in the current
83 * theme. You can create new `GtkIconTheme` objects, but it’s much more
84 * efficient to use the standard icon theme of the `GtkWidget` so that the
85 * icon information is shared with other people looking up icons.
86 *
87 * ```c
88 * GtkIconTheme *icon_theme;
89 * GtkIconPaintable *icon;
90 * GdkPaintable *paintable;
91 *
92 * icon_theme = gtk_icon_theme_get_for_display (gtk_widget_get_display (my_widget));
93 * icon = gtk_icon_theme_lookup_icon (icon_theme,
94 * "my-icon-name", // icon name
95 * 48, // icon size
96 * 1, // scale
97 * 0, // flags);
98 * paintable = GDK_PAINTABLE (icon);
99 * // Use the paintable
100 * g_object_unref (icon);
101 * ```
102 */
103
104
105/* There are a lot of icon names (around 1000 in adwaita and 1000 in
106 * hicolor), and the are short, which makes individual allocations
107 * wasteful (i.e. glibc malloc min chunk size is 32 byte), so we and
108 * fragmenting. Instead we store the string in larger chunks of memory
109 * for the string data, avoiding duplicates via a hash.
110 *
111 * Additionally, doing a up-front hash lookup to intern an
112 * icon name allows further hash lookups (for each directory in
113 * the theme) to use g_direct_hash/equal() which makes those
114 * lookups faster.
115 */
116
117typedef struct _GtkStringSetChunk GtkStringSetChunk;
118
119/* This size allows a malloc to be one page, including the next_chunk
120 pointer and the malloc overhead, which is a good size (one page) */
121#define STRING_SET_CHUNK_SIZE (4096 - 2 *sizeof (gpointer))
122
123struct _GtkStringSetChunk {
124 GtkStringSetChunk *next;
125 char data[STRING_SET_CHUNK_SIZE];
126};
127
128struct _GtkStringSet {
129 GHashTable *hash;
130 GtkStringSetChunk *chunks;
131 int used_in_chunk;
132};
133
134static void
135gtk_string_set_init (GtkStringSet *set)
136{
137 set->hash = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, NULL, NULL);
138 set->chunks = NULL;
139 set->used_in_chunk = STRING_SET_CHUNK_SIZE; /* To trigger a grow directly */
140}
141
142static void
143gtk_string_set_destroy (GtkStringSet *set)
144{
145 GtkStringSetChunk *next, *chunk;
146 g_hash_table_destroy (hash_table: set->hash);
147 for (chunk = set->chunks; chunk != NULL; chunk = next)
148 {
149 next = chunk->next;
150 g_free (mem: chunk);
151 }
152}
153
154static const char *
155gtk_string_set_lookup (GtkStringSet *set,
156 const char *string)
157{
158 return g_hash_table_lookup (hash_table: set->hash, key: string);
159}
160
161static void
162gtk_string_set_list (GtkStringSet *set,
163 GHashTable *result)
164{
165 GHashTableIter iter;
166 gpointer key;
167
168 g_hash_table_iter_init (iter: &iter, hash_table: set->hash);
169 while (g_hash_table_iter_next (iter: &iter, key: &key, NULL))
170 {
171 char *string = key;
172 g_hash_table_insert (hash_table: result, key: string, value: string);
173 }
174}
175
176const char *
177gtk_string_set_add (GtkStringSet *set,
178 const char *string)
179{
180 const char *intern = g_hash_table_lookup (hash_table: set->hash, key: string);
181
182 if (intern == NULL)
183 {
184 GtkStringSetChunk *chunk;
185 int string_len = strlen (s: string) + 1;
186
187 if (string_len > STRING_SET_CHUNK_SIZE - set->used_in_chunk)
188 {
189 gsize chunk_size = sizeof (GtkStringSetChunk);
190 /* Doesn't fit in existing chunk, need a new one. Normally
191 * we allocate one of regular size, but in the case the
192 * string is longer than CHUNK_SIZE that we allocate enough
193 * for it to fit this one string. */
194 if (string_len > STRING_SET_CHUNK_SIZE)
195 chunk_size += string_len - STRING_SET_CHUNK_SIZE;
196 chunk = g_malloc (n_bytes: chunk_size);
197 chunk->next = set->chunks;
198 set->chunks = chunk;
199 set->used_in_chunk = 0;
200 }
201 else
202 {
203 /* We fit in existing chunk */
204 chunk = set->chunks;
205 }
206
207 intern = &chunk->data[set->used_in_chunk];
208 memcpy (dest: (char *)intern, src: string, n: string_len);
209 set->used_in_chunk += string_len;
210 g_hash_table_insert (hash_table: set->hash, key: (char *)intern, value: (char *)intern);
211 }
212
213 return intern;
214}
215
216/* Threading:
217 *
218 * GtkIconTheme is partially threadsafe, construction and setup can
219 * only be done on the main thread (and this is not really fixable
220 * since it uses other things like GdkDisplay and GSettings and
221 * signals on those. However, once the icon theme is set up on the
222 * main thread we can pass it to a thread and do basic lookups on
223 * it. This will cause any parallel calls on the main thread (or any
224 * other thread) to block until its done, but most of the time
225 * lookups are fast. The only time its not fast is when we need
226 * to rescan the theme, but then it would be slow if we didn't block
227 * and did the rescan ourselves anyway.
228 *
229 * All private functions that take a GtkIconTheme (or one of its
230 * private data types (like IconThemeDir, UnthemedIcon, etc) arg are
231 * expected to be called with the icon theme lock held, unless the
232 * function has a _unlocked suffix. Any similar function that must be
233 * called on the main thread, will have a _mainthread suffix.
234 *
235 * So the rules for such functions are:
236 *
237 * * non-_unlocked function cannot call _unlocked functions.
238 * * _unlocked must lock before calling a non-_unlocked.
239 * * non-_mainthread cannot call _mainthread.
240 * * Public APIs must lock before calling a non-_unlocked private function
241 * * Public APIs that never call _mainthread are threadsafe.
242 *
243 * There is a global "icon_cache" G_LOCK, which protects icon_cache
244 * and lru_cache in GtkIconTheme as well as its reverse pointer
245 * GtkIcon->in_cache. This is sometimes taken with the theme lock held
246 * (from the theme side) and sometimes not (from the icon side),
247 * but we never take another lock after taking it, so this is safe.
248 * Since this is a global (not per icon/theme) lock we should never
249 * block while holding it.
250 *
251 * Sometimes there are "weak" references to the icon theme that can
252 * call into the icon theme. For example, from the "theme-changed"
253 * signal. Since these don't own the theme they can run in parallel
254 * with some other thread which could be finalizing the theme. To
255 * avoid this all such references are done via the GtkIconThemeRef
256 * object which contains an NULL:able pointer to the theme and the
257 * main lock for that theme. Using this we can safely generate a ref
258 * for the theme if it still lives (or get NULL if it doesn't).
259 *
260 * The icon theme needs to call into the icons sometimes, for example
261 * when checking if it should be cached (icon_should_cache__unlocked)
262 * this takes the icon->texture_lock while the icon theme is locked.
263 * In order to avoid ABBA style deadlocks this means the icon code
264 * should never try to lock an icon theme.
265 */
266
267#define FALLBACK_ICON_THEME "hicolor"
268
269typedef enum
270{
271 ICON_THEME_DIR_FIXED,
272 ICON_THEME_DIR_SCALABLE,
273 ICON_THEME_DIR_THRESHOLD,
274 ICON_THEME_DIR_UNTHEMED
275} IconThemeDirType;
276
277#if 0
278#define DEBUG_CACHE(args) g_print args
279#else
280#define DEBUG_CACHE(args)
281#endif
282
283#define LRU_CACHE_SIZE 100
284#define MAX_LRU_TEXTURE_SIZE 128
285
286typedef struct _GtkIconPaintableClass GtkIconPaintableClass;
287typedef struct _GtkIconThemeClass GtkIconThemeClass;
288
289
290typedef struct _GtkIconThemeRef GtkIconThemeRef;
291
292/* Acts as a database of information about an icon theme.
293 * Normally, you retrieve the icon theme for a particular
294 * display using gtk_icon_theme_get_for_display() and it
295 * will contain information about current icon theme for
296 * that display, but you can also create a new `GtkIconTheme`
297 * object and set the icon theme name explicitly using
298 * gtk_icon_theme_set_theme_name().
299 */
300struct _GtkIconTheme
301{
302 GObject parent_instance;
303 GtkIconThemeRef *ref;
304
305 GHashTable *icon_cache; /* Protected by icon_cache lock */
306
307 GtkIconPaintable *lru_cache[LRU_CACHE_SIZE]; /* Protected by icon_cache lock */
308 int lru_cache_current; /* Protected by icon_cache lock */
309
310 char *current_theme;
311 char **search_path;
312 char **resource_path;
313
314 guint custom_theme : 1;
315 guint is_display_singleton : 1;
316 guint pixbuf_supports_svg : 1;
317 guint themes_valid : 1;
318
319 /* A list of all the themes needed to look up icons.
320 * In search order, without duplicates
321 */
322 GList *themes;
323 GHashTable *unthemed_icons;
324
325 /* GdkDisplay for the icon theme (may be NULL) */
326 GdkDisplay *display;
327 GtkSettings *display_settings;
328
329 /* time when we last stat:ed for theme changes */
330 gint64 last_stat_time;
331 GArray *dir_mtimes;
332
333 gulong theme_changed_idle;
334
335 int serial;
336};
337
338struct _GtkIconThemeClass
339{
340 GObjectClass parent_class;
341
342 void (* changed) (GtkIconTheme *self);
343};
344
345typedef struct {
346 char **icon_names;
347 int size;
348 int scale;
349 GtkIconLookupFlags flags;
350} IconKey;
351
352struct _GtkIconPaintableClass
353{
354 GObjectClass parent_class;
355};
356
357/* This lock protects both IconTheme.icon_cache and the dependent Icon.in_cache.
358 * Its a global lock, so hold it only for short times. */
359G_LOCK_DEFINE_STATIC(icon_cache);
360
361/**
362 * GtkIconPaintable:
363 *
364 * Contains information found when looking up an icon in `GtkIconTheme`.
365 *
366 * `GtkIconPaintable` implements `GdkPaintable`.
367 */
368struct _GtkIconPaintable
369{
370 GObject parent_instance;
371
372 /* Information about the source
373 */
374 IconKey key;
375 GtkIconTheme *in_cache; /* Protected by icon_cache lock */
376
377 char *icon_name;
378 char *filename;
379 GLoadableIcon *loadable;
380
381#ifdef G_OS_WIN32
382 /* win32 icon (if there is any) */
383 GdkPixbuf *win32_icon;
384#endif
385
386 /* Parameters influencing the scaled icon
387 */
388 int desired_size;
389 int desired_scale;
390 guint is_svg : 1;
391 guint is_resource : 1;
392 guint is_symbolic : 1;
393
394 /* Cached information if we go ahead and try to load the icon.
395 *
396 * All access to these are protected by the texture_lock. Everything
397 * above is immutable after construction and can be used without
398 * locks.
399 */
400 GMutex texture_lock;
401
402 GdkTexture *texture;
403};
404
405typedef struct
406{
407 char *name;
408 char *display_name;
409 char *comment;
410
411 GArray *dir_sizes; /* IconThemeDirSize */
412 GArray *dirs; /* IconThemeDir */
413 GtkStringSet icons;
414} IconTheme;
415
416typedef struct
417{
418 guint16 dir_index; /* index in dirs */
419 guint8 best_suffix;
420 guint8 best_suffix_no_svg;
421} IconThemeFile;
422
423typedef struct
424{
425 IconThemeDirType type;
426 int size;
427 int min_size;
428 int max_size;
429 int threshold;
430 int scale;
431
432 GArray *icon_files;
433 GHashTable *icon_hash; /* name (interned) -> file index */
434} IconThemeDirSize;
435
436typedef struct
437{
438 gboolean is_resource;
439 char *path; /* e.g. "/usr/share/icons/hicolor/32x32/apps" */
440} IconThemeDir;
441
442typedef struct
443{
444 char *svg_filename;
445 char *no_svg_filename;
446 gboolean is_resource;
447} UnthemedIcon;
448
449typedef struct
450{
451 char *dir;
452 time_t mtime;
453 GtkIconCache *cache;
454 gboolean exists;
455} IconThemeDirMtime;
456
457static void gtk_icon_theme_finalize (GObject *object);
458static void gtk_icon_theme_dispose (GObject *object);
459static IconTheme * theme_new (const char *theme_name,
460 GKeyFile *theme_file);
461static void theme_dir_size_destroy (IconThemeDirSize *dir_size);
462static void theme_dir_destroy (IconThemeDir *dir);
463static void theme_destroy (IconTheme *theme);
464static GtkIconPaintable *theme_lookup_icon (IconTheme *theme,
465 const char *icon_name,
466 int size,
467 int scale,
468 gboolean allow_svg);
469static gboolean theme_has_icon (IconTheme *theme,
470 const char *icon_name);
471static void theme_subdir_load (GtkIconTheme *self,
472 IconTheme *theme,
473 GKeyFile *theme_file,
474 char *subdir);
475static void do_theme_change (GtkIconTheme *self);
476static void blow_themes (GtkIconTheme *self);
477static gboolean rescan_themes (GtkIconTheme *self);
478static GtkIconPaintable *icon_paintable_new (const char *icon_name,
479 int desired_size,
480 int desired_scale);
481static IconCacheFlag suffix_from_name (const char *name);
482static void icon_ensure_texture__locked (GtkIconPaintable *icon,
483 gboolean in_thread);
484static void gtk_icon_theme_unset_display (GtkIconTheme *self);
485static void gtk_icon_theme_set_display (GtkIconTheme *self,
486 GdkDisplay *display);
487static void update_current_theme__mainthread (GtkIconTheme *self);
488static gboolean ensure_valid_themes (GtkIconTheme *self,
489 gboolean non_blocking);
490
491
492static guint signal_changed = 0;
493
494/* This is like a weak ref with a lock, anyone doing
495 * operations on the theme must take the lock in this,
496 * but you can also take the lock even if the theme
497 * has been finalized (but theme will then be NULL).
498 *
499 * This is used to avoid race conditions where signals
500 * like theme-changed happen on the main thread while
501 * the last active owning ref of the icon theme is
502 * on some thread.
503 */
504struct _GtkIconThemeRef
505{
506 gatomicrefcount count;
507 GMutex lock;
508 GtkIconTheme *theme;
509};
510
511static GtkIconThemeRef *
512gtk_icon_theme_ref_new (GtkIconTheme *theme)
513{
514 GtkIconThemeRef *ref = g_new0 (GtkIconThemeRef, 1);
515
516 g_atomic_ref_count_init (arc: &ref->count);
517 g_mutex_init (mutex: &ref->lock);
518 ref->theme = theme;
519
520 return ref;
521}
522
523static GtkIconThemeRef *
524gtk_icon_theme_ref_ref (GtkIconThemeRef *ref)
525{
526 g_atomic_ref_count_inc (arc: &ref->count);
527 return ref;
528}
529
530static void
531gtk_icon_theme_ref_unref (GtkIconThemeRef *ref)
532{
533 if (g_atomic_ref_count_dec (arc: &ref->count))
534 {
535 g_assert (ref->theme == NULL);
536 g_mutex_clear (mutex: &ref->lock);
537 g_free (mem: ref);
538 }
539}
540
541/* Take the lock and if available ensure the theme lives until (at
542 * least) ref_release is called. */
543static GtkIconTheme *
544gtk_icon_theme_ref_aquire (GtkIconThemeRef *ref)
545{
546 g_mutex_lock (mutex: &ref->lock);
547 if (ref->theme)
548 g_object_ref (ref->theme);
549 return ref->theme;
550}
551
552static void
553gtk_icon_theme_ref_release (GtkIconThemeRef *ref)
554{
555 GtkIconTheme *theme;
556
557 /* Get a pointer to the theme, because when we unlock it could become NULLed by dispose, this pointer still owns a ref */
558 theme = ref->theme;
559 g_mutex_unlock (mutex: &ref->lock);
560
561 /* Then unref outside the lock, because otherwise if this is the last ref the dispose handler would deadlock trying to NULL ref->theme */
562 if (theme)
563 g_object_unref (object: theme);
564
565}
566
567static void
568gtk_icon_theme_ref_dispose (GtkIconThemeRef *ref)
569{
570 gtk_icon_theme_ref_aquire (ref);
571 ref->theme = NULL;
572 gtk_icon_theme_ref_release (ref);
573}
574
575static void
576gtk_icon_theme_lock (GtkIconTheme *self)
577{
578 g_mutex_lock (mutex: &self->ref->lock);
579}
580
581static void
582gtk_icon_theme_unlock (GtkIconTheme *self)
583{
584 g_mutex_unlock (mutex: &self->ref->lock);
585}
586
587
588static guint
589icon_key_hash (gconstpointer _key)
590{
591 const IconKey *key = _key;
592 guint h = 0;
593 int i;
594 for (i = 0; key->icon_names[i] != NULL; i++)
595 h ^= g_str_hash (v: key->icon_names[i]);
596
597 h ^= key->size * 0x10001;
598 h ^= key->scale * 0x1000010;
599 h ^= key->flags * 0x100000100;
600
601 return h;
602}
603
604static gboolean
605icon_key_equal (gconstpointer _a,
606 gconstpointer _b)
607{
608 const IconKey *a = _a;
609 const IconKey *b = _b;
610 int i;
611
612 if (a->size != b->size)
613 return FALSE;
614
615 if (a->scale != b->scale)
616 return FALSE;
617
618 if (a->flags != b->flags)
619 return FALSE;
620
621 for (i = 0;
622 a->icon_names[i] != NULL &&
623 b->icon_names[i] != NULL; i++)
624 {
625 if (strcmp (s1: a->icon_names[i], s2: b->icon_names[i]) != 0)
626 return FALSE;
627 }
628
629 return a->icon_names[i] == NULL && b->icon_names[i] == NULL;
630}
631
632/****************** Icon cache ***********************
633 *
634 * The icon cache, this spans both GtkIconTheme and GtkIcon, so the locking is
635 * a bit tricky. Never do block with the lock held, and never do any
636 * callouts to other code. In particular, don't call theme or finalizers
637 * because that will take the lock when removing from the icon cache.
638 */
639
640/* This is called with icon_cache lock held so must not take any locks */
641static gboolean
642_icon_cache_should_lru_cache (GtkIconPaintable *icon)
643{
644 return icon->desired_size <= MAX_LRU_TEXTURE_SIZE;
645}
646
647/* This returns the old lru element because we can't unref it with
648 * the lock held */
649static GtkIconPaintable *
650_icon_cache_add_to_lru_cache (GtkIconTheme *theme,
651 GtkIconPaintable *icon)
652{
653 GtkIconPaintable *old_icon = NULL;
654
655 /* Avoid storing the same info multiple times in a row */
656 if (theme->lru_cache[theme->lru_cache_current] != icon)
657 {
658 theme->lru_cache_current = (theme->lru_cache_current + 1) % LRU_CACHE_SIZE;
659 old_icon = theme->lru_cache[theme->lru_cache_current];
660 theme->lru_cache[theme->lru_cache_current] = g_object_ref (icon);
661 }
662
663 return old_icon;
664}
665
666static GtkIconPaintable *
667icon_cache_lookup (GtkIconTheme *theme,
668 IconKey *key)
669{
670 GtkIconPaintable *old_icon = NULL;
671 GtkIconPaintable *icon;
672
673 G_LOCK (icon_cache);
674
675 icon = g_hash_table_lookup (hash_table: theme->icon_cache, key);
676 if (icon != NULL)
677 {
678 DEBUG_CACHE (("cache hit %p (%s %d 0x%x) (cache size %d)\n",
679 icon,
680 g_strjoinv (",", icon->key.icon_names),
681 icon->key.size, icon->key.flags,
682 g_hash_table_size (theme->icon_cache)));
683
684 icon = g_object_ref (icon);
685
686 /* Move item to front in LRU cache */
687 if (_icon_cache_should_lru_cache (icon))
688 old_icon = _icon_cache_add_to_lru_cache (theme, icon);
689 }
690
691 G_UNLOCK (icon_cache);
692
693 /* Call potential finalizer outside the lock */
694 if (old_icon)
695 g_object_unref (object: old_icon);
696
697 return icon;
698}
699
700/* The icon was removed from the icon_hash hash table.
701 * This is a callback from the icon_cache hashtable, so the icon_cache lock is already held.
702 */
703static void
704icon_uncached_cb (GtkIconPaintable *icon)
705{
706 DEBUG_CACHE (("removing %p (%s %d 0x%x) from cache (icon_them: %p) (cache size %d)\n",
707 icon,
708 g_strjoinv (",", icon->key.icon_names),
709 icon->key.size, icon->key.flags,
710 self,
711 icon_theme != NULL ? g_hash_table_size (self->icon_cache) : 0));
712 g_assert (icon->in_cache != NULL);
713 icon->in_cache = NULL;
714}
715
716static void
717icon_cache_mark_used_if_cached (GtkIconPaintable *icon)
718{
719 GtkIconPaintable *old_icon = NULL;
720
721 if (!_icon_cache_should_lru_cache (icon))
722 return;
723
724 G_LOCK (icon_cache);
725 if (icon->in_cache)
726 old_icon = _icon_cache_add_to_lru_cache (theme: icon->in_cache, icon);
727 G_UNLOCK (icon_cache);
728
729 /* Call potential finalizers outside the lock */
730 if (old_icon)
731 g_object_unref (object: old_icon);
732}
733
734static void
735icon_cache_add (GtkIconTheme *theme,
736 GtkIconPaintable *icon)
737{
738 GtkIconPaintable *old_icon = NULL;
739
740 G_LOCK (icon_cache);
741 icon->in_cache = theme;
742 g_hash_table_insert (hash_table: theme->icon_cache, key: &icon->key, value: icon);
743
744 if (_icon_cache_should_lru_cache (icon))
745 old_icon = _icon_cache_add_to_lru_cache (theme, icon);
746 DEBUG_CACHE (("adding %p (%s %d 0x%x) to cache (cache size %d)\n",
747 icon,
748 g_strjoinv (",", icon->key.icon_names),
749 icon->key.size, icon->key.flags,
750 g_hash_table_size (theme->icon_cache)));
751 G_UNLOCK (icon_cache);
752
753 /* Call potential finalizer outside the lock */
754 if (old_icon)
755 g_object_unref (object: old_icon);
756}
757
758static void
759icon_cache_remove (GtkIconPaintable *icon)
760{
761 G_LOCK (icon_cache);
762 if (icon->in_cache)
763 g_hash_table_remove (hash_table: icon->in_cache->icon_cache, key: &icon->key);
764 G_UNLOCK (icon_cache);
765}
766
767static void
768icon_cache_clear (GtkIconTheme *theme)
769{
770 int i;
771 GtkIconPaintable *old_icons[LRU_CACHE_SIZE];
772
773 G_LOCK (icon_cache);
774 g_hash_table_remove_all (hash_table: theme->icon_cache);
775 for (i = 0; i < LRU_CACHE_SIZE; i ++)
776 {
777 old_icons[i] = theme->lru_cache[i];
778 theme->lru_cache[i] = NULL;
779 }
780 G_UNLOCK (icon_cache);
781
782 /* Call potential finalizers outside the lock */
783 for (i = 0; i < LRU_CACHE_SIZE; i ++)
784 {
785 if (old_icons[i] != NULL)
786 g_object_unref (object: old_icons[i]);
787 }
788}
789
790/****************** End of icon cache ***********************/
791
792G_DEFINE_TYPE (GtkIconTheme, gtk_icon_theme, G_TYPE_OBJECT)
793
794/**
795 * gtk_icon_theme_new:
796 *
797 * Creates a new icon theme object.
798 *
799 * Icon theme objects are used to lookup up an icon by name
800 * in a particular icon theme. Usually, you’ll want to use
801 * [func@Gtk.IconTheme.get_for_display] rather than creating
802 * a new icon theme object for scratch.
803 *
804 * Returns: the newly created `GtkIconTheme` object.
805 */
806GtkIconTheme *
807gtk_icon_theme_new (void)
808{
809 return g_object_new (GTK_TYPE_ICON_THEME, NULL);
810}
811
812static void
813load_theme_thread (GTask *task,
814 gpointer source_object,
815 gpointer task_data,
816 GCancellable *cancellable)
817{
818 GtkIconTheme *self = GTK_ICON_THEME (source_object);
819
820 gtk_icon_theme_lock (self);
821 ensure_valid_themes (self, FALSE);
822 gtk_icon_theme_unlock (self);
823 g_task_return_pointer (task, NULL, NULL);
824}
825
826static void
827gtk_icon_theme_load_in_thread (GtkIconTheme *self)
828{
829 GTask *task;
830
831 task = g_task_new (source_object: self, NULL, NULL, NULL);
832 g_task_set_task_data (task, g_object_ref (self), task_data_destroy: g_object_unref);
833 g_task_run_in_thread (task, task_func: load_theme_thread);
834 g_object_unref (object: task);
835}
836
837/**
838 * gtk_icon_theme_get_for_display:
839 * @display: a `GdkDisplay`
840 *
841 * Gets the icon theme object associated with @display.
842 *
843 * If this function has not previously been called for the given
844 * display, a new icon theme object will be created and associated
845 * with the display. Icon theme objects are fairly expensive to create,
846 * so using this function is usually a better choice than calling
847 * [ctor@Gtk.IconTheme.new] and setting the display yourself; by using
848 * this function a single icon theme object will be shared between users.
849 *
850 * Returns: (transfer none): A unique `GtkIconTheme` associated with
851 * the given display. This icon theme is associated with the display
852 * and can be used as long as the display is open. Do not ref or unref it.
853 */
854GtkIconTheme *
855gtk_icon_theme_get_for_display (GdkDisplay *display)
856{
857 GtkIconTheme *self;
858
859 g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
860
861 self = g_object_get_data (G_OBJECT (display), key: "gtk-icon-theme");
862 if (!self)
863 {
864 self = gtk_icon_theme_new ();
865 self->is_display_singleton = TRUE;
866 g_object_set_data (G_OBJECT (display), I_("gtk-icon-theme"), data: self);
867
868 /* Call this after setting the user-data, because it recurses into gtk_icon_theme_get_for_display via the thememing machinery */
869 gtk_icon_theme_set_display (self, display);
870
871 /* Queue early read of the default themes, we read the icon theme name in set_display(). */
872 gtk_icon_theme_load_in_thread (self);
873 }
874
875 return self;
876}
877
878enum {
879 PROP_DISPLAY = 1,
880 PROP_ICON_NAMES,
881 PROP_SEARCH_PATH,
882 PROP_RESOURCE_PATH,
883 PROP_THEME_NAME,
884 LAST_PROP
885};
886
887static GParamSpec *props[LAST_PROP];
888
889static void
890gtk_icon_theme_get_property (GObject *object,
891 guint prop_id,
892 GValue *value,
893 GParamSpec *pspec)
894{
895 GtkIconTheme *self = GTK_ICON_THEME (object);
896
897 switch (prop_id)
898 {
899 case PROP_DISPLAY:
900 g_value_set_object (value, v_object: self->display);
901 break;
902
903 case PROP_ICON_NAMES:
904 g_value_take_boxed (value, v_boxed: gtk_icon_theme_get_icon_names (self));
905 break;
906
907 case PROP_SEARCH_PATH:
908 g_value_set_boxed (value, v_boxed: self->search_path);
909 break;
910
911 case PROP_RESOURCE_PATH:
912 g_value_set_boxed (value, v_boxed: self->resource_path);
913 break;
914
915 case PROP_THEME_NAME:
916 g_value_take_string (value, v_string: gtk_icon_theme_get_theme_name (self));
917 break;
918
919 default:
920 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
921 break;
922 }
923}
924
925static void
926gtk_icon_theme_set_property (GObject *object,
927 guint prop_id,
928 const GValue *value,
929 GParamSpec *pspec)
930{
931 GtkIconTheme *self = GTK_ICON_THEME (object);
932
933 switch (prop_id)
934 {
935 case PROP_DISPLAY:
936 gtk_icon_theme_set_display (self, display: g_value_get_object (value));
937 break;
938
939 case PROP_SEARCH_PATH:
940 gtk_icon_theme_set_search_path (self, path: g_value_get_boxed (value));
941 break;
942
943 case PROP_RESOURCE_PATH:
944 gtk_icon_theme_set_resource_path (self, path: g_value_get_boxed (value));
945 break;
946
947 case PROP_THEME_NAME:
948 gtk_icon_theme_set_theme_name (self, theme_name: g_value_get_string (value));
949 break;
950
951 default:
952 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
953 break;
954 }
955}
956
957static void
958gtk_icon_theme_class_init (GtkIconThemeClass *klass)
959{
960 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
961
962 gobject_class->finalize = gtk_icon_theme_finalize;
963 gobject_class->dispose = gtk_icon_theme_dispose;
964 gobject_class->get_property = gtk_icon_theme_get_property;
965 gobject_class->set_property = gtk_icon_theme_set_property;
966
967 /**
968 * GtkIconTheme::changed:
969 * @self: the icon theme
970 *
971 * Emitted when the icon theme changes.
972 *
973 * This can happen becuase current icon theme is switched or
974 * because GTK detects that a change has occurred in the
975 * contents of the current icon theme.
976 */
977 signal_changed = g_signal_new (I_("changed"),
978 G_TYPE_FROM_CLASS (klass),
979 signal_flags: G_SIGNAL_RUN_LAST,
980 G_STRUCT_OFFSET (GtkIconThemeClass, changed),
981 NULL, NULL,
982 NULL,
983 G_TYPE_NONE, n_params: 0);
984
985 /**
986 * GtkIconTheme:display:
987 *
988 * The display that this icon theme object is attached to.
989 */
990 props[PROP_DISPLAY] =
991 g_param_spec_object (name: "display",
992 P_("Display"),
993 P_("Display"),
994 GDK_TYPE_DISPLAY,
995 flags: G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
996
997 /**
998 * GtkIconTheme:icon-names:
999 *
1000 * The icon names that are supported by the icon theme.
1001 */
1002 props[PROP_ICON_NAMES] =
1003 g_param_spec_boxed (name: "icon-names",
1004 P_("Supported icon names"),
1005 P_("Supported icon names"),
1006 G_TYPE_STRV,
1007 GTK_PARAM_READABLE);
1008
1009 /**
1010 * GtkIconTheme:search-path:
1011 *
1012 * The search path for this icon theme.
1013 *
1014 * When looking for icons, GTK will search for a subdirectory of
1015 * one or more of the directories in the search path with the same
1016 * name as the icon theme containing an index.theme file. (Themes
1017 * from multiple of the path elements are combined to allow themes
1018 * to be extended by adding icons in the user’s home directory.)
1019 */
1020 props[PROP_SEARCH_PATH] =
1021 g_param_spec_boxed (name: "search-path",
1022 P_("Search path"),
1023 P_("Search path"),
1024 G_TYPE_STRV,
1025 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1026
1027 /**
1028 * GtkIconTheme:resource-path:
1029 *
1030 * Resource paths that will be looked at when looking for icons,
1031 * similar to search paths.
1032 *
1033 * The resources are considered as part of the hicolor icon theme
1034 * and must be located in subdirectories that are defined in the
1035 * hicolor icon theme, such as `@path/16x16/actions/run.png`.
1036 * Icons that are directly placed in the resource path instead
1037 * of a subdirectory are also considered as ultimate fallback.
1038 */
1039 props[PROP_RESOURCE_PATH] =
1040 g_param_spec_boxed (name: "resource-path",
1041 P_("Resource path"),
1042 P_("Resource path"),
1043 G_TYPE_STRV,
1044 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1045
1046 /**
1047 * GtkIconTheme:theme-name:
1048 *
1049 * The name of the icon theme that is being used.
1050 *
1051 * Unless set to a different value, this will be the value of
1052 * the `GtkSettings:gtk-icon-theme-name` property of the `GtkSettings`
1053 * object associated to the display of the icontheme object.
1054 */
1055 props[PROP_THEME_NAME] =
1056 g_param_spec_string (name: "theme-name",
1057 P_("Theme name"),
1058 P_("Theme name"),
1059 NULL,
1060 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1061
1062 g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_PROP, pspecs: props);
1063}
1064
1065
1066/* Callback when the display that the icon theme is attached
1067 * to is closed; unset the display, and if it’s the unique theme
1068 * for the display, drop the reference
1069 */
1070static void
1071display_closed__mainthread_unlocked (GdkDisplay *display,
1072 gboolean is_error,
1073 GtkIconThemeRef *ref)
1074{
1075 GtkIconTheme *self = gtk_icon_theme_ref_aquire (ref);
1076 gboolean was_display_singleton;
1077
1078 if (self)
1079 {
1080 /* This is only set at construction and accessed here in the main thread, so no locking necessary */
1081 was_display_singleton = self->is_display_singleton;
1082 if (was_display_singleton)
1083 {
1084 g_object_set_data (G_OBJECT (display), I_("gtk-icon-theme"), NULL);
1085 self->is_display_singleton = FALSE;
1086 }
1087
1088 gtk_icon_theme_unset_display (self);
1089 update_current_theme__mainthread (self);
1090
1091 if (was_display_singleton)
1092 g_object_unref (object: self);
1093 }
1094
1095 gtk_icon_theme_ref_release (ref);
1096}
1097
1098static void
1099update_current_theme__mainthread (GtkIconTheme *self)
1100{
1101#define theme_changed(_old, _new) \
1102 ((_old && !_new) || (!_old && _new) || \
1103 (_old && _new && strcmp (_old, _new) != 0))
1104
1105 if (!self->custom_theme)
1106 {
1107 char *theme = NULL;
1108 gboolean changed = FALSE;
1109
1110 if (self->display)
1111 {
1112 GtkSettings *settings = gtk_settings_get_for_display (display: self->display);
1113 g_object_get (object: settings, first_property_name: "gtk-icon-theme-name", &theme, NULL);
1114 }
1115
1116 if (theme_changed (self->current_theme, theme))
1117 {
1118 g_free (mem: self->current_theme);
1119 self->current_theme = theme;
1120 changed = TRUE;
1121 }
1122 else
1123 g_free (mem: theme);
1124
1125 if (changed)
1126 do_theme_change (self);
1127 }
1128#undef theme_changed
1129}
1130
1131/* Callback when the icon theme GtkSetting changes
1132 */
1133static void
1134theme_changed__mainthread_unlocked (GtkSettings *settings,
1135 GParamSpec *pspec,
1136 GtkIconThemeRef *ref)
1137{
1138 GtkIconTheme *self = gtk_icon_theme_ref_aquire (ref);
1139
1140 if (self)
1141 {
1142 update_current_theme__mainthread (self);
1143
1144 /* Queue early read of the new theme */
1145 gtk_icon_theme_load_in_thread (self);
1146 }
1147
1148 gtk_icon_theme_ref_release (ref);
1149}
1150
1151static void
1152gtk_icon_theme_unset_display (GtkIconTheme *self)
1153{
1154 if (self->display)
1155 {
1156 g_signal_handlers_disconnect_by_func (self->display,
1157 (gpointer) display_closed__mainthread_unlocked,
1158 self->ref);
1159 g_signal_handlers_disconnect_by_func (self->display_settings,
1160 (gpointer) theme_changed__mainthread_unlocked,
1161 self->ref);
1162
1163 self->display = NULL;
1164 self->display_settings = NULL;
1165
1166 g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_DISPLAY]);
1167 }
1168}
1169
1170void
1171gtk_icon_theme_set_display (GtkIconTheme *self,
1172 GdkDisplay *display)
1173{
1174 g_return_if_fail (GTK_ICON_THEME (self));
1175 g_return_if_fail (display == NULL || GDK_IS_DISPLAY (display));
1176
1177 gtk_icon_theme_lock (self);
1178
1179 g_object_freeze_notify (G_OBJECT (self));
1180
1181 gtk_icon_theme_unset_display (self);
1182
1183 if (display)
1184 {
1185 self->display = display;
1186 self->display_settings = gtk_settings_get_for_display (display);
1187
1188 g_signal_connect_data (instance: display, detailed_signal: "closed",
1189 G_CALLBACK (display_closed__mainthread_unlocked),
1190 data: gtk_icon_theme_ref_ref (ref: self->ref),
1191 destroy_data: (GClosureNotify)gtk_icon_theme_ref_unref,
1192 connect_flags: 0);
1193 g_signal_connect_data (instance: self->display_settings, detailed_signal: "notify::gtk-icon-theme-name",
1194 G_CALLBACK (theme_changed__mainthread_unlocked),
1195 data: gtk_icon_theme_ref_ref (ref: self->ref),
1196 destroy_data: (GClosureNotify)gtk_icon_theme_ref_unref,
1197 connect_flags: 0);
1198
1199 g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_DISPLAY]);
1200 }
1201
1202 update_current_theme__mainthread (self);
1203
1204 gtk_icon_theme_unlock (self);
1205
1206 g_object_thaw_notify (G_OBJECT (self));
1207}
1208
1209/* Checks whether a loader for SVG files has been registered
1210 * with GdkPixbuf.
1211 */
1212static gboolean
1213pixbuf_supports_svg (void)
1214{
1215 GSList *formats;
1216 GSList *tmp_list;
1217 static int found_svg = -1;
1218
1219 if (found_svg != -1)
1220 return found_svg;
1221
1222 formats = gdk_pixbuf_get_formats ();
1223
1224 found_svg = FALSE;
1225 for (tmp_list = formats; tmp_list && !found_svg; tmp_list = tmp_list->next)
1226 {
1227 char **mime_types = gdk_pixbuf_format_get_mime_types (format: tmp_list->data);
1228 char **mime_type;
1229
1230 for (mime_type = mime_types; *mime_type && !found_svg; mime_type++)
1231 {
1232 if (strcmp (s1: *mime_type, s2: "image/svg") == 0)
1233 found_svg = TRUE;
1234 }
1235
1236 g_strfreev (str_array: mime_types);
1237 }
1238
1239 g_slist_free (list: formats);
1240
1241 return found_svg;
1242}
1243
1244static void
1245free_dir_mtime (IconThemeDirMtime *dir_mtime)
1246{
1247 if (dir_mtime->cache)
1248 gtk_icon_cache_unref (cache: dir_mtime->cache);
1249
1250 g_free (mem: dir_mtime->dir);
1251}
1252
1253static void
1254gtk_icon_theme_init (GtkIconTheme *self)
1255{
1256 const char * const *xdg_data_dirs;
1257 int i, j;
1258 int len;
1259
1260 self->ref = gtk_icon_theme_ref_new (theme: self);
1261
1262 self->icon_cache = g_hash_table_new_full (hash_func: icon_key_hash, key_equal_func: icon_key_equal, NULL,
1263 value_destroy_func: (GDestroyNotify)icon_uncached_cb);
1264
1265 self->custom_theme = FALSE;
1266 self->dir_mtimes = g_array_new (FALSE, TRUE, element_size: sizeof (IconThemeDirMtime));
1267 g_array_set_clear_func (array: self->dir_mtimes, clear_func: (GDestroyNotify)free_dir_mtime);
1268
1269 xdg_data_dirs = g_get_system_data_dirs ();
1270 for (i = 0; xdg_data_dirs[i]; i++) ;
1271
1272 len = 2 * i + 3;
1273
1274 self->search_path = g_new (char *, len);
1275
1276 i = 0;
1277 self->search_path[i++] = g_build_filename (first_element: g_get_user_data_dir (), "icons", NULL);
1278 self->search_path[i++] = g_build_filename (first_element: g_get_home_dir (), ".icons", NULL);
1279
1280 for (j = 0; xdg_data_dirs[j]; j++)
1281 self->search_path[i++] = g_build_filename (first_element: xdg_data_dirs[j], "icons", NULL);
1282
1283 for (j = 0; xdg_data_dirs[j]; j++)
1284 self->search_path[i++] = g_build_filename (first_element: xdg_data_dirs[j], "pixmaps", NULL);
1285
1286 self->search_path[i] = NULL;
1287
1288 self->resource_path = g_new (char *, 2);
1289 self->resource_path[0] = g_strdup (str: "/org/gtk/libgtk/icons/");
1290 self->resource_path[1] = NULL;
1291
1292 self->themes_valid = FALSE;
1293 self->themes = NULL;
1294 self->unthemed_icons = NULL;
1295
1296 self->pixbuf_supports_svg = pixbuf_supports_svg ();
1297}
1298
1299static gboolean
1300theme_changed_idle__mainthread_unlocked (gpointer user_data)
1301{
1302 GtkIconThemeRef *ref = (GtkIconThemeRef *)user_data;
1303 GtkIconTheme *self;
1304 GdkDisplay *display = NULL;
1305
1306 self = gtk_icon_theme_ref_aquire (ref);
1307 if (self)
1308 {
1309 g_object_ref (self); /* Ensure theme lives during the changed signal emissions */
1310
1311 self->theme_changed_idle = 0;
1312
1313 if (self->display && self->is_display_singleton)
1314 display = g_object_ref (self->display);
1315 }
1316 gtk_icon_theme_ref_release (ref);
1317
1318 if (self)
1319 {
1320 /* Emit signals outside locks. */
1321 g_signal_emit (instance: self, signal_id: signal_changed, detail: 0);
1322
1323 if (display)
1324 {
1325 GtkSettings *settings = gtk_settings_get_for_display (display: self->display);
1326 gtk_style_provider_changed (GTK_STYLE_PROVIDER (settings));
1327 gtk_system_setting_changed (display, setting: GTK_SYSTEM_SETTING_ICON_THEME);
1328 g_object_unref (object: display);
1329 }
1330
1331 g_object_unref (object: self);
1332 }
1333
1334 return FALSE;
1335}
1336
1337static void
1338queue_theme_changed (GtkIconTheme *self)
1339{
1340 if (!self->theme_changed_idle)
1341 {
1342 self->theme_changed_idle = g_idle_add_full (GTK_PRIORITY_RESIZE - 2,
1343 function: theme_changed_idle__mainthread_unlocked,
1344 data: gtk_icon_theme_ref_ref (ref: self->ref),
1345 notify: (GDestroyNotify)gtk_icon_theme_ref_unref);
1346 gdk_source_set_static_name_by_id (tag: self->theme_changed_idle, name: "[gtk] theme_changed_idle");
1347 }
1348}
1349
1350static void
1351do_theme_change (GtkIconTheme *self)
1352{
1353 icon_cache_clear (theme: self);
1354
1355 if (!self->themes_valid)
1356 return;
1357
1358 GTK_DISPLAY_NOTE (self->display, ICONTHEME,
1359 g_message ("change to icon theme \"%s\"", self->current_theme));
1360 blow_themes (self);
1361
1362 queue_theme_changed (self);
1363}
1364
1365static void
1366blow_themes (GtkIconTheme *self)
1367{
1368 if (self->themes_valid)
1369 {
1370 g_list_free_full (list: self->themes, free_func: (GDestroyNotify) theme_destroy);
1371 g_array_set_size (array: self->dir_mtimes, length: 0);
1372 g_hash_table_destroy (hash_table: self->unthemed_icons);
1373 }
1374 self->themes = NULL;
1375 self->unthemed_icons = NULL;
1376 self->themes_valid = FALSE;
1377 self->serial++;
1378}
1379
1380int
1381gtk_icon_theme_get_serial (GtkIconTheme *self)
1382{
1383 return self->serial;
1384}
1385
1386static void
1387gtk_icon_theme_dispose (GObject *object)
1388{
1389 GtkIconTheme *self = GTK_ICON_THEME (object);
1390
1391 /* We make sure all outstanding GtkIconThemeRefs to us are NULLed
1392 * out so that no other threads than the one running finalize will
1393 * refer to the icon theme after this. This could happen if
1394 * we finalize on a thread and on the main thread some display or
1395 * setting signal is emitted.
1396 *
1397 * It is possible that before we acquire the lock this happens
1398 * and the other thread refs the icon theme for some reason, but
1399 * this is ok as it is allowed to resurrect objects in dispose
1400 * (but not in finalize).
1401 */
1402 gtk_icon_theme_ref_dispose (ref: self->ref);
1403
1404 G_OBJECT_CLASS (gtk_icon_theme_parent_class)->dispose (object);
1405}
1406
1407static void
1408gtk_icon_theme_finalize (GObject *object)
1409{
1410 GtkIconTheme *self = GTK_ICON_THEME (object);
1411
1412 /* We don't actually need to take the lock here, because by now
1413 there can be no other threads that own a ref to this object, but
1414 technically this is considered "locked" */
1415
1416 icon_cache_clear (theme: self);
1417
1418 if (self->theme_changed_idle)
1419 g_source_remove (tag: self->theme_changed_idle);
1420
1421 gtk_icon_theme_unset_display (self);
1422
1423 g_free (mem: self->current_theme);
1424
1425 g_strfreev (str_array: self->search_path);
1426 g_strfreev (str_array: self->resource_path);
1427
1428 blow_themes (self);
1429 g_array_free (array: self->dir_mtimes, TRUE);
1430
1431 gtk_icon_theme_ref_unref (ref: self->ref);
1432
1433 G_OBJECT_CLASS (gtk_icon_theme_parent_class)->finalize (object);
1434}
1435
1436/**
1437 * gtk_icon_theme_set_search_path:
1438 * @self: a `GtkIconTheme`
1439 * @path: (array zero-terminated=1) (element-type filename) (nullable): NULL-terminated
1440 * array of directories that are searched for icon themes
1441 *
1442 * Sets the search path for the icon theme object.
1443 *
1444 * When looking for an icon theme, GTK will search for a subdirectory
1445 * of one or more of the directories in @path with the same name
1446 * as the icon theme containing an index.theme file. (Themes from
1447 * multiple of the path elements are combined to allow themes to be
1448 * extended by adding icons in the user’s home directory.)
1449 *
1450 * In addition if an icon found isn’t found either in the current
1451 * icon theme or the default icon theme, and an image file with
1452 * the right name is found directly in one of the elements of
1453 * @path, then that image will be used for the icon name.
1454 * (This is legacy feature, and new icons should be put
1455 * into the fallback icon theme, which is called hicolor,
1456 * rather than directly on the icon path.)
1457 */
1458void
1459gtk_icon_theme_set_search_path (GtkIconTheme *self,
1460 const char * const *path)
1461{
1462 char **search_path;
1463
1464 g_return_if_fail (GTK_IS_ICON_THEME (self));
1465
1466 gtk_icon_theme_lock (self);
1467
1468 search_path = g_strdupv (str_array: (char **)path);
1469 g_strfreev (str_array: self->search_path);
1470 self->search_path = search_path;
1471
1472 do_theme_change (self);
1473
1474 gtk_icon_theme_unlock (self);
1475
1476 g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_SEARCH_PATH]);
1477}
1478
1479/**
1480 * gtk_icon_theme_get_search_path:
1481 * @self: a `GtkIconTheme`
1482 *
1483 * Gets the current search path.
1484 *
1485 * See [method@Gtk.IconTheme.set_search_path].
1486 *
1487 * Returns: (transfer full) (array zero-terminated=1) (element-type filename) (nullable):
1488 * a list of icon theme path directories
1489 */
1490char **
1491gtk_icon_theme_get_search_path (GtkIconTheme *self)
1492{
1493 char **paths;
1494
1495 g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL);
1496
1497 gtk_icon_theme_lock (self);
1498
1499 paths = g_strdupv (str_array: self->search_path);
1500
1501 gtk_icon_theme_unlock (self);
1502
1503 return paths;
1504}
1505
1506/**
1507 * gtk_icon_theme_add_search_path:
1508 * @self: a `GtkIconTheme`
1509 * @path: (type filename): directory name to append to the icon path
1510 *
1511 * Appends a directory to the search path.
1512 *
1513 * See [method@Gtk.IconTheme.set_search_path].
1514 */
1515void
1516gtk_icon_theme_add_search_path (GtkIconTheme *self,
1517 const char *path)
1518{
1519 char **paths;
1520 int len;
1521
1522 g_return_if_fail (GTK_IS_ICON_THEME (self));
1523 g_return_if_fail (path != NULL);
1524
1525 len = g_strv_length (str_array: self->search_path);
1526 paths = g_new (char *, len + 2);
1527 memcpy (dest: paths, src: self->search_path, n: sizeof (char *) * len);
1528 paths[len] = (char *)path;
1529 paths[len + 1] = NULL;
1530
1531 gtk_icon_theme_set_search_path (self, path: (const char * const *)paths);
1532
1533 g_free (mem: paths);
1534}
1535
1536/**
1537 * gtk_icon_theme_set_resource_path:
1538 * @self: a `GtkIconTheme`
1539 * @path: (array zero-terminated=1) (element-type utf8) (nullable):
1540 * NULL-terminated array of resource paths
1541 * that are searched for icons
1542 *
1543 * Sets the resource paths that will be looked at when
1544 * looking for icons, similar to search paths.
1545 *
1546 * The resources are considered as part of the hicolor icon theme
1547 * and must be located in subdirectories that are defined in the
1548 * hicolor icon theme, such as `@path/16x16/actions/run.png`
1549 * or `@path/scalable/actions/run.svg`.
1550 *
1551 * Icons that are directly placed in the resource path instead
1552 * of a subdirectory are also considered as ultimate fallback,
1553 * but they are treated like unthemed icons.
1554 */
1555void
1556gtk_icon_theme_set_resource_path (GtkIconTheme *self,
1557 const char * const *path)
1558{
1559 char **search_path;
1560
1561 g_return_if_fail (GTK_IS_ICON_THEME (self));
1562
1563 gtk_icon_theme_lock (self);
1564
1565 search_path = g_strdupv (str_array: (char **)path);
1566 g_strfreev (str_array: self->resource_path);
1567 self->resource_path = search_path;
1568
1569 do_theme_change (self);
1570
1571 gtk_icon_theme_unlock (self);
1572
1573 g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_RESOURCE_PATH]);
1574}
1575
1576/**
1577 * gtk_icon_theme_get_resource_path:
1578 * @self: a `GtkIconTheme`
1579 *
1580 * Gets the current resource path.
1581 *
1582 * See [method@Gtk.IconTheme.set_resource_path].
1583 *
1584 * Returns: (transfer full) (array zero-terminated=1) (element-type utf8) (nullable):
1585 * A list of resource paths
1586 */
1587char **
1588gtk_icon_theme_get_resource_path (GtkIconTheme *self)
1589{
1590 char **paths;
1591
1592 g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL);
1593
1594 gtk_icon_theme_lock (self);
1595
1596 paths = g_strdupv (str_array: self->resource_path);
1597
1598 gtk_icon_theme_unlock (self);
1599
1600 return paths;
1601}
1602
1603/**
1604 * gtk_icon_theme_add_resource_path:
1605 * @self: a `GtkIconTheme`
1606 * @path: a resource path
1607 *
1608 * Adds a resource path that will be looked at when looking
1609 * for icons, similar to search paths.
1610 *
1611 * See [method@Gtk.IconTheme.set_resource_path].
1612 *
1613 * This function should be used to make application-specific icons
1614 * available as part of the icon theme.
1615 */
1616void
1617gtk_icon_theme_add_resource_path (GtkIconTheme *self,
1618 const char *path)
1619{
1620 char **paths;
1621 int len;
1622
1623 g_return_if_fail (GTK_IS_ICON_THEME (self));
1624 g_return_if_fail (path != NULL);
1625
1626 len = g_strv_length (str_array: self->resource_path);
1627 paths = g_new (char *, len + 2);
1628 memcpy (dest: paths, src: self->resource_path, n: sizeof (char *) * len);
1629 paths[len] = (char *)path;
1630 paths[len + 1] = NULL;
1631
1632 gtk_icon_theme_set_resource_path (self, path: (const char * const *)paths);
1633
1634 g_free (mem: paths);
1635}
1636
1637/**
1638 * gtk_icon_theme_set_theme_name:
1639 * @self: a `GtkIconTheme`
1640 * @theme_name: (nullable): name of icon theme to use instead of
1641 * configured theme, or %NULL to unset a previously set custom theme
1642 *
1643 * Sets the name of the icon theme that the `GtkIconTheme` object uses
1644 * overriding system configuration.
1645 *
1646 * This function cannot be called on the icon theme objects returned
1647 * from [func@Gtk.IconTheme.get_for_display].
1648 */
1649void
1650gtk_icon_theme_set_theme_name (GtkIconTheme *self,
1651 const char *theme_name)
1652{
1653 g_return_if_fail (GTK_IS_ICON_THEME (self));
1654 g_return_if_fail (!self->is_display_singleton);
1655
1656 gtk_icon_theme_lock (self);
1657
1658 if (theme_name != NULL)
1659 {
1660 self->custom_theme = TRUE;
1661 if (!self->current_theme || strcmp (s1: theme_name, s2: self->current_theme) != 0)
1662 {
1663 g_free (mem: self->current_theme);
1664 self->current_theme = g_strdup (str: theme_name);
1665
1666 do_theme_change (self);
1667 }
1668 }
1669 else
1670 {
1671 if (self->custom_theme)
1672 {
1673 self->custom_theme = FALSE;
1674 update_current_theme__mainthread (self);
1675 }
1676 }
1677
1678 gtk_icon_theme_unlock (self);
1679
1680 g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_THEME_NAME]);
1681}
1682
1683/**
1684 * gtk_icon_theme_get_theme_name:
1685 * @self: a `GtkIconTheme`
1686 *
1687 * Gets the current icon theme name.
1688 *
1689 * Returns (transfer full): the current icon theme name,
1690 */
1691char *
1692gtk_icon_theme_get_theme_name (GtkIconTheme *self)
1693{
1694 char *theme_name;
1695
1696 g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL);
1697
1698 gtk_icon_theme_lock (self);
1699
1700 if (self->custom_theme)
1701 theme_name = g_strdup (str: self->current_theme);
1702 else
1703 {
1704 if (self->display)
1705 {
1706 GtkSettings *settings = gtk_settings_get_for_display (display: self->display);
1707 g_object_get (object: settings, first_property_name: "gtk-icon-theme-name", &theme_name, NULL);
1708 }
1709 else
1710 theme_name = NULL;
1711 }
1712
1713 gtk_icon_theme_unlock (self);
1714
1715 return theme_name;
1716}
1717
1718static const char builtin_hicolor_index[] =
1719"[Icon Theme]\n"
1720"Name=Hicolor\n"
1721"Hidden=True\n"
1722"Directories=16x16/actions,16x16/status,22x22/actions,24x24/actions,24x24/status,32x32/actions,32x32/status,48x48/status,64x64/actions,scalable/status,scalable/actions\n"
1723"[16x16/actions]\n"
1724"Size=16\n"
1725"Type=Threshold\n"
1726"[16x16/status]\n"
1727"Size=16\n"
1728"Type=Threshold\n"
1729"[22x22/actions]\n"
1730"Size=22\n"
1731"Type=Threshold\n"
1732"[24x24/actions]\n"
1733"Size=24\n"
1734"Type=Threshold\n"
1735"[24x24/status]\n"
1736"Size=24\n"
1737"Type=Threshold\n"
1738"[32x32/actions]\n"
1739"Size=32\n"
1740"Type=Threshold\n"
1741"[32x32/status]\n"
1742"Size=32\n"
1743"Type=Threshold\n"
1744"[48x48/status]\n"
1745"Size=48\n"
1746"Type=Threshold\n"
1747"[64x64/actions]\n"
1748"Size=64\n"
1749"Type=Threshold\n"
1750"[scalable/status]\n"
1751"MinSize=1\n"
1752"Size=128\n"
1753"MaxSize=256\n"
1754"Type=Scalable\n"
1755"[scalable/actions]\n"
1756"MinSize=1\n"
1757"Size=128\n"
1758"MaxSize=256\n"
1759"Type=Scalable\n";
1760
1761static void
1762insert_theme (GtkIconTheme *self,
1763 const char *theme_name)
1764{
1765 int i;
1766 GList *l;
1767 char **dirs;
1768 char **scaled_dirs;
1769 char **themes;
1770 IconTheme *theme = NULL;
1771 char *path;
1772 GKeyFile *theme_file;
1773 GError *error = NULL;
1774 GStatBuf stat_buf;
1775
1776 for (l = self->themes; l != NULL; l = l->next)
1777 {
1778 theme = l->data;
1779 if (strcmp (s1: theme->name, s2: theme_name) == 0)
1780 return;
1781 }
1782
1783 for (i = 0; self->search_path[i]; i++)
1784 {
1785 IconThemeDirMtime dir_mtime;
1786
1787 path = g_build_filename (first_element: self->search_path[i], theme_name, NULL);
1788 dir_mtime.cache = NULL;
1789 dir_mtime.dir = path;
1790 if (g_stat (file: path, buf: &stat_buf) == 0 && S_ISDIR (stat_buf.st_mode))
1791 {
1792 dir_mtime.mtime = stat_buf.st_mtime;
1793 dir_mtime.exists = TRUE;
1794 }
1795 else
1796 {
1797 dir_mtime.mtime = 0;
1798 dir_mtime.exists = FALSE;
1799 }
1800
1801 g_array_insert_val (self->dir_mtimes, 0, dir_mtime);
1802 }
1803
1804 theme_file = NULL;
1805 for (i = 0; self->search_path[i] && !theme_file; i++)
1806 {
1807 path = g_build_filename (first_element: self->search_path[i], theme_name, "index.theme", NULL);
1808 if (g_file_test (filename: path, test: G_FILE_TEST_IS_REGULAR))
1809 {
1810 theme_file = g_key_file_new ();
1811 g_key_file_set_list_separator (key_file: theme_file, separator: ',');
1812 if (!g_key_file_load_from_file (key_file: theme_file, file: path, flags: 0, error: &error))
1813 {
1814 g_key_file_free (key_file: theme_file);
1815 theme_file = NULL;
1816 g_error_free (error);
1817 error = NULL;
1818 }
1819 }
1820 g_free (mem: path);
1821 }
1822
1823 if (theme_file == NULL)
1824 {
1825 if (strcmp (s1: theme_name, FALLBACK_ICON_THEME) == 0)
1826 {
1827 theme_file = g_key_file_new ();
1828 g_key_file_set_list_separator (key_file: theme_file, separator: ',');
1829 g_key_file_load_from_data (key_file: theme_file, data: builtin_hicolor_index, length: -1, flags: 0, NULL);
1830 }
1831 else
1832 return;
1833 }
1834
1835 dirs = g_key_file_get_string_list (key_file: theme_file, group_name: "Icon Theme", key: "Directories", NULL, NULL);
1836 if (!dirs)
1837 {
1838 g_warning ("Theme file for %s has no directories", theme_name);
1839 g_key_file_free (key_file: theme_file);
1840 return;
1841 }
1842
1843 scaled_dirs = g_key_file_get_string_list (key_file: theme_file, group_name: "Icon Theme", key: "ScaledDirectories", NULL, NULL);
1844
1845 theme = theme_new (theme_name, theme_file);
1846 self->themes = g_list_prepend (list: self->themes, data: theme);
1847
1848 for (i = 0; dirs[i] != NULL; i++)
1849 theme_subdir_load (self, theme, theme_file, subdir: dirs[i]);
1850
1851 if (scaled_dirs)
1852 {
1853 for (i = 0; scaled_dirs[i] != NULL; i++)
1854 theme_subdir_load (self, theme, theme_file, subdir: scaled_dirs[i]);
1855 }
1856
1857 g_strfreev (str_array: dirs);
1858 g_strfreev (str_array: scaled_dirs);
1859
1860 themes = g_key_file_get_string_list (key_file: theme_file,
1861 group_name: "Icon Theme",
1862 key: "Inherits",
1863 NULL,
1864 NULL);
1865 if (themes)
1866 {
1867 for (i = 0; themes[i] != NULL; i++)
1868 insert_theme (self, theme_name: themes[i]);
1869
1870 g_strfreev (str_array: themes);
1871 }
1872
1873 g_key_file_free (key_file: theme_file);
1874}
1875
1876static void
1877free_unthemed_icon (UnthemedIcon *unthemed_icon)
1878{
1879 g_free (mem: unthemed_icon->svg_filename);
1880 g_free (mem: unthemed_icon->no_svg_filename);
1881 g_slice_free (UnthemedIcon, unthemed_icon);
1882}
1883
1884static void
1885strip_suffix_inline (char *filename)
1886{
1887 char *dot;
1888
1889 if (g_str_has_suffix (str: filename, suffix: ".symbolic.png"))
1890 filename[strlen(s: filename)-13] = 0;
1891
1892 dot = strrchr (s: filename, c: '.');
1893
1894 if (dot != NULL)
1895 *dot = 0;
1896}
1897
1898static char *
1899strip_suffix (const char *filename)
1900{
1901 const char *dot;
1902
1903 if (g_str_has_suffix (str: filename, suffix: ".symbolic.png"))
1904 return g_strndup (str: filename, n: strlen(s: filename)-13);
1905
1906 dot = strrchr (s: filename, c: '.');
1907
1908 if (dot == NULL)
1909 return g_strdup (str: filename);
1910
1911 return g_strndup (str: filename, n: dot - filename);
1912}
1913
1914static void
1915add_unthemed_icon (GtkIconTheme *self,
1916 const char *dir,
1917 const char *file,
1918 gboolean is_resource)
1919{
1920 IconCacheFlag new_suffix, old_suffix;
1921 char *abs_file;
1922 char *base_name;
1923 UnthemedIcon *unthemed_icon;
1924
1925 new_suffix = suffix_from_name (name: file);
1926
1927 if (new_suffix == ICON_CACHE_FLAG_NONE)
1928 return;
1929
1930 abs_file = g_build_filename (first_element: dir, file, NULL);
1931 base_name = strip_suffix (filename: file);
1932
1933 unthemed_icon = g_hash_table_lookup (hash_table: self->unthemed_icons, key: base_name);
1934
1935 if (unthemed_icon)
1936 {
1937 if (new_suffix == ICON_CACHE_FLAG_SVG_SUFFIX)
1938 {
1939 if (unthemed_icon->svg_filename)
1940 g_free (mem: abs_file);
1941 else
1942 unthemed_icon->svg_filename = abs_file;
1943 }
1944 else
1945 {
1946 if (unthemed_icon->no_svg_filename)
1947 {
1948 old_suffix = suffix_from_name (name: unthemed_icon->no_svg_filename);
1949 if (new_suffix > old_suffix)
1950 {
1951 g_free (mem: unthemed_icon->no_svg_filename);
1952 unthemed_icon->no_svg_filename = abs_file;
1953 }
1954 else
1955 g_free (mem: abs_file);
1956 }
1957 else
1958 unthemed_icon->no_svg_filename = abs_file;
1959 }
1960
1961 g_free (mem: base_name);
1962 }
1963 else
1964 {
1965 unthemed_icon = g_slice_new0 (UnthemedIcon);
1966
1967 unthemed_icon->is_resource = is_resource;
1968
1969 if (new_suffix == ICON_CACHE_FLAG_SVG_SUFFIX)
1970 unthemed_icon->svg_filename = abs_file;
1971 else
1972 unthemed_icon->no_svg_filename = abs_file;
1973
1974 /* takes ownership of base_name */
1975 g_hash_table_replace (hash_table: self->unthemed_icons, key: base_name, value: unthemed_icon);
1976 }
1977}
1978
1979static void
1980load_themes (GtkIconTheme *self)
1981{
1982 GDir *gdir;
1983 int base;
1984 char *dir;
1985 const char *file;
1986 GStatBuf stat_buf;
1987 int j;
1988
1989 if (self->current_theme)
1990 insert_theme (self, theme_name: self->current_theme);
1991
1992 insert_theme (self, FALLBACK_ICON_THEME);
1993 self->themes = g_list_reverse (list: self->themes);
1994
1995 self->unthemed_icons = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal,
1996 key_destroy_func: g_free, value_destroy_func: (GDestroyNotify)free_unthemed_icon);
1997
1998 for (base = 0; self->search_path[base]; base++)
1999 {
2000 IconThemeDirMtime *dir_mtime;
2001 dir = self->search_path[base];
2002
2003 g_array_set_size (array: self->dir_mtimes, length: self->dir_mtimes->len + 1);
2004 dir_mtime = &g_array_index (self->dir_mtimes, IconThemeDirMtime, self->dir_mtimes->len - 1);
2005
2006 dir_mtime->dir = g_strdup (str: dir);
2007 dir_mtime->mtime = 0;
2008 dir_mtime->exists = FALSE;
2009 dir_mtime->cache = NULL;
2010
2011 if (g_stat (file: dir, buf: &stat_buf) != 0 || !S_ISDIR (stat_buf.st_mode))
2012 continue;
2013 dir_mtime->mtime = stat_buf.st_mtime;
2014 dir_mtime->exists = TRUE;
2015
2016 dir_mtime->cache = gtk_icon_cache_new_for_path (path: dir);
2017 if (dir_mtime->cache != NULL)
2018 continue;
2019
2020 gdir = g_dir_open (path: dir, flags: 0, NULL);
2021 if (gdir == NULL)
2022 continue;
2023
2024 while ((file = g_dir_read_name (dir: gdir)))
2025 add_unthemed_icon (self, dir, file, FALSE);
2026
2027 g_dir_close (dir: gdir);
2028 }
2029
2030 for (j = 0; self->resource_path[j]; j++)
2031 {
2032 char **children;
2033 int i;
2034
2035 dir = self->resource_path[j];
2036 children = g_resources_enumerate_children (path: dir, lookup_flags: 0, NULL);
2037 if (!children)
2038 continue;
2039
2040 for (i = 0; children[i]; i++)
2041 add_unthemed_icon (self, dir, file: children[i], TRUE);
2042
2043 g_strfreev (str_array: children);
2044 }
2045
2046 self->themes_valid = TRUE;
2047
2048 self->last_stat_time = g_get_monotonic_time ();
2049
2050 GTK_DISPLAY_NOTE (self->display, ICONTHEME, {
2051 GList *l;
2052 GString *s;
2053 s = g_string_new ("Current icon themes ");
2054 for (l = self->themes; l; l = l->next)
2055 {
2056 IconTheme *theme = l->data;
2057 g_string_append (s, theme->name);
2058 g_string_append_c (s, ' ');
2059 }
2060 g_message ("%s", s->str);
2061 g_string_free (s, TRUE);
2062 });
2063}
2064
2065static gboolean
2066ensure_valid_themes (GtkIconTheme *self,
2067 gboolean non_blocking)
2068{
2069 gboolean was_valid = self->themes_valid;
2070
2071 if (self->themes_valid)
2072 {
2073 gint64 now = g_get_monotonic_time ();
2074
2075 if ((now - self->last_stat_time) / G_USEC_PER_SEC > 5)
2076 {
2077 if (non_blocking)
2078 return FALSE;
2079
2080 if (rescan_themes (self))
2081 {
2082 icon_cache_clear (theme: self);
2083 blow_themes (self);
2084 }
2085 }
2086 }
2087
2088 if (!self->themes_valid)
2089 {
2090 gint64 before G_GNUC_UNUSED;
2091
2092 if (non_blocking)
2093 return FALSE;
2094
2095 before = GDK_PROFILER_CURRENT_TIME;
2096
2097 load_themes (self);
2098
2099 gdk_profiler_end_mark (before, "icon theme load", self->current_theme);
2100
2101 if (was_valid)
2102 queue_theme_changed (self);
2103 }
2104
2105 return TRUE;
2106}
2107
2108static inline gboolean
2109icon_name_is_symbolic (const char *icon_name,
2110 int icon_name_len)
2111{
2112
2113 if (icon_name_len < 0)
2114 icon_name_len = strlen (s: icon_name);
2115
2116 if (icon_name_len > strlen (s: "-symbolic"))
2117 {
2118 if (strcmp (s1: icon_name + icon_name_len - strlen (s: "-symbolic"), s2: "-symbolic") == 0)
2119 return TRUE;
2120 }
2121
2122 if (icon_name_len > strlen (s: "-symbolic-ltr"))
2123 {
2124 if (strcmp (s1: icon_name + icon_name_len - strlen (s: "-symbolic-ltr"), s2: "-symbolic-ltr") == 0 ||
2125 strcmp (s1: icon_name + icon_name_len - strlen (s: "-symbolic-rtl"), s2: "-symbolic-rtl") == 0)
2126 return TRUE;
2127 }
2128
2129 return FALSE;
2130}
2131
2132static inline gboolean
2133icon_uri_is_symbolic (const char *icon_name,
2134 int icon_name_len)
2135{
2136 if (icon_name_len < 0)
2137 icon_name_len = strlen (s: icon_name);
2138
2139 if (icon_name_len > strlen (s: "-symbolic.svg"))
2140 {
2141 if (strcmp (s1: icon_name + icon_name_len - strlen (s: "-symbolic.svg"), s2: "-symbolic.svg") == 0 ||
2142 strcmp (s1: icon_name + icon_name_len - strlen (s: ".symbolic.png"), s2: ".symbolic.png") == 0)
2143 return TRUE;
2144 }
2145
2146 if (icon_name_len > strlen (s: "-symbolic-ltr.svg"))
2147 {
2148 if (strcmp (s1: icon_name + icon_name_len - strlen (s: "-symbolic.ltr.svg"), s2: "-symbolic-ltr.svg") == 0 ||
2149 strcmp (s1: icon_name + icon_name_len - strlen (s: "-symbolic.rtl.svg"), s2: "-symbolic-rtl.svg") == 0)
2150 return TRUE;
2151 }
2152
2153 return FALSE;
2154}
2155
2156static GtkIconPaintable *
2157real_choose_icon (GtkIconTheme *self,
2158 const char *icon_names[],
2159 int size,
2160 int scale,
2161 GtkIconLookupFlags flags,
2162 gboolean non_blocking)
2163{
2164 GList *l;
2165 GtkIconPaintable *icon = NULL;
2166 UnthemedIcon *unthemed_icon = NULL;
2167 const char *icon_name = NULL;
2168 IconTheme *theme = NULL;
2169 int i;
2170 IconKey key;
2171
2172 if (!ensure_valid_themes (self, non_blocking))
2173 return NULL;
2174
2175 key.icon_names = (char **)icon_names;
2176 key.size = size;
2177 key.scale = scale;
2178 key.flags = flags;
2179
2180 /* This is used in the icontheme unit test */
2181 GTK_DISPLAY_NOTE (self->display, ICONTHEME,
2182 for (i = 0; icon_names[i]; i++)
2183 g_message ("\tlookup name: %s", icon_names[i]));
2184
2185 icon = icon_cache_lookup (theme: self, key: &key);
2186 if (icon)
2187 return icon;
2188
2189 /* For symbolic icons, do a search in all registered themes first;
2190 * a theme that inherits them from a parent theme might provide
2191 * an alternative full-color version, but still expect the symbolic icon
2192 * to show up instead.
2193 *
2194 * In other words: We prefer symbolic icons in inherited themes over
2195 * generic icons in the theme.
2196 */
2197 for (l = self->themes; l; l = l->next)
2198 {
2199 theme = l->data;
2200 for (i = 0; icon_names[i] && icon_name_is_symbolic (icon_name: icon_names[i], icon_name_len: -1); i++)
2201 {
2202 icon_name = icon_names[i];
2203 icon = theme_lookup_icon (theme, icon_name, size, scale, allow_svg: self->pixbuf_supports_svg);
2204 if (icon)
2205 goto out;
2206 }
2207 }
2208
2209 for (l = self->themes; l; l = l->next)
2210 {
2211 theme = l->data;
2212
2213 for (i = 0; icon_names[i]; i++)
2214 {
2215 icon_name = icon_names[i];
2216 icon = theme_lookup_icon (theme, icon_name, size, scale, allow_svg: self->pixbuf_supports_svg);
2217 if (icon)
2218 goto out;
2219 }
2220 }
2221
2222 theme = NULL;
2223
2224 for (i = 0; icon_names[i]; i++)
2225 {
2226 unthemed_icon = g_hash_table_lookup (hash_table: self->unthemed_icons, key: icon_names[i]);
2227 if (unthemed_icon)
2228 {
2229 icon = icon_paintable_new (icon_name: icon_names[i], desired_size: size, desired_scale: scale);
2230
2231 /* A SVG icon, when allowed, beats out a XPM icon, but not a PNG icon */
2232 if (self->pixbuf_supports_svg &&
2233 unthemed_icon->svg_filename &&
2234 (!unthemed_icon->no_svg_filename ||
2235 suffix_from_name (name: unthemed_icon->no_svg_filename) < ICON_CACHE_FLAG_PNG_SUFFIX))
2236 icon->filename = g_strdup (str: unthemed_icon->svg_filename);
2237 else if (unthemed_icon->no_svg_filename)
2238 icon->filename = g_strdup (str: unthemed_icon->no_svg_filename);
2239 else
2240 {
2241 g_clear_object (&icon);
2242 }
2243
2244 if (icon)
2245 {
2246 icon->is_svg = suffix_from_name (name: icon->filename) == ICON_CACHE_FLAG_SVG_SUFFIX;
2247 icon->is_resource = unthemed_icon->is_resource;
2248 goto out;
2249 }
2250 }
2251 }
2252
2253#ifdef G_OS_WIN32
2254 /* Still not found an icon, check if reference to a Win32 resource */
2255 {
2256 char **resources;
2257 HICON hIcon = NULL;
2258
2259 resources = g_strsplit (icon_names[0], ",", 0);
2260 if (resources[0])
2261 {
2262 wchar_t *wfile = g_utf8_to_utf16 (resources[0], -1, NULL, NULL, NULL);
2263 ExtractIconExW (wfile, resources[1] ? atoi (resources[1]) : 0, &hIcon, NULL, 1);
2264 g_free (wfile);
2265 }
2266
2267 if (hIcon)
2268 {
2269 icon = icon_paintable_new (resources[0], size, scale);
2270 icon->win32_icon = gdk_win32_icon_to_pixbuf_libgtk_only (hIcon, NULL, NULL);
2271 DestroyIcon (hIcon);
2272 goto out;
2273 }
2274 g_strfreev (resources);
2275 }
2276#endif
2277
2278
2279 /* Fall back to missing icon */
2280 if (icon == NULL)
2281 {
2282 GTK_NOTE(ICONFALLBACK, {
2283 char *s = g_strjoinv (", ", (char **)icon_names);
2284 g_message ("No icon found in %s (or fallbacks) for: %s", self->current_theme, s);
2285 g_free (s);
2286 });
2287 icon = icon_paintable_new (icon_name: "image-missing", desired_size: size, desired_scale: scale);
2288 icon->filename = g_strdup (IMAGE_MISSING_RESOURCE_PATH);
2289 icon->is_resource = TRUE;
2290 }
2291
2292 out:
2293 g_assert (icon != NULL);
2294
2295 icon->key.icon_names = g_strdupv (str_array: (char **)icon_names);
2296 icon->key.size = size;
2297 icon->key.scale = scale;
2298 icon->key.flags = flags;
2299
2300 icon_cache_add (theme: self, icon);
2301
2302 return icon;
2303}
2304
2305static void
2306icon_name_list_add_icon (GtkStrvBuilder *icons,
2307 const char *dir_suffix,
2308 char *icon_name)
2309{
2310 if (dir_suffix)
2311 gtk_strv_builder_append (self: icons, value: g_strconcat (string1: icon_name, dir_suffix, NULL));
2312 gtk_strv_builder_append (self: icons, value: icon_name);
2313}
2314
2315static GtkIconPaintable *
2316choose_icon (GtkIconTheme *self,
2317 const char *icon_names[],
2318 int size,
2319 int scale,
2320 GtkTextDirection direction,
2321 GtkIconLookupFlags flags,
2322 gboolean non_blocking)
2323{
2324 gboolean has_regular = FALSE, has_symbolic = FALSE;
2325 GtkIconPaintable *icon;
2326 GtkStrvBuilder new_names;
2327 const char *dir_suffix;
2328 guint i;
2329
2330 switch (direction)
2331 {
2332 case GTK_TEXT_DIR_NONE:
2333 dir_suffix = NULL;
2334 break;
2335 case GTK_TEXT_DIR_LTR:
2336 dir_suffix = "-ltr";
2337 break;
2338 case GTK_TEXT_DIR_RTL:
2339 dir_suffix = "-rtl";
2340 break;
2341 default:
2342 g_assert_not_reached();
2343 dir_suffix = NULL;
2344 break;
2345 }
2346
2347 for (i = 0; icon_names[i]; i++)
2348 {
2349 if (icon_name_is_symbolic (icon_name: icon_names[i], icon_name_len: -1))
2350 has_symbolic = TRUE;
2351 else
2352 has_regular = TRUE;
2353 }
2354
2355 if ((flags & GTK_ICON_LOOKUP_FORCE_REGULAR) && has_symbolic)
2356 {
2357 gtk_strv_builder_init (self: &new_names);
2358 for (i = 0; icon_names[i]; i++)
2359 {
2360 if (icon_name_is_symbolic (icon_name: icon_names[i], icon_name_len: -1))
2361 icon_name_list_add_icon (icons: &new_names, dir_suffix, icon_name: g_strndup (str: icon_names[i], n: strlen (s: icon_names[i]) - strlen (s: "-symbolic")));
2362 else
2363 icon_name_list_add_icon (icons: &new_names, dir_suffix, icon_name: g_strdup (str: icon_names[i]));
2364 }
2365 for (i = 0; icon_names[i]; i++)
2366 {
2367 if (icon_name_is_symbolic (icon_name: icon_names[i], icon_name_len: -1))
2368 icon_name_list_add_icon (icons: &new_names, dir_suffix, icon_name: g_strdup (str: icon_names[i]));
2369 }
2370
2371 icon = real_choose_icon (self,
2372 icon_names: (const char **) gtk_strv_builder_get_data (self: &new_names),
2373 size,
2374 scale,
2375 flags: flags & ~(GTK_ICON_LOOKUP_FORCE_REGULAR | GTK_ICON_LOOKUP_FORCE_SYMBOLIC),
2376 non_blocking);
2377
2378 gtk_strv_builder_clear (self: &new_names);
2379 }
2380 else if ((flags & GTK_ICON_LOOKUP_FORCE_SYMBOLIC) && has_regular)
2381 {
2382 gtk_strv_builder_init (self: &new_names);
2383 for (i = 0; icon_names[i]; i++)
2384 {
2385 if (!icon_name_is_symbolic (icon_name: icon_names[i], icon_name_len: -1))
2386 icon_name_list_add_icon (icons: &new_names, dir_suffix, icon_name: g_strconcat (string1: icon_names[i], "-symbolic", NULL));
2387 else
2388 icon_name_list_add_icon (icons: &new_names, dir_suffix, icon_name: g_strdup (str: icon_names[i]));
2389 }
2390 for (i = 0; icon_names[i]; i++)
2391 {
2392 if (!icon_name_is_symbolic (icon_name: icon_names[i], icon_name_len: -1))
2393 icon_name_list_add_icon (icons: &new_names, dir_suffix, icon_name: g_strdup (str: icon_names[i]));
2394 }
2395
2396 icon = real_choose_icon (self,
2397 icon_names: (const char **) gtk_strv_builder_get_data (self: &new_names),
2398 size,
2399 scale,
2400 flags: flags & ~(GTK_ICON_LOOKUP_FORCE_REGULAR | GTK_ICON_LOOKUP_FORCE_SYMBOLIC),
2401 non_blocking);
2402
2403 gtk_strv_builder_clear (self: &new_names);
2404 }
2405 else if (dir_suffix)
2406 {
2407 gtk_strv_builder_init (self: &new_names);
2408 for (i = 0; icon_names[i]; i++)
2409 {
2410 icon_name_list_add_icon (icons: &new_names, dir_suffix, icon_name: g_strdup (str: icon_names[i]));
2411 }
2412
2413 icon = real_choose_icon (self,
2414 icon_names: (const char **) gtk_strv_builder_get_data (self: &new_names),
2415 size,
2416 scale,
2417 flags: flags & ~(GTK_ICON_LOOKUP_FORCE_REGULAR | GTK_ICON_LOOKUP_FORCE_SYMBOLIC),
2418 non_blocking);
2419
2420 gtk_strv_builder_clear (self: &new_names);
2421 }
2422 else
2423 {
2424 icon = real_choose_icon (self,
2425 icon_names,
2426 size,
2427 scale,
2428 flags: flags & ~(GTK_ICON_LOOKUP_FORCE_REGULAR | GTK_ICON_LOOKUP_FORCE_SYMBOLIC),
2429 non_blocking);
2430 }
2431
2432 return icon;
2433}
2434
2435static void
2436load_icon_thread (GTask *task,
2437 gpointer source_object,
2438 gpointer task_data,
2439 GCancellable *cancellable)
2440{
2441 GtkIconPaintable *self = GTK_ICON_PAINTABLE (source_object);
2442
2443 g_mutex_lock (mutex: &self->texture_lock);
2444 icon_ensure_texture__locked (icon: self, TRUE);
2445 g_mutex_unlock (mutex: &self->texture_lock);
2446 g_task_return_pointer (task, NULL, NULL);
2447}
2448
2449/**
2450 * gtk_icon_theme_lookup_icon:
2451 * @self: a `GtkIconTheme`
2452 * @icon_name: the name of the icon to lookup
2453 * @fallbacks: (nullable) (array zero-terminated=1):
2454 * @size: desired icon size.
2455 * @scale: the window scale this will be displayed on
2456 * @direction: text direction the icon will be displayed in
2457 * @flags: flags modifying the behavior of the icon lookup
2458 *
2459 * Looks up a named icon for a desired size and window scale,
2460 * returning a `GtkIconPaintable`.
2461 *
2462 * The icon can then be rendered by using it as a `GdkPaintable`,
2463 * or you can get information such as the filename and size.
2464 *
2465 * If the available @icon_name is not available and @fallbacks are
2466 * provided, they will be tried in order.
2467 *
2468 * If no matching icon is found, then a paintable that renders the
2469 * "missing icon" icon is returned. If you need to do something else
2470 * for missing icons you need to use [method@Gtk.IconTheme.has_icon].
2471 *
2472 * Note that you probably want to listen for icon theme changes and
2473 * update the icon. This is usually done by overriding the
2474 * GtkWidgetClass.css-changed() function.
2475 *
2476 * Returns: (transfer full): a `GtkIconPaintable` object
2477 * containing the icon.
2478 */
2479GtkIconPaintable *
2480gtk_icon_theme_lookup_icon (GtkIconTheme *self,
2481 const char *icon_name,
2482 const char *fallbacks[],
2483 int size,
2484 int scale,
2485 GtkTextDirection direction,
2486 GtkIconLookupFlags flags)
2487{
2488 GtkIconPaintable *icon;
2489
2490 g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL);
2491 g_return_val_if_fail (icon_name != NULL, NULL);
2492 g_return_val_if_fail (scale >= 1, NULL);
2493
2494 GTK_DISPLAY_NOTE (self->display, ICONTHEME,
2495 g_message ("looking up icon %s for scale %d", icon_name, scale));
2496
2497 gtk_icon_theme_lock (self);
2498
2499 if (fallbacks)
2500 {
2501 gsize n_fallbacks = g_strv_length (str_array: (char **) fallbacks);
2502 const char **names = g_new (const char *, n_fallbacks + 2);
2503
2504 names[0] = icon_name;
2505 memcpy (dest: &names[1], src: fallbacks, n: sizeof (char *) * n_fallbacks);
2506 names[n_fallbacks + 1] = NULL;
2507
2508 icon = choose_icon (self, icon_names: names, size, scale, direction, flags, FALSE);
2509
2510 g_free (mem: names);
2511 }
2512 else
2513 {
2514 const char *names[2];
2515
2516 names[0] = icon_name;
2517 names[1] = NULL;
2518
2519 icon = choose_icon (self, icon_names: names, size, scale, direction, flags, FALSE);
2520 }
2521
2522 gtk_icon_theme_unlock (self);
2523
2524 if (flags & GTK_ICON_LOOKUP_PRELOAD)
2525 {
2526 gboolean has_texture = FALSE;
2527
2528 /* If we fail to get the lock it is because some other thread is
2529 currently loading the icon, so we need to do nothing */
2530 if (g_mutex_trylock (mutex: &icon->texture_lock))
2531 {
2532 has_texture = icon->texture != NULL;
2533 g_mutex_unlock (mutex: &icon->texture_lock);
2534
2535 if (!has_texture)
2536 {
2537 GTask *task = g_task_new (source_object: icon, NULL, NULL, NULL);
2538 g_task_run_in_thread (task, task_func: load_icon_thread);
2539 g_object_unref (object: task);
2540 }
2541 }
2542 }
2543
2544 return icon;
2545}
2546
2547/* Error quark */
2548GQuark
2549gtk_icon_theme_error_quark (void)
2550{
2551 return g_quark_from_static_string (string: "gtk-icon-theme-error-quark");
2552}
2553
2554void
2555gtk_icon_theme_lookup_symbolic_colors (GtkCssStyle *style,
2556 GdkRGBA color_out[4])
2557{
2558 GtkCssValue *palette, *color;
2559 const char *names[4] = {
2560 [GTK_SYMBOLIC_COLOR_ERROR] = "error",
2561 [GTK_SYMBOLIC_COLOR_WARNING] = "warning",
2562 [GTK_SYMBOLIC_COLOR_SUCCESS] = "success"
2563 };
2564 const GdkRGBA *lookup;
2565 gsize i;
2566
2567 color = style->core->color;
2568 palette = style->core->icon_palette;
2569 color_out[GTK_SYMBOLIC_COLOR_FOREGROUND] = *gtk_css_color_value_get_rgba (color);
2570
2571 for (i = 1; i < 4; i++)
2572 {
2573 lookup = gtk_css_palette_value_get_color (value: palette, color_name: names[i]);
2574 if (lookup)
2575 color_out[i] = *lookup;
2576 else
2577 color_out[i] = color_out[GTK_SYMBOLIC_COLOR_FOREGROUND];
2578 }
2579}
2580
2581
2582/**
2583 * gtk_icon_theme_has_icon:
2584 * @self: a `GtkIconTheme`
2585 * @icon_name: the name of an icon
2586 *
2587 * Checks whether an icon theme includes an icon
2588 * for a particular name.
2589 *
2590 * Returns: %TRUE if @self includes an
2591 * icon for @icon_name.
2592 */
2593gboolean
2594gtk_icon_theme_has_icon (GtkIconTheme *self,
2595 const char *icon_name)
2596{
2597 GList *l;
2598 gboolean res = FALSE;
2599
2600 g_return_val_if_fail (GTK_IS_ICON_THEME (self), FALSE);
2601 g_return_val_if_fail (icon_name != NULL, FALSE);
2602
2603 gtk_icon_theme_lock (self);
2604
2605 ensure_valid_themes (self, FALSE);
2606
2607 for (l = self->themes; l; l = l->next)
2608 {
2609 if (theme_has_icon (theme: l->data, icon_name))
2610 {
2611 res = TRUE;
2612 goto out;
2613 }
2614 }
2615
2616 out:
2617 gtk_icon_theme_unlock (self);
2618
2619 return res;
2620}
2621
2622/**
2623 * gtk_icon_theme_has_gicon:
2624 * @self: a `GtkIconTheme`
2625 * @gicon: a `GIcon`
2626 *
2627 * Checks whether an icon theme includes an icon
2628 * for a particular `GIcon`.
2629 *
2630 * Returns: %TRUE if @self includes an icon for @gicon
2631 *
2632 * Since: 4.2
2633 */
2634gboolean
2635gtk_icon_theme_has_gicon (GtkIconTheme *self,
2636 GIcon *gicon)
2637{
2638 const char * const *names;
2639 gboolean res = FALSE;
2640
2641 if (!G_IS_THEMED_ICON (gicon))
2642 return TRUE;
2643
2644 names = g_themed_icon_get_names (G_THEMED_ICON (gicon));
2645
2646 gtk_icon_theme_lock (self);
2647
2648 ensure_valid_themes (self, FALSE);
2649
2650 for (int i = 0; names[i]; i++)
2651 {
2652 for (GList *l = self->themes; l; l = l->next)
2653 {
2654 if (theme_has_icon (theme: l->data, icon_name: names[i]))
2655 {
2656 res = TRUE;
2657 goto out;
2658 }
2659 }
2660 }
2661
2662 out:
2663 gtk_icon_theme_unlock (self);
2664
2665 return res;
2666}
2667
2668static void
2669add_size (gpointer key,
2670 gpointer value,
2671 gpointer user_data)
2672{
2673 int **res_p = user_data;
2674
2675 **res_p = GPOINTER_TO_INT (key);
2676
2677 (*res_p)++;
2678}
2679
2680/**
2681 * gtk_icon_theme_get_icon_sizes:
2682 * @self: a `GtkIconTheme`
2683 * @icon_name: the name of an icon
2684 *
2685 * Returns an array of integers describing the sizes at which
2686 * the icon is available without scaling.
2687 *
2688 * A size of -1 means that the icon is available in a scalable
2689 * format. The array is zero-terminated.
2690 *
2691 * Returns: (array zero-terminated=1) (transfer full): A newly
2692 * allocated array describing the sizes at which the icon is
2693 * available. The array should be freed with g_free() when it is no
2694 * longer needed.
2695 */
2696int *
2697gtk_icon_theme_get_icon_sizes (GtkIconTheme *self,
2698 const char *icon_name)
2699{
2700 GList *l;
2701 int i;
2702 GHashTable *sizes;
2703 int *result, *r;
2704
2705 g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL);
2706
2707 gtk_icon_theme_lock (self);
2708
2709 ensure_valid_themes (self, FALSE);
2710
2711 sizes = g_hash_table_new (hash_func: g_direct_hash, key_equal_func: g_direct_equal);
2712
2713 for (l = self->themes; l; l = l->next)
2714 {
2715 IconTheme *theme = l->data;
2716 const char *interned_icon_name = gtk_string_set_lookup (set: &theme->icons, string: icon_name);
2717
2718 for (i = 0; i < theme->dir_sizes->len; i++)
2719 {
2720 IconThemeDirSize *dir_size = &g_array_index (theme->dir_sizes, IconThemeDirSize, i);
2721
2722 if (dir_size->type != ICON_THEME_DIR_SCALABLE && g_hash_table_lookup_extended (hash_table: sizes, GINT_TO_POINTER (dir_size->size), NULL, NULL))
2723 continue;
2724
2725 if (!g_hash_table_contains (hash_table: dir_size->icon_hash, key: interned_icon_name))
2726 continue;
2727
2728 if (dir_size->type == ICON_THEME_DIR_SCALABLE)
2729 g_hash_table_insert (hash_table: sizes, GINT_TO_POINTER (-1), NULL);
2730 else
2731 g_hash_table_insert (hash_table: sizes, GINT_TO_POINTER (dir_size->size), NULL);
2732 }
2733 }
2734
2735 r = result = g_new0 (int, g_hash_table_size (sizes) + 1);
2736
2737 g_hash_table_foreach (hash_table: sizes, func: add_size, user_data: &r);
2738 g_hash_table_destroy (hash_table: sizes);
2739
2740 gtk_icon_theme_unlock (self);
2741
2742 return result;
2743}
2744
2745static void
2746add_key_to_hash (gpointer key,
2747 gpointer value,
2748 gpointer user_data)
2749{
2750 GHashTable *hash = user_data;
2751
2752 g_hash_table_insert (hash_table: hash, key, value: key);
2753}
2754
2755/**
2756 * gtk_icon_theme_get_icon_names:
2757 * @self: a `GtkIconTheme`
2758 *
2759 * Lists the names of icons in the current icon theme.
2760 *
2761 * Returns: (array zero-terminated=1) (element-type utf8) (transfer full): a string array
2762 * holding the names of all the icons in the theme. You must
2763 * free the array using g_strfreev().
2764 */
2765char **
2766gtk_icon_theme_get_icon_names (GtkIconTheme *self)
2767{
2768 GHashTable *icons;
2769 GHashTableIter iter;
2770 char **names;
2771 char *key;
2772 int i;
2773 GList *l;
2774
2775 gtk_icon_theme_lock (self);
2776
2777 ensure_valid_themes (self, FALSE);
2778
2779 icons = g_hash_table_new (hash_func: g_str_hash, key_equal_func: g_str_equal);
2780
2781 l = self->themes;
2782 while (l != NULL)
2783 {
2784 IconTheme *theme = l->data;
2785 gtk_string_set_list (set: &theme->icons, result: icons);
2786 l = l->next;
2787 }
2788
2789 g_hash_table_foreach (hash_table: self->unthemed_icons,
2790 func: add_key_to_hash,
2791 user_data: icons);
2792
2793 names = g_new (char *, g_hash_table_size (icons) + 1);
2794
2795 i = 0;
2796 g_hash_table_iter_init (iter: &iter, hash_table: icons);
2797 while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&key, NULL))
2798 names[i++] = g_strdup (str: key);
2799
2800 names[i] = NULL;
2801
2802 g_hash_table_destroy (hash_table: icons);
2803
2804 gtk_icon_theme_unlock (self);
2805
2806 return names;
2807}
2808
2809static gboolean
2810rescan_themes (GtkIconTheme *self)
2811{
2812 int stat_res;
2813 GStatBuf stat_buf;
2814 guint i;
2815
2816 for (i = 0; i < self->dir_mtimes->len; i++)
2817 {
2818 const IconThemeDirMtime *dir_mtime = &g_array_index (self->dir_mtimes, IconThemeDirMtime, i);
2819
2820 stat_res = g_stat (file: dir_mtime->dir, buf: &stat_buf);
2821
2822 /* dir mtime didn't change */
2823 if (stat_res == 0 && dir_mtime->exists &&
2824 S_ISDIR (stat_buf.st_mode) &&
2825 dir_mtime->mtime == stat_buf.st_mtime)
2826 continue;
2827 /* didn't exist before, and still doesn't */
2828 if (!dir_mtime->exists &&
2829 (stat_res != 0 || !S_ISDIR (stat_buf.st_mode)))
2830 continue;
2831
2832 return TRUE;
2833 }
2834
2835 self->last_stat_time = g_get_monotonic_time ();
2836
2837 return FALSE;
2838}
2839
2840static IconTheme *
2841theme_new (const char *theme_name,
2842 GKeyFile *theme_file)
2843{
2844 IconTheme *theme;
2845
2846 theme = g_new0 (IconTheme, 1);
2847 theme->name = g_strdup (str: theme_name);
2848 theme->dir_sizes = g_array_new (FALSE, FALSE, element_size: sizeof (IconThemeDirSize));
2849 theme->dirs = g_array_new (FALSE, FALSE, element_size: sizeof (IconThemeDir));
2850 gtk_string_set_init (set: &theme->icons);
2851
2852 theme->display_name =
2853 g_key_file_get_locale_string (key_file: theme_file, group_name: "Icon Theme", key: "Name", NULL, NULL);
2854 if (!theme->display_name)
2855 g_warning ("Theme file for %s has no name", theme_name);
2856
2857 theme->comment =
2858 g_key_file_get_locale_string (key_file: theme_file,
2859 group_name: "Icon Theme", key: "Comment",
2860 NULL, NULL);
2861 return theme;
2862}
2863
2864static void
2865theme_destroy (IconTheme *theme)
2866{
2867 gsize i;
2868
2869 g_free (mem: theme->name);
2870 g_free (mem: theme->display_name);
2871 g_free (mem: theme->comment);
2872
2873 for (i = 0; i < theme->dir_sizes->len; i++)
2874 theme_dir_size_destroy (dir_size: &g_array_index (theme->dir_sizes, IconThemeDirSize, i));
2875 g_array_free (array: theme->dir_sizes, TRUE);
2876
2877 for (i = 0; i < theme->dirs->len; i++)
2878 theme_dir_destroy (dir: &g_array_index (theme->dirs, IconThemeDir, i));
2879 g_array_free (array: theme->dirs, TRUE);
2880
2881 gtk_string_set_destroy (set: &theme->icons);
2882
2883 g_free (mem: theme);
2884}
2885
2886static void
2887theme_dir_size_destroy (IconThemeDirSize *dir)
2888{
2889 if (dir->icon_hash)
2890 g_hash_table_destroy (hash_table: dir->icon_hash);
2891 if (dir->icon_files)
2892 g_array_free (array: dir->icon_files, TRUE);
2893}
2894
2895static void
2896theme_dir_destroy (IconThemeDir *dir)
2897{
2898 g_free (mem: dir->path);
2899}
2900
2901static int
2902theme_dir_size_difference (IconThemeDirSize *dir_size,
2903 int size,
2904 int scale)
2905{
2906 int scaled_size, scaled_dir_size;
2907 int min, max;
2908
2909 scaled_size = size * scale;
2910 scaled_dir_size = dir_size->size * dir_size->scale;
2911
2912 switch (dir_size->type)
2913 {
2914 case ICON_THEME_DIR_FIXED:
2915 return abs (x: scaled_size - scaled_dir_size);
2916
2917 case ICON_THEME_DIR_SCALABLE:
2918 if (scaled_size < (dir_size->min_size * dir_size->scale))
2919 return (dir_size->min_size * dir_size->scale) - scaled_size;
2920 if (size > (dir_size->max_size * dir_size->scale))
2921 return scaled_size - (dir_size->max_size * dir_size->scale);
2922 return 0;
2923
2924 case ICON_THEME_DIR_THRESHOLD:
2925 min = (dir_size->size - dir_size->threshold) * dir_size->scale;
2926 max = (dir_size->size + dir_size->threshold) * dir_size->scale;
2927 if (scaled_size < min)
2928 return min - scaled_size;
2929 if (scaled_size > max)
2930 return scaled_size - max;
2931 return 0;
2932
2933 case ICON_THEME_DIR_UNTHEMED:
2934 default:
2935 g_assert_not_reached ();
2936 return 1000;
2937 }
2938}
2939
2940static const char *
2941string_from_suffix (IconCacheFlag suffix)
2942{
2943 switch (suffix)
2944 {
2945 case ICON_CACHE_FLAG_XPM_SUFFIX:
2946 return ".xpm";
2947 case ICON_CACHE_FLAG_SVG_SUFFIX:
2948 return ".svg";
2949 case ICON_CACHE_FLAG_PNG_SUFFIX:
2950 return ".png";
2951 case ICON_CACHE_FLAG_SYMBOLIC_PNG_SUFFIX:
2952 return ".symbolic.png";
2953 case ICON_CACHE_FLAG_NONE:
2954 case ICON_CACHE_FLAG_HAS_ICON_FILE:
2955 default:
2956 g_assert_not_reached();
2957 return NULL;
2958 }
2959}
2960
2961static inline IconCacheFlag
2962suffix_from_name (const char *name)
2963{
2964 const gsize name_len = strlen (s: name);
2965
2966 if (name_len > 4)
2967 {
2968 if (name_len > strlen (s: ".symbolic.png"))
2969 {
2970 if (strcmp (s1: name + name_len - strlen (s: ".symbolic.png"), s2: ".symbolic.png") == 0)
2971 return ICON_CACHE_FLAG_SYMBOLIC_PNG_SUFFIX;
2972 }
2973
2974 if (strcmp (s1: name + name_len - strlen (s: ".png"), s2: ".png") == 0)
2975 return ICON_CACHE_FLAG_PNG_SUFFIX;
2976
2977 if (strcmp (s1: name + name_len - strlen (s: ".svg"), s2: ".svg") == 0)
2978 return ICON_CACHE_FLAG_SVG_SUFFIX;
2979
2980 if (strcmp (s1: name + name_len - strlen (s: ".xpm"), s2: ".xpm") == 0)
2981 return ICON_CACHE_FLAG_XPM_SUFFIX;
2982 }
2983
2984 return ICON_CACHE_FLAG_NONE;
2985}
2986
2987static IconCacheFlag
2988best_suffix (IconCacheFlag suffix,
2989 gboolean allow_svg)
2990{
2991 if ((suffix & ICON_CACHE_FLAG_SYMBOLIC_PNG_SUFFIX) != 0)
2992 return ICON_CACHE_FLAG_SYMBOLIC_PNG_SUFFIX;
2993 else if ((suffix & ICON_CACHE_FLAG_PNG_SUFFIX) != 0)
2994 return ICON_CACHE_FLAG_PNG_SUFFIX;
2995 else if (allow_svg && ((suffix & ICON_CACHE_FLAG_SVG_SUFFIX) != 0))
2996 return ICON_CACHE_FLAG_SVG_SUFFIX;
2997 else if ((suffix & ICON_CACHE_FLAG_XPM_SUFFIX) != 0)
2998 return ICON_CACHE_FLAG_XPM_SUFFIX;
2999 else
3000 return ICON_CACHE_FLAG_NONE;
3001}
3002
3003/* returns TRUE if dir_a is a better match */
3004static gboolean
3005compare_dir_size_matches (IconThemeDirSize *dir_a, int difference_a,
3006 IconThemeDirSize *dir_b, int difference_b,
3007 int requested_size,
3008 int requested_scale)
3009{
3010 int diff_a;
3011 int diff_b;
3012
3013 if (difference_a == 0)
3014 {
3015 if (difference_b != 0)
3016 return TRUE;
3017
3018 /* a and b both exact matches */
3019 }
3020 else
3021 {
3022 /* If scaling, *always* prefer downscaling */
3023 if (dir_a->size >= requested_size &&
3024 dir_b->size < requested_size)
3025 return TRUE;
3026
3027 if (dir_a->size < requested_size &&
3028 dir_b->size >= requested_size)
3029 return FALSE;
3030
3031 /* Otherwise prefer the closest match */
3032
3033 if (difference_a < difference_b)
3034 return TRUE;
3035
3036 if (difference_a > difference_b)
3037 return FALSE;
3038
3039 /* same pixel difference */
3040 }
3041
3042 if (dir_a->scale == requested_scale &&
3043 dir_b->scale != requested_scale)
3044 return TRUE;
3045
3046 if (dir_a->scale != requested_scale &&
3047 dir_b->scale == requested_scale)
3048 return FALSE;
3049
3050 /* a and b both match the scale */
3051
3052 if (dir_a->type != ICON_THEME_DIR_SCALABLE &&
3053 dir_b->type == ICON_THEME_DIR_SCALABLE)
3054 return TRUE;
3055
3056 if (dir_a->type == ICON_THEME_DIR_SCALABLE &&
3057 dir_b->type != ICON_THEME_DIR_SCALABLE)
3058 return FALSE;
3059
3060 /* a and b both are scalable */
3061
3062 diff_a = abs (x: requested_size * requested_scale - dir_a->size * dir_a->scale);
3063 diff_b = abs (x: requested_size * requested_scale - dir_b->size * dir_b->scale);
3064
3065 return diff_a <= diff_b;
3066}
3067
3068static GtkIconPaintable *
3069theme_lookup_icon (IconTheme *theme,
3070 const char *icon_name, /* interned */
3071 int size,
3072 int scale,
3073 gboolean allow_svg)
3074{
3075 IconThemeDirSize *min_dir_size;
3076 IconThemeFile *min_file;
3077 int min_difference;
3078 IconCacheFlag min_suffix;
3079 int i;
3080
3081 /* Its not uncommon with misses, so we do an early check which allows us do
3082 * do a lot less work.
3083 * We also intern the name so later hash lookups are faster. */
3084 icon_name = gtk_string_set_lookup (set: &theme->icons, string: icon_name);
3085 if (icon_name == NULL)
3086 return FALSE;
3087
3088 min_difference = G_MAXINT;
3089 min_dir_size = NULL;
3090
3091 for (i = 0; i < theme->dir_sizes->len; i++)
3092 {
3093 IconThemeDirSize *dir_size = &g_array_index (theme->dir_sizes, IconThemeDirSize, i);
3094 IconThemeFile *file;
3095 guint best_suffix;
3096 int difference;
3097 gpointer file_index;
3098
3099 if (!g_hash_table_lookup_extended (hash_table: dir_size->icon_hash, lookup_key: icon_name, NULL, value: &file_index))
3100 continue;
3101
3102 file = &g_array_index (dir_size->icon_files, IconThemeFile, GPOINTER_TO_INT(file_index));
3103
3104
3105 if (allow_svg)
3106 best_suffix = file->best_suffix;
3107 else
3108 best_suffix = file->best_suffix_no_svg;
3109
3110 if (best_suffix == ICON_CACHE_FLAG_NONE)
3111 continue;
3112
3113 difference = theme_dir_size_difference (dir_size, size, scale);
3114 if (min_dir_size == NULL ||
3115 compare_dir_size_matches (dir_a: dir_size, difference_a: difference,
3116 dir_b: min_dir_size, difference_b: min_difference,
3117 requested_size: size, requested_scale: scale))
3118 {
3119 min_dir_size = dir_size;
3120 min_file = file;
3121 min_suffix = best_suffix;
3122 min_difference = difference;
3123 }
3124 }
3125
3126 if (min_dir_size)
3127 {
3128 GtkIconPaintable *icon;
3129 IconThemeDir *dir = &g_array_index (theme->dirs, IconThemeDir, min_file->dir_index);
3130 char *filename;
3131
3132 icon = icon_paintable_new (icon_name, desired_size: size, desired_scale: scale);
3133
3134 filename = g_strconcat (string1: icon_name, string_from_suffix (suffix: min_suffix), NULL);
3135 icon->filename = g_build_filename (first_element: dir->path, filename, NULL);
3136 icon->is_svg = min_suffix == ICON_CACHE_FLAG_SVG_SUFFIX;
3137 icon->is_resource = dir->is_resource;
3138 icon->is_symbolic = icon_uri_is_symbolic (icon_name: filename, icon_name_len: -1);
3139 g_free (mem: filename);
3140
3141 return icon;
3142 }
3143
3144 return NULL;
3145}
3146
3147static gboolean
3148theme_has_icon (IconTheme *theme,
3149 const char *icon_name)
3150{
3151 return gtk_string_set_lookup (set: &theme->icons, string: icon_name) != NULL;
3152}
3153
3154static GHashTable *
3155scan_directory (GtkIconTheme *self,
3156 char *full_dir,
3157 GtkStringSet *set)
3158{
3159 GDir *gdir;
3160 const char *name;
3161 GHashTable *icons = NULL;
3162
3163 GTK_DISPLAY_NOTE (self->display, ICONTHEME,
3164 g_message ("scanning directory %s", full_dir));
3165
3166 gdir = g_dir_open (path: full_dir, flags: 0, NULL);
3167
3168 if (gdir == NULL)
3169 return NULL;
3170
3171 while ((name = g_dir_read_name (dir: gdir)))
3172 {
3173 const char *interned;
3174 IconCacheFlag suffix, hash_suffix;
3175
3176 suffix = suffix_from_name (name);
3177 if (suffix == ICON_CACHE_FLAG_NONE)
3178 continue;
3179
3180 strip_suffix_inline (filename: (char *)name);
3181 interned = gtk_string_set_add (set, string: name);
3182
3183 if (!icons)
3184 icons = g_hash_table_new_full (hash_func: g_direct_hash, key_equal_func: g_direct_equal, NULL, NULL);
3185
3186 hash_suffix = GPOINTER_TO_INT (g_hash_table_lookup (icons, interned));
3187 /* takes ownership of base_name */
3188 g_hash_table_replace (hash_table: icons, key: (char *)interned, GUINT_TO_POINTER (hash_suffix|suffix));
3189 }
3190
3191 g_dir_close (dir: gdir);
3192
3193 return icons;
3194}
3195
3196static GHashTable *
3197scan_resource_directory (GtkIconTheme *self,
3198 const char *full_dir,
3199 GtkStringSet *set)
3200{
3201 GHashTable *icons = NULL;
3202 char **children;
3203 int i;
3204
3205 GTK_DISPLAY_NOTE (self->display, ICONTHEME,
3206 g_message ("scanning resource directory %s", full_dir));
3207
3208 children = g_resources_enumerate_children (path: full_dir, lookup_flags: 0, NULL);
3209
3210 if (children)
3211 {
3212 for (i = 0; children[i]; i++)
3213 {
3214 char *name = children[i];
3215 const char *interned;
3216 IconCacheFlag suffix, hash_suffix;
3217
3218 suffix = suffix_from_name (name);
3219 if (suffix == ICON_CACHE_FLAG_NONE)
3220 continue;
3221
3222 if (!icons)
3223 icons = g_hash_table_new_full (hash_func: g_direct_hash, key_equal_func: g_direct_equal, NULL, NULL);
3224
3225 strip_suffix_inline (filename: name);
3226 interned = gtk_string_set_add (set, string: name);
3227
3228 hash_suffix = GPOINTER_TO_INT (g_hash_table_lookup (icons, interned));
3229 /* takes ownership of base_name */
3230 g_hash_table_replace (hash_table: icons, key: (char *)interned, GUINT_TO_POINTER (hash_suffix|suffix));
3231 }
3232
3233 g_strfreev (str_array: children);
3234 }
3235
3236 return icons;
3237}
3238
3239static gboolean
3240theme_dir_size_equal (IconThemeDirSize *a,
3241 IconThemeDirSize *b)
3242{
3243 return
3244 a->type == b->type &&
3245 a->size == b->size &&
3246 a->min_size == b->min_size &&
3247 a->max_size == b->max_size &&
3248 a->threshold == b->threshold &&
3249 a->scale == b->scale;
3250 }
3251
3252static guint32
3253theme_ensure_dir_size (IconTheme *theme,
3254 IconThemeDirType type,
3255 int size,
3256 int min_size,
3257 int max_size,
3258 int threshold,
3259 int scale)
3260{
3261 guint32 index;
3262 IconThemeDirSize new = { 0 };
3263
3264 new.type = type;
3265 new.size = size;
3266 new.min_size = min_size;
3267 new.max_size = max_size;
3268 new.threshold = threshold;
3269 new.scale = scale;
3270
3271 for (index = 0; index < theme->dir_sizes->len; index++)
3272 {
3273 IconThemeDirSize *dir_size = &g_array_index (theme->dir_sizes, IconThemeDirSize, index);
3274
3275 if (theme_dir_size_equal (a: dir_size, b: &new))
3276 return index;
3277 }
3278
3279 new.icon_files = g_array_new (FALSE, FALSE, element_size: sizeof (IconThemeFile));
3280 /* The keys are interned strings, so use direct hash/equal */
3281 new.icon_hash = g_hash_table_new_full (hash_func: g_direct_hash, key_equal_func: g_direct_equal, NULL, NULL);
3282
3283 index = theme->dir_sizes->len;
3284 g_array_append_val (theme->dir_sizes, new);
3285
3286 return index;
3287}
3288
3289static guint32
3290theme_add_icon_dir (IconTheme *theme,
3291 gboolean is_resource,
3292 char *path /* takes ownership */)
3293{
3294 IconThemeDir new_dir = { 0 };
3295 guint32 dir_index;
3296
3297 new_dir.is_resource = is_resource;
3298 new_dir.path = path;
3299
3300 dir_index = theme->dirs->len;
3301 g_array_append_val (theme->dirs, new_dir);
3302 return dir_index;
3303}
3304
3305static void
3306theme_add_icon_file (IconTheme *theme,
3307 const char *icon_name, /* interned */
3308 guint suffixes,
3309 IconThemeDirSize *dir_size,
3310 guint dir_index)
3311{
3312 IconThemeFile new_file = { 0 };
3313 guint index;
3314
3315 if (g_hash_table_contains (hash_table: dir_size->icon_hash, key: icon_name))
3316 return;
3317
3318 new_file.dir_index = dir_index;
3319 new_file.best_suffix = best_suffix (suffix: suffixes, TRUE);
3320 new_file.best_suffix_no_svg = best_suffix (suffix: suffixes, FALSE);
3321
3322 index = dir_size->icon_files->len;
3323 g_array_append_val (dir_size->icon_files, new_file);
3324
3325 g_hash_table_insert (hash_table: dir_size->icon_hash, key: (char *)icon_name, GINT_TO_POINTER(index));
3326
3327}
3328
3329/* Icon names are are already interned */
3330static void
3331theme_add_dir_with_icons (IconTheme *theme,
3332 IconThemeDirSize *dir_size,
3333 gboolean is_resource,
3334 char *path /* takes ownership */,
3335 GHashTable *icons)
3336{
3337 GHashTableIter iter;
3338 gpointer key, value;
3339 guint32 dir_index;
3340
3341 dir_index = theme_add_icon_dir (theme, is_resource, path);
3342
3343 g_hash_table_iter_init (iter: &iter, hash_table: icons);
3344 while (g_hash_table_iter_next (iter: &iter, key: &key, value: &value))
3345 {
3346 const char *icon_name = key; /* interned */
3347 guint suffixes = GPOINTER_TO_INT(value);
3348 theme_add_icon_file (theme, icon_name, suffixes, dir_size, dir_index);
3349 }
3350}
3351
3352static void
3353theme_subdir_load (GtkIconTheme *self,
3354 IconTheme *theme,
3355 GKeyFile *theme_file,
3356 char *subdir)
3357{
3358 char *type_string;
3359 IconThemeDirType type;
3360 int size;
3361 int min_size;
3362 int max_size;
3363 int threshold;
3364 GError *error = NULL;
3365 guint32 dir_size_index;
3366 IconThemeDirSize *dir_size;
3367 int scale;
3368 guint i;
3369
3370 size = g_key_file_get_integer (key_file: theme_file, group_name: subdir, key: "Size", error: &error);
3371 if (error)
3372 {
3373 g_error_free (error);
3374 g_warning ("Theme directory %s of theme %s has no size field\n",
3375 subdir, theme->name);
3376 return;
3377 }
3378
3379 type = ICON_THEME_DIR_THRESHOLD;
3380 type_string = g_key_file_get_string (key_file: theme_file, group_name: subdir, key: "Type", NULL);
3381 if (type_string)
3382 {
3383 if (strcmp (s1: type_string, s2: "Fixed") == 0)
3384 type = ICON_THEME_DIR_FIXED;
3385 else if (strcmp (s1: type_string, s2: "Scalable") == 0)
3386 type = ICON_THEME_DIR_SCALABLE;
3387 else if (strcmp (s1: type_string, s2: "Threshold") == 0)
3388 type = ICON_THEME_DIR_THRESHOLD;
3389
3390 g_free (mem: type_string);
3391 }
3392
3393 if (g_key_file_has_key (key_file: theme_file, group_name: subdir, key: "MaxSize", NULL))
3394 max_size = g_key_file_get_integer (key_file: theme_file, group_name: subdir, key: "MaxSize", NULL);
3395 else
3396 max_size = size;
3397
3398 if (g_key_file_has_key (key_file: theme_file, group_name: subdir, key: "MinSize", NULL))
3399 min_size = g_key_file_get_integer (key_file: theme_file, group_name: subdir, key: "MinSize", NULL);
3400 else
3401 min_size = size;
3402
3403 if (g_key_file_has_key (key_file: theme_file, group_name: subdir, key: "Threshold", NULL))
3404 threshold = g_key_file_get_integer (key_file: theme_file, group_name: subdir, key: "Threshold", NULL);
3405 else
3406 threshold = 2;
3407
3408 if (g_key_file_has_key (key_file: theme_file, group_name: subdir, key: "Scale", NULL))
3409 scale = g_key_file_get_integer (key_file: theme_file, group_name: subdir, key: "Scale", NULL);
3410 else
3411 scale = 1;
3412
3413 dir_size_index = theme_ensure_dir_size (theme, type, size, min_size, max_size, threshold, scale);
3414 dir_size = &g_array_index (theme->dir_sizes, IconThemeDirSize, dir_size_index);
3415
3416 for (i = 0; i < self->dir_mtimes->len; i++)
3417 {
3418 IconThemeDirMtime *dir_mtime = &g_array_index (self->dir_mtimes, IconThemeDirMtime, i);
3419 char *full_dir;
3420
3421 if (!dir_mtime->exists)
3422 continue; /* directory doesn't exist */
3423
3424 full_dir = g_build_filename (first_element: dir_mtime->dir, subdir, NULL);
3425
3426 /* First, see if we have a cache for the directory */
3427 if (dir_mtime->cache != NULL || g_file_test (filename: full_dir, test: G_FILE_TEST_IS_DIR))
3428 {
3429 GHashTable *icons = NULL;
3430
3431 if (dir_mtime->cache == NULL)
3432 {
3433 /* This will return NULL if the cache doesn't exist or is outdated */
3434 dir_mtime->cache = gtk_icon_cache_new_for_path (path: dir_mtime->dir);
3435 }
3436
3437 if (dir_mtime->cache != NULL)
3438 icons = gtk_icon_cache_list_icons_in_directory (cache: dir_mtime->cache, directory: subdir, set: &theme->icons);
3439 else
3440 icons = scan_directory (self, full_dir, set: &theme->icons);
3441
3442 if (icons)
3443 {
3444 theme_add_dir_with_icons (theme,
3445 dir_size,
3446 FALSE,
3447 g_steal_pointer (&full_dir),
3448 icons);
3449 g_hash_table_destroy (hash_table: icons);
3450 }
3451 }
3452
3453 g_free (mem: full_dir);
3454 }
3455
3456 if (strcmp (s1: theme->name, FALLBACK_ICON_THEME) == 0)
3457 {
3458 int r;
3459 for (r = 0; self->resource_path[r]; r++)
3460 {
3461 GHashTable *icons;
3462 char *full_dir;
3463
3464 /* Force a trailing / here, to avoid extra copies in GResource */
3465 full_dir = g_build_filename (first_element: self->resource_path[r], subdir, " ", NULL);
3466 full_dir[strlen (s: full_dir) - 1] = '\0';
3467
3468 icons = scan_resource_directory (self, full_dir, set: &theme->icons);
3469 if (icons)
3470 {
3471 theme_add_dir_with_icons (theme,
3472 dir_size,
3473 TRUE,
3474 g_steal_pointer (&full_dir),
3475 icons);
3476 g_hash_table_destroy (hash_table: icons);
3477 }
3478
3479 g_free (mem: full_dir);
3480 }
3481 }
3482}
3483
3484/*
3485 * GtkIconPaintable
3486 */
3487
3488static void icon_paintable_init (GdkPaintableInterface *iface);
3489static void icon_symbolic_paintable_init (GtkSymbolicPaintableInterface *iface);
3490
3491enum
3492{
3493 PROP_0,
3494 PROP_FILE,
3495 PROP_ICON_NAME,
3496 PROP_IS_SYMBOLIC,
3497};
3498
3499G_DEFINE_TYPE_WITH_CODE (GtkIconPaintable, gtk_icon_paintable, G_TYPE_OBJECT,
3500 G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
3501 icon_paintable_init)
3502 G_IMPLEMENT_INTERFACE (GTK_TYPE_SYMBOLIC_PAINTABLE,
3503 icon_symbolic_paintable_init))
3504
3505static void
3506gtk_icon_paintable_init (GtkIconPaintable *icon)
3507{
3508 g_mutex_init (mutex: &icon->texture_lock);
3509}
3510
3511static GtkIconPaintable *
3512icon_paintable_new (const char *icon_name,
3513 int desired_size,
3514 int desired_scale)
3515{
3516 GtkIconPaintable *icon;
3517
3518 icon = g_object_new (GTK_TYPE_ICON_PAINTABLE,
3519 first_property_name: "icon-name", icon_name,
3520 NULL);
3521
3522 icon->desired_size = desired_size;
3523 icon->desired_scale = desired_scale;
3524
3525 return icon;
3526}
3527
3528static void
3529gtk_icon_paintable_finalize (GObject *object)
3530{
3531 GtkIconPaintable *icon = (GtkIconPaintable *) object;
3532
3533 icon_cache_remove (icon);
3534
3535 g_strfreev (str_array: icon->key.icon_names);
3536
3537 g_free (mem: icon->filename);
3538 g_free (mem: icon->icon_name);
3539
3540 g_clear_object (&icon->loadable);
3541 g_clear_object (&icon->texture);
3542#ifdef G_OS_WIN32
3543 g_clear_object (&icon->win32_icon);
3544#endif
3545
3546 g_mutex_clear (mutex: &icon->texture_lock);
3547
3548 G_OBJECT_CLASS (gtk_icon_paintable_parent_class)->finalize (object);
3549}
3550
3551static void
3552gtk_icon_paintable_get_property (GObject *object,
3553 guint prop_id,
3554 GValue *value,
3555 GParamSpec *pspec)
3556{
3557 GtkIconPaintable *icon = GTK_ICON_PAINTABLE (object);
3558
3559 switch (prop_id)
3560 {
3561 case PROP_FILE:
3562 g_value_take_object (value, v_object: gtk_icon_paintable_get_file (self: icon));
3563 break;
3564
3565 case PROP_ICON_NAME:
3566 g_value_set_string (value, v_string: icon->icon_name);
3567 break;
3568
3569 case PROP_IS_SYMBOLIC:
3570 g_value_set_boolean (value, v_boolean: icon->is_symbolic);
3571 break;
3572
3573 default:
3574 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3575 }
3576}
3577
3578static void
3579gtk_icon_paintable_set_property (GObject *object,
3580 guint prop_id,
3581 const GValue *value,
3582 GParamSpec *pspec)
3583{
3584 GtkIconPaintable *icon = GTK_ICON_PAINTABLE (object);
3585 GFile *file;
3586
3587 switch (prop_id)
3588 {
3589 case PROP_FILE:
3590 icon->is_resource = FALSE;
3591 g_clear_pointer (&icon->filename, g_free);
3592
3593 file = G_FILE (g_value_get_object (value));
3594 if (file)
3595 {
3596 icon->is_resource = g_file_has_uri_scheme (file, uri_scheme: "resource");
3597 if (icon->is_resource)
3598 {
3599 char *uri = g_file_get_uri (file);
3600 icon->filename = g_strdup (str: uri + 11); /* resource:// */
3601 g_free (mem: uri);
3602 }
3603 else
3604 icon->filename = g_file_get_path (file);
3605 }
3606 break;
3607
3608 case PROP_ICON_NAME:
3609 g_free (mem: icon->icon_name);
3610 icon->icon_name = g_value_dup_string (value);
3611 break;
3612
3613 case PROP_IS_SYMBOLIC:
3614 icon->is_symbolic = g_value_get_boolean (value);
3615 break;
3616
3617 default:
3618 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3619 }
3620}
3621
3622
3623static void
3624gtk_icon_paintable_class_init (GtkIconPaintableClass *klass)
3625{
3626 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
3627
3628 gobject_class->get_property = gtk_icon_paintable_get_property;
3629 gobject_class->set_property = gtk_icon_paintable_set_property;
3630 gobject_class->finalize = gtk_icon_paintable_finalize;
3631
3632 /**
3633 * GtkIconPaintable:file: (attributes org.gtk.Property.get=gtk_icon_paintable_get_file)
3634 *
3635 * The file representing the icon, if any.
3636 */
3637 g_object_class_install_property (oclass: gobject_class, property_id: PROP_FILE,
3638 pspec: g_param_spec_object (name: "file",
3639 P_("file"),
3640 P_("The file representing the icon"),
3641 G_TYPE_FILE,
3642 flags: G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
3643
3644 /**
3645 * GtkIconPaintable:icon-name: (attributes org.gtk.Property.get=gtk_icon_paintable_get_icon_name)
3646 *
3647 * The icon name that was chosen during lookup.
3648 */
3649 g_object_class_install_property (oclass: gobject_class, property_id: PROP_ICON_NAME,
3650 pspec: g_param_spec_string (name: "icon-name",
3651 P_("Icon name"),
3652 P_("The icon name chosen during lookup"),
3653 NULL,
3654 flags: G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
3655
3656 /**
3657 * GtkIconPaintable:is-symbolic: (attributes org.gtk.Property.get=gtk_icon_paintable_is_symbolic)
3658 *
3659 * Whether the icon is symbolic or not.
3660 */
3661 g_object_class_install_property (oclass: gobject_class, property_id: PROP_IS_SYMBOLIC,
3662 pspec: g_param_spec_boolean (name: "is-symbolic",
3663 P_("Is symbolic"),
3664 P_("If the icon is symbolic"),
3665 FALSE,
3666 flags: G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK));
3667}
3668
3669static GFile *
3670new_resource_file (const char *filename)
3671{
3672 char *escaped = g_uri_escape_string (unescaped: filename,
3673 G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
3674 char *uri = g_strconcat (string1: "resource://", escaped, NULL);
3675 GFile *file = g_file_new_for_uri (uri);
3676
3677 g_free (mem: escaped);
3678 g_free (mem: uri);
3679
3680 return file;
3681}
3682
3683/**
3684 * gtk_icon_paintable_get_file: (attributes org.gtk.Method.get_property=file)
3685 * @self: a `GtkIconPaintable`
3686 *
3687 * Gets the `GFile` that was used to load the icon.
3688 *
3689 * Returns %NULL if the icon was not loaded from a file.
3690 *
3691 * Returns: (nullable) (transfer full): the `GFile` for the icon
3692 */
3693GFile *
3694gtk_icon_paintable_get_file (GtkIconPaintable *icon)
3695{
3696 if (icon->filename)
3697 {
3698 if (icon->is_resource)
3699 return new_resource_file (filename: icon->filename);
3700 else
3701 return g_file_new_for_path (path: icon->filename);
3702 }
3703
3704 return NULL;
3705}
3706
3707/**
3708 * gtk_icon_paintable_get_icon_name: (attributes org.gtk.Method.get_property=icon-name)
3709 * @self: a `GtkIconPaintable`
3710 *
3711 * Get the icon name being used for this icon.
3712 *
3713 * When an icon looked up in the icon theme was not available, the
3714 * icon theme may use fallback icons - either those specified to
3715 * gtk_icon_theme_lookup_icon() or the always-available
3716 * "image-missing". The icon chosen is returned by this function.
3717 *
3718 * If the icon was created without an icon theme, this function
3719 * returns %NULL.
3720 *
3721 *
3722 * Returns: (nullable) (type filename): the themed icon-name for the
3723 * icon, or %NULL if its not a themed icon.
3724 */
3725const char *
3726gtk_icon_paintable_get_icon_name (GtkIconPaintable *icon)
3727{
3728 g_return_val_if_fail (icon != NULL, NULL);
3729
3730 return icon->icon_name;
3731}
3732
3733/**
3734 * gtk_icon_paintable_is_symbolic: (attributes org.gtk.Method.get_property=is-symbolic)
3735 * @self: a `GtkIconPaintable`
3736 *
3737 * Checks if the icon is symbolic or not.
3738 *
3739 * This currently uses only the file name and not the file contents
3740 * for determining this. This behaviour may change in the future.
3741 *
3742 * Note that to render a symbolic `GtkIconPaintable` properly (with
3743 * recoloring), you have to set its icon name on a `GtkImage`.
3744 *
3745 * Returns: %TRUE if the icon is symbolic, %FALSE otherwise
3746 */
3747gboolean
3748gtk_icon_paintable_is_symbolic (GtkIconPaintable *icon)
3749{
3750 g_return_val_if_fail (GTK_IS_ICON_PAINTABLE (icon), FALSE);
3751
3752 return icon->is_symbolic;
3753}
3754
3755/* This function contains the complicated logic for deciding
3756 * on the size at which to load the icon and loading it at
3757 * that size.
3758 */
3759static void
3760icon_ensure_texture__locked (GtkIconPaintable *icon,
3761 gboolean in_thread)
3762{
3763 gint64 before;
3764 int pixel_size;
3765 GError *load_error = NULL;
3766
3767 icon_cache_mark_used_if_cached (icon);
3768
3769 if (icon->texture)
3770 return;
3771
3772 before = GDK_PROFILER_CURRENT_TIME;
3773
3774 /* This is the natural pixel size for the requested icon size + scale in this directory.
3775 * We precalculate this so we can use it as a rasterization size for svgs.
3776 */
3777 pixel_size = icon->desired_size * icon->desired_scale;
3778
3779 /* At this point, we need to actually get the icon; either from the
3780 * builtin image or by loading the file
3781 */
3782#ifdef G_OS_WIN32
3783 if (icon->win32_icon)
3784 {
3785 icon->texture = gdk_texture_new_for_pixbuf (icon->win32_icon);
3786 }
3787 else
3788#endif
3789 if (icon->is_resource)
3790 {
3791 if (icon->is_svg)
3792 {
3793 GdkPixbuf *source_pixbuf;
3794
3795 if (gtk_icon_paintable_is_symbolic (icon))
3796 source_pixbuf = gtk_make_symbolic_pixbuf_from_resource (path: icon->filename,
3797 width: pixel_size, height: pixel_size,
3798 scale: icon->desired_scale,
3799 error: &load_error);
3800 else
3801 source_pixbuf = _gdk_pixbuf_new_from_resource_at_scale (resource_path: icon->filename,
3802 width: pixel_size, height: pixel_size,
3803 TRUE, error: &load_error);
3804 if (source_pixbuf)
3805 {
3806 icon->texture = gdk_texture_new_for_pixbuf (pixbuf: source_pixbuf);
3807 g_object_unref (object: source_pixbuf);
3808 }
3809 }
3810 else
3811 icon->texture = gdk_texture_new_from_resource (resource_path: icon->filename);
3812 }
3813 else if (icon->filename)
3814 {
3815 if (icon->is_svg)
3816 {
3817 GdkPixbuf *source_pixbuf;
3818
3819 if (gtk_icon_paintable_is_symbolic (icon))
3820 source_pixbuf = gtk_make_symbolic_pixbuf_from_path (path: icon->filename,
3821 width: pixel_size, height: pixel_size,
3822 scale: icon->desired_scale,
3823 error: &load_error);
3824 else
3825 {
3826 GFile *file = g_file_new_for_path (path: icon->filename);
3827 GInputStream *stream = G_INPUT_STREAM (g_file_read (file, NULL, &load_error));
3828
3829 g_object_unref (object: file);
3830 if (stream)
3831 {
3832 source_pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream,
3833 width: pixel_size, height: pixel_size,
3834 TRUE, NULL,
3835 error: &load_error);
3836 g_object_unref (object: stream);
3837 }
3838 else
3839 source_pixbuf = NULL;
3840 }
3841 if (source_pixbuf)
3842 {
3843 icon->texture = gdk_texture_new_for_pixbuf (pixbuf: source_pixbuf);
3844 g_object_unref (object: source_pixbuf);
3845 }
3846 }
3847 else
3848 {
3849 icon->texture = gdk_texture_new_from_filename (path: icon->filename, error: &load_error);
3850 }
3851 }
3852 else
3853 {
3854 GInputStream *stream;
3855 GdkPixbuf *source_pixbuf;
3856
3857 g_assert (icon->loadable);
3858
3859 stream = g_loadable_icon_load (icon: icon->loadable,
3860 size: pixel_size,
3861 NULL, NULL,
3862 error: &load_error);
3863 if (stream)
3864 {
3865 /* SVG icons are a special case - we just immediately scale them
3866 * to the desired size
3867 */
3868 if (icon->is_svg)
3869 {
3870 source_pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream,
3871 width: pixel_size, height: pixel_size,
3872 TRUE, NULL,
3873 error: &load_error);
3874 }
3875 else
3876 source_pixbuf = _gdk_pixbuf_new_from_stream (stream,
3877 NULL, error: &load_error);
3878 g_object_unref (object: stream);
3879 if (source_pixbuf)
3880 {
3881 icon->texture = gdk_texture_new_for_pixbuf (pixbuf: source_pixbuf);
3882 g_object_unref (object: source_pixbuf);
3883 }
3884 }
3885 }
3886
3887 if (!icon->texture)
3888 {
3889 g_warning ("Failed to load icon %s: %s", icon->filename, load_error->message);
3890 g_clear_error (err: &load_error);
3891 icon->texture = gdk_texture_new_from_resource (IMAGE_MISSING_RESOURCE_PATH);
3892 icon->icon_name = g_strdup (str: "image-missing");
3893 icon->is_symbolic = FALSE;
3894 }
3895
3896 if (GDK_PROFILER_IS_RUNNING)
3897 {
3898 gint64 end = GDK_PROFILER_CURRENT_TIME;
3899 /* Don't report quick (< 0.5 msec) parses */
3900 if (end - before > 500000 || !in_thread)
3901 {
3902 gdk_profiler_add_markf (before, (end - before), in_thread ? "icon load (thread)" : "icon load" ,
3903 "%s size %d@%d", icon->filename, icon->desired_size, icon->desired_scale);
3904 }
3905 }
3906}
3907
3908static GdkTexture *
3909gtk_icon_paintable_ensure_texture (GtkIconPaintable *self)
3910{
3911 GdkTexture *texture = NULL;
3912
3913 g_mutex_lock (mutex: &self->texture_lock);
3914
3915 icon_ensure_texture__locked (icon: self, FALSE);
3916
3917 texture = self->texture;
3918
3919 g_mutex_unlock (mutex: &self->texture_lock);
3920
3921 g_assert (texture != NULL);
3922
3923 return texture;
3924}
3925
3926static void
3927init_color_matrix (graphene_matrix_t *color_matrix,
3928 graphene_vec4_t *color_offset,
3929 const GdkRGBA *foreground_color,
3930 const GdkRGBA *success_color,
3931 const GdkRGBA *warning_color,
3932 const GdkRGBA *error_color)
3933{
3934 const GdkRGBA fg_default = { 0.7450980392156863, 0.7450980392156863, 0.7450980392156863, 1.0};
3935 const GdkRGBA success_default = { 0.3046921492332342,0.6015716792553597, 0.023437857633325704, 1.0};
3936 const GdkRGBA warning_default = {0.9570458533607996, 0.47266346227206835, 0.2421911955443656, 1.0 };
3937 const GdkRGBA error_default = { 0.796887159533074, 0 ,0, 1.0 };
3938 const GdkRGBA *fg = foreground_color ? foreground_color : &fg_default;
3939 const GdkRGBA *sc = success_color ? success_color : &success_default;
3940 const GdkRGBA *wc = warning_color ? warning_color : &warning_default;
3941 const GdkRGBA *ec = error_color ? error_color : &error_default;
3942
3943 graphene_matrix_init_from_float (m: color_matrix,
3944 v: (float[16]) {
3945 sc->red - fg->red, sc->green - fg->green, sc->blue - fg->blue, 0,
3946 wc->red - fg->red, wc->green - fg->green, wc->blue - fg->blue, 0,
3947 ec->red - fg->red, ec->green - fg->green, ec->blue - fg->blue, 0,
3948 0, 0, 0, fg->alpha
3949 });
3950 graphene_vec4_init (v: color_offset, x: fg->red, y: fg->green, z: fg->blue, w: 0);
3951}
3952
3953
3954static void
3955icon_paintable_snapshot (GdkPaintable *paintable,
3956 GtkSnapshot *snapshot,
3957 double width,
3958 double height)
3959{
3960 gtk_symbolic_paintable_snapshot_symbolic (paintable: GTK_SYMBOLIC_PAINTABLE (ptr: paintable), snapshot, width, height, NULL, n_colors: 0);
3961}
3962
3963static void
3964gtk_icon_paintable_snapshot_symbolic (GtkSymbolicPaintable *paintable,
3965 GtkSnapshot *snapshot,
3966 double width,
3967 double height,
3968 const GdkRGBA *colors,
3969 gsize n_colors)
3970{
3971 GtkIconPaintable *icon = GTK_ICON_PAINTABLE (paintable);
3972 GdkTexture *texture;
3973 int texture_width, texture_height;
3974 double render_width;
3975 double render_height;
3976 gboolean symbolic;
3977
3978 texture = gtk_icon_paintable_ensure_texture (self: icon);
3979 symbolic = gtk_icon_paintable_is_symbolic (icon);
3980
3981 if (symbolic)
3982 {
3983 graphene_matrix_t matrix;
3984 graphene_vec4_t offset;
3985
3986 init_color_matrix (color_matrix: &matrix, color_offset: &offset,
3987 foreground_color: &colors[0], success_color: &colors[3],
3988 warning_color: &colors[2], error_color: &colors[1]);
3989
3990 gtk_snapshot_push_color_matrix (snapshot, color_matrix: &matrix, color_offset: &offset);
3991 }
3992
3993 texture_width = gdk_texture_get_width (texture);
3994 texture_height = gdk_texture_get_height (texture);
3995
3996 /* Keep aspect ratio and center */
3997 if (texture_width >= texture_height)
3998 {
3999 render_width = width;
4000 render_height = height * ((double)texture_height / texture_width);
4001 }
4002 else
4003 {
4004 render_width = width * ((double)texture_width / texture_height);
4005 render_height = height;
4006 }
4007
4008 gtk_snapshot_append_texture (snapshot, texture,
4009 bounds: &GRAPHENE_RECT_INIT ((width - render_width) / 2,
4010 (height - render_height) / 2,
4011 render_width,
4012 render_height));
4013
4014 if (symbolic)
4015 gtk_snapshot_pop (snapshot);
4016}
4017
4018static GdkPaintableFlags
4019icon_paintable_get_flags (GdkPaintable *paintable)
4020{
4021 return GDK_PAINTABLE_STATIC_SIZE | GDK_PAINTABLE_STATIC_CONTENTS;
4022}
4023
4024static int
4025icon_paintable_get_intrinsic_width (GdkPaintable *paintable)
4026{
4027 GtkIconPaintable *icon = GTK_ICON_PAINTABLE (paintable);
4028
4029 return icon->desired_size;
4030}
4031
4032static int
4033icon_paintable_get_intrinsic_height (GdkPaintable *paintable)
4034{
4035 GtkIconPaintable *icon = GTK_ICON_PAINTABLE (paintable);
4036
4037 return icon->desired_size;
4038}
4039
4040static void
4041icon_paintable_init (GdkPaintableInterface *iface)
4042{
4043 iface->snapshot = icon_paintable_snapshot;
4044 iface->get_flags = icon_paintable_get_flags;
4045 iface->get_intrinsic_width = icon_paintable_get_intrinsic_width;
4046 iface->get_intrinsic_height = icon_paintable_get_intrinsic_height;
4047}
4048
4049static void
4050icon_symbolic_paintable_init (GtkSymbolicPaintableInterface *iface)
4051{
4052 iface->snapshot_symbolic = gtk_icon_paintable_snapshot_symbolic;
4053}
4054
4055/**
4056 * gtk_icon_paintable_new_for_file:
4057 * @file: a `GFile`
4058 * @size: desired icon size
4059 * @scale: the desired scale
4060 *
4061 * Creates a `GtkIconPaintable` for a file with a given size and scale.
4062 *
4063 * The icon can then be rendered by using it as a `GdkPaintable`.
4064 *
4065 * Returns: (transfer full): a `GtkIconPaintable` containing
4066 * for the icon. Unref with g_object_unref()
4067 */
4068GtkIconPaintable *
4069gtk_icon_paintable_new_for_file (GFile *file,
4070 int size,
4071 int scale)
4072{
4073 GtkIconPaintable *icon;
4074
4075 icon = icon_paintable_new (NULL, desired_size: size, desired_scale: scale);
4076 icon->loadable = G_LOADABLE_ICON (g_file_icon_new (file));
4077 icon->is_resource = g_file_has_uri_scheme (file, uri_scheme: "resource");
4078
4079 if (icon->is_resource)
4080 {
4081 char *uri;
4082
4083 uri = g_file_get_uri (file);
4084 icon->filename = g_strdup (str: uri + 11); /* resource:// */
4085 g_free (mem: uri);
4086 }
4087 else
4088 {
4089 icon->filename = g_file_get_path (file);
4090 }
4091
4092 icon->is_svg = suffix_from_name (name: icon->filename) == ICON_CACHE_FLAG_SVG_SUFFIX;
4093
4094 return icon;
4095}
4096
4097/**
4098 * gtk_icon_theme_lookup_by_gicon:
4099 * @self: a `GtkIconTheme`
4100 * @icon: the `GIcon` to look up
4101 * @size: desired icon size
4102 * @scale: the desired scale
4103 * @direction: text direction the icon will be displayed in
4104 * @flags: flags modifying the behavior of the icon lookup
4105 *
4106 * Looks up a icon for a desired size and window scale.
4107 *
4108 * The icon can then be rendered by using it as a `GdkPaintable`,
4109 * or you can get information such as the filename and size.
4110 *
4111 * Returns: (transfer full): a `GtkIconPaintable` containing
4112 * information about the icon. Unref with g_object_unref()
4113 */
4114GtkIconPaintable *
4115gtk_icon_theme_lookup_by_gicon (GtkIconTheme *self,
4116 GIcon *gicon,
4117 int size,
4118 int scale,
4119 GtkTextDirection direction,
4120 GtkIconLookupFlags flags)
4121{
4122 GtkIconPaintable *paintable = NULL;
4123
4124 g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL);
4125 g_return_val_if_fail (G_IS_ICON (gicon), NULL);
4126 g_return_val_if_fail (size > 0, NULL);
4127 g_return_val_if_fail (scale > 0, NULL);
4128
4129 /* We can't render emblemed icons atm, but at least render the base */
4130 while (G_IS_EMBLEMED_ICON (gicon))
4131 gicon = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (gicon));
4132 g_assert (gicon); /* shut up gcc -Wnull-dereference */
4133
4134 if (GDK_IS_TEXTURE (gicon))
4135 {
4136 paintable = icon_paintable_new (NULL, desired_size: size, desired_scale: scale);
4137 paintable->texture = g_object_ref (GDK_TEXTURE (gicon));
4138 }
4139 else if (GDK_IS_PIXBUF (gicon))
4140 {
4141 paintable = icon_paintable_new (NULL, desired_size: size, desired_scale: scale);
4142 paintable->texture = gdk_texture_new_for_pixbuf (GDK_PIXBUF (gicon));
4143 }
4144 else if (G_IS_FILE_ICON (gicon))
4145 {
4146 GFile *file = g_file_icon_get_file (G_FILE_ICON (gicon));
4147
4148 paintable = gtk_icon_paintable_new_for_file (file, size, scale);
4149 }
4150 else if (G_IS_LOADABLE_ICON (gicon))
4151 {
4152 paintable = icon_paintable_new (NULL, desired_size: size, desired_scale: scale);
4153 paintable->loadable = G_LOADABLE_ICON (g_object_ref (gicon));
4154 paintable->is_svg = FALSE;
4155 }
4156 else if (G_IS_THEMED_ICON (gicon))
4157 {
4158 const char **names;
4159
4160 names = (const char **) g_themed_icon_get_names (G_THEMED_ICON (gicon));
4161 paintable = gtk_icon_theme_lookup_icon (self, icon_name: names[0], fallbacks: &names[1], size, scale, direction, flags);
4162 }
4163 else
4164 {
4165 g_debug ("Unhandled GIcon type %s", G_OBJECT_TYPE_NAME (gicon));
4166 paintable = icon_paintable_new (icon_name: "image-missing", desired_size: size, desired_scale: scale);
4167 paintable->filename = g_strdup (IMAGE_MISSING_RESOURCE_PATH);
4168 paintable->is_resource = TRUE;
4169 }
4170
4171 return paintable;
4172}
4173
4174/**
4175 * gtk_icon_theme_get_display:
4176 * @self: a `GtkIconTheme`
4177 *
4178 * Returns the display that the `GtkIconTheme` object was
4179 * created for.
4180 *
4181 * Returns: (nullable) (transfer none): the display of @icon_theme
4182 */
4183GdkDisplay *
4184gtk_icon_theme_get_display (GtkIconTheme *self)
4185{
4186 g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL);
4187
4188 return self->display;
4189}
4190
4191

source code of gtk/gtk/gtkicontheme.c