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
54typedef 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;
67
68typedef struct {
69 char *name;
70 GList *sizes;
72
73typedef 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
93static 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
145static 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);
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
191static 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
204static 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
265gboolean 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
369uint32_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}
406uint32_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
443cairo_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)
cairo_surface_t * rofi_icon_fetcher_get(const uint32_t uid)
void rofi_icon_fetcher_destroy(void)
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
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