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 | |
117 | typedef 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 | |
123 | struct _GtkStringSetChunk { |
124 | GtkStringSetChunk *next; |
125 | char data[STRING_SET_CHUNK_SIZE]; |
126 | }; |
127 | |
128 | struct _GtkStringSet { |
129 | GHashTable *hash; |
130 | GtkStringSetChunk *chunks; |
131 | int used_in_chunk; |
132 | }; |
133 | |
134 | static void |
135 | gtk_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 | |
142 | static void |
143 | gtk_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 | |
154 | static const char * |
155 | gtk_string_set_lookup (GtkStringSet *set, |
156 | const char *string) |
157 | { |
158 | return g_hash_table_lookup (hash_table: set->hash, key: string); |
159 | } |
160 | |
161 | static void |
162 | gtk_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 | |
176 | const char * |
177 | gtk_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 | |
269 | typedef 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 | |
286 | typedef struct _GtkIconPaintableClass GtkIconPaintableClass; |
287 | typedef struct _GtkIconThemeClass GtkIconThemeClass; |
288 | |
289 | |
290 | typedef 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 | */ |
300 | struct _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 | |
338 | struct _GtkIconThemeClass |
339 | { |
340 | GObjectClass parent_class; |
341 | |
342 | void (* changed) (GtkIconTheme *self); |
343 | }; |
344 | |
345 | typedef struct { |
346 | char **icon_names; |
347 | int size; |
348 | int scale; |
349 | GtkIconLookupFlags flags; |
350 | } IconKey; |
351 | |
352 | struct _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. */ |
359 | G_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 | */ |
368 | struct _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 | |
405 | typedef struct |
406 | { |
407 | char *name; |
408 | char *display_name; |
409 | char *; |
410 | |
411 | GArray *dir_sizes; /* IconThemeDirSize */ |
412 | GArray *dirs; /* IconThemeDir */ |
413 | GtkStringSet icons; |
414 | } IconTheme; |
415 | |
416 | typedef struct |
417 | { |
418 | guint16 dir_index; /* index in dirs */ |
419 | guint8 best_suffix; |
420 | guint8 best_suffix_no_svg; |
421 | } IconThemeFile; |
422 | |
423 | typedef 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 | |
436 | typedef struct |
437 | { |
438 | gboolean is_resource; |
439 | char *path; /* e.g. "/usr/share/icons/hicolor/32x32/apps" */ |
440 | } IconThemeDir; |
441 | |
442 | typedef struct |
443 | { |
444 | char *svg_filename; |
445 | char *no_svg_filename; |
446 | gboolean is_resource; |
447 | } UnthemedIcon; |
448 | |
449 | typedef struct |
450 | { |
451 | char *dir; |
452 | time_t mtime; |
453 | GtkIconCache *cache; |
454 | gboolean exists; |
455 | } IconThemeDirMtime; |
456 | |
457 | static void gtk_icon_theme_finalize (GObject *object); |
458 | static void gtk_icon_theme_dispose (GObject *object); |
459 | static IconTheme * theme_new (const char *theme_name, |
460 | GKeyFile *theme_file); |
461 | static void theme_dir_size_destroy (IconThemeDirSize *dir_size); |
462 | static void theme_dir_destroy (IconThemeDir *dir); |
463 | static void theme_destroy (IconTheme *theme); |
464 | static GtkIconPaintable *theme_lookup_icon (IconTheme *theme, |
465 | const char *icon_name, |
466 | int size, |
467 | int scale, |
468 | gboolean allow_svg); |
469 | static gboolean theme_has_icon (IconTheme *theme, |
470 | const char *icon_name); |
471 | static void theme_subdir_load (GtkIconTheme *self, |
472 | IconTheme *theme, |
473 | GKeyFile *theme_file, |
474 | char *subdir); |
475 | static void do_theme_change (GtkIconTheme *self); |
476 | static void blow_themes (GtkIconTheme *self); |
477 | static gboolean rescan_themes (GtkIconTheme *self); |
478 | static GtkIconPaintable *icon_paintable_new (const char *icon_name, |
479 | int desired_size, |
480 | int desired_scale); |
481 | static IconCacheFlag suffix_from_name (const char *name); |
482 | static void icon_ensure_texture__locked (GtkIconPaintable *icon, |
483 | gboolean in_thread); |
484 | static void gtk_icon_theme_unset_display (GtkIconTheme *self); |
485 | static void gtk_icon_theme_set_display (GtkIconTheme *self, |
486 | GdkDisplay *display); |
487 | static void update_current_theme__mainthread (GtkIconTheme *self); |
488 | static gboolean ensure_valid_themes (GtkIconTheme *self, |
489 | gboolean non_blocking); |
490 | |
491 | |
492 | static 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 | */ |
504 | struct _GtkIconThemeRef |
505 | { |
506 | gatomicrefcount count; |
507 | GMutex lock; |
508 | GtkIconTheme *theme; |
509 | }; |
510 | |
511 | static GtkIconThemeRef * |
512 | gtk_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 | |
523 | static GtkIconThemeRef * |
524 | gtk_icon_theme_ref_ref (GtkIconThemeRef *ref) |
525 | { |
526 | g_atomic_ref_count_inc (arc: &ref->count); |
527 | return ref; |
528 | } |
529 | |
530 | static void |
531 | gtk_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. */ |
543 | static GtkIconTheme * |
544 | gtk_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 | |
552 | static void |
553 | gtk_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 | |
567 | static void |
568 | gtk_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 | |
575 | static void |
576 | gtk_icon_theme_lock (GtkIconTheme *self) |
577 | { |
578 | g_mutex_lock (mutex: &self->ref->lock); |
579 | } |
580 | |
581 | static void |
582 | gtk_icon_theme_unlock (GtkIconTheme *self) |
583 | { |
584 | g_mutex_unlock (mutex: &self->ref->lock); |
585 | } |
586 | |
587 | |
588 | static guint |
589 | icon_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 | |
604 | static gboolean |
605 | icon_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 */ |
641 | static 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 */ |
649 | static 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 | |
666 | static GtkIconPaintable * |
667 | icon_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 | */ |
703 | static void |
704 | icon_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 | |
716 | static void |
717 | icon_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 | |
734 | static void |
735 | icon_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 | |
758 | static void |
759 | icon_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 | |
767 | static void |
768 | icon_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 | |
792 | G_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 | */ |
806 | GtkIconTheme * |
807 | gtk_icon_theme_new (void) |
808 | { |
809 | return g_object_new (GTK_TYPE_ICON_THEME, NULL); |
810 | } |
811 | |
812 | static void |
813 | load_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 | |
826 | static void |
827 | gtk_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 | */ |
854 | GtkIconTheme * |
855 | gtk_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 | |
878 | enum { |
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 | |
887 | static GParamSpec *props[LAST_PROP]; |
888 | |
889 | static void |
890 | gtk_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 | |
925 | static void |
926 | gtk_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 | |
957 | static void |
958 | gtk_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 | */ |
1070 | static void |
1071 | display_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 | |
1098 | static void |
1099 | update_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 | */ |
1133 | static void |
1134 | theme_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 | |
1151 | static void |
1152 | gtk_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 | |
1170 | void |
1171 | gtk_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 | */ |
1212 | static gboolean |
1213 | pixbuf_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 | |
1244 | static void |
1245 | free_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 | |
1253 | static void |
1254 | gtk_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 | |
1299 | static gboolean |
1300 | theme_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 | |
1337 | static void |
1338 | queue_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 | |
1350 | static void |
1351 | do_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 | |
1365 | static void |
1366 | blow_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 | |
1380 | int |
1381 | gtk_icon_theme_get_serial (GtkIconTheme *self) |
1382 | { |
1383 | return self->serial; |
1384 | } |
1385 | |
1386 | static void |
1387 | gtk_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 | |
1407 | static void |
1408 | gtk_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 | */ |
1458 | void |
1459 | gtk_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 | */ |
1490 | char ** |
1491 | gtk_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 | */ |
1515 | void |
1516 | gtk_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 | */ |
1555 | void |
1556 | gtk_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 | */ |
1587 | char ** |
1588 | gtk_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 | */ |
1616 | void |
1617 | gtk_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 | */ |
1649 | void |
1650 | gtk_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 | */ |
1691 | char * |
1692 | gtk_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 | |
1718 | static 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 | |
1761 | static void |
1762 | insert_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 | |
1876 | static void |
1877 | free_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 | |
1884 | static void |
1885 | strip_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 | |
1898 | static char * |
1899 | strip_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 | |
1914 | static void |
1915 | add_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 | |
1979 | static void |
1980 | load_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 | |
2065 | static gboolean |
2066 | ensure_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 | |
2108 | static inline gboolean |
2109 | icon_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 | |
2132 | static inline gboolean |
2133 | icon_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 | |
2156 | static GtkIconPaintable * |
2157 | real_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 | |
2305 | static void |
2306 | icon_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 | |
2315 | static GtkIconPaintable * |
2316 | choose_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 | |
2435 | static void |
2436 | load_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 | */ |
2479 | GtkIconPaintable * |
2480 | gtk_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 */ |
2548 | GQuark |
2549 | gtk_icon_theme_error_quark (void) |
2550 | { |
2551 | return g_quark_from_static_string (string: "gtk-icon-theme-error-quark" ); |
2552 | } |
2553 | |
2554 | void |
2555 | gtk_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 | */ |
2593 | gboolean |
2594 | gtk_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 | */ |
2634 | gboolean |
2635 | gtk_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 | |
2668 | static void |
2669 | add_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 | */ |
2696 | int * |
2697 | gtk_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 | |
2745 | static void |
2746 | add_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 | */ |
2765 | char ** |
2766 | gtk_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 | |
2809 | static gboolean |
2810 | rescan_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 | |
2840 | static IconTheme * |
2841 | theme_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 | |
2864 | static void |
2865 | theme_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 | |
2886 | static void |
2887 | theme_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 | |
2895 | static void |
2896 | theme_dir_destroy (IconThemeDir *dir) |
2897 | { |
2898 | g_free (mem: dir->path); |
2899 | } |
2900 | |
2901 | static int |
2902 | theme_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 | |
2940 | static const char * |
2941 | string_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 | |
2961 | static inline IconCacheFlag |
2962 | suffix_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 | |
2987 | static IconCacheFlag |
2988 | best_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 */ |
3004 | static gboolean |
3005 | compare_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 | |
3068 | static GtkIconPaintable * |
3069 | theme_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 | |
3147 | static gboolean |
3148 | theme_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 | |
3154 | static GHashTable * |
3155 | scan_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 | |
3196 | static GHashTable * |
3197 | scan_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 | |
3239 | static gboolean |
3240 | theme_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 | |
3252 | static guint32 |
3253 | theme_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 | |
3289 | static guint32 |
3290 | theme_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 | |
3305 | static void |
3306 | theme_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 */ |
3330 | static void |
3331 | theme_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 | |
3352 | static void |
3353 | theme_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 | |
3488 | static void icon_paintable_init (GdkPaintableInterface *iface); |
3489 | static void icon_symbolic_paintable_init (GtkSymbolicPaintableInterface *iface); |
3490 | |
3491 | enum |
3492 | { |
3493 | PROP_0, |
3494 | PROP_FILE, |
3495 | PROP_ICON_NAME, |
3496 | PROP_IS_SYMBOLIC, |
3497 | }; |
3498 | |
3499 | G_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 | |
3505 | static void |
3506 | gtk_icon_paintable_init (GtkIconPaintable *icon) |
3507 | { |
3508 | g_mutex_init (mutex: &icon->texture_lock); |
3509 | } |
3510 | |
3511 | static GtkIconPaintable * |
3512 | icon_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 | |
3528 | static void |
3529 | gtk_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 | |
3551 | static void |
3552 | gtk_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 | |
3578 | static void |
3579 | gtk_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 | |
3623 | static void |
3624 | gtk_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 | |
3669 | static GFile * |
3670 | new_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 | */ |
3693 | GFile * |
3694 | gtk_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 | */ |
3725 | const char * |
3726 | gtk_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 | */ |
3747 | gboolean |
3748 | gtk_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 | */ |
3759 | static void |
3760 | icon_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 | |
3908 | static GdkTexture * |
3909 | gtk_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 | |
3926 | static void |
3927 | init_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 | |
3954 | static void |
3955 | icon_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 | |
3963 | static void |
3964 | gtk_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 | |
4018 | static GdkPaintableFlags |
4019 | icon_paintable_get_flags (GdkPaintable *paintable) |
4020 | { |
4021 | return GDK_PAINTABLE_STATIC_SIZE | GDK_PAINTABLE_STATIC_CONTENTS; |
4022 | } |
4023 | |
4024 | static int |
4025 | icon_paintable_get_intrinsic_width (GdkPaintable *paintable) |
4026 | { |
4027 | GtkIconPaintable *icon = GTK_ICON_PAINTABLE (paintable); |
4028 | |
4029 | return icon->desired_size; |
4030 | } |
4031 | |
4032 | static int |
4033 | icon_paintable_get_intrinsic_height (GdkPaintable *paintable) |
4034 | { |
4035 | GtkIconPaintable *icon = GTK_ICON_PAINTABLE (paintable); |
4036 | |
4037 | return icon->desired_size; |
4038 | } |
4039 | |
4040 | static void |
4041 | icon_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 | |
4049 | static void |
4050 | icon_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 | */ |
4068 | GtkIconPaintable * |
4069 | gtk_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 | */ |
4114 | GtkIconPaintable * |
4115 | gtk_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 | */ |
4183 | GdkDisplay * |
4184 | gtk_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 | |