summaryrefslogtreecommitdiff
path: root/src/desktop_vec.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/desktop_vec.c')
-rw-r--r--src/desktop_vec.c362
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);
+// }