From 728ec54dc50eb6c50f0bfae141817119f2f5f2bc Mon Sep 17 00:00:00 2001 From: Jason Beetham Date: Sun, 28 Dec 2025 23:47:34 -0700 Subject: [PATCH 1/3] Unix now uses XDG paths to find icons, making it easier to provide images. Defaulting to hicolor for icons --- .gitignore | 1 + src/image.c | 44 +++++++++++++++++++++++++++++++++++++++++++- src/image.h | 3 +++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c94817c..487fbaf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ docs/Gemfile.lock docs/_site docs/.jekyll-cache +build/ diff --git a/src/image.c b/src/image.c index df4a7c8..a06a686 100644 --- a/src/image.c +++ b/src/image.c @@ -11,6 +11,7 @@ #include "util.h" #include "debug.h" #include +#include #define NANOSVG_IMPLEMENTATION #include #define NANOSVGRAST_IMPLEMENTATION @@ -106,6 +107,42 @@ int load_next_slideshow_background_async(void *data) return 0; } +#ifdef __unix__ +SDL_Surface *load_surface_from_xdg(const char *path){ + // TODO: allow the user to specify a specific theme in the config.ini + // then prefer that to `hicolor`. + // Spec for this is here: https://specifications.freedesktop.org/icon-theme/latest/#icon_lookup + SDL_Surface *surface = NULL; + const char* xdg_dirs = getenv("XDG_DATA_DIRS"); + const char* icon_path = "/icons/hicolor/scalable/apps/"; + if(xdg_dirs != NULL){ + ssize_t last_delim = -1; + ssize_t dirs_len = strlen(xdg_dirs); + while(surface == NULL && last_delim < dirs_len){ + char xdg_path[4096] = {0}; + ssize_t start_ind = last_delim; + if(start_ind < 0) + start_ind = 0; + + ssize_t end = strcspn(&xdg_dirs[start_ind], ":"); + memcpy(&xdg_path, &xdg_dirs[start_ind], end); + memcpy(&xdg_path[end], icon_path, strlen(icon_path)); + memcpy(&xdg_path[end + strlen(icon_path)], path, strlen(path)); + surface = IMG_Load(xdg_path); + if (surface == NULL) { + log_error( + "Could not load image from xdg %s\n%s", + xdg_path, + IMG_GetError() + ); + } + last_delim += end + 1; + } + } + return surface; +} +#endif + // A function to load a texture from a file SDL_Texture *load_texture_from_file(const char *path) { @@ -119,8 +156,13 @@ SDL_Texture *load_texture_from_file(const char *path) path, IMG_GetError() ); +#ifdef __unix__ + // we failed to find the image normally, lets try xdg on unix + surface = load_surface_from_xdg(path); +#endif } - else + + if(surface != NULL) texture = load_texture(surface); } return texture; diff --git a/src/image.h b/src/image.h index b32d612..065719a 100644 --- a/src/image.h +++ b/src/image.h @@ -28,6 +28,9 @@ void render_scroll_indicators(Scroll *scroll, int height, Geometry *geo); SDL_Surface *load_next_slideshow_background(Slideshow *slideshow, bool transition); int load_next_slideshow_background_async(void *data); SDL_Texture *load_texture(SDL_Surface *surface); +#ifdef __unix__ +SDL_Surface *load_surface_from_xdg(const char *path); +#endif SDL_Texture *load_texture_from_file(const char *path); SDL_Texture *rasterize_svg(char *buffer, int w, int h, SDL_Rect *rect); SDL_Texture *rasterize_svg_from_file(const char *path, int w, int h, SDL_Rect *rect); From 140057e753640a17a6d46faad652ea2f48a568f8 Mon Sep 17 00:00:00 2001 From: Jason Beetham Date: Mon, 29 Dec 2025 23:11:01 -0700 Subject: [PATCH 2/3] Support more image types from xdg data directories --- src/image.c | 59 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/image.c b/src/image.c index a06a686..88987c6 100644 --- a/src/image.c +++ b/src/image.c @@ -114,32 +114,45 @@ SDL_Surface *load_surface_from_xdg(const char *path){ // Spec for this is here: https://specifications.freedesktop.org/icon-theme/latest/#icon_lookup SDL_Surface *surface = NULL; const char* xdg_dirs = getenv("XDG_DATA_DIRS"); - const char* icon_path = "/icons/hicolor/scalable/apps/"; - if(xdg_dirs != NULL){ - ssize_t last_delim = -1; - ssize_t dirs_len = strlen(xdg_dirs); - while(surface == NULL && last_delim < dirs_len){ - char xdg_path[4096] = {0}; - ssize_t start_ind = last_delim; - if(start_ind < 0) - start_ind = 0; - - ssize_t end = strcspn(&xdg_dirs[start_ind], ":"); - memcpy(&xdg_path, &xdg_dirs[start_ind], end); - memcpy(&xdg_path[end], icon_path, strlen(icon_path)); - memcpy(&xdg_path[end + strlen(icon_path)], path, strlen(path)); - surface = IMG_Load(xdg_path); - if (surface == NULL) { - log_error( - "Could not load image from xdg %s\n%s", - xdg_path, - IMG_GetError() - ); + const char* sizes[] = { + "scalable", + "512x512", + "256x256", + "128x128", + "64x64", + "32x32" + }; + + for(int i = 0; i < sizeof(sizes) / sizeof(void*); i++){ + char icon_path[128] = {}; + snprintf(icon_path, 127, "/icons/hicolor/%s/apps/", sizes[i]); + + if(xdg_dirs != NULL){ + ssize_t last_delim = -1; + ssize_t dirs_len = strlen(xdg_dirs); + while(surface == NULL && last_delim < dirs_len){ + char xdg_path[4096] = {0}; + ssize_t start_ind = last_delim; + if(start_ind < 0) + start_ind = 0; + + ssize_t end = strcspn(&xdg_dirs[start_ind], ":"); + memcpy(&xdg_path, &xdg_dirs[start_ind], end); + memcpy(&xdg_path[end], icon_path, strlen(icon_path)); + memcpy(&xdg_path[end + strlen(icon_path)], path, strlen(path)); + surface = IMG_Load(xdg_path); + if (surface != NULL) + return surface; + + last_delim += end + 1; } - last_delim += end + 1; } } - return surface; + + log_error( + "Could not load %s from XDG_DATA_DIRS", + path + ); } #endif From c58c431ca461703c80ea6efa2e7bc809ca9e5ae4 Mon Sep 17 00:00:00 2001 From: Jason Beetham Date: Tue, 30 Dec 2025 12:45:03 -0700 Subject: [PATCH 3/3] Parses desktop files when provided as the only argument on unix, making it even easier to configure --- src/image.c | 14 +++++++--- src/launcher.c | 1 + src/util.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/util.h | 7 ++++- 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/image.c b/src/image.c index 88987c6..360865c 100644 --- a/src/image.c +++ b/src/image.c @@ -122,8 +122,9 @@ SDL_Surface *load_surface_from_xdg(const char *path){ "64x64", "32x32" }; + const char* exts[] = {"", ".svg", ".png"}; - for(int i = 0; i < sizeof(sizes) / sizeof(void*); i++){ + for(int i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++){ char icon_path[128] = {}; snprintf(icon_path, 127, "/icons/hicolor/%s/apps/", sizes[i]); @@ -140,9 +141,14 @@ SDL_Surface *load_surface_from_xdg(const char *path){ memcpy(&xdg_path, &xdg_dirs[start_ind], end); memcpy(&xdg_path[end], icon_path, strlen(icon_path)); memcpy(&xdg_path[end + strlen(icon_path)], path, strlen(path)); - surface = IMG_Load(xdg_path); - if (surface != NULL) - return surface; + for(int j = 0; j < sizeof(exts) / sizeof(exts[0]); j++){ + memcpy(&xdg_path[end + strlen(icon_path) + strlen(path)], exts[j], strlen(exts[j])); + surface = IMG_Load(xdg_path); + if (surface != NULL) + return surface; + } + + last_delim += end + 1; } diff --git a/src/launcher.c b/src/launcher.c index 3b8e0bc..8bd38d8 100755 --- a/src/launcher.c +++ b/src/launcher.c @@ -723,6 +723,7 @@ static void move_right() calculate_button_geometry(current_menu->root_entry, (int) buttons); if (config.highlight) highlight->rect.x = current_entry->icon_rect.x - config.highlight_hpadding; + highlight->rect.x = current_entry->icon_rect.x - config.highlight_hpadding; current_menu->page++; current_menu->highlight_position = 0; } diff --git a/src/util.c b/src/util.c index 1df240b..a97f2cf 100755 --- a/src/util.c +++ b/src/util.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -124,6 +125,65 @@ void parse_config_file(const char *config_file_path) log_fatal("Could not parse config file"); } +#ifdef __unix__ +int desktop_config_handler(void *user, const char *section, const char *name, const char *value){ + Entry* entry = (Entry*)(user); + if(MATCH(section, "Desktop Entry")){ + if(MATCH(name, "Icon")) + entry->icon_path = strdup(value); + + if(MATCH(name, "Exec")){ + ssize_t end = strcspn(value, "%@"); + char* cmd = malloc(end + 1); + cmd[end] = '\0'; + memcpy(cmd, value, end); + entry->cmd = cmd; + log_error("Parsed command '%s' from desktop file.", cmd); + } + + if(MATCH(name, "Name")) + entry->title = strdup(value); + return 0; + } + return 1; +} + + +Entry parse_possible_desktop_file(const char* name){ + const char* xdg_dirs = getenv("XDG_DATA_DIRS"); + const char* app_path = "/applications/"; + + Entry result = {}; + if(xdg_dirs != NULL){ + ssize_t last_delim = -1; + ssize_t dirs_len = strlen(xdg_dirs); + while(last_delim < dirs_len){ + char xdg_path[4096] = {0}; + ssize_t start_ind = last_delim; + if(start_ind < 0) + start_ind = 0; + + ssize_t end = strcspn(&xdg_dirs[start_ind], ":"); + memcpy(&xdg_path, &xdg_dirs[start_ind], end); + memcpy(&xdg_path[end], app_path, strlen(app_path)); + memcpy(&xdg_path[end + strlen(app_path)], name, strlen(name)); + + FILE *file = fopen(xdg_path, "r"); + if(file != NULL){ + int error = ini_parse_file(file, desktop_config_handler, &result); + fclose(file); + if(error == 0) + return result; + } + last_delim += end + 1; + } + } + return result; + +} +#endif + + // A function to handle config file parsing int config_handler(void *user, const char *section, const char *name, const char *value) { @@ -445,8 +505,19 @@ int config_handler(void *user, const char *section, const char *name, const char // Store data in entry struct int i; for (i = 0;i < 3 && token != NULL; i++) { - if (i == 0) + if (i == 0){ entry->title = strdup(token); +#ifdef __unix__ + Entry ent = parse_possible_desktop_file(entry->title); + if(ent.cmd != NULL || ent.icon_path != NULL || ent.title != NULL){ + entry->cmd = ent.cmd; + entry->icon_path = ent.icon_path; + entry->title = ent.title; + i = 3; + break; + } +#endif + } else if (i == 1) { entry->icon_path = strdup(token); clean_path(entry->icon_path); @@ -1101,4 +1172,4 @@ void sprintf_alloc(char **buffer, const char *format, ...) } va_end(args1); va_end(args2); -} \ No newline at end of file +} diff --git a/src/util.h b/src/util.h index 90dc0cf..b6bc676 100755 --- a/src/util.h +++ b/src/util.h @@ -23,6 +23,11 @@ struct gamepad_info { }; int config_handler(void *user, const char *section, const char *name, const char *value); +#ifdef __unix__ +int desktop_config_handler(void *user, const char *section, const char *name, const char *value); +Entry parse_possible_desktop_file(const char* name); +#endif + int convert_percent(const char *string, int max_value); const char *get_mode_setting(int type, int value); int utf8_length(const char *string); @@ -46,4 +51,4 @@ void read_file(const char *path, char **buffer); void sprintf_alloc(char **buffer, const char *format, ...); Uint16 get_unicode_code_point(const char *p, int *bytes); Menu *get_menu(const char *menu_name); -Entry *advance_entries(Entry *entry, int spaces, Direction direction); \ No newline at end of file +Entry *advance_entries(Entry *entry, int spaces, Direction direction);