diff options
Diffstat (limited to 'src/desktop_vec.c')
-rw-r--r-- | src/desktop_vec.c | 362 |
1 files changed, 362 insertions, 0 deletions
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 <glib.h> +#include <stdbool.h> +#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); +// } |