rofi  1.7.0
rofi-icon-fetcher.c
Go to the documentation of this file.
1 /*
2  * rofi
3  *
4  * MIT/X11 License
5  * Copyright © 2013-2021 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 #include <xcb/xproto.h>
34 
35 #include "helper.h"
36 #include "rofi-icon-fetcher.h"
37 #include "rofi-types.h"
38 #include "settings.h"
39 
40 #include "keyb.h"
41 #include "view.h"
42 #include "xcb.h"
43 
44 #include "nkutils-enum.h"
45 #include "nkutils-xdg-theme.h"
46 
47 #include <stdint.h>
48 
49 #include "helper.h"
50 #include <gdk-pixbuf/gdk-pixbuf.h>
51 
52 typedef struct {
53  // Context for icon-themes.
54  NkXdgThemeContext *xdg_context;
55 
56  // On name.
57  GHashTable *icon_cache;
58  // On uid.
59  GHashTable *icon_cache_uid;
60 
61  // list extensions
63  uint32_t last_uid;
64 } IconFetcher;
65 
66 typedef struct {
67  char *name;
68  GList *sizes;
70 
71 typedef struct {
73 
74  GCond *cond;
75  GMutex *mutex;
76  unsigned int *acount;
77 
78  uint32_t uid;
79  int wsize;
80  int hsize;
81  cairo_surface_t *surface;
82 
85 
90 
91 static void rofi_icon_fetch_entry_free(gpointer data) {
93 
94  // Free name/key.
95  g_free(entry->name);
96 
97  for (GList *iter = g_list_first(entry->sizes); iter;
98  iter = g_list_next(iter)) {
99  IconFetcherEntry *sentry = (IconFetcherEntry *)(iter->data);
100 
101  cairo_surface_destroy(sentry->surface);
102  g_free(sentry);
103  }
104 
105  g_list_free(entry->sizes);
106  g_free(entry);
107 }
108 
110  g_assert(rofi_icon_fetcher_data == NULL);
111 
112  static const gchar *const icon_fallback_themes[] = {"Adwaita", "gnome", NULL};
113  const char *themes[2] = {config.icon_theme, NULL};
114 
115  rofi_icon_fetcher_data = g_malloc0(sizeof(IconFetcher));
116 
118  nk_xdg_theme_context_new(icon_fallback_themes, NULL);
119  nk_xdg_theme_preload_themes_icon(rofi_icon_fetcher_data->xdg_context, themes);
120 
122  g_hash_table_new(g_direct_hash, g_direct_equal);
123  rofi_icon_fetcher_data->icon_cache = g_hash_table_new_full(
124  g_str_hash, g_str_equal, NULL, rofi_icon_fetch_entry_free);
125 
126  GSList *l = gdk_pixbuf_get_formats();
127  for (GSList *li = l; li != NULL; li = g_slist_next(li)) {
128  gchar **exts =
129  gdk_pixbuf_format_get_extensions((GdkPixbufFormat *)li->data);
130 
131  for (unsigned int i = 0; exts && exts[i]; i++) {
133  g_list_append(rofi_icon_fetcher_data->supported_extensions, exts[i]);
134  g_info("Add image extension: %s", exts[i]);
135  exts[i] = NULL;
136  }
137 
138  g_free(exts);
139  }
140  g_slist_free(l);
141 }
142 
143 static void free_wrapper(gpointer data, G_GNUC_UNUSED gpointer user_data) {
144  g_free(data);
145 }
146 
148  if (rofi_icon_fetcher_data == NULL) {
149  return;
150  }
151 
152  nk_xdg_theme_context_free(rofi_icon_fetcher_data->xdg_context);
153 
154  g_hash_table_unref(rofi_icon_fetcher_data->icon_cache_uid);
155  g_hash_table_unref(rofi_icon_fetcher_data->icon_cache);
156 
158  NULL);
160  g_free(rofi_icon_fetcher_data);
161 }
162 
163 /*
164  * _rofi_icon_fetcher_get_icon_surface and alpha_mult
165  * are inspired by gdk_cairo_set_source_pixbuf
166  * GDK is:
167  * Copyright (C) 2011-2018 Red Hat, Inc.
168  */
169 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
171 #define RED_BYTE 2
173 #define GREEN_BYTE 1
175 #define BLUE_BYTE 0
177 #define ALPHA_BYTE 3
178 #else
180 #define RED_BYTE 1
182 #define GREEN_BYTE 2
184 #define BLUE_BYTE 3
186 #define ALPHA_BYTE 0
187 #endif
188 
189 static inline guchar alpha_mult(guchar c, guchar a) {
190  guint16 t;
191  switch (a) {
192  case 0xff:
193  return c;
194  case 0x00:
195  return 0x00;
196  default:
197  t = c * a + 0x7f;
198  return ((t >> 8) + t) >> 8;
199  }
200 }
201 
202 static cairo_surface_t *
204  gint width, height;
205  const guchar *pixels;
206  gint stride;
207  gboolean alpha;
208 
209  if (pixbuf == NULL) {
210  return NULL;
211  }
212 
213  width = gdk_pixbuf_get_width(pixbuf);
214  height = gdk_pixbuf_get_height(pixbuf);
215  pixels = gdk_pixbuf_read_pixels(pixbuf);
216  stride = gdk_pixbuf_get_rowstride(pixbuf);
217  alpha = gdk_pixbuf_get_has_alpha(pixbuf);
218 
219  cairo_surface_t *surface = NULL;
220 
221  gint cstride;
222  guint lo, o;
223  guchar a = 0xff;
224  const guchar *pixels_end, *line;
225  guchar *cpixels;
226 
227  pixels_end = pixels + height * stride;
228  o = alpha ? 4 : 3;
229  lo = o * width;
230 
231  surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
232  cpixels = cairo_image_surface_get_data(surface);
233  cstride = cairo_image_surface_get_stride(surface);
234 
235  cairo_surface_flush(surface);
236  while (pixels < pixels_end) {
237  line = pixels;
238  const guchar *line_end = line + lo;
239  guchar *cline = cpixels;
240 
241  while (line < line_end) {
242  if (alpha) {
243  a = line[3];
244  }
245  cline[RED_BYTE] = alpha_mult(line[0], a);
246  cline[GREEN_BYTE] = alpha_mult(line[1], a);
247  cline[BLUE_BYTE] = alpha_mult(line[2], a);
248  cline[ALPHA_BYTE] = a;
249 
250  line += o;
251  cline += 4;
252  }
253 
254  pixels += stride;
255  cpixels += cstride;
256  }
257  cairo_surface_mark_dirty(surface);
258  cairo_surface_flush(surface);
259 
260  return surface;
261 }
262 
263 gboolean rofi_icon_fetcher_file_is_image(const char *const path) {
264  if (path == NULL) {
265  return FALSE;
266  }
267  const char *suf = strrchr(path, '.');
268  if (suf == NULL) {
269  return FALSE;
270  }
271  suf++;
272 
273  for (GList *iter = rofi_icon_fetcher_data->supported_extensions; iter != NULL;
274  iter = g_list_next(iter)) {
275  if (g_ascii_strcasecmp(iter->data, suf) == 0) {
276  return TRUE;
277  }
278  }
279  return FALSE;
280 }
281 
283  G_GNUC_UNUSED gpointer user_data) {
284  g_debug("starting up icon fetching thread.");
285  // as long as dr->icon is updated atomicly.. (is a pointer write atomic?)
286  // this should be fine running in another thread.
287  IconFetcherEntry *sentry = (IconFetcherEntry *)sdata;
288  const gchar *themes[] = {config.icon_theme, NULL};
289 
290  const gchar *icon_path;
291  gchar *icon_path_ = NULL;
292 
293  if (g_path_is_absolute(sentry->entry->name)) {
294  icon_path = sentry->entry->name;
295  } else {
296  icon_path = icon_path_ = nk_xdg_theme_get_icon(
297  rofi_icon_fetcher_data->xdg_context, themes, NULL, sentry->entry->name,
298  MIN(sentry->wsize, sentry->hsize), 1, TRUE);
299  if (icon_path_ == NULL) {
300  g_debug("failed to get icon %s(%dx%d): n/a", sentry->entry->name,
301  sentry->wsize, sentry->hsize);
302 
303  const char *ext = g_strrstr(sentry->entry->name, ".");
304  if (ext) {
305  icon_path = helper_get_theme_path(sentry->entry->name, ext);
306  }
307  if (icon_path == NULL) {
308  return;
309  }
310  } else {
311  g_debug("found icon %s(%dx%d): %s", sentry->entry->name, sentry->wsize,
312  sentry->hsize, icon_path);
313  }
314  }
315  cairo_surface_t *icon_surf = NULL;
316 
317  const char *suf = strrchr(icon_path, '.');
318  if (suf == NULL) {
319  return;
320  }
321 
322  GError *error = NULL;
323  GdkPixbuf *pb = gdk_pixbuf_new_from_file_at_scale(
324  icon_path, sentry->wsize, sentry->hsize, TRUE, &error);
325  if (error != NULL) {
326  g_warning("Failed to load image: %s", error->message);
327  g_error_free(error);
328  if (pb) {
329  g_object_unref(pb);
330  }
331  } else {
333  g_object_unref(pb);
334  }
335 
336  sentry->surface = icon_surf;
337  g_free(icon_path_);
339 }
340 
341 uint32_t rofi_icon_fetcher_query_advanced(const char *name, const int wsize,
342  const int hsize) {
343  g_debug("Query: %s(%dx%d)", name, wsize, hsize);
344  IconFetcherNameEntry *entry =
345  g_hash_table_lookup(rofi_icon_fetcher_data->icon_cache, name);
346  if (entry == NULL) {
347  entry = g_new0(IconFetcherNameEntry, 1);
348  entry->name = g_strdup(name);
349  g_hash_table_insert(rofi_icon_fetcher_data->icon_cache, entry->name, entry);
350  }
351  IconFetcherEntry *sentry;
352  for (GList *iter = g_list_first(entry->sizes); iter;
353  iter = g_list_next(iter)) {
354  sentry = iter->data;
355  if (sentry->wsize == wsize && sentry->hsize == hsize) {
356  return sentry->uid;
357  }
358  }
359 
360  // Not found.
361  sentry = g_new0(IconFetcherEntry, 1);
362  sentry->uid = ++(rofi_icon_fetcher_data->last_uid);
363  sentry->wsize = wsize;
364  sentry->hsize = hsize;
365  sentry->entry = entry;
366  sentry->surface = NULL;
367 
368  entry->sizes = g_list_prepend(entry->sizes, sentry);
369  g_hash_table_insert(rofi_icon_fetcher_data->icon_cache_uid,
370  GINT_TO_POINTER(sentry->uid), sentry);
371 
372  // Push into fetching queue.
374  g_thread_pool_push(tpool, sentry, NULL);
375 
376  return sentry->uid;
377 }
378 uint32_t rofi_icon_fetcher_query(const char *name, const int size) {
379  g_debug("Query: %s(%d)", name, size);
380  IconFetcherNameEntry *entry =
381  g_hash_table_lookup(rofi_icon_fetcher_data->icon_cache, name);
382  if (entry == NULL) {
383  entry = g_new0(IconFetcherNameEntry, 1);
384  entry->name = g_strdup(name);
385  g_hash_table_insert(rofi_icon_fetcher_data->icon_cache, entry->name, entry);
386  }
387  IconFetcherEntry *sentry;
388  for (GList *iter = g_list_first(entry->sizes); iter;
389  iter = g_list_next(iter)) {
390  sentry = iter->data;
391  if (sentry->wsize == size && sentry->hsize == size) {
392  return sentry->uid;
393  }
394  }
395 
396  // Not found.
397  sentry = g_new0(IconFetcherEntry, 1);
398  sentry->uid = ++(rofi_icon_fetcher_data->last_uid);
399  sentry->wsize = size;
400  sentry->hsize = size;
401  sentry->entry = entry;
402  sentry->surface = NULL;
403 
404  entry->sizes = g_list_prepend(entry->sizes, sentry);
405  g_hash_table_insert(rofi_icon_fetcher_data->icon_cache_uid,
406  GINT_TO_POINTER(sentry->uid), sentry);
407 
408  // Push into fetching queue.
410  g_thread_pool_push(tpool, sentry, NULL);
411 
412  return sentry->uid;
413 }
414 
415 cairo_surface_t *rofi_icon_fetcher_get(const uint32_t uid) {
416  IconFetcherEntry *sentry = g_hash_table_lookup(
417  rofi_icon_fetcher_data->icon_cache_uid, GINT_TO_POINTER(uid));
418  if (sentry) {
419  return sentry->surface;
420  }
421  return NULL;
422 }
char * helper_get_theme_path(const char *file, const char *ext)
Definition: helper.c:1052
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)
void rofi_icon_fetcher_destroy(void)
cairo_surface_t * rofi_icon_fetcher_get(const uint32_t uid)
uint32_t rofi_icon_fetcher_query(const char *name, const int size)
void rofi_icon_fetcher_init(void)
void rofi_view_reload(void)
Definition: view.c:497
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
#define ALPHA_BYTE
#define BLUE_BYTE
static guchar alpha_mult(guchar c, guchar a)
#define GREEN_BYTE
static cairo_surface_t * rofi_icon_fetcher_get_surface_from_pixbuf(GdkPixbuf *pixbuf)
static void rofi_icon_fetcher_worker(thread_state *sdata, G_GNUC_UNUSED gpointer user_data)
#define RED_BYTE
Settings config
IconFetcherNameEntry * entry
unsigned int * acount
cairo_surface_t * surface
thread_state state
uint32_t last_uid
NkXdgThemeContext * xdg_context
GHashTable * icon_cache_uid
GList * supported_extensions
GHashTable * icon_cache
char * icon_theme
Definition: settings.h:81
void(* callback)(struct _thread_state *t, gpointer data)
Definition: rofi-types.h:314
GThreadPool * tpool
Definition: view.c:83