From 37353b78441a68db4564f2a4e2b07587508b7b83 Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Sun, 28 Jan 2024 17:47:42 +0100 Subject: [PATCH 01/30] implemented xdg thumbnails fetching with fallback on mimetype icons for menu entries in filebrowser mode --- Makefile.am | 2 + include/md5.h | 26 +++++ source/md5.c | 223 +++++++++++++++++++++++++++++++++++++ source/modes/filebrowser.c | 4 + source/rofi-icon-fetcher.c | 157 +++++++++++++++++++++++++- 5 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 include/md5.h create mode 100644 source/md5.c diff --git a/Makefile.am b/Makefile.am index acf567ef8..1b2a2c28f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -64,6 +64,7 @@ SOURCES=\ source/mode.c\ source/keyb.c\ config/config.c\ + source/md5.c\ source/helper.c\ source/timings.c\ source/history.c\ @@ -96,6 +97,7 @@ SOURCES=\ include/rofi.h\ include/rofi-types.h\ include/rofi-icon-fetcher.h\ + include/md5.h\ include/mode.h\ include/mode-private.h\ include/settings.h\ diff --git a/include/md5.h b/include/md5.h new file mode 100644 index 000000000..0239e45e4 --- /dev/null +++ b/include/md5.h @@ -0,0 +1,26 @@ +#ifndef MD5_H +#define MD5_H + +#include +#include +#include +#include + +typedef struct{ + uint64_t size; // Size of input in bytes + uint32_t buffer[4]; // Current accumulation of hash + uint8_t input[64]; // Input to be used in the next step + uint8_t digest[16]; // Result of algorithm +}MD5Context; + +uint32_t rotateLeft(uint32_t x, uint32_t n); + +void md5Init(MD5Context *ctx); +void md5Update(MD5Context *ctx, uint8_t *input, size_t input_len); +void md5Finalize(MD5Context *ctx); +void md5Step(uint32_t *buffer, uint32_t *input); + +void md5String(char *input, uint8_t *result); +void md5File(FILE *file, uint8_t *result); + +#endif diff --git a/source/md5.c b/source/md5.c new file mode 100644 index 000000000..f79a427b7 --- /dev/null +++ b/source/md5.c @@ -0,0 +1,223 @@ +/* + * Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm + * and modified slightly to be functionally identical but condensed into control structures. + */ + +#include "md5.h" + +/* + * Constants defined by the MD5 algorithm + */ +#define A 0x67452301 +#define B 0xefcdab89 +#define C 0x98badcfe +#define D 0x10325476 + +static uint32_t S[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; + +static uint32_t K[] = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; + +/* + * Padding used to make the size (in bits) of the input congruent to 448 mod 512 + */ +static uint8_t PADDING[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* + * Bit-manipulation functions defined by the MD5 algorithm + */ +#define F(X, Y, Z) ((X & Y) | (~X & Z)) +#define G(X, Y, Z) ((X & Z) | (Y & ~Z)) +#define H(X, Y, Z) (X ^ Y ^ Z) +#define I(X, Y, Z) (Y ^ (X | ~Z)) + +/* + * Rotates a 32-bit word left by n bits + */ +uint32_t rotateLeft(uint32_t x, uint32_t n){ + return (x << n) | (x >> (32 - n)); +} + + +/* + * Initialize a context + */ +void md5Init(MD5Context *ctx){ + ctx->size = (uint64_t)0; + + ctx->buffer[0] = (uint32_t)A; + ctx->buffer[1] = (uint32_t)B; + ctx->buffer[2] = (uint32_t)C; + ctx->buffer[3] = (uint32_t)D; +} + +/* + * Add some amount of input to the context + * + * If the input fills out a block of 512 bits, apply the algorithm (md5Step) + * and save the result in the buffer. Also updates the overall size. + */ +void md5Update(MD5Context *ctx, uint8_t *input_buffer, size_t input_len){ + uint32_t input[16]; + unsigned int offset = ctx->size % 64; + ctx->size += (uint64_t)input_len; + + // Copy each byte in input_buffer into the next space in our context input + for(unsigned int i = 0; i < input_len; ++i){ + ctx->input[offset++] = (uint8_t)*(input_buffer + i); + + // If we've filled our context input, copy it into our local array input + // then reset the offset to 0 and fill in a new buffer. + // Every time we fill out a chunk, we run it through the algorithm + // to enable some back and forth between cpu and i/o + if(offset % 64 == 0){ + for(unsigned int j = 0; j < 16; ++j){ + // Convert to little-endian + // The local variable `input` our 512-bit chunk separated into 32-bit words + // we can use in calculations + input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 | + (uint32_t)(ctx->input[(j * 4) + 2]) << 16 | + (uint32_t)(ctx->input[(j * 4) + 1]) << 8 | + (uint32_t)(ctx->input[(j * 4)]); + } + md5Step(ctx->buffer, input); + offset = 0; + } + } +} + +/* + * Pad the current input to get to 448 bytes, append the size in bits to the very end, + * and save the result of the final iteration into digest. + */ +void md5Finalize(MD5Context *ctx){ + uint32_t input[16]; + unsigned int offset = ctx->size % 64; + unsigned int padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset; + + // Fill in the padding and undo the changes to size that resulted from the update + md5Update(ctx, PADDING, padding_length); + ctx->size -= (uint64_t)padding_length; + + // Do a final update (internal to this function) + // Last two 32-bit words are the two halves of the size (converted from bytes to bits) + for(unsigned int j = 0; j < 14; ++j){ + input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 | + (uint32_t)(ctx->input[(j * 4) + 2]) << 16 | + (uint32_t)(ctx->input[(j * 4) + 1]) << 8 | + (uint32_t)(ctx->input[(j * 4)]); + } + input[14] = (uint32_t)(ctx->size * 8); + input[15] = (uint32_t)((ctx->size * 8) >> 32); + + md5Step(ctx->buffer, input); + + // Move the result into digest (convert from little-endian) + for(unsigned int i = 0; i < 4; ++i){ + ctx->digest[(i * 4) + 0] = (uint8_t)((ctx->buffer[i] & 0x000000FF)); + ctx->digest[(i * 4) + 1] = (uint8_t)((ctx->buffer[i] & 0x0000FF00) >> 8); + ctx->digest[(i * 4) + 2] = (uint8_t)((ctx->buffer[i] & 0x00FF0000) >> 16); + ctx->digest[(i * 4) + 3] = (uint8_t)((ctx->buffer[i] & 0xFF000000) >> 24); + } +} + +/* + * Step on 512 bits of input with the main MD5 algorithm. + */ +void md5Step(uint32_t *buffer, uint32_t *input){ + uint32_t AA = buffer[0]; + uint32_t BB = buffer[1]; + uint32_t CC = buffer[2]; + uint32_t DD = buffer[3]; + + uint32_t E; + + unsigned int j; + + for(unsigned int i = 0; i < 64; ++i){ + switch(i / 16){ + case 0: + E = F(BB, CC, DD); + j = i; + break; + case 1: + E = G(BB, CC, DD); + j = ((i * 5) + 1) % 16; + break; + case 2: + E = H(BB, CC, DD); + j = ((i * 3) + 5) % 16; + break; + default: + E = I(BB, CC, DD); + j = (i * 7) % 16; + break; + } + + uint32_t temp = DD; + DD = CC; + CC = BB; + BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]); + AA = temp; + } + + buffer[0] += AA; + buffer[1] += BB; + buffer[2] += CC; + buffer[3] += DD; +} + +/* + * Functions that run the algorithm on the provided input and put the digest into result. + * result should be able to store 16 bytes. + */ +void md5String(char *input, uint8_t *result){ + MD5Context ctx; + md5Init(&ctx); + md5Update(&ctx, (uint8_t *)input, strlen(input)); + md5Finalize(&ctx); + + memcpy(result, ctx.digest, 16); +} + +void md5File(FILE *file, uint8_t *result){ + char *input_buffer = malloc(1024); + size_t input_size = 0; + + MD5Context ctx; + md5Init(&ctx); + + while((input_size = fread(input_buffer, 1, 1024, file)) > 0){ + md5Update(&ctx, (uint8_t *)input_buffer, input_size); + } + + md5Finalize(&ctx); + + free(input_buffer); + + memcpy(result, ctx.digest, 16); +} diff --git a/source/modes/filebrowser.c b/source/modes/filebrowser.c index 47df45889..545322958 100644 --- a/source/modes/filebrowser.c +++ b/source/modes/filebrowser.c @@ -614,6 +614,10 @@ static cairo_surface_t *_get_icon(const Mode *sw, unsigned int selected_line, } if (rofi_icon_fetcher_file_is_image(dr->path)) { dr->icon_fetch_uid = rofi_icon_fetcher_query(dr->path, height); + } else if (dr->type == RFILE) { + gchar* _path = g_strconcat("thumbnail://", dr->path, NULL); + dr->icon_fetch_uid = rofi_icon_fetcher_query(_path, height); + g_free(_path); } else { dr->icon_fetch_uid = rofi_icon_fetcher_query(icon_name[dr->type], height); } diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index c65463aee..aa52263a9 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -51,6 +51,8 @@ #include "helper.h" #include +#include "md5.h" + typedef struct { // Context for icon-themes. NkXdgThemeContext *xdg_context; @@ -282,6 +284,87 @@ gboolean rofi_icon_fetcher_file_is_image(const char *const path) { return FALSE; } +// https://stackoverflow.com/questions/57723372/cast-uint8-t-to-hex-string-2-digits/57723618#57723618 +static int md5_to_hex(char* dest, size_t dest_len, const uint8_t* values, + size_t val_len) { + static const char hex_table[] = "0123456789abcdef"; + if(dest_len < (val_len*2+1)) /* check that dest is large enough */ + return 0; + while(val_len--) { + /* shift down the top nibble and pick a char from the hex_table */ + *dest++ = hex_table[*values >> 4]; + /* extract the bottom nibble and pick a char from the hex_table */ + *dest++ = hex_table[*values++ & 0xF]; + } + *dest = 0; + return 1; +} + +// build file thumbnail's path using md5 hash of its encoded uri string +static gchar* rofi_icon_fetcher_get_file_thumbnail(const gchar* file_path, + int requested_size, + int *thumb_size) { + // convert filename to encoded uri string and calc its md5 hash + gchar *encoded_entry_name = g_filename_to_uri(file_path, NULL, NULL); + + int md5_size = 16; + uint8_t md5[md5_size]; + md5String(encoded_entry_name, md5); + + g_free(encoded_entry_name); + + // convert md5 hash to hex string + int hex_size = md5_size * 2 + 1; + char md5_hex[hex_size]; + md5_to_hex(md5_hex, hex_size, md5, md5_size); + + // determine thumbnail folder based on the request size + const gchar* cache_dir = g_get_user_cache_dir(); + gchar* thumb_path; + + if (requested_size <= 128) { + *thumb_size = 128; + thumb_path = g_strconcat(cache_dir, "/thumbnails/normal/", + md5_hex, ".png", NULL); + } else if (requested_size <= 256) { + *thumb_size = 256; + thumb_path = g_strconcat(cache_dir, "/thumbnails/large/", + md5_hex, ".png", NULL); + } else if (requested_size <= 512) { + *thumb_size = 512; + thumb_path = g_strconcat(cache_dir, "/thumbnails/x-large/", + md5_hex, ".png", NULL); + } else { + *thumb_size = 1024; + thumb_path = g_strconcat(cache_dir, "/thumbnails/xx-large/", + md5_hex, ".png", NULL); + } + + return thumb_path; +} + +// retrieves icon key from a .desktop file +static gchar* rofi_icon_fetcher_get_desktop_icon(const gchar* file_path) { + GKeyFile *kf = g_key_file_new(); + GError *key_error = NULL; + gchar *icon_key = NULL; + + gboolean res = g_key_file_load_from_file(kf, file_path, 0, &key_error); + + if (res) { + icon_key = g_key_file_get_string(kf, "Desktop Entry", "Icon", NULL); + } else { + g_debug("Failed to parse desktop file %s because: %s.", + file_path, key_error->message); + + g_error_free(key_error); + } + + g_key_file_free(kf); + + return icon_key; +} + static void rofi_icon_fetcher_worker(thread_state *sdata, G_GNUC_UNUSED gpointer user_data) { g_debug("starting up icon fetching thread."); @@ -293,7 +376,79 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, const gchar *icon_path; gchar *icon_path_ = NULL; - if (g_path_is_absolute(sentry->entry->name)) { + if (g_str_has_prefix(sentry->entry->name, "thumbnail://")) { + // remove uri thumbnail prefix from entry name + gchar *entry_name = &sentry->entry->name[12]; + + // if the entry name is an absolute path try to fetch its thumbnail + if (g_path_is_absolute(entry_name)) { + if (g_str_has_suffix(entry_name, ".desktop")) { + // if the entry is a .desktop file try to read its icon key + gchar *icon_key = rofi_icon_fetcher_get_desktop_icon(entry_name); + + if (icon_key == NULL || strlen(icon_key) == 0) { + // no icon in .desktop file, fallback on mimetype icon (text/plain) + icon_path = icon_path_ = nk_xdg_theme_get_icon( + rofi_icon_fetcher_data->xdg_context, themes, NULL, "text-plain", + MIN(sentry->wsize, sentry->hsize), 1, TRUE); + + g_free(icon_key); + } else if (g_path_is_absolute(icon_key)) { + // icon in .desktop file is an absolute path to an image + icon_path = icon_path_ = icon_key; + } else { + // icon in .desktop file is a standard icon name + icon_path = icon_path_ = nk_xdg_theme_get_icon( + rofi_icon_fetcher_data->xdg_context, themes, NULL, icon_key, + MIN(sentry->wsize, sentry->hsize), 1, TRUE); + + g_free(icon_key); + } + } else { + // look for file thumbnail in appropriate folder based on requested size + int requested_size = MAX(sentry->wsize, sentry->hsize); + int thumb_size; + + icon_path = icon_path_ = rofi_icon_fetcher_get_file_thumbnail( + entry_name, requested_size, &thumb_size); + + if (!g_file_test(icon_path, G_FILE_TEST_EXISTS)) { + // TODO: try to generate thumbnail + g_debug("%s thumbnail not found, generating...", icon_path); + + // use mime-type of file to show a theme icon + char *content_type = g_content_type_guess(entry_name, NULL, 0, NULL); + char *mime_type = g_content_type_get_mime_type(content_type); + + // replace forward slashes with minus sign to get the icon's name + int index = 0; + + while(mime_type[index]) { + if(mime_type[index] == '/') + mime_type[index] = '-'; + index++; + } + + g_free(icon_path_); + + // try to fetch the mime-type icon + icon_path = icon_path_ = nk_xdg_theme_get_icon( + rofi_icon_fetcher_data->xdg_context, themes, NULL, mime_type, + MIN(sentry->wsize, sentry->hsize), 1, TRUE); + + g_free(mime_type); + g_free(content_type); + } + } + } + + // no suitable icon or thumbnail was found + if (icon_path_ == NULL) { + sentry->query_done = TRUE; + rofi_view_reload(); + return; + } + } else if (g_path_is_absolute(sentry->entry->name)) { icon_path = sentry->entry->name; } else if (g_str_has_prefix(sentry->entry->name, " Date: Sun, 28 Jan 2024 18:28:57 +0100 Subject: [PATCH 02/30] included original license text --- include/md5.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/include/md5.h b/include/md5.h index 0239e45e4..491366c1a 100644 --- a/include/md5.h +++ b/include/md5.h @@ -1,6 +1,31 @@ #ifndef MD5_H #define MD5_H +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. +*/ + #include #include #include From 68272cea5ce125db707ce0843ee71360e5d50b1b Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Sun, 28 Jan 2024 18:29:35 +0100 Subject: [PATCH 03/30] added md5 header and source file --- meson.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meson.build b/meson.build index ff5fb2b35..bac07b0ab 100644 --- a/meson.build +++ b/meson.build @@ -187,6 +187,7 @@ rofi_sources = files( 'source/timings.c', 'source/history.c', 'source/theme.c', + 'source/md5.c', 'source/rofi-icon-fetcher.c', 'source/css-colors.c', 'source/widgets/box.c', @@ -219,6 +220,7 @@ rofi_sources = files( 'include/keyb.h', 'include/view.h', 'include/view-internal.h', + 'include/md5.h', 'include/rofi-icon-fetcher.h', 'include/helper.h', 'include/helper-theme.h', From 5b64132f6ac9939bedbd2504c4960a8519b5d57a Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 30 Jan 2024 21:12:50 +0100 Subject: [PATCH 04/30] implemented xdg compatible thumbnail's creation --- source/rofi-icon-fetcher.c | 192 +++++++++++++++++++++++++++++++++---- 1 file changed, 173 insertions(+), 19 deletions(-) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index aa52263a9..937432268 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -53,6 +53,10 @@ #include "md5.h" +// thumbnailers key file's group and file extension +#define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry" +#define THUMBNAILER_EXTENSION ".thumbnailer" + typedef struct { // Context for icon-themes. NkXdgThemeContext *xdg_context; @@ -65,6 +69,9 @@ typedef struct { // list extensions GList *supported_extensions; uint32_t last_uid; + + // thumbnailers per mime-types hashmap + GHashTable *thumbnailers; } IconFetcher; typedef struct { @@ -93,6 +100,128 @@ typedef struct { */ IconFetcher *rofi_icon_fetcher_data = NULL; +static void rofi_icon_fetcher_load_thumbnailers(const gchar *path) { + gchar *thumb_path = g_build_filename(path, "thumbnailers", NULL); + + GDir *dir = g_dir_open(thumb_path, 0, NULL); + + if (!dir) { + g_free(thumb_path); + return; + } + + const gchar *dirent; + + while ((dirent = g_dir_read_name(dir))) { + if (!g_str_has_suffix(dirent, THUMBNAILER_EXTENSION)) + continue; + + gchar *filename = g_build_filename(thumb_path, dirent, NULL); + GKeyFile *key_file = g_key_file_new(); + GError *error = NULL; + + if (!g_key_file_load_from_file(key_file, filename, 0, &error)) { + g_warning("Error loading thumbnailer %s: %s", filename, error->message); + g_error_free(error); + } else { + gchar *command = g_key_file_get_string( + key_file, THUMBNAILER_ENTRY_GROUP, "Exec", NULL); + gchar **mime_types = g_key_file_get_string_list( + key_file, THUMBNAILER_ENTRY_GROUP, "MimeType", NULL, NULL); + + if (mime_types && command) { + guint i; + for (i = 0; mime_types[i] != NULL; i++) { + if (!g_hash_table_lookup( + rofi_icon_fetcher_data->thumbnailers, mime_types[i])) { + g_info("Loading thumbnailer %s for mimetype %s", filename, mime_types[i]); + g_hash_table_insert( + rofi_icon_fetcher_data->thumbnailers, + g_strdup(mime_types[i]), + g_strdup(command) + ); + } + } + } + + if (mime_types) g_strfreev(mime_types); + if (command) g_free(command); + } + + g_key_file_free(key_file); + g_free(filename); + } + + g_dir_close(dir); + g_free(thumb_path); +} + +static gboolean rofi_icon_fetcher_create_thumbnail(const gchar *mime_type, + const gchar *filename, + const gchar *encoded_uri, + const gchar *output_path, + int size) { + gboolean thumbnail_created = FALSE; + + const gchar *command = g_hash_table_lookup( + rofi_icon_fetcher_data->thumbnailers, mime_type); + + if (!command) { + return thumbnail_created; + } + + // split command string to isolate arguments and expand them in a list + GStrvBuilder *cmd_builder = g_strv_builder_new(); + gchar **command_parts = g_strsplit(command, " ", 0); + gchar **command_args = NULL; + + if (command_parts) { + // add executable and arguments of the thumbnailer to the list + guint i; + for (i = 0; command_parts[i] != NULL; i++) { + if (strcmp(command_parts[i], "%i") == 0) { + g_strv_builder_add(cmd_builder, filename); + } else if (strcmp(command_parts[i], "%u") == 0) { + g_strv_builder_add(cmd_builder, encoded_uri); + } else if (strcmp(command_parts[i], "%o") == 0) { + g_strv_builder_add(cmd_builder, output_path); + } else if (strcmp(command_parts[i], "%s") == 0) { + char size_str[33]; + sprintf(size_str, "%d", size); + g_strv_builder_add(cmd_builder, size_str); + } else { + g_strv_builder_add(cmd_builder, command_parts[i]); + } + } + + command_args = g_strv_builder_end(cmd_builder); + + g_strfreev(command_parts); + } + + g_strv_builder_unref(cmd_builder); + + if (command_args) { + // launch and wait thumbnailers process + gint wait_status; + GError *error = NULL; + + gboolean spawned = g_spawn_sync("/usr/bin", command_args, + NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL, &wait_status, &error); + + if (spawned) { + thumbnail_created = g_spawn_check_wait_status(wait_status, NULL); + } else { + g_warning("Error calling thumbnailer %s: %s", command, error->message); + g_error_free(error); + } + + g_strfreev(command_args); + } + + return thumbnail_created; +} + static void rofi_icon_fetch_entry_free(gpointer data) { IconFetcherNameEntry *entry = (IconFetcherNameEntry *)data; @@ -143,6 +272,20 @@ void rofi_icon_fetcher_init(void) { g_free(exts); } g_slist_free(l); + + // load available thumbnailers from system dirs and user dir + rofi_icon_fetcher_data->thumbnailers = g_hash_table_new_full( + g_str_hash, g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_free); + + const gchar * const *system_data_dirs = g_get_system_data_dirs(); + const gchar *user_data_dir = g_get_user_data_dir(); + + rofi_icon_fetcher_load_thumbnailers(user_data_dir); + + guint i; + for (i = 0; system_data_dirs[i] != NULL; i++) { + rofi_icon_fetcher_load_thumbnailers(system_data_dirs[i]); + } } static void free_wrapper(gpointer data, G_GNUC_UNUSED gpointer user_data) { @@ -153,6 +296,8 @@ void rofi_icon_fetcher_destroy(void) { if (rofi_icon_fetcher_data == NULL) { return; } + + g_hash_table_unref(rofi_icon_fetcher_data->thumbnailers); nk_xdg_theme_context_free(rofi_icon_fetcher_data->xdg_context); @@ -413,31 +558,40 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, entry_name, requested_size, &thumb_size); if (!g_file_test(icon_path, G_FILE_TEST_EXISTS)) { - // TODO: try to generate thumbnail + // try to generate thumbnail g_debug("%s thumbnail not found, generating...", icon_path); - // use mime-type of file to show a theme icon + gchar *encoded_uri = g_filename_to_uri(entry_name, NULL, NULL); char *content_type = g_content_type_guess(entry_name, NULL, 0, NULL); char *mime_type = g_content_type_get_mime_type(content_type); - - // replace forward slashes with minus sign to get the icon's name - int index = 0; - - while(mime_type[index]) { - if(mime_type[index] == '/') - mime_type[index] = '-'; - index++; - } - g_free(icon_path_); - - // try to fetch the mime-type icon - icon_path = icon_path_ = nk_xdg_theme_get_icon( - rofi_icon_fetcher_data->xdg_context, themes, NULL, mime_type, - MIN(sentry->wsize, sentry->hsize), 1, TRUE); + if (mime_type) { + gboolean created = rofi_icon_fetcher_create_thumbnail( + mime_type, entry_name, encoded_uri, icon_path_, thumb_size); + + if (!created) { + // replace forward slashes with minus sign to get the icon's name + int index = 0; + + while(mime_type[index]) { + if(mime_type[index] == '/') + mime_type[index] = '-'; + index++; + } + + g_free(icon_path_); + + // try to fetch the mime-type icon + icon_path = icon_path_ = nk_xdg_theme_get_icon( + rofi_icon_fetcher_data->xdg_context, themes, NULL, mime_type, + MIN(sentry->wsize, sentry->hsize), 1, TRUE); + } + + g_free(mime_type); + g_free(content_type); + } - g_free(mime_type); - g_free(content_type); + g_free(encoded_uri); } } } From 423ee4e93bde3b9a82ed9d76e8de196e430626f8 Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Fri, 2 Feb 2024 12:27:15 +0100 Subject: [PATCH 05/30] added -preview-cmd string option to program settings --- config/config.c | 3 +++ include/settings.h | 3 +++ source/xrmoptions.c | 7 +++++++ 3 files changed, 13 insertions(+) diff --git a/config/config.c b/config/config.c index 7bde97035..4fa34eda7 100644 --- a/config/config.c +++ b/config/config.c @@ -45,6 +45,9 @@ Settings config = { /** Whether to load and show icons */ .show_icons = FALSE, + + /** Custom command to generate preview icons */ + .preview_cmd = NULL, /** Terminal to use. (for ssh and open in terminal) */ .terminal_emulator = "rofi-sensible-terminal", diff --git a/include/settings.h b/include/settings.h index 1af9b9527..5da4279fc 100644 --- a/include/settings.h +++ b/include/settings.h @@ -60,6 +60,9 @@ typedef struct { /** Whether to load and show icons */ gboolean show_icons; + + /** Custom command to generate preview icons */ + char *preview_cmd; /** Terminal to use */ char *terminal_emulator; diff --git a/source/xrmoptions.c b/source/xrmoptions.c index c2f6efc28..72e16bc1b 100644 --- a/source/xrmoptions.c +++ b/source/xrmoptions.c @@ -128,6 +128,13 @@ static XrmOption xrmOptions[] = { NULL, "Whether to load and show icons", CONFIG_DEFAULT}, + + {xrm_String, + "preview-cmd", + {.str = &config.preview_cmd}, + NULL, + "Custom command to generate preview icons", + CONFIG_DEFAULT}, {xrm_String, "terminal", From 1b9eec513deef94bf0b248fc1542bbc95392e869 Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Fri, 2 Feb 2024 12:28:19 +0100 Subject: [PATCH 06/30] support custom command to create images for entries with thumbnail:// prefix --- source/rofi-icon-fetcher.c | 113 ++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 33 deletions(-) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index 937432268..a7d16dc5e 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -156,26 +156,22 @@ static void rofi_icon_fetcher_load_thumbnailers(const gchar *path) { g_free(thumb_path); } -static gboolean rofi_icon_fetcher_create_thumbnail(const gchar *mime_type, - const gchar *filename, - const gchar *encoded_uri, - const gchar *output_path, - int size) { - gboolean thumbnail_created = FALSE; - - const gchar *command = g_hash_table_lookup( - rofi_icon_fetcher_data->thumbnailers, mime_type); - - if (!command) { - return thumbnail_created; - } - - // split command string to isolate arguments and expand them in a list - GStrvBuilder *cmd_builder = g_strv_builder_new(); +static gchar** setup_thumbnailer_command(const gchar *command, + const gchar *filename, + const gchar *encoded_uri, + const gchar *output_path, + int size) { gchar **command_parts = g_strsplit(command, " ", 0); gchar **command_args = NULL; - + + GStrvBuilder *cmd_builder = g_strv_builder_new(); + if (command_parts) { + // set process niceness value to 19 (low priority) + g_strv_builder_add(cmd_builder, "nice"); + g_strv_builder_add(cmd_builder, "-n"); + g_strv_builder_add(cmd_builder, "19"); + // add executable and arguments of the thumbnailer to the list guint i; for (i = 0; command_parts[i] != NULL; i++) { @@ -200,22 +196,48 @@ static gboolean rofi_icon_fetcher_create_thumbnail(const gchar *mime_type, } g_strv_builder_unref(cmd_builder); + + return command_args; +} - if (command_args) { - // launch and wait thumbnailers process - gint wait_status; - GError *error = NULL; +static gboolean exec_thumbnailer_command(gchar **command_args) { + // launch and wait thumbnailers process + gint wait_status; + GError *error = NULL; - gboolean spawned = g_spawn_sync("/usr/bin", command_args, - NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL, &wait_status, &error); + gboolean spawned = g_spawn_sync("/usr/bin", command_args, + NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL, &wait_status, &error); - if (spawned) { - thumbnail_created = g_spawn_check_wait_status(wait_status, NULL); - } else { - g_warning("Error calling thumbnailer %s: %s", command, error->message); - g_error_free(error); - } + if (spawned) { + return g_spawn_check_wait_status(wait_status, NULL); + } else { + g_warning("Error calling thumbnailer: %s", error->message); + g_error_free(error); + return FALSE; + } +} + +static gboolean rofi_icon_fetcher_create_thumbnail(const gchar *mime_type, + const gchar *filename, + const gchar *encoded_uri, + const gchar *output_path, + int size) { + gboolean thumbnail_created = FALSE; + + gchar *command = g_hash_table_lookup( + rofi_icon_fetcher_data->thumbnailers, mime_type); + + if (!command) { + return thumbnail_created; + } + + // split command string to isolate arguments and expand them in a list + gchar **command_args = setup_thumbnailer_command( + command, filename, encoded_uri, output_path, size); + + if (command_args) { + thumbnail_created = exec_thumbnailer_command(command_args); g_strfreev(command_args); } @@ -524,9 +546,36 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, if (g_str_has_prefix(sentry->entry->name, "thumbnail://")) { // remove uri thumbnail prefix from entry name gchar *entry_name = &sentry->entry->name[12]; + + // use custom user command to generate the thumbnail + if (config.preview_cmd != NULL) { + gchar *encoded_uri = g_filename_to_uri(entry_name, NULL, NULL); + int requested_size = MAX(sentry->wsize, sentry->hsize); + int thumb_size; - // if the entry name is an absolute path try to fetch its thumbnail - if (g_path_is_absolute(entry_name)) { + icon_path = icon_path_ = rofi_icon_fetcher_get_file_thumbnail( + entry_name, requested_size, &thumb_size); + + if (!g_file_test(icon_path, G_FILE_TEST_EXISTS)) { + char **command_args = NULL; + int argsv = 0; + char size_str[33]; + sprintf(size_str, "%d", thumb_size); + + helper_parse_setup( + config.preview_cmd, &command_args, &argsv, + "{input}", entry_name, "{uri}", encoded_uri, + "{output}", icon_path_, "{size}", size_str, NULL); + + if (command_args) { + exec_thumbnailer_command(command_args); + g_strfreev(command_args); + } + } + + g_free(encoded_uri); + } else if (g_path_is_absolute(entry_name)) { + // if the entry name is an absolute path try to fetch its thumbnail if (g_str_has_suffix(entry_name, ".desktop")) { // if the entry is a .desktop file try to read its icon key gchar *icon_key = rofi_icon_fetcher_get_desktop_icon(entry_name); @@ -559,8 +608,6 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, if (!g_file_test(icon_path, G_FILE_TEST_EXISTS)) { // try to generate thumbnail - g_debug("%s thumbnail not found, generating...", icon_path); - gchar *encoded_uri = g_filename_to_uri(entry_name, NULL, NULL); char *content_type = g_content_type_guess(entry_name, NULL, 0, NULL); char *mime_type = g_content_type_get_mime_type(content_type); From 32a9ffa5eb50f65965c4eb43fac5d4934155e543 Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Mon, 5 Feb 2024 08:46:19 +0100 Subject: [PATCH 07/30] fix custom thumbnailer command crash caused by null uri when entry is not a valid filename --- source/rofi-icon-fetcher.c | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index a7d16dc5e..533acb840 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -467,18 +467,14 @@ static int md5_to_hex(char* dest, size_t dest_len, const uint8_t* values, return 1; } -// build file thumbnail's path using md5 hash of its encoded uri string -static gchar* rofi_icon_fetcher_get_file_thumbnail(const gchar* file_path, - int requested_size, - int *thumb_size) { - // convert filename to encoded uri string and calc its md5 hash - gchar *encoded_entry_name = g_filename_to_uri(file_path, NULL, NULL); - +// build thumbnail's path using md5 hash of an entry name +static gchar* rofi_icon_fetcher_get_thumbnail(gchar* name, + int requested_size, + int *thumb_size) { + // calc entry_name md5 hash int md5_size = 16; uint8_t md5[md5_size]; - md5String(encoded_entry_name, md5); - - g_free(encoded_entry_name); + md5String(name, md5); // convert md5 hash to hex string int hex_size = md5_size * 2 + 1; @@ -549,11 +545,10 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, // use custom user command to generate the thumbnail if (config.preview_cmd != NULL) { - gchar *encoded_uri = g_filename_to_uri(entry_name, NULL, NULL); int requested_size = MAX(sentry->wsize, sentry->hsize); int thumb_size; - icon_path = icon_path_ = rofi_icon_fetcher_get_file_thumbnail( + icon_path = icon_path_ = rofi_icon_fetcher_get_thumbnail( entry_name, requested_size, &thumb_size); if (!g_file_test(icon_path, G_FILE_TEST_EXISTS)) { @@ -564,7 +559,7 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, helper_parse_setup( config.preview_cmd, &command_args, &argsv, - "{input}", entry_name, "{uri}", encoded_uri, + "{input}", entry_name, "{output}", icon_path_, "{size}", size_str, NULL); if (command_args) { @@ -572,8 +567,6 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, g_strfreev(command_args); } } - - g_free(encoded_uri); } else if (g_path_is_absolute(entry_name)) { // if the entry name is an absolute path try to fetch its thumbnail if (g_str_has_suffix(entry_name, ".desktop")) { @@ -599,16 +592,17 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, g_free(icon_key); } } else { - // look for file thumbnail in appropriate folder based on requested size + // build encoded uri string from absolute file path + gchar *encoded_uri = g_filename_to_uri(entry_name, NULL, NULL); int requested_size = MAX(sentry->wsize, sentry->hsize); int thumb_size; - icon_path = icon_path_ = rofi_icon_fetcher_get_file_thumbnail( - entry_name, requested_size, &thumb_size); + // look for file thumbnail in appropriate folder based on requested size + icon_path = icon_path_ = rofi_icon_fetcher_get_thumbnail( + encoded_uri, requested_size, &thumb_size); if (!g_file_test(icon_path, G_FILE_TEST_EXISTS)) { // try to generate thumbnail - gchar *encoded_uri = g_filename_to_uri(entry_name, NULL, NULL); char *content_type = g_content_type_guess(entry_name, NULL, 0, NULL); char *mime_type = g_content_type_get_mime_type(content_type); @@ -637,14 +631,14 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, g_free(mime_type); g_free(content_type); } - - g_free(encoded_uri); } + + g_free(encoded_uri); } } // no suitable icon or thumbnail was found - if (icon_path_ == NULL) { + if (icon_path_ == NULL || !g_file_test(icon_path, G_FILE_TEST_EXISTS)) { sentry->query_done = TRUE; rofi_view_reload(); return; From aa44499346f4a1fc542851a4a041999cff7de4f7 Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Mon, 5 Feb 2024 12:29:32 +0100 Subject: [PATCH 08/30] check entry_name is not NULL or empty when generating thumbnails; use snprintf to avoid static analyzer complains --- source/rofi-icon-fetcher.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index 533acb840..3a540077a 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -183,7 +183,7 @@ static gchar** setup_thumbnailer_command(const gchar *command, g_strv_builder_add(cmd_builder, output_path); } else if (strcmp(command_parts[i], "%s") == 0) { char size_str[33]; - sprintf(size_str, "%d", size); + snprintf(size_str, 33, "%d", size); g_strv_builder_add(cmd_builder, size_str); } else { g_strv_builder_add(cmd_builder, command_parts[i]); @@ -543,6 +543,12 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, // remove uri thumbnail prefix from entry name gchar *entry_name = &sentry->entry->name[12]; + if (entry_name == NULL || strcmp(entry_name, "") == 0) { + sentry->query_done = TRUE; + rofi_view_reload(); + return; + } + // use custom user command to generate the thumbnail if (config.preview_cmd != NULL) { int requested_size = MAX(sentry->wsize, sentry->hsize); From c573765cc4e6788840807a9680ecaa8868ed55ec Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 6 Feb 2024 19:14:30 +0100 Subject: [PATCH 09/30] avoid using gstrvbuilder to build thumbnailer command args --- source/rofi-icon-fetcher.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index 3a540077a..b77f9f796 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -162,40 +162,42 @@ static gchar** setup_thumbnailer_command(const gchar *command, const gchar *output_path, int size) { gchar **command_parts = g_strsplit(command, " ", 0); - gchar **command_args = NULL; + guint command_parts_count = g_strv_length(command_parts); - GStrvBuilder *cmd_builder = g_strv_builder_new(); + gchar **command_args = NULL; if (command_parts) { + command_args = malloc(sizeof(gchar*) * (command_parts_count + 3 + 1)); + // set process niceness value to 19 (low priority) - g_strv_builder_add(cmd_builder, "nice"); - g_strv_builder_add(cmd_builder, "-n"); - g_strv_builder_add(cmd_builder, "19"); + guint current_index = 0; + + command_args[current_index++] = strdup("nice"); + command_args[current_index++] = strdup("-n"); + command_args[current_index++] = strdup("19"); // add executable and arguments of the thumbnailer to the list guint i; for (i = 0; command_parts[i] != NULL; i++) { if (strcmp(command_parts[i], "%i") == 0) { - g_strv_builder_add(cmd_builder, filename); + command_args[current_index++] = strdup(filename); } else if (strcmp(command_parts[i], "%u") == 0) { - g_strv_builder_add(cmd_builder, encoded_uri); + command_args[current_index++] = strdup(encoded_uri); } else if (strcmp(command_parts[i], "%o") == 0) { - g_strv_builder_add(cmd_builder, output_path); + command_args[current_index++] = strdup(output_path); } else if (strcmp(command_parts[i], "%s") == 0) { char size_str[33]; snprintf(size_str, 33, "%d", size); - g_strv_builder_add(cmd_builder, size_str); + command_args[current_index++] = strdup(size_str); } else { - g_strv_builder_add(cmd_builder, command_parts[i]); + command_args[current_index++] = strdup(command_parts[i]); } } - command_args = g_strv_builder_end(cmd_builder); + command_args[current_index++] = NULL; g_strfreev(command_parts); } - - g_strv_builder_unref(cmd_builder); return command_args; } From 50d8ed9920d22d6e4bd4e8b757048aee4e97214d Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 6 Feb 2024 19:21:31 +0100 Subject: [PATCH 10/30] fixed static analyzer complain about always wrong condition --- source/rofi-icon-fetcher.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index b77f9f796..ad6f263fe 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -545,7 +545,7 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, // remove uri thumbnail prefix from entry name gchar *entry_name = &sentry->entry->name[12]; - if (entry_name == NULL || strcmp(entry_name, "") == 0) { + if (strcmp(entry_name, "") == 0) { sentry->query_done = TRUE; rofi_view_reload(); return; @@ -563,7 +563,7 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, char **command_args = NULL; int argsv = 0; char size_str[33]; - sprintf(size_str, "%d", thumb_size); + snprintf(size_str, 33, "%d", thumb_size); helper_parse_setup( config.preview_cmd, &command_args, &argsv, From 059f748a38253374860d77b10f0d4e6b1164e40e Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 6 Feb 2024 19:28:05 +0100 Subject: [PATCH 11/30] use g_spawn_check_exit_status to avoid bump to glib 2.70 --- source/rofi-icon-fetcher.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index ad6f263fe..8cf8110b2 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -211,7 +211,7 @@ static gboolean exec_thumbnailer_command(gchar **command_args) { NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL, &wait_status, &error); if (spawned) { - return g_spawn_check_wait_status(wait_status, NULL); + return g_spawn_check_exit_status(wait_status, NULL); } else { g_warning("Error calling thumbnailer: %s", error->message); g_error_free(error); From e6e2f87ab0b7252b39b0f4aac7b9973e6249b678 Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Mon, 12 Feb 2024 21:34:33 +0100 Subject: [PATCH 12/30] removed md5-c dependency and use glib checksum implementation --- Makefile.am | 2 - include/md5.h | 51 --------- source/md5.c | 223 ------------------------------------- source/rofi-icon-fetcher.c | 31 +----- 4 files changed, 5 insertions(+), 302 deletions(-) delete mode 100644 include/md5.h delete mode 100644 source/md5.c diff --git a/Makefile.am b/Makefile.am index 1b2a2c28f..acf567ef8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -64,7 +64,6 @@ SOURCES=\ source/mode.c\ source/keyb.c\ config/config.c\ - source/md5.c\ source/helper.c\ source/timings.c\ source/history.c\ @@ -97,7 +96,6 @@ SOURCES=\ include/rofi.h\ include/rofi-types.h\ include/rofi-icon-fetcher.h\ - include/md5.h\ include/mode.h\ include/mode-private.h\ include/settings.h\ diff --git a/include/md5.h b/include/md5.h deleted file mode 100644 index 491366c1a..000000000 --- a/include/md5.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef MD5_H -#define MD5_H - -/* -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -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 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. -*/ - -#include -#include -#include -#include - -typedef struct{ - uint64_t size; // Size of input in bytes - uint32_t buffer[4]; // Current accumulation of hash - uint8_t input[64]; // Input to be used in the next step - uint8_t digest[16]; // Result of algorithm -}MD5Context; - -uint32_t rotateLeft(uint32_t x, uint32_t n); - -void md5Init(MD5Context *ctx); -void md5Update(MD5Context *ctx, uint8_t *input, size_t input_len); -void md5Finalize(MD5Context *ctx); -void md5Step(uint32_t *buffer, uint32_t *input); - -void md5String(char *input, uint8_t *result); -void md5File(FILE *file, uint8_t *result); - -#endif diff --git a/source/md5.c b/source/md5.c deleted file mode 100644 index f79a427b7..000000000 --- a/source/md5.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm - * and modified slightly to be functionally identical but condensed into control structures. - */ - -#include "md5.h" - -/* - * Constants defined by the MD5 algorithm - */ -#define A 0x67452301 -#define B 0xefcdab89 -#define C 0x98badcfe -#define D 0x10325476 - -static uint32_t S[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; - -static uint32_t K[] = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, - 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, - 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, - 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, - 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, - 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, - 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, - 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, - 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, - 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, - 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, - 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, - 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, - 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, - 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, - 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; - -/* - * Padding used to make the size (in bits) of the input congruent to 448 mod 512 - */ -static uint8_t PADDING[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - -/* - * Bit-manipulation functions defined by the MD5 algorithm - */ -#define F(X, Y, Z) ((X & Y) | (~X & Z)) -#define G(X, Y, Z) ((X & Z) | (Y & ~Z)) -#define H(X, Y, Z) (X ^ Y ^ Z) -#define I(X, Y, Z) (Y ^ (X | ~Z)) - -/* - * Rotates a 32-bit word left by n bits - */ -uint32_t rotateLeft(uint32_t x, uint32_t n){ - return (x << n) | (x >> (32 - n)); -} - - -/* - * Initialize a context - */ -void md5Init(MD5Context *ctx){ - ctx->size = (uint64_t)0; - - ctx->buffer[0] = (uint32_t)A; - ctx->buffer[1] = (uint32_t)B; - ctx->buffer[2] = (uint32_t)C; - ctx->buffer[3] = (uint32_t)D; -} - -/* - * Add some amount of input to the context - * - * If the input fills out a block of 512 bits, apply the algorithm (md5Step) - * and save the result in the buffer. Also updates the overall size. - */ -void md5Update(MD5Context *ctx, uint8_t *input_buffer, size_t input_len){ - uint32_t input[16]; - unsigned int offset = ctx->size % 64; - ctx->size += (uint64_t)input_len; - - // Copy each byte in input_buffer into the next space in our context input - for(unsigned int i = 0; i < input_len; ++i){ - ctx->input[offset++] = (uint8_t)*(input_buffer + i); - - // If we've filled our context input, copy it into our local array input - // then reset the offset to 0 and fill in a new buffer. - // Every time we fill out a chunk, we run it through the algorithm - // to enable some back and forth between cpu and i/o - if(offset % 64 == 0){ - for(unsigned int j = 0; j < 16; ++j){ - // Convert to little-endian - // The local variable `input` our 512-bit chunk separated into 32-bit words - // we can use in calculations - input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 | - (uint32_t)(ctx->input[(j * 4) + 2]) << 16 | - (uint32_t)(ctx->input[(j * 4) + 1]) << 8 | - (uint32_t)(ctx->input[(j * 4)]); - } - md5Step(ctx->buffer, input); - offset = 0; - } - } -} - -/* - * Pad the current input to get to 448 bytes, append the size in bits to the very end, - * and save the result of the final iteration into digest. - */ -void md5Finalize(MD5Context *ctx){ - uint32_t input[16]; - unsigned int offset = ctx->size % 64; - unsigned int padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset; - - // Fill in the padding and undo the changes to size that resulted from the update - md5Update(ctx, PADDING, padding_length); - ctx->size -= (uint64_t)padding_length; - - // Do a final update (internal to this function) - // Last two 32-bit words are the two halves of the size (converted from bytes to bits) - for(unsigned int j = 0; j < 14; ++j){ - input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 | - (uint32_t)(ctx->input[(j * 4) + 2]) << 16 | - (uint32_t)(ctx->input[(j * 4) + 1]) << 8 | - (uint32_t)(ctx->input[(j * 4)]); - } - input[14] = (uint32_t)(ctx->size * 8); - input[15] = (uint32_t)((ctx->size * 8) >> 32); - - md5Step(ctx->buffer, input); - - // Move the result into digest (convert from little-endian) - for(unsigned int i = 0; i < 4; ++i){ - ctx->digest[(i * 4) + 0] = (uint8_t)((ctx->buffer[i] & 0x000000FF)); - ctx->digest[(i * 4) + 1] = (uint8_t)((ctx->buffer[i] & 0x0000FF00) >> 8); - ctx->digest[(i * 4) + 2] = (uint8_t)((ctx->buffer[i] & 0x00FF0000) >> 16); - ctx->digest[(i * 4) + 3] = (uint8_t)((ctx->buffer[i] & 0xFF000000) >> 24); - } -} - -/* - * Step on 512 bits of input with the main MD5 algorithm. - */ -void md5Step(uint32_t *buffer, uint32_t *input){ - uint32_t AA = buffer[0]; - uint32_t BB = buffer[1]; - uint32_t CC = buffer[2]; - uint32_t DD = buffer[3]; - - uint32_t E; - - unsigned int j; - - for(unsigned int i = 0; i < 64; ++i){ - switch(i / 16){ - case 0: - E = F(BB, CC, DD); - j = i; - break; - case 1: - E = G(BB, CC, DD); - j = ((i * 5) + 1) % 16; - break; - case 2: - E = H(BB, CC, DD); - j = ((i * 3) + 5) % 16; - break; - default: - E = I(BB, CC, DD); - j = (i * 7) % 16; - break; - } - - uint32_t temp = DD; - DD = CC; - CC = BB; - BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]); - AA = temp; - } - - buffer[0] += AA; - buffer[1] += BB; - buffer[2] += CC; - buffer[3] += DD; -} - -/* - * Functions that run the algorithm on the provided input and put the digest into result. - * result should be able to store 16 bytes. - */ -void md5String(char *input, uint8_t *result){ - MD5Context ctx; - md5Init(&ctx); - md5Update(&ctx, (uint8_t *)input, strlen(input)); - md5Finalize(&ctx); - - memcpy(result, ctx.digest, 16); -} - -void md5File(FILE *file, uint8_t *result){ - char *input_buffer = malloc(1024); - size_t input_size = 0; - - MD5Context ctx; - md5Init(&ctx); - - while((input_size = fread(input_buffer, 1, 1024, file)) > 0){ - md5Update(&ctx, (uint8_t *)input_buffer, input_size); - } - - md5Finalize(&ctx); - - free(input_buffer); - - memcpy(result, ctx.digest, 16); -} diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index db54e5391..27310d0da 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -51,8 +51,6 @@ #include "helper.h" #include -#include "md5.h" - // thumbnailers key file's group and file extension #define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry" #define THUMBNAILER_EXTENSION ".thumbnailer" @@ -453,35 +451,14 @@ gboolean rofi_icon_fetcher_file_is_image(const char *const path) { return FALSE; } -// https://stackoverflow.com/questions/57723372/cast-uint8-t-to-hex-string-2-digits/57723618#57723618 -static int md5_to_hex(char* dest, size_t dest_len, const uint8_t* values, - size_t val_len) { - static const char hex_table[] = "0123456789abcdef"; - if(dest_len < (val_len*2+1)) /* check that dest is large enough */ - return 0; - while(val_len--) { - /* shift down the top nibble and pick a char from the hex_table */ - *dest++ = hex_table[*values >> 4]; - /* extract the bottom nibble and pick a char from the hex_table */ - *dest++ = hex_table[*values++ & 0xF]; - } - *dest = 0; - return 1; -} - // build thumbnail's path using md5 hash of an entry name static gchar* rofi_icon_fetcher_get_thumbnail(gchar* name, int requested_size, int *thumb_size) { // calc entry_name md5 hash - int md5_size = 16; - uint8_t md5[md5_size]; - md5String(name, md5); - - // convert md5 hash to hex string - int hex_size = md5_size * 2 + 1; - char md5_hex[hex_size]; - md5_to_hex(md5_hex, hex_size, md5, md5_size); + GChecksum* checksum = g_checksum_new(G_CHECKSUM_MD5); + g_checksum_update(checksum, (guchar*)name, -1); + const gchar *md5_hex = g_checksum_get_string(checksum); // determine thumbnail folder based on the request size const gchar* cache_dir = g_get_user_cache_dir(); @@ -504,6 +481,8 @@ static gchar* rofi_icon_fetcher_get_thumbnail(gchar* name, thumb_path = g_strconcat(cache_dir, "/thumbnails/xx-large/", md5_hex, ".png", NULL); } + + g_checksum_free(checksum); return thumb_path; } From 600563cc35beb516dd1285d4833f672a8a84064c Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 13 Feb 2024 08:08:57 +0100 Subject: [PATCH 13/30] fixed meson build after md5-c library removal --- meson.build | 2 -- 1 file changed, 2 deletions(-) diff --git a/meson.build b/meson.build index bac07b0ab..ff5fb2b35 100644 --- a/meson.build +++ b/meson.build @@ -187,7 +187,6 @@ rofi_sources = files( 'source/timings.c', 'source/history.c', 'source/theme.c', - 'source/md5.c', 'source/rofi-icon-fetcher.c', 'source/css-colors.c', 'source/widgets/box.c', @@ -220,7 +219,6 @@ rofi_sources = files( 'include/keyb.h', 'include/view.h', 'include/view-internal.h', - 'include/md5.h', 'include/rofi-icon-fetcher.h', 'include/helper.h', 'include/helper-theme.h', From 9d8e0f37f6f02013fd24f8fcb046afdf3be9dbfb Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 13 Feb 2024 08:09:16 +0100 Subject: [PATCH 14/30] support thumbnail generation in recursivebrowser mode --- source/modes/recursivebrowser.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/modes/recursivebrowser.c b/source/modes/recursivebrowser.c index 3e010b418..d6abe5891 100644 --- a/source/modes/recursivebrowser.c +++ b/source/modes/recursivebrowser.c @@ -447,12 +447,12 @@ static cairo_surface_t *_get_icon(const Mode *sw, unsigned int selected_line, FBFile *dr = &(pd->array[selected_line]); if (dr->icon_fetch_uid > 0 && dr->icon_fetch_size == height) { return rofi_icon_fetcher_get(dr->icon_fetch_uid); - } - if (rofi_icon_fetcher_file_is_image(dr->path)) { - dr->icon_fetch_uid = rofi_icon_fetcher_query(dr->path, height); + }else if (dr->type == RFILE) { + gchar* _path = g_strconcat("thumbnail://", dr->path, NULL); + dr->icon_fetch_uid = rofi_icon_fetcher_query(_path, height); + g_free(_path); } else { - dr->icon_fetch_uid = - rofi_icon_fetcher_query(rb_icon_name[dr->type], height); + dr->icon_fetch_uid = rofi_icon_fetcher_query(rb_icon_name[dr->type], height); } dr->icon_fetch_size = height; return rofi_icon_fetcher_get(dr->icon_fetch_uid); From b57bc6de51a9c5134ea3fb16f1a73dc34c90b50e Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 13 Feb 2024 12:25:27 +0100 Subject: [PATCH 15/30] restored check rofi_icon_fetcher_file_is_image --- source/modes/recursivebrowser.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/modes/recursivebrowser.c b/source/modes/recursivebrowser.c index d6abe5891..d84e0c2f6 100644 --- a/source/modes/recursivebrowser.c +++ b/source/modes/recursivebrowser.c @@ -447,7 +447,10 @@ static cairo_surface_t *_get_icon(const Mode *sw, unsigned int selected_line, FBFile *dr = &(pd->array[selected_line]); if (dr->icon_fetch_uid > 0 && dr->icon_fetch_size == height) { return rofi_icon_fetcher_get(dr->icon_fetch_uid); - }else if (dr->type == RFILE) { + } + if (rofi_icon_fetcher_file_is_image(dr->path)) { + dr->icon_fetch_uid = rofi_icon_fetcher_query(dr->path, height); + } else if (dr->type == RFILE) { gchar* _path = g_strconcat("thumbnail://", dr->path, NULL); dr->icon_fetch_uid = rofi_icon_fetcher_query(_path, height); g_free(_path); From 29a2b390a2fff6872cc727341105dff498d0d88c Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 13 Feb 2024 12:25:43 +0100 Subject: [PATCH 16/30] create thumbnail directories if not existing --- source/rofi-icon-fetcher.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index 27310d0da..6a27d18ff 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -462,26 +462,35 @@ static gchar* rofi_icon_fetcher_get_thumbnail(gchar* name, // determine thumbnail folder based on the request size const gchar* cache_dir = g_get_user_cache_dir(); + gchar* thumb_dir; gchar* thumb_path; if (requested_size <= 128) { *thumb_size = 128; + thumb_dir = g_strconcat(cache_dir, "/thumbnails/normal/", NULL); thumb_path = g_strconcat(cache_dir, "/thumbnails/normal/", md5_hex, ".png", NULL); } else if (requested_size <= 256) { *thumb_size = 256; + thumb_dir = g_strconcat(cache_dir, "/thumbnails/large/", NULL); thumb_path = g_strconcat(cache_dir, "/thumbnails/large/", md5_hex, ".png", NULL); } else if (requested_size <= 512) { *thumb_size = 512; + thumb_dir = g_strconcat(cache_dir, "/thumbnails/x-large/", NULL); thumb_path = g_strconcat(cache_dir, "/thumbnails/x-large/", md5_hex, ".png", NULL); } else { *thumb_size = 1024; + thumb_dir = g_strconcat(cache_dir, "/thumbnails/xx-large/", NULL); thumb_path = g_strconcat(cache_dir, "/thumbnails/xx-large/", md5_hex, ".png", NULL); } - + + // create thumbnail directory if it does not exist + g_mkdir_with_parents(thumb_dir, 0700); + + g_free(thumb_dir); g_checksum_free(checksum); return thumb_path; From 8152a7167a0cdfdd8ce4ac099da4617739610421 Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 13 Feb 2024 12:33:00 +0100 Subject: [PATCH 17/30] use g_malloc0, g_strdup and g_strdup_printf --- source/rofi-icon-fetcher.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index 6a27d18ff..c0019148a 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -165,30 +165,28 @@ static gchar** setup_thumbnailer_command(const gchar *command, gchar **command_args = NULL; if (command_parts) { - command_args = malloc(sizeof(gchar*) * (command_parts_count + 3 + 1)); + command_args = g_malloc0(sizeof(gchar*) * (command_parts_count + 3 + 1)); // set process niceness value to 19 (low priority) guint current_index = 0; - command_args[current_index++] = strdup("nice"); - command_args[current_index++] = strdup("-n"); - command_args[current_index++] = strdup("19"); + command_args[current_index++] = g_strdup("nice"); + command_args[current_index++] = g_strdup("-n"); + command_args[current_index++] = g_strdup("19"); // add executable and arguments of the thumbnailer to the list guint i; for (i = 0; command_parts[i] != NULL; i++) { if (strcmp(command_parts[i], "%i") == 0) { - command_args[current_index++] = strdup(filename); + command_args[current_index++] = g_strdup(filename); } else if (strcmp(command_parts[i], "%u") == 0) { - command_args[current_index++] = strdup(encoded_uri); + command_args[current_index++] = g_strdup(encoded_uri); } else if (strcmp(command_parts[i], "%o") == 0) { - command_args[current_index++] = strdup(output_path); + command_args[current_index++] = g_strdup(output_path); } else if (strcmp(command_parts[i], "%s") == 0) { - char size_str[33]; - snprintf(size_str, 33, "%d", size); - command_args[current_index++] = strdup(size_str); + command_args[current_index++] = g_strdup_printf("%d", size); } else { - command_args[current_index++] = strdup(command_parts[i]); + command_args[current_index++] = g_strdup(command_parts[i]); } } @@ -550,13 +548,14 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, if (!g_file_test(icon_path, G_FILE_TEST_EXISTS)) { char **command_args = NULL; int argsv = 0; - char size_str[33]; - snprintf(size_str, 33, "%d", thumb_size); + gchar *size_str = g_strdup_printf("%d", thumb_size); helper_parse_setup( config.preview_cmd, &command_args, &argsv, "{input}", entry_name, "{output}", icon_path_, "{size}", size_str, NULL); + + g_free(size_str); if (command_args) { exec_thumbnailer_command(command_args); From c5e13e6c2e4247398eaa1fe47c7b12931101318a Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Wed, 14 Feb 2024 09:22:45 +0100 Subject: [PATCH 18/30] fixed formatting with clang-format --- config/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.c b/config/config.c index 4fa34eda7..10ef93c07 100644 --- a/config/config.c +++ b/config/config.c @@ -45,7 +45,7 @@ Settings config = { /** Whether to load and show icons */ .show_icons = FALSE, - + /** Custom command to generate preview icons */ .preview_cmd = NULL, From 15e206db7bfdb62c513a3cb8be9449eea3aa16c1 Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 20 Feb 2024 16:03:21 +0100 Subject: [PATCH 19/30] don't wait for jobs in execution when finalizing the icon fetcher worker threadpool --- source/view.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/view.c b/source/view.c index 29ca23073..c27fcb87e 100644 --- a/source/view.c +++ b/source/view.c @@ -2664,7 +2664,8 @@ void rofi_view_workers_initialize(void) { } void rofi_view_workers_finalize(void) { if (tpool) { - g_thread_pool_free(tpool, TRUE, TRUE); + // Discard all unprocessed jobs and don't wait for current jobs in execution + g_thread_pool_free(tpool, TRUE, FALSE); tpool = NULL; } } From cfdcc096b9c07f8880f0a8e3e45d51f6a973264b Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 20 Feb 2024 16:04:05 +0100 Subject: [PATCH 20/30] destroy and rebuild the icon fetcher worker threadpool when the current page is changed --- source/widgets/listview.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/widgets/listview.c b/source/widgets/listview.c index 2e333cb6a..f304f2382 100644 --- a/source/widgets/listview.c +++ b/source/widgets/listview.c @@ -284,6 +284,10 @@ static unsigned int scroll_per_page(listview *lv) { (lv->max_elements > 0) ? (lv->selected / lv->max_elements) : 0; offset = page * lv->max_elements; if (page != lv->cur_page) { + + rofi_view_workers_finalize(); + rofi_view_workers_initialize(); + lv->cur_page = page; lv->rchanged = TRUE; } From f7fe4846ffa1204b7bc1caed04eaee4ec868b41a Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 20 Feb 2024 16:05:50 +0100 Subject: [PATCH 21/30] added query_started boolean member to IconFetcherEntry; check if an icon fetcher query was started on an IconFetcherEntry and submit the query again otherwise --- source/rofi-icon-fetcher.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index c0019148a..ebb880845 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -89,6 +89,7 @@ typedef struct { int hsize; cairo_surface_t *surface; gboolean query_done; + gboolean query_started; IconFetcherNameEntry *entry; } IconFetcherEntry; @@ -527,6 +528,8 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, const gchar *icon_path; gchar *icon_path_ = NULL; + sentry->query_started = TRUE; + if (g_str_has_prefix(sentry->entry->name, "thumbnail://")) { // remove uri thumbnail prefix from entry name gchar *entry_name = &sentry->entry->name[12]; @@ -737,6 +740,9 @@ uint32_t rofi_icon_fetcher_query_advanced(const char *name, const int wsize, iter = g_list_next(iter)) { sentry = iter->data; if (sentry->wsize == wsize && sentry->hsize == hsize) { + if (!sentry->query_started) { + g_thread_pool_push(tpool, sentry, NULL); + } return sentry->uid; } } @@ -748,6 +754,7 @@ uint32_t rofi_icon_fetcher_query_advanced(const char *name, const int wsize, sentry->hsize = hsize; sentry->entry = entry; sentry->query_done = FALSE; + sentry->query_started = FALSE; sentry->surface = NULL; entry->sizes = g_list_prepend(entry->sizes, sentry); @@ -775,6 +782,9 @@ uint32_t rofi_icon_fetcher_query(const char *name, const int size) { iter = g_list_next(iter)) { sentry = iter->data; if (sentry->wsize == size && sentry->hsize == size) { + if (!sentry->query_started) { + g_thread_pool_push(tpool, sentry, NULL); + } return sentry->uid; } } @@ -785,6 +795,8 @@ uint32_t rofi_icon_fetcher_query(const char *name, const int size) { sentry->wsize = size; sentry->hsize = size; sentry->entry = entry; + sentry->query_done = FALSE; + sentry->query_started = FALSE; sentry->surface = NULL; entry->sizes = g_list_prepend(entry->sizes, sentry); From 6c5b8be60cee02457e09ecb373fe70b8bedc0b9f Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Tue, 20 Feb 2024 16:06:44 +0100 Subject: [PATCH 22/30] force icon cache lookup even if the item has a valid icon_fetch_uid (the fetching job could have been discarded before starting) --- source/modes/dmenu.c | 3 --- source/modes/filebrowser.c | 3 --- source/modes/recursivebrowser.c | 3 --- 3 files changed, 9 deletions(-) diff --git a/source/modes/dmenu.c b/source/modes/dmenu.c index 276153021..d6a0189e4 100644 --- a/source/modes/dmenu.c +++ b/source/modes/dmenu.c @@ -715,9 +715,6 @@ static cairo_surface_t *dmenu_get_icon(const Mode *sw, if (dr->icon_name == NULL) { return NULL; } - if (dr->icon_fetch_uid > 0 && dr->icon_fetch_size == height) { - return rofi_icon_fetcher_get(dr->icon_fetch_uid); - } uint32_t uid = dr->icon_fetch_uid = rofi_icon_fetcher_query(dr->icon_name, height); dr->icon_fetch_size = height; diff --git a/source/modes/filebrowser.c b/source/modes/filebrowser.c index 545322958..6f7122f24 100644 --- a/source/modes/filebrowser.c +++ b/source/modes/filebrowser.c @@ -609,9 +609,6 @@ static cairo_surface_t *_get_icon(const Mode *sw, unsigned int selected_line, (FileBrowserModePrivateData *)mode_get_private_data(sw); g_return_val_if_fail(pd->array != NULL, NULL); FBFile *dr = &(pd->array[selected_line]); - if (dr->icon_fetch_uid > 0 && dr->icon_fetch_size == height) { - return rofi_icon_fetcher_get(dr->icon_fetch_uid); - } if (rofi_icon_fetcher_file_is_image(dr->path)) { dr->icon_fetch_uid = rofi_icon_fetcher_query(dr->path, height); } else if (dr->type == RFILE) { diff --git a/source/modes/recursivebrowser.c b/source/modes/recursivebrowser.c index d84e0c2f6..67528b62f 100644 --- a/source/modes/recursivebrowser.c +++ b/source/modes/recursivebrowser.c @@ -445,9 +445,6 @@ static cairo_surface_t *_get_icon(const Mode *sw, unsigned int selected_line, (FileBrowserModePrivateData *)mode_get_private_data(sw); g_return_val_if_fail(pd->array != NULL, NULL); FBFile *dr = &(pd->array[selected_line]); - if (dr->icon_fetch_uid > 0 && dr->icon_fetch_size == height) { - return rofi_icon_fetcher_get(dr->icon_fetch_uid); - } if (rofi_icon_fetcher_file_is_image(dr->path)) { dr->icon_fetch_uid = rofi_icon_fetcher_query(dr->path, height); } else if (dr->type == RFILE) { From 32848379b9c9477c26887d616474ff7895b09d90 Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Mon, 26 Feb 2024 19:54:37 +0100 Subject: [PATCH 23/30] search binaries in PATH when executing thumbnailer command --- source/rofi-icon-fetcher.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index ebb880845..02181fed7 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -204,8 +204,8 @@ static gboolean exec_thumbnailer_command(gchar **command_args) { gint wait_status; GError *error = NULL; - gboolean spawned = g_spawn_sync("/usr/bin", command_args, - NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL, &wait_status, &error); + gboolean spawned = g_spawn_sync(NULL, command_args, + NULL, G_SPAWN_DEFAULT | G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, &wait_status, &error); if (spawned) { return g_spawn_check_exit_status(wait_status, NULL); From b179ff050a23c81ccc23cecef987dc6e8005cbb9 Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Sat, 9 Mar 2024 10:57:40 +0100 Subject: [PATCH 24/30] mark icon query as not started in threadpool item free_func --- source/rofi-icon-fetcher.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/rofi-icon-fetcher.c b/source/rofi-icon-fetcher.c index 1ea067d84..ace7e6315 100644 --- a/source/rofi-icon-fetcher.c +++ b/source/rofi-icon-fetcher.c @@ -248,6 +248,7 @@ static gboolean rofi_icon_fetcher_create_thumbnail(const gchar *mime_type, static void rofi_icon_fetch_thread_pool_entry_remove(gpointer data) { IconFetcherEntry *entry = (IconFetcherEntry *)data; // Mark it in a way it should be re-fetched on next query? + entry->query_started = FALSE; } static void rofi_icon_fetch_entry_free(gpointer data) { @@ -535,8 +536,6 @@ static void rofi_icon_fetcher_worker(thread_state *sdata, const gchar *icon_path; gchar *icon_path_ = NULL; - sentry->query_started = TRUE; - if (g_str_has_prefix(sentry->entry->name, "thumbnail://")) { // remove uri thumbnail prefix from entry name gchar *entry_name = &sentry->entry->name[12]; @@ -761,7 +760,7 @@ uint32_t rofi_icon_fetcher_query_advanced(const char *name, const int wsize, sentry->hsize = hsize; sentry->entry = entry; sentry->query_done = FALSE; - sentry->query_started = FALSE; + sentry->query_started = TRUE; sentry->surface = NULL; entry->sizes = g_list_prepend(entry->sizes, sentry); @@ -804,7 +803,7 @@ uint32_t rofi_icon_fetcher_query(const char *name, const int size) { sentry->hsize = size; sentry->entry = entry; sentry->query_done = FALSE; - sentry->query_started = FALSE; + sentry->query_started = TRUE; sentry->surface = NULL; entry->sizes = g_list_prepend(entry->sizes, sentry); @@ -813,6 +812,7 @@ uint32_t rofi_icon_fetcher_query(const char *name, const int size) { // Push into fetching queue. sentry->state.callback = rofi_icon_fetcher_worker; + sentry->state.free = rofi_icon_fetch_thread_pool_entry_remove; sentry->state.priority = G_PRIORITY_LOW; g_thread_pool_push(tpool, sentry, NULL); From 58ff766c65a8c961182837b360dbd74d9d12420f Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Sat, 9 Mar 2024 10:59:05 +0100 Subject: [PATCH 25/30] added listview page_changed_callback; rebuild icon fetcher threadpool in page_changed_callback --- include/widgets/listview.h | 10 +++++++++- source/view.c | 8 +++++++- source/widgets/listview.c | 11 ++++++++--- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/include/widgets/listview.h b/include/widgets/listview.h index 292118383..1b22dbf48 100644 --- a/include/widgets/listview.h +++ b/include/widgets/listview.h @@ -84,6 +84,13 @@ typedef void (*listview_selection_changed_callback)(listview *lv, */ typedef void (*listview_mouse_activated_cb)(listview *, gboolean, void *); + +/** + * Callback when current page is changed. + */ +typedef void (*listview_page_changed_cb)(void); + + /** * @param parent The widget's parent. * @param name The name of the to be created widget. @@ -95,7 +102,8 @@ typedef void (*listview_mouse_activated_cb)(listview *, gboolean, void *); * @returns a new listview */ listview *listview_create(widget *parent, const char *name, - listview_update_callback cb, void *udata, + listview_update_callback cb, + listview_page_changed_cb page_cb, void *udata, unsigned int eh, gboolean reverse); /** diff --git a/source/view.c b/source/view.c index a8039bf2d..5851f3661 100644 --- a/source/view.c +++ b/source/view.c @@ -1353,6 +1353,11 @@ static void update_callback(textbox *t, icon *ico, unsigned int index, textbox_font(t, *type); } } +static void page_changed_callback() +{ + rofi_view_workers_finalize(); + rofi_view_workers_initialize(); +} void rofi_view_update(RofiViewState *state, gboolean qr) { if (!widget_need_redraw(WIDGET(state->main_window))) { @@ -2354,7 +2359,8 @@ static void rofi_view_add_widget(RofiViewState *state, widget *parent_widget, return; } state->list_view = listview_create(parent_widget, name, update_callback, - state, config.element_height, 0); + page_changed_callback, state, + config.element_height, 0); listview_set_selection_changed_callback( state->list_view, selection_changed_callback, (void *)state); box_add((box *)parent_widget, WIDGET(state->list_view), TRUE); diff --git a/source/widgets/listview.c b/source/widgets/listview.c index f304f2382..957b9ce8f 100644 --- a/source/widgets/listview.c +++ b/source/widgets/listview.c @@ -120,6 +120,8 @@ struct _listview { xcb_timestamp_t last_click; listview_mouse_activated_cb mouse_activated; void *mouse_activated_data; + + listview_page_changed_cb page_callback; char *listview_name; @@ -285,8 +287,8 @@ static unsigned int scroll_per_page(listview *lv) { offset = page * lv->max_elements; if (page != lv->cur_page) { - rofi_view_workers_finalize(); - rofi_view_workers_initialize(); + if (lv->page_callback) + lv->page_callback(); lv->cur_page = page; lv->rchanged = TRUE; @@ -749,7 +751,8 @@ static gboolean listview_element_motion_notify(widget *wid, } listview *listview_create(widget *parent, const char *name, - listview_update_callback cb, void *udata, + listview_update_callback cb, + listview_page_changed_cb page_cb, void *udata, unsigned int eh, gboolean reverse) { listview *lv = g_malloc0(sizeof(listview)); widget_init(WIDGET(lv), parent, WIDGET_TYPE_LISTVIEW, name); @@ -786,6 +789,8 @@ listview *listview_create(widget *parent, const char *name, lv->callback = cb; lv->udata = udata; + lv->page_callback = page_cb; + // Some settings. lv->spacing = rofi_theme_get_distance(WIDGET(lv), "spacing", DEFAULT_SPACING); lv->menu_columns = From 97b25ebd832f38f0a0c8545149121fa43accec0a Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Sun, 10 Mar 2024 11:50:39 +0100 Subject: [PATCH 26/30] [listview] Add missing code documentation param --- include/widgets/listview.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/widgets/listview.h b/include/widgets/listview.h index 1b22dbf48..9fee5b52a 100644 --- a/include/widgets/listview.h +++ b/include/widgets/listview.h @@ -95,6 +95,7 @@ typedef void (*listview_page_changed_cb)(void); * @param parent The widget's parent. * @param name The name of the to be created widget. * @param cb The update callback. + * @param page_cb The page change callback. * @param udata The user data to pass to the callback * @param eh The height of one element * @param reverse Reverse the listview order. From 5d69c6434fe489d2fa682c3e4cb385ec8aedc3fa Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Thu, 16 May 2024 14:03:41 +0200 Subject: [PATCH 27/30] Create rofi-thumbnails.5.markdown --- doc/rofi-thumbnails.5.markdown | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 doc/rofi-thumbnails.5.markdown diff --git a/doc/rofi-thumbnails.5.markdown b/doc/rofi-thumbnails.5.markdown new file mode 100644 index 000000000..26fa546b1 --- /dev/null +++ b/doc/rofi-thumbnails.5.markdown @@ -0,0 +1,35 @@ +# rofi-thumbnails(5) + +## NAME + +**rofi-thumbnails** - Rofi thumbnails system + +## DESCRIPTION + +rofi is now able to show thumbnails for all file types where an XDG compatible thumbnailer is present in the system. +This is done by default in filebrowser and recursivebrowser mode, if rofi is launched with the `-show-icons` argument. +In a custom user script or dmenu mode, it is possible to produce entry icons using XDG thumbnailers by adding the prefix `thumbnail://` to the filename +specified after `\0icon\x1f`, for example: +```bash + echo -en "EntryName\0icon\x1fthumbnail://path/to/file\n" | rofi -dmenu -show-icons +``` + +### XDG thumbnailers + +XDG thumbnailers are files with a ".thumbnailer" suffix and a structure similar to ".desktop" files for launching applications. They are placed in `/usr/share/thumbnailers/` or `$HOME/.local/share/thumbnailers/`, and contain a list of mimetypes, for which is possible to produce the thumbnail image, and a string with the command to create said image. The example below shows the content of `ffmpegthumbnailer.thumbnailer`, a thumbnailer for video files using ffmpeg: +``` +[Thumbnailer Entry] +TryExec=ffmpegthumbnailer +Exec=ffmpegthumbnailer -i %i -o %o -s %s -f +MimeType=video/jpeg;video/mp4;video/mpeg;video/quicktime;video/x-ms-asf;video/x-ms-wm;video/x-ms-wmv;video/x-ms-asx;video/x-ms-wmx;video/x-ms-wvx;video/x-msvideo;video/x-flv;video/x-matroska;application/mxf;video/3gp;video/3gpp;video/dv;video/divx;video/fli;video/flv;video/mp2t;video/mp4v-es;video/msvideo;video/ogg;video/vivo;video/vnd.divx;video/vnd.mpegurl;video/vnd.rn-realvideo;application/vnd.rn-realmedia;video/vnd.vivo;video/webm;video/x-anim;video/x-avi;video/x-flc;video/x-fli;video/x-flic;video/x-m4v;video/x-mpeg;video/x-mpeg2;video/x-nsv;video/x-ogm+ogg;video/x-theora+ogg +``` +The images produced are named as the md5sum of the input files and placed, depending on their size, in the XDG thumbnails directories: `$HOME/.cache/thumbnails/{normal,large,x-large,xx-large}`. They are then loaded by rofi as entry icons and can also be used by file managers like Thunar, Caja or KDE Dolphin to show their thumbnails. Additionally, if a thumbnail for a file is found in the thumbnails directories (produced previously by rofi or a file manager), rofi will load it instead of calling the thumbnailer. +If a suitable thumbnailer for a given file is not found, rofi will try to use the corresponding mimetype icon from the icon theme. + +### Custom command to create thumbnails + +It is possible to use a custom command to generate thumbnails for generic entry names, for example a script that downloads an icon given its url or selects different icons depending on the input. This can be done providing the `-preview-cmd` argument followed by a string with the command to execute, with the following syntax: +``` + rofi ... -preview-cmd 'path/to/script_or_cmd "{input}" "{output}" "{size}"' +``` +rofi will call the script or command substituting `{input}` with the input entry icon name (the string after `\0icon\x1fthumbnail://`), `{output}` with the output filename of the thumbnail and `{size}` with the requested thumbnail size. The script or command is responsible of producing a thumbnail image (if possible respecting the requested size) and saving it in the given `{output}` filename. From 46473d473d561c458becd192b0b46d77c1a3e2da Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Sat, 25 May 2024 15:40:29 +0200 Subject: [PATCH 28/30] Updated documentation with apparmor issues and workaround --- doc/rofi-thumbnails.5.markdown | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/doc/rofi-thumbnails.5.markdown b/doc/rofi-thumbnails.5.markdown index 26fa546b1..04cc66a66 100644 --- a/doc/rofi-thumbnails.5.markdown +++ b/doc/rofi-thumbnails.5.markdown @@ -33,3 +33,38 @@ It is possible to use a custom command to generate thumbnails for generic entry rofi ... -preview-cmd 'path/to/script_or_cmd "{input}" "{output}" "{size}"' ``` rofi will call the script or command substituting `{input}` with the input entry icon name (the string after `\0icon\x1fthumbnail://`), `{output}` with the output filename of the thumbnail and `{size}` with the requested thumbnail size. The script or command is responsible of producing a thumbnail image (if possible respecting the requested size) and saving it in the given `{output}` filename. + +### Issues with AppArmor +In Linux distributions using AppArmor (such as Ubuntu and Debian), the default rules shipped can cause issues with thumbnails generation. If that is the case, AppArmor can be disabled by issuing the following commands +``` +sudo systemctl stop apparmor +sudo systemctl disable apparmor +``` +In alternative, the following apparmor profile con be placed in a file named /etc/apparmor.d/usr.bin.rofi +``` +#vim:syntax=apparmor +# AppArmor policy for rofi + +#include + +/usr/bin/rofi { + #include + + # TCP/UDP network access for NFS + network inet stream, + network inet6 stream, + network inet dgram, + network inet6 dgram, + + /usr/bin/rofi mr, + + @{HOME}/ r, + @{HOME}/** rw, + owner @{HOME}/.cache/thumbnails/** rw, +} +``` +then run +``` +apparmor_parser -r /etc/apparmor.d/usr.bin.rofi +``` +to reload the rule. This assumes that rofi binary is in /usr/bin, that is the case of a standard package installation. From 6acf5dbbc86813cdad1daf5f942636145608ae4f Mon Sep 17 00:00:00 2001 From: lbonn Date: Thu, 20 Jun 2024 00:16:22 +0200 Subject: [PATCH 29/30] [Doc] Ship rofi-thumbnails.5 With some formatting fixes --- Makefile.am | 9 +++++++-- README.md | 1 + doc/meson.build | 1 + doc/rofi-thumbnails.5.markdown | 33 ++++++++++++++++++++++++--------- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Makefile.am b/Makefile.am index adcda08c1..509f7761c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -186,7 +186,8 @@ generate-manpage: doc/rofi.1\ doc/rofi-dmenu.5\ doc/rofi-keys.5\ doc/rofi-script.5\ - doc/rofi-theme.5 + doc/rofi-theme.5 \ + doc/rofi-thumbnails.5 doc/rofi.1: doc/rofi.1.markdown pandoc --standalone --to=man --lua-filter=$(top_srcdir)/doc/man_filter.lua -f markdown-tex_math_dollars -o ./$@ ./$< @@ -204,6 +205,8 @@ doc/rofi-script.5: doc/rofi-script.5.markdown pandoc --standalone --to=man --lua-filter=$(top_srcdir)/doc/man_filter.lua -f markdown-tex_math_dollars -o ./$@ ./$< doc/rofi-theme.5: doc/rofi-theme.5.markdown pandoc --standalone --to=man --lua-filter=$(top_srcdir)/doc/man_filter.lua -f markdown-tex_math_dollars -o ./$@ ./$< +doc/rofi-thumbnails.5: doc/rofi-thumbnails.5.markdown + pandoc --standalone --to=man --lua-filter=$(top_srcdir)/doc/man_filter.lua -f markdown-tex_math_dollars -o ./$@ ./$< endif @@ -217,10 +220,12 @@ dist_man5_MANS=\ doc/rofi-dmenu.5\ doc/rofi-keys.5\ doc/rofi-script.5\ - doc/rofi-theme.5 + doc/rofi-theme.5\ + doc/rofi-thumbnails.5 EXTRA_DIST += \ doc/rofi-theme.5.markdown \ + doc/rofi-thumbnails.5.markdown \ doc/rofi-debugging.5.markdown \ doc/rofi-script.5.markdown \ doc/rofi-keys.5.markdown \ diff --git a/README.md b/README.md index 830447013..49cecb731 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ new issue. - [rofi-debugging](doc/rofi-debugging.5.markdown) - [rofi-script](doc/rofi-script.5.markdown) - [rofi-theme-selector](doc/rofi-theme-selector.1.markdown) + - [rofi-thumbnails](doc/rofi-thumbnails.5.markdown) - [rofi-keys](doc/rofi-keys.5.markdown) - [rofi-dmenu](doc/rofi-dmenu.5.markdown) diff --git a/doc/meson.build b/doc/meson.build index b5bb2f015..7c399eb4c 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -7,6 +7,7 @@ man_files = [ 'rofi-keys.5', 'rofi-script.5', 'rofi-theme.5', + 'rofi-thumbnails.5', ] fs = import('fs') diff --git a/doc/rofi-thumbnails.5.markdown b/doc/rofi-thumbnails.5.markdown index 04cc66a66..d49c6b8c7 100644 --- a/doc/rofi-thumbnails.5.markdown +++ b/doc/rofi-thumbnails.5.markdown @@ -6,41 +6,53 @@ ## DESCRIPTION -rofi is now able to show thumbnails for all file types where an XDG compatible thumbnailer is present in the system. -This is done by default in filebrowser and recursivebrowser mode, if rofi is launched with the `-show-icons` argument. +**rofi** is now able to show thumbnails for all file types where an XDG compatible thumbnailer is present in the system. + +This is done by default in filebrowser and recursivebrowser mode, if **rofi** is launched with the `-show-icons` argument. + In a custom user script or dmenu mode, it is possible to produce entry icons using XDG thumbnailers by adding the prefix `thumbnail://` to the filename specified after `\0icon\x1f`, for example: + ```bash - echo -en "EntryName\0icon\x1fthumbnail://path/to/file\n" | rofi -dmenu -show-icons +echo -en "EntryName\0icon\x1fthumbnail://path/to/file\n" | rofi -dmenu -show-icons ``` ### XDG thumbnailers XDG thumbnailers are files with a ".thumbnailer" suffix and a structure similar to ".desktop" files for launching applications. They are placed in `/usr/share/thumbnailers/` or `$HOME/.local/share/thumbnailers/`, and contain a list of mimetypes, for which is possible to produce the thumbnail image, and a string with the command to create said image. The example below shows the content of `ffmpegthumbnailer.thumbnailer`, a thumbnailer for video files using ffmpeg: + ``` [Thumbnailer Entry] TryExec=ffmpegthumbnailer Exec=ffmpegthumbnailer -i %i -o %o -s %s -f MimeType=video/jpeg;video/mp4;video/mpeg;video/quicktime;video/x-ms-asf;video/x-ms-wm;video/x-ms-wmv;video/x-ms-asx;video/x-ms-wmx;video/x-ms-wvx;video/x-msvideo;video/x-flv;video/x-matroska;application/mxf;video/3gp;video/3gpp;video/dv;video/divx;video/fli;video/flv;video/mp2t;video/mp4v-es;video/msvideo;video/ogg;video/vivo;video/vnd.divx;video/vnd.mpegurl;video/vnd.rn-realvideo;application/vnd.rn-realmedia;video/vnd.vivo;video/webm;video/x-anim;video/x-avi;video/x-flc;video/x-fli;video/x-flic;video/x-m4v;video/x-mpeg;video/x-mpeg2;video/x-nsv;video/x-ogm+ogg;video/x-theora+ogg ``` -The images produced are named as the md5sum of the input files and placed, depending on their size, in the XDG thumbnails directories: `$HOME/.cache/thumbnails/{normal,large,x-large,xx-large}`. They are then loaded by rofi as entry icons and can also be used by file managers like Thunar, Caja or KDE Dolphin to show their thumbnails. Additionally, if a thumbnail for a file is found in the thumbnails directories (produced previously by rofi or a file manager), rofi will load it instead of calling the thumbnailer. -If a suitable thumbnailer for a given file is not found, rofi will try to use the corresponding mimetype icon from the icon theme. + +The images produced are named as the md5sum of the input files and placed, depending on their size, in the XDG thumbnails directories: `$HOME/.cache/thumbnails/{normal,large,x-large,xx-large}`. They are then loaded by **rofi** as entry icons and can also be used by file managers like Thunar, Caja or KDE Dolphin to show their thumbnails. Additionally, if a thumbnail for a file is found in the thumbnails directories (produced previously by **rofi** or a file manager), **rofi** will load it instead of calling the thumbnailer. + +If a suitable thumbnailer for a given file is not found, **rofi** will try to use the corresponding mimetype icon from the icon theme. ### Custom command to create thumbnails It is possible to use a custom command to generate thumbnails for generic entry names, for example a script that downloads an icon given its url or selects different icons depending on the input. This can be done providing the `-preview-cmd` argument followed by a string with the command to execute, with the following syntax: + ``` - rofi ... -preview-cmd 'path/to/script_or_cmd "{input}" "{output}" "{size}"' +rofi ... -preview-cmd 'path/to/script_or_cmd "{input}" "{output}" "{size}"' ``` -rofi will call the script or command substituting `{input}` with the input entry icon name (the string after `\0icon\x1fthumbnail://`), `{output}` with the output filename of the thumbnail and `{size}` with the requested thumbnail size. The script or command is responsible of producing a thumbnail image (if possible respecting the requested size) and saving it in the given `{output}` filename. + +**rofi** will call the script or command substituting `{input}` with the input entry icon name (the string after `\0icon\x1fthumbnail://`), `{output}` with the output filename of the thumbnail and `{size}` with the requested thumbnail size. The script or command is responsible of producing a thumbnail image (if possible respecting the requested size) and saving it in the given `{output}` filename. ### Issues with AppArmor + In Linux distributions using AppArmor (such as Ubuntu and Debian), the default rules shipped can cause issues with thumbnails generation. If that is the case, AppArmor can be disabled by issuing the following commands + ``` sudo systemctl stop apparmor sudo systemctl disable apparmor ``` + In alternative, the following apparmor profile con be placed in a file named /etc/apparmor.d/usr.bin.rofi + ``` #vim:syntax=apparmor # AppArmor policy for rofi @@ -63,8 +75,11 @@ In alternative, the following apparmor profile con be placed in a file named /et owner @{HOME}/.cache/thumbnails/** rw, } ``` -then run + +then run + ``` apparmor_parser -r /etc/apparmor.d/usr.bin.rofi ``` -to reload the rule. This assumes that rofi binary is in /usr/bin, that is the case of a standard package installation. + +to reload the rule. This assumes that **rofi** binary is in /usr/bin, that is the case of a standard package installation. From 8a671cb80002ee7c8b34cf5ce13dfa188debb04b Mon Sep 17 00:00:00 2001 From: giomatfois62 Date: Fri, 21 Jun 2024 07:26:49 +0200 Subject: [PATCH 30/30] use a more compact thumbnailer example --- doc/rofi-thumbnails.5.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/rofi-thumbnails.5.markdown b/doc/rofi-thumbnails.5.markdown index d49c6b8c7..e18ced7e1 100644 --- a/doc/rofi-thumbnails.5.markdown +++ b/doc/rofi-thumbnails.5.markdown @@ -19,13 +19,13 @@ echo -en "EntryName\0icon\x1fthumbnail://path/to/file\n" | rofi -dmenu -show-ico ### XDG thumbnailers -XDG thumbnailers are files with a ".thumbnailer" suffix and a structure similar to ".desktop" files for launching applications. They are placed in `/usr/share/thumbnailers/` or `$HOME/.local/share/thumbnailers/`, and contain a list of mimetypes, for which is possible to produce the thumbnail image, and a string with the command to create said image. The example below shows the content of `ffmpegthumbnailer.thumbnailer`, a thumbnailer for video files using ffmpeg: +XDG thumbnailers are files with a ".thumbnailer" suffix and a structure similar to ".desktop" files for launching applications. They are placed in `/usr/share/thumbnailers/` or `$HOME/.local/share/thumbnailers/`, and contain a list of mimetypes, for which is possible to produce the thumbnail image, and a string with the command to create said image. The example below shows the content of `librsvg.thumbnailer`, a thumbnailer for svg files using librsvg: ``` [Thumbnailer Entry] -TryExec=ffmpegthumbnailer -Exec=ffmpegthumbnailer -i %i -o %o -s %s -f -MimeType=video/jpeg;video/mp4;video/mpeg;video/quicktime;video/x-ms-asf;video/x-ms-wm;video/x-ms-wmv;video/x-ms-asx;video/x-ms-wmx;video/x-ms-wvx;video/x-msvideo;video/x-flv;video/x-matroska;application/mxf;video/3gp;video/3gpp;video/dv;video/divx;video/fli;video/flv;video/mp2t;video/mp4v-es;video/msvideo;video/ogg;video/vivo;video/vnd.divx;video/vnd.mpegurl;video/vnd.rn-realvideo;application/vnd.rn-realmedia;video/vnd.vivo;video/webm;video/x-anim;video/x-avi;video/x-flc;video/x-fli;video/x-flic;video/x-m4v;video/x-mpeg;video/x-mpeg2;video/x-nsv;video/x-ogm+ogg;video/x-theora+ogg +TryExec=/usr/bin/gdk-pixbuf-thumbnailer +Exec=/usr/bin/gdk-pixbuf-thumbnailer -s %s %u %o +MimeType=image/svg+xml;image/svg+xml-compressed; ``` The images produced are named as the md5sum of the input files and placed, depending on their size, in the XDG thumbnails directories: `$HOME/.cache/thumbnails/{normal,large,x-large,xx-large}`. They are then loaded by **rofi** as entry icons and can also be used by file managers like Thunar, Caja or KDE Dolphin to show their thumbnails. Additionally, if a thumbnail for a file is found in the thumbnails directories (produced previously by **rofi** or a file manager), **rofi** will load it instead of calling the thumbnailer.