rofi/source/rofi-icon-fetcher.c

462 lines
14 KiB
C
Raw Normal View History

/*
* rofi
*
* MIT/X11 License
2020-01-01 11:23:12 +00:00
* Copyright © 2013-2020 Qball Cow <qball@gmpclient.org>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
/** The log domain of this Helper. */
#define G_LOG_DOMAIN "Helpers.IconFetcher"
#include <stdlib.h>
#include <config.h>
2020-10-18 19:37:05 +00:00
#include "rofi-icon-fetcher.h"
#include "rofi-types.h"
#include "helper.h"
2018-06-09 20:17:32 +00:00
#include "settings.h"
#include "xcb.h"
#include "keyb.h"
#include "view.h"
#include "nkutils-xdg-theme.h"
#include "nkutils-enum.h"
#include <stdint.h>
#include <jpeglib.h>
#include <setjmp.h>
2018-08-08 13:55:13 +00:00
typedef struct
{
// Context for icon-themes.
NkXdgThemeContext *xdg_context;
// On name.
2018-08-08 13:55:13 +00:00
GHashTable *icon_cache;
// On uid.
2018-08-08 13:55:13 +00:00
GHashTable *icon_cache_uid;
2018-08-08 13:55:13 +00:00
uint32_t last_uid;
} IconFetcher;
2018-08-08 13:55:13 +00:00
typedef struct
{
char *name;
GList *sizes;
} IconFetcherNameEntry;
2018-08-08 13:55:13 +00:00
typedef struct
{
thread_state state;
2018-08-08 13:55:13 +00:00
GCond *cond;
GMutex *mutex;
unsigned int *acount;
2018-08-08 13:55:13 +00:00
uint32_t uid;
int size;
cairo_surface_t *surface;
IconFetcherNameEntry *entry;
} IconFetcherEntry;
2018-06-12 12:11:08 +00:00
/**
* The icon fetcher internal state.
*/
IconFetcher *rofi_icon_fetcher_data = NULL;
static void rofi_icon_fetch_entry_free ( gpointer data )
{
2018-08-08 13:55:13 +00:00
IconFetcherNameEntry *entry = (IconFetcherNameEntry *) data;
// Free name/key.
g_free ( entry->name );
for ( GList *iter = g_list_first ( entry->sizes ); iter; iter = g_list_next ( iter ) ) {
2018-08-08 13:55:13 +00:00
IconFetcherEntry *sentry = (IconFetcherEntry *) ( iter->data );
cairo_surface_destroy ( sentry->surface );
g_free ( sentry );
}
g_list_free ( entry->sizes );
g_free ( entry );
}
void rofi_icon_fetcher_init ( void )
{
g_assert ( rofi_icon_fetcher_data == NULL );
static const gchar * const icon_fallback_themes[] = {
"Adwaita",
"gnome",
NULL
};
2018-08-08 13:55:13 +00:00
const char *themes[2] = { config.icon_theme, NULL };
2018-08-08 13:55:13 +00:00
rofi_icon_fetcher_data = g_malloc0 ( sizeof ( IconFetcher ) );
rofi_icon_fetcher_data->xdg_context = nk_xdg_theme_context_new ( icon_fallback_themes, NULL );
nk_xdg_theme_preload_themes_icon ( rofi_icon_fetcher_data->xdg_context, themes );
rofi_icon_fetcher_data->icon_cache_uid = g_hash_table_new ( g_direct_hash, g_direct_equal );
rofi_icon_fetcher_data->icon_cache = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, rofi_icon_fetch_entry_free );
}
void rofi_icon_fetcher_destroy ( void )
{
2018-08-08 13:55:13 +00:00
if ( rofi_icon_fetcher_data == NULL ) {
return;
}
nk_xdg_theme_context_free ( rofi_icon_fetcher_data->xdg_context );
g_hash_table_unref ( rofi_icon_fetcher_data->icon_cache_uid );
g_hash_table_unref ( rofi_icon_fetcher_data->icon_cache );
g_free ( rofi_icon_fetcher_data );
}
2020-08-28 14:41:59 +00:00
static cairo_surface_t* cairo_image_surface_create_from_jpeg_private ( struct jpeg_decompress_struct* cinfo )
{
cairo_surface_t* surface = 0;
unsigned char * data = 0;
unsigned char * rgb = 0;
2020-08-28 14:41:59 +00:00
jpeg_read_header ( cinfo, TRUE );
jpeg_start_decompress ( cinfo );
2020-08-28 14:41:59 +00:00
surface = cairo_image_surface_create ( CAIRO_FORMAT_RGB24, cinfo->image_width, cinfo->image_height );
data = cairo_image_surface_get_data ( surface );
rgb = (unsigned char *) ( malloc ( cinfo->output_width * cinfo->output_components ) );
2020-08-28 14:41:59 +00:00
while ( cinfo->output_scanline < cinfo->output_height ) {
unsigned int i;
int scanline = cinfo->output_scanline * cairo_image_surface_get_stride ( surface );
2020-08-28 14:41:59 +00:00
jpeg_read_scanlines ( cinfo, &rgb, 1 );
2020-08-28 14:41:59 +00:00
for ( i = 0; i < cinfo->output_width; i++ ) {
int offset = scanline + ( i * 4 );
2020-08-28 14:41:59 +00:00
data[offset + 3] = 255;
data[offset + 2] = rgb[( i * 3 )];
data[offset + 1] = rgb[( i * 3 ) + 1];
data[offset ] = rgb[( i * 3 ) + 2];
}
}
2020-08-28 14:41:59 +00:00
free ( rgb );
2020-08-28 14:41:59 +00:00
jpeg_finish_decompress ( cinfo );
jpeg_destroy_decompress ( cinfo );
2020-08-28 14:41:59 +00:00
cairo_surface_mark_dirty ( surface );
2020-08-28 14:41:59 +00:00
return surface;
}
struct jpegErrorManager {
/* "public" fields */
struct jpeg_error_mgr pub;
/* for return to caller */
jmp_buf setjmp_buffer;
};
char jpegLastErrorMsg[JMSG_LENGTH_MAX];
static void jpegErrorExit (j_common_ptr cinfo)
{
/* cinfo->err actually points to a jpegErrorManager struct */
struct jpegErrorManager* myerr = (struct jpegErrorManager*) cinfo->err;
/* Create the message */
( *(cinfo->err->format_message) ) (cinfo, jpegLastErrorMsg);
2020-10-18 19:37:05 +00:00
g_warning ( jpegLastErrorMsg, NULL );
/* Jump to the setjmp point */
longjmp(myerr->setjmp_buffer, 1);
}
2020-08-28 14:41:59 +00:00
static cairo_surface_t* cairo_image_surface_create_from_jpeg ( const char* file )
{
struct jpeg_decompress_struct cinfo;
cairo_surface_t * surface;
FILE * infile;
2020-08-28 14:41:59 +00:00
if ( ( infile = fopen ( file, "rb" ) ) == NULL ) {
return NULL;
}
struct jpegErrorManager jerr;
cinfo.err = jpeg_std_error( &jerr.pub);
jerr.pub.error_exit = jpegErrorExit;
/* Establish the setjmp return context for my_error_exit to use. */
if (setjmp(jerr.setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error. */
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return NULL;
}
2020-08-28 14:41:59 +00:00
jpeg_create_decompress ( &cinfo );
jpeg_stdio_src ( &cinfo, infile );
2020-08-28 14:41:59 +00:00
surface = cairo_image_surface_create_from_jpeg_private ( &cinfo );
2020-08-28 14:41:59 +00:00
fclose ( infile );
2020-08-28 14:41:59 +00:00
return surface;
}
2020-10-18 19:37:05 +00:00
#ifdef HAVE_LIBGIF
#include <gif_lib.h>
static cairo_surface_t* cairo_image_surface_create_from_gif(const char* file )
{
cairo_surface_t* img = NULL;
int err;
GifFileType* gif = DGifOpenFileName(file, &err);
if (!gif) {
g_warning( "[%i] %s", err, GifErrorString(err));
return NULL;
}
// decode with high-level API
if (DGifSlurp(gif) != GIF_OK) {
g_warning("Decoder error: %s", GifErrorString(gif->Error));
goto done;
}
if (!gif->SavedImages) {
g_warning("No saved images");
goto done;
}
// create canvas
img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, gif->SWidth, gif->SHeight);
if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) {
g_warning("Unable to create surface: %s",
cairo_status_to_string(cairo_surface_status(img)));
cairo_surface_destroy(img);
img = NULL;
goto done;
}
// we don't support animation, show the first frame only
const GifImageDesc* frame = &gif->SavedImages->ImageDesc;
const GifColorType* colors = gif->SColorMap ? gif->SColorMap->Colors :
frame->ColorMap->Colors;
uint32_t* base = (uint32_t*)(cairo_image_surface_get_data(img) +
frame->Top * cairo_image_surface_get_stride(img));
for (int y = 0; y < frame->Height; ++y) {
uint32_t* pixel = base + y * gif->SWidth + frame->Left;
const uint8_t* raster = &gif->SavedImages->RasterBits[y * gif->SWidth];
for (int x = 0; x < frame->Width; ++x) {
const uint8_t color = raster[x];
if (color != gif->SBackGroundColor) {
const GifColorType* rgb = &colors[color];
*pixel = 0xff000000 |
rgb->Red << 16 | rgb->Green << 8 | rgb->Blue;
}
++pixel;
}
}
cairo_surface_mark_dirty(img);
done:
DGifCloseFile(gif, NULL);
return img;
}
#endif
enum {
IMAGE_PNG,
IMAGE_JPG,
IMAGE_JPEG,
IMAGE_SVG,
#ifdef HAVE_LIBGIF
IMAGE_GIF,
#endif
IMAGE_MAX_VALUES
} RofiIconFetchDecoder;
static const gchar * const _image_exts[IMAGE_MAX_VALUES] = {
[IMAGE_PNG] = ".png",
[IMAGE_JPG] = ".jpg",
[IMAGE_JPEG] = ".jpeg",
[IMAGE_SVG] = ".svg",
#ifdef HAVE_LIBGIF
[IMAGE_GIF] = ".gif",
#endif
};
gboolean rofi_icon_fetcher_file_is_image ( const char * const path )
{
if ( path == NULL ) {
return FALSE;
}
const char *suf = strrchr(path, '.');
if ( suf == NULL ) {
return FALSE;
}
guint64 type;
gboolean r = nk_enum_parse ( suf, _image_exts, G_N_ELEMENTS(_image_exts), TRUE, FALSE, &type );
return r;
}
static void rofi_icon_fetcher_worker ( thread_state *sdata, G_GNUC_UNUSED gpointer user_data )
{
g_debug ( "starting up icon fetching thread." );
// as long as dr->icon is updated atomicly.. (is a pointer write atomic?)
// this should be fine running in another thread.
2018-08-08 13:55:13 +00:00
IconFetcherEntry *sentry = (IconFetcherEntry *) sdata;
const gchar *themes[] = {
2018-06-09 20:17:32 +00:00
config.icon_theme,
NULL
};
2018-08-08 13:55:13 +00:00
const gchar *icon_path;
gchar *icon_path_ = NULL;
if ( g_path_is_absolute ( sentry->entry->name ) ) {
icon_path = sentry->entry->name;
}
else {
icon_path = icon_path_ = nk_xdg_theme_get_icon ( rofi_icon_fetcher_data->xdg_context, themes, NULL, sentry->entry->name, sentry->size, 1, TRUE );
if ( icon_path_ == NULL ) {
2018-08-08 13:55:13 +00:00
g_debug ( "failed to get icon %s(%d): n/a", sentry->entry->name, sentry->size );
return;
}
else{
g_debug ( "found icon %s(%d): %s", sentry->entry->name, sentry->size, icon_path );
}
}
cairo_surface_t *icon_surf = NULL;
const char *suf = strrchr(icon_path, '.');
if ( suf == NULL ) {
return ;
}
guint64 type;
gboolean is_image = nk_enum_parse ( suf, _image_exts, G_N_ELEMENTS(_image_exts), TRUE, FALSE, &type );
if ( is_image )
{
if ( type == IMAGE_PNG ) {
icon_surf = cairo_image_surface_create_from_png ( icon_path );
}
2020-10-18 19:37:05 +00:00
#ifdef HAVE_LIBGIF
else if ( type == IMAGE_GIF ) {
icon_surf = cairo_image_surface_create_from_gif ( icon_path );
}
2020-10-18 19:37:05 +00:00
#endif
else if ( type == IMAGE_JPG || type == IMAGE_JPEG ) {
icon_surf = cairo_image_surface_create_from_jpeg ( icon_path );
}
else if ( type == IMAGE_SVG ) {
icon_surf = cairo_image_surface_create_from_svg ( icon_path, sentry->size );
}
else {
g_debug ( "icon type not yet supported: %s", icon_path );
}
}
if ( icon_surf ) {
if ( cairo_surface_status ( icon_surf ) == CAIRO_STATUS_SUCCESS ) {
2020-08-28 14:41:59 +00:00
float sw = sentry->size / (float) cairo_image_surface_get_width ( icon_surf );
float sh = sentry->size / (float) cairo_image_surface_get_height ( icon_surf );
2020-08-28 14:41:59 +00:00
float scale = ( sw > sh ) ? sh : sw;
if ( scale < 0.5 ) {
cairo_surface_t * surface = cairo_image_surface_create (
cairo_image_surface_get_format ( icon_surf ),
cairo_image_surface_get_width ( icon_surf ) * scale,
cairo_image_surface_get_height ( icon_surf ) * scale );
cairo_t *d = cairo_create ( surface );
cairo_scale ( d, scale, scale );
2020-08-28 14:41:59 +00:00
cairo_set_source_surface ( d, icon_surf, 0.0, 0.0 );
cairo_pattern_set_filter ( cairo_get_source ( d ), CAIRO_FILTER_FAST );
cairo_paint ( d );
cairo_destroy ( d );
cairo_surface_destroy ( icon_surf );
icon_surf = surface;
}
}
// check if surface is valid.
if ( cairo_surface_status ( icon_surf ) != CAIRO_STATUS_SUCCESS ) {
g_debug ( "icon failed to open: %s(%d): %s", sentry->entry->name, sentry->size, icon_path );
cairo_surface_destroy ( icon_surf );
icon_surf = NULL;
}
2018-08-08 13:55:13 +00:00
sentry->surface = icon_surf;
}
g_free ( icon_path_ );
rofi_view_reload ();
}
uint32_t rofi_icon_fetcher_query ( const char *name, const int size )
{
2018-08-08 13:55:13 +00:00
g_debug ( "Query: %s(%d)", name, size );
IconFetcherNameEntry *entry = g_hash_table_lookup ( rofi_icon_fetcher_data->icon_cache, name );
if ( entry == NULL ) {
2018-08-08 13:55:13 +00:00
entry = g_new0 ( IconFetcherNameEntry, 1 );
entry->name = g_strdup ( name );
g_hash_table_insert ( rofi_icon_fetcher_data->icon_cache, entry->name, entry );
}
IconFetcherEntry *sentry;
2018-08-08 13:55:13 +00:00
for ( GList *iter = g_list_first ( entry->sizes ); iter; iter = g_list_next ( iter ) ) {
sentry = iter->data;
2018-08-08 13:55:13 +00:00
if ( sentry->size == size ) {
return sentry->uid;
}
}
// Not found.
2018-08-08 13:55:13 +00:00
sentry = g_new0 ( IconFetcherEntry, 1 );
sentry->uid = ++( rofi_icon_fetcher_data->last_uid );
sentry->size = size;
sentry->entry = entry;
sentry->surface = NULL;
entry->sizes = g_list_prepend ( entry->sizes, sentry );
2018-08-08 13:55:13 +00:00
g_hash_table_insert ( rofi_icon_fetcher_data->icon_cache_uid, GINT_TO_POINTER ( sentry->uid ), sentry );
// Push into fetching queue.
sentry->state.callback = rofi_icon_fetcher_worker;
2018-08-08 13:55:13 +00:00
g_thread_pool_push ( tpool, sentry, NULL );
return sentry->uid;
}
cairo_surface_t * rofi_icon_fetcher_get ( const uint32_t uid )
{
2018-08-08 13:55:13 +00:00
IconFetcherEntry *sentry = g_hash_table_lookup ( rofi_icon_fetcher_data->icon_cache_uid, GINT_TO_POINTER ( uid ) );
if ( sentry ) {
return sentry->surface;
}
return NULL;
}