From 9e8af9d25106b615dabf956305f4ef80496ed412 Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Sun, 24 Jul 2022 12:31:09 +0100 Subject: Add drun mode. This is a pretty simple implementation, but it should work for most use cases. Notably, generic application names aren't used (though that could be added without too much hassle), and neither are keywords (that would be more difficult). --- src/desktop_vec.c | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 src/desktop_vec.c (limited to 'src/desktop_vec.c') diff --git a/src/desktop_vec.c b/src/desktop_vec.c new file mode 100644 index 0000000..d02b2ac --- /dev/null +++ b/src/desktop_vec.c @@ -0,0 +1,362 @@ +#include +#include +#include "desktop_vec.h" +#include "log.h" +#include "string_vec.h" +#include "xmalloc.h" + +static bool match_current_desktop(char * const *desktop_list, gsize length); + +[[nodiscard("memory leaked")]] +struct desktop_vec desktop_vec_create(void) +{ + struct desktop_vec vec = { + .count = 0, + .size = 128, + .buf = xcalloc(128, sizeof(*vec.buf)), + }; + return vec; +} + +void desktop_vec_destroy(struct desktop_vec *restrict vec) +{ + for (size_t i = 0; i < vec->count; i++) { + free(vec->buf[i].id); + free(vec->buf[i].name); + free(vec->buf[i].path); + } + free(vec->buf); +} + +void desktop_vec_add( + struct desktop_vec *restrict vec, + const char *restrict id, + const char *restrict name, + const char *restrict path) +{ + if (vec->count == vec->size) { + vec->size *= 2; + vec->buf = xrealloc(vec->buf, vec->size * sizeof(vec->buf[0])); + } + vec->buf[vec->count].id = xstrdup(id); + vec->buf[vec->count].name = xstrdup(name); + vec->buf[vec->count].path = xstrdup(path); + vec->count++; +} + +void desktop_vec_add_file(struct desktop_vec *vec, const char *id, const char *path) +{ + GKeyFile *file = g_key_file_new(); + if (!g_key_file_load_from_file(file, path, G_KEY_FILE_NONE, NULL)) { + log_error("Failed to open %s.\n", path); + return; + } + + const char *group = "Desktop Entry"; + + if (g_key_file_get_boolean(file, group, "Hidden", NULL) + || g_key_file_get_boolean(file, group, "NoDisplay", NULL)) { + goto cleanup_file; + } + + char *name = g_key_file_get_locale_string(file, group, "Name", NULL, NULL); + if (name == NULL) { + log_error("%s: No name found.\n", path); + goto cleanup_file; + } + + gsize length; + gchar **list = g_key_file_get_string_list(file, group, "OnlyShowIn", &length, NULL); + if (list) { + bool match = match_current_desktop(list, length); + g_strfreev(list); + list = NULL; + if (!match) { + goto cleanup_name; + } + } + + list = g_key_file_get_string_list(file, group, "NotShowIn", &length, NULL); + if (list) { + bool match = match_current_desktop(list, length); + g_strfreev(list); + list = NULL; + if (match) { + goto cleanup_name; + } + } + + desktop_vec_add(vec, id, name, path); + +cleanup_name: + free(name); +cleanup_file: + g_key_file_unref(file); +} + +static int cmpdesktopp(const void *restrict a, const void *restrict b) +{ + struct desktop_entry *restrict d1 = (struct desktop_entry *)a; + struct desktop_entry *restrict d2 = (struct desktop_entry *)b; + return strcmp(d1->name, d2->name); +} + +void desktop_vec_sort(struct desktop_vec *restrict vec) +{ + qsort(vec->buf, vec->count, sizeof(vec->buf[0]), cmpdesktopp); +} + +struct desktop_entry *desktop_vec_find(struct desktop_vec *restrict vec, const char *name) +{ + /* + * Explicitly cast away const-ness, as even though we won't modify the + * name, the compiler rightly complains that we might. + */ + struct desktop_entry tmp = { .name = (char *)name }; + return bsearch(&tmp, vec->buf, vec->count, sizeof(vec->buf[0]), cmpdesktopp); +} + +struct desktop_vec desktop_vec_load(FILE *file) +{ + struct desktop_vec vec = desktop_vec_create(); + if (file == NULL) { + return vec; + } + + ssize_t bytes_read; + char *line = NULL; + size_t len; + while ((bytes_read = getline(&line, &len, file)) != -1) { + if (line[bytes_read - 1] == '\n') { + line[bytes_read - 1] = '\0'; + } + char *id = line; + size_t sublen = strlen(line); + char *name = &line[sublen + 1]; + sublen = strlen(name); + char *path = &name[sublen + 1]; + desktop_vec_add(&vec, id, name, path); + } + free(line); + + return vec; +} + +void desktop_vec_save(struct desktop_vec *restrict vec, FILE *restrict file) +{ + /* + * Using null bytes for field separators is a bit odd, but it makes + * parsing very quick and easy. + */ + for (size_t i = 0; i < vec->count; i++) { + fputs(vec->buf[i].id, file); + fputc('\0', file); + fputs(vec->buf[i].name, file); + fputc('\0', file); + fputs(vec->buf[i].path, file); + fputc('\n', file); + } +} + +bool match_current_desktop(char * const *desktop_list, gsize length) +{ + const char *xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP"); + if (xdg_current_desktop == NULL) { + return false; + } + + struct string_vec desktops = string_vec_create(); + + char *saveptr = NULL; + char *tmp = xstrdup(xdg_current_desktop); + char *desktop = strtok_r(tmp, ":", &saveptr); + while (desktop != NULL) { + string_vec_add(&desktops, desktop); + desktop = strtok_r(NULL, ":", &saveptr); + } + free(tmp); + + for (gsize i = 0; i < length; i++) { + if (string_vec_find(&desktops, desktop_list[i])) { + return true; + } + } + + string_vec_destroy(&desktops); + return false; +} + +/* + * Checking-in commented-out code is generally bad practice, but this may be + * needed in the near future. Using the various GKeyFile functions above + * ensures correct behaviour, but is relatively slow (~3-4 ms for 60 desktop + * files). Below are some quick and dirty replacement functions, which work + * correctly except for name localisation, and are ~4x faster. If we go a while + * without needing these, they should be deleted. + */ + +// static char *strip(const char *str) +// { +// size_t start = 0; +// size_t end = strlen(str); +// while (start <= end && isspace(str[start])) { +// start++; +// } +// if (start == end) { +// return NULL; +// } +// while (end > start && (isspace(str[end]) || str[end] == '\0')) { +// end--; +// } +// if (end < start) { +// return NULL; +// } +// if (str[start] == '"' && str[end] == '"' && end > start) { +// start++; +// end--; +// } +// size_t len = end - start + 1; +// char *buf = xcalloc(len + 1, 1); +// strncpy(buf, str + start, len); +// buf[len] = '\0'; +// return buf; +// } +// +// static char *get_option(const char *line) +// { +// size_t index = 0; +// while (line[index] != '=' && index < strlen(line)) { +// index++; +// } +// if (index >= strlen(line)) { +// return NULL; +// } +// index++; +// while (isspace(line[index]) && index < strlen(line)) { +// index++; +// } +// if (index >= strlen(line)) { +// return NULL; +// } +// return strip(&line[index]); +// } +// static bool match_current_desktop2(const char *desktop_list) +// { +// const char *xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP"); +// if (xdg_current_desktop == NULL) { +// return false; +// } +// +// struct string_vec desktops = string_vec_create(); +// +// char *saveptr = NULL; +// char *tmp = xstrdup(xdg_current_desktop); +// char *desktop = strtok_r(tmp, ":", &saveptr); +// while (desktop != NULL) { +// string_vec_add(&desktops, desktop); +// desktop = strtok_r(NULL, ":", &saveptr); +// } +// free(tmp); +// +// /* +// * Technically this will fail if the desktop list contains an escaped +// * \;, but I don't know of any desktops with semicolons in their names. +// */ +// saveptr = NULL; +// tmp = xstrdup(desktop_list); +// desktop = strtok_r(tmp, ";", &saveptr); +// while (desktop != NULL) { +// if (string_vec_find(&desktops, desktop)) { +// return true; +// } +// desktop = strtok_r(NULL, ";", &saveptr); +// } +// free(tmp); +// +// string_vec_destroy(&desktops); +// return false; +// } +// +// static void desktop_vec_add_file2(struct desktop_vec *desktop, const char *id, const char *path) +// { +// FILE *file = fopen(path, "rb"); +// if (!file) { +// log_error("Failed to open %s.\n", path); +// return; +// } +// +// char *line = NULL; +// size_t len; +// bool found = false; +// while(getline(&line, &len, file) > 0) { +// if (!strncmp(line, "[Desktop Entry]", strlen("[Desktop Entry]"))) { +// found = true; +// break; +// } +// } +// if (!found) { +// log_error("%s: No [Desktop Entry] section found.\n", path); +// goto cleanup_file; +// } +// +// /* Please forgive the macro usage. */ +// #define OPTION(key) (!strncmp(line, (key), strlen((key)))) +// char *name = NULL; +// found = false; +// while(getline(&line, &len, file) > 0) { +// /* We've left the [Desktop Entry] section, stop parsing. */ +// if (line[0] == '[') { +// break; +// } +// if (OPTION("Name")) { +// if (line[4] == ' ' || line[4] == '=') { +// found = true; +// name = get_option(line); +// } +// } else if (OPTION("Hidden") +// || OPTION("NoDisplay")) { +// char *option = get_option(line); +// if (option != NULL) { +// bool match = !strcmp(option, "true"); +// free(option); +// if (match) { +// goto cleanup_file; +// } +// } +// } else if (OPTION("OnlyShowIn")) { +// char *option = get_option(line); +// if (option != NULL) { +// bool match = match_current_desktop2(option); +// free(option); +// if (!match) { +// goto cleanup_file; +// } +// } +// } else if (OPTION("NotShowIn")) { +// char *option = get_option(line); +// if (option != NULL) { +// bool match = match_current_desktop2(option); +// free(option); +// if (match) { +// goto cleanup_file; +// } +// } +// } +// } +// if (!found) { +// log_error("%s: No name found.\n", path); +// goto cleanup_name; +// } +// if (name == NULL) { +// log_error("%s: Malformed name key.\n", path); +// goto cleanup_file; +// } +// +// desktop_vec_add(desktop, id, name, path); +// +// cleanup_name: +// free(name); +// cleanup_file: +// free(line); +// fclose(file); +// } -- cgit v1.2.3