pax_global_header00006660000000000000000000000064146723357250014530gustar00rootroot0000000000000052 comment=b3936ed9826375334dbbaa999e7216d1b36b445b libsfdo-0.1.3/000077500000000000000000000000001467233572500131535ustar00rootroot00000000000000libsfdo-0.1.3/.clang-format000066400000000000000000000035731467233572500155360ustar00rootroot00000000000000AlignAfterOpenBracket: DontAlign AlignArrayOfStructures: None AlignConsecutiveAssignments: None AlignConsecutiveBitFields: None AlignConsecutiveDeclarations: None AlignConsecutiveMacros: None AlignEscapedNewlines: DontAlign AlignOperands: DontAlign AlignTrailingComments: Never AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortEnumsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true BinPackArguments: true BinPackParameters: true BitFieldColonSpacing: Both BracedInitializerIndentWidth: 4 BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeTernaryOperators: true ColumnLimit: 100 ContinuationIndentWidth: 8 DisableFormat: false IncludeBlocks: Regroup IncludeCategories: - Regex: "^<" Priority: -1 - Regex: ".*" Priority: 0 IndentCaseBlocks: false IndentCaseLabels: false IndentGotoLabels: false IndentPPDirectives: None IndentWidth: 4 IndentWrappedFunctionNames: false InsertBraces: true InsertNewlineAtEOF: true KeepEmptyLinesAtTheStartOfBlocks: false Language: Cpp LineEnding: LF MaxEmptyLinesToKeep: 1 PointerAlignment: Right ReflowComments: true RemoveSemicolon: true SortIncludes: CaseSensitive SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAroundPointerQualifiers: Default SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeParens: ControlStatements SpaceBeforeSquareBrackets: false SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInCStyleCastParentheses: false SpacesInConditionalStatement: false SpacesInParentheses: false SpacesInSquareBrackets: false TabWidth: 4 UseTab: Always libsfdo-0.1.3/.editorconfig000066400000000000000000000003451467233572500156320ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true max_line_length = 100 [*.[ch]] indent_style = tab indent_size = 4 [meson.build] indent_style = space indent_size = 2 libsfdo-0.1.3/.gitignore000066400000000000000000000001251467233572500151410ustar00rootroot00000000000000.* !.clang-format !.editorconfig !.gitignore !.gitlab-ci.yml !.lcovrc subprojects/* libsfdo-0.1.3/.gitlab-ci.yml000066400000000000000000000052121467233572500156070ustar00rootroot00000000000000include: - project: "freedesktop/ci-templates" ref: b61a03cabbf308e81289f7aaaf0b5a80a34ffb99 file: "/templates/fedora.yml" variables: FDO_UPSTREAM_REPO: vyivel/libsfdo MESON_COMMON_OPTIONS: > --fatal-meson-warnings -Dwerror=true -Db_sanitize=address,undefined stages: - prep - style-check - build .policy: retry: max: 2 when: - runner_system_failure - stuck_or_timeout_failure interruptible: true .fedora-40: extends: - .policy variables: FDO_DISTRIBUTION_VERSION: "40" FDO_DISTRIBUTION_TAG: "2024-09-14.2" prep-fedora-40: extends: - .fdo.container-build@fedora - .fedora-40 stage: prep variables: GIT_STRATEGY: none FDO_DISTRIBUTION_PACKAGES: > git-core pkgconf-pkg-config meson lcov clang-tools-extra gcc clang lld compiler-rt libasan libubsan libasan-static libubsan-static .image-fedora: extends: - .fdo.distribution-image@fedora - .fedora-40 clang-format: extends: - .image-fedora stage: style-check script: - meson setup build/ - ninja -C build/ clang-format-check .build-and-test: extends: - .image-fedora stage: build script: - > meson setup build/ ${MESON_COMMON_OPTIONS} ${MESON_OPTIONS} - ninja -C build/ -k0 -j${FDO_CI_CONCURRENT:-4} - meson test -C build/ --num-processes ${FDO_CI_CONCURRENT:-4} artifacts: when: always expire_in: 1 week paths: - "build/meson-logs" build-clang: extends: - .build-and-test variables: CC: clang CC_LD: lld MESON_OPTIONS: > -Db_lundef=false build-release: extends: - .build-and-test variables: MESON_OPTIONS: > -Dbuildtype=release build-coverage: extends: - .image-fedora stage: build script: - meson setup build/ ${MESON_COMMON_OPTIONS} -Db_coverage=true - ninja -C build/ -k0 -j${FDO_CI_CONCURRENT:-4} - mkdir -p build-coverage/ - > lcov --config-file .lcovrc --directory build/ --capture --initial --output-file "build-coverage/baseline.lcov" - meson test -C build/ --num-processes ${FDO_CI_CONCURRENT:-4} - > lcov --config-file .lcovrc --directory build/ --capture --output-file "build-coverage/test.lcov" - > lcov --add-tracefile "build-coverage/baseline.lcov" --add-tracefile "build-coverage/test.lcov" --output-file "build-coverage/out.lcov" artifacts: when: always expire_in: 1 week paths: - "build/meson-logs" - "build-coverage/" coverage: '/^\s+lines\.+:\s+([\d.]+\%)\s+/' libsfdo-0.1.3/.lcovrc000066400000000000000000000000311467233572500144360ustar00rootroot00000000000000lcov_branch_coverage = 1 libsfdo-0.1.3/LICENSE000066400000000000000000000024251467233572500141630ustar00rootroot00000000000000Copyright (c) 2024, Kirill Primak All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. libsfdo-0.1.3/README.md000066400000000000000000000022041467233572500144300ustar00rootroot00000000000000# libsfdo A collection of libraries which implement some of [the freedesktop.org specifications]. See respective header files for documentation. Discuss in [#eclairs on Libera.Chat]. [the freedesktop.org specifications]: https://specifications.freedesktop.org/ [#eclairs on Libera.Chat]: https://web.libera.chat/#eclairs ## Disclaimer freedesktop.org specifications are sometimes ambiguous in their requirements, leaving room for interpretation. libsfdo tries to follow them as closely as possible nonetheless, except for cases when doing so would add too much complexity for no benefit and/or result in suboptimal behavior. Additionally, libsfdo is much stricter than other implementations, so it may refuse to process non-conformant desktop entry files or icon themes. It is advised that you try to fix the offending files before opening an issue. ## Implementations Specification | Library -|- basedir-spec | `libsfdo-basedir` desktop-entry-spec | `libsfdo-desktop`, `libsfdo-desktop-file` icon-theme-spec | `libsfdo-icon` ## Building ```sh meson setup build/ ninja -C build/ ``` ## License BSD-2-Clause See `LICENSE` for more information. libsfdo-0.1.3/common/000077500000000000000000000000001467233572500144435ustar00rootroot00000000000000libsfdo-0.1.3/common/dirs.c000066400000000000000000000024551467233572500155560ustar00rootroot00000000000000#include #include #include "common/dirs.h" #include "common/membuild.h" #include "common/path.h" #include "common/size.h" bool sfdo_dirs_store(const struct sfdo_string *src_dirs, size_t n_src_dirs, struct sfdo_string **dst_dirs, size_t *n_dst_dirs, char **dst_mem) { struct sfdo_string *dirs = calloc(n_src_dirs, sizeof(*dirs)); if (dirs == NULL) { return false; } size_t mem_size = 0; for (size_t i = 0; i < n_src_dirs; i++) { const struct sfdo_string *dir = &src_dirs[i]; mem_size += dir->len + 1; if (sfdo_path_needs_extra_slash(dir->data, dir->len)) { ++mem_size; } } struct sfdo_membuild mem_buf; if (!sfdo_membuild_setup(&mem_buf, mem_size)) { free(dirs); return false; } for (size_t i = 0; i < n_src_dirs; i++) { const struct sfdo_string *src = &src_dirs[i]; struct sfdo_string *dst = &dirs[i]; dst->data = mem_buf.data + mem_buf.len; sfdo_membuild_add(&mem_buf, src->data, src->len, NULL); if (sfdo_path_needs_extra_slash(src->data, src->len)) { sfdo_membuild_add(&mem_buf, "/", SFDO_SIZE1, NULL); } dst->len = (size_t)(mem_buf.data + mem_buf.len - dst->data); sfdo_membuild_add(&mem_buf, "", SFDO_SIZE1, NULL); } assert(mem_buf.len == mem_size); *dst_dirs = dirs; *n_dst_dirs = n_src_dirs; *dst_mem = mem_buf.data; return true; } libsfdo-0.1.3/common/grow.c000066400000000000000000000014121467233572500155630ustar00rootroot00000000000000#include #include "common/grow.h" bool sfdo_grow(void *data_ptr, size_t *cap, size_t len, size_t entry_size) { return sfdo_grow_n(data_ptr, cap, len, entry_size, 1); } bool sfdo_grow_n(void *data_ptr, size_t *cap, size_t len, size_t entry_size, size_t n) { size_t new_len = len + n; if (new_len < len) { // Overflow return false; } else if (new_len < *cap) { return true; } size_t new_cap = *cap == 0 ? 256 : *cap; while (new_cap < new_len) { size_t double_cap = new_cap * 2; if (double_cap < new_cap) { // Overflow return false; } new_cap = double_cap; } void **data = data_ptr; void *new_data = realloc(*data, new_cap * entry_size); if (new_data == NULL) { return false; } *data = new_data; *cap = new_cap; return true; } libsfdo-0.1.3/common/hash.c000066400000000000000000000044051467233572500155350ustar00rootroot00000000000000#include #include #include #include "common/hash.h" static uint32_t hash_str(const char *s, size_t len) { // FNV-1a, 32 bit uint32_t h = 0x811c9dc5; for (size_t i = 0; i < len; i++) { uint32_t c = (uint32_t)s[i]; h = (h ^ c) * 0x01000193; } return h; } void sfdo_hashmap_init(struct sfdo_hashmap *map, size_t entry_size) { assert(entry_size >= sizeof(struct sfdo_hashmap_entry)); map->mem = NULL; map->len = map->cap = 0; map->entry_size = entry_size; } void sfdo_hashmap_finish(struct sfdo_hashmap *map) { free(map->mem); } void sfdo_hashmap_clear(struct sfdo_hashmap *map) { free(map->mem); map->mem = NULL; map->len = map->cap = 0; } void *sfdo_hashmap_get(struct sfdo_hashmap *map, const char *key, size_t key_len, bool add) { uint32_t hash = hash_str(key, key_len); if (map->len > 0) { for (size_t i = hash % map->cap;; i = (i + 1) % map->cap) { struct sfdo_hashmap_entry *entry = (struct sfdo_hashmap_entry *)&map->mem[map->entry_size * i]; if (entry->key == NULL) { break; } else if (entry->hash == hash && entry->key_len == key_len && (entry->key == key || memcmp(entry->key, key, key_len) == 0)) { return entry; } } } if (add) { if (map->len * 2 >= map->cap) { if (map->cap >= SIZE_MAX / 2 / map->entry_size) { return NULL; } size_t cap = map->cap == 0 ? 256 : map->cap * 2; char *mem = calloc(map->entry_size, cap); if (mem == NULL) { return NULL; } for (size_t i = 0; i < map->cap; i++) { struct sfdo_hashmap_entry *src = (struct sfdo_hashmap_entry *)&map->mem[map->entry_size * i]; if (src->key == NULL) { continue; } for (size_t j = src->hash % cap;; j = (j + 1) % cap) { struct sfdo_hashmap_entry *dst = (struct sfdo_hashmap_entry *)&mem[map->entry_size * j]; if (dst->key == NULL) { memcpy(dst, src, map->entry_size); break; } } } free(map->mem); map->mem = mem; map->cap = cap; } ++map->len; for (size_t i = hash % map->cap;; i = (i + 1) % map->cap) { struct sfdo_hashmap_entry *entry = (struct sfdo_hashmap_entry *)&map->mem[map->entry_size * i]; if (entry->key == NULL) { entry->hash = hash; entry->key_len = key_len; return entry; } } } return NULL; } libsfdo-0.1.3/common/log.c000066400000000000000000000016431467233572500153740ustar00rootroot00000000000000#include "common/log.h" static void noop_handler(enum sfdo_log_level level, const char *fmt, va_list args, void *data) { (void)level; (void)fmt; (void)args; (void)data; } void logger_setup(struct sfdo_logger *logger) { logger->level = SFDO_LOG_LEVEL_SILENT; logger->func = noop_handler; logger->data = NULL; } void logger_configure(struct sfdo_logger *logger, enum sfdo_log_level level, sfdo_log_handler_func_t func, void *data) { if (func == NULL) { func = noop_handler; } logger->level = level; logger->func = func; logger->data = data; } void logger_write(struct sfdo_logger *logger, enum sfdo_log_level level, const char *fmt, ...) { if (level > logger->level) { return; } va_list args; va_start(args, fmt); logger->func(level, fmt, args, logger->data); va_end(args); } void logger_write_oom(struct sfdo_logger *logger) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "Memory allocation failed"); } libsfdo-0.1.3/common/membuild.c000066400000000000000000000012141467233572500164030ustar00rootroot00000000000000#include #include #include #include "common/membuild.h" bool sfdo_membuild_setup(struct sfdo_membuild *membuild, size_t cap) { if (cap > 0) { membuild->data = malloc(cap); if (membuild->data == NULL) { return false; } } else { membuild->data = NULL; } membuild->len = 0; return true; } void sfdo_membuild_add(struct sfdo_membuild *membuild, ...) { va_list args; va_start(args, membuild); const char *data; while ((data = va_arg(args, const char *)) != NULL) { size_t len = va_arg(args, size_t); memcpy(&membuild->data[membuild->len], data, len); membuild->len += len; } va_end(args); } libsfdo-0.1.3/common/meson.build000066400000000000000000000002041467233572500166010ustar00rootroot00000000000000private_src = files( 'dirs.c', 'grow.c', 'hash.c', 'log.c', 'membuild.c', 'strbuild.c', 'striter.c', 'strpool.c', ) libsfdo-0.1.3/common/strbuild.c000066400000000000000000000025221467233572500164400ustar00rootroot00000000000000#include #include #include #include #include "common/strbuild.h" void sfdo_strbuild_init(struct sfdo_strbuild *strbuild) { strbuild->data = NULL; strbuild->len = strbuild->cap = 0; } void sfdo_strbuild_finish(struct sfdo_strbuild *strbuild) { free(strbuild->data); } void sfdo_strbuild_reset(struct sfdo_strbuild *strbuild) { strbuild->len = 0; } bool sfdo_strbuild_add(struct sfdo_strbuild *strbuild, ...) { va_list args; va_start(args, strbuild); size_t total_len = 0; while (va_arg(args, const char *) != NULL) { total_len += va_arg(args, size_t); } va_end(args); size_t cap = strbuild->cap == 0 ? 4096 : strbuild->cap; // end must be strictly bigger than cap to have space for a null terminator size_t end = strbuild->len + total_len; while (end >= cap) { if (cap > SIZE_MAX / 2) { return false; } cap *= 2; } if (strbuild->cap != cap) { char *pb_data = realloc(strbuild->data, cap); if (pb_data == NULL) { return false; } strbuild->data = pb_data; strbuild->cap = cap; } va_start(args, strbuild); const char *data; while ((data = va_arg(args, const char *)) != NULL) { size_t len = va_arg(args, size_t); memcpy(&strbuild->data[strbuild->len], data, len); strbuild->len += len; } va_end(args); strbuild->data[strbuild->len] = '\0'; return true; } libsfdo-0.1.3/common/striter.c000066400000000000000000000006171467233572500163070ustar00rootroot00000000000000#include "common/striter.h" bool sfdo_striter(const char *list, char sep, size_t *iter, size_t *start, size_t *len) { if (list[*iter] == '\0') { return false; } *start = *iter; while (true) { if (list[*iter] == '\0') { *len = *iter - *start; break; } else if (list[*iter] == sep) { *len = *iter - *start; ++(*iter); break; } else { ++(*iter); } } return true; } libsfdo-0.1.3/common/strpool.c000066400000000000000000000037251467233572500163200ustar00rootroot00000000000000#include #include #include #include "common/strpool.h" struct sfdo_strpool_chunk { struct sfdo_strpool_chunk *next; char data[]; }; #define CHUNK_MIN_SIZE (4096 - sizeof(struct sfdo_strpool_chunk) - 8) const char *sfdo_strpool_add(struct sfdo_strpool *pool, const char *data, size_t len) { if (len == 0) { return ""; } size_t size = len + 1; char *out = NULL; if (size > pool->n_free) { size_t data_size = size > CHUNK_MIN_SIZE ? size : CHUNK_MIN_SIZE; struct sfdo_strpool_chunk *chunk = malloc(sizeof(*chunk) + data_size); if (chunk == NULL) { return NULL; } size_t chunk_nfree = data_size - size; if (chunk_nfree < pool->n_free) { // Put the new chunk after head assert(pool->chunks != NULL); chunk->next = pool->chunks->next; pool->chunks->next = chunk; } else { // The new chunk is the new head chunk->next = pool->chunks; pool->chunks = chunk; pool->n_free = chunk_nfree; } out = chunk->data; } else { // If there's free space, the total size is CHUNK_MIN_SIZE char *start = pool->chunks->data + CHUNK_MIN_SIZE - pool->n_free; pool->n_free -= size; out = start; } memcpy(out, data, len); out[len] = '\0'; return out; } void sfdo_strpool_init(struct sfdo_strpool *pool) { pool->chunks = NULL; pool->n_free = 0; } void sfdo_strpool_finish(struct sfdo_strpool *pool) { struct sfdo_strpool_chunk *chunk = pool->chunks; while (chunk != NULL) { struct sfdo_strpool_chunk *next = chunk->next; free(chunk); chunk = next; } } void sfdo_strpool_save(struct sfdo_strpool *pool, struct sfdo_strpool_state *state) { state->chunk = pool->chunks; state->n_free = pool->n_free; } void sfdo_strpool_restore(struct sfdo_strpool *pool, struct sfdo_strpool_state *state) { struct sfdo_strpool_chunk *chunk = pool->chunks; while (chunk != state->chunk) { struct sfdo_strpool_chunk *next = chunk->next; free(chunk); chunk = next; } pool->chunks = chunk; pool->n_free = state->n_free; } libsfdo-0.1.3/examples/000077500000000000000000000000001467233572500147715ustar00rootroot00000000000000libsfdo-0.1.3/examples/basedir-dump.c000066400000000000000000000027661467233572500175240ustar00rootroot00000000000000#include #include #include static void print_dir(const char *name, const char *dir, size_t dir_len) { printf("%s: ", name); if (dir != NULL) { printf("%s (%zu bytes)\n", dir, dir_len); } else { printf("\n"); } } static void print_dir_list(const char *name, const struct sfdo_string *dirs, size_t n_dirs) { printf("%s: %zu dirs\n", name, n_dirs); for (size_t i = 0; i < n_dirs; i++) { printf(" %zu: %s (%zu bytes)\n", i, dirs[i].data, dirs[i].len); } } int main(void) { struct sfdo_basedir_ctx *ctx = sfdo_basedir_ctx_create(); if (ctx == NULL) { fprintf(stderr, "sfdo_basedir_ctx_create() failed\n"); exit(1); } const char *dir; size_t dir_len; const struct sfdo_string *dirs; size_t n_dirs; dir = sfdo_basedir_get_data_home(ctx, &dir_len); print_dir("XDG_DATA_HOME", dir, dir_len); dirs = sfdo_basedir_get_data_system_dirs(ctx, &n_dirs); print_dir_list("XDG_DATA_DIRS", dirs, n_dirs); dir = sfdo_basedir_get_config_home(ctx, &dir_len); print_dir("XDG_CONFIG_HOME", dir, dir_len); dirs = sfdo_basedir_get_config_system_dirs(ctx, &n_dirs); print_dir_list("XDG_CONFIG_DIRS", dirs, n_dirs); dir = sfdo_basedir_get_state_home(ctx, &dir_len); print_dir("XDG_STATE_HOME", dir, dir_len); dir = sfdo_basedir_get_cache_home(ctx, &dir_len); print_dir("XDG_CACHE_HOME", dir, dir_len); dir = sfdo_basedir_get_runtime_dir(ctx, &dir_len); print_dir("XDG_RUNTIME_DIR", dir, dir_len); sfdo_basedir_ctx_destroy(ctx); return 0; } libsfdo-0.1.3/examples/desktop-dump.c000066400000000000000000000042031467233572500175500ustar00rootroot00000000000000#include #include #include #include #include #include static void log_handler(enum sfdo_log_level level, const char *fmt, va_list args, void *data) { (void)level; (void)data; static const char *levels[] = { [SFDO_LOG_LEVEL_SILENT] = "", [SFDO_LOG_LEVEL_ERROR] = "error", [SFDO_LOG_LEVEL_INFO] = "info", [SFDO_LOG_LEVEL_DEBUG] = "debug", }; fprintf(stdout, "[%s] ", levels[level]); vfprintf(stdout, fmt, args); fprintf(stdout, "\n"); } static void die_usage(const char *prog) { printf("Usage: %s [-d] [-e environment] [-l locale]\n", prog); exit(1); } int main(int argc, char **argv) { bool debug = false; const char *locale = NULL; const char *env = NULL; char *prog = argv[0]; int opt; while ((opt = getopt(argc, argv, "de:l:")) != -1) { switch (opt) { case 'd': debug = true; break; case 'e': env = optarg; break; case 'l': locale = optarg; break; default: die_usage(prog); } } argv += optind; argc -= optind; if (argc > 0) { die_usage(prog); } struct sfdo_basedir_ctx *basedir_ctx = sfdo_basedir_ctx_create(); struct sfdo_desktop_ctx *ctx = sfdo_desktop_ctx_create(basedir_ctx); sfdo_desktop_ctx_set_log_handler( ctx, debug ? SFDO_LOG_LEVEL_DEBUG : SFDO_LOG_LEVEL_ERROR, log_handler, NULL); struct sfdo_desktop_db *db = sfdo_desktop_db_load(ctx, locale); if (db == NULL) { fprintf(stderr, "Failed to load a database\n"); exit(1); } size_t n_entries; struct sfdo_desktop_entry **entries = sfdo_desktop_db_get_entries(db, &n_entries); size_t env_len = 0; if (env != NULL) { env_len = strlen(env); } for (size_t i = 0; i < n_entries; i++) { struct sfdo_desktop_entry *entry = entries[i]; if (sfdo_desktop_entry_get_no_display(entry)) { continue; } if (!sfdo_desktop_entry_show_in(entry, env, env_len)) { continue; } const char *id = sfdo_desktop_entry_get_id(entry, NULL); const char *name = sfdo_desktop_entry_get_name(entry, NULL); printf("%s: %s\n", id, name); } sfdo_desktop_db_destroy(db); sfdo_desktop_ctx_destroy(ctx); sfdo_basedir_ctx_destroy(basedir_ctx); return 0; } libsfdo-0.1.3/examples/desktop-exec.c000066400000000000000000000070221467233572500175310ustar00rootroot00000000000000#include #include #include #include #include #include #include #include static void log_handler(enum sfdo_log_level level, const char *fmt, va_list args, void *data) { (void)level; (void)data; static const char *levels[] = { [SFDO_LOG_LEVEL_SILENT] = "", [SFDO_LOG_LEVEL_ERROR] = "error", [SFDO_LOG_LEVEL_INFO] = "info", [SFDO_LOG_LEVEL_DEBUG] = "debug", }; fprintf(stdout, "[%s] ", levels[level]); vfprintf(stdout, fmt, args); fprintf(stdout, "\n"); } static void die_usage(const char *prog) { printf("Usage: %s [-dp] [-a ] [args…]\n", prog); exit(1); } static struct sfdo_desktop_exec *get_exec( struct sfdo_desktop_db *db, const char *id, size_t id_len, const char *action_id) { struct sfdo_desktop_entry *entry = sfdo_desktop_db_get_entry_by_id(db, id, id_len); if (entry == NULL) { fprintf(stderr, "Entry not found\n"); return NULL; } else if (sfdo_desktop_entry_get_type(entry) != SFDO_DESKTOP_ENTRY_APPLICATION) { fprintf(stderr, "Not an application\n"); return NULL; } struct sfdo_desktop_exec *exec = NULL; if (action_id != NULL) { size_t n_actions; struct sfdo_desktop_entry_action **actions = sfdo_desktop_entry_get_actions(entry, &n_actions); for (size_t i = 0; i < n_actions; i++) { struct sfdo_desktop_entry_action *iter = actions[i]; const char *iter_id = sfdo_desktop_entry_action_get_id(iter, NULL); if (strcmp(iter_id, action_id) == 0) { exec = sfdo_desktop_entry_action_get_exec(iter); if (exec == NULL) { fprintf(stderr, "Action is not executable\n"); return NULL; } break; } } if (exec == NULL) { fprintf(stderr, "Action not found\n"); } } else { exec = sfdo_desktop_entry_get_exec(entry); if (exec == NULL) { fprintf(stderr, "Application is not executable\n"); return NULL; } } return exec; } int main(int argc, char **argv) { bool debug = false; const char *action = NULL; bool print = false; char *prog = argv[0]; int opt; while ((opt = getopt(argc, argv, "dpa:")) != -1) { switch (opt) { case 'd': debug = true; break; case 'p': print = true; break; case 'a': action = optarg; break; default: die_usage(prog); } } argv += optind; argc -= optind; if (argc < 1) { die_usage(prog); } const char *id = argv[0]; size_t id_len = strlen(id); ++argv; --argc; struct sfdo_basedir_ctx *basedir_ctx = sfdo_basedir_ctx_create(); struct sfdo_desktop_ctx *ctx = sfdo_desktop_ctx_create(basedir_ctx); sfdo_desktop_ctx_set_log_handler( ctx, debug ? SFDO_LOG_LEVEL_DEBUG : SFDO_LOG_LEVEL_ERROR, log_handler, NULL); struct sfdo_desktop_db *db = sfdo_desktop_db_load(ctx, NULL); if (db == NULL) { fprintf(stderr, "Failed to load a database\n"); exit(1); } struct sfdo_desktop_exec *exec = get_exec(db, id, id_len, action); if (exec != NULL) { struct sfdo_desktop_exec_command *cmd = sfdo_desktop_exec_format_list(exec, (const char **)argv, (size_t)argc); size_t n_args; const char **args = sfdo_desktop_exec_command_get_args(cmd, &n_args); if (print) { for (size_t i = 0; i < n_args; i++) { printf("%zu: %s\n", i + 1, args[i]); } } else { pid_t child = fork(); if (child == 0) { execvp(args[0], (char *const *)args); fprintf(stderr, "execvp() failed: %s\n", strerror(errno)); exit(1); } } sfdo_desktop_exec_command_destroy(cmd); } sfdo_desktop_db_destroy(db); sfdo_desktop_ctx_destroy(ctx); sfdo_basedir_ctx_destroy(basedir_ctx); return 0; } libsfdo-0.1.3/examples/desktop-file-query.c000066400000000000000000000047741467233572500207020ustar00rootroot00000000000000#include #include #include #include #include struct query { const char *group_name; const char *key; size_t key_len; }; static void die_usage(const char *prog) { printf("Usage: %s [-d] [-l locale] [group/key...]\n", prog); exit(1); } int main(int argc, char **argv) { int options = SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT; const char *locale = NULL; char *prog = argv[0]; int opt; while ((opt = getopt(argc, argv, "dl:")) != -1) { switch (opt) { case 'd': options |= SFDO_DESKTOP_FILE_LOAD_ALLOW_DUPLICATE_GROUPS; break; case 'l': locale = optarg; break; default: die_usage(prog); } } argv += optind; argc -= optind; if (argc < 1) { die_usage(prog); } const char *path = argv[0]; FILE *fp = fopen(path, "r"); if (fp == NULL) { fprintf(stderr, "Failed to open: %s: %s\n", path, strerror(errno)); exit(1); } argv += 1; argc -= 1; struct query *queries = NULL; size_t n_queries = (size_t)argc; if (n_queries > 0) { queries = calloc(n_queries, sizeof(*queries)); if (queries == NULL) { fprintf(stderr, "Memory allocation error\n"); exit(1); } } for (size_t i = 0; i < n_queries; i++) { char *query_s = argv[i]; char *key_p = strchr(query_s, '/'); if (key_p == NULL) { die_usage(prog); } *(key_p++) = '\0'; struct query *query = &queries[i]; query->group_name = query_s; query->key = key_p; query->key_len = strlen(query->key); } struct sfdo_desktop_file_error error; struct sfdo_desktop_file_document *doc = sfdo_desktop_file_document_load(fp, locale, options, &error); fclose(fp); if (doc == NULL) { fprintf(stderr, "%d:%d: %s\n", error.line, error.column, sfdo_desktop_file_error_code_get_description(error.code)); exit(1); } for (struct sfdo_desktop_file_group *group = sfdo_desktop_file_document_get_groups(doc); group != NULL; group = sfdo_desktop_file_group_get_next(group)) { const char *name = sfdo_desktop_file_group_get_name(group, NULL); for (size_t i = 0; i < n_queries; i++) { struct query *query = &queries[i]; if (strcmp(query->group_name, name) == 0) { struct sfdo_desktop_file_entry *entry = sfdo_desktop_file_group_get_entry(group, query->key, query->key_len); if (entry != NULL) { const char *value = sfdo_desktop_file_entry_get_localized_value(entry, NULL); printf("%s/%s: %s\n", name, query->key, value); break; } } } } sfdo_desktop_file_document_destroy(doc); free(queries); return 0; } libsfdo-0.1.3/examples/desktop-load.c000066400000000000000000000140001467233572500175160ustar00rootroot00000000000000#include #include #include #include #include #include #include static void log_handler(enum sfdo_log_level level, const char *fmt, va_list args, void *data) { (void)level; (void)data; static const char *levels[] = { [SFDO_LOG_LEVEL_SILENT] = "", [SFDO_LOG_LEVEL_ERROR] = "error", [SFDO_LOG_LEVEL_INFO] = "info", [SFDO_LOG_LEVEL_DEBUG] = "debug", }; fprintf(stdout, "[%s] ", levels[level]); vfprintf(stdout, fmt, args); fprintf(stdout, "\n"); } static void print_str(const char *name, const char *str, size_t str_len) { printf("%s: ", name); if (str != NULL) { printf("%s (%zu bytes)\n", str, str_len); } else { printf("\n"); } } static void print_exec(const char *name, struct sfdo_desktop_exec *exec) { printf("%s: <", name); if (exec != NULL) { if (sfdo_desktop_exec_get_has_target(exec)) { printf("has target"); if (sfdo_desktop_exec_get_supports_list(exec)) { printf(", supports lists"); } if (sfdo_desktop_exec_get_supports_uri(exec)) { printf(", supports URIs"); } } else { printf("has no target"); } } else { printf("none"); } printf(">\n"); } static void print_list(const char *name, const struct sfdo_string *strs, size_t n_strs) { printf("%s:\n", name); for (size_t i = 0; i < n_strs; i++) { printf(" %zu: %s (%zu bytes)\n", i, strs[i].data, strs[i].len); } } static void print_boolean(const char *name, bool value) { printf("%s: %s\n", name, value ? "true" : "false"); } static void die_usage(const char *prog) { printf("Usage: %s [-d] [-e environment] [-l locale] \n", prog); exit(1); } int main(int argc, char **argv) { bool debug = false; const char *locale = NULL; const char *env = NULL; char *prog = argv[0]; int opt; while ((opt = getopt(argc, argv, "de:l:")) != -1) { switch (opt) { case 'd': debug = true; break; case 'e': env = optarg; break; case 'l': locale = optarg; break; default: die_usage(prog); } } argv += optind; argc -= optind; if (argc < 1) { die_usage(prog); } const char *id = argv[0]; size_t id_len = strlen(id); struct sfdo_basedir_ctx *basedir_ctx = sfdo_basedir_ctx_create(); struct sfdo_desktop_ctx *ctx = sfdo_desktop_ctx_create(basedir_ctx); sfdo_desktop_ctx_set_log_handler( ctx, debug ? SFDO_LOG_LEVEL_DEBUG : SFDO_LOG_LEVEL_ERROR, log_handler, NULL); struct sfdo_desktop_db *db = sfdo_desktop_db_load(ctx, locale); if (db == NULL) { fprintf(stderr, "Failed to load a database\n"); exit(1); } struct sfdo_desktop_entry *entry = sfdo_desktop_db_get_entry_by_id(db, id, id_len); if (entry != NULL) { const char *str; size_t str_len; const struct sfdo_string *strs; size_t n_strs; str = sfdo_desktop_entry_get_id(entry, &str_len); print_str("ID", str, str_len); str = sfdo_desktop_entry_get_file_path(entry, &str_len); print_str("File path", str, str_len); str = sfdo_desktop_entry_get_name(entry, &str_len); print_str("Name", str, str_len); str = sfdo_desktop_entry_get_generic_name(entry, &str_len); print_str("GenericName", str, str_len); print_boolean("NoDisplay", sfdo_desktop_entry_get_no_display(entry)); str = sfdo_desktop_entry_get_comment(entry, &str_len); print_str("Comment", str, str_len); str = sfdo_desktop_entry_get_icon(entry, &str_len); print_str("Icon", str, str_len); print_boolean("Shown", sfdo_desktop_entry_show_in(entry, env, SFDO_NT)); strs = sfdo_desktop_entry_get_implements(entry, &n_strs); print_list("Implements", strs, n_strs); switch (sfdo_desktop_entry_get_type(entry)) { case SFDO_DESKTOP_ENTRY_APPLICATION: print_boolean("DBusActivatable", sfdo_desktop_entry_get_dbus_activatable(entry)); print_exec("Exec", sfdo_desktop_entry_get_exec(entry)); str = sfdo_desktop_entry_get_try_exec(entry, &str_len); print_str("TryExec", str, str_len); str = sfdo_desktop_entry_get_path(entry, &str_len); print_str("Path", str, str_len); print_boolean("Terminal", sfdo_desktop_entry_get_terminal(entry)); strs = sfdo_desktop_entry_get_mimetypes(entry, &n_strs); print_list("MimeType", strs, n_strs); strs = sfdo_desktop_entry_get_categories(entry, &n_strs); print_list("Categories", strs, n_strs); strs = sfdo_desktop_entry_get_keywords(entry, &n_strs); print_list("Keywords", strs, n_strs); const char *startup_notify = NULL; switch (sfdo_desktop_entry_get_startup_notify(entry)) { case SFDO_DESKTOP_ENTRY_STARTUP_NOTIFY_FALSE: startup_notify = "false"; break; case SFDO_DESKTOP_ENTRY_STARTUP_NOTIFY_TRUE: startup_notify = "true"; break; case SFDO_DESKTOP_ENTRY_STARTUP_NOTIFY_UNKNOWN: startup_notify = "unknown"; break; } assert(startup_notify != NULL); printf("StartupNotify: %s\n", startup_notify); str = sfdo_desktop_entry_get_startup_wm_class(entry, &str_len); print_str("StartupWMClass", str, str_len); print_boolean( "PrefersNonDefaultGPU", sfdo_desktop_entry_get_prefers_non_default_gpu(entry)); print_boolean("SingleMainWindow", sfdo_desktop_entry_get_single_main_window(entry)); size_t n_actions; struct sfdo_desktop_entry_action **actions = sfdo_desktop_entry_get_actions(entry, &n_actions); printf("Actions:\n"); for (size_t i = 0; i < n_actions; i++) { struct sfdo_desktop_entry_action *action = actions[i]; str = sfdo_desktop_entry_action_get_id(action, &str_len); assert(str != NULL); printf(" %s (%zu bytes):\n", str, str_len); str = sfdo_desktop_entry_action_get_name(action, &str_len); print_str(" Name", str, str_len); str = sfdo_desktop_entry_action_get_icon(action, &str_len); print_str(" Icon", str, str_len); print_exec(" Exec", sfdo_desktop_entry_action_get_exec(action)); } break; case SFDO_DESKTOP_ENTRY_LINK: str = sfdo_desktop_entry_get_url(entry, &str_len); print_str("URL", str, str_len); break; case SFDO_DESKTOP_ENTRY_DIRECTORY: break; } } sfdo_desktop_db_destroy(db); sfdo_desktop_ctx_destroy(ctx); sfdo_basedir_ctx_destroy(basedir_ctx); return 0; } libsfdo-0.1.3/examples/icon-lookup.c000066400000000000000000000053751467233572500174060ustar00rootroot00000000000000#include #include #include #include #include #include #define N_NAMES_MAX 16 static void log_handler(enum sfdo_log_level level, const char *fmt, va_list args, void *data) { (void)level; (void)data; static const char *levels[] = { [SFDO_LOG_LEVEL_SILENT] = "", [SFDO_LOG_LEVEL_ERROR] = "error", [SFDO_LOG_LEVEL_INFO] = "info", [SFDO_LOG_LEVEL_DEBUG] = "debug", }; fprintf(stdout, "[%s] ", levels[level]); vfprintf(stdout, fmt, args); fprintf(stdout, "\n"); } static void die_usage(const char *prog) { printf("Usage: %s [-dmrs] \n", prog); exit(1); } int main(int argc, char **argv) { int load_options = SFDO_ICON_THEME_LOAD_OPTIONS_DEFAULT; int lookup_options = SFDO_ICON_THEME_LOOKUP_OPTIONS_DEFAULT; bool debug = false; char *prog = argv[0]; int opt; while ((opt = getopt(argc, argv, "dmrs")) != -1) { switch (opt) { case 'd': debug = true; break; case 'm': load_options |= SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING; break; case 'r': load_options |= SFDO_ICON_THEME_LOAD_OPTION_RELAXED; break; case 's': lookup_options |= SFDO_ICON_THEME_LOOKUP_OPTION_NO_SVG; break; default: die_usage(prog); } } argv += optind; argc -= optind; if (argc < 4) { die_usage(prog); } const char *theme_name = argv[0]; int size = atoi(argv[1]); int scale = atoi(argv[2]); argv += 3; argc -= 3; size_t n_names = (size_t)argc; if (n_names >= N_NAMES_MAX) { n_names = N_NAMES_MAX; } struct sfdo_string names[N_NAMES_MAX]; for (size_t i = 0; i < n_names; i++) { names[i].data = argv[i]; names[i].len = strlen(argv[i]); } struct sfdo_basedir_ctx *basedir_ctx = sfdo_basedir_ctx_create(); if (basedir_ctx == NULL) { fprintf(stderr, "sfdo_basedir_ctx_create() failed\n"); exit(1); } struct sfdo_icon_ctx *ctx = sfdo_icon_ctx_create(basedir_ctx); if (ctx == NULL) { fprintf(stderr, "sfdo_icon_ctx_create() failed\n"); exit(1); } sfdo_icon_ctx_set_log_handler( ctx, debug ? SFDO_LOG_LEVEL_DEBUG : SFDO_LOG_LEVEL_ERROR, log_handler, NULL); struct sfdo_icon_theme *theme = sfdo_icon_theme_load(ctx, theme_name, load_options); if (theme == NULL) { fprintf(stderr, "Failed to load the icon theme\n"); exit(1); } bool found = false; struct sfdo_icon_file *file = sfdo_icon_theme_lookup_best(theme, names, n_names, size, scale, lookup_options); if (file == SFDO_ICON_FILE_INVALID) { fprintf(stderr, "Failed to look up the icon\n"); exit(1); } else if (file != NULL) { printf("%s\n", sfdo_icon_file_get_path(file, NULL)); found = true; } sfdo_icon_file_destroy(file); sfdo_icon_theme_destroy(theme); sfdo_icon_ctx_destroy(ctx); sfdo_basedir_ctx_destroy(basedir_ctx); return found ? 0 : 1; } libsfdo-0.1.3/examples/meson.build000066400000000000000000000013631467233572500171360ustar00rootroot00000000000000examples = [ { 'name': 'basedir-dump', 'deps': [ sfdo_basedir, ], }, { 'name': 'desktop-file-query', 'deps': [ sfdo_desktop_file, ], }, { 'name': 'desktop-dump', 'deps': [ sfdo_basedir, sfdo_desktop, ], }, { 'name': 'desktop-load', 'deps': [ sfdo_basedir, sfdo_desktop, ], }, { 'name': 'desktop-exec', 'deps': [ sfdo_basedir, sfdo_desktop, ], }, { 'name': 'icon-lookup', 'deps': [ sfdo_basedir, sfdo_icon, ], }, ] foreach template : examples name = template['name'] executable( name, name + '.c', include_directories: include_dir, dependencies: template['deps'], ) endforeach libsfdo-0.1.3/include/000077500000000000000000000000001467233572500145765ustar00rootroot00000000000000libsfdo-0.1.3/include/common/000077500000000000000000000000001467233572500160665ustar00rootroot00000000000000libsfdo-0.1.3/include/common/api.h000066400000000000000000000001351467233572500170070ustar00rootroot00000000000000#ifndef API_H #define API_H #define SFDO_API __attribute__((visibility("default"))) #endif libsfdo-0.1.3/include/common/dirs.h000066400000000000000000000003661467233572500172050ustar00rootroot00000000000000#ifndef COMMON_DIRS_H #define COMMON_DIRS_H #include #include bool sfdo_dirs_store(const struct sfdo_string *src_dirs, size_t n_src_dirs, struct sfdo_string **dst_dirs, size_t *n_dst_dirs, char **dst_mem); #endif libsfdo-0.1.3/include/common/grow.h000066400000000000000000000003661467233572500172220ustar00rootroot00000000000000#ifndef GROW_H #define GROW_H #include #include bool sfdo_grow(void *data_ptr, size_t *cap, size_t len, size_t entry_size); bool sfdo_grow_n(void *data_ptr, size_t *cap, size_t len, size_t entry_size, size_t n); #endif libsfdo-0.1.3/include/common/hash.h000066400000000000000000000012261467233572500171630ustar00rootroot00000000000000#ifndef HASH_H #define HASH_H #include #include #include struct sfdo_hashmap_entry { uint32_t hash; const char *key; // Borrowed, NULL if empty size_t key_len; }; struct sfdo_hashmap { char *mem; size_t len, cap; size_t entry_size; }; void sfdo_hashmap_init(struct sfdo_hashmap *map, size_t entry_size); void sfdo_hashmap_finish(struct sfdo_hashmap *map); void sfdo_hashmap_clear(struct sfdo_hashmap *map); // Returns a complete entry if it was found, entry with key == NULL if it was just added, // NULL otherwise void *sfdo_hashmap_get(struct sfdo_hashmap *map, const char *key, size_t key_len, bool add); #endif libsfdo-0.1.3/include/common/log.h000066400000000000000000000012711467233572500170210ustar00rootroot00000000000000#ifndef LOG_H #define LOG_H #include struct sfdo_logger { enum sfdo_log_level level; sfdo_log_handler_func_t func; void *data; }; void logger_setup(struct sfdo_logger *logger); void logger_configure(struct sfdo_logger *logger, enum sfdo_log_level level, sfdo_log_handler_func_t func, void *data); #ifdef __GNUC__ #define SFDO_ATTRIBUTE_PRINTF(start, end) __attribute__((format(printf, start, end))) #else #define SFDO_ATTRIBUTE_PRINTF(start, end) #endif void logger_write(struct sfdo_logger *logger, enum sfdo_log_level level, const char *fmt, ...) SFDO_ATTRIBUTE_PRINTF(3, 4); #undef SFDO_ATTRIBUTE_PRINTF void logger_write_oom(struct sfdo_logger *logger); #endif libsfdo-0.1.3/include/common/membuild.h000066400000000000000000000004631467233572500200400ustar00rootroot00000000000000#ifndef MEMBUILD_H #define MEMBUILD_H #include #include struct sfdo_membuild { char *data; size_t len; }; bool sfdo_membuild_setup(struct sfdo_membuild *membuild, size_t cap); // (const char *, size_t)*, NULL void sfdo_membuild_add(struct sfdo_membuild *membuild, ...); #endif libsfdo-0.1.3/include/common/path.h000066400000000000000000000003141467233572500171710ustar00rootroot00000000000000#ifndef PATH_H #define PATH_H #include #include static inline bool sfdo_path_needs_extra_slash(const char *path, size_t len) { return len >= 2 && path[len - 1] != '/'; } #endif libsfdo-0.1.3/include/common/size.h000066400000000000000000000001631467233572500172110ustar00rootroot00000000000000#ifndef SIZE_H #define SIZE_H // To be used with sfdo_{str,mem}build_add() #define SFDO_SIZE1 ((size_t)1) #endif libsfdo-0.1.3/include/common/strbuild.h000066400000000000000000000006411467233572500200700ustar00rootroot00000000000000#ifndef STRBUILD_H #define STRBUILD_H #include #include struct sfdo_strbuild { char *data; size_t len, cap; }; void sfdo_strbuild_init(struct sfdo_strbuild *strbuild); void sfdo_strbuild_finish(struct sfdo_strbuild *strbuild); void sfdo_strbuild_reset(struct sfdo_strbuild *strbuild); // (const char *, size_t)*, NULL bool sfdo_strbuild_add(struct sfdo_strbuild *strbuild, ...); #endif libsfdo-0.1.3/include/common/striter.h000066400000000000000000000002601467233572500177310ustar00rootroot00000000000000#ifndef STRITER_H #define STRITER_H #include #include bool sfdo_striter(const char *list, char sep, size_t *iter, size_t *start, size_t *len); #endif libsfdo-0.1.3/include/common/strpool.h000066400000000000000000000012021467233572500177340ustar00rootroot00000000000000#ifndef STRPOOL_H #define STRPOOL_H #include #include struct sfdo_strpool_chunk; struct sfdo_strpool { struct sfdo_strpool_chunk *chunks; size_t n_free; }; struct sfdo_strpool_state { struct sfdo_strpool_chunk *chunk; size_t n_free; }; void sfdo_strpool_init(struct sfdo_strpool *pool); void sfdo_strpool_finish(struct sfdo_strpool *pool); const char *sfdo_strpool_add(struct sfdo_strpool *pool, const char *data, size_t len); void sfdo_strpool_save(struct sfdo_strpool *pool, struct sfdo_strpool_state *state); void sfdo_strpool_restore(struct sfdo_strpool *pool, struct sfdo_strpool_state *state); #endif libsfdo-0.1.3/include/sfdo-basedir.h000066400000000000000000000062301467233572500173120ustar00rootroot00000000000000#ifndef SFDO_BASEDIR_H #define SFDO_BASEDIR_H #ifdef __cplusplus extern "C" { #endif #include #include // libsfdo-basedir implements the XDG base directory specification, version 0.8: // // https://specifications.freedesktop.org/basedir-spec/0.8/ // // For convenience, all paths end with a slash. struct sfdo_basedir_ctx; // Create a context. // // Returns NULL on memory allocation error. struct sfdo_basedir_ctx *sfdo_basedir_ctx_create(void); // Destroy a context. // // ctx may be NULL, in which case the function is no-op. void sfdo_basedir_ctx_destroy(struct sfdo_basedir_ctx *ctx); // Get the preference-ordered set of base directories to search for data files including the // user-specific directory. // // The number of directories is saved to n_directories. const struct sfdo_string *sfdo_basedir_get_data_dirs( struct sfdo_basedir_ctx *ctx, size_t *n_directories); // Get the base directory relative to which user-specific data files should be stored. // // The length of the path is saved to len. len may be NULL. const char *sfdo_basedir_get_data_home(struct sfdo_basedir_ctx *ctx, size_t *len); // Get the preference-ordered set of base directories to search for data files excluding the // user-specific directory. // // The number of directories is saved to n_directories. const struct sfdo_string *sfdo_basedir_get_data_system_dirs( struct sfdo_basedir_ctx *ctx, size_t *n_directories); // Get the preference-ordered set of base directories to search for configuration files including // the user-specific directory. // // The number of directories is saved to n_directories. const struct sfdo_string *sfdo_basedir_get_config_dirs( struct sfdo_basedir_ctx *ctx, size_t *n_directories); // Get the base directory relative to which user-specific configuration files should be stored. // // The length of the path is saved to len. len may be NULL. const char *sfdo_basedir_get_config_home(struct sfdo_basedir_ctx *ctx, size_t *len); // Get the preference-ordered set of base directories to search for configuration files excluding // the user-specific directory. // // The number of directories is saved to n_directories. const struct sfdo_string *sfdo_basedir_get_config_system_dirs( struct sfdo_basedir_ctx *ctx, size_t *n_directories); // Get the base directory relative to which user-specific state files should be stored. // // The length of the path is saved to len. len may be NULL. const char *sfdo_basedir_get_state_home(struct sfdo_basedir_ctx *ctx, size_t *len); // Get the base directory relative to which user-specific non-essential data files should be stored. // // The length of the path is saved to len. len may be NULL. const char *sfdo_basedir_get_cache_home(struct sfdo_basedir_ctx *ctx, size_t *len); // Get the base directory relative to which user-specific non-essential runtime files and other file // objects (such as sockets, named pipes, ...) should be stored. // // The length of the path is saved to len. len may be NULL. // // Returns NULL if the corresponding environment variable is unset or invalid. const char *sfdo_basedir_get_runtime_dir(struct sfdo_basedir_ctx *ctx, size_t *len); #ifdef __cplusplus } #endif #endif libsfdo-0.1.3/include/sfdo-common.h000066400000000000000000000011571467233572500171740ustar00rootroot00000000000000#ifndef SFDO_COMMON_H #define SFDO_COMMON_H #ifdef __cplusplus extern "C" { #endif #include #include // Used to specify that a sequence of characters is null-terminated. #define SFDO_NT ((size_t)(-1)) struct sfdo_string { const char *data; // UTF-8, null-terminated size_t len; // In octets, excluding the null terminator }; enum sfdo_log_level { SFDO_LOG_LEVEL_SILENT, SFDO_LOG_LEVEL_ERROR, SFDO_LOG_LEVEL_INFO, SFDO_LOG_LEVEL_DEBUG, }; typedef void (*sfdo_log_handler_func_t)( enum sfdo_log_level level, const char *fmt, va_list args, void *data); #ifdef __cplusplus } #endif #endif libsfdo-0.1.3/include/sfdo-desktop-file.h000066400000000000000000000105721467233572500202730ustar00rootroot00000000000000#ifndef SFDO_DESKTOP_FILE_H #define SFDO_DESKTOP_FILE_H #ifdef __cplusplus extern "C" { #endif #include #include #include // libsfdo-desktop-file implements the desktop entry file format from the desktop entry // specification, version 1.5: // // https://specifications.freedesktop.org/desktop-entry-spec/1.5/ enum sfdo_desktop_file_error_code { SFDO_DESKTOP_FILE_ERROR_NONE = 0, SFDO_DESKTOP_FILE_ERROR_IO, SFDO_DESKTOP_FILE_ERROR_NT, SFDO_DESKTOP_FILE_ERROR_UTF8, SFDO_DESKTOP_FILE_ERROR_OOM, SFDO_DESKTOP_FILE_ERROR_SYNTAX, SFDO_DESKTOP_FILE_ERROR_DUPLICATE_GROUP, SFDO_DESKTOP_FILE_ERROR_DUPLICATE_KEY, SFDO_DESKTOP_FILE_ERROR_NO_DEFAULT_VALUE, }; struct sfdo_desktop_file_error { enum sfdo_desktop_file_error_code code; int line, column; }; struct sfdo_desktop_file_document; struct sfdo_desktop_file_group; struct sfdo_desktop_file_entry; enum sfdo_desktop_file_load_options { // The default desktop entry file loading options. SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT = 0, // If this flag is set, the loader will allow groups with the same names. It becomes the // responsibility of the caller to handle duplicate groups correctly. SFDO_DESKTOP_FILE_LOAD_ALLOW_DUPLICATE_GROUPS = 1 << 0, }; // Load a document. // // locale is a string of form lang_COUNTRY.ENCODING@MODIFIER, where _COUNTRY, .ENCODING, and // @MODIFIER may be omitted. It is used to select the best values for localized strings. May be // NULL. // // options is a result of bitwise OR of zero or more enum sfdo_desktop_file_load_options values. // // Returns NULL on failure, in which case the information about the error is saved to error. error // may be NULL. struct sfdo_desktop_file_document *sfdo_desktop_file_document_load( FILE *fp, const char *locale, int options, struct sfdo_desktop_file_error *error); // Destroy a document. // // document may be NULL, in which case the function is no-op. void sfdo_desktop_file_document_destroy(struct sfdo_desktop_file_document *document); // Get the first group of the document. // // Returns NULL if the document has no groups. struct sfdo_desktop_file_group *sfdo_desktop_file_document_get_groups( struct sfdo_desktop_file_document *document); // Get the description of an error by its code. const char *sfdo_desktop_file_error_code_get_description(enum sfdo_desktop_file_error_code code); // Get the next group of the document. // // Returns NULL if there is no next group. struct sfdo_desktop_file_group *sfdo_desktop_file_group_get_next( struct sfdo_desktop_file_group *group); // Get the name of a group. // // The length of the name is saved to len. len may be NULL. const char *sfdo_desktop_file_group_get_name(struct sfdo_desktop_file_group *group, size_t *len); // Get the group location in the file. // // The location is saved to line and column. line and column may be NULL. void sfdo_desktop_file_group_get_location( struct sfdo_desktop_file_group *group, int *line, int *column); // Get an entry from a group by a key. // // If key_len is equal to SFDO_NT, key is assumed to be null-terminated. // // Returns NULL if there is no matching entry. struct sfdo_desktop_file_entry *sfdo_desktop_file_group_get_entry( struct sfdo_desktop_file_group *group, const char *key, size_t key_len); // Get the entry key. // // The length of the key is saved to len. len may be NULL. const char *sfdo_desktop_file_entry_get_key(struct sfdo_desktop_file_entry *entry, size_t *len); // Get the entry value. // // The length of the value is saved to len. len may be NULL. const char *sfdo_desktop_file_entry_get_value(struct sfdo_desktop_file_entry *entry, size_t *len); // Get the localized entry value. // // The length of the value is saved to len. len may be NULL. const char *sfdo_desktop_file_entry_get_localized_value( struct sfdo_desktop_file_entry *entry, size_t *len); // Get the entry value list. const struct sfdo_string *sfdo_desktop_file_entry_get_value_list( struct sfdo_desktop_file_entry *entry, size_t *n_items); // Get the localized entry value list. const struct sfdo_string *sfdo_desktop_file_entry_get_localized_value_list( struct sfdo_desktop_file_entry *entry, size_t *n_items); // Get the entry location in the file. // // The location is saved to line and column. line and column may be NULL. void sfdo_desktop_file_entry_get_location( struct sfdo_desktop_file_entry *entry, int *line, int *column); #ifdef __cplusplus } #endif #endif libsfdo-0.1.3/include/sfdo-desktop.h000066400000000000000000000256641467233572500173660ustar00rootroot00000000000000#ifndef SFDO_DESKTOP_H #define SFDO_DESKTOP_H #ifdef __cplusplus extern "C" { #endif #include #include // libsfdo-desktop implements the desktop entry specification, version 1.5: // // https://specifications.freedesktop.org/desktop-entry-spec/1.5/ struct sfdo_basedir_ctx; enum sfdo_desktop_entry_type { SFDO_DESKTOP_ENTRY_APPLICATION, SFDO_DESKTOP_ENTRY_LINK, SFDO_DESKTOP_ENTRY_DIRECTORY, }; enum sfdo_desktop_entry_startup_notify { // The application does not work with startup notification at all. SFDO_DESKTOP_ENTRY_STARTUP_NOTIFY_FALSE, // The application will send a "remove" message when started with the DESKTOP_STARTUP_ID // environment variable set. SFDO_DESKTOP_ENTRY_STARTUP_NOTIFY_TRUE, // The startup notification support status is unknown. SFDO_DESKTOP_ENTRY_STARTUP_NOTIFY_UNKNOWN, }; struct sfdo_desktop_exec; struct sfdo_desktop_exec_command; struct sfdo_desktop_ctx; struct sfdo_desktop_db; struct sfdo_desktop_entry; struct sfdo_desktop_entry_action; // Create a context. // // basedir_ctx is used to create the default list of paths which are scanned for desktop entry // files. Once the context is created, basedir_ctx is not referenced anymore and can be destroyed. // // basedir_ctx may be NULL, in which case the default list is empty. // // Returns NULL on memory allocation error. struct sfdo_desktop_ctx *sfdo_desktop_ctx_create(struct sfdo_basedir_ctx *basedir_ctx); // Destroy a context. // // ctx may be NULL, in which case the function is no-op. void sfdo_desktop_ctx_destroy(struct sfdo_desktop_ctx *ctx); // Set the context log handler. // // func will be called for each message with a level lower than or equal to level. func may be NULL. void sfdo_desktop_ctx_set_log_handler(struct sfdo_desktop_ctx *ctx, enum sfdo_log_level level, sfdo_log_handler_func_t func, void *data); // Load a desktop entry database from the default list of paths. // // locale is a string of form lang_COUNTRY.ENCODING@MODIFIER, where _COUNTRY, .ENCODING, and // @MODIFIER may be omitted. It is used to select the best values for localized strings. May be // NULL. // // Returns NULL on memory allocation error. struct sfdo_desktop_db *sfdo_desktop_db_load(struct sfdo_desktop_ctx *ctx, const char *locale); // Load a desktop entry database. // // locale is a string of form lang_COUNTRY.ENCODING@MODIFIER, where _COUNTRY, .ENCODING, and // @MODIFIER may be omitted. It is used to select the best values for localized strings. May be // NULL. // // Returns NULL on memory allocation error. struct sfdo_desktop_db *sfdo_desktop_db_load_from(struct sfdo_desktop_ctx *ctx, const char *locale, const struct sfdo_string *basedirs, size_t n_basedirs); // Destroy a desktop entry database. // // db may be NULL, in which case the function is no-op. void sfdo_desktop_db_destroy(struct sfdo_desktop_db *db); // Get a desktop entry by an ID. // // If id_len is equal to SFDO_NT, id is assumed to be null-terminated. // // Returns NULL if there is no matching desktop entry. struct sfdo_desktop_entry *sfdo_desktop_db_get_entry_by_id( struct sfdo_desktop_db *db, const char *id, size_t id_len); // Get the list of desktop entries in a database. // // The number of entries is saved to n_entries. struct sfdo_desktop_entry **sfdo_desktop_db_get_entries( struct sfdo_desktop_db *db, size_t *n_entries); // Get the desktop entry type. enum sfdo_desktop_entry_type sfdo_desktop_entry_get_type(struct sfdo_desktop_entry *entry); // Get the desktop entry ID. // // The length of the ID is saved to len. len may be NULL. const char *sfdo_desktop_entry_get_id(struct sfdo_desktop_entry *entry, size_t *len); // Get the desktop entry file path. // // The length of the path is saved to len. len may be NULL. const char *sfdo_desktop_entry_get_file_path(struct sfdo_desktop_entry *entry, size_t *len); // Get the desktop entry name. // // The length of the name is saved to len. len may be NULL. const char *sfdo_desktop_entry_get_name(struct sfdo_desktop_entry *entry, size_t *len); // Get the generic desktop entry name. // // The length of the name is saved to len. len may be NULL. // // Returns NULL if the corresponding key is absent. const char *sfdo_desktop_entry_get_generic_name(struct sfdo_desktop_entry *entry, size_t *len); // Returns true if the desktop entry shouldn't be displayed to the user, false otherwise. bool sfdo_desktop_entry_get_no_display(struct sfdo_desktop_entry *entry); // Get the desktop entry tooltip. // // The length of the tooltip is saved to len. len may be NULL. // // Returns NULL if the corresponding key is absent. const char *sfdo_desktop_entry_get_comment(struct sfdo_desktop_entry *entry, size_t *len); // Get the desktop entry icon name/path. // // The length of the name/path is saved to len. len may be NULL. // // Returns NULL if the corresponding key is absent. const char *sfdo_desktop_entry_get_icon(struct sfdo_desktop_entry *entry, size_t *len); // env is the desktop environment name. env may be NULL, in which case the default value is // returned. // // If env_len is equal to SFDO_NT and env is not NULL, env is assumed to be null-terminated. // // Returns true if the desktop entry should be shown. bool sfdo_desktop_entry_show_in(struct sfdo_desktop_entry *entry, const char *env, size_t env_len); // Returns true if D-Bus activation is supported for the application. // // The desktop entry type must be "Application". bool sfdo_desktop_entry_get_dbus_activatable(struct sfdo_desktop_entry *entry); // // The desktop entry type must be "Application". // // Returns NULL if the corresponding key is absent. const char *sfdo_desktop_entry_get_try_exec(struct sfdo_desktop_entry *entry, size_t *len); // Get the application's command template. // // The desktop entry type must be "Application". // // Returns NULL if the corresponding key is absent. struct sfdo_desktop_exec *sfdo_desktop_entry_get_exec(struct sfdo_desktop_entry *entry); // Get the working directory to run the application in. // // The desktop entry type must be "Application". // // Returns NULL if the corresponding key is absent. const char *sfdo_desktop_entry_get_path(struct sfdo_desktop_entry *entry, size_t *len); // Returns true if the application runs in a terminal window, false otherwise. // // The desktop entry type must be "Application". bool sfdo_desktop_entry_get_terminal(struct sfdo_desktop_entry *entry); // Get the list of application actions. // // The desktop entry type must be "Application". struct sfdo_desktop_entry_action **sfdo_desktop_entry_get_actions( struct sfdo_desktop_entry *entry, size_t *n_actions); // Get the list of MIME types supported by the application. // // The desktop entry type must be "Application". const struct sfdo_string *sfdo_desktop_entry_get_mimetypes( struct sfdo_desktop_entry *entry, size_t *n_mimetypes); // Get the list of categories in which the entry should be shown. // // The desktop entry type must be "Application". const struct sfdo_string *sfdo_desktop_entry_get_categories( struct sfdo_desktop_entry *entry, size_t *n_categories); // Get the list of interfaces the application implements. // // The desktop entry type must be "Application". const struct sfdo_string *sfdo_desktop_entry_get_implements( struct sfdo_desktop_entry *entry, size_t *n_implements); // Get the list of strings which may be used in addition to other metadata to describe the // application. // // The desktop entry type must be "Application". const struct sfdo_string *sfdo_desktop_entry_get_keywords( struct sfdo_desktop_entry *entry, size_t *n_keywords); // Get the startup notification support status. // // The desktop entry type must be "Application". enum sfdo_desktop_entry_startup_notify sfdo_desktop_entry_get_startup_notify( struct sfdo_desktop_entry *entry); // Get the WM class or WM name hint that the application will map at least one window with. // // The desktop entry type must be "Application". // // Returns NULL if the corresponding key is absent. const char *sfdo_desktop_entry_get_startup_wm_class(struct sfdo_desktop_entry *entry, size_t *len); // Get the URL to access. // // The desktop entry type must be "Link". const char *sfdo_desktop_entry_get_url(struct sfdo_desktop_entry *entry, size_t *len); // Returns true if the application prefers to be run on a more powerful discrete GPU if available. // // The desktop entry type must be "Application". bool sfdo_desktop_entry_get_prefers_non_default_gpu(struct sfdo_desktop_entry *entry); // Returns true if the application has a single main window and does not support having an // additional one opened. // // The desktop entry type must be "Application". bool sfdo_desktop_entry_get_single_main_window(struct sfdo_desktop_entry *entry); // Get the action ID. // // The length of the ID is saved to len. len may be NULL. const char *sfdo_desktop_entry_action_get_id(struct sfdo_desktop_entry_action *action, size_t *len); // Get the action name. // // The length of the name is saved to len. len may be NULL. const char *sfdo_desktop_entry_action_get_name( struct sfdo_desktop_entry_action *action, size_t *len); // Get the action icon name/path. // // The length of the name/path is saved to len. len may be NULL. // // Returns NULL if the corresponding key is absent. const char *sfdo_desktop_entry_action_get_icon( struct sfdo_desktop_entry_action *action, size_t *len); // Get the action's command template. // // Returns NULL if the corresponding key is absent. struct sfdo_desktop_exec *sfdo_desktop_entry_action_get_exec( struct sfdo_desktop_entry_action *action); // Returns true if the command template has a target, false otherwise. bool sfdo_desktop_exec_get_has_target(struct sfdo_desktop_exec *exec); // Returns true if the command template supports using a list of paths as a target, false // otherwise. bool sfdo_desktop_exec_get_supports_list(struct sfdo_desktop_exec *exec); // Returns true if the command template supports using URI(s) as a target, false otherwise. bool sfdo_desktop_exec_get_supports_uri(struct sfdo_desktop_exec *exec); // Format a command template with a given path. // // Returns NULL on memory allocation error. struct sfdo_desktop_exec_command *sfdo_desktop_exec_format( struct sfdo_desktop_exec *exec, const char *path); // Format a command template with a given list of paths. // // If the command template doesn't support using a list of paths as a target, only the first path // will be used. // // Returns NULL on memory allocation error. struct sfdo_desktop_exec_command *sfdo_desktop_exec_format_list( struct sfdo_desktop_exec *exec, const char **paths, size_t n_paths); // Get the NULL-terminated list of arguments of the formatted command. // // The number of arguments excluding the NULL terminator is saved to n_args. n_args may be NULL. const char **sfdo_desktop_exec_command_get_args( struct sfdo_desktop_exec_command *command, size_t *n_args); // Destroy a formatted command. // // command may be NULL, in which case the function is no-op. void sfdo_desktop_exec_command_destroy(struct sfdo_desktop_exec_command *command); #ifdef __cplusplus } #endif #endif libsfdo-0.1.3/include/sfdo-desktop/000077500000000000000000000000001467233572500172005ustar00rootroot00000000000000libsfdo-0.1.3/include/sfdo-desktop/internal.h000066400000000000000000000056011467233572500211670ustar00rootroot00000000000000#ifndef SFDO_DESKTOP_INTERNAL_H #define SFDO_DESKTOP_INTERNAL_H #include #include #include "common/hash.h" #include "common/log.h" #include "common/strpool.h" struct sfdo_desktop_ctx { char *default_basedirs_mem; struct sfdo_string *default_basedirs; size_t default_n_basedirs; struct sfdo_logger logger; }; enum sfdo_desktop_entry_exec_flags { SFDO_DESKTOP_ENTRY_EXEC_SUPPORTS_URI = 1 << 0, SFDO_DESKTOP_ENTRY_EXEC_SUPPORTS_LIST = 1 << 1, SFDO_DESKTOP_ENTRY_EXEC_EMBED_SINGLE = 1 << 2, }; struct sfdo_desktop_exec { const char **literals; // NULL if unset size_t n_literals; // If EMBED_SINGLE is set, target_i is the index of a literal the target is embedded into // Otherwise, targets are inserted immediately before the literal at target_i size_t target_i; // (size_t)-1 if none bool supports_uri; bool supports_list; struct { // If and only if (before + after) > 0, the target is embedded and not inserted size_t before; size_t after; } embed; }; struct sfdo_desktop_entry_action { struct sfdo_string id; struct sfdo_string name; struct sfdo_string icon; struct sfdo_desktop_exec exec; }; struct sfdo_desktop_entry { enum sfdo_desktop_entry_type type; struct sfdo_string id; struct sfdo_string file_path; struct sfdo_string name; struct sfdo_string generic_name; struct sfdo_string comment; struct sfdo_string icon; struct sfdo_string *implements; size_t n_implements; struct sfdo_string *show_exceptions; size_t n_show_exceptions; bool no_display; bool default_show; union { struct { struct sfdo_string try_exec; struct sfdo_string path; struct sfdo_string startup_wm_class; struct sfdo_desktop_exec exec; struct sfdo_string *mimetypes; size_t n_mimetypes; struct sfdo_string *categories; size_t n_categories; struct sfdo_string *keywords; size_t n_keywords; struct sfdo_desktop_entry_action *actions_mem; struct sfdo_desktop_entry_action **actions; size_t n_actions; enum sfdo_desktop_entry_startup_notify startup_notify; // SPEC: not marked as application-only but makes little sense otherwise bool dbus_activatable; bool terminal; bool prefers_non_default_gpu; bool single_main_window; } app; struct { struct sfdo_string url; } link; }; }; struct sfdo_desktop_exec_command { const char **args; // Terminated with NULL size_t n_args; // Excluding NULL char *embedded_mem; // Used for non-standalone %u/%f }; struct sfdo_desktop_map_entry { struct sfdo_hashmap_entry base; struct sfdo_desktop_entry *entry; // Owned, may be NULL if deliberately skipped }; struct sfdo_desktop_db { struct sfdo_desktop_ctx *ctx; struct sfdo_string *basedirs; size_t n_basedirs; char *basedirs_mem; struct sfdo_strpool strings; struct sfdo_hashmap entries; // sfdo_desktop_map_entry struct sfdo_desktop_entry **entries_list; // Shared with map entries size_t n_entries; }; #endif libsfdo-0.1.3/include/sfdo-icon.h000066400000000000000000000122321467233572500166300ustar00rootroot00000000000000#ifndef SFDO_ICON_H #define SFDO_ICON_H #ifdef __cplusplus extern "C" { #endif #include #include // libsfdo-icon implements the icon theme specification, version 0.13: // // https://specifications.freedesktop.org/icon-theme-spec/0.13/ // // Note that the icon lookup algorithm used by libsfdo-icon doesn't match the specification; // instead, an algorithm similar to GTK's is used as it results in better matches. // Indicates that looking up an icon has failed. #define SFDO_ICON_FILE_INVALID ((struct sfdo_icon_file *)-1) struct sfdo_basedir_ctx; struct sfdo_icon_ctx; struct sfdo_icon_theme; enum sfdo_icon_file_format { SFDO_ICON_FILE_FORMAT_PNG, SFDO_ICON_FILE_FORMAT_SVG, SFDO_ICON_FILE_FORMAT_XPM, }; struct sfdo_icon_file; enum sfdo_icon_theme_load_options { // The default icon theme loading options. SFDO_ICON_THEME_LOAD_OPTIONS_DEFAULT = 0, // If this flag is set, the loader will impose less restrictions on the format of icon theme // files. This option only exists because there are too many non-conformant themes in the wild. // It is advised that you don't set this flag by default and instead offer a way for a user to // set it manually. SFDO_ICON_THEME_LOAD_OPTION_RELAXED = 1 << 0, // If this flag is set, the loader will continue loading even if it fails to find a theme or one // of its dependencies. SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING = 1 << 1, }; enum sfdo_icon_theme_lookup_options { // The default icon theme lookup options. SFDO_ICON_THEME_LOOKUP_OPTIONS_DEFAULT = 0, // If this flag is set, SVG icons will be ignored. SFDO_ICON_THEME_LOOKUP_OPTION_NO_SVG = (1 << 0), // If this flag is set, no automatic rescan will be performed. By default, looking up an icon // will trigger a theme rescan, unless it has been already done less than 5 seconds ago. SFDO_ICON_THEME_LOOKUP_OPTION_NO_RESCAN = (1 << 1), }; // Create a context. // // basedir_ctx is used to create the default list of paths which are scanned for icon themes. Once // the context is created, basedir_ctx is not referenced anymore and can be destroyed. // // basedir_ctx may be NULL, in which case the default list is empty. // // Returns NULL on memory allocation error. struct sfdo_icon_ctx *sfdo_icon_ctx_create(struct sfdo_basedir_ctx *basedir_ctx); // Destroy a context. // // ctx may be NULL, in which case the function is no-op. void sfdo_icon_ctx_destroy(struct sfdo_icon_ctx *ctx); // Set the context log handler. // // func will be called for each message with a level lower than or equal to level. func may be NULL. void sfdo_icon_ctx_set_log_handler(struct sfdo_icon_ctx *ctx, enum sfdo_log_level level, sfdo_log_handler_func_t func, void *data); // Load an icon theme by a name from the default list of paths. // // name may be NULL, in which case the default icon theme will be loaded. // // options is a result of bitwise OR of zero or more enum sfdo_icon_theme_load_options values. // // Returns NULL on failure. struct sfdo_icon_theme *sfdo_icon_theme_load( struct sfdo_icon_ctx *ctx, const char *name, int options); // Load an icon theme by a name. // // name may be NULL, in which case the default icon theme will be loaded. // // options is a result of bitwise OR of zero or more enum sfdo_icon_theme_load_options values. // // Returns NULL on failure. struct sfdo_icon_theme *sfdo_icon_theme_load_from(struct sfdo_icon_ctx *ctx, const char *name, const struct sfdo_string *basedirs, size_t n_basedirs, int options); // Destroy an icon theme. // // theme may be NULL, in which case the function is no-op. void sfdo_icon_theme_destroy(struct sfdo_icon_theme *theme); // Forcefully rescan the icon theme directories. // // Returns true on success, false otherwise. bool sfdo_icon_theme_rescan(struct sfdo_icon_theme *theme); // Find the best matching icon file by name, size, and scale. // // options is a result of bitwise OR of zero or more enum sfdo_icon_theme_lookup_options values. // // If name_len is equal to SFDO_NT, name is assumed to be null-terminated. // // Returns NULL if no file has been found, or SFDO_ICON_FILE_INVALID on failure. struct sfdo_icon_file *sfdo_icon_theme_lookup(struct sfdo_icon_theme *theme, const char *name, size_t name_len, int size, int scale, int options); // Find the best matching icon file by names, size, and scale. // // options is a result of bitwise OR of zero or more enum sfdo_icon_theme_lookup_options values. // // If name_len is equal to SFDO_NT, name is assumed to be null-terminated. // // Returns NULL if no file has been found, or SFDO_ICON_FILE_INVALID on failure. struct sfdo_icon_file *sfdo_icon_theme_lookup_best(struct sfdo_icon_theme *theme, const struct sfdo_string *names, size_t n_names, int size, int scale, int options); // Destroy an icon file. // // file may be NULL or SFDO_ICON_FILE_INVALID, in which case the function is no-op. void sfdo_icon_file_destroy(struct sfdo_icon_file *file); // Get an icon file path. // // The length of the path is saved to len. len may be NULL. const char *sfdo_icon_file_get_path(struct sfdo_icon_file *file, size_t *len); // Get an icon file format. enum sfdo_icon_file_format sfdo_icon_file_get_format(struct sfdo_icon_file *file); #ifdef __cplusplus } #endif #endif libsfdo-0.1.3/include/sfdo-icon/000077500000000000000000000000001467233572500164575ustar00rootroot00000000000000libsfdo-0.1.3/include/sfdo-icon/internal.h000066400000000000000000000055321467233572500204510ustar00rootroot00000000000000#ifndef SFDO_ICON_INTERNAL_H #define SFDO_ICON_INTERNAL_H #include #include #include #include #include "common/hash.h" #include "common/log.h" #include "common/strbuild.h" #include "common/strpool.h" enum sfdo_icon_format_mask { SFDO_ICON_FORMAT_MASK_PNG = (1 << 0), SFDO_ICON_FORMAT_MASK_SVG = (1 << 1), SFDO_ICON_FORMAT_MASK_XPM = (1 << 2), }; struct sfdo_icon_ctx { char *default_basedirs_mem; struct sfdo_string *default_basedirs; size_t default_n_basedirs; struct sfdo_logger logger; }; struct sfdo_icon_image { const struct sfdo_string *basedir; const struct sfdo_icon_subdir *subdir; // NULL if fallback int formats; // enum sfdo_icon_format size_t next_i; // -1 if last }; struct sfdo_icon_image_list { struct sfdo_hashmap_entry base; // sfdo_icon_state.map size_t start_i, end_i; }; // Can be moved struct sfdo_icon_state { struct sfdo_hashmap map; // sfdo_icon_image_list struct sfdo_icon_image *images; struct sfdo_strpool names; time_t *dir_mtimes; bool *dir_exists; }; enum sfdo_icon_subdir_type { SFDO_ICON_SUBDIR_FIXED, SFDO_ICON_SUBDIR_SCALABLE, SFDO_ICON_SUBDIR_THRESHOLD, }; struct sfdo_icon_subdir { struct sfdo_string path; // Borrowed from theme strings, relative to / enum sfdo_icon_subdir_type type; int size, scale; int min_pixel_size, max_pixel_size; }; struct sfdo_icon_theme_node { const char *name; // Borrowed from theme strings size_t name_len; struct sfdo_icon_theme_node *next; struct sfdo_icon_subdir *subdirs; size_t n_subdirs; struct sfdo_icon_state state; }; struct sfdo_icon_theme { struct sfdo_icon_ctx *ctx; struct sfdo_icon_theme_node *nodes; struct sfdo_strpool strings; struct sfdo_string *basedirs; size_t n_basedirs; char *basedirs_mem; struct sfdo_icon_state state; struct timespec scan_time; struct sfdo_strbuild path_buf; }; struct sfdo_icon_scanner { struct sfdo_logger *logger; struct sfdo_icon_state state; size_t images_len, images_cap; struct sfdo_hashmap image_names; // sfdo_hashmap_entry }; bool icon_state_init(struct sfdo_icon_state *state, size_t n_dirs); void icon_state_finish(struct sfdo_icon_state *state); struct sfdo_icon_cache *icon_cache_create( const char *path, time_t dir_mtime, struct sfdo_logger *logger); void icon_cache_destroy(struct sfdo_icon_cache *cache); bool icon_cache_scan_dir(struct sfdo_icon_cache *cache, struct sfdo_icon_scanner *scanner, const struct sfdo_string *basedir, const struct sfdo_icon_subdir *subdir); const char *icon_scanner_intern_name( struct sfdo_icon_scanner *scanner, const char *name, size_t name_len); bool icon_scanner_add_image(struct sfdo_icon_scanner *scanner, const struct sfdo_string *basedir, const struct sfdo_icon_subdir *subdir, const char *name, size_t name_len, int formats); bool icon_theme_maybe_rescan(struct sfdo_icon_theme *theme); #endif libsfdo-0.1.3/meson.build000066400000000000000000000045041467233572500153200ustar00rootroot00000000000000project( 'libsfdo', 'c', version: '0.1.3', license: 'BSD-2-Clause', default_options: [ 'c_std=c11', 'warning_level=3', ], ) soversion = '0' cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments([ '-Wconversion', '-Wendif-labels', '-Wimplicit-fallthrough=2', '-Wlogical-op', '-Wmissing-include-dirs', '-Wmissing-prototypes', '-Wold-style-definition', '-Wpointer-arith', '-Woverflow', '-Wshadow', '-Wstrict-aliasing=2', '-Wstrict-prototypes', '-Wundef', '-fvisibility=hidden', '-D_POSIX_C_SOURCE=200809L', ]), language: 'c') include_dir = include_directories('include') subdir('common') private = static_library( 'sfdo-private', private_src, include_directories: include_dir, ) install_headers('include/sfdo-common.h') pkgconfig = import('pkgconfig') libs = [ { 'name': 'basedir', 'description': 'XDG base directory specification implementation in C', }, { 'name': 'desktop-file', 'description': 'Desktop entry file format parser library', }, { 'name': 'desktop', 'sfdo-deps': [ 'basedir', 'desktop-file', ], 'description': 'Desktop entry specification implementation in C', }, { 'name': 'icon', 'sfdo-deps': [ 'basedir', 'desktop-file', ], 'description': 'Icon theme specification implementation in C', }, ] foreach template : libs name = template['name'] subdir('sfdo-' + name) install_headers('include/sfdo-' + name + '.h') deps = template.get('deps', []) foreach sfdo_dep : template.get('sfdo-deps', []) deps += get_variable('sfdo_' + sfdo_dep.underscorify()) endforeach lib = library( 'sfdo-' + name, get_variable('sfdo_' + name.underscorify() + '_src'), include_directories: include_dir, link_with: private, dependencies: deps, version: soversion, install: true, ) dep = declare_dependency( include_directories: include_dir, link_with: lib, ) set_variable('sfdo_' + name.underscorify(), dep) full_name = 'libsfdo-' + name meson.override_dependency(full_name, dep) pkgconfig.generate( lib, name: full_name, version: meson.project_version(), description: template['description'], ) endforeach if get_option('examples') subdir('examples') endif if get_option('tests') subdir('tests') endif libsfdo-0.1.3/meson_options.txt000066400000000000000000000002321467233572500166050ustar00rootroot00000000000000option('examples', type: 'boolean', value: true, description: 'Build examples') option('tests', type: 'boolean', value: true, description: 'Build tests') libsfdo-0.1.3/scripts/000077500000000000000000000000001467233572500146425ustar00rootroot00000000000000libsfdo-0.1.3/scripts/iconcache.py000077500000000000000000000070101467233572500171310ustar00rootroot00000000000000#!/usr/bin/env python3 import sys from collections import defaultdict END = 0xFFFFFFFF def icon_hash(s): h = 0 for c in s: h = (h << 5) - h + ord(c) return h class Hole: def __init__(self, addr, size): self.addr = addr self.size = size class Cache: def __init__(self): self.data = [] def alloc(self, size): addr = len(self.data) data = [0x5A] * size self.data.extend(data) return Hole(addr, size) def alloc16(self): return self.alloc(2) def alloc32(self): return self.alloc(4) def write(self, hole, n): # Big-endian for i in range(hole.size): self.data[hole.addr + hole.size - i - 1] = (n >> (8 * i)) & 0xFF def push16(self, n): self.write(self.alloc16(), n) def push32(self, n): self.write(self.alloc32(), n) def push_strings(self, str_dict): for s, hole in str_dict.items(): self.write(hole, self.curr()) self.data.extend(s.encode() + b"\0") def curr(self): return len(self.data) def main(): dir_map = dict() icon_map = defaultdict(lambda: defaultdict(set)) icons_path = sys.argv[1] for line in open(icons_path).readlines(): line = line.strip() if not line or line.startswith("#"): continue s, _, file_name = line.rpartition("/") name, ext = file_name.split(".") dir_i = dir_map.get(s, len(dir_map)) dir_map[s] = dir_i icon_map[name][dir_i].add(ext) cache = Cache() # MAJOR_VERSION cache.push16(1) # MINOR_VERSION cache.push16(0) # HASH_OFFSET hash_off_hole = cache.alloc32() # DIRECTORY_LIST_OFFSET dir_list_off_hole = cache.alloc32() cache.write(dir_list_off_hole, cache.curr()) # N_DIRECTORIES cache.push32(len(dir_map)) dir_holes = dict() for dir in dir_map.keys(): # DIRECTORY_OFFSET dir_holes[dir] = cache.alloc32() cache.push_strings(dir_holes) buckets = [[] for _ in range(31)] for name in icon_map.keys(): h = icon_hash(name) b = buckets[h % len(buckets)] b.append(name) cache.write(hash_off_hole, cache.curr()) # N_BUCKETS cache.push32(len(buckets)) bucket_holes = [] for _ in range(len(buckets)): # ICON_OFFSET bucket_holes.append(cache.alloc32()) name_holes = dict() list_holes = dict() for i, b in enumerate(buckets): hole = bucket_holes[i] for name in b: cache.write(hole, cache.curr()) # CHAIN_OFFSET hole = cache.alloc32() # NAME_OFFSET name_holes[name] = cache.alloc32() # IMAGE_LIST_OFFSET list_holes[name] = cache.alloc32() cache.write(hole, END) cache.push_strings(name_holes) for name, dirs in icon_map.items(): hole = list_holes[name] cache.write(hole, cache.curr()) # N_IMAGES cache.push32(len(dirs)) for dir_i, exts in dirs.items(): flags = 0 if "xpm" in exts: flags |= 1 if "svg" in exts: flags |= 2 if "png" in exts: flags |= 4 # DIRECTORY_INDEX cache.push16(dir_i) # FLAGS cache.push16(flags) # IMAGE_DATA_OFFSET cache.push32(END) cache_path = sys.argv[2] cache_file = open(cache_path, "wb") cache_file.write(bytes(cache.data)) main() libsfdo-0.1.3/sfdo-basedir/000077500000000000000000000000001467233572500155155ustar00rootroot00000000000000libsfdo-0.1.3/sfdo-basedir/basedir.c000066400000000000000000000203311467233572500172710ustar00rootroot00000000000000#include #include #include #include #include "common/api.h" #include "common/membuild.h" #include "common/path.h" #include "common/size.h" #include "common/striter.h" #define DATA_HOME_FALLBACK "/.local/share/" #define CONFIG_HOME_FALLBACK "/.config/" #define STATE_HOME_FALLBACK "/.local/state/" #define CACHE_HOME_FALLBACK "/.cache/" #define DATA_DIRS_FALLBACK "/usr/local/share/:/usr/share/" #define CONFIG_DIRS_FALLBACK "/etc/xdg/" // Lists in ctx include home directories struct sfdo_basedir_ctx { char *data_dirs_mem; struct sfdo_string *data_dirs; size_t n_data_dirs; char *config_dirs_mem; struct sfdo_string *config_dirs; size_t n_config_dirs; char *state_home_mem; struct sfdo_string state_home; char *cache_home_mem; struct sfdo_string cache_home; char *runtime_dir_mem; struct sfdo_string runtime_dir; }; static inline bool is_unset_or_empty(const char *var) { return var == NULL || var[0] == '\0'; } static inline bool is_absolute(const char *path) { return path[0] == '/'; } static inline void finalize_dir(struct sfdo_membuild *mem_buf, struct sfdo_string *dir) { dir->len = (size_t)(mem_buf->data + mem_buf->len - dir->data); sfdo_membuild_add(mem_buf, "", SFDO_SIZE1, NULL); } static bool init_dir_list(struct sfdo_string **ptr, char **mem_ptr, size_t *n_dirs_ptr, const char *home, size_t home_len, const char *home_var_name, const char *home_fallback, size_t home_fallback_len, const char *list_var_name, const char *list_fallback) { const char *list = getenv(list_var_name); if (is_unset_or_empty(list)) { list = list_fallback; } const char *home_var_path = getenv(home_var_name); bool home_var_path_valid = !is_unset_or_empty(home_var_path) && is_absolute(home_var_path); size_t home_path_len; size_t mem_size; if (home_var_path_valid) { home_path_len = strlen(home_var_path); mem_size = home_path_len + 1; if (sfdo_path_needs_extra_slash(home_var_path, home_path_len)) { ++mem_size; } } else { home_path_len = home_len + home_fallback_len; mem_size = home_path_len + 1; } size_t n_dirs = 1; size_t path_start, path_len; size_t iter = 0; while (sfdo_striter(list, ':', &iter, &path_start, &path_len)) { const char *path = list + path_start; if (path_len > 0 && is_absolute(path)) { ++n_dirs; mem_size += path_len + 1; if (sfdo_path_needs_extra_slash(path, path_len)) { ++mem_size; } } } struct sfdo_string *dirs = calloc(n_dirs, sizeof(*dirs)); if (dirs == NULL) { return false; } struct sfdo_membuild mem_buf; if (!sfdo_membuild_setup(&mem_buf, mem_size)) { free(dirs); return false; } // Home directory size_t dir_i = 0; struct sfdo_string *dir = &dirs[dir_i++]; dir->data = mem_buf.data + mem_buf.len; if (home_var_path_valid) { sfdo_membuild_add(&mem_buf, home_var_path, home_path_len, NULL); if (sfdo_path_needs_extra_slash(home_var_path, home_path_len)) { sfdo_membuild_add(&mem_buf, "/", SFDO_SIZE1, NULL); } } else { sfdo_membuild_add(&mem_buf, home, home_len, home_fallback, home_fallback_len, NULL); } finalize_dir(&mem_buf, dir); iter = 0; while (sfdo_striter(list, ':', &iter, &path_start, &path_len)) { const char *path = list + path_start; if (path_len > 0 && is_absolute(path)) { dir = &dirs[dir_i++]; dir->data = mem_buf.data + mem_buf.len; sfdo_membuild_add(&mem_buf, path, path_len, NULL); if (sfdo_path_needs_extra_slash(path, path_len)) { sfdo_membuild_add(&mem_buf, "/", SFDO_SIZE1, NULL); } finalize_dir(&mem_buf, dir); } } assert(dir_i == n_dirs); assert(mem_buf.len == mem_size); *ptr = dirs; *mem_ptr = mem_buf.data; *n_dirs_ptr = n_dirs; return true; } static bool init_dir(struct sfdo_string *ptr, char **mem_ptr, const char *home, size_t home_len, const char *var_name, const char *home_fallback, size_t home_fallback_len) { const char *var_path = getenv(var_name); bool var_path_valid = !is_unset_or_empty(var_path) && is_absolute(var_path); size_t path_len; size_t mem_size; if (var_path_valid) { path_len = strlen(var_path); mem_size = path_len + 1; if (sfdo_path_needs_extra_slash(var_path, path_len)) { ++mem_size; } } else { if (home_fallback == NULL) { return true; } path_len = home_len + home_fallback_len; mem_size = path_len + 1; } struct sfdo_membuild mem_buf; if (!sfdo_membuild_setup(&mem_buf, mem_size)) { return false; } ptr->data = mem_buf.data; if (var_path_valid) { sfdo_membuild_add(&mem_buf, var_path, path_len, NULL); if (sfdo_path_needs_extra_slash(var_path, path_len)) { sfdo_membuild_add(&mem_buf, "/", SFDO_SIZE1, NULL); } } else { sfdo_membuild_add(&mem_buf, home, home_len, home_fallback, home_fallback_len, NULL); } finalize_dir(&mem_buf, ptr); assert(mem_buf.len == mem_size); *mem_ptr = mem_buf.data; return true; } struct sfdo_basedir_ctx; SFDO_API struct sfdo_basedir_ctx *sfdo_basedir_ctx_create(void) { struct sfdo_basedir_ctx *ctx = calloc(1, sizeof(*ctx)); if (ctx == NULL) { return NULL; } const char *home = getenv("HOME"); if (home == NULL) { // All home fallbacks start with "/" so results will be absolute paths home = ""; } size_t home_len = strlen(home); if (home_len >= 1 && home[home_len - 1] == '/') { // Avoid duplicate slashes --home_len; } if (!init_dir_list(&ctx->data_dirs, &ctx->data_dirs_mem, &ctx->n_data_dirs, home, home_len, "XDG_DATA_HOME", DATA_HOME_FALLBACK, sizeof(DATA_HOME_FALLBACK) - 1, "XDG_DATA_DIRS", DATA_DIRS_FALLBACK)) { goto err; } if (!init_dir_list(&ctx->config_dirs, &ctx->config_dirs_mem, &ctx->n_config_dirs, home, home_len, "XDG_CONFIG_HOME", CONFIG_HOME_FALLBACK, sizeof(CONFIG_HOME_FALLBACK) - 1, "XDG_CONFIG_DIRS", CONFIG_DIRS_FALLBACK)) { goto err; } if (!init_dir(&ctx->state_home, &ctx->state_home_mem, home, home_len, "XDG_STATE_HOME", STATE_HOME_FALLBACK, sizeof(STATE_HOME_FALLBACK) - 1)) { goto err; } if (!init_dir(&ctx->cache_home, &ctx->cache_home_mem, home, home_len, "XDG_CACHE_HOME", CACHE_HOME_FALLBACK, sizeof(CACHE_HOME_FALLBACK) - 1)) { goto err; } if (!init_dir(&ctx->runtime_dir, &ctx->runtime_dir_mem, home, home_len, "XDG_RUNTIME_DIR", NULL, 0)) { goto err; } return ctx; err: sfdo_basedir_ctx_destroy(ctx); return NULL; } SFDO_API void sfdo_basedir_ctx_destroy(struct sfdo_basedir_ctx *ctx) { if (ctx == NULL) { return; } free(ctx->data_dirs); free(ctx->config_dirs); free(ctx->data_dirs_mem); free(ctx->config_dirs_mem); free(ctx->state_home_mem); free(ctx->cache_home_mem); free(ctx->runtime_dir_mem); free(ctx); } SFDO_API const struct sfdo_string *sfdo_basedir_get_data_dirs( struct sfdo_basedir_ctx *ctx, size_t *n_directories) { *n_directories = ctx->n_data_dirs; return ctx->data_dirs; } SFDO_API const char *sfdo_basedir_get_data_home(struct sfdo_basedir_ctx *ctx, size_t *len) { struct sfdo_string *data_home = ctx->data_dirs; if (len != NULL) { *len = data_home->len; } return data_home->data; } SFDO_API const struct sfdo_string *sfdo_basedir_get_data_system_dirs( struct sfdo_basedir_ctx *ctx, size_t *n_directories) { *n_directories = ctx->n_data_dirs - 1; return ctx->data_dirs + 1; } SFDO_API const struct sfdo_string *sfdo_basedir_get_config_dirs( struct sfdo_basedir_ctx *ctx, size_t *n_directories) { *n_directories = ctx->n_config_dirs; return ctx->config_dirs; } SFDO_API const char *sfdo_basedir_get_config_home(struct sfdo_basedir_ctx *ctx, size_t *len) { struct sfdo_string *config_home = ctx->config_dirs; if (len != NULL) { *len = config_home->len; } return config_home->data; } SFDO_API const struct sfdo_string *sfdo_basedir_get_config_system_dirs( struct sfdo_basedir_ctx *ctx, size_t *n_directories) { *n_directories = ctx->n_config_dirs - 1; return ctx->config_dirs + 1; } SFDO_API const char *sfdo_basedir_get_state_home(struct sfdo_basedir_ctx *ctx, size_t *len) { if (len != NULL) { *len = ctx->state_home.len; } return ctx->state_home.data; } SFDO_API const char *sfdo_basedir_get_cache_home(struct sfdo_basedir_ctx *ctx, size_t *len) { if (len != NULL) { *len = ctx->cache_home.len; } return ctx->cache_home.data; } SFDO_API const char *sfdo_basedir_get_runtime_dir(struct sfdo_basedir_ctx *ctx, size_t *len) { if (len != NULL) { *len = ctx->runtime_dir.len; } return ctx->runtime_dir.data; } libsfdo-0.1.3/sfdo-basedir/meson.build000066400000000000000000000000531467233572500176550ustar00rootroot00000000000000sfdo_basedir_src = files( 'basedir.c', ) libsfdo-0.1.3/sfdo-desktop-file/000077500000000000000000000000001467233572500164725ustar00rootroot00000000000000libsfdo-0.1.3/sfdo-desktop-file/file.c000066400000000000000000000524521467233572500175650ustar00rootroot00000000000000#include #include #include #include #include #include "common/api.h" #include "common/grow.h" #include "common/hash.h" #include "common/membuild.h" #include "common/size.h" #define RUNE_EOF (-1) #define RUNE_NONE (-2) #define N_LOCALES_MAX 4 struct sfdo_desktop_file_value { char *data; // NULL if unset size_t len; char *items_mem; struct sfdo_string *items; size_t n_items; }; struct sfdo_desktop_file_entry { char *key; size_t key_len; struct sfdo_desktop_file_value value; // May be unset struct sfdo_desktop_file_value localized_value; int line, column; uint8_t locale_match_level; // Locale index + 1 }; struct sfdo_desktop_file_map_entry { struct sfdo_hashmap_entry base; struct sfdo_desktop_file_entry *entry; }; struct sfdo_desktop_file_group { struct sfdo_desktop_file_group *next; char *name; size_t name_len; int line, column; struct sfdo_hashmap entries; // sfdo_desktop_file_entry }; struct sfdo_desktop_file_document { struct sfdo_desktop_file_group *groups; }; struct sfdo_desktop_file_loader { struct sfdo_desktop_file_document *doc; struct sfdo_desktop_file_group *curr_group; struct sfdo_hashmap group_set; // sfdo_hashmap_entry int32_t rune; char rune_bytes[4]; size_t rune_len; char *locale_data; const char *locales[N_LOCALES_MAX]; size_t n_locales; char *buf; size_t buf_len, buf_cap; size_t *item_buf; size_t item_buf_len, item_buf_cap; int line, column; FILE *fp; struct sfdo_desktop_file_error *error; bool allow_duplicate_groups; }; static inline bool is_ws(int32_t rune) { return rune == ' ' || rune == '\t'; } static inline bool is_end(int32_t rune) { return rune == '\n' || rune == RUNE_EOF; } static inline bool is_group_char(int32_t rune) { return rune >= 0x20 && rune <= 0x7e && rune != '['; } static inline bool is_key_char(int32_t rune) { return (rune >= 'A' && rune <= 'Z') || (rune >= 'a' && rune <= 'z') || (rune >= '0' && rune <= '9') || rune == '-'; } static void set_error_at(struct sfdo_desktop_file_loader *loader, enum sfdo_desktop_file_error_code code, int line, int column) { struct sfdo_desktop_file_error *error = loader->error; error->code = code; error->line = line; error->column = column; } static void set_error( struct sfdo_desktop_file_loader *loader, enum sfdo_desktop_file_error_code code) { set_error_at(loader, code, loader->line, loader->column); } static bool peek(struct sfdo_desktop_file_loader *loader) { if (loader->rune != RUNE_NONE) { return true; } int c = fgetc(loader->fp); if (c == EOF) { if (ferror(loader->fp)) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_IO); return false; } loader->rune_len = 0; loader->rune = RUNE_EOF; return true; } int32_t rune = 0; char leader = (char)c; loader->rune_bytes[0] = leader; if ((leader & 0x80) == 0x00) { rune = leader; loader->rune_len = 1; } else if ((leader & 0xe0) == 0xc0) { rune = leader & 0x3f; loader->rune_len = 2; } else if ((leader & 0xf0) == 0xe0) { rune = leader & 0x1f; loader->rune_len = 3; } else if ((leader & 0xf8) == 0xf0) { rune = leader & 0x0f; loader->rune_len = 4; } else { set_error(loader, SFDO_DESKTOP_FILE_ERROR_UTF8); return false; } for (size_t i = 1; i < loader->rune_len; i++) { c = fgetc(loader->fp); if (c == EOF) { if (ferror(loader->fp)) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_IO); } else { set_error(loader, SFDO_DESKTOP_FILE_ERROR_UTF8); } return false; } char cont = (char)c; loader->rune_bytes[i] = cont; if ((cont & 0xc0) != 0x80) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_UTF8); return false; } rune = (rune << 6) | (cont & 0x3f); } size_t exp_len = rune <= 0x7f ? 1 : rune <= 0x7ff ? 2 : rune <= 0xffff ? 3 : 4; if (rune < 0 || rune > 0x10ffff || (rune >= 0xd8000 && rune <= 0xdffff) || loader->rune_len != exp_len) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_UTF8); return false; } else if (rune == 0) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_NT); return false; } loader->rune = rune; return true; } static inline void advance(struct sfdo_desktop_file_loader *loader) { assert(loader->rune != RUNE_NONE); if (loader->rune == '\n') { ++loader->line; loader->column = 1; } else { ++loader->column; } loader->rune = RUNE_NONE; } static bool add_bytes(struct sfdo_desktop_file_loader *loader, char *bytes, size_t len) { if (!sfdo_grow_n(&loader->buf, &loader->buf_cap, loader->buf_len, 1, len)) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_OOM); return false; } memcpy(loader->buf + loader->buf_len, bytes, len); loader->buf_len += len; return true; } static bool add_item(struct sfdo_desktop_file_loader *loader, size_t end) { if (!sfdo_grow(&loader->item_buf, &loader->item_buf_cap, loader->item_buf_len, sizeof(*loader->item_buf))) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_OOM); return false; } loader->item_buf[loader->item_buf_len++] = end; return true; } static inline bool add_rune(struct sfdo_desktop_file_loader *loader) { return add_bytes(loader, loader->rune_bytes, loader->rune_len); } static inline void reset_buf(struct sfdo_desktop_file_loader *loader) { loader->buf_len = 0; loader->item_buf_len = 0; } static bool skip_ws(struct sfdo_desktop_file_loader *loader) { while (true) { if (!peek(loader)) { return false; } else if (!is_ws(loader->rune)) { break; } advance(loader); } return true; } static bool skip_comment(struct sfdo_desktop_file_loader *loader) { assert(loader->rune == '#'); while (true) { if (!peek(loader)) { return false; } if (is_end(loader->rune)) { break; } advance(loader); } advance(loader); return true; } static bool read_group(struct sfdo_desktop_file_loader *loader) { assert(loader->rune == '['); int line = loader->line; int column = loader->column; advance(loader); reset_buf(loader); while (true) { if (!peek(loader)) { return false; } if (loader->rune == ']') { break; } else if (!is_group_char(loader->rune)) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_SYNTAX); return false; } if (!add_rune(loader)) { return false; } advance(loader); } assert(loader->rune == ']'); advance(loader); if (!skip_ws(loader)) { return false; } if (!is_end(loader->rune)) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_SYNTAX); return false; } advance(loader); struct sfdo_hashmap_entry *map_entry = sfdo_hashmap_get(&loader->group_set, loader->buf, loader->buf_len, true); if (map_entry == NULL) { set_error_at(loader, SFDO_DESKTOP_FILE_ERROR_OOM, line, column); return false; } else if (map_entry->key != NULL && !loader->allow_duplicate_groups) { set_error_at(loader, SFDO_DESKTOP_FILE_ERROR_DUPLICATE_GROUP, line, column); return false; } char *name = malloc(loader->buf_len + 1); if (name == NULL) { set_error_at(loader, SFDO_DESKTOP_FILE_ERROR_OOM, line, column); return false; } memcpy(name, loader->buf, loader->buf_len); name[loader->buf_len] = '\0'; struct sfdo_desktop_file_group *group = calloc(1, sizeof(*group)); if (group == NULL) { set_error_at(loader, SFDO_DESKTOP_FILE_ERROR_OOM, line, column); free(name); return false; } group->name = name; group->name_len = loader->buf_len; group->line = line; group->column = column; map_entry->key = name; sfdo_hashmap_init(&group->entries, sizeof(struct sfdo_desktop_file_map_entry)); if (loader->curr_group == NULL) { loader->doc->groups = group; } else { loader->curr_group->next = group; } loader->curr_group = group; return true; } static void finish_value(struct sfdo_desktop_file_value *value) { free(value->data); free(value->items_mem); free(value->items); } static bool read_entry(struct sfdo_desktop_file_loader *loader) { struct sfdo_desktop_file_group *group = loader->curr_group; if (group == NULL) { // An entry without a group set_error(loader, SFDO_DESKTOP_FILE_ERROR_SYNTAX); return false; } int line = loader->line; int column = loader->column; reset_buf(loader); while (true) { if (!peek(loader)) { return false; } int32_t rune = loader->rune; if (is_ws(rune) || rune == '=' || rune == '[') { break; } else if (!is_key_char(rune)) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_SYNTAX); return false; } if (!add_rune(loader)) { return false; } advance(loader); } struct sfdo_desktop_file_map_entry *map_entry = sfdo_hashmap_get(&group->entries, loader->buf, loader->buf_len, true); struct sfdo_desktop_file_entry *entry = NULL; if (map_entry == NULL) { set_error_at(loader, SFDO_DESKTOP_FILE_ERROR_OOM, line, column); return false; } else if (map_entry->base.key == NULL) { entry = calloc(1, sizeof(*entry)); if (entry == NULL) { set_error_at(loader, SFDO_DESKTOP_FILE_ERROR_OOM, line, column); return false; } entry->key = malloc(loader->buf_len + 1); if (entry->key == NULL) { set_error_at(loader, SFDO_DESKTOP_FILE_ERROR_OOM, line, column); free(entry); return false; } memcpy(entry->key, loader->buf, loader->buf_len); entry->key[loader->buf_len] = '\0'; entry->key_len = loader->buf_len; entry->line = line; entry->column = column; map_entry->base.key = entry->key; map_entry->entry = entry; } else { entry = map_entry->entry; } struct sfdo_desktop_file_value *value = NULL; if (loader->rune == '[') { reset_buf(loader); advance(loader); while (true) { if (!peek(loader)) { return false; } if (is_end(loader->rune)) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_SYNTAX); return false; } else if (loader->rune == ']') { break; } if (!add_rune(loader)) { return false; } advance(loader); } assert(loader->rune == ']'); advance(loader); if (!add_bytes(loader, "", 1)) { return false; } for (uint8_t i = entry->locale_match_level; i < loader->n_locales; i++) { if (strcmp(loader->locales[i], loader->buf) == 0) { entry->locale_match_level = i + 1; value = &entry->localized_value; break; } } } else { if (entry->value.data != NULL) { set_error_at(loader, SFDO_DESKTOP_FILE_ERROR_DUPLICATE_KEY, line, column); return false; } value = &entry->value; } if (!skip_ws(loader)) { return false; } if (loader->rune != '=') { set_error(loader, SFDO_DESKTOP_FILE_ERROR_SYNTAX); return false; } advance(loader); if (!skip_ws(loader)) { return false; } if (value != NULL) { finish_value(value); *value = (struct sfdo_desktop_file_value){0}; reset_buf(loader); entry->line = line; entry->column = column; } line = loader->line; column = loader->column; // Trailing empty items not followed by a separator are ignored bool curr_item_is_empty = true; bool escaped = false; while (true) { if (!peek(loader)) { return false; } if (is_end(loader->rune)) { break; } curr_item_is_empty = false; if (escaped) { char byte; switch (loader->rune) { case 's': byte = ' '; break; case 'n': byte = '\n'; break; case 't': byte = '\t'; break; case 'r': byte = '\r'; break; case '\\': byte = '\\'; break; case ';': byte = ';'; break; default: set_error(loader, SFDO_DESKTOP_FILE_ERROR_SYNTAX); return false; } if (value != NULL && !add_bytes(loader, &byte, 1)) { return false; } escaped = false; } else if (loader->rune == '\\') { escaped = true; } else if (value != NULL) { if (loader->rune == ';') { if (!add_item(loader, loader->buf_len)) { return false; } curr_item_is_empty = true; } if (!add_rune(loader)) { return false; } } advance(loader); } if (escaped) { set_error(loader, SFDO_DESKTOP_FILE_ERROR_SYNTAX); return false; } // Skip end advance(loader); if (value != NULL) { value->data = malloc(loader->buf_len + 1); if (value->data == NULL) { set_error_at(loader, SFDO_DESKTOP_FILE_ERROR_OOM, line, column); return false; } size_t items_mem_size = loader->buf_len; if (!curr_item_is_empty) { // Not terminated by a separator if (!add_item(loader, items_mem_size)) { free(value->data); return false; } // Add space for NUL ++items_mem_size; } if (loader->item_buf_len > 0) { value->items_mem = malloc(items_mem_size); value->items = calloc(loader->item_buf_len, sizeof(*value->items)); if (value->items_mem == NULL || value->items == NULL) { free(value->data); set_error_at(loader, SFDO_DESKTOP_FILE_ERROR_OOM, line, column); return false; } size_t item_start = 0; value->n_items = loader->item_buf_len; for (size_t i = 0; i < value->n_items; i++) { size_t item_end = loader->item_buf[i]; char *data = &value->items_mem[item_start]; size_t len = item_end - item_start; memcpy(data, &loader->buf[item_start], len); data[len] = '\0'; value->items[i] = (struct sfdo_string){ .data = data, .len = len, }; item_start = item_end + 1; } value->n_items = loader->item_buf_len; } value->len = loader->buf_len; memcpy(value->data, loader->buf, value->len); value->data[value->len] = '\0'; } return true; } static bool validate_group(struct sfdo_desktop_file_loader *loader) { struct sfdo_desktop_file_group *group = loader->curr_group; if (group == NULL) { return true; } struct sfdo_hashmap *entries = &group->entries; for (size_t i = 0; i < entries->cap; i++) { struct sfdo_desktop_file_map_entry *map_entry = &((struct sfdo_desktop_file_map_entry *)entries->mem)[i]; if (map_entry->base.key != NULL) { struct sfdo_desktop_file_entry *entry = map_entry->entry; assert(entry != NULL); if (entry->value.data == NULL) { set_error_at(loader, SFDO_DESKTOP_FILE_ERROR_NO_DEFAULT_VALUE, entry->line, entry->column); return false; } } } return true; } static bool load(struct sfdo_desktop_file_loader *loader) { while (true) { if (!skip_ws(loader)) { return false; } switch (loader->rune) { case RUNE_EOF: if (!validate_group(loader)) { return false; } return true; case '\n': advance(loader); continue; case '#': if (!skip_comment(loader)) { return false; } break; case '[': if (!validate_group(loader)) { return false; } if (!read_group(loader)) { return false; } break; default: if (!read_entry(loader)) { return false; } break; } } } static bool prepare_locales(struct sfdo_desktop_file_loader *loader, const char *str) { loader->n_locales = 0; if (str == NULL) { return true; } size_t lang_len = strcspn(str, "_.@"); if (lang_len == 0) { return true; } size_t country_i = 0; size_t country_len = 0; size_t modifier_i = 0; size_t modifier_len = 0; size_t len = lang_len; if (str[len] == '_') { country_i = ++len; country_len = strcspn(str + len, ".@"); len += country_len; } if (str[len] == '.') { ++len; len += strcspn(str + len, "@"); } if (str[len] == '@') { modifier_i = ++len; modifier_len = strlen(str + len); len += modifier_len; } bool has_country = country_len > 0; bool has_modifier = modifier_len > 0; size_t mem_size = lang_len + 1; if (has_modifier) { mem_size += lang_len + 1 + modifier_len + 1; } if (has_country) { mem_size += lang_len + 1 + country_len + 1; } if (has_country && has_modifier) { mem_size += lang_len + 1 + country_len + 1 + modifier_len + 1; } struct sfdo_membuild mem_buf; if (!sfdo_membuild_setup(&mem_buf, mem_size)) { return false; } loader->locales[loader->n_locales++] = mem_buf.data + mem_buf.len; sfdo_membuild_add(&mem_buf, str, lang_len, "", SFDO_SIZE1, NULL); if (has_modifier) { loader->locales[loader->n_locales++] = mem_buf.data + mem_buf.len; sfdo_membuild_add(&mem_buf, str, lang_len, "@", SFDO_SIZE1, str + modifier_i, modifier_len, "", SFDO_SIZE1, NULL); } if (has_country) { loader->locales[loader->n_locales++] = mem_buf.data + mem_buf.len; sfdo_membuild_add(&mem_buf, str, lang_len, "_", SFDO_SIZE1, str + country_i, country_len, "", SFDO_SIZE1, NULL); } if (has_country && has_modifier) { loader->locales[loader->n_locales++] = mem_buf.data + mem_buf.len; sfdo_membuild_add(&mem_buf, str, lang_len, "_", SFDO_SIZE1, str + country_i, country_len, "@", SFDO_SIZE1, str + modifier_i, modifier_len, "", SFDO_SIZE1, NULL); } loader->locale_data = mem_buf.data; assert(mem_buf.len == mem_size); return true; } SFDO_API struct sfdo_desktop_file_document *sfdo_desktop_file_document_load( FILE *fp, const char *locale, int options, struct sfdo_desktop_file_error *error) { struct sfdo_desktop_file_error placeholder; if (error == NULL) { error = &placeholder; } struct sfdo_desktop_file_loader loader = { .rune = RUNE_NONE, .line = 1, .column = 1, .fp = fp, .error = error, .allow_duplicate_groups = (options & SFDO_DESKTOP_FILE_LOAD_ALLOW_DUPLICATE_GROUPS) != 0, }; loader.doc = calloc(1, sizeof(*loader.doc)); if (loader.doc == NULL) { set_error(&loader, SFDO_DESKTOP_FILE_ERROR_OOM); return NULL; } if (!prepare_locales(&loader, locale)) { set_error(&loader, SFDO_DESKTOP_FILE_ERROR_OOM); free(loader.doc); return NULL; } sfdo_hashmap_init(&loader.group_set, sizeof(struct sfdo_hashmap_entry)); bool ok = load(&loader); sfdo_hashmap_finish(&loader.group_set); free(loader.locale_data); free(loader.buf); free(loader.item_buf); if (ok) { return loader.doc; } else { sfdo_desktop_file_document_destroy(loader.doc); return NULL; } } SFDO_API void sfdo_desktop_file_document_destroy(struct sfdo_desktop_file_document *document) { if (document == NULL) { return; } struct sfdo_desktop_file_group *group = document->groups; while (group != NULL) { struct sfdo_desktop_file_group *next = group->next; struct sfdo_hashmap *entries = &group->entries; for (size_t i = 0; i < entries->cap; i++) { struct sfdo_desktop_file_map_entry *map_entry = &((struct sfdo_desktop_file_map_entry *)entries->mem)[i]; if (map_entry->base.key != NULL) { struct sfdo_desktop_file_entry *entry = map_entry->entry; assert(entry != NULL); free(entry->key); finish_value(&entry->value); finish_value(&entry->localized_value); free(entry); } } sfdo_hashmap_finish(entries); free(group->name); free(group); group = next; } free(document); } SFDO_API struct sfdo_desktop_file_group *sfdo_desktop_file_document_get_groups( struct sfdo_desktop_file_document *document) { return document->groups; } SFDO_API const char *sfdo_desktop_file_error_code_get_description( enum sfdo_desktop_file_error_code code) { switch (code) { case SFDO_DESKTOP_FILE_ERROR_NONE: return "Success"; case SFDO_DESKTOP_FILE_ERROR_IO: return "Input error"; case SFDO_DESKTOP_FILE_ERROR_NT: return "Unexpected NUL"; case SFDO_DESKTOP_FILE_ERROR_UTF8: return "Invalid UTF-8 sequence"; case SFDO_DESKTOP_FILE_ERROR_OOM: return "Out of memory"; case SFDO_DESKTOP_FILE_ERROR_SYNTAX: return "Syntax error"; case SFDO_DESKTOP_FILE_ERROR_DUPLICATE_GROUP: return "Duplicate group"; case SFDO_DESKTOP_FILE_ERROR_DUPLICATE_KEY: return "Duplicate key"; case SFDO_DESKTOP_FILE_ERROR_NO_DEFAULT_VALUE: return "No default value"; } return "Unknown error"; } SFDO_API struct sfdo_desktop_file_group *sfdo_desktop_file_group_get_next( struct sfdo_desktop_file_group *group) { return group->next; } SFDO_API const char *sfdo_desktop_file_group_get_name( struct sfdo_desktop_file_group *group, size_t *len) { if (len != NULL) { *len = group->name_len; } return group->name; } SFDO_API void sfdo_desktop_file_group_get_location( struct sfdo_desktop_file_group *group, int *line, int *column) { if (line != NULL) { *line = group->line; } if (column != NULL) { *column = group->column; } } SFDO_API struct sfdo_desktop_file_entry *sfdo_desktop_file_group_get_entry( struct sfdo_desktop_file_group *group, const char *key, size_t key_len) { if (key_len == SFDO_NT) { key_len = strlen(key); } struct sfdo_desktop_file_map_entry *map_entry = sfdo_hashmap_get(&group->entries, key, key_len, false); if (map_entry == NULL) { return NULL; } return map_entry->entry; } SFDO_API const char *sfdo_desktop_file_entry_get_key( struct sfdo_desktop_file_entry *entry, size_t *len) { if (len != NULL) { *len = entry->key_len; } return entry->key; } SFDO_API const char *sfdo_desktop_file_entry_get_value( struct sfdo_desktop_file_entry *entry, size_t *len) { if (len != NULL) { *len = entry->value.len; } return entry->value.data; } SFDO_API const char *sfdo_desktop_file_entry_get_localized_value( struct sfdo_desktop_file_entry *entry, size_t *len) { if (entry->localized_value.data == NULL) { return sfdo_desktop_file_entry_get_value(entry, len); } if (len != NULL) { *len = entry->localized_value.len; } return entry->localized_value.data; } SFDO_API void sfdo_desktop_file_entry_get_location( struct sfdo_desktop_file_entry *entry, int *line, int *column) { if (line != NULL) { *line = entry->line; } if (column != NULL) { *column = entry->column; } } SFDO_API const struct sfdo_string *sfdo_desktop_file_entry_get_value_list( struct sfdo_desktop_file_entry *entry, size_t *n_items) { *n_items = entry->value.n_items; return entry->value.items; } SFDO_API const struct sfdo_string *sfdo_desktop_file_entry_get_localized_value_list( struct sfdo_desktop_file_entry *entry, size_t *n_items) { if (entry->localized_value.data == NULL) { return sfdo_desktop_file_entry_get_value_list(entry, n_items); } *n_items = entry->localized_value.n_items; return entry->localized_value.items; } libsfdo-0.1.3/sfdo-desktop-file/meson.build000066400000000000000000000000551467233572500206340ustar00rootroot00000000000000sfdo_desktop_file_src = files( 'file.c', ) libsfdo-0.1.3/sfdo-desktop/000077500000000000000000000000001467233572500155555ustar00rootroot00000000000000libsfdo-0.1.3/sfdo-desktop/db.c000066400000000000000000001243141467233572500163130ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "common/api.h" #include "common/dirs.h" #include "common/grow.h" #include "common/size.h" #include "common/strbuild.h" #include "common/striter.h" #include "common/strpool.h" #include "sfdo-desktop/internal.h" #define ENTRY_DESKTOP_SUFFIX ".desktop" #define ENTRY_DIRECTORY_SUFFIX ".directory" #define DESKTOP_ACTION_PREFIX "Desktop Action " struct sfdo_desktop_exec_scanner { struct sfdo_desktop_entry *entry; const char *data; size_t data_len; int line, column; struct sfdo_desktop_exec *dst; size_t i; // index in exec char *buf; size_t buf_len, buf_cap; const char **lit_buf; size_t lit_buf_len, lit_buf_cap; }; struct sfdo_desktop_loader { struct sfdo_desktop_db *db; const char *locale; size_t n_entries; struct sfdo_strbuild path_buf; struct sfdo_strbuild id_buf; struct sfdo_desktop_exec_scanner exec; }; enum sfdo_desktop_entry_load_result { SFDO_DESKTOP_ENTRY_LOAD_OK, SFDO_DESKTOP_ENTRY_LOAD_ERROR, SFDO_DESKTOP_ENTRY_LOAD_OOM, }; enum sfdo_desktop_entry_value_req { SFDO_DESKTOP_ENTRY_VALUE_OPTIONAL, SFDO_DESKTOP_ENTRY_VALUE_REQUIRED, }; enum sfdo_desktop_entry_value_type { SFDO_DESKTOP_ENTRY_VALUE_STRING, // Also used for iconstring SFDO_DESKTOP_ENTRY_VALUE_LOCALESTRING, }; static void exec_finish(struct sfdo_desktop_exec *exec) { free(exec->literals); } static void desktop_entry_destroy(struct sfdo_desktop_entry *entry) { if (entry == NULL) { return; } free(entry->show_exceptions); free(entry->implements); switch (entry->type) { case SFDO_DESKTOP_ENTRY_APPLICATION: free(entry->app.mimetypes); free(entry->app.categories); free(entry->app.keywords); exec_finish(&entry->app.exec); for (size_t i = 0; i < entry->app.n_actions; i++) { struct sfdo_desktop_entry_action *action = entry->app.actions[i]; exec_finish(&action->exec); } free(entry->app.actions); free(entry->app.actions_mem); break; case SFDO_DESKTOP_ENTRY_LINK: break; case SFDO_DESKTOP_ENTRY_DIRECTORY: break; } free(entry); } static enum sfdo_desktop_entry_load_result load_optional_boolean(struct sfdo_desktop_loader *loader, struct sfdo_desktop_file_group *group, const char *key, size_t key_len, bool *dst, bool *exists) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_file_entry *entry; if ((entry = sfdo_desktop_file_group_get_entry(group, key, key_len)) != NULL) { size_t value_len; const char *value = sfdo_desktop_file_entry_get_value(entry, &value_len); if (value_len == 4 && memcmp(value, "true", 4) == 0) { *dst = true; } else if (value_len == 5 && memcmp(value, "false", 5) == 0) { *dst = false; } else { int line, column; sfdo_desktop_file_entry_get_location(entry, &line, &column); logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: expected true or false, got \"%s\"", line, column, value); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } *exists = true; } else { *dst = false; *exists = false; } return SFDO_DESKTOP_ENTRY_LOAD_OK; } static enum sfdo_desktop_entry_load_result load_boolean(struct sfdo_desktop_loader *loader, struct sfdo_desktop_file_group *group, const char *key, size_t key_len, bool *dst) { bool exists; return load_optional_boolean(loader, group, key, key_len, dst, &exists); } static enum sfdo_desktop_entry_load_result store_string(struct sfdo_desktop_loader *loader, const char *value, size_t value_len, struct sfdo_string *dst) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; const char *owned = sfdo_strpool_add(&db->strings, value, value_len); if (owned == NULL) { logger_write_oom(logger); return SFDO_DESKTOP_ENTRY_LOAD_OOM; } dst->data = owned; dst->len = value_len; return SFDO_DESKTOP_ENTRY_LOAD_OK; } static enum sfdo_desktop_entry_load_result load_string(struct sfdo_desktop_loader *loader, struct sfdo_desktop_file_group *group, const char *key, size_t key_len, enum sfdo_desktop_entry_value_req req, enum sfdo_desktop_entry_value_type type, struct sfdo_string *dst) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_file_entry *entry; if ((entry = sfdo_desktop_file_group_get_entry(group, key, key_len)) != NULL) { size_t value_len; const char *value = NULL; switch (type) { case SFDO_DESKTOP_ENTRY_VALUE_STRING: value = sfdo_desktop_file_entry_get_value(entry, &value_len); break; case SFDO_DESKTOP_ENTRY_VALUE_LOCALESTRING: value = sfdo_desktop_file_entry_get_localized_value(entry, &value_len); break; } assert(value != NULL); return store_string(loader, value, value_len, dst); } else if (req == SFDO_DESKTOP_ENTRY_VALUE_REQUIRED) { int group_line, group_column; sfdo_desktop_file_group_get_location(group, &group_line, &group_column); logger_write( logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: %s is unset", group_line, group_column, key); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } return SFDO_DESKTOP_ENTRY_LOAD_OK; } static enum sfdo_desktop_entry_load_result store_list(struct sfdo_desktop_loader *loader, const struct sfdo_string *src, size_t n_src, struct sfdo_string **dst, size_t *n_dst) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; if (n_src == 0) { *dst = NULL; *n_dst = 0; return SFDO_DESKTOP_ENTRY_LOAD_OK; } struct sfdo_string *items = calloc(n_src, sizeof(*items)); if (items == NULL) { logger_write_oom(logger); return SFDO_DESKTOP_ENTRY_LOAD_OOM; } for (size_t i = 0; i < n_src; i++) { const struct sfdo_string *src_item = &src[i]; struct sfdo_string *item = &items[i]; item->data = sfdo_strpool_add(&db->strings, src_item->data, src_item->len); if (item->data == NULL) { logger_write_oom(logger); free(items); return SFDO_DESKTOP_ENTRY_LOAD_OOM; } item->len = src_item->len; } *dst = items; *n_dst = n_src; return SFDO_DESKTOP_ENTRY_LOAD_OK; } static enum sfdo_desktop_entry_load_result load_list(struct sfdo_desktop_loader *loader, struct sfdo_desktop_file_group *group, const char *key, size_t key_len, enum sfdo_desktop_entry_value_type type, struct sfdo_string **dst, size_t *n_dst) { struct sfdo_desktop_file_entry *entry; if ((entry = sfdo_desktop_file_group_get_entry(group, key, key_len)) != NULL) { const struct sfdo_string *items = NULL; size_t n_items = 0; switch (type) { case SFDO_DESKTOP_ENTRY_VALUE_STRING: items = sfdo_desktop_file_entry_get_value_list(entry, &n_items); break; case SFDO_DESKTOP_ENTRY_VALUE_LOCALESTRING: items = sfdo_desktop_file_entry_get_localized_value_list(entry, &n_items); break; } return store_list(loader, items, n_items, dst, n_dst); } else { *dst = NULL; *n_dst = 0; } return SFDO_DESKTOP_ENTRY_LOAD_OK; } static enum sfdo_desktop_entry_load_result load_actions(struct sfdo_desktop_loader *loader, struct sfdo_desktop_file_group *group, struct sfdo_desktop_entry_action **dst_mem, struct sfdo_desktop_entry_action ***dst, size_t *n_dst, struct sfdo_hashmap *set) { struct sfdo_desktop_file_entry *entry; if ((entry = sfdo_desktop_file_group_get_entry(group, "Actions", 7)) == NULL) { *dst = NULL; *n_dst = 0; return SFDO_DESKTOP_ENTRY_LOAD_OK; } int line, column; sfdo_desktop_file_entry_get_location(entry, &line, &column); struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; size_t n_items; const struct sfdo_string *items = sfdo_desktop_file_entry_get_value_list(entry, &n_items); if (n_items == 0) { *dst = NULL; *n_dst = 0; *dst_mem = NULL; return SFDO_DESKTOP_ENTRY_LOAD_OK; } struct sfdo_desktop_entry_action **actions = calloc(n_items, sizeof(struct sfdo_desktop_entry_action *)); struct sfdo_desktop_entry_action *actions_mem = calloc(n_items, sizeof(*actions_mem)); if (actions == NULL || actions_mem == NULL) { free(actions); free(actions_mem); logger_write_oom(logger); return SFDO_DESKTOP_ENTRY_LOAD_OOM; } enum sfdo_desktop_entry_load_result r; size_t action_i = 0; for (size_t i = 0; i < n_items; i++) { const struct sfdo_string *item = &items[i]; struct sfdo_hashmap_entry *map_entry = sfdo_hashmap_get(set, item->data, item->len, true); if (map_entry == NULL) { logger_write_oom(logger); r = SFDO_DESKTOP_ENTRY_LOAD_OOM; goto err; } else if (map_entry->key != NULL) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: duplicate action %s", line, column, map_entry->key); r = SFDO_DESKTOP_ENTRY_LOAD_ERROR; goto err; } const char *owned = sfdo_strpool_add(&db->strings, item->data, item->len); if (owned == NULL) { logger_write_oom(logger); r = SFDO_DESKTOP_ENTRY_LOAD_OOM; goto err; } map_entry->key = owned; struct sfdo_desktop_entry_action *action = &actions_mem[action_i]; action->id.data = owned; action->id.len = item->len; actions[action_i] = action; ++action_i; } *dst_mem = actions_mem; *dst = actions; *n_dst = n_items; return SFDO_DESKTOP_ENTRY_LOAD_OK; err: free(actions); free(actions_mem); return r; } static bool exec_char_is_ws(char c) { return c == ' ' || c == '\t'; } static bool exec_char_is_reserved(char c) { switch (c) { case ' ': case '\t': case '\n': case '"': case '\'': case '\\': case '>': case '<': case '~': case '|': case '&': case ';': case '$': case '*': case '?': case '#': case '(': case ')': case '`': return true; default: return false; } } static bool exec_char_needs_escape(char c) { switch (c) { case '"': case '`': case '$': case '\\': return true; default: return false; } } static bool exec_char_is_deprecated_field_code(char c) { switch (c) { case 'd': case 'D': case 'n': case 'N': case 'v': case 'm': return true; default: return false; } } static enum sfdo_desktop_entry_load_result exec_validate_character( struct sfdo_desktop_loader *loader, char c, bool quoted) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_exec_scanner *scanner = &loader->exec; if (c == '=' && scanner->lit_buf_len == 0) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: unexpected \"=\" in the executable path at position %zu", scanner->line, scanner->column, scanner->i); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } if (quoted) { if (exec_char_needs_escape(c)) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: unescaped character at position %zu", scanner->line, scanner->column, scanner->i); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } } else { if (exec_char_is_reserved(c)) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: reserved character in a unquoted arg at position %zu", scanner->line, scanner->column, scanner->i); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } } return SFDO_DESKTOP_ENTRY_LOAD_OK; } static enum sfdo_desktop_entry_load_result exec_add_byte( struct sfdo_desktop_loader *loader, char c) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_exec_scanner *scanner = &loader->exec; if (!sfdo_grow(&scanner->buf, &scanner->buf_cap, scanner->buf_len, 1)) { logger_write_oom(logger); return SFDO_DESKTOP_ENTRY_LOAD_OOM; } scanner->buf[scanner->buf_len++] = c; return SFDO_DESKTOP_ENTRY_LOAD_OK; } static enum sfdo_desktop_entry_load_result exec_add_string( struct sfdo_desktop_loader *loader, struct sfdo_string *str) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_exec_scanner *scanner = &loader->exec; if (!sfdo_grow_n(&scanner->buf, &scanner->buf_cap, scanner->buf_len, 1, str->len)) { logger_write_oom(logger); return SFDO_DESKTOP_ENTRY_LOAD_OOM; } memcpy(&scanner->buf[scanner->buf_len], str->data, str->len); scanner->buf_len += str->len; return SFDO_DESKTOP_ENTRY_LOAD_OK; } static enum sfdo_desktop_entry_load_result exec_add_literal( struct sfdo_desktop_loader *loader, const char *literal) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_exec_scanner *scanner = &loader->exec; if (!sfdo_grow(&scanner->lit_buf, &scanner->lit_buf_cap, scanner->lit_buf_len, sizeof(const char *))) { logger_write_oom(logger); return SFDO_DESKTOP_ENTRY_LOAD_OOM; } scanner->lit_buf[scanner->lit_buf_len++] = literal; return SFDO_DESKTOP_ENTRY_LOAD_OK; } static enum sfdo_desktop_entry_load_result exec_save_literal(struct sfdo_desktop_loader *loader) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_exec_scanner *scanner = &loader->exec; enum sfdo_desktop_entry_load_result r = SFDO_DESKTOP_ENTRY_LOAD_OK; size_t len = scanner->buf_len; if ((r = exec_add_byte(loader, '\0')) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } const char *literal = sfdo_strpool_add(&db->strings, scanner->buf, len); if (literal == NULL) { logger_write_oom(logger); return SFDO_DESKTOP_ENTRY_LOAD_OOM; } return exec_add_literal(loader, literal); } static enum sfdo_desktop_entry_load_result exec_set_target( struct sfdo_desktop_loader *loader, char code) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_exec_scanner *scanner = &loader->exec; struct sfdo_desktop_exec *dst = scanner->dst; if (dst->target_i != (size_t)-1) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: a command line must not have multiple target field codes", scanner->line, scanner->column); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } dst->target_i = scanner->lit_buf_len; dst->supports_uri = code == 'u' || code == 'U'; dst->supports_list = code == 'F' || code == 'U'; return SFDO_DESKTOP_ENTRY_LOAD_OK; } static enum sfdo_desktop_entry_load_result exec_add_quoted(struct sfdo_desktop_loader *loader) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_exec_scanner *scanner = &loader->exec; enum sfdo_desktop_entry_load_result r = SFDO_DESKTOP_ENTRY_LOAD_OK; size_t start_i = scanner->i; ++scanner->i; // Skip the opening quote char escape = '\0'; size_t escape_i = 0; while (true) { if (scanner->i == scanner->data_len) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: unclosed quote at position %zu", scanner->line, scanner->column, start_i); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } char c = scanner->data[scanner->i]; if (escape != '\0') { if (escape == '\\') { if (!exec_char_needs_escape(c)) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: invalid escape sequence at position %zu", scanner->line, scanner->column, escape_i); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } } else if (escape == '%') { if (c != '%') { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: unexpected field code in a quoted argument at position %zu", scanner->line, scanner->column, escape_i); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } } else { assert(false); } escape = '\0'; if ((r = exec_add_byte(loader, c)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } } else if (c == '"') { break; } else if (c == '\\' || c == '%') { escape = c; escape_i = scanner->i; } else if ((r = exec_validate_character(loader, c, true)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } else { if ((r = exec_add_byte(loader, c)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } } ++scanner->i; } if ((r = exec_save_literal(loader)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } ++scanner->i; // Skip the closing quote return SFDO_DESKTOP_ENTRY_LOAD_OK; } static bool exec_try_consume_standalone(struct sfdo_desktop_exec_scanner *scanner, char *code) { const char *exec = scanner->data; size_t i = scanner->i; size_t len = scanner->data_len; if (len - i < 2 || exec[i] != '%') { return false; } *code = exec[i + 1]; if (len - i > 2 && !exec_char_is_ws(exec[i + 2])) { // Followed by a non-whitespace character return false; } scanner->i += 2; return true; } static enum sfdo_desktop_entry_load_result exec_add_unquoted(struct sfdo_desktop_loader *loader) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_exec_scanner *scanner = &loader->exec; struct sfdo_desktop_entry *entry = scanner->entry; enum sfdo_desktop_entry_load_result r = SFDO_DESKTOP_ENTRY_LOAD_OK; size_t field_i = scanner->i; char standalone = '\0'; if (exec_try_consume_standalone(scanner, &standalone)) { switch (standalone) { case 'f': case 'u': case 'F': case 'U': if ((r = exec_set_target(loader, standalone)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } break; case 'i': if (scanner->entry->icon.data != NULL) { if ((r = exec_add_literal(loader, "--icon")) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } if ((r = exec_add_literal(loader, entry->icon.data)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } } break; case '%': if ((r = exec_add_literal(loader, "%")) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } break; case 'c': if ((r = exec_add_literal(loader, entry->name.data)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } break; case 'k': if ((r = exec_add_literal(loader, entry->file_path.data)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } break; default: if (!exec_char_is_deprecated_field_code(standalone)) { goto err_invalid_field_code; } break; } return SFDO_DESKTOP_ENTRY_LOAD_OK; } struct sfdo_desktop_exec *dst = scanner->dst; bool field = false; bool has_target = false; while (scanner->i < scanner->data_len) { char c = scanner->data[scanner->i]; if (field) { field = false; switch (c) { case 'f': case 'u': if ((r = exec_set_target(loader, c)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } assert(!has_target); assert(dst->embed.before == 0 && dst->embed.after == 0); has_target = true; dst->embed.before = scanner->buf_len; break; case 'F': case 'U': case 'i': logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: field code at position %zu must be standalone", scanner->line, scanner->column, field_i); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; case '%': if ((r = exec_add_byte(loader, c)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } break; case 'c': if ((r = exec_add_string(loader, &entry->name)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } break; case 'k': if ((r = exec_add_string(loader, &entry->file_path)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } break; default: if (!exec_char_is_deprecated_field_code(c)) { goto err_invalid_field_code; } break; } } else if (exec_char_is_ws(c)) { break; } else if (c == '%') { field = true; field_i = scanner->i; } else if ((r = exec_validate_character(loader, c, false)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } else { if ((r = exec_add_byte(loader, c)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } } ++scanner->i; } if (field) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: truncated field code at position %zu", scanner->line, scanner->column, field_i); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } if (has_target) { dst->embed.after = scanner->buf_len - dst->embed.before; assert(dst->embed.before != 0 || dst->embed.after != 0); } if ((r = exec_save_literal(loader)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } return SFDO_DESKTOP_ENTRY_LOAD_OK; err_invalid_field_code: logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: invalid field code at position %zu", scanner->line, scanner->column, field_i); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } static bool exec_find_arg(struct sfdo_desktop_exec_scanner *scanner) { while (true) { if (scanner->i == scanner->data_len) { return false; } else if (!exec_char_is_ws(scanner->data[scanner->i])) { return true; } ++scanner->i; } } static void exec_scanner_start(struct sfdo_desktop_exec_scanner *scanner, struct sfdo_desktop_file_entry *file_entry, struct sfdo_desktop_entry *entry, struct sfdo_desktop_exec *dst) { scanner->entry = entry; scanner->data = sfdo_desktop_file_entry_get_value(file_entry, &scanner->data_len); sfdo_desktop_file_entry_get_location(file_entry, &scanner->line, &scanner->column); scanner->dst = dst; scanner->i = 0; scanner->buf_len = 0; scanner->lit_buf_len = 0; *dst = (struct sfdo_desktop_exec){ .literals = NULL, .target_i = (size_t)-1, }; } static enum sfdo_desktop_entry_load_result load_exec(struct sfdo_desktop_loader *loader, struct sfdo_desktop_file_entry *file_entry, struct sfdo_desktop_entry *entry, struct sfdo_desktop_exec *dst) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_exec_scanner *scanner = &loader->exec; exec_scanner_start(scanner, file_entry, entry, dst); enum sfdo_desktop_entry_load_result r = SFDO_DESKTOP_ENTRY_LOAD_OK; while (exec_find_arg(scanner)) { scanner->buf_len = 0; if (scanner->data[scanner->i] == '"') { r = exec_add_quoted(loader); } else { r = exec_add_unquoted(loader); } if (r != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } } dst->n_literals = scanner->lit_buf_len; dst->literals = calloc(dst->n_literals, sizeof(const char *)); if (dst->literals == NULL) { logger_write_oom(logger); return SFDO_DESKTOP_ENTRY_LOAD_OOM; } memcpy(dst->literals, scanner->lit_buf, sizeof(const char *) * dst->n_literals); return SFDO_DESKTOP_ENTRY_LOAD_OK; } static enum sfdo_desktop_entry_load_result load_show_in(struct sfdo_desktop_loader *loader, struct sfdo_desktop_file_group *group, struct sfdo_desktop_entry *d_entry) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_desktop_file_entry *yes_entry = sfdo_desktop_file_group_get_entry(group, "OnlyShowIn", 10); struct sfdo_desktop_file_entry *no_entry = sfdo_desktop_file_group_get_entry(group, "NotShowIn", 9); const struct sfdo_string *items; size_t n_items; enum sfdo_desktop_entry_load_result r = SFDO_DESKTOP_ENTRY_LOAD_OK; if (yes_entry != NULL) { d_entry->default_show = false; items = sfdo_desktop_file_entry_get_value_list(yes_entry, &n_items); if ((r = store_list(loader, items, n_items, &d_entry->show_exceptions, &d_entry->n_show_exceptions)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } if (no_entry != NULL) { items = sfdo_desktop_file_entry_get_value_list(no_entry, &n_items); // XXX: this is O(n²) but also never happens in practice so whatever for (size_t no_i = 0; no_i < n_items; no_i++) { const struct sfdo_string *no = &items[no_i]; for (size_t yes_i = 0; yes_i < d_entry->n_show_exceptions; yes_i++) { struct sfdo_string *yes = &d_entry->show_exceptions[yes_i]; if (yes->len == no->len && memcmp(yes->data, no->data, yes->len) == 0) { int group_line, group_column; sfdo_desktop_file_group_get_location(group, &group_line, &group_column); logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: %s is both in OnlyShowIn and NotShowIn", group_line, group_column, yes->data); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } } } } } else { d_entry->default_show = true; if (no_entry != NULL) { items = sfdo_desktop_file_entry_get_value_list(no_entry, &n_items); if ((r = store_list(loader, items, n_items, &d_entry->show_exceptions, &d_entry->n_show_exceptions)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } } } return SFDO_DESKTOP_ENTRY_LOAD_OK; } static bool dbus_name_char_is_valid_leader(char c, bool allow_hyphen) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (allow_hyphen && c == '-'); } static bool dbus_name_char_is_valid_cont(char c, bool allow_hyphen) { return (c >= '0' && c <= '9') || dbus_name_char_is_valid_leader(c, allow_hyphen); } static bool validate_dbus_name(const char *name, size_t len, bool interface) { if (len > 255) { // Exceeds maximum name length return false; } int n_elements = 0; size_t elem_start, elem_len; size_t iter = 0; while (sfdo_striter(name, '.', &iter, &elem_start, &elem_len)) { if (elem_len == 0) { // All elements must contain at least one character return false; } if (!dbus_name_char_is_valid_leader(name[elem_start], !interface)) { return false; } for (size_t i = 1; i < elem_len; i++) { if (!dbus_name_char_is_valid_cont(name[elem_start + i], !interface)) { return false; } } ++n_elements; } if (interface && n_elements < 2) { // Interface names are composed of 2 or more elements return false; } return true; } static enum sfdo_desktop_entry_load_result entry_load(struct sfdo_desktop_loader *loader, struct sfdo_desktop_file_document *doc, struct sfdo_desktop_map_entry *map_entry) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; const char *group_name; size_t group_name_len; struct sfdo_desktop_file_entry *entry; const char *value; size_t value_len; const struct sfdo_string *items; size_t n_items; int group_line, group_column; struct sfdo_desktop_file_group *group = sfdo_desktop_file_document_get_groups(doc); if (group == NULL) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "Expected \"Desktop Entry\" group"); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } sfdo_desktop_file_group_get_location(group, &group_line, &group_column); group_name = sfdo_desktop_file_group_get_name(group, &group_name_len); if (strcmp(group_name, "Desktop Entry") != 0) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: expected \"Desktop Entry\" group, got \"%s\"", group_line, group_column, group_name); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } enum sfdo_desktop_entry_type type; if ((entry = sfdo_desktop_file_group_get_entry(group, "Type", 4)) != NULL) { value = sfdo_desktop_file_entry_get_value(entry, &value_len); if (strcmp(value, "Application") == 0) { type = SFDO_DESKTOP_ENTRY_APPLICATION; } else if (strcmp(value, "Link") == 0) { type = SFDO_DESKTOP_ENTRY_LINK; } else if (strcmp(value, "Directory") == 0) { type = SFDO_DESKTOP_ENTRY_DIRECTORY; } else { logger_write(logger, SFDO_LOG_LEVEL_INFO, "Skipping %s of unknown Type \"%s\"", loader->id_buf.data, value); return SFDO_DESKTOP_ENTRY_LOAD_OK; } } else { logger_write( logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: Type is unset", group_line, group_column); return SFDO_DESKTOP_ENTRY_LOAD_ERROR; } enum sfdo_desktop_entry_load_result r = SFDO_DESKTOP_ENTRY_LOAD_OK; bool hidden; if ((r = load_boolean(loader, group, "Hidden", 6, &hidden)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { return r; } if (hidden) { logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Skipping hidden entry %s", map_entry->base.key); return SFDO_DESKTOP_ENTRY_LOAD_OK; } // The desktop entry isn't skipped immediately struct sfdo_desktop_entry *d_entry = calloc(1, sizeof(*d_entry)); if (d_entry == NULL) { logger_write_oom(logger); return SFDO_DESKTOP_ENTRY_LOAD_OOM; } d_entry->type = type; struct sfdo_hashmap action_set; sfdo_hashmap_init(&action_set, sizeof(struct sfdo_hashmap_entry)); d_entry->id.data = map_entry->base.key; d_entry->id.len = map_entry->base.key_len; if ((r = store_string(loader, loader->path_buf.data, loader->path_buf.len, &d_entry->file_path)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_string(loader, group, "Name", 4, SFDO_DESKTOP_ENTRY_VALUE_REQUIRED, SFDO_DESKTOP_ENTRY_VALUE_LOCALESTRING, &d_entry->name)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_boolean(loader, group, "NoDisplay", 9, &d_entry->no_display)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_string(loader, group, "GenericName", 11, SFDO_DESKTOP_ENTRY_VALUE_OPTIONAL, SFDO_DESKTOP_ENTRY_VALUE_LOCALESTRING, &d_entry->generic_name)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_string(loader, group, "Comment", 7, SFDO_DESKTOP_ENTRY_VALUE_OPTIONAL, SFDO_DESKTOP_ENTRY_VALUE_LOCALESTRING, &d_entry->comment)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_string(loader, group, "Icon", 4, SFDO_DESKTOP_ENTRY_VALUE_OPTIONAL, SFDO_DESKTOP_ENTRY_VALUE_LOCALESTRING, &d_entry->icon)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_show_in(loader, group, d_entry)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((entry = sfdo_desktop_file_group_get_entry(group, "Implements", 10)) != NULL) { items = sfdo_desktop_file_entry_get_value_list(entry, &n_items); if ((r = store_list(loader, items, n_items, &d_entry->implements, &d_entry->n_implements)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } for (size_t i = 0; i < d_entry->n_implements; i++) { struct sfdo_string *iface = &d_entry->implements[i]; if (!validate_dbus_name(iface->data, iface->len, true)) { int line, column; sfdo_desktop_file_entry_get_location(entry, &line, &column); logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: %s is not a valid D-Bus name", line, column, iface->data); r = SFDO_DESKTOP_ENTRY_LOAD_ERROR; goto end; } } } bool startup_notify; bool has_startup_notify; size_t action_i = 0; switch (type) { case SFDO_DESKTOP_ENTRY_APPLICATION: if ((r = load_optional_boolean(loader, group, "StartupNotify", 13, &startup_notify, &has_startup_notify)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } d_entry->app.startup_notify = has_startup_notify ? startup_notify ? SFDO_DESKTOP_ENTRY_STARTUP_NOTIFY_TRUE : SFDO_DESKTOP_ENTRY_STARTUP_NOTIFY_FALSE : SFDO_DESKTOP_ENTRY_STARTUP_NOTIFY_UNKNOWN; if ((r = load_boolean(loader, group, "DBusActivatable", 15, &d_entry->app.dbus_activatable)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if (d_entry->app.dbus_activatable) { if (!validate_dbus_name(d_entry->id.data, d_entry->id.len, false)) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: DBusActivatable is true but %s is not a valid D-Bus name", group_line, group_column, d_entry->id.data); r = SFDO_DESKTOP_ENTRY_LOAD_ERROR; goto end; } } if ((r = load_boolean(loader, group, "Terminal", 8, &d_entry->app.terminal)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_boolean(loader, group, "PrefersNonDefaultGPU", 20, &d_entry->app.prefers_non_default_gpu)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_boolean(loader, group, "SingleMainWindow", 16, &d_entry->app.single_main_window)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_string(loader, group, "TryExec", 7, SFDO_DESKTOP_ENTRY_VALUE_OPTIONAL, SFDO_DESKTOP_ENTRY_VALUE_STRING, &d_entry->app.try_exec)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((entry = sfdo_desktop_file_group_get_entry(group, "Exec", 4)) != NULL) { if ((r = load_exec(loader, entry, d_entry, &d_entry->app.exec)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } } else if (!d_entry->app.dbus_activatable) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: Exec is unset while DBusActivatable is unset or false", group_line, group_column); r = SFDO_DESKTOP_ENTRY_LOAD_ERROR; goto end; } if ((r = load_string(loader, group, "Path", 4, SFDO_DESKTOP_ENTRY_VALUE_OPTIONAL, SFDO_DESKTOP_ENTRY_VALUE_STRING, &d_entry->app.path)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_string(loader, group, "StartupWMClass", 14, SFDO_DESKTOP_ENTRY_VALUE_OPTIONAL, SFDO_DESKTOP_ENTRY_VALUE_STRING, &d_entry->app.startup_wm_class)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_list(loader, group, "MimeType", 8, SFDO_DESKTOP_ENTRY_VALUE_STRING, &d_entry->app.mimetypes, &d_entry->app.n_mimetypes)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_actions(loader, group, &d_entry->app.actions_mem, &d_entry->app.actions, &d_entry->app.n_actions, &action_set)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_list(loader, group, "Categories", 10, SFDO_DESKTOP_ENTRY_VALUE_STRING, &d_entry->app.categories, &d_entry->app.n_categories)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_list(loader, group, "Keywords", 8, SFDO_DESKTOP_ENTRY_VALUE_LOCALESTRING, &d_entry->app.keywords, &d_entry->app.n_keywords)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } for (group = sfdo_desktop_file_group_get_next(group); group != NULL; group = sfdo_desktop_file_group_get_next(group)) { group_name = sfdo_desktop_file_group_get_name(group, &group_name_len); if (strncmp(group_name, DESKTOP_ACTION_PREFIX, sizeof(DESKTOP_ACTION_PREFIX) - 1) != 0) { // Unknown group continue; } sfdo_desktop_file_group_get_location(group, &group_line, &group_column); size_t action_id_len = group_name_len - sizeof(DESKTOP_ACTION_PREFIX) + 1; const char *action_id = group_name + sizeof(DESKTOP_ACTION_PREFIX) - 1; if (sfdo_hashmap_get(&action_set, action_id, action_id_len, false) == NULL) { // "It is not valid to have an action group for an action identifier not mentioned // in the Actions key. Such an action group must be ignored by implementors." logger_write( logger, SFDO_LOG_LEVEL_ERROR, "Ignoring unknown action %s", group_name); continue; } assert(action_i < d_entry->app.n_actions); struct sfdo_desktop_entry_action *action = d_entry->app.actions[action_i++]; if ((r = load_string(loader, group, "Name", 4, SFDO_DESKTOP_ENTRY_VALUE_REQUIRED, SFDO_DESKTOP_ENTRY_VALUE_LOCALESTRING, &action->name)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((r = load_string(loader, group, "Icon", 4, SFDO_DESKTOP_ENTRY_VALUE_OPTIONAL, SFDO_DESKTOP_ENTRY_VALUE_LOCALESTRING, &action->icon)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } if ((entry = sfdo_desktop_file_group_get_entry(group, "Exec", 4)) != NULL) { if ((r = load_exec(loader, entry, d_entry, &action->exec)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } } else if (!d_entry->app.dbus_activatable) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: Exec is unset while DBusActivatable is unset or false", group_line, group_column); r = SFDO_DESKTOP_ENTRY_LOAD_ERROR; goto end; } } if (action_i != d_entry->app.n_actions) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "Found only %zu action groups out of %zu", action_i, d_entry->app.n_actions); r = SFDO_DESKTOP_ENTRY_LOAD_ERROR; goto end; } break; case SFDO_DESKTOP_ENTRY_LINK: if ((r = load_string(loader, group, "URL", 3, SFDO_DESKTOP_ENTRY_VALUE_REQUIRED, SFDO_DESKTOP_ENTRY_VALUE_STRING, &d_entry->link.url)) != SFDO_DESKTOP_ENTRY_LOAD_OK) { goto end; } break; case SFDO_DESKTOP_ENTRY_DIRECTORY: break; } end: sfdo_hashmap_finish(&action_set); if (r == SFDO_DESKTOP_ENTRY_LOAD_OK) { map_entry->entry = d_entry; } else { desktop_entry_destroy(d_entry); } return r; } static bool scan_dir(struct sfdo_desktop_loader *loader, size_t basedir_len) { struct sfdo_desktop_db *db = loader->db; struct sfdo_logger *logger = &db->ctx->logger; struct sfdo_strbuild *pb = &loader->path_buf; struct sfdo_strbuild *ib = &loader->id_buf; DIR *dirp = opendir(pb->data); if (dirp == NULL) { return true; } logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Scanning dir %s", pb->data); size_t base_pb_len = pb->len; size_t base_id_len = ib->len; bool ok = false; struct dirent *dirent; while ((dirent = readdir(dirp)) != NULL) { char *name = dirent->d_name; if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { continue; } size_t name_len = strlen(name); pb->len = base_pb_len; if (!sfdo_strbuild_add(pb, name, name_len, NULL)) { logger_write_oom(logger); goto end; } ib->len = base_id_len; struct stat statbuf; if (stat(pb->data, &statbuf) != 0) { continue; } if (S_ISDIR(statbuf.st_mode)) { if (!sfdo_strbuild_add(ib, name, name_len, "-", SFDO_SIZE1, NULL)) { logger_write_oom(logger); goto end; } if (!sfdo_strbuild_add(pb, "/", SFDO_SIZE1, NULL)) { logger_write_oom(logger); goto end; } if (!scan_dir(loader, basedir_len)) { goto end; } continue; } size_t suffix_len = 0; const char *name_end = name + name_len + 1; if (name_len > sizeof(ENTRY_DESKTOP_SUFFIX) && strcmp(name_end - sizeof(ENTRY_DESKTOP_SUFFIX), ENTRY_DESKTOP_SUFFIX) == 0) { suffix_len = sizeof(ENTRY_DESKTOP_SUFFIX) - 1; } else if (name_len > sizeof(ENTRY_DIRECTORY_SUFFIX) && strcmp(name_end - sizeof(ENTRY_DIRECTORY_SUFFIX), ENTRY_DIRECTORY_SUFFIX) == 0) { suffix_len = sizeof(ENTRY_DIRECTORY_SUFFIX) - 1; } else { // If there's any other extension (/(.*\).(.+)/), skip the file; otherwise, try to read // it anyway, or as the spec calls it, "fall back to recognition via 'magic detection'". bool has_ext = false; for (size_t i = 0; i < name_len - 1; i++) { if (name[i] == '.') { has_ext = true; break; } } if (has_ext) { continue; } } if (!sfdo_strbuild_add(ib, name, name_len - suffix_len, NULL)) { logger_write_oom(logger); goto end; } struct sfdo_desktop_map_entry *map_entry = sfdo_hashmap_get(&db->entries, ib->data, ib->len, true); if (map_entry == NULL) { logger_write_oom(logger); goto end; } else if (map_entry->base.key != NULL) { // Add only the first one continue; } else { map_entry->base.key = sfdo_strpool_add(&db->strings, ib->data, ib->len); if (map_entry->base.key == NULL) { logger_write_oom(logger); goto end; } } FILE *fp = fopen(pb->data, "r"); if (fp == NULL) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "Failed to open %s: %s", pb->data, strerror(errno)); continue; } struct sfdo_desktop_file_error desktop_file_error; struct sfdo_desktop_file_document *doc = sfdo_desktop_file_document_load( fp, loader->locale, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, &desktop_file_error); fclose(fp); if (doc != NULL) { // Avoid storing strings for entries we end up ignoring due to bad format struct sfdo_strpool_state strings_state; sfdo_strpool_save(&db->strings, &strings_state); enum sfdo_desktop_entry_load_result result = entry_load(loader, doc, map_entry); sfdo_desktop_file_document_destroy(doc); switch (result) { case SFDO_DESKTOP_ENTRY_LOAD_OK: if (map_entry->entry != NULL) { logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Loaded entry %s", ib->data); ++loader->n_entries; } continue; case SFDO_DESKTOP_ENTRY_LOAD_ERROR: sfdo_strpool_restore(&db->strings, &strings_state); break; case SFDO_DESKTOP_ENTRY_LOAD_OOM: goto end; } } else { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: %s", desktop_file_error.line, desktop_file_error.column, sfdo_desktop_file_error_code_get_description(desktop_file_error.code)); } logger_write(logger, SFDO_LOG_LEVEL_ERROR, "Failed to load %s", pb->data); } ok = true; end: closedir(dirp); return ok; } static struct sfdo_desktop_db *db_create( struct sfdo_desktop_ctx *ctx, const struct sfdo_string *basedirs, size_t n_basedirs) { struct sfdo_desktop_db *db = calloc(1, sizeof(*db)); if (db == NULL) { goto err_db; } if (!sfdo_dirs_store(basedirs, n_basedirs, &db->basedirs, &db->n_basedirs, &db->basedirs_mem)) { goto err_dirs; } db->ctx = ctx; sfdo_hashmap_init(&db->entries, sizeof(struct sfdo_desktop_map_entry)); sfdo_strpool_init(&db->strings); return db; err_dirs: free(db); err_db: logger_write_oom(&ctx->logger); return NULL; } SFDO_API struct sfdo_desktop_db *sfdo_desktop_db_load( struct sfdo_desktop_ctx *ctx, const char *locale) { return sfdo_desktop_db_load_from(ctx, locale, ctx->default_basedirs, ctx->default_n_basedirs); } SFDO_API struct sfdo_desktop_db *sfdo_desktop_db_load_from(struct sfdo_desktop_ctx *ctx, const char *locale, const struct sfdo_string *basedirs, size_t n_basedirs) { struct sfdo_desktop_db *db = db_create(ctx, basedirs, n_basedirs); if (db == NULL) { return NULL; } struct sfdo_desktop_loader loader = { .db = db, .locale = locale, }; struct sfdo_strbuild *pb = &loader.path_buf; struct sfdo_strbuild *ib = &loader.id_buf; sfdo_strbuild_init(pb); sfdo_strbuild_init(ib); bool ok = false; for (size_t basedir_i = 0; basedir_i < db->n_basedirs; basedir_i++) { struct sfdo_string *basedir = &db->basedirs[basedir_i]; sfdo_strbuild_reset(pb); sfdo_strbuild_reset(ib); if (!sfdo_strbuild_add(pb, basedir->data, basedir->len, NULL)) { logger_write_oom(&db->ctx->logger); goto end; } if (!scan_dir(&loader, basedir->len)) { goto end; } } if (loader.n_entries > 0) { db->entries_list = calloc(loader.n_entries, sizeof(struct sfdo_desktop_entry *)); if (db->entries_list == NULL) { logger_write_oom(&db->ctx->logger); goto end; } } db->n_entries = loader.n_entries; struct sfdo_hashmap *entries = &db->entries; size_t list_i = 0; for (size_t i = 0; i < entries->cap; i++) { struct sfdo_desktop_map_entry *map_entry = &((struct sfdo_desktop_map_entry *)entries->mem)[i]; if (map_entry->base.key != NULL && map_entry->entry != NULL) { db->entries_list[list_i++] = map_entry->entry; } } assert(list_i == db->n_entries); ok = true; end: sfdo_strbuild_finish(pb); sfdo_strbuild_finish(ib); struct sfdo_desktop_exec_scanner *exec_scanner = &loader.exec; free(exec_scanner->buf); free(exec_scanner->lit_buf); if (ok) { return db; } else { sfdo_desktop_db_destroy(db); return NULL; } } SFDO_API void sfdo_desktop_db_destroy(struct sfdo_desktop_db *db) { if (db == NULL) { return; } for (size_t i = 0; i < db->n_entries; i++) { desktop_entry_destroy(db->entries_list[i]); } free(db->entries_list); sfdo_hashmap_finish(&db->entries); sfdo_strpool_finish(&db->strings); free(db->basedirs_mem); free(db->basedirs); free(db); } SFDO_API struct sfdo_desktop_entry *sfdo_desktop_db_get_entry_by_id( struct sfdo_desktop_db *db, const char *id, size_t id_len) { if (id_len == SFDO_NT) { id_len = strlen(id); } struct sfdo_desktop_map_entry *map_entry = sfdo_hashmap_get(&db->entries, id, id_len, false); if (map_entry == NULL) { return NULL; } return map_entry->entry; } SFDO_API struct sfdo_desktop_entry **sfdo_desktop_db_get_entries( struct sfdo_desktop_db *db, size_t *n_entries) { *n_entries = db->n_entries; return db->entries_list; } libsfdo-0.1.3/sfdo-desktop/desktop.c000066400000000000000000000035461467233572500174020ustar00rootroot00000000000000#include #include #include #include #include "common/api.h" #include "common/membuild.h" #include "sfdo-desktop/internal.h" #define APPLICATIONS_SUFFIX "applications/" SFDO_API struct sfdo_desktop_ctx *sfdo_desktop_ctx_create(struct sfdo_basedir_ctx *basedir_ctx) { struct sfdo_desktop_ctx *ctx = calloc(1, sizeof(*ctx)); if (ctx == NULL) { return NULL; } logger_setup(&ctx->logger); if (basedir_ctx != NULL) { size_t n_dirs; const struct sfdo_string *data_dirs = sfdo_basedir_get_data_dirs(basedir_ctx, &n_dirs); size_t mem_size = 0; for (size_t i = 0; i < n_dirs; i++) { mem_size += data_dirs[i].len + sizeof(APPLICATIONS_SUFFIX); } struct sfdo_string *dirs = calloc(n_dirs, sizeof(*dirs)); if (dirs == NULL) { goto err; } struct sfdo_membuild mem_buf; if (!sfdo_membuild_setup(&mem_buf, mem_size)) { free(dirs); goto err; } for (size_t i = 0; i < n_dirs; i++) { const struct sfdo_string *data_dir = &data_dirs[i]; dirs[i].data = mem_buf.data + mem_buf.len; // APPLICATIONS_SUFFIX includes a null terminator sfdo_membuild_add(&mem_buf, data_dir->data, data_dir->len, APPLICATIONS_SUFFIX, sizeof(APPLICATIONS_SUFFIX), NULL); dirs[i].len = data_dir->len + sizeof(APPLICATIONS_SUFFIX) - 1; } ctx->default_basedirs = dirs; ctx->default_basedirs_mem = mem_buf.data; ctx->default_n_basedirs = n_dirs; } return ctx; err: sfdo_desktop_ctx_destroy(ctx); return NULL; } SFDO_API void sfdo_desktop_ctx_destroy(struct sfdo_desktop_ctx *ctx) { if (ctx == NULL) { return; } free(ctx->default_basedirs); free(ctx->default_basedirs_mem); free(ctx); } SFDO_API void sfdo_desktop_ctx_set_log_handler(struct sfdo_desktop_ctx *ctx, enum sfdo_log_level level, sfdo_log_handler_func_t func, void *data) { logger_configure(&ctx->logger, level, func, data); } libsfdo-0.1.3/sfdo-desktop/entry.c000066400000000000000000000206451467233572500170710ustar00rootroot00000000000000#include #include #include #include #include "common/api.h" #include "sfdo-desktop/internal.h" static const char *unpack_string(struct sfdo_string *string, size_t *len) { if (len != NULL) { *len = string->len; } return string->data; } SFDO_API enum sfdo_desktop_entry_type sfdo_desktop_entry_get_type( struct sfdo_desktop_entry *entry) { return entry->type; } SFDO_API const char *sfdo_desktop_entry_get_id(struct sfdo_desktop_entry *entry, size_t *len) { return unpack_string(&entry->id, len); } SFDO_API const char *sfdo_desktop_entry_get_file_path( struct sfdo_desktop_entry *entry, size_t *len) { return unpack_string(&entry->file_path, len); } SFDO_API const char *sfdo_desktop_entry_get_name(struct sfdo_desktop_entry *entry, size_t *len) { return unpack_string(&entry->name, len); } SFDO_API const char *sfdo_desktop_entry_get_generic_name( struct sfdo_desktop_entry *entry, size_t *len) { return unpack_string(&entry->generic_name, len); } SFDO_API bool sfdo_desktop_entry_get_no_display(struct sfdo_desktop_entry *entry) { return entry->no_display; } SFDO_API const char *sfdo_desktop_entry_get_comment(struct sfdo_desktop_entry *entry, size_t *len) { return unpack_string(&entry->comment, len); } SFDO_API const char *sfdo_desktop_entry_get_icon(struct sfdo_desktop_entry *entry, size_t *len) { return unpack_string(&entry->icon, len); } SFDO_API bool sfdo_desktop_entry_show_in( struct sfdo_desktop_entry *entry, const char *env, size_t env_len) { if (env != NULL) { if (env_len == SFDO_NT) { env_len = strlen(env); } for (size_t i = 0; i < entry->n_show_exceptions; i++) { struct sfdo_string *ex = &entry->show_exceptions[i]; if (ex->len == env_len && memcmp(ex->data, env, env_len) == 0) { return !entry->default_show; } } } return entry->default_show; } SFDO_API bool sfdo_desktop_entry_get_dbus_activatable(struct sfdo_desktop_entry *entry) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); return entry->app.dbus_activatable; } SFDO_API const char *sfdo_desktop_entry_get_try_exec( struct sfdo_desktop_entry *entry, size_t *len) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); return unpack_string(&entry->app.try_exec, len); } SFDO_API struct sfdo_desktop_exec *sfdo_desktop_entry_get_exec(struct sfdo_desktop_entry *entry) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); if (entry->app.exec.literals == NULL) { return NULL; } return &entry->app.exec; } SFDO_API const char *sfdo_desktop_entry_get_path(struct sfdo_desktop_entry *entry, size_t *len) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); return unpack_string(&entry->app.path, len); } SFDO_API bool sfdo_desktop_entry_get_terminal(struct sfdo_desktop_entry *entry) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); return entry->app.terminal; } SFDO_API struct sfdo_desktop_entry_action **sfdo_desktop_entry_get_actions( struct sfdo_desktop_entry *entry, size_t *n_actions) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); *n_actions = entry->app.n_actions; return entry->app.actions; } SFDO_API const struct sfdo_string *sfdo_desktop_entry_get_mimetypes( struct sfdo_desktop_entry *entry, size_t *n_mimetypes) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); *n_mimetypes = entry->app.n_mimetypes; return entry->app.mimetypes; } SFDO_API const struct sfdo_string *sfdo_desktop_entry_get_categories( struct sfdo_desktop_entry *entry, size_t *n_categories) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); *n_categories = entry->app.n_categories; return entry->app.categories; } SFDO_API const struct sfdo_string *sfdo_desktop_entry_get_implements( struct sfdo_desktop_entry *entry, size_t *n_implements) { *n_implements = entry->n_implements; return entry->implements; } SFDO_API const struct sfdo_string *sfdo_desktop_entry_get_keywords( struct sfdo_desktop_entry *entry, size_t *n_keywords) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); *n_keywords = entry->app.n_keywords; return entry->app.keywords; } SFDO_API enum sfdo_desktop_entry_startup_notify sfdo_desktop_entry_get_startup_notify( struct sfdo_desktop_entry *entry) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); return entry->app.startup_notify; } SFDO_API const char *sfdo_desktop_entry_get_startup_wm_class( struct sfdo_desktop_entry *entry, size_t *len) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); return unpack_string(&entry->app.startup_wm_class, len); } SFDO_API const char *sfdo_desktop_entry_get_url(struct sfdo_desktop_entry *entry, size_t *len) { assert(entry->type == SFDO_DESKTOP_ENTRY_LINK); return unpack_string(&entry->link.url, len); } SFDO_API bool sfdo_desktop_entry_get_prefers_non_default_gpu(struct sfdo_desktop_entry *entry) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); return entry->app.prefers_non_default_gpu; } SFDO_API bool sfdo_desktop_entry_get_single_main_window(struct sfdo_desktop_entry *entry) { assert(entry->type == SFDO_DESKTOP_ENTRY_APPLICATION); return entry->app.single_main_window; } SFDO_API const char *sfdo_desktop_entry_action_get_id( struct sfdo_desktop_entry_action *action, size_t *len) { return unpack_string(&action->id, len); } SFDO_API const char *sfdo_desktop_entry_action_get_name( struct sfdo_desktop_entry_action *action, size_t *len) { return unpack_string(&action->name, len); } SFDO_API const char *sfdo_desktop_entry_action_get_icon( struct sfdo_desktop_entry_action *action, size_t *len) { return unpack_string(&action->icon, len); } SFDO_API struct sfdo_desktop_exec *sfdo_desktop_entry_action_get_exec( struct sfdo_desktop_entry_action *action) { if (action->exec.literals == NULL) { return NULL; } return &action->exec; } SFDO_API bool sfdo_desktop_exec_get_has_target(struct sfdo_desktop_exec *exec) { return exec->target_i != (size_t)-1; } SFDO_API bool sfdo_desktop_exec_get_supports_list(struct sfdo_desktop_exec *exec) { return exec->supports_list; } SFDO_API bool sfdo_desktop_exec_get_supports_uri(struct sfdo_desktop_exec *exec) { return exec->supports_uri; } SFDO_API struct sfdo_desktop_exec_command *sfdo_desktop_exec_format( struct sfdo_desktop_exec *exec, const char *path) { return sfdo_desktop_exec_format_list(exec, &path, 1); } SFDO_API struct sfdo_desktop_exec_command *sfdo_desktop_exec_format_list( struct sfdo_desktop_exec *exec, const char **paths, size_t n_paths) { bool has_target = sfdo_desktop_exec_get_has_target(exec); bool embed_single = exec->embed.before > 0 || exec->embed.after > 0; size_t n_args = exec->n_literals; if (has_target && !embed_single) { if (!exec->supports_list && n_paths > 1) { // Only use the first target n_paths = 1; } n_args += n_paths; } struct sfdo_desktop_exec_command *cmd = calloc(1, sizeof(*cmd)); if (cmd == NULL) { goto err_cmd; } cmd->n_args = n_args; // cmd->args[n_args] is NULL cmd->args = calloc(n_args + 1, sizeof(const char *)); if (cmd->args == NULL) { goto err_args; } if (has_target) { size_t src_i = 0; size_t dst_i = 0; while (src_i < exec->target_i) { cmd->args[dst_i++] = exec->literals[src_i++]; } if (embed_single && n_paths > 0) { const char *embed_src = exec->literals[src_i++]; const char *uri = paths[0]; size_t uri_len = strlen(uri); cmd->embedded_mem = malloc(exec->embed.before + exec->embed.after + uri_len + 1); if (cmd->embedded_mem == NULL) { goto err_embedded_mem; } char *ptr = cmd->embedded_mem; memcpy(ptr, embed_src, exec->embed.before); ptr += exec->embed.before; memcpy(ptr, uri, uri_len); ptr += uri_len; memcpy(ptr, embed_src + exec->embed.before, exec->embed.after); ptr += exec->embed.after; *ptr = '\0'; cmd->args[dst_i++] = cmd->embedded_mem; } else { for (size_t i = 0; i < n_paths; i++) { cmd->args[dst_i++] = paths[i]; } } while (src_i < exec->n_literals) { cmd->args[dst_i++] = exec->literals[src_i++]; } } else { for (size_t i = 0; i < n_args; i++) { cmd->args[i] = exec->literals[i]; } } return cmd; err_embedded_mem: free(cmd->args); err_args: free(cmd); err_cmd: return NULL; } SFDO_API const char **sfdo_desktop_exec_command_get_args( struct sfdo_desktop_exec_command *command, size_t *n_args) { if (n_args != NULL) { *n_args = command->n_args; } return command->args; } SFDO_API void sfdo_desktop_exec_command_destroy(struct sfdo_desktop_exec_command *command) { free(command->embedded_mem); free(command->args); free(command); } libsfdo-0.1.3/sfdo-desktop/meson.build000066400000000000000000000001021467233572500177100ustar00rootroot00000000000000sfdo_desktop_src = files( 'db.c', 'desktop.c', 'entry.c', ) libsfdo-0.1.3/sfdo-icon/000077500000000000000000000000001467233572500150345ustar00rootroot00000000000000libsfdo-0.1.3/sfdo-icon/cache.c000066400000000000000000000166311467233572500162520ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include "common/grow.h" #include "sfdo-icon/internal.h" struct sfdo_icon_cache_image { const char *name; // Borrowed from data size_t name_len; int formats; // enum sfdo_icon_format size_t next_i; // -1 if last }; struct sfdo_icon_cache_dir { const char *name; // Borrowed from data size_t name_len; size_t start_i, end_i; }; struct sfdo_icon_cache { char *data; // mmap'ed size_t size; struct sfdo_icon_cache_dir *dirs; size_t n_dirs; struct sfdo_icon_cache_image *images; }; static inline bool read_card16(struct sfdo_icon_cache *cache, size_t offset, uint16_t *out) { if (offset > cache->size - 2) { return false; } const uint8_t *data = (uint8_t *)&cache->data[offset]; *out = (uint16_t)(((uint16_t)data[0] << 8) | ((uint16_t)data[1] << 0)); return true; } static inline bool read_card32(struct sfdo_icon_cache *cache, size_t offset, uint32_t *out) { if (offset > cache->size - 4) { return false; } const uint8_t *data = (uint8_t *)&cache->data[offset]; *out = (uint32_t)(((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) | ((uint32_t)data[2] << 8) | ((uint32_t)data[3] << 0)); return true; } static inline bool read_str( struct sfdo_icon_cache *cache, size_t offset, const char **out, size_t *out_len) { if (offset > cache->size) { return false; } size_t limit = cache->size - offset; *out = &cache->data[offset]; *out_len = strnlen(*out, limit); return *out_len < limit; } static bool load_cache(struct sfdo_icon_cache *cache, struct sfdo_logger *logger) { uint16_t major, minor; if (!read_card16(cache, 0, &major) || !read_card16(cache, 2, &minor)) { goto err_format; } if (major != 1 || minor != 0) { logger_write(logger, SFDO_LOG_LEVEL_INFO, "Expected version 1.0, got %d.%d", major, minor); return false; } uint32_t hash_off, dir_list_off; if (!read_card32(cache, 4, &hash_off) || !read_card32(cache, 8, &dir_list_off)) { goto err_format; } uint32_t n_dirs; if (!read_card32(cache, dir_list_off, &n_dirs)) { goto err_format; } cache->n_dirs = n_dirs; cache->dirs = calloc(n_dirs, sizeof(*cache->dirs)); if (cache->dirs == NULL) { logger_write_oom(logger); return false; } for (uint32_t i = 0; i < n_dirs; i++) { uint32_t dir_off; if (!read_card32(cache, dir_list_off + 4 * (1 + i), &dir_off)) { goto err_format; } struct sfdo_icon_cache_dir *dir = &cache->dirs[i]; if (!read_str(cache, dir_off, &dir->name, &dir->name_len)) { goto err_format; } dir->start_i = (size_t)-1; } size_t images_len = 0, images_cap = 0; uint32_t n_buckets; if (!read_card32(cache, hash_off, &n_buckets)) { goto err_format; } for (uint32_t i = 0; i < n_buckets; i++) { uint32_t icon_off; if (!read_card32(cache, hash_off + 4 + 4 * i, &icon_off)) { goto err_format; }; while (icon_off != 0xFFFFFFFF) { uint32_t chain_off, name_off, image_list_off; if (!read_card32(cache, icon_off, &chain_off) || !read_card32(cache, icon_off + 4, &name_off) || !read_card32(cache, icon_off + 8, &image_list_off)) { goto err_format; } const char *name; size_t name_len; if (!read_str(cache, name_off, &name, &name_len)) { goto err_format; } uint32_t n_images; if (!read_card32(cache, image_list_off, &n_images)) { goto err_format; } for (uint32_t j = 0; j < n_images; j++) { uint32_t image_off = image_list_off + 4 + 8 * j; uint16_t dir_i, flags; if (!read_card16(cache, image_off, &dir_i) || !read_card16(cache, image_off + 2, &flags)) { goto err_format; } if (dir_i >= n_dirs) { goto err_format; } if (!sfdo_grow(&cache->images, &images_cap, images_len, sizeof(*cache->images))) { logger_write_oom(logger); return false; } struct sfdo_icon_cache_dir *dir = &cache->dirs[dir_i]; if (dir->start_i == (size_t)-1) { dir->start_i = images_len; } else { cache->images[dir->end_i].next_i = images_len; } dir->end_i = images_len; struct sfdo_icon_cache_image *image = &cache->images[images_len++]; image->name = name; image->name_len = name_len; image->formats = 0; if ((flags & 0x1) != 0) { image->formats |= SFDO_ICON_FORMAT_MASK_XPM; } if ((flags & 0x2) != 0) { image->formats |= SFDO_ICON_FORMAT_MASK_SVG; } if ((flags & 0x4) != 0) { image->formats |= SFDO_ICON_FORMAT_MASK_PNG; } image->next_i = (size_t)-1; } icon_off = chain_off; } } logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Found %zu cached image(s)", images_len); return true; err_format: logger_write(logger, SFDO_LOG_LEVEL_INFO, "Invalid icon-theme.cache format"); return false; } struct sfdo_icon_cache *icon_cache_create( const char *path, time_t dir_mtime, struct sfdo_logger *logger) { int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) { goto err_fd; } logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Found cache file %s", path); struct stat statbuf; if (fstat(fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) { logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Not a regular file"); goto err_stat; } if (statbuf.st_mtime < dir_mtime) { logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Too old: file mtime %ld, dir mtime %ld", (long)statbuf.st_mtime, (long)dir_mtime); goto err_stat; } size_t size = (size_t)statbuf.st_size; char *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if (data == MAP_FAILED) { logger_write( logger, SFDO_LOG_LEVEL_ERROR, "Failed to mmap() %s: %s", path, strerror(errno)); return NULL; } struct sfdo_icon_cache *cache = calloc(1, sizeof(*cache)); if (cache == NULL) { logger_write_oom(logger); munmap(data, size); return NULL; } cache->data = data; cache->size = size; if (!load_cache(cache, logger)) { logger_write(logger, SFDO_LOG_LEVEL_INFO, "Failed to load, ignoring"); icon_cache_destroy(cache); return NULL; } return cache; err_stat: close(fd); err_fd: return NULL; } void icon_cache_destroy(struct sfdo_icon_cache *cache) { if (cache == NULL) { return; } munmap(cache->data, cache->size); free(cache->dirs); free(cache->images); free(cache); } bool icon_cache_scan_dir(struct sfdo_icon_cache *cache, struct sfdo_icon_scanner *scanner, const struct sfdo_string *basedir, const struct sfdo_icon_subdir *subdir) { struct sfdo_logger *logger = scanner->logger; assert(subdir != NULL); const char *subdir_data = subdir->path.data; struct sfdo_icon_cache_dir *dir = NULL; for (size_t i = 0; i < cache->n_dirs; i++) { struct sfdo_icon_cache_dir *curr = &cache->dirs[i]; if (subdir->path.len == curr->name_len && memcmp(subdir_data, curr->name, curr->name_len) == 0) { dir = curr; break; } } if (dir == NULL) { return true; } size_t n_images = 0; struct sfdo_icon_cache_image *image; for (size_t i = dir->start_i; i != (size_t)-1; i = image->next_i) { image = &cache->images[i]; const char *name = icon_scanner_intern_name(scanner, image->name, image->name_len); if (name == NULL) { return false; } if (!icon_scanner_add_image( scanner, basedir, subdir, name, image->name_len, image->formats)) { return false; } ++n_images; } logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Added %zu cached image(s) for %s in %s", n_images, subdir_data, basedir->data); return true; } libsfdo-0.1.3/sfdo-icon/icon.c000066400000000000000000000046351467233572500161400ustar00rootroot00000000000000#include #include #include #include #include #include "common/api.h" #include "common/membuild.h" #include "sfdo-icon/internal.h" #define ICONS_HOME_DIR "/.icons/" #define ICONS_SUFFIX "icons/" #define PIXMAPS_BASE_DIR "/usr/share/pixmaps/" SFDO_API struct sfdo_icon_ctx *sfdo_icon_ctx_create(struct sfdo_basedir_ctx *basedir_ctx) { struct sfdo_icon_ctx *ctx = calloc(1, sizeof(*ctx)); if (ctx == NULL) { return NULL; } logger_setup(&ctx->logger); if (basedir_ctx != NULL) { const char *home = getenv("HOME"); if (home == NULL) { home = ""; } size_t home_len = strlen(home); size_t n_data_dirs; const struct sfdo_string *data_dirs = sfdo_basedir_get_data_dirs(basedir_ctx, &n_data_dirs); size_t n_dirs = n_data_dirs + 2; size_t mem_size = home_len + sizeof(ICONS_HOME_DIR); for (size_t i = 0; i < n_data_dirs; i++) { mem_size += data_dirs[i].len + sizeof(ICONS_SUFFIX); } struct sfdo_string *dirs = calloc(n_dirs, sizeof(*dirs)); if (dirs == NULL) { goto err; } struct sfdo_membuild mem_buf; if (!sfdo_membuild_setup(&mem_buf, mem_size)) { free(dirs); goto err; } struct sfdo_string *dir_iter = dirs; dir_iter->data = mem_buf.data + mem_buf.len; dir_iter->len = home_len + sizeof(ICONS_HOME_DIR) - 1; sfdo_membuild_add(&mem_buf, home, home_len, ICONS_HOME_DIR, sizeof(ICONS_HOME_DIR), NULL); ++dir_iter; for (size_t i = 0; i < n_data_dirs; i++) { const struct sfdo_string *data_dir = &data_dirs[i]; dir_iter->data = mem_buf.data + mem_buf.len; dir_iter->len = data_dir->len + sizeof(ICONS_SUFFIX) - 1; sfdo_membuild_add(&mem_buf, data_dir->data, data_dir->len, ICONS_SUFFIX, sizeof(ICONS_SUFFIX), NULL); ++dir_iter; } assert(mem_buf.len == mem_size); dir_iter->data = PIXMAPS_BASE_DIR; dir_iter->len = sizeof(PIXMAPS_BASE_DIR) - 1; ctx->default_basedirs = dirs; ctx->default_basedirs_mem = mem_buf.data; ctx->default_n_basedirs = n_dirs; } return ctx; err: sfdo_icon_ctx_destroy(ctx); return NULL; } SFDO_API void sfdo_icon_ctx_destroy(struct sfdo_icon_ctx *ctx) { if (ctx == NULL) { return; } free(ctx->default_basedirs); free(ctx->default_basedirs_mem); free(ctx); } SFDO_API void sfdo_icon_ctx_set_log_handler(struct sfdo_icon_ctx *ctx, enum sfdo_log_level level, sfdo_log_handler_func_t func, void *data) { logger_configure(&ctx->logger, level, func, data); } libsfdo-0.1.3/sfdo-icon/load.c000066400000000000000000000400511467233572500161170ustar00rootroot00000000000000#include #include #include #include #include #include "common/api.h" #include "common/dirs.h" #include "common/hash.h" #include "common/striter.h" #include "common/strpool.h" #include "sfdo-icon/internal.h" #define DEFAULT_THEME_NAME "hicolor" #define INDEX_THEME_PATH "/index.theme" struct sfdo_icon_scheduled_node { const char *name; // Borrowed from theme strings size_t name_len; struct sfdo_icon_scheduled_node *next; }; struct sfdo_icon_subdir_group { struct sfdo_hashmap_entry base; bool seen; }; struct sfdo_icon_loader { struct sfdo_icon_theme *theme; struct sfdo_hashmap seen_nodes; // sfdo_hashmap_entry struct sfdo_icon_scheduled_node *scheduled_nodes; // Owned struct sfdo_icon_scheduled_node *curr_node; bool relaxed; bool allow_missing; }; // Returns 0 on error static int parse_positive_integer(const char *s) { int n = 0; for (; *s != '\0'; s++) { if (*s < '0' || *s > '9') { return false; } // Arbitrary limit such that n² ≤ INT_MAX if (n >= 32768) { return 0; } n = n * 10 + *s - '0'; } return n; } static bool schedule_node(struct sfdo_icon_loader *loader, const char *name, size_t name_len, struct sfdo_icon_scheduled_node **out) { struct sfdo_logger *logger = &loader->theme->ctx->logger; struct sfdo_hashmap_entry *entry = sfdo_hashmap_get(&loader->seen_nodes, name, name_len, true); if (entry == NULL) { logger_write_oom(logger); return false; } else if (entry->key != NULL) { // Already scheduled logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "%s already scheduled", name); *out = NULL; return true; } const char *owned_name = sfdo_strpool_add(&loader->theme->strings, name, name_len); if (owned_name == NULL) { logger_write_oom(logger); return false; } logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Scheduling %s", owned_name); entry->key = owned_name; struct sfdo_icon_scheduled_node *s_node = calloc(1, sizeof(*s_node)); if (s_node == NULL) { logger_write_oom(logger); return false; } s_node->name = owned_name; s_node->name_len = name_len; *out = s_node; return true; } static bool schedule_node_list(struct sfdo_icon_loader *loader, const char *list) { struct sfdo_icon_scheduled_node *last = loader->curr_node; size_t name_start, name_len; size_t iter = 0; while (sfdo_striter(list, ',', &iter, &name_start, &name_len)) { if (name_len == 0) { continue; } struct sfdo_icon_scheduled_node *s_node; if (!schedule_node(loader, list + name_start, name_len, &s_node)) { return false; } if (s_node != NULL) { s_node->next = last->next; last->next = s_node; last = s_node; } } return true; } static bool add_directory_list(struct sfdo_icon_loader *loader, const char *list, struct sfdo_hashmap *subdir_set, int group_line, int group_column) { struct sfdo_icon_theme *theme = loader->theme; struct sfdo_logger *logger = &theme->ctx->logger; size_t dir_start, dir_len; size_t iter = 0; while (sfdo_striter(list, ',', &iter, &dir_start, &dir_len)) { if (dir_len == 0) { continue; } const char *dir = list + dir_start; struct sfdo_hashmap_entry *entry = sfdo_hashmap_get(subdir_set, dir, dir_len, true); if (entry == NULL) { logger_write_oom(logger); return false; } else if (entry->key != NULL) { logger_write(logger, loader->relaxed ? SFDO_LOG_LEVEL_INFO : SFDO_LOG_LEVEL_ERROR, "%d:%d: duplicate directory \"%s\"", group_line, group_column, entry->key); if (loader->relaxed) { continue; } else { return false; } } entry->key = sfdo_strpool_add(&theme->strings, dir, dir_len); if (entry->key == NULL) { logger_write_oom(logger); return false; } } return true; } static struct sfdo_icon_theme_node *node_load(struct sfdo_icon_loader *loader, struct sfdo_desktop_file_document *doc, const char *node_name, size_t node_name_len) { struct sfdo_icon_theme *theme = loader->theme; struct sfdo_logger *logger = &theme->ctx->logger; const char *group_name; size_t group_name_len; int group_line, group_column; struct sfdo_desktop_file_entry *entry; int entry_line, entry_column; const char *value; struct sfdo_desktop_file_group *group = sfdo_desktop_file_document_get_groups(doc); if (group == NULL) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "Expected \"Icon Theme\" group"); return NULL; } sfdo_desktop_file_group_get_location(group, &group_line, &group_column); group_name = sfdo_desktop_file_group_get_name(group, &group_name_len); if (strcmp(group_name, "Icon Theme") != 0) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: expected \"Icon Theme\" group, got \"%s\"", group_line, group_column, group_name); return NULL; } if ((entry = sfdo_desktop_file_group_get_entry(group, "Inherits", 8)) != NULL) { if (!schedule_node_list(loader, sfdo_desktop_file_entry_get_value(entry, NULL))) { return NULL; } } struct sfdo_icon_theme_node *node = NULL; struct sfdo_hashmap subdir_set; sfdo_hashmap_init(&subdir_set, sizeof(struct sfdo_icon_subdir_group)); if ((entry = sfdo_desktop_file_group_get_entry(group, "Directories", 11)) != NULL) { value = sfdo_desktop_file_entry_get_value(entry, NULL); if (!add_directory_list(loader, value, &subdir_set, group_line, group_column)) { goto err_initial; } } else { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: Directories is unset", group_line, group_column); goto err_initial; } if ((entry = sfdo_desktop_file_group_get_entry(group, "ScaledDirectories", 17)) != NULL) { value = sfdo_desktop_file_entry_get_value(entry, NULL); if (!add_directory_list(loader, value, &subdir_set, group_line, group_column)) { goto err_initial; } } node = calloc(1, sizeof(*node)); if (node == NULL) { logger_write_oom(logger); goto err_node; } node->n_subdirs = subdir_set.len; if (node->n_subdirs > 0) { node->subdirs = calloc(node->n_subdirs, sizeof(*node->subdirs)); if (node->subdirs == NULL) { logger_write_oom(logger); goto err_node_subdirs; } } if (!icon_state_init(&node->state, (node->n_subdirs + 1) * theme->n_basedirs)) { logger_write_oom(logger); goto err_node_state; } node->name = node_name; node->name_len = node_name_len; size_t subdir_i = 0; for (group = sfdo_desktop_file_group_get_next(group); group != NULL; group = sfdo_desktop_file_group_get_next(group)) { group_name = sfdo_desktop_file_group_get_name(group, &group_name_len); if (strncmp(group_name, "X-", 2) == 0) { // Extension group continue; } struct sfdo_icon_subdir_group *subdir_group = sfdo_hashmap_get(&subdir_set, group_name, group_name_len, false); if (subdir_group == NULL) { // Unknown group continue; } sfdo_desktop_file_group_get_location(group, &group_line, &group_column); if (subdir_group->seen) { assert(loader->relaxed); logger_write(logger, SFDO_LOG_LEVEL_INFO, "%d:%d: duplicate directory group \"%s\"", group_line, group_column, group_name); continue; } subdir_group->seen = true; assert(subdir_i < node->n_subdirs); struct sfdo_icon_subdir *subdir = &node->subdirs[subdir_i++]; subdir->path.data = subdir_group->base.key; subdir->path.len = group_name_len; if ((entry = sfdo_desktop_file_group_get_entry(group, "Size", 4)) != NULL) { value = sfdo_desktop_file_entry_get_value(entry, NULL); if ((subdir->size = parse_positive_integer(value)) == 0) { goto err_dir_value; } } else { logger_write( logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: Size is unset", group_line, group_column); goto err_dir; } subdir->type = SFDO_ICON_SUBDIR_THRESHOLD; if ((entry = sfdo_desktop_file_group_get_entry(group, "Type", 4)) != NULL) { value = sfdo_desktop_file_entry_get_value(entry, NULL); if (strcmp(value, "Fixed") == 0) { subdir->type = SFDO_ICON_SUBDIR_FIXED; } else if (strcmp(value, "Scalable") == 0) { subdir->type = SFDO_ICON_SUBDIR_SCALABLE; } else if (strcmp(value, "Threshold") == 0) { subdir->type = SFDO_ICON_SUBDIR_THRESHOLD; } else { sfdo_desktop_file_entry_get_location(entry, &entry_line, &entry_column); logger_write(logger, loader->relaxed ? SFDO_LOG_LEVEL_INFO : SFDO_LOG_LEVEL_ERROR, "%d:%d: invalid Type \"%s\"", entry_line, entry_column, value); if (!loader->relaxed) { goto err_dir; } } } if ((entry = sfdo_desktop_file_group_get_entry(group, "Scale", 5)) != NULL) { value = sfdo_desktop_file_entry_get_value(entry, NULL); if ((subdir->scale = parse_positive_integer(value)) == 0) { goto err_dir_value; } } else { subdir->scale = 1; } subdir->min_pixel_size = subdir->size; subdir->max_pixel_size = subdir->size; int threshold = 2; switch (subdir->type) { case SFDO_ICON_SUBDIR_FIXED: subdir->min_pixel_size = subdir->max_pixel_size = subdir->size; break; case SFDO_ICON_SUBDIR_SCALABLE: if ((entry = sfdo_desktop_file_group_get_entry(group, "MinSize", 7)) != NULL) { value = sfdo_desktop_file_entry_get_value(entry, NULL); if ((subdir->min_pixel_size = parse_positive_integer(value)) == 0) { goto err_dir_value; } } if ((entry = sfdo_desktop_file_group_get_entry(group, "MaxSize", 7)) != NULL) { value = sfdo_desktop_file_entry_get_value(entry, NULL); if ((subdir->max_pixel_size = parse_positive_integer(value)) == 0) { goto err_dir_value; } } if (subdir->min_pixel_size > subdir->max_pixel_size) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: invalid size range: minimum %d, maximum %d", group_line, group_column, subdir->min_pixel_size, subdir->max_pixel_size); goto err_dir; } break; case SFDO_ICON_SUBDIR_THRESHOLD: if ((entry = sfdo_desktop_file_group_get_entry(group, "Threshold", 9)) != NULL) { value = sfdo_desktop_file_entry_get_value(entry, NULL); if ((threshold = parse_positive_integer(value)) == 0) { goto err_dir_value; } } // SPEC: the specification incorrectly suggests using MinSize/MaxSize for distance // calculation even for Threshold subdirectories subdir->min_pixel_size = subdir->size - threshold; subdir->max_pixel_size = subdir->size + threshold; break; } subdir->min_pixel_size *= subdir->scale; subdir->max_pixel_size *= subdir->scale; } if (subdir_i != node->n_subdirs) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "Found only %zu directory groups out of %zu", subdir_i, node->n_subdirs); goto err_dir; } goto end; err_dir_value: assert(entry != NULL); const char *key = sfdo_desktop_file_entry_get_key(entry, NULL); sfdo_desktop_file_entry_get_location(entry, &entry_line, &entry_column); logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: invalid %s value \"%s\"", entry_line, entry_column, key, value); err_dir: icon_state_finish(&node->state); err_node_state: free(node->subdirs); err_node_subdirs: free(node); err_node: node = NULL; err_initial: end: sfdo_hashmap_finish(&subdir_set); return node; } static void node_destroy(struct sfdo_icon_theme_node *node) { if (node == NULL) { return; } icon_state_finish(&node->state); free(node->subdirs); free(node); } static bool load_node(struct sfdo_icon_loader *loader, struct sfdo_icon_scheduled_node *s_node, struct sfdo_icon_theme_node **out) { struct sfdo_icon_theme *theme = loader->theme; struct sfdo_logger *logger = &theme->ctx->logger; logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Loading %s", s_node->name); FILE *fp = NULL; struct sfdo_strbuild *pb = &theme->path_buf; for (size_t i = 0; i < theme->n_basedirs; i++) { const struct sfdo_string *basedir = &theme->basedirs[i]; sfdo_strbuild_reset(pb); if (!sfdo_strbuild_add(pb, basedir->data, basedir->len, s_node->name, s_node->name_len, INDEX_THEME_PATH, sizeof(INDEX_THEME_PATH) - 1, NULL)) { logger_write_oom(logger); return false; } fp = fopen(pb->data, "r"); if (fp != NULL) { logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Found an icon theme file %s", pb->data); break; } } if (fp == NULL) { logger_write(logger, loader->allow_missing ? SFDO_LOG_LEVEL_INFO : SFDO_LOG_LEVEL_ERROR, "Couldn't find an icon theme file for %s", s_node->name); *out = NULL; return loader->allow_missing; } int df_options = SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT; if (loader->relaxed) { df_options |= SFDO_DESKTOP_FILE_LOAD_ALLOW_DUPLICATE_GROUPS; } struct sfdo_desktop_file_error desktop_file_error; struct sfdo_desktop_file_document *doc = sfdo_desktop_file_document_load(fp, NULL, df_options, &desktop_file_error); fclose(fp); if (doc != NULL) { struct sfdo_icon_theme_node *node = node_load(loader, doc, s_node->name, s_node->name_len); sfdo_desktop_file_document_destroy(doc); if (node != NULL) { *out = node; return true; } } else { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "%d:%d: %s", desktop_file_error.line, desktop_file_error.column, sfdo_desktop_file_error_code_get_description(desktop_file_error.code)); } logger_write(logger, SFDO_LOG_LEVEL_ERROR, "Failed to load %s", pb->data); return false; } static bool load_theme(struct sfdo_icon_theme *theme, const char *name, int options) { struct sfdo_icon_loader loader = { .theme = theme, .relaxed = (options & SFDO_ICON_THEME_LOAD_OPTION_RELAXED) != 0, .allow_missing = (options & SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING) != 0, }; sfdo_hashmap_init(&loader.seen_nodes, sizeof(struct sfdo_hashmap_entry)); bool ok = false; if (!schedule_node(&loader, name, strlen(name), &loader.scheduled_nodes)) { goto end; } assert(loader.scheduled_nodes != NULL); // The default theme is processed last if (!schedule_node(&loader, DEFAULT_THEME_NAME, sizeof(DEFAULT_THEME_NAME) - 1, &loader.scheduled_nodes->next)) { goto end; } struct sfdo_icon_theme_node **node_ptr = &theme->nodes; for (struct sfdo_icon_scheduled_node *s_node = loader.scheduled_nodes; s_node != NULL; s_node = s_node->next) { loader.curr_node = s_node; struct sfdo_icon_theme_node *node; if (!load_node(&loader, s_node, &node)) { goto end; } if (node != NULL) { *node_ptr = node; node_ptr = &node->next; } } ok = true; end: sfdo_hashmap_finish(&loader.seen_nodes); struct sfdo_icon_scheduled_node *s_node = loader.scheduled_nodes; while (s_node != NULL) { struct sfdo_icon_scheduled_node *next = s_node->next; free(s_node); s_node = next; } return ok; } static struct sfdo_icon_theme *theme_create( struct sfdo_icon_ctx *ctx, const struct sfdo_string *basedirs, size_t n_basedirs) { struct sfdo_icon_theme *theme = calloc(1, sizeof(*theme)); if (theme == NULL) { goto err_theme; } if (!sfdo_dirs_store( basedirs, n_basedirs, &theme->basedirs, &theme->n_basedirs, &theme->basedirs_mem)) { goto err_dirs; } if (!icon_state_init(&theme->state, n_basedirs)) { goto err_icon_state; } theme->ctx = ctx; sfdo_strbuild_init(&theme->path_buf); sfdo_strpool_init(&theme->strings); return theme; err_icon_state: free(theme->basedirs_mem); free(theme->basedirs); err_dirs: free(theme); err_theme: logger_write_oom(&ctx->logger); return NULL; } SFDO_API struct sfdo_icon_theme *sfdo_icon_theme_load( struct sfdo_icon_ctx *ctx, const char *name, int options) { return sfdo_icon_theme_load_from( ctx, name, ctx->default_basedirs, ctx->default_n_basedirs, options); } SFDO_API struct sfdo_icon_theme *sfdo_icon_theme_load_from(struct sfdo_icon_ctx *ctx, const char *name, const struct sfdo_string *basedirs, size_t n_basedirs, int options) { (void)options; struct sfdo_icon_theme *theme = theme_create(ctx, basedirs, n_basedirs); if (theme == NULL) { return NULL; } if (name == NULL) { name = DEFAULT_THEME_NAME; } if (!load_theme(theme, name, options)) { goto err; } if (!sfdo_icon_theme_rescan(theme)) { goto err; } return theme; err: sfdo_icon_theme_destroy(theme); return NULL; } SFDO_API void sfdo_icon_theme_destroy(struct sfdo_icon_theme *theme) { if (theme == NULL) { return; } struct sfdo_icon_theme_node *node = theme->nodes; while (node != NULL) { struct sfdo_icon_theme_node *next = node->next; node_destroy(node); node = next; } icon_state_finish(&theme->state); sfdo_strbuild_finish(&theme->path_buf); sfdo_strpool_finish(&theme->strings); free(theme->basedirs_mem); free(theme->basedirs); free(theme); } libsfdo-0.1.3/sfdo-icon/lookup.c000066400000000000000000000161751467233572500165230ustar00rootroot00000000000000#include #include #include #include #include #include #include "common/api.h" #include "sfdo-icon/internal.h" #define EXTENSION_LEN 3 struct sfdo_icon_file { enum sfdo_icon_file_format format; size_t path_len; char path[]; }; // SPEC: the algorithm in the specification results in suboptimal matches static bool curr_is_better(const struct sfdo_icon_image *best, int best_dist, const struct sfdo_icon_image *curr, int curr_dist, int size, int scale, int pixel_size) { const struct sfdo_icon_subdir *best_dir = best->subdir; const struct sfdo_icon_subdir *curr_dir = curr->subdir; if (curr_dist == 0) { if (best_dist != 0) { // Prefer exact over non-exact return true; } // Both are exact matches } else { // curr isn't an exact match; prefer downscaling if (curr_dir->size >= size && best_dir->size < size) { return true; } else if (curr_dir->size < size && best_dir->size >= size) { return false; } // Both are upscaled or downscaled; prefer closest to exact if (curr_dist < best_dist) { return true; } else if (curr_dist > best_dist) { return false; } } // Both are the same distance away from being exact; prefer matching scale if (curr_dir->scale == scale && best_dir->scale != scale) { return true; } else if (curr_dir->scale != scale && best_dir->scale == scale) { return false; } // Both have the same scale; prefer non-scalable if (curr_dir->type != SFDO_ICON_SUBDIR_SCALABLE && best_dir->type == SFDO_ICON_SUBDIR_SCALABLE) { return true; } else if (curr_dir->type == SFDO_ICON_SUBDIR_SCALABLE && best_dir->type != SFDO_ICON_SUBDIR_SCALABLE) { return false; } // Both are scalable or non-scalable; prefer closest to requested pixel size return abs(pixel_size - curr_dir->size * curr_dir->scale) < abs(pixel_size - best_dir->size * best_dir->scale); } static const struct sfdo_icon_image *node_lookup_icon(struct sfdo_icon_theme_node *node, const struct sfdo_string *name, int size, int scale, int pixel_size, int formats) { struct sfdo_icon_state *state = &node->state; struct sfdo_icon_image_list *list = sfdo_hashmap_get(&state->map, name->data, name->len, false); if (list == NULL) { return NULL; } const struct sfdo_icon_image *best = NULL; int best_dist = INT_MAX; const struct sfdo_icon_image *img; for (size_t i = list->start_i; i != (size_t)-1; i = img->next_i) { img = &state->images[i]; if ((formats & img->formats) == 0) { continue; } const struct sfdo_icon_subdir *subdir = img->subdir; if (subdir == NULL) { if (best != NULL) { return best; } return img; } int dist = pixel_size < subdir->min_pixel_size ? subdir->min_pixel_size - pixel_size : subdir->max_pixel_size < pixel_size ? pixel_size - subdir->max_pixel_size : 0; if (best == NULL || curr_is_better(best, best_dist, img, dist, size, scale, pixel_size)) { best_dist = dist; best = img; } } return best; } static const struct sfdo_icon_image *theme_lookup_fallback_icon( struct sfdo_icon_theme *theme, const struct sfdo_string *name, int formats) { struct sfdo_icon_state *state = &theme->state; struct sfdo_icon_image_list *list = sfdo_hashmap_get(&state->map, name->data, name->len, false); if (list == NULL) { return NULL; } const struct sfdo_icon_image *img; for (size_t i = list->start_i; i != (size_t)-1; i = img->next_i) { img = &state->images[i]; if ((formats & img->formats) != 0) { return img; } } return NULL; } SFDO_API struct sfdo_icon_file *sfdo_icon_theme_lookup(struct sfdo_icon_theme *theme, const char *name, size_t name_len, int size, int scale, int options) { if (name_len == SFDO_NT) { name_len = strlen(name); } struct sfdo_string name_str = { .data = name, .len = name_len, }; return sfdo_icon_theme_lookup_best(theme, &name_str, 1, size, scale, options); } SFDO_API struct sfdo_icon_file *sfdo_icon_theme_lookup_best(struct sfdo_icon_theme *theme, const struct sfdo_string *names, size_t n_names, int size, int scale, int options) { struct sfdo_logger *logger = &theme->ctx->logger; assert(size > 0); assert(scale > 0); if ((options & SFDO_ICON_THEME_LOOKUP_OPTION_NO_RESCAN) == 0) { if (!icon_theme_maybe_rescan(theme)) { return SFDO_ICON_FILE_INVALID; } } int formats = SFDO_ICON_FORMAT_MASK_PNG | SFDO_ICON_FORMAT_MASK_XPM; if ((options & SFDO_ICON_THEME_LOOKUP_OPTION_NO_SVG) == 0) { formats |= SFDO_ICON_FORMAT_MASK_SVG; } const struct sfdo_icon_image *img = NULL; const struct sfdo_string *img_name = NULL; struct sfdo_icon_theme_node *node = NULL; int pixel_size = size * scale; for (node = theme->nodes; node != NULL; node = node->next) { for (size_t i = 0; i < n_names; i++) { const struct sfdo_string *name = &names[i]; img = node_lookup_icon(node, name, size, scale, pixel_size, formats); if (img != NULL) { img_name = name; goto found; } } } for (size_t i = 0; i < n_names; i++) { const struct sfdo_string *name = &names[i]; img = theme_lookup_fallback_icon(theme, name, formats); if (img != NULL) { img_name = name; goto found; } } return NULL; found: assert((node == NULL) == (img->subdir == NULL)); size_t path_len; if (node != NULL) { // basedir (with slash), node name, slash, subdir, slash, icon name, dot, extension path_len = img->basedir->len + node->name_len + 1 + img->subdir->path.len + 1 + img_name->len + 1 + EXTENSION_LEN; } else { // basedir (with slash), icon name, dot, extension path_len = img->basedir->len + img_name->len + 1 + EXTENSION_LEN; } size_t path_size = path_len + 1; struct sfdo_icon_file *file = calloc(1, sizeof(*file) + path_size); if (file == NULL) { logger_write_oom(logger); return SFDO_ICON_FILE_INVALID; } file->path_len = path_len; formats &= img->formats; if ((formats & SFDO_ICON_FORMAT_MASK_PNG) != 0) { file->format = SFDO_ICON_FILE_FORMAT_PNG; } else if ((formats & SFDO_ICON_FORMAT_MASK_SVG) != 0) { file->format = SFDO_ICON_FILE_FORMAT_SVG; } else if ((formats & SFDO_ICON_FORMAT_MASK_XPM) != 0) { file->format = SFDO_ICON_FILE_FORMAT_XPM; } else { abort(); // Unreachable } const char *ext = NULL; switch (file->format) { case SFDO_ICON_FILE_FORMAT_PNG: ext = "png"; break; case SFDO_ICON_FILE_FORMAT_SVG: ext = "svg"; break; case SFDO_ICON_FILE_FORMAT_XPM: ext = "xpm"; break; } assert(ext != NULL); if (node != NULL) { snprintf(file->path, path_size, "%s%s/%s/%s.%s", img->basedir->data, node->name, img->subdir->path.data, img_name->data, ext); } else { snprintf(file->path, path_size, "%s%s.%s", img->basedir->data, img_name->data, ext); } return file; } SFDO_API void sfdo_icon_file_destroy(struct sfdo_icon_file *file) { if (file == NULL || file == SFDO_ICON_FILE_INVALID) { return; } free(file); } SFDO_API const char *sfdo_icon_file_get_path(struct sfdo_icon_file *file, size_t *len) { assert(file != NULL && file != SFDO_ICON_FILE_INVALID); if (len != NULL) { *len = file->path_len; } return file->path; } SFDO_API enum sfdo_icon_file_format sfdo_icon_file_get_format(struct sfdo_icon_file *file) { assert(file != NULL && file != SFDO_ICON_FILE_INVALID); return file->format; } libsfdo-0.1.3/sfdo-icon/meson.build000066400000000000000000000001451467233572500171760ustar00rootroot00000000000000sfdo_icon_src = files( 'cache.c', 'icon.c', 'load.c', 'lookup.c', 'scan.c', 'state.c', ) libsfdo-0.1.3/sfdo-icon/scan.c000066400000000000000000000306351467233572500161330ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "common/api.h" #include "common/grow.h" #include "common/size.h" #include "common/strpool.h" #include "sfdo-icon/internal.h" #define ICON_THEME_CACHE_PATH "/icon-theme.cache" struct sfdo_icon_scanner_image { struct sfdo_hashmap_entry base; // sfdo_icon_scanner.image_set int formats; // enum sfdo_icon_format_mask }; static void check_dir_stats(struct sfdo_icon_state *state, size_t dir_i, const char *path) { struct stat statbuf; if (stat(path, &statbuf) == 0) { state->dir_exists[dir_i] = true; state->dir_mtimes[dir_i] = statbuf.st_mtime; } else { state->dir_exists[dir_i] = false; state->dir_mtimes[dir_i] = 0; } } static bool scanner_init( struct sfdo_icon_scanner *scanner, struct sfdo_logger *logger, size_t n_dirs) { if (!icon_state_init(&scanner->state, n_dirs)) { logger_write_oom(logger); return false; } scanner->logger = logger; scanner->images_len = scanner->images_cap = 0; sfdo_hashmap_init(&scanner->image_names, sizeof(struct sfdo_hashmap_entry)); return true; } static void scanner_finish(struct sfdo_icon_scanner *scanner) { sfdo_hashmap_finish(&scanner->image_names); } static void scanner_discard_and_finish(struct sfdo_icon_scanner *scanner) { icon_state_finish(&scanner->state); scanner_finish(scanner); } static void scanner_commit_and_finish( struct sfdo_icon_scanner *scanner, struct sfdo_icon_state *out) { icon_state_finish(out); *out = scanner->state; scanner_finish(scanner); } const char *icon_scanner_intern_name( struct sfdo_icon_scanner *scanner, const char *name, size_t name_len) { struct sfdo_logger *logger = scanner->logger; struct sfdo_hashmap_entry *name_entry = sfdo_hashmap_get(&scanner->image_names, name, name_len, true); if (name_entry == NULL) { logger_write_oom(logger); return NULL; } else if (name_entry->key == NULL) { name_entry->key = sfdo_strpool_add(&scanner->state.names, name, name_len); if (name_entry->key == NULL) { logger_write_oom(logger); return NULL; } } return name_entry->key; } bool icon_scanner_add_image(struct sfdo_icon_scanner *scanner, const struct sfdo_string *basedir, const struct sfdo_icon_subdir *subdir, const char *name, size_t name_len, int formats) { struct sfdo_logger *logger = scanner->logger; struct sfdo_icon_state *state = &scanner->state; if (!sfdo_grow(&state->images, &scanner->images_cap, scanner->images_len, sizeof(*state->images))) { logger_write_oom(logger); return false; } struct sfdo_icon_image_list *image_list = sfdo_hashmap_get(&state->map, name, name_len, true); if (image_list == NULL) { logger_write_oom(logger); return false; } else if (image_list->base.key == NULL) { image_list->base.key = name; image_list->start_i = scanner->images_len; } else { state->images[image_list->end_i].next_i = scanner->images_len; } image_list->end_i = scanner->images_len; struct sfdo_icon_image *image = &state->images[scanner->images_len++]; image->basedir = basedir; image->subdir = subdir; image->formats = formats; image->next_i = (size_t)-1; return true; } static bool scan_dir(struct sfdo_icon_scanner *scanner, const char *path, const struct sfdo_string *basedir, const struct sfdo_icon_subdir *subdir) { struct sfdo_logger *logger = scanner->logger; DIR *dirp = opendir(path); if (dirp == NULL) { logger_write(logger, SFDO_LOG_LEVEL_ERROR, "Failed to open directory %s: %s", path, strerror(errno)); return false; } struct sfdo_hashmap image_set; sfdo_hashmap_init(&image_set, sizeof(struct sfdo_icon_scanner_image)); bool ok = false; struct dirent *dirent; while ((dirent = readdir(dirp)) != NULL) { char *name = dirent->d_name; size_t name_len = strlen(dirent->d_name); if (name_len < 5) { continue; } size_t icon_name_len = name_len - 4; if (name[icon_name_len] != '.') { continue; } char *ext = &name[icon_name_len + 1]; int format = 0; if (strcmp(ext, "png") == 0) { format = SFDO_ICON_FORMAT_MASK_PNG; } else if (strcmp(ext, "svg") == 0) { format = SFDO_ICON_FORMAT_MASK_SVG; } else if (strcmp(ext, "xpm") == 0) { format = SFDO_ICON_FORMAT_MASK_XPM; } else { continue; } struct sfdo_icon_scanner_image *entry = sfdo_hashmap_get(&image_set, name, icon_name_len, true); if (entry == NULL) { logger_write_oom(logger); return false; } else if (entry->base.key == NULL) { entry->base.key = icon_scanner_intern_name(scanner, name, icon_name_len); if (entry->base.key == NULL) { goto end; } entry->formats = 0; } entry->formats |= format; } for (size_t i = 0; i < image_set.cap; i++) { struct sfdo_icon_scanner_image *entry = &((struct sfdo_icon_scanner_image *)image_set.mem)[i]; if (entry->base.key != NULL) { if (!icon_scanner_add_image(scanner, basedir, subdir, entry->base.key, entry->base.key_len, entry->formats)) { goto end; } } } ok = true; if (image_set.len > 0) { logger_write( logger, SFDO_LOG_LEVEL_DEBUG, "Added %zu image(s) from %s", image_set.len, path); } end: sfdo_hashmap_finish(&image_set); closedir(dirp); return ok; } static bool rescan_node(struct sfdo_icon_theme_node *node, struct sfdo_icon_theme *theme) { struct sfdo_logger *logger = &theme->ctx->logger; logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Scanning %s", node->name); struct sfdo_icon_cache **cache_files = calloc(theme->n_basedirs, sizeof(struct sfdo_icon_cache *)); if (cache_files == NULL) { logger_write_oom(logger); return false; } struct sfdo_icon_scanner scanner; if (!scanner_init(&scanner, logger, theme->n_basedirs * (node->n_subdirs + 1))) { free(cache_files); return false; } bool ok = false; struct sfdo_strbuild *pb = &theme->path_buf; size_t node_dir_i = 0; for (size_t basedir_i = 0; basedir_i < theme->n_basedirs; basedir_i++) { struct sfdo_string *basedir = &theme->basedirs[basedir_i]; sfdo_strbuild_reset(pb); if (!sfdo_strbuild_add(pb, basedir->data, basedir->len, node->name, node->name_len, NULL)) { logger_write_oom(logger); goto end; } check_dir_stats(&scanner.state, node_dir_i++, pb->data); if (!scanner.state.dir_exists[basedir_i]) { continue; } if (!sfdo_strbuild_add( pb, ICON_THEME_CACHE_PATH, sizeof(ICON_THEME_CACHE_PATH) - 1, NULL)) { goto end; } cache_files[basedir_i] = icon_cache_create(pb->data, scanner.state.dir_mtimes[basedir_i], logger); } for (size_t subdir_i = 0; subdir_i < node->n_subdirs; subdir_i++) { struct sfdo_icon_subdir *subdir = &node->subdirs[subdir_i]; for (size_t basedir_i = 0; basedir_i < theme->n_basedirs; basedir_i++) { size_t dir_i = node_dir_i++; if (!scanner.state.dir_exists[basedir_i]) { // If the /// doesn't exist, its subdirs don't exist either scanner.state.dir_exists[dir_i] = false; scanner.state.dir_mtimes[dir_i] = 0; continue; } struct sfdo_string *basedir = &theme->basedirs[basedir_i]; sfdo_strbuild_reset(pb); if (!sfdo_strbuild_add(pb, basedir->data, basedir->len, node->name, node->name_len, "/", SFDO_SIZE1, subdir->path.data, subdir->path.len, NULL)) { logger_write_oom(logger); goto end; } check_dir_stats(&scanner.state, dir_i, pb->data); if (!scanner.state.dir_exists[dir_i]) { continue; } struct sfdo_icon_cache *cache_file = cache_files[basedir_i]; if (cache_file != NULL) { if (!icon_cache_scan_dir(cache_file, &scanner, basedir, subdir)) { goto end; } continue; } if (!scan_dir(&scanner, pb->data, basedir, subdir)) { goto end; } } } ok = true; end: for (size_t basedir_i = 0; basedir_i < theme->n_basedirs; basedir_i++) { struct sfdo_icon_cache *cache_file = cache_files[basedir_i]; icon_cache_destroy(cache_file); } free(cache_files); if (ok) { logger_write(logger, SFDO_LOG_LEVEL_INFO, "Found %zu image(s) in %s", scanner.images_len, node->name); scanner_commit_and_finish(&scanner, &node->state); return true; } else { scanner_discard_and_finish(&scanner); return false; } } static bool rescan_fallback(struct sfdo_icon_theme *theme) { struct sfdo_logger *logger = &theme->ctx->logger; logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "Scanning fallback icon directories"); struct sfdo_icon_scanner scanner; if (!scanner_init(&scanner, &theme->ctx->logger, theme->n_basedirs)) { return false; } bool ok = false; for (size_t basedir_i = 0; basedir_i < theme->n_basedirs; basedir_i++) { struct sfdo_string *basedir = &theme->basedirs[basedir_i]; check_dir_stats(&scanner.state, basedir_i, basedir->data); if (!scanner.state.dir_exists[basedir_i]) { continue; } if (!scan_dir(&scanner, basedir->data, basedir, NULL)) { goto end; } } ok = true; end: if (ok) { logger_write( logger, SFDO_LOG_LEVEL_INFO, "Found %zu fallback image(s)", scanner.images_len); scanner_commit_and_finish(&scanner, &theme->state); return true; } else { scanner_discard_and_finish(&scanner); return false; } } static bool rescan_theme(struct sfdo_icon_theme *theme) { for (struct sfdo_icon_theme_node *node = theme->nodes; node != NULL; node = node->next) { if (!rescan_node(node, theme)) { return false; } } if (!rescan_fallback(theme)) { return false; } return true; } static bool dir_is_stale(const struct sfdo_icon_state *state, struct sfdo_icon_theme *theme, const char *path, size_t dir_i) { struct sfdo_logger *logger = &theme->ctx->logger; bool exists = false; time_t mtime = 0; struct stat statbuf; if (stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) { exists = true; mtime = statbuf.st_mtime; } bool c_exists = state->dir_exists[dir_i]; time_t c_mtime = state->dir_mtimes[dir_i]; if (exists != c_exists || (exists && mtime != c_mtime)) { logger_write(logger, SFDO_LOG_LEVEL_DEBUG, "%s is stale: old=%s,%lld new=%s,%lld", path, c_exists ? "yes" : "no", (long long)c_mtime, exists ? "yes" : "no", (long long)mtime); return true; } return false; } static bool node_check_stale( struct sfdo_icon_theme_node *node, struct sfdo_icon_theme *theme, bool *out) { struct sfdo_logger *logger = &theme->ctx->logger; struct sfdo_strbuild *pb = &theme->path_buf; size_t node_dir_i = 0; for (size_t basedir_i = 0; basedir_i < theme->n_basedirs; basedir_i++) { size_t dir_i = node_dir_i++; struct sfdo_string *basedir = &theme->basedirs[basedir_i]; sfdo_strbuild_reset(pb); if (!sfdo_strbuild_add(pb, basedir->data, basedir->len, node->name, node->name_len, NULL)) { logger_write_oom(logger); return false; } if (dir_is_stale(&node->state, theme, pb->data, dir_i)) { *out = true; return true; } } for (size_t subdir_i = 0; subdir_i < node->n_subdirs; subdir_i++) { struct sfdo_icon_subdir *subdir = &node->subdirs[subdir_i]; for (size_t basedir_i = 0; basedir_i < theme->n_basedirs; basedir_i++) { size_t dir_i = node_dir_i++; if (!node->state.dir_exists[basedir_i]) { // If the /// doesn't exist, its subdirs don't exist either continue; } struct sfdo_string *basedir = &theme->basedirs[basedir_i]; sfdo_strbuild_reset(pb); if (!sfdo_strbuild_add(pb, basedir->data, basedir->len, node->name, node->name_len, "/", SFDO_SIZE1, subdir->path.data, subdir->path.len, NULL)) { logger_write_oom(logger); return false; } if (dir_is_stale(&node->state, theme, pb->data, dir_i)) { *out = true; return true; } } } *out = false; return true; } // Returns true on success, false otherwise bool icon_theme_maybe_rescan(struct sfdo_icon_theme *theme) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); const struct timespec *then = &theme->scan_time; // At least 5 seconds between automatic rescans if (now.tv_sec - then->tv_sec - (now.tv_nsec < then->tv_sec) < 5) { return true; } theme->scan_time = now; for (size_t basedir_i = 0; basedir_i < theme->n_basedirs; basedir_i++) { struct sfdo_string *basedir = &theme->basedirs[basedir_i]; if (dir_is_stale(&theme->state, theme, basedir->data, basedir_i)) { return rescan_theme(theme); } } for (struct sfdo_icon_theme_node *node = theme->nodes; node != NULL; node = node->next) { bool stale; if (!node_check_stale(node, theme, &stale)) { return false; } else if (stale && !rescan_node(node, theme)) { return false; } } return true; } SFDO_API bool sfdo_icon_theme_rescan(struct sfdo_icon_theme *theme) { clock_gettime(CLOCK_MONOTONIC, &theme->scan_time); return rescan_theme(theme); } libsfdo-0.1.3/sfdo-icon/state.c000066400000000000000000000014011467233572500163140ustar00rootroot00000000000000#include #include "common/strpool.h" #include "sfdo-icon/internal.h" bool icon_state_init(struct sfdo_icon_state *state, size_t n_dirs) { state->dir_mtimes = calloc(n_dirs, sizeof(*state->dir_mtimes)); state->dir_exists = calloc(n_dirs, sizeof(*state->dir_exists)); if (state->dir_mtimes == NULL || state->dir_exists == NULL) { free(state->dir_mtimes); free(state->dir_exists); return false; } sfdo_hashmap_init(&state->map, sizeof(struct sfdo_icon_image_list)); sfdo_strpool_init(&state->names); state->images = NULL; return true; } void icon_state_finish(struct sfdo_icon_state *state) { sfdo_hashmap_finish(&state->map); sfdo_strpool_finish(&state->names); free(state->images); free(state->dir_mtimes); free(state->dir_exists); } libsfdo-0.1.3/tests/000077500000000000000000000000001467233572500143155ustar00rootroot00000000000000libsfdo-0.1.3/tests/basedir.c000066400000000000000000000101401467233572500160660ustar00rootroot00000000000000#include #include #include #include #include #include #define TEST_HOME "/home/sfdo-test-user" static void check_string( const char *name, const char *got, size_t got_len, const char *exp, size_t exp_len) { if (exp_len == SFDO_NT) { exp_len = strlen(exp); } if (got_len != exp_len || memcmp(got, exp, exp_len) != 0) { fprintf(stderr, "\"%s\" string mismatch\n" "Expected (length: %zu):\t%s\n" "Got (length: %zu):\t%s\n", name, got_len, got, exp_len, exp); exit(1); } } static void check_dirs( const char *name, const struct sfdo_string *got, size_t got_len, size_t exp_len, ...) { va_list args; va_start(args, exp_len); struct sfdo_string *exp = calloc(exp_len, sizeof(*exp)); for (size_t i = 0; i < exp_len; i++) { exp[i].data = va_arg(args, const char *); exp[i].len = va_arg(args, size_t); if (exp[i].len == SFDO_NT) { exp[i].len = strlen(exp[i].data); } } va_end(args); if (got_len != exp_len) { goto mismatch; } for (size_t i = 0; i < exp_len; i++) { if (got[i].len != exp[i].len || memcmp(got[i].data, exp[i].data, got[i].len) != 0) { goto mismatch; } } free(exp); return; mismatch: fprintf(stderr, "\"%s\" directory list mismatch\n", name); fprintf(stderr, "Expected (length: %zu):\n", exp_len); for (size_t i = 0; i < exp_len; i++) { fprintf(stderr, " %zu)\t(length: %zu)\t%s\n", i, exp[i].len, exp[i].data); } fprintf(stderr, "Got (length: %zu):\n", got_len); for (size_t i = 0; i < got_len; i++) { fprintf(stderr, " %zu)\t(length: %zu)\t%s\n", i, got[i].len, got[i].data); } exit(1); } static void test_home_dirs(const char *home) { setenv("HOME", home, 1); struct sfdo_basedir_ctx *ctx = sfdo_basedir_ctx_create(); const char *data; size_t len; data = sfdo_basedir_get_data_home(ctx, &len); check_string("data home", data, len, TEST_HOME "/.local/share/", SFDO_NT); data = sfdo_basedir_get_config_home(ctx, &len); check_string("config home", data, len, TEST_HOME "/.config/", SFDO_NT); data = sfdo_basedir_get_state_home(ctx, &len); check_string("state home", data, len, TEST_HOME "/.local/state/", SFDO_NT); data = sfdo_basedir_get_cache_home(ctx, &len); check_string("cache home", data, len, TEST_HOME "/.cache/", SFDO_NT); sfdo_basedir_ctx_destroy(ctx); } static void test_system_dirs(void) { struct sfdo_basedir_ctx *ctx = sfdo_basedir_ctx_create(); const struct sfdo_string *dirs; size_t n_dirs; dirs = sfdo_basedir_get_data_system_dirs(ctx, &n_dirs); check_dirs("data dirs", dirs, n_dirs, 2, "/usr/local/share/", SFDO_NT, "/usr/share/", SFDO_NT); dirs = sfdo_basedir_get_config_system_dirs(ctx, &n_dirs); check_dirs("config dirs", dirs, n_dirs, 1, "/etc/xdg/", SFDO_NT); sfdo_basedir_ctx_destroy(ctx); } static void reset(void) { unsetenv("HOME"); unsetenv("XDG_DATA_HOME"); unsetenv("XDG_DATA_DIRS"); unsetenv("XDG_CONFIG_HOME"); unsetenv("XDG_CONFIG_DIRS"); unsetenv("XDG_STATE_HOME"); unsetenv("XDG_CACHE_HOME"); unsetenv("XDG_RUNTIME_DIR"); } int main(void) { reset(); test_home_dirs(TEST_HOME); test_home_dirs(TEST_HOME "/"); reset(); setenv("XDG_DATA_HOME", TEST_HOME "/.local/share", 1); setenv("XDG_CONFIG_HOME", TEST_HOME "/.config", 1); setenv("XDG_STATE_HOME", TEST_HOME "/.local/state", 1); setenv("XDG_CACHE_HOME", TEST_HOME "/.cache", 1); test_home_dirs("/UNUSED"); reset(); setenv("XDG_DATA_HOME", TEST_HOME "/.local/share/", 1); setenv("XDG_CONFIG_HOME", TEST_HOME "/.config/", 1); setenv("XDG_STATE_HOME", TEST_HOME "/.local/state/", 1); setenv("XDG_CACHE_HOME", TEST_HOME "/.cache/", 1); test_home_dirs("/UNUSED"); reset(); setenv("XDG_DATA_HOME", "../", 1); setenv("XDG_CONFIG_HOME", "../", 1); setenv("XDG_STATE_HOME", "../", 1); setenv("XDG_CACHE_HOME", "../", 1); test_home_dirs(TEST_HOME); reset(); test_system_dirs(); reset(); setenv("XDG_DATA_DIRS", "/usr/local/share:/usr/share", 1); setenv("XDG_CONFIG_DIRS", "/etc/xdg", 1); test_system_dirs(); reset(); setenv("XDG_DATA_DIRS", "/usr/local/share/:../:::/usr/share/", 1); setenv("XDG_CONFIG_DIRS", "/etc/xdg/:", 1); test_system_dirs(); return 0; } libsfdo-0.1.3/tests/desktop-file.c000066400000000000000000000305471467233572500170600ustar00rootroot00000000000000#include #include #include #include #include static struct sfdo_desktop_file_document *load_doc(const char *str, size_t len, const char *locale, int options, struct sfdo_desktop_file_error *error) { if (len == SFDO_NT) { len = strlen(str); } FILE *fp = fmemopen((char *)str, len, "r"); struct sfdo_desktop_file_document *doc = sfdo_desktop_file_document_load(fp, locale, options, error); fclose(fp); return doc; } static void load_error(const char *name, const char *str, size_t len, const char *locale, int options, enum sfdo_desktop_file_error_code exp_code, int exp_line, int exp_column) { struct sfdo_desktop_file_error got; struct sfdo_desktop_file_document *doc = load_doc(str, len, locale, options, &got); if (doc != NULL) { fprintf(stderr, "\"%s\" unexpected success\n" "Expected: %d:%d: %s\n", name, exp_line, exp_column, sfdo_desktop_file_error_code_get_description(exp_code)); exit(1); } if (exp_line != got.line || exp_column != got.column || exp_code != got.code) { fprintf(stderr, "\"%s\" error mismatch\n" "Expected: %d:%d:\t%s\n" "Got: %d:%d:\t%s\n", name, exp_line, exp_column, sfdo_desktop_file_error_code_get_description(exp_code), got.line, got.column, sfdo_desktop_file_error_code_get_description(got.code)); exit(1); } } static struct sfdo_desktop_file_document *load_success( const char *name, const char *str, size_t len, const char *locale, int options) { struct sfdo_desktop_file_error error; struct sfdo_desktop_file_document *doc = load_doc(str, len, locale, options, &error); if (doc == NULL) { fprintf(stderr, "\"%s\" unexpected error: %d:%d: %s\n", name, error.line, error.column, sfdo_desktop_file_error_code_get_description(error.code)); exit(1); } return doc; } static void check_value(const char *name, const char *str, size_t len, const char *locale, int options, bool localized, const char *exp) { struct sfdo_desktop_file_document *doc = load_success(name, str, len, locale, options); struct sfdo_desktop_file_group *group = sfdo_desktop_file_document_get_groups(doc); if (group == NULL) { fprintf(stderr, "\"%s\" has no groups?", name); exit(1); } struct sfdo_desktop_file_entry *entry = sfdo_desktop_file_group_get_entry(group, "key", 3); if (group == NULL) { fprintf(stderr, "\"%s\" has no \"key\" entry?", name); exit(1); } size_t exp_len = strlen(exp); size_t got_len; const char *got = localized ? sfdo_desktop_file_entry_get_localized_value(entry, &got_len) : sfdo_desktop_file_entry_get_value(entry, &got_len); if (got_len != exp_len || memcmp(got, exp, exp_len) != 0) { fprintf(stderr, "\"%s\" value mismatch\n" "Expected (length: %zu):\t%s\n" "Got (length: %zu):\t%s\n", name, got_len, got, exp_len, exp); exit(1); } sfdo_desktop_file_document_destroy(doc); } static void check_value_list(const char *name, const char *str, size_t len, const char *locale, int options, bool localized, size_t exp_len, ...) { struct sfdo_desktop_file_document *doc = load_success(name, str, len, locale, options); struct sfdo_desktop_file_group *group = sfdo_desktop_file_document_get_groups(doc); if (group == NULL) { fprintf(stderr, "\"%s\" has no groups?", name); exit(1); } struct sfdo_desktop_file_entry *entry = sfdo_desktop_file_group_get_entry(group, "key", 3); if (group == NULL) { fprintf(stderr, "\"%s\" has no \"key\" entry?", name); exit(1); } va_list args; va_start(args, exp_len); struct sfdo_string *exp = calloc(exp_len, sizeof(*exp)); for (size_t i = 0; i < exp_len; i++) { exp[i].data = va_arg(args, const char *); exp[i].len = strlen(exp[i].data); } va_end(args); size_t got_len; const struct sfdo_string *got = localized ? sfdo_desktop_file_entry_get_localized_value_list(entry, &got_len) : sfdo_desktop_file_entry_get_value_list(entry, &got_len); if (got_len != exp_len) { goto mismatch; } for (size_t i = 0; i < exp_len; i++) { if (got[i].len != exp[i].len || memcmp(got[i].data, exp[i].data, got[i].len) != 0) { goto mismatch; } } free(exp); sfdo_desktop_file_document_destroy(doc); return; mismatch: fprintf(stderr, "\"%s\" value list mismatch\n", name); fprintf(stderr, "Expected (length: %zu):\n", exp_len); for (size_t i = 0; i < exp_len; i++) { fprintf(stderr, " %zu)\t(length: %zu)\t%s\n", i, exp[i].len, exp[i].data); } fprintf(stderr, "Got (length: %zu):\n", got_len); for (size_t i = 0; i < got_len; i++) { fprintf(stderr, " %zu)\t(length: %zu)\t%s\n", i, got[i].len, got[i].data); } exit(1); } int main(void) { // Basic parsing load_error("NUL", "[Group\0Name]\n", 15, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_NT, 1, 7); load_error("invalid UTF-8", "[Group\xffName]\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_UTF8, 1, 7); load_error("bad group char", "[Group[]\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_SYNTAX, 1, 7); load_error("extra after group", "[Group] extra\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_SYNTAX, 1, 9); load_error("entry without a group", "key=value\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_SYNTAX, 1, 1); load_error("bad key char", "[Group]\n" "key_name=value\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_SYNTAX, 2, 4); load_error("entry with no value", "[Group]\n" "key\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_SYNTAX, 2, 4); // The error is on \n load_error("truncated localized entry", "[Group]\n" "key[locale\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_SYNTAX, 2, 11); load_error("localized entry with no value", "[Group]\n" "key[locale]\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_SYNTAX, 2, 12); load_error("bad escaped character", "[Group]\n" "key=value\\a\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_SYNTAX, 2, 11); load_error("truncated escaped character", "[Group]\n" "key=value\\\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_SYNTAX, 2, 11); load_error("duplicate groups", "[Group]\n" "[Group]\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_DUPLICATE_GROUP, 2, 1); load_error("duplicate keys", "[Group]\n" "key=value\n" "key=value\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_DUPLICATE_KEY, 3, 1); load_error("no default value", "[Group]\n" "key[en_US]=value\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, SFDO_DESKTOP_FILE_ERROR_NO_DEFAULT_VALUE, 2, 1); check_value("simple value", "[Group]\n" "key=value\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, "value"); // NB: trailing spaces are not ignored, same as GLib check_value("simple value with spaces", " [Group] \n" " key = value\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, "value"); check_value("simple value a comment", "[Group]\n" "# comment\n" "key=value\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, "value"); check_value("simple value with localized getter", "[Group]\n" "key=value\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "value"); check_value("escaped", "[Group]\n" "key=\\s\\n\\t\\r\\\\\n", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, " \n\t\r\\"); // Localized values static const char localized_str[] = "[Group]\n" "key=default\n" "\n" "key[lang]=lang\n" "key[lang@MODIFIER]=lang@MODIFIER\n" "key[lang_COUNTRY]=lang_COUNTRY\n" "key[lang_COUNTRY@MODIFIER]=lang_COUNTRY@MODIFIER\n" "\n" "key[lang2@MODIFIER]=lang2@MODIFIER\n" "key[lang2_COUNTRY]=lang2_COUNTRY\n"; check_value("localized, no locale", localized_str, sizeof(localized_str) - 1, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "default"); check_value("localized, empty locale", localized_str, sizeof(localized_str) - 1, "", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "default"); check_value("localized, lang", localized_str, sizeof(localized_str) - 1, "lang", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang"); check_value("localized, lang+modifier", localized_str, sizeof(localized_str) - 1, "lang@MODIFIER", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang@MODIFIER"); check_value("localized, lang+country", localized_str, sizeof(localized_str) - 1, "lang_COUNTRY", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang_COUNTRY"); check_value("localized, lang+country+modifier", localized_str, sizeof(localized_str) - 1, "lang_COUNTRY@MODIFIER", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang_COUNTRY@MODIFIER"); check_value("localized, lang+country+modifier with encoding", localized_str, sizeof(localized_str) - 1, "lang_COUNTRY.ENCODING@MODIFIER", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang_COUNTRY@MODIFIER"); check_value("localized, (bad)lang", localized_str, sizeof(localized_str) - 1, "BAD", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "default"); check_value("localized, lang+(bad)modifier", localized_str, sizeof(localized_str) - 1, "lang@BAD", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang"); check_value("localized, lang+(bad)country", localized_str, sizeof(localized_str) - 1, "lang_BAD", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang"); check_value("localized, lang+country+(bad)modifier", localized_str, sizeof(localized_str) - 1, "lang_COUNTRY@BAD", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang_COUNTRY"); check_value("localized, lang+(bad)country+modifier", localized_str, sizeof(localized_str) - 1, "lang_BAD@MODIFIER", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang@MODIFIER"); check_value("localized, lang+(bad)country+(bad)modifier", localized_str, sizeof(localized_str) - 1, "lang_BAD@BAD", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang"); check_value("localized, lang2", localized_str, sizeof(localized_str) - 1, "default", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "default"); check_value("localized, lang2+country", localized_str, sizeof(localized_str) - 1, "lang2_COUNTRY", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang2_COUNTRY"); check_value("localized, lang2+modifier", localized_str, sizeof(localized_str) - 1, "lang2@MODIFIER", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang2@MODIFIER"); check_value("localized, lang2+country+modifier", localized_str, sizeof(localized_str) - 1, "lang2_COUNTRY@MODIFIER", SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, true, "lang2_COUNTRY"); // Lists check_value_list("list with one item", "[Group]\n" "key = single", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, 1, "single"); check_value_list("list with zero items", "[Group]\n" "key = ", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, 0); check_value_list("list with two items", "[Group]\n" "key = alpha;beta", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, 2, "alpha", "beta"); check_value_list("list with two items + trailing separator", "[Group]\n" "key = alpha;beta;", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, 2, "alpha", "beta"); check_value_list("list with two items, the first is empty", "[Group]\n" "key = ;beta", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, 2, "", "beta"); check_value_list("list with two items, the second is empty", "[Group]\n" "key = alpha;;", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, 2, "alpha", ""); check_value_list("list with one empty item", "[Group]\n" "key = ;", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, 1, ""); check_value_list("list with three items, the second is empty", "[Group]\n" "key = foo;;bar", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, 3, "foo", "", "bar"); check_value_list("list with items with escaped separators", "[Group]\n" "key = Steins\\;Gate;Chaos\\;Head;", SFDO_NT, NULL, SFDO_DESKTOP_FILE_LOAD_OPTIONS_DEFAULT, false, 2, "Steins;Gate", "Chaos;Head"); return 0; } libsfdo-0.1.3/tests/desktop.c000066400000000000000000000273251467233572500161430ustar00rootroot00000000000000#include #include #include #include static void check_value(const char *name, const char *got, size_t got_len, const char *exp) { size_t exp_len = strlen(exp); if (got_len != exp_len || memcmp(got, exp, exp_len) != 0) { fprintf(stderr, "\"%s\" value mismatch\n" "Expected (length: %zu):\t%s\n" "Got (length: %zu):\t%s\n", name, got_len, got, exp_len, exp); exit(1); } } static void check_value_list( const char *name, const struct sfdo_string *got, size_t got_len, size_t exp_len, ...) { va_list args; va_start(args, exp_len); struct sfdo_string *exp = calloc(exp_len, sizeof(*exp)); for (size_t i = 0; i < exp_len; i++) { exp[i].data = va_arg(args, const char *); exp[i].len = strlen(exp[i].data); } va_end(args); if (got_len != exp_len) { goto mismatch; } for (size_t i = 0; i < exp_len; i++) { if (got[i].len != exp[i].len || memcmp(got[i].data, exp[i].data, got[i].len) != 0) { goto mismatch; } } free(exp); return; mismatch: fprintf(stderr, "\"%s\" value list mismatch\n", name); fprintf(stderr, "Expected (length: %zu):\n", exp_len); for (size_t i = 0; i < exp_len; i++) { fprintf(stderr, " %zu)\t(length: %zu)\t%s\n", i, exp[i].len, exp[i].data); } fprintf(stderr, "Got (length: %zu):\n", got_len); for (size_t i = 0; i < got_len; i++) { fprintf(stderr, " %zu)\t(length: %zu)\t%s\n", i, got[i].len, got[i].data); } exit(1); } static void check_exec(const char *name, struct sfdo_desktop_exec *exec, size_t exp_len, ...) { va_list args; va_start(args, exp_len); struct sfdo_string *exp = calloc(exp_len, sizeof(*exp)); for (size_t i = 0; i < exp_len; i++) { exp[i].data = va_arg(args, const char *); exp[i].len = strlen(exp[i].data); } const char *targets[64]; size_t n_targets = 0; while ((targets[n_targets] = va_arg(args, const char *)) != NULL) { ++n_targets; } va_end(args); struct sfdo_desktop_exec_command *command = sfdo_desktop_exec_format_list(exec, targets, n_targets); size_t got_len; const char **got_data = sfdo_desktop_exec_command_get_args(command, &got_len); struct sfdo_string *got = calloc(exp_len, sizeof(*got)); for (size_t i = 0; i < got_len; i++) { got[i].data = got_data[i]; got[i].len = strlen(got_data[i]); } if (got_len != exp_len) { goto mismatch; } for (size_t i = 0; i < exp_len; i++) { if (got[i].len != exp[i].len || memcmp(got[i].data, exp[i].data, got[i].len) != 0) { goto mismatch; } } free(exp); free(got); sfdo_desktop_exec_command_destroy(command); return; mismatch: fprintf(stderr, "\"%s\" exec command args mismatch\n", name); fprintf(stderr, "Expected (length: %zu):\n", exp_len); for (size_t i = 0; i < exp_len; i++) { fprintf(stderr, " %zu)\t(length: %zu)\t%s\n", i, exp[i].len, exp[i].data); } fprintf(stderr, "Got (length: %zu):\n", got_len); for (size_t i = 0; i < got_len; i++) { fprintf(stderr, " %zu)\t(length: %zu)\t%s\n", i, got[i].len, got[i].data); } exit(1); } static struct sfdo_desktop_entry *get_entry(struct sfdo_desktop_db *db, const char *name) { struct sfdo_desktop_entry *entry = sfdo_desktop_db_get_entry_by_id(db, name, SFDO_NT); if (entry == NULL) { fprintf(stderr, "\"%s\" entry not found\n", name); exit(1); } return entry; } static void ensure_no_entry(struct sfdo_desktop_db *db, const char *name) { struct sfdo_desktop_entry *entry = sfdo_desktop_db_get_entry_by_id(db, name, SFDO_NT); if (entry != NULL) { fprintf(stderr, "\"%s\" entry unexpectedly found\n", name); exit(1); } } static void log_handler(enum sfdo_log_level level, const char *fmt, va_list args, void *data) { (void)level; (void)data; static const char *levels[] = { [SFDO_LOG_LEVEL_SILENT] = "", [SFDO_LOG_LEVEL_ERROR] = "error", [SFDO_LOG_LEVEL_INFO] = "info", [SFDO_LOG_LEVEL_DEBUG] = "debug", }; fprintf(stdout, "[%s] ", levels[level]); vfprintf(stdout, fmt, args); fprintf(stdout, "\n"); } int main(void) { struct sfdo_desktop_ctx *ctx = sfdo_desktop_ctx_create(NULL); sfdo_desktop_ctx_set_log_handler(ctx, SFDO_LOG_LEVEL_DEBUG, log_handler, NULL); static const struct sfdo_string basedirs[] = { { .data = "desktop/basedir1", .len = 16, }, { .data = "desktop/basedir2", .len = 16, }, }; struct sfdo_desktop_db *db = sfdo_desktop_db_load_from(ctx, NULL, basedirs, sizeof(basedirs) / sizeof(*basedirs)); struct sfdo_desktop_entry *entry; const char *value; size_t value_len; const struct sfdo_string *items; size_t n_items; struct sfdo_desktop_entry_action *action; struct sfdo_desktop_entry_action **actions; size_t n_actions; // Basic loading entry = get_entry(db, "simple"); if (sfdo_desktop_entry_get_type(entry) != SFDO_DESKTOP_ENTRY_APPLICATION) { fprintf(stderr, "simple isn't Application?\n"); exit(1); } value = sfdo_desktop_entry_get_name(entry, &value_len); check_value("simple name", value, value_len, "simple"); if (sfdo_desktop_entry_get_startup_notify(entry) != SFDO_DESKTOP_ENTRY_STARTUP_NOTIFY_UNKNOWN) { fprintf(stderr, "simple StartupNotify isn't unknown\n"); exit(1); } if (!sfdo_desktop_entry_show_in(entry, NULL, SFDO_NT)) { fprintf(stderr, "simple is not shown by default\n"); exit(1); } // Unusual IDs get_entry(db, "com.example.complex"); get_entry(db, "com-example-nested"); get_entry(db, "magic"); // Normal properties entry = get_entry(db, "com.example.app-all"); value = sfdo_desktop_entry_get_name(entry, &value_len); check_value("app-all name", value, value_len, "app-all"); value = sfdo_desktop_entry_get_generic_name(entry, &value_len); check_value("app-all generic name", value, value_len, "generic"); if (!sfdo_desktop_entry_get_no_display(entry)) { fprintf(stderr, "app-all NoDisplay is false\n"); exit(1); } value = sfdo_desktop_entry_get_comment(entry, &value_len); check_value("app-all comment", value, value_len, "comment"); value = sfdo_desktop_entry_get_icon(entry, &value_len); check_value("app-all icon", value, value_len, "icon"); value = sfdo_desktop_entry_get_try_exec(entry, &value_len); check_value("app-all try exec", value, value_len, "/bin/false"); value = sfdo_desktop_entry_get_path(entry, &value_len); check_value("app-all path", value, value_len, "/etc"); if (!sfdo_desktop_entry_get_terminal(entry)) { fprintf(stderr, "app-all Terminal is false\n"); exit(1); } items = sfdo_desktop_entry_get_mimetypes(entry, &n_items); check_value_list("app-all mimetypes", items, n_items, 2, "foo", "bar"); items = sfdo_desktop_entry_get_categories(entry, &n_items); check_value_list("app-all categories", items, n_items, 3, "Settings", "System", "Utility"); items = sfdo_desktop_entry_get_keywords(entry, &n_items); check_value_list("app-all keywords", items, n_items, 0); if (!sfdo_desktop_entry_get_prefers_non_default_gpu(entry)) { fprintf(stderr, "app-all PrefersNonDefaultGPU is false\n"); exit(1); } if (sfdo_desktop_entry_get_startup_notify(entry) != SFDO_DESKTOP_ENTRY_STARTUP_NOTIFY_TRUE) { fprintf(stderr, "simple StartupNotify isn't true\n"); exit(1); } if (!sfdo_desktop_entry_get_single_main_window(entry)) { fprintf(stderr, "app-all SingleMainWindow is false\n"); exit(1); } entry = get_entry(db, "dir"); if (sfdo_desktop_entry_get_type(entry) != SFDO_DESKTOP_ENTRY_DIRECTORY) { fprintf(stderr, "dir isn't Directory?\n"); exit(1); } entry = get_entry(db, "link"); if (sfdo_desktop_entry_get_type(entry) != SFDO_DESKTOP_ENTRY_LINK) { fprintf(stderr, "link isn't Link?\n"); exit(1); } value = sfdo_desktop_entry_get_url(entry, &value_len); check_value("link URL", value, value_len, "https://example.com"); // ShowIn entry = get_entry(db, "only-show-in"); if (sfdo_desktop_entry_show_in(entry, NULL, SFDO_NT)) { fprintf(stderr, "only-show-in is shown by default\n"); exit(1); } if (!sfdo_desktop_entry_show_in(entry, "allowed", SFDO_NT)) { fprintf(stderr, "only-show-in is not shown by \"allowed\"\n"); exit(1); } entry = get_entry(db, "both-show-in"); if (sfdo_desktop_entry_show_in(entry, NULL, SFDO_NT)) { fprintf(stderr, "both-show-in is shown by default\n"); exit(1); } if (!sfdo_desktop_entry_show_in(entry, "allowed", SFDO_NT)) { fprintf(stderr, "both-show-in is not shown by \"allowed\"\n"); exit(1); } entry = get_entry(db, "not-show-in"); if (!sfdo_desktop_entry_show_in(entry, NULL, SFDO_NT)) { fprintf(stderr, "not-show-in is not shown by default\n"); exit(1); } if (sfdo_desktop_entry_show_in(entry, "disallowed", SFDO_NT)) { fprintf(stderr, "not-show-in is shown by \"disallowed\"\n"); exit(1); } // Actions entry = get_entry(db, "actions"); actions = sfdo_desktop_entry_get_actions(entry, &n_actions); if (n_actions != 2) { fprintf(stderr, "actions has %zu actions\n", n_actions); exit(1); } action = actions[0]; value = sfdo_desktop_entry_action_get_id(action, &value_len); check_value("actions[0] id", value, value_len, "Do Thing"); value = sfdo_desktop_entry_action_get_name(action, &value_len); check_value("actions[0] name", value, value_len, "Do Thing Action"); action = actions[1]; value = sfdo_desktop_entry_action_get_id(action, &value_len); check_value("actions[1] id", value, value_len, "Sleep"); value = sfdo_desktop_entry_action_get_name(action, &value_len); check_value("actions[1] name", value, value_len, "Sleep Action"); // Exec entry = get_entry(db, "exec-simple"); check_exec("exec-simple", sfdo_desktop_entry_get_exec(entry), 1, "/bin/sh", NULL); check_exec("exec-simple with target", sfdo_desktop_entry_get_exec(entry), 1, "/bin/sh", "target", NULL); entry = get_entry(db, "exec-complex"); check_exec("exec-complex", sfdo_desktop_entry_get_exec(entry), 6, "quoted arg$", "--icon", "icon", sfdo_desktop_entry_get_name(entry, NULL), sfdo_desktop_entry_get_file_path(entry, NULL), "%", NULL); check_exec("exec-complex with targets", sfdo_desktop_entry_get_exec(entry), 8, "quoted arg$", "--icon", "icon", sfdo_desktop_entry_get_name(entry, NULL), sfdo_desktop_entry_get_file_path(entry, NULL), "foo", "bar", "%", "foo", "bar", NULL); entry = get_entry(db, "exec-embed"); char name_buf[64]; snprintf(name_buf, sizeof(name_buf), "name=%s", sfdo_desktop_entry_get_name(entry, NULL)); char path_buf[64]; snprintf(path_buf, sizeof(path_buf), "path=%s", sfdo_desktop_entry_get_file_path(entry, NULL)); check_exec("exec-embed", sfdo_desktop_entry_get_exec(entry), 5, "/bin/sh", name_buf, path_buf, "target=", "deprecated=", NULL); check_exec("exec-embed with target", sfdo_desktop_entry_get_exec(entry), 5, "/bin/sh", name_buf, path_buf, "target=target", "deprecated=", "target", NULL); // DBus get_entry(db, "com.example.dbus"); // Skipped ensure_no_entry(db, "hidden"); ensure_no_entry(db, "unknown-type"); // Bad entries ensure_no_entry(db, "bad-action-duplicate"); ensure_no_entry(db, "bad-action-no-exec"); ensure_no_entry(db, "bad-boolean"); ensure_no_entry(db, "bad-dbus-cont"); ensure_no_entry(db, "bad-dbus-empty"); ensure_no_entry(db, "bad-dbus-leader"); ensure_no_entry(db, "bad-dbus-long"); ensure_no_entry(db, "bad-dbus-single"); ensure_no_entry(db, "bad-empty"); ensure_no_entry(db, "bad-exec-escape"); ensure_no_entry(db, "bad-exec-field-in-quoted"); ensure_no_entry(db, "bad-exec-invalid-field"); ensure_no_entry(db, "bad-exec-multiple-targets"); ensure_no_entry(db, "bad-exec-no-closing-quote"); ensure_no_entry(db, "bad-exec-path"); ensure_no_entry(db, "bad-exec-reserved"); ensure_no_entry(db, "bad-exec-surrounded-standalone"); ensure_no_entry(db, "bad-exec-truncated-field"); ensure_no_entry(db, "bad-exec-unescaped"); ensure_no_entry(db, "bad-ext"); ensure_no_entry(db, "bad-group"); ensure_no_entry(db, "bad-missing-action"); ensure_no_entry(db, "bad-no-exec"); ensure_no_entry(db, "bad-no-required"); ensure_no_entry(db, "bad-show-in-overlap"); sfdo_desktop_db_destroy(db); sfdo_desktop_ctx_destroy(ctx); return 0; } libsfdo-0.1.3/tests/desktop/000077500000000000000000000000001467233572500157665ustar00rootroot00000000000000libsfdo-0.1.3/tests/desktop/basedir1/000077500000000000000000000000001467233572500174605ustar00rootroot00000000000000libsfdo-0.1.3/tests/desktop/basedir1/actions.desktop000066400000000000000000000004171467233572500225150ustar00rootroot00000000000000[Desktop Entry] Name=actions Type=Application Exec=/bin/sh Actions=Do Thing;Sleep [Desktop Action Do Thing] Name=Do Thing Action Exec=/bin/do-thing [Desktop Action Unknown] Name=Unknown Action Exec=/bin/unknown [Desktop Action Sleep] Name=Sleep Action Exec=/bin/sleep libsfdo-0.1.3/tests/desktop/basedir1/bad-action-duplicate.desktop000066400000000000000000000002641467233572500250260ustar00rootroot00000000000000[Desktop Entry] Name=bad-action-duplicate Type=Application Exec=/bin/sh Actions=foo;bar;foo [Desktop Action foo] Name=foo Exec=/bin/sh [Desktop Action bar] Name=bar Exec=/bin/sh libsfdo-0.1.3/tests/desktop/basedir1/bad-action-no-exec.desktop000066400000000000000000000001611467233572500244060ustar00rootroot00000000000000[Desktop Entry] Name=bad-action-no-exec Type=Application Exec=/bin/sh Actions=foo [Desktop Action foo] Name=foo libsfdo-0.1.3/tests/desktop/basedir1/bad-boolean.desktop000066400000000000000000000001201467233572500232070ustar00rootroot00000000000000[Desktop Entry] Name=bad-boolean Type=Application Exec=/bin/false NoDisplay=bad libsfdo-0.1.3/tests/desktop/basedir1/bad-dbus-cont.desktop000066400000000000000000000001351467233572500234740ustar00rootroot00000000000000[Desktop Entry] Name=bad-dbus-cont Type=Application Exec=/bin/sh Implements=co?m.example.Bad libsfdo-0.1.3/tests/desktop/basedir1/bad-dbus-empty.desktop000066400000000000000000000001261467233572500236670ustar00rootroot00000000000000[Desktop Entry] Name=bad-dbus-empty Type=Application Exec=/bin/sh Implements=com..Bad libsfdo-0.1.3/tests/desktop/basedir1/bad-dbus-leader.desktop000066400000000000000000000001371467233572500237670ustar00rootroot00000000000000[Desktop Entry] Name=bad-dbus-leader Type=Application Exec=/bin/sh Implements=?com.example.Bad libsfdo-0.1.3/tests/desktop/basedir1/bad-dbus-long.desktop000066400000000000000000000005151467233572500234720ustar00rootroot00000000000000[Desktop Entry] Name=bad-dbus-long Type=Application Exec=/bin/sh Implements=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa libsfdo-0.1.3/tests/desktop/basedir1/bad-dbus-single.desktop000066400000000000000000000001251467233572500240110ustar00rootroot00000000000000[Desktop Entry] Name=bad-dbus-single Type=Application Exec=/bin/sh Implements=single libsfdo-0.1.3/tests/desktop/basedir1/bad-empty.desktop000066400000000000000000000000001467233572500227230ustar00rootroot00000000000000libsfdo-0.1.3/tests/desktop/basedir1/bad-exec-escape.desktop000066400000000000000000000001261467233572500237600ustar00rootroot00000000000000[Desktop Entry] Name=bad-exec-unescaped Type=Application Icon=icon Exec=/bin/sh "\\m" libsfdo-0.1.3/tests/desktop/basedir1/bad-exec-field-in-quoted.desktop000066400000000000000000000001331467233572500255040ustar00rootroot00000000000000[Desktop Entry] Name=bad-exec-field-in-quoted Type=Application Icon=icon Exec=/bin/sh "%f" libsfdo-0.1.3/tests/desktop/basedir1/bad-exec-invalid-field.desktop000066400000000000000000000001311467233572500252230ustar00rootroot00000000000000[Desktop Entry] Name=bad-exec-truncated-field Type=Application Icon=icon Exec=/bin/sh %q libsfdo-0.1.3/tests/desktop/basedir1/bad-exec-multiple-targets.desktop000066400000000000000000000001351467233572500260220ustar00rootroot00000000000000[Desktop Entry] Name=bad-exec-multiple-targets Type=Application Icon=icon Exec=/bin/sh %F %u libsfdo-0.1.3/tests/desktop/basedir1/bad-exec-no-closing-quote.desktop000066400000000000000000000001401467233572500257170ustar00rootroot00000000000000[Desktop Entry] Name=bad-exec-no-closing-quote Type=Application Icon=icon Exec=/bin/sh "foo bar libsfdo-0.1.3/tests/desktop/basedir1/bad-exec-path.desktop000066400000000000000000000001471467233572500234570ustar00rootroot00000000000000[Desktop Entry] Name=bad-exec-path Type=Application Icon=icon Exec="WAYLAND_DISPLAY=wayland-1 /bin/sh" libsfdo-0.1.3/tests/desktop/basedir1/bad-exec-reserved.desktop000066400000000000000000000001241467233572500243350ustar00rootroot00000000000000[Desktop Entry] Name=bad-exec-reserved Type=Application Icon=icon Exec=/bin/sh $USD libsfdo-0.1.3/tests/desktop/basedir1/bad-exec-surrounded-standalone.desktop000066400000000000000000000001511467233572500270360ustar00rootroot00000000000000[Desktop Entry] Name=bad-exec-surrounded-standalone Type=Application Icon=icon Exec=/bin/sh --targets=%U libsfdo-0.1.3/tests/desktop/basedir1/bad-exec-truncated-field.desktop000066400000000000000000000001301467233572500255650ustar00rootroot00000000000000[Desktop Entry] Name=bad-exec-truncated-field Type=Application Icon=icon Exec=/bin/sh % libsfdo-0.1.3/tests/desktop/basedir1/bad-exec-unescaped.desktop000066400000000000000000000001271467233572500244700ustar00rootroot00000000000000[Desktop Entry] Name=bad-exec-unescaped Type=Application Icon=icon Exec=/bin/sh "$USD" libsfdo-0.1.3/tests/desktop/basedir1/bad-ext.txt000066400000000000000000000000731467233572500215450ustar00rootroot00000000000000[Desktop Entry] Name=bad-ext Type=Application Exec=/bin/sh libsfdo-0.1.3/tests/desktop/basedir1/bad-group.desktop000066400000000000000000000000641467233572500227330ustar00rootroot00000000000000[meow] Name=bad-group Type=Application Exec=/bin/sh libsfdo-0.1.3/tests/desktop/basedir1/bad-missing-action.desktop000066400000000000000000000002021467233572500245150ustar00rootroot00000000000000[Desktop Entry] Name=bad-missing-action Type=Application Exec=/bin/sh Actions=foo;bar [Desktop Action bar] Name=bar Exec=/bin/sh libsfdo-0.1.3/tests/desktop/basedir1/bad-no-exec.desktop000066400000000000000000000000621467233572500231330ustar00rootroot00000000000000[Desktop Entry] Name=bad-no-exec Type=Application libsfdo-0.1.3/tests/desktop/basedir1/bad-no-required.desktop000066400000000000000000000000411467233572500240240ustar00rootroot00000000000000[Desktop Entry] Type=Application libsfdo-0.1.3/tests/desktop/basedir1/bad-show-in-overlap.desktop000066400000000000000000000001541467233572500246310ustar00rootroot00000000000000[Desktop Entry] Name=bad-show-in-overlap Type=Application Exec=/bin/sh OnlyShowIn=foo;bar NotShowIn=bar;buz libsfdo-0.1.3/tests/desktop/basedir1/both-show-in.desktop000066400000000000000000000001611467233572500233670ustar00rootroot00000000000000[Desktop Entry] Name=both-show-in Type=Application Exec=/bin/sh OnlyShowIn=allowed;good NotShowIn=disallowed;bad libsfdo-0.1.3/tests/desktop/basedir1/com.example.app-all.desktop000066400000000000000000000005171467233572500246130ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=app-all GenericName=generic NoDisplay=true Comment=comment Icon=icon Hidden=false DBusActivatable=true TryExec=/bin/false Path=/etc Terminal=true MimeType=foo;bar; Categories=Settings;System;Utility Keywords= StartupNotify=true StartupWMClass=class PrefersNonDefaultGPU=true SingleMainWindow=true libsfdo-0.1.3/tests/desktop/basedir1/com.example.complex.desktop000066400000000000000000000000731467233572500247310ustar00rootroot00000000000000[Desktop Entry] Name=complex Type=Application Exec=/bin/sh libsfdo-0.1.3/tests/desktop/basedir1/com.example.dbus.desktop000066400000000000000000000001601467233572500242140ustar00rootroot00000000000000[Desktop Entry] Name=dbus Type=Application DBusActivatable=true Implements=com.example.Sleepy;com.example.Tired libsfdo-0.1.3/tests/desktop/basedir1/com/000077500000000000000000000000001467233572500202365ustar00rootroot00000000000000libsfdo-0.1.3/tests/desktop/basedir1/com/example/000077500000000000000000000000001467233572500216715ustar00rootroot00000000000000libsfdo-0.1.3/tests/desktop/basedir1/com/example/nested.desktop000066400000000000000000000000721467233572500245450ustar00rootroot00000000000000[Desktop Entry] Name=nested Type=Application Exec=/bin/sh libsfdo-0.1.3/tests/desktop/basedir1/dir.directory000066400000000000000000000000501467233572500221570ustar00rootroot00000000000000[Desktop Entry] Name=dir Type=Directory libsfdo-0.1.3/tests/desktop/basedir1/exec-complex.desktop000066400000000000000000000001631467233572500234440ustar00rootroot00000000000000[Desktop Entry] Name=exec-complex Type=Application Icon=icon Exec="quoted arg\\$" %i %c %k %F %% %d %D %n %N %v %m libsfdo-0.1.3/tests/desktop/basedir1/exec-embed.desktop000066400000000000000000000001461467233572500230520ustar00rootroot00000000000000[Desktop Entry] Name=exec-embed Type=Application Exec=/bin/sh name=%c path=%k target=%f deprecated=%v libsfdo-0.1.3/tests/desktop/basedir1/exec-simple.desktop000066400000000000000000000000771467233572500232720ustar00rootroot00000000000000[Desktop Entry] Name=exec-simple Type=Application Exec=/bin/sh libsfdo-0.1.3/tests/desktop/basedir1/hidden.desktop000066400000000000000000000001111467233572500222770ustar00rootroot00000000000000[Desktop Entry] Name=hidden Type=Application Exec=/bin/false Hidden=true libsfdo-0.1.3/tests/desktop/basedir1/link.desktop000066400000000000000000000000741467233572500220110ustar00rootroot00000000000000[Desktop Entry] Name=link Type=Link URL=https://example.com libsfdo-0.1.3/tests/desktop/basedir1/magic000066400000000000000000000000711467233572500204610ustar00rootroot00000000000000[Desktop Entry] Name=magic Type=Application Exec=/bin/sh libsfdo-0.1.3/tests/desktop/basedir1/not-show-in.desktop000066400000000000000000000001301467233572500232270ustar00rootroot00000000000000[Desktop Entry] Name=not-show-in Type=Application Exec=/bin/sh NotShowIn=disallowed;bad libsfdo-0.1.3/tests/desktop/basedir1/only-show-in.desktop000066400000000000000000000001301467233572500234100ustar00rootroot00000000000000[Desktop Entry] Name=only-show-in Type=Application Exec=/bin/sh OnlyShowIn=allowed;good libsfdo-0.1.3/tests/desktop/basedir1/simple.desktop000066400000000000000000000000721467233572500223430ustar00rootroot00000000000000[Desktop Entry] Name=simple Type=Application Exec=/bin/sh libsfdo-0.1.3/tests/desktop/basedir1/unknown-type.desktop000066400000000000000000000000571467233572500235330ustar00rootroot00000000000000[Desktop Entry] Name=unknown-type Type=Unknown libsfdo-0.1.3/tests/desktop/basedir2/000077500000000000000000000000001467233572500174615ustar00rootroot00000000000000libsfdo-0.1.3/tests/desktop/basedir2/bad-boolean.desktop000066400000000000000000000001611467233572500232150ustar00rootroot00000000000000# Overridden by an invalid entry [Desktop Entry] Name=bad-boolean Type=Application Exec=/bin/false NoDisplay=bad libsfdo-0.1.3/tests/desktop/basedir2/hidden.desktop000066400000000000000000000001401467233572500223020ustar00rootroot00000000000000# Overridden by a hidden entry [Desktop Entry] Name=not hidden Type=Application Exec=/bin/false libsfdo-0.1.3/tests/icon.c000066400000000000000000000136101467233572500154120ustar00rootroot00000000000000#include #include #include #include #include static void log_handler(enum sfdo_log_level level, const char *fmt, va_list args, void *data) { (void)level; (void)data; static const char *levels[] = { [SFDO_LOG_LEVEL_SILENT] = "", [SFDO_LOG_LEVEL_ERROR] = "error", [SFDO_LOG_LEVEL_INFO] = "info", [SFDO_LOG_LEVEL_DEBUG] = "debug", }; fprintf(stdout, "[%s] ", levels[level]); vfprintf(stdout, fmt, args); fprintf(stdout, "\n"); } static struct sfdo_icon_theme *load_theme( struct sfdo_icon_ctx *ctx, const char *name, int options) { static const struct sfdo_string basedirs[] = { { .data = "icon/basedir1", .len = 13, }, }; return sfdo_icon_theme_load_from( ctx, name, basedirs, sizeof(basedirs) / sizeof(*basedirs), options); } static struct sfdo_icon_theme *load_success( struct sfdo_icon_ctx *ctx, const char *name, int options) { struct sfdo_icon_theme *theme = load_theme(ctx, name, options); if (theme == NULL) { fprintf(stderr, "\"%s\" unexpected error\n", name); exit(1); } return theme; } static void load_error(struct sfdo_icon_ctx *ctx, const char *name, int options) { struct sfdo_icon_theme *theme = load_theme(ctx, name, options); if (theme != NULL) { fprintf(stderr, "\"%s\" unexpected error\n", name); exit(1); } } static struct sfdo_icon_file *lookup(const char *case_name, struct sfdo_icon_theme *theme, const char *name, int size, int scale, int options) { struct sfdo_icon_file *file = sfdo_icon_theme_lookup(theme, name, SFDO_NT, size, scale, options); if (file == SFDO_ICON_FILE_INVALID) { fprintf(stderr, "\"%s\" runtime icon lookup error\n", case_name); exit(1); } return file; } static void lookup_success(const char *case_name, struct sfdo_icon_theme *theme, const char *name, int size, int scale, int options, enum sfdo_icon_file_format exp_format, const char *exp_path) { struct sfdo_icon_file *file = lookup(case_name, theme, name, size, scale, options); if (file == NULL) { fprintf(stderr, "\"%s\" unexpected icon lookup error\n", case_name); exit(1); } const char *got_path = sfdo_icon_file_get_path(file, NULL); if (strcmp(exp_path, got_path) != 0) { fprintf(stderr, "\"%s\" path mismatch: expected %s, got %s\n", case_name, exp_path, got_path); exit(1); } enum sfdo_icon_file_format got_format = sfdo_icon_file_get_format(file); if (exp_format != got_format) { static const char *format_names[] = { [SFDO_ICON_FILE_FORMAT_PNG] = "png", [SFDO_ICON_FILE_FORMAT_SVG] = "svg", [SFDO_ICON_FILE_FORMAT_XPM] = "xpm", }; fprintf(stderr, "\"%s\" format mismatch: expected %s, got %s\n", case_name, format_names[exp_format], format_names[got_format]); exit(1); } sfdo_icon_file_destroy(file); } static void lookup_error(const char *case_name, struct sfdo_icon_theme *theme, const char *name, int size, int scale, int options) { struct sfdo_icon_file *file = lookup(case_name, theme, name, size, scale, options); if (file != NULL) { fprintf(stderr, "\"%s\" unexpected icon lookup success\n", case_name); exit(1); } } int main(void) { // Update cache mtime to ensure it's not stale utimes("icon/basedir1/cached/icon-theme.cache", NULL); struct sfdo_icon_ctx *ctx = sfdo_icon_ctx_create(NULL); sfdo_icon_ctx_set_log_handler(ctx, SFDO_LOG_LEVEL_DEBUG, log_handler, NULL); struct sfdo_icon_theme *theme; theme = load_success(ctx, "hicolor", SFDO_ICON_THEME_LOAD_OPTIONS_DEFAULT); lookup_success("fixed", theme, "fixed", 16, 1, SFDO_ICON_THEME_LOOKUP_OPTIONS_DEFAULT, SFDO_ICON_FILE_FORMAT_PNG, "icon/basedir1/hicolor/fixed/fixed.png"); lookup_success("scalable", theme, "scalable", 32, 1, SFDO_ICON_THEME_LOOKUP_OPTIONS_DEFAULT, SFDO_ICON_FILE_FORMAT_SVG, "icon/basedir1/hicolor/scalable/nested/scalable.svg"); lookup_success("fallback", theme, "fallback", 24, 1, SFDO_ICON_THEME_LOOKUP_OPTIONS_DEFAULT, SFDO_ICON_FILE_FORMAT_PNG, "icon/basedir1/fallback.png"); lookup_error( "nonexistent", theme, "nonexistent", 24, 1, SFDO_ICON_THEME_LOOKUP_OPTIONS_DEFAULT); sfdo_icon_theme_destroy(theme); load_error(ctx, "nonexistent", SFDO_ICON_THEME_LOAD_OPTIONS_DEFAULT); sfdo_icon_theme_destroy( load_success(ctx, "nonexistent", SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING)); theme = load_success(ctx, "loop-first", SFDO_ICON_THEME_LOAD_OPTIONS_DEFAULT); lookup_success("loop-first", theme, "loop", 16, 1, SFDO_ICON_THEME_LOOKUP_OPTIONS_DEFAULT, SFDO_ICON_FILE_FORMAT_PNG, "icon/basedir1/loop-first/dir/loop.png"); sfdo_icon_theme_destroy(theme); theme = load_success(ctx, "loop-second", SFDO_ICON_THEME_LOAD_OPTIONS_DEFAULT); lookup_success("loop-second", theme, "loop", 16, 1, SFDO_ICON_THEME_LOOKUP_OPTIONS_DEFAULT, SFDO_ICON_FILE_FORMAT_PNG, "icon/basedir1/loop-second/dir/loop.png"); sfdo_icon_theme_destroy(theme); theme = load_success(ctx, "cached", SFDO_ICON_THEME_LOAD_OPTIONS_DEFAULT); lookup_error("cached", theme, "placeholder", 24, 1, SFDO_ICON_THEME_LOOKUP_OPTION_NO_RESCAN); lookup_success("cached foo", theme, "foo", 24, 1, SFDO_ICON_THEME_LOOKUP_OPTION_NO_RESCAN, SFDO_ICON_FILE_FORMAT_PNG, "icon/basedir1/cached/24x24/foo.png"); lookup_success("cached something", theme, "something", 24, 1, SFDO_ICON_THEME_LOOKUP_OPTION_NO_RESCAN, SFDO_ICON_FILE_FORMAT_SVG, "icon/basedir1/cached/24x24/something.svg"); lookup_success("cached multi 24", theme, "multi", 24, 1, SFDO_ICON_THEME_LOOKUP_OPTION_NO_RESCAN, SFDO_ICON_FILE_FORMAT_PNG, "icon/basedir1/cached/24x24/multi.png"); lookup_success("cached multi 22", theme, "multi", 22, 1, SFDO_ICON_THEME_LOOKUP_OPTION_NO_RESCAN, SFDO_ICON_FILE_FORMAT_SVG, "icon/basedir1/cached/22x22/multi.svg"); lookup_success("cached multi 22 no_svg", theme, "multi", 22, 1, SFDO_ICON_THEME_LOOKUP_OPTION_NO_RESCAN | SFDO_ICON_THEME_LOOKUP_OPTION_NO_SVG, SFDO_ICON_FILE_FORMAT_XPM, "icon/basedir1/cached/22x22/multi.xpm"); sfdo_icon_theme_destroy(theme); sfdo_icon_ctx_destroy(ctx); return 0; } libsfdo-0.1.3/tests/icon/000077500000000000000000000000001467233572500152455ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/000077500000000000000000000000001467233572500167375ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/cached/000077500000000000000000000000001467233572500201465ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/cached/22x22/000077500000000000000000000000001467233572500207255ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/cached/22x22/README000066400000000000000000000001101467233572500215750ustar00rootroot00000000000000This file only exists to make Git track this otherwise empty directory. libsfdo-0.1.3/tests/icon/basedir1/cached/24x24/000077500000000000000000000000001467233572500207315ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/cached/24x24/README000066400000000000000000000001101467233572500216010ustar00rootroot00000000000000This file only exists to make Git track this otherwise empty directory. libsfdo-0.1.3/tests/icon/basedir1/cached/cache.txt000066400000000000000000000001241467233572500217470ustar00rootroot0000000000000024x24/foo.png 24x24/something.svg 24x24/multi.png 22x22/multi.svg 22x22/multi.xpm libsfdo-0.1.3/tests/icon/basedir1/cached/icon-theme.cache000066400000000000000000000004101467233572500231560ustar00rootroot00000000000000$ 24x2422x22ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¤ÿÿÿÿ°ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¼ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈèÿÿÿÿÒôÿÿÿÿØÜsomethingmultifooÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿlibsfdo-0.1.3/tests/icon/basedir1/cached/index.theme000066400000000000000000000002471467233572500223040ustar00rootroot00000000000000[Icon Theme] Name=cached Comment=cached Directories=placeholder,22x22,24x24 [placeholder] Size=24 Type=Fixed [22x22] Size=22 Type=Fixed [24x24] Size=24 Type=Fixed libsfdo-0.1.3/tests/icon/basedir1/cached/placeholder/000077500000000000000000000000001467233572500224305ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/cached/placeholder/placeholder.png000066400000000000000000000000001467233572500254060ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/fallback.png000066400000000000000000000000001467233572500211720ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/hicolor/000077500000000000000000000000001467233572500203765ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/hicolor/fixed/000077500000000000000000000000001467233572500214755ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/hicolor/fixed/fixed.png000066400000000000000000000000001467233572500232700ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/hicolor/index.theme000066400000000000000000000002161467233572500225300ustar00rootroot00000000000000[Icon Theme] Name=basic Comment=basic Directories=fixed,scalable/nested [fixed] Size=16 Type=Fixed [scalable/nested] Size=32 Type=Scalable libsfdo-0.1.3/tests/icon/basedir1/hicolor/scalable/000077500000000000000000000000001467233572500221445ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/hicolor/scalable/nested/000077500000000000000000000000001467233572500234265ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/hicolor/scalable/nested/scalable.svg000066400000000000000000000000001467233572500257030ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/loop-first/000077500000000000000000000000001467233572500210355ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/loop-first/dir/000077500000000000000000000000001467233572500216135ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/loop-first/dir/loop.png000066400000000000000000000000001467233572500232600ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/loop-first/index.theme000066400000000000000000000001601467233572500231650ustar00rootroot00000000000000[Icon Theme] Name=loop-first Comment=loop-first Inherits=loop-second Directories=dir [dir] Size=16 Type=Fixed libsfdo-0.1.3/tests/icon/basedir1/loop-second/000077500000000000000000000000001467233572500211615ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/loop-second/dir/000077500000000000000000000000001467233572500217375ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/loop-second/dir/loop.png000066400000000000000000000000001467233572500234040ustar00rootroot00000000000000libsfdo-0.1.3/tests/icon/basedir1/loop-second/index.theme000066400000000000000000000001611467233572500233120ustar00rootroot00000000000000[Icon Theme] Name=loop-second Comment=loop-second Inherits=loop-first Directories=dir [dir] Size=16 Type=Fixed libsfdo-0.1.3/tests/meson.build000066400000000000000000000015051467233572500164600ustar00rootroot00000000000000tests = [ { 'name': 'basedir', 'deps': [ sfdo_basedir, ], }, { 'name': 'desktop-file', 'deps': [ sfdo_desktop_file, ], }, { 'name': 'desktop', 'deps': [ sfdo_desktop, ], 'datadir': 'desktop', }, { 'name': 'icon', 'deps': [ sfdo_icon, ], 'datadir': 'icon', }, ] foreach template : tests name = template['name'] deps = [] if 'datadir' in template datadir = template['datadir'] deps += custom_target( name + '-data', output: name + '-data', command: ['cp', '-R', meson.current_source_dir() / datadir, datadir] ) endif test( name, executable( name, name + '.c', include_directories: include_dir, dependencies: template['deps'], ), depends: deps, ) endforeach