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