rofi 2.1.0
rofi-icon-fetcher.c
Go to the documentation of this file.
1/*
2 * rofi
3 *
4 * MIT/X11 License
5 * Copyright © 2013-2023 Qball Cow <qball@gmpclient.org>
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining
8 * a copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sublicense, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 *
26 */
27
29#define G_LOG_DOMAIN "Helpers.IconFetcher"
30
31#include "config.h"
32#include <stdlib.h>
33
34#include "helper.h"
35#include "rofi-icon-fetcher.h"
36#include "rofi-types.h"
37#include "settings.h"
38#include <cairo.h>
39#include <pango/pangocairo.h>
40
41#include "display.h"
42#include "keyb.h"
43#include "view.h"
44
45#include "nkutils-enum.h"
46#include "nkutils-xdg-theme.h"
47
48#include <stdint.h>
49
50#include "helper.h"
51#include <gdk-pixbuf/gdk-pixbuf.h>
52
54#define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry"
56#define THUMBNAILER_EXTENSION ".thumbnailer"
57
58typedef struct {
59 // Context for icon-themes.
60 NkXdgThemeContext *xdg_context;
61
62 // On name.
63 GHashTable *icon_cache;
64 // On uid.
65 GHashTable *icon_cache_uid;
66
67 // list extensions
69 uint32_t last_uid;
70
71 // thumbnailers per mime-types hashmap
72 GHashTable *thumbnailers;
74
75typedef struct {
76 char *name;
77 GList *sizes;
79
80typedef struct {
82
83 GCond *cond;
84 GMutex *mutex;
85 unsigned int *acount;
86
87 uint32_t uid;
88 int wsize;
89 int hsize;
90 guint scale;
91 cairo_surface_t *surface;
92 gboolean query_done;
93 gboolean query_started;
94
97
98// Free method.
99static void rofi_icon_fetch_entry_free(gpointer data);
105
106static void rofi_icon_fetcher_load_thumbnailers(const gchar *path) {
107 gchar *thumb_path = g_build_filename(path, "thumbnailers", NULL);
108
109 GDir *dir = g_dir_open(thumb_path, 0, NULL);
110
111 if (!dir) {
112 g_free(thumb_path);
113 return;
114 }
115
116 const gchar *dirent;
117
118 while ((dirent = g_dir_read_name(dir))) {
119 if (!g_str_has_suffix(dirent, THUMBNAILER_EXTENSION))
120 continue;
121
122 gchar *filename = g_build_filename(thumb_path, dirent, NULL);
123 GKeyFile *key_file = g_key_file_new();
124 GError *error = NULL;
125
126 if (!g_key_file_load_from_file(key_file, filename, 0, &error)) {
127 g_warning("Error loading thumbnailer %s: %s", filename, error->message);
128 g_error_free(error);
129 } else {
130 gchar *command = g_key_file_get_string(key_file, THUMBNAILER_ENTRY_GROUP,
131 "Exec", NULL);
132 gchar **mime_types = g_key_file_get_string_list(
133 key_file, THUMBNAILER_ENTRY_GROUP, "MimeType", NULL, NULL);
134
135 if (mime_types && command) {
136 guint i;
137 for (i = 0; mime_types[i] != NULL; i++) {
138 if (!g_hash_table_lookup(rofi_icon_fetcher_data->thumbnailers,
139 mime_types[i])) {
140 g_info("Loading thumbnailer %s for mimetype %s", filename,
141 mime_types[i]);
142 g_hash_table_insert(rofi_icon_fetcher_data->thumbnailers,
143 g_strdup(mime_types[i]), g_strdup(command));
144 }
145 }
146 }
147
148 if (mime_types)
149 g_strfreev(mime_types);
150 if (command)
151 g_free(command);
152 }
153
154 g_key_file_free(key_file);
155 g_free(filename);
156 }
157
158 g_dir_close(dir);
159 g_free(thumb_path);
160}
161
162static gchar **setup_thumbnailer_command(const gchar *command,
163 const gchar *filename,
164 const gchar *encoded_uri,
165 const gchar *output_path, int size) {
166 gchar **command_parts = g_strsplit(command, " ", 0);
167 guint command_parts_count = g_strv_length(command_parts);
168
169 gchar **command_args = NULL;
170
171 if (command_parts) {
172 command_args = g_malloc0(sizeof(gchar *) * (command_parts_count + 3 + 1));
173
174 // set process niceness value to 19 (low priority)
175 guint current_index = 0;
176
177 command_args[current_index++] = g_strdup("nice");
178 command_args[current_index++] = g_strdup("-n");
179 command_args[current_index++] = g_strdup("19");
180
181 // add executable and arguments of the thumbnailer to the list
182 guint i;
183 for (i = 0; command_parts[i] != NULL; i++) {
184 if (strcmp(command_parts[i], "%i") == 0) {
185 command_args[current_index++] = g_strdup(filename);
186 } else if (strcmp(command_parts[i], "%u") == 0) {
187 command_args[current_index++] = g_strdup(encoded_uri);
188 } else if (strcmp(command_parts[i], "%o") == 0) {
189 command_args[current_index++] = g_strdup(output_path);
190 } else if (strcmp(command_parts[i], "%s") == 0) {
191 command_args[current_index++] = g_strdup_printf("%d", size);
192 } else {
193 command_args[current_index++] = g_strdup(command_parts[i]);
194 }
195 }
196
197 command_args[current_index++] = NULL;
198
199 g_strfreev(command_parts);
200 }
201
202 return command_args;
203}
204
205static gboolean exec_thumbnailer_command(gchar **command_args) {
206 // launch and wait thumbnailers process
207 gint wait_status;
208 GError *error = NULL;
209
210 gboolean spawned = g_spawn_sync(NULL, command_args, NULL,
211 G_SPAWN_DEFAULT | G_SPAWN_SEARCH_PATH, NULL,
212 NULL, NULL, NULL, &wait_status, &error);
213
214 if (spawned) {
215 return g_spawn_check_wait_status(wait_status, NULL);
216 } else {
217 g_warning("Error calling thumbnailer: %s", error->message);
218 g_error_free(error);
219
220 return FALSE;
221 }
222}
223
224static gboolean rofi_icon_fetcher_create_thumbnail(const gchar *mime_type,
225 const gchar *filename,
226 const gchar *encoded_uri,
227 const gchar *output_path,
228 int size) {
229 gboolean thumbnail_created = FALSE;
230
231 gchar *command =
232 g_hash_table_lookup(rofi_icon_fetcher_data->thumbnailers, mime_type);
233
234 if (!command) {
235 return thumbnail_created;
236 }
237
238 // split command string to isolate arguments and expand them in a list
239 gchar **command_args = setup_thumbnailer_command(
240 command, filename, encoded_uri, output_path, size);
241
242 if (command_args) {
243 thumbnail_created = exec_thumbnailer_command(command_args);
244 g_strfreev(command_args);
245 }
246
247 return thumbnail_created;
248}
249
251 IconFetcherEntry *entry = (IconFetcherEntry *)data;
252 // Mark it in a way it should be re-fetched on next query?
253 entry->query_started = FALSE;
254}
255
256static void rofi_icon_fetch_entry_free(gpointer data) {
258
259 // Free name/key.
260 g_free(entry->name);
261
262 for (GList *iter = g_list_first(entry->sizes); iter;
263 iter = g_list_next(iter)) {
264 IconFetcherEntry *sentry = (IconFetcherEntry *)(iter->data);
265
266 cairo_surface_destroy(sentry->surface);
267 g_free(sentry);
268 }
269
270 g_list_free(entry->sizes);
271 g_free(entry);
272}
273
275 g_assert(rofi_icon_fetcher_data == NULL);
276
277 icon_fallback_themes = g_strsplit(config.fallback_icon_themes, ",", 0);
278
279 const char *themes[2] = {config.icon_theme, NULL};
280
281 rofi_icon_fetcher_data = g_malloc0(sizeof(IconFetcher));
282
283 rofi_icon_fetcher_data->xdg_context = nk_xdg_theme_context_new(
284 (const gchar *const *)icon_fallback_themes, NULL);
285 nk_xdg_theme_preload_themes_icon(rofi_icon_fetcher_data->xdg_context, themes);
286
287 rofi_icon_fetcher_data->icon_cache_uid =
288 g_hash_table_new(g_direct_hash, g_direct_equal);
289 rofi_icon_fetcher_data->icon_cache = g_hash_table_new_full(
290 g_str_hash, g_str_equal, NULL, rofi_icon_fetch_entry_free);
291
292 GSList *l = gdk_pixbuf_get_formats();
293 for (GSList *li = l; li != NULL; li = g_slist_next(li)) {
294 gchar **exts =
295 gdk_pixbuf_format_get_extensions((GdkPixbufFormat *)li->data);
296
297 for (unsigned int i = 0; exts && exts[i]; i++) {
298 rofi_icon_fetcher_data->supported_extensions =
299 g_list_append(rofi_icon_fetcher_data->supported_extensions, exts[i]);
300 g_info("Add image extension: %s", exts[i]);
301 exts[i] = NULL;
302 }
303
304 g_free(exts);
305 }
306 g_slist_free(l);
307
308 // load available thumbnailers from system dirs and user dir
309 rofi_icon_fetcher_data->thumbnailers = g_hash_table_new_full(
310 g_str_hash, g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_free);
311
312 const gchar *const *system_data_dirs = g_get_system_data_dirs();
313 const gchar *user_data_dir = g_get_user_data_dir();
314
316
317 guint i;
318 for (i = 0; system_data_dirs[i] != NULL; i++) {
319 rofi_icon_fetcher_load_thumbnailers(system_data_dirs[i]);
320 }
321}
322
323static void free_wrapper(gpointer data, G_GNUC_UNUSED gpointer user_data) {
324 g_free(data);
325}
326
328 if (rofi_icon_fetcher_data == NULL) {
329 return;
330 }
331
332 g_hash_table_unref(rofi_icon_fetcher_data->thumbnailers);
333
334 nk_xdg_theme_context_free(rofi_icon_fetcher_data->xdg_context);
335
336 g_hash_table_unref(rofi_icon_fetcher_data->icon_cache_uid);
337 g_hash_table_unref(rofi_icon_fetcher_data->icon_cache);
338
339 g_list_foreach(rofi_icon_fetcher_data->supported_extensions, free_wrapper,
340 NULL);
341 g_list_free(rofi_icon_fetcher_data->supported_extensions);
343
344 // Context is free'ed above, so it should be save to deallocate.
345 g_strfreev(icon_fallback_themes);
346}
347
348/*
349 * _rofi_icon_fetcher_get_icon_surface and alpha_mult
350 * are inspired by gdk_cairo_set_source_pixbuf
351 * GDK is:
352 * Copyright (C) 2011-2018 Red Hat, Inc.
353 */
354#if G_BYTE_ORDER == G_LITTLE_ENDIAN
356#define RED_BYTE 2
358#define GREEN_BYTE 1
360#define BLUE_BYTE 0
362#define ALPHA_BYTE 3
363#else
365#define RED_BYTE 1
367#define GREEN_BYTE 2
369#define BLUE_BYTE 3
371#define ALPHA_BYTE 0
372#endif
373
374static inline guchar alpha_mult(guchar c, guchar a) {
375 guint16 t;
376 switch (a) {
377 case 0xff:
378 return c;
379 case 0x00:
380 return 0x00;
381 default:
382 t = c * a + 0x7f;
383 return ((t >> 8) + t) >> 8;
384 }
385}
386
387static cairo_surface_t *
389 gint width, height;
390 const guchar *pixels;
391 gint stride;
392 gboolean alpha;
393
394 if (pixbuf == NULL) {
395 return NULL;
396 }
397
398 width = gdk_pixbuf_get_width(pixbuf);
399 height = gdk_pixbuf_get_height(pixbuf);
400 pixels = gdk_pixbuf_read_pixels(pixbuf);
401 stride = gdk_pixbuf_get_rowstride(pixbuf);
402 alpha = gdk_pixbuf_get_has_alpha(pixbuf);
403
404 cairo_surface_t *surface = NULL;
405
406 gint cstride;
407 guint lo, o;
408 guchar a = 0xff;
409 const guchar *pixels_end, *line;
410 guchar *cpixels;
411
412 pixels_end = pixels + height * stride;
413 o = alpha ? 4 : 3;
414 lo = o * width;
415
416 surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
417 cpixels = cairo_image_surface_get_data(surface);
418 cstride = cairo_image_surface_get_stride(surface);
419
420 cairo_surface_flush(surface);
421 while (pixels < pixels_end) {
422 line = pixels;
423 const guchar *line_end = line + lo;
424 guchar *cline = cpixels;
425
426 while (line < line_end) {
427 if (alpha) {
428 a = line[3];
429 }
430 cline[RED_BYTE] = alpha_mult(line[0], a);
431 cline[GREEN_BYTE] = alpha_mult(line[1], a);
432 cline[BLUE_BYTE] = alpha_mult(line[2], a);
433 cline[ALPHA_BYTE] = a;
434
435 line += o;
436 cline += 4;
437 }
438
439 pixels += stride;
440 cpixels += cstride;
441 }
442 cairo_surface_mark_dirty(surface);
443 cairo_surface_flush(surface);
444
445 return surface;
446}
447
448gboolean rofi_icon_fetcher_file_is_image(const char *const path) {
449 if (path == NULL) {
450 return FALSE;
451 }
452 const char *suf = strrchr(path, '.');
453 if (suf == NULL) {
454 return FALSE;
455 }
456 suf++;
457
458 for (GList *iter = rofi_icon_fetcher_data->supported_extensions; iter != NULL;
459 iter = g_list_next(iter)) {
460 if (g_ascii_strcasecmp(iter->data, suf) == 0) {
461 return TRUE;
462 }
463 }
464 return FALSE;
465}
466
467// build thumbnail's path using md5 hash of an entry name
468static gchar *rofi_icon_fetcher_get_thumbnail(gchar *name, int requested_size,
469 int *thumb_size) {
470 // calc entry_name md5 hash
471 GChecksum *checksum = g_checksum_new(G_CHECKSUM_MD5);
472 g_checksum_update(checksum, (guchar *)name, -1);
473 const gchar *md5_hex = g_checksum_get_string(checksum);
474
475 // determine thumbnail folder based on the request size
476 const gchar *cache_dir = g_get_user_cache_dir();
477 gchar *thumb_dir;
478 gchar *thumb_path;
479
480 if (requested_size <= 128) {
481 *thumb_size = 128;
482 thumb_dir = g_strconcat(cache_dir, "/thumbnails/normal/", NULL);
483 thumb_path =
484 g_strconcat(cache_dir, "/thumbnails/normal/", md5_hex, ".png", NULL);
485 } else if (requested_size <= 256) {
486 *thumb_size = 256;
487 thumb_dir = g_strconcat(cache_dir, "/thumbnails/large/", NULL);
488 thumb_path =
489 g_strconcat(cache_dir, "/thumbnails/large/", md5_hex, ".png", NULL);
490 } else if (requested_size <= 512) {
491 *thumb_size = 512;
492 thumb_dir = g_strconcat(cache_dir, "/thumbnails/x-large/", NULL);
493 thumb_path =
494 g_strconcat(cache_dir, "/thumbnails/x-large/", md5_hex, ".png", NULL);
495 } else {
496 *thumb_size = 1024;
497 thumb_dir = g_strconcat(cache_dir, "/thumbnails/xx-large/", NULL);
498 thumb_path =
499 g_strconcat(cache_dir, "/thumbnails/xx-large/", md5_hex, ".png", NULL);
500 }
501
502 // create thumbnail directory if it does not exist
503 g_mkdir_with_parents(thumb_dir, 0700);
504
505 g_free(thumb_dir);
506 g_checksum_free(checksum);
507
508 return thumb_path;
509}
510
511// retrieves icon key from a .desktop file
512static gchar *rofi_icon_fetcher_get_desktop_icon(const gchar *file_path) {
513 GKeyFile *kf = g_key_file_new();
514 GError *key_error = NULL;
515 gchar *icon_key = NULL;
516
517 gboolean res = g_key_file_load_from_file(kf, file_path, 0, &key_error);
518
519 if (res) {
520 icon_key = g_key_file_get_string(kf, "Desktop Entry", "Icon", NULL);
521 } else {
522 g_debug("Failed to parse desktop file %s because: %s.", file_path,
523 key_error->message);
524
525 g_error_free(key_error);
526 }
527
528 g_key_file_free(kf);
529
530 return icon_key;
531}
532
534 G_GNUC_UNUSED gpointer user_data) {
535 g_debug("starting up icon fetching thread.");
536 // as long as dr->icon is updated atomicly.. (is a pointer write atomic?)
537 // this should be fine running in another thread.
538 IconFetcherEntry *sentry = (IconFetcherEntry *)sdata;
539 const gchar *themes[] = {config.icon_theme, NULL};
540
541 const gchar *icon_path;
542 gchar *icon_path_ = NULL;
543
544 if (g_str_has_prefix(sentry->entry->name, "thumbnail://")) {
545 // remove uri thumbnail prefix from entry name
546 gchar *entry_name = &sentry->entry->name[12];
547
548 if (strcmp(entry_name, "") == 0) {
549 sentry->query_done = TRUE;
551 return;
552 }
553
554 // use custom user command to generate the thumbnail
555 if (config.preview_cmd != NULL) {
556 int requested_size = MAX(sentry->wsize, sentry->hsize);
557 int thumb_size;
558
559 icon_path = icon_path_ = rofi_icon_fetcher_get_thumbnail(
560 entry_name, requested_size, &thumb_size);
561
562 if (!g_file_test(icon_path, G_FILE_TEST_EXISTS)) {
563 char **command_args = NULL;
564 int argsv = 0;
565 gchar *size_str = g_strdup_printf("%d", thumb_size);
566
567 helper_parse_setup(config.preview_cmd, &command_args, &argsv, "{input}",
568 entry_name, "{output}", icon_path_, "{size}",
569 size_str, NULL);
570
571 g_free(size_str);
572
573 if (command_args) {
574 exec_thumbnailer_command(command_args);
575 g_strfreev(command_args);
576 }
577 }
578 } else if (g_path_is_absolute(entry_name)) {
579 // if the entry name is an absolute path try to fetch its thumbnail
580 if (g_str_has_suffix(entry_name, ".desktop")) {
581 // if the entry is a .desktop file try to read its icon key
582 gchar *icon_key = rofi_icon_fetcher_get_desktop_icon(entry_name);
583
584 if (icon_key == NULL || strlen(icon_key) == 0) {
585 // no icon in .desktop file, fallback on mimetype icon (text/plain)
586 icon_path = icon_path_ = nk_xdg_theme_get_icon(
587 rofi_icon_fetcher_data->xdg_context, themes, NULL, "text-plain",
588 MIN(sentry->wsize, sentry->hsize), 1, TRUE);
589
590 g_free(icon_key);
591 } else if (g_path_is_absolute(icon_key)) {
592 // icon in .desktop file is an absolute path to an image
593 icon_path = icon_path_ = icon_key;
594 } else {
595 // icon in .desktop file is a standard icon name
596 icon_path = icon_path_ = nk_xdg_theme_get_icon(
597 rofi_icon_fetcher_data->xdg_context, themes, NULL, icon_key,
598 MIN(sentry->wsize, sentry->hsize), 1, TRUE);
599
600 g_free(icon_key);
601 }
602 } else {
603 // build encoded uri string from absolute file path
604 gchar *encoded_uri = g_filename_to_uri(entry_name, NULL, NULL);
605 int requested_size = MAX(sentry->wsize, sentry->hsize);
606 int thumb_size;
607
608 // look for file thumbnail in appropriate folder based on requested size
609 icon_path = icon_path_ = rofi_icon_fetcher_get_thumbnail(
610 encoded_uri, requested_size, &thumb_size);
611
612 if (!g_file_test(icon_path, G_FILE_TEST_EXISTS)) {
613 // try to generate thumbnail
614 char *content_type = g_content_type_guess(entry_name, NULL, 0, NULL);
615 char *mime_type = g_content_type_get_mime_type(content_type);
616
617 if (mime_type) {
618 gboolean created = rofi_icon_fetcher_create_thumbnail(
619 mime_type, entry_name, encoded_uri, icon_path_, thumb_size);
620
621 if (!created) {
622 // replace forward slashes with minus sign to get the icon's name
623 int index = 0;
624
625 while (mime_type[index]) {
626 if (mime_type[index] == '/')
627 mime_type[index] = '-';
628 index++;
629 }
630
631 g_free(icon_path_);
632
633 // try to fetch the mime-type icon
634 icon_path = icon_path_ = nk_xdg_theme_get_icon(
635 rofi_icon_fetcher_data->xdg_context, themes, NULL, mime_type,
636 MIN(sentry->wsize, sentry->hsize), 1, TRUE);
637 }
638
639 g_free(mime_type);
640 g_free(content_type);
641 }
642 }
643
644 g_free(encoded_uri);
645 }
646 }
647
648 // no suitable icon or thumbnail was found
649 if (icon_path_ == NULL || !g_file_test(icon_path, G_FILE_TEST_EXISTS)) {
650 sentry->query_done = TRUE;
652 return;
653 }
654 } else if (g_path_is_absolute(sentry->entry->name)) {
655 icon_path = sentry->entry->name;
656 } else if (g_str_has_prefix(sentry->entry->name, "<span")) {
657 cairo_surface_t *surface = cairo_image_surface_create(
658 CAIRO_FORMAT_ARGB32, sentry->wsize, sentry->hsize);
659 cairo_t *cr = cairo_create(surface);
660 PangoLayout *layout = pango_cairo_create_layout(cr);
661 pango_layout_set_markup(layout, sentry->entry->name, -1);
662
663 int width, height;
664 pango_layout_get_size(layout, &width, &height);
665 double ws = sentry->wsize / ((double)width / PANGO_SCALE);
666 double wh = sentry->hsize / ((double)height / PANGO_SCALE);
667 double scale = MIN(ws, wh);
668
669 cairo_move_to(
670 cr, (sentry->wsize - ((double)width / PANGO_SCALE) * scale) / 2.0,
671 (sentry->hsize - ((double)height / PANGO_SCALE) * scale) / 2.0);
672 cairo_scale(cr, scale, scale);
673 pango_cairo_update_layout(cr, layout);
674 pango_layout_get_size(layout, &width, &height);
675 pango_cairo_show_layout(cr, layout);
676 g_object_unref(layout);
677 cairo_destroy(cr);
678 sentry->surface = surface;
679 sentry->query_done = TRUE;
681 return;
682
683 } else {
684 icon_path = icon_path_ = nk_xdg_theme_get_icon(
685 rofi_icon_fetcher_data->xdg_context, themes, NULL, sentry->entry->name,
686 MIN(sentry->wsize, sentry->hsize), sentry->scale, TRUE);
687 if (icon_path_ == NULL) {
688 g_debug("failed to get icon %s(%dx%d): n/a", sentry->entry->name,
689 sentry->wsize, sentry->hsize);
690
691 const char *ext = g_strrstr(sentry->entry->name, ".");
692 if (ext) {
693 const char *exts2[2] = {ext, NULL};
694 icon_path = icon_path_ =
695 helper_get_theme_path(sentry->entry->name, exts2, NULL);
696 }
697 if (icon_path_ == NULL) {
698 sentry->query_done = TRUE;
700 return;
701 }
702 } else {
703 g_debug("found icon %s(%dx%d): %s", sentry->entry->name, sentry->wsize,
704 sentry->hsize, icon_path);
705 }
706 }
707 cairo_surface_t *icon_surf = NULL;
708
709#if 0 // unsure why added in past?
710 const char *suf = strrchr(icon_path, '.');
711 if (suf == NULL) {
712 sentry->query_done = TRUE;
713 g_free(icon_path_);
715 return;
716 }
717#endif
718
719 int width = sentry->wsize, height = sentry->hsize;
720 if (width > 0)
721 width *= sentry->scale;
722 if (height > 0)
723 height *= sentry->scale;
724
725 GError *error = NULL;
726 GdkPixbuf *pb =
727 gdk_pixbuf_new_from_file_at_scale(icon_path, width, height, TRUE, &error);
728
729 /*
730 * The GIF codec throws GDK_PIXBUF_ERROR_INCOMPLETE_ANIMATION if it's closed
731 * without decoding all the frames. Since gdk_pixbuf_new_from_file_at_scale
732 * only decodes the first frame, this specific error needs to be ignored.
733 */
734 if (error != NULL && g_error_matches(error, GDK_PIXBUF_ERROR,
735 GDK_PIXBUF_ERROR_INCOMPLETE_ANIMATION)) {
736 g_clear_error(&error);
737 }
738
739 if (error != NULL) {
740 g_warning("Failed to load image: |%s| %d %d %s (%p)", icon_path,
741 sentry->wsize, sentry->hsize, error->message, (void *)pb);
742 g_error_free(error);
743 if (pb) {
744 g_object_unref(pb);
745 }
746 } else {
748 g_object_unref(pb);
749 }
750
751 sentry->surface = icon_surf;
752 g_free(icon_path_);
753 sentry->query_done = TRUE;
755}
756
757uint32_t rofi_icon_fetcher_query_advanced(const char *name, const int wsize,
758 const int hsize) {
759 g_debug("Query: %s(%dx%d)", name, wsize, hsize);
760 IconFetcherNameEntry *entry =
761 g_hash_table_lookup(rofi_icon_fetcher_data->icon_cache, name);
762 if (entry == NULL) {
763 entry = g_new0(IconFetcherNameEntry, 1);
764 entry->name = g_strdup(name);
765 g_hash_table_insert(rofi_icon_fetcher_data->icon_cache, entry->name, entry);
766 }
767 IconFetcherEntry *sentry;
768 const guint scale = display_scale();
769 for (GList *iter = g_list_first(entry->sizes); iter;
770 iter = g_list_next(iter)) {
771 sentry = iter->data;
772 if (sentry->wsize == wsize && sentry->hsize == hsize &&
773 sentry->scale == scale) {
774 if (!sentry->query_started) {
775 g_thread_pool_push(tpool, sentry, NULL);
776 }
777 return sentry->uid;
778 }
779 }
780
781 // Not found.
782 sentry = g_new0(IconFetcherEntry, 1);
783 sentry->uid = ++(rofi_icon_fetcher_data->last_uid);
784 sentry->wsize = wsize;
785 sentry->hsize = hsize;
786 sentry->scale = scale;
787 sentry->entry = entry;
788 sentry->query_done = FALSE;
789 sentry->query_started = TRUE;
790 sentry->surface = NULL;
791
792 entry->sizes = g_list_prepend(entry->sizes, sentry);
793 g_hash_table_insert(rofi_icon_fetcher_data->icon_cache_uid,
794 GINT_TO_POINTER(sentry->uid), sentry);
795
796 // Push into fetching queue.
799 sentry->state.priority = G_PRIORITY_LOW;
800 g_thread_pool_push(tpool, sentry, NULL);
801
802 return sentry->uid;
803}
804uint32_t rofi_icon_fetcher_query(const char *name, const int size) {
805 g_debug("Query: %s(%d)", name, size);
806 IconFetcherNameEntry *entry =
807 g_hash_table_lookup(rofi_icon_fetcher_data->icon_cache, name);
808 if (entry == NULL) {
809 entry = g_new0(IconFetcherNameEntry, 1);
810 entry->name = g_strdup(name);
811 g_hash_table_insert(rofi_icon_fetcher_data->icon_cache, entry->name, entry);
812 }
813 IconFetcherEntry *sentry;
814 const guint scale = display_scale();
815 for (GList *iter = g_list_first(entry->sizes); iter;
816 iter = g_list_next(iter)) {
817 sentry = iter->data;
818 if (sentry->wsize == size && sentry->hsize == size &&
819 sentry->scale == scale) {
820 if (!sentry->query_started) {
821 g_thread_pool_push(tpool, sentry, NULL);
822 }
823 return sentry->uid;
824 }
825 }
826
827 // Not found.
828 sentry = g_new0(IconFetcherEntry, 1);
829 sentry->uid = ++(rofi_icon_fetcher_data->last_uid);
830 sentry->wsize = size;
831 sentry->hsize = size;
832 sentry->scale = scale;
833 sentry->entry = entry;
834 sentry->query_done = FALSE;
835 sentry->query_started = TRUE;
836 sentry->surface = NULL;
837
838 entry->sizes = g_list_prepend(entry->sizes, sentry);
839 g_hash_table_insert(rofi_icon_fetcher_data->icon_cache_uid,
840 GINT_TO_POINTER(sentry->uid), sentry);
841
842 // Push into fetching queue.
845 sentry->state.priority = G_PRIORITY_LOW;
846 g_thread_pool_push(tpool, sentry, NULL);
847
848 return sentry->uid;
849}
850
851cairo_surface_t *rofi_icon_fetcher_get(const uint32_t uid) {
852 IconFetcherEntry *sentry = g_hash_table_lookup(
853 rofi_icon_fetcher_data->icon_cache_uid, GINT_TO_POINTER(uid));
854 if (sentry) {
855 return sentry->surface;
856 }
857 g_warning("Querying an non-existing uid");
858 return NULL;
859}
860
861gboolean rofi_icon_fetcher_get_ex(const uint32_t uid,
862 cairo_surface_t **surface) {
863 IconFetcherEntry *sentry = g_hash_table_lookup(
864 rofi_icon_fetcher_data->icon_cache_uid, GINT_TO_POINTER(uid));
865 *surface = NULL;
866 if (sentry) {
867 *surface = sentry->surface;
868 return sentry->query_done;
869 }
870 g_warning("Querying an non-existing uid");
871 return FALSE;
872}
guint display_scale(void)
Definition display.c:78
int helper_parse_setup(char *string, char ***output, int *length,...)
Definition helper.c:102
uint32_t rofi_icon_fetcher_query_advanced(const char *name, const int wsize, const int hsize)
gboolean rofi_icon_fetcher_file_is_image(const char *const path)
cairo_surface_t * rofi_icon_fetcher_get(const uint32_t uid)
void rofi_icon_fetcher_destroy(void)
gboolean rofi_icon_fetcher_get_ex(const uint32_t uid, cairo_surface_t **surface)
uint32_t rofi_icon_fetcher_query(const char *name, const int size)
void rofi_icon_fetcher_init(void)
const char * cache_dir
Definition rofi.c:86
void rofi_view_reload(void)
Definition view.c:2221
char * helper_get_theme_path(const char *file, const char **ext, const char *parent_file)
Definition helper.c:1590
static void rofi_icon_fetcher_load_thumbnailers(const gchar *path)
static void rofi_icon_fetch_entry_free(gpointer data)
static void free_wrapper(gpointer data, G_GNUC_UNUSED gpointer user_data)
IconFetcher * rofi_icon_fetcher_data
static gboolean exec_thumbnailer_command(gchar **command_args)
#define ALPHA_BYTE
#define BLUE_BYTE
static guchar alpha_mult(guchar c, guchar a)
#define GREEN_BYTE
static gboolean rofi_icon_fetcher_create_thumbnail(const gchar *mime_type, const gchar *filename, const gchar *encoded_uri, const gchar *output_path, int size)
static gchar ** setup_thumbnailer_command(const gchar *command, const gchar *filename, const gchar *encoded_uri, const gchar *output_path, int size)
static cairo_surface_t * rofi_icon_fetcher_get_surface_from_pixbuf(GdkPixbuf *pixbuf)
static gchar * rofi_icon_fetcher_get_desktop_icon(const gchar *file_path)
static void rofi_icon_fetch_thread_pool_entry_remove(gpointer data)
static void rofi_icon_fetcher_worker(thread_state *sdata, G_GNUC_UNUSED gpointer user_data)
static gchar * rofi_icon_fetcher_get_thumbnail(gchar *name, int requested_size, int *thumb_size)
char ** icon_fallback_themes
#define RED_BYTE
#define THUMBNAILER_EXTENSION
#define THUMBNAILER_ENTRY_GROUP
struct _thread_state thread_state
Settings config
IconFetcherNameEntry * entry
unsigned int * acount
cairo_surface_t * surface
NkXdgThemeContext * xdg_context
GHashTable * icon_cache_uid
GList * supported_extensions
GHashTable * thumbnailers
GHashTable * icon_cache
void(* callback)(struct _thread_state *t, gpointer data)
Definition rofi-types.h:368
void(* free)(void *)
Definition rofi-types.h:369
GThreadPool * tpool
Definition view.c:70