29#define G_LOG_DOMAIN "Helpers.IconFetcher"
33#include <xcb/xproto.h>
40#include <pango/pangocairo.h>
46#include "nkutils-enum.h"
47#include "nkutils-xdg-theme.h"
52#include <gdk-pixbuf/gdk-pixbuf.h>
55#define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry"
57#define THUMBNAILER_EXTENSION ".thumbnailer"
106 gchar *thumb_path = g_build_filename(path,
"thumbnailers", NULL);
108 GDir *dir = g_dir_open(thumb_path, 0, NULL);
117 while ((dirent = g_dir_read_name(dir))) {
121 gchar *filename = g_build_filename(thumb_path, dirent, NULL);
122 GKeyFile *key_file = g_key_file_new();
123 GError *error = NULL;
125 if (!g_key_file_load_from_file(key_file, filename, 0, &error)) {
126 g_warning(
"Error loading thumbnailer %s: %s", filename, error->message);
131 gchar **mime_types = g_key_file_get_string_list(
134 if (mime_types && command) {
136 for (i = 0; mime_types[i] != NULL; i++) {
139 g_info(
"Loading thumbnailer %s for mimetype %s", filename,
142 g_strdup(mime_types[i]), g_strdup(command));
148 g_strfreev(mime_types);
153 g_key_file_free(key_file);
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);
168 gchar **command_args = NULL;
171 command_args = g_malloc0(
sizeof(gchar *) * (command_parts_count + 3 + 1));
174 guint current_index = 0;
176 command_args[current_index++] = g_strdup(
"nice");
177 command_args[current_index++] = g_strdup(
"-n");
178 command_args[current_index++] = g_strdup(
"19");
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);
192 command_args[current_index++] = g_strdup(command_parts[i]);
196 command_args[current_index++] = NULL;
198 g_strfreev(command_parts);
207 GError *error = NULL;
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);
214 return g_spawn_check_wait_status(wait_status, NULL);
216 g_warning(
"Error calling thumbnailer: %s", error->message);
224 const gchar *filename,
225 const gchar *encoded_uri,
226 const gchar *output_path,
228 gboolean thumbnail_created = FALSE;
234 return thumbnail_created;
239 command, filename, encoded_uri, output_path, size);
243 g_strfreev(command_args);
246 return thumbnail_created;
261 for (GList *iter = g_list_first(entry->
sizes); iter;
262 iter = g_list_next(iter)) {
265 cairo_surface_destroy(sentry->
surface);
269 g_list_free(entry->
sizes);
276 static const gchar *
const icon_fallback_themes[] = {
"Adwaita",
"gnome", NULL};
282 nk_xdg_theme_context_new(icon_fallback_themes, NULL);
286 g_hash_table_new(g_direct_hash, g_direct_equal);
290 GSList *l = gdk_pixbuf_get_formats();
291 for (GSList *li = l; li != NULL; li = g_slist_next(li)) {
293 gdk_pixbuf_format_get_extensions((GdkPixbufFormat *)li->data);
295 for (
unsigned int i = 0; exts && exts[i]; i++) {
298 g_info(
"Add image extension: %s", exts[i]);
308 g_str_hash, g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_free);
310 const gchar *
const *system_data_dirs = g_get_system_data_dirs();
311 const gchar *user_data_dir = g_get_user_data_dir();
316 for (i = 0; system_data_dirs[i] != NULL; i++) {
321static void free_wrapper(gpointer data, G_GNUC_UNUSED gpointer user_data) {
349#if G_BYTE_ORDER == G_LITTLE_ENDIAN
378 return ((t >> 8) + t) >> 8;
382static cairo_surface_t *
385 const guchar *pixels;
389 if (pixbuf == NULL) {
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);
399 cairo_surface_t *surface = NULL;
404 const guchar *pixels_end, *line;
407 pixels_end = pixels + height * stride;
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);
415 cairo_surface_flush(surface);
416 while (pixels < pixels_end) {
418 const guchar *line_end = line + lo;
419 guchar *cline = cpixels;
421 while (line < line_end) {
437 cairo_surface_mark_dirty(surface);
438 cairo_surface_flush(surface);
447 const char *suf = strrchr(path,
'.');
454 iter = g_list_next(iter)) {
455 if (g_ascii_strcasecmp(iter->data, suf) == 0) {
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);
471 const gchar *
cache_dir = g_get_user_cache_dir();
475 if (requested_size <= 128) {
477 thumb_dir = g_strconcat(
cache_dir,
"/thumbnails/normal/", NULL);
479 g_strconcat(
cache_dir,
"/thumbnails/normal/", md5_hex,
".png", NULL);
480 }
else if (requested_size <= 256) {
482 thumb_dir = g_strconcat(
cache_dir,
"/thumbnails/large/", NULL);
484 g_strconcat(
cache_dir,
"/thumbnails/large/", md5_hex,
".png", NULL);
485 }
else if (requested_size <= 512) {
487 thumb_dir = g_strconcat(
cache_dir,
"/thumbnails/x-large/", NULL);
489 g_strconcat(
cache_dir,
"/thumbnails/x-large/", md5_hex,
".png", NULL);
492 thumb_dir = g_strconcat(
cache_dir,
"/thumbnails/xx-large/", NULL);
494 g_strconcat(
cache_dir,
"/thumbnails/xx-large/", md5_hex,
".png", NULL);
498 g_mkdir_with_parents(thumb_dir, 0700);
501 g_checksum_free(checksum);
508 GKeyFile *kf = g_key_file_new();
509 GError *key_error = NULL;
510 gchar *icon_key = NULL;
512 gboolean res = g_key_file_load_from_file(kf, file_path, 0, &key_error);
515 icon_key = g_key_file_get_string(kf,
"Desktop Entry",
"Icon", NULL);
517 g_debug(
"Failed to parse desktop file %s because: %s.", file_path,
520 g_error_free(key_error);
529 G_GNUC_UNUSED gpointer user_data) {
530 g_debug(
"starting up icon fetching thread.");
536 const gchar *icon_path;
537 gchar *icon_path_ = NULL;
539 if (g_str_has_prefix(sentry->
entry->
name,
"thumbnail://")) {
541 gchar *entry_name = &sentry->
entry->
name[12];
543 if (strcmp(entry_name,
"") == 0) {
551 int requested_size = MAX(sentry->
wsize, sentry->
hsize);
555 entry_name, requested_size, &thumb_size);
557 if (!g_file_test(icon_path, G_FILE_TEST_EXISTS)) {
558 char **command_args = NULL;
560 gchar *size_str = g_strdup_printf(
"%d", thumb_size);
563 entry_name,
"{output}", icon_path_,
"{size}",
570 g_strfreev(command_args);
573 }
else if (g_path_is_absolute(entry_name)) {
575 if (g_str_has_suffix(entry_name,
".desktop")) {
579 if (icon_key == NULL || strlen(icon_key) == 0) {
581 icon_path = icon_path_ = nk_xdg_theme_get_icon(
586 }
else if (g_path_is_absolute(icon_key)) {
588 icon_path = icon_path_ = icon_key;
591 icon_path = icon_path_ = nk_xdg_theme_get_icon(
599 gchar *encoded_uri = g_filename_to_uri(entry_name, NULL, NULL);
600 int requested_size = MAX(sentry->
wsize, sentry->
hsize);
605 encoded_uri, requested_size, &thumb_size);
607 if (!g_file_test(icon_path, G_FILE_TEST_EXISTS)) {
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);
614 mime_type, entry_name, encoded_uri, icon_path_, thumb_size);
620 while (mime_type[index]) {
621 if (mime_type[index] ==
'/')
622 mime_type[index] =
'-';
629 icon_path = icon_path_ = nk_xdg_theme_get_icon(
635 g_free(content_type);
644 if (icon_path_ == NULL || !g_file_test(icon_path, G_FILE_TEST_EXISTS)) {
649 }
else if (g_path_is_absolute(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);
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);
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);
679 icon_path = icon_path_ = nk_xdg_theme_get_icon(
682 if (icon_path_ == NULL) {
683 g_debug(
"failed to get icon %s(%dx%d): n/a", sentry->
entry->
name,
686 const char *ext = g_strrstr(sentry->
entry->
name,
".");
688 const char *exts2[2] = {ext, NULL};
689 icon_path = icon_path_ =
692 if (icon_path_ == NULL) {
698 g_debug(
"found icon %s(%dx%d): %s", sentry->
entry->
name, sentry->
wsize,
699 sentry->
hsize, icon_path);
702 cairo_surface_t *icon_surf = NULL;
705 const char *suf = strrchr(icon_path,
'.');
714 GError *error = NULL;
715 GdkPixbuf *pb = gdk_pixbuf_new_from_file_at_scale(
716 icon_path, sentry->
wsize, sentry->
hsize, TRUE, &error);
723 if (error != NULL && g_error_matches(error, GDK_PIXBUF_ERROR,
724 GDK_PIXBUF_ERROR_INCOMPLETE_ANIMATION)) {
725 g_clear_error(&error);
729 g_warning(
"Failed to load image: |%s| %d %d %s (%p)", icon_path,
730 sentry->
wsize, sentry->
hsize, error->message, (
void *)pb);
748 g_debug(
"Query: %s(%dx%d)", name, wsize, hsize);
753 entry->
name = g_strdup(name);
757 for (GList *iter = g_list_first(entry->
sizes); iter;
758 iter = g_list_next(iter)) {
760 if (sentry->
wsize == wsize && sentry->
hsize == hsize) {
762 g_thread_pool_push(
tpool, sentry, NULL);
771 sentry->
wsize = wsize;
772 sentry->
hsize = hsize;
773 sentry->
entry = entry;
778 entry->
sizes = g_list_prepend(entry->
sizes, sentry);
780 GINT_TO_POINTER(sentry->
uid), sentry);
786 g_thread_pool_push(
tpool, sentry, NULL);
791 g_debug(
"Query: %s(%d)", name, size);
796 entry->
name = g_strdup(name);
800 for (GList *iter = g_list_first(entry->
sizes); iter;
801 iter = g_list_next(iter)) {
803 if (sentry->
wsize == size && sentry->
hsize == size) {
805 g_thread_pool_push(
tpool, sentry, NULL);
814 sentry->
wsize = size;
815 sentry->
hsize = size;
816 sentry->
entry = entry;
821 entry->
sizes = g_list_prepend(entry->
sizes, sentry);
823 GINT_TO_POINTER(sentry->
uid), sentry);
829 g_thread_pool_push(
tpool, sentry, NULL);
840 g_warning(
"Querying an non-existing uid");
845 cairo_surface_t **surface) {
853 g_warning(
"Querying an non-existing uid");
int helper_parse_setup(char *string, char ***output, int *length,...)
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)
void rofi_view_reload(void)
char * helper_get_theme_path(const char *file, const char **ext, const char *parent_file)
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)
static guchar alpha_mult(guchar c, guchar a)
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 THUMBNAILER_EXTENSION
#define THUMBNAILER_ENTRY_GROUP
IconFetcherNameEntry * entry
cairo_surface_t * surface
NkXdgThemeContext * xdg_context
GHashTable * icon_cache_uid
GList * supported_extensions
GHashTable * thumbnailers
void(* callback)(struct _thread_state *t, gpointer data)