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