summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPhil Jones <philj56@gmail.com>2022-07-24 12:31:09 +0100
committerPhil Jones <philj56@gmail.com>2022-07-24 12:40:26 +0100
commit9e8af9d25106b615dabf956305f4ef80496ed412 (patch)
tree180ad86a9c1675777c83a9243db5129a01be9146 /src
parentee0ed3c506f8f45fce42cbabc1d9521382740924 (diff)
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).
Diffstat (limited to 'src')
-rw-r--r--src/compgen.c6
-rw-r--r--src/config.c2
-rw-r--r--src/desktop_vec.c362
-rw-r--r--src/desktop_vec.h36
-rw-r--r--src/drun.c287
-rw-r--r--src/drun.h11
-rw-r--r--src/entry.h3
-rw-r--r--src/history.c25
-rw-r--r--src/history.h5
-rw-r--r--src/main.c42
-rw-r--r--src/string_vec.c2
-rw-r--r--src/tofi.h1
12 files changed, 764 insertions, 18 deletions
diff --git a/src/compgen.c b/src/compgen.c
index f656f32..b4bcc4c 100644
--- a/src/compgen.c
+++ b/src/compgen.c
@@ -12,7 +12,7 @@
#include "string_vec.h"
#include "xmalloc.h"
-static const char *default_state_dir = ".cache";
+static const char *default_cache_dir = ".cache";
static const char *cache_basename = "tofi-compgen";
[[nodiscard("memory leaked")]]
@@ -26,7 +26,7 @@ static char *get_cache_path() {
return NULL;
}
size_t len = strlen(home) + 1
- + strlen(default_state_dir) + 1
+ + strlen(default_cache_dir) + 1
+ strlen(cache_basename) + 1;
cache_name = xmalloc(len);
snprintf(
@@ -34,7 +34,7 @@ static char *get_cache_path() {
len,
"%s/%s/%s",
home,
- default_state_dir,
+ default_cache_dir,
cache_basename);
} else {
size_t len = strlen(state_path) + 1
diff --git a/src/config.c b/src/config.c
index 713cc7e..a2e0f03 100644
--- a/src/config.c
+++ b/src/config.c
@@ -291,6 +291,8 @@ bool parse_option(struct tofi *tofi, const char *filename, size_t lineno, const
tofi->hide_cursor = parse_bool(filename, lineno, value, &err);
} else if (strcasecmp(option, "history") == 0) {
tofi->use_history = parse_bool(filename, lineno, value, &err);
+ } else if (strcasecmp(option, "drun-launch") == 0) {
+ tofi->drun_launch = parse_bool(filename, lineno, value, &err);
} else if (strcasecmp(option, "hint-font") == 0) {
tofi->window.entry.harfbuzz.disable_hinting = !parse_bool(filename, lineno, value, &err);
} else if (strcasecmp(option, "late-keyboard-init") == 0) {
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);
+// }
diff --git a/src/desktop_vec.h b/src/desktop_vec.h
new file mode 100644
index 0000000..9c15ad9
--- /dev/null
+++ b/src/desktop_vec.h
@@ -0,0 +1,36 @@
+#ifndef DESKTOP_VEC_H
+#define DESKTOP_VEC_H
+
+#include <stddef.h>
+#include <stdio.h>
+
+struct desktop_entry {
+ char *id;
+ char *name;
+ char *path;
+};
+
+struct desktop_vec {
+ size_t count;
+ size_t size;
+ struct desktop_entry *buf;
+};
+
+[[nodiscard("memory leaked")]]
+struct desktop_vec desktop_vec_create(void);
+void desktop_vec_destroy(struct desktop_vec *restrict vec);
+void desktop_vec_add(
+ struct desktop_vec *restrict vec,
+ const char *restrict id,
+ const char *restrict name,
+ const char *restrict path);
+void desktop_vec_add_file(struct desktop_vec *desktop, const char *id, const char *path);
+
+void desktop_vec_sort(struct desktop_vec *restrict vec);
+struct desktop_entry *desktop_vec_find(struct desktop_vec *restrict vec, const char *name);
+
+struct desktop_vec desktop_vec_load(FILE *file);
+void desktop_vec_save(struct desktop_vec *restrict vec, FILE *restrict file);
+
+
+#endif /* DESKTOP_VEC_H */
diff --git a/src/drun.c b/src/drun.c
new file mode 100644
index 0000000..bc755fb
--- /dev/null
+++ b/src/drun.c
@@ -0,0 +1,287 @@
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fts.h>
+#include <glib.h>
+#include <gio/gdesktopappinfo.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include "drun.h"
+#include "history.h"
+#include "log.h"
+#include "mkdirp.h"
+#include "string_vec.h"
+#include "xmalloc.h"
+
+static const char *default_data_dir = ".local/share/";
+static const char *default_cache_dir = ".cache/";
+static const char *cache_basename = "tofi-drun";
+
+[[nodiscard("memory leaked")]]
+static char *get_cache_path() {
+ char *cache_name = NULL;
+ const char *state_path = getenv("XDG_CACHE_HOME");
+ if (state_path == NULL) {
+ const char *home = getenv("HOME");
+ if (home == NULL) {
+ log_error("Couldn't retrieve HOME from environment.\n");
+ return NULL;
+ }
+ size_t len = strlen(home) + 1
+ + strlen(default_cache_dir) + 1
+ + strlen(cache_basename) + 1;
+ cache_name = xmalloc(len);
+ snprintf(
+ cache_name,
+ len,
+ "%s/%s/%s",
+ home,
+ default_cache_dir,
+ cache_basename);
+ } else {
+ size_t len = strlen(state_path) + 1
+ + strlen(cache_basename) + 1;
+ cache_name = xmalloc(len);
+ snprintf(
+ cache_name,
+ len,
+ "%s/%s",
+ state_path,
+ cache_basename);
+ }
+ return cache_name;
+}
+
+[[nodiscard("memory leaked")]]
+static struct string_vec get_application_paths() {
+ char *base_paths = NULL;
+ const char *xdg_data_dirs = getenv("XDG_DATA_DIRS");
+ if (xdg_data_dirs == NULL) {
+ xdg_data_dirs = "/usr/local/share/:/usr/share/";
+ }
+ const char *xdg_data_home = getenv("XDG_DATA_HOME");
+ if (xdg_data_home == NULL) {
+ const char *home = getenv("HOME");
+ if (home == NULL) {
+ log_error("Couldn't retrieve HOME from environment.\n");
+ exit(EXIT_FAILURE);
+ }
+ size_t len = strlen(home) + 1
+ + strlen(default_data_dir) + 1
+ + strlen(xdg_data_dirs) + 1;
+ base_paths = xmalloc(len);
+ snprintf(
+ base_paths,
+ len,
+ "%s/%s:%s",
+ home,
+ default_data_dir,
+ xdg_data_dirs);
+ } else {
+ size_t len = strlen(xdg_data_home) + 1
+ + strlen(xdg_data_dirs) + 1;
+ base_paths = xmalloc(len);
+ snprintf(
+ base_paths,
+ len,
+ "%s:%s",
+ xdg_data_home,
+ xdg_data_dirs);
+ }
+
+
+ /* Append /applications/ to each entry. */
+ struct string_vec paths = string_vec_create();
+ char *saveptr = NULL;
+ char *path_entry = strtok_r(base_paths, ":", &saveptr);
+ while (path_entry != NULL) {
+ const char *subdir = "applications/";
+ size_t len = strlen(path_entry) + 1 + strlen(subdir) + 1;
+ char *apps = xmalloc(len);
+ snprintf(apps, len, "%s/%s", path_entry, subdir);
+ string_vec_add(&paths, apps);
+ free(apps);
+ path_entry = strtok_r(NULL, ":", &saveptr);
+ }
+ free(base_paths);
+
+ return paths;
+}
+
+static void parse_desktop_file(gpointer key, gpointer value, void *data)
+{
+ const char *id = key;
+ const char *path = value;
+ struct desktop_vec *apps = data;
+
+ desktop_vec_add_file(apps, id, path);
+}
+
+struct desktop_vec drun_generate(void)
+{
+ log_debug("Retrieving application dirs.\n");
+ struct string_vec paths = get_application_paths();
+ struct string_vec desktop_files = string_vec_create();
+ log_debug("Scanning for .desktop files.\n");
+ for (size_t i = 0; i < paths.count; i++) {
+ const char *path_entry = paths.buf[i].string;
+ DIR *dir = opendir(path_entry);
+ if (dir != NULL) {
+ struct dirent *d;
+ while ((d = readdir(dir)) != NULL) {
+ const char *extension = strrchr(d->d_name, '.');
+ if (extension == NULL) {
+ continue;
+ }
+ if (strcmp(extension, ".desktop")) {
+ continue;
+ }
+ string_vec_add(&desktop_files, d->d_name);
+ }
+ closedir(dir);
+ }
+ }
+ log_debug("Found %zu files.\n", desktop_files.count);
+
+
+ log_debug("Parsing .desktop files.\n");
+ /*
+ * The Desktop Entry Specification says that only the highest
+ * precedence application file with a given ID should be used, so store
+ * the id / path pairs into a hash table to enforce uniqueness.
+ */
+ GHashTable *id_hash = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
+ struct desktop_vec apps = desktop_vec_create();
+ for (size_t i = 0; i < paths.count; i++) {
+ char *path_entry = paths.buf[i].string;
+ char *tree[2] = { path_entry, NULL };
+ size_t prefix_len = strlen(path_entry);
+ FTS *fts = fts_open(tree, FTS_LOGICAL, NULL);
+ FTSENT *entry = fts_read(fts);
+ for (; entry != NULL; entry = fts_read(fts)) {
+ const char *extension = strrchr(entry->fts_name, '.');
+ if (extension == NULL) {
+ continue;
+ }
+ if (strcmp(extension, ".desktop")) {
+ continue;
+ }
+ char *id = xstrdup(&entry->fts_path[prefix_len]);
+ char *slash = strchr(id, '/');
+ while (slash != NULL) {
+ *slash = '-';
+ slash = strchr(slash, '/');
+ }
+ /*
+ * We're iterating from highest to lowest precedence,
+ * so only the first file with a given ID should be
+ * stored.
+ */
+ if (!g_hash_table_contains(id_hash, id)) {
+ char *path = xstrdup(entry->fts_path);
+ g_hash_table_insert(id_hash, id, path);
+ } else {
+ free(id);
+ }
+
+ }
+ fts_close(fts);
+ }
+
+ /* Parse the remaining files into our desktop_vec. */
+ g_hash_table_foreach(id_hash, parse_desktop_file, &apps);
+ g_hash_table_unref(id_hash);
+
+ log_debug("Found %zu apps.\n", apps.count);
+
+ /*
+ * It's now safe to sort the desktop file vector, as the rules about
+ * file precedence have been taken care of.
+ */
+ log_debug("Sorting results.\n");
+ desktop_vec_sort(&apps);
+
+ string_vec_destroy(&desktop_files);
+ string_vec_destroy(&paths);
+ return apps;
+}
+
+struct desktop_vec drun_generate_cached()
+{
+ log_debug("Retrieving cache location.\n");
+ char *cache_path = get_cache_path();
+
+ struct stat sb;
+ if (cache_path == NULL) {
+ return drun_generate();
+ }
+
+ /* If the cache doesn't exist, create it and return */
+ errno = 0;
+ if (stat(cache_path, &sb) == -1) {
+ if (errno == ENOENT) {
+ struct desktop_vec apps = drun_generate();
+ if (!mkdirp(cache_path)) {
+ free(cache_path);
+ return apps;
+ }
+ FILE *cache = fopen(cache_path, "wb");
+ desktop_vec_save(&apps, cache);
+ fclose(cache);
+ free(cache_path);
+ return apps;
+ }
+ free(cache_path);
+ return drun_generate();
+ }
+
+ log_debug("Retrieving application dirs.\n");
+ struct string_vec application_path = get_application_paths();;
+
+ /* The cache exists, so check if it's still in date */
+ bool out_of_date = false;
+ for (size_t i = 0; i < application_path.count; i++) {
+ struct stat path_sb;
+ if (stat(application_path.buf[i].string, &path_sb) == 0) {
+ if (path_sb.st_mtim.tv_sec > sb.st_mtim.tv_sec) {
+ out_of_date = true;
+ break;
+ }
+ }
+ }
+ string_vec_destroy(&application_path);
+
+ struct desktop_vec apps;
+ if (out_of_date) {
+ log_debug("Cache out of date, updating.\n");
+ log_indent();
+ apps = drun_generate();
+ log_unindent();
+ FILE *cache = fopen(cache_path, "wb");
+ desktop_vec_save(&apps, cache);
+ fclose(cache);
+ } else {
+ log_debug("Cache up to date, loading.\n");
+ FILE *cache = fopen(cache_path, "rb");
+ apps = desktop_vec_load(cache);
+ fclose(cache);
+ }
+ free(cache_path);
+ return apps;
+}
+
+void drun_launch(const char *filename)
+{
+ GDesktopAppInfo *info = g_desktop_app_info_new_from_filename(filename);
+ GAppLaunchContext *context = g_app_launch_context_new();
+
+ if (!g_app_info_launch((GAppInfo *)info, NULL, context, NULL)) {
+ log_error("Failed to launch %s.\n", filename);
+ }
+
+ g_object_unref(context);
+ g_object_unref(info);
+}
diff --git a/src/drun.h b/src/drun.h
new file mode 100644
index 0000000..2e14599
--- /dev/null
+++ b/src/drun.h
@@ -0,0 +1,11 @@
+#ifndef DRUN_H
+#define DRUN_H
+
+#include "desktop_vec.h"
+#include "string_vec.h"
+
+struct desktop_vec drun_generate(void);
+struct desktop_vec drun_generate_cached(void);
+void drun_launch(const char *filename);
+
+#endif /* DRUN_H */
diff --git a/src/entry.h b/src/entry.h
index 4f01252..63aad8a 100644
--- a/src/entry.h
+++ b/src/entry.h
@@ -7,6 +7,7 @@
#include <cairo/cairo.h>
#include <wchar.h>
#include "color.h"
+#include "desktop_vec.h"
#include "history.h"
#include "image.h"
#include "surface.h"
@@ -35,10 +36,12 @@ struct entry {
uint32_t selection;
struct string_vec results;
struct string_vec commands;
+ struct desktop_vec apps;
struct history history;
bool use_pango;
/* Options */
+ bool drun;
bool horizontal;
uint32_t num_results;
int32_t result_spacing;
diff --git a/src/history.c b/src/history.c
index 7e2a84b..95e7760 100644
--- a/src/history.c
+++ b/src/history.c
@@ -13,11 +13,18 @@
static const char *default_state_dir = ".local/state";
static const char *histfile_basename = "tofi-history";
+static const char *drun_histfile_basename = "tofi-drun-history";
[[nodiscard("memory leaked")]]
static struct history history_create(void);
-static char *get_histfile_path() {
+static char *get_histfile_path(bool drun) {
+ const char *basename;
+ if (drun) {
+ basename = drun_histfile_basename;
+ } else {
+ basename = histfile_basename;
+ }
char *histfile_name = NULL;
const char *state_path = getenv("XDG_STATE_HOME");
if (state_path == NULL) {
@@ -28,7 +35,7 @@ static char *get_histfile_path() {
}
size_t len = strlen(home) + 1
+ strlen(default_state_dir) + 1
- + strlen(histfile_basename) + 1;
+ + strlen(basename) + 1;
histfile_name = xmalloc(len);
snprintf(
histfile_name,
@@ -36,25 +43,25 @@ static char *get_histfile_path() {
"%s/%s/%s",
home,
default_state_dir,
- histfile_basename);
+ basename);
} else {
size_t len = strlen(state_path) + 1
- + strlen(histfile_basename) + 1;
+ + strlen(basename) + 1;
histfile_name = xmalloc(len);
snprintf(
histfile_name,
len,
"%s/%s",
state_path,
- histfile_basename);
+ basename);
}
return histfile_name;
}
-struct history history_load()
+struct history history_load(bool drun)
{
struct history vec = history_create();
- char *histfile_name = get_histfile_path();
+ char *histfile_name = get_histfile_path(drun);
if (histfile_name == NULL) {
return vec;
}
@@ -108,9 +115,9 @@ struct history history_load()
return vec;
}
-void history_save(struct history *history)
+void history_save(struct history *history, bool drun)
{
- char *histfile_name = get_histfile_path();
+ char *histfile_name = get_histfile_path(drun);
if (histfile_name == NULL) {
return;
}
diff --git a/src/history.h b/src/history.h
index 6fedecc..199e028 100644
--- a/src/history.h
+++ b/src/history.h
@@ -1,6 +1,7 @@
#ifndef HISTORY_H
#define HISTORY_H
+#include <stdbool.h>
#include <stddef.h>
struct program {
@@ -24,8 +25,8 @@ void history_add(struct history *restrict vec, const char *restrict str);
//void history_remove(struct history *restrict vec, const char *restrict str);
[[nodiscard("memory leaked")]]
-struct history history_load(void);
+struct history history_load(bool drun);
-void history_save(struct history *history);
+void history_save(struct history *history, bool drun);
#endif /* HISTORY_H */
diff --git a/src/main.c b/src/main.c
index 96140f5..f0aaac3 100644
--- a/src/main.c
+++ b/src/main.c
@@ -16,6 +16,7 @@
#include <xkbcommon/xkbcommon.h>
#include "tofi.h"
#include "compgen.h"
+#include "drun.h"
#include "config.h"
#include "entry.h"
#include "image.h"
@@ -160,7 +161,7 @@ static void wl_keyboard_key(
sizeof(buf));
wchar_t ch;
mbtowc(&ch, buf, sizeof(buf));
- if (len > 0 && iswprint(ch) && !iswblank(ch)) {
+ if (len > 0 && iswprint(ch) && (tofi->window.entry.drun || !iswblank(ch))) {
if (entry->input_length < N_ELEM(entry->input) - 1) {
entry->input[entry->input_length] = ch;
entry->input_length++;
@@ -686,6 +687,7 @@ static void usage()
" --hide-cursor <true|false> Hide the cursor.\n"
" --horizontal <true|false> List results horizontally.\n"
" --history <true|false> Sort results by number of usages.\n"
+" --drun-launch <true|false> Launch apps directly in drun mode.\n"
" --hint-font <true|false> Perform font hinting.\n"
" --late-keyboard-init (EXPERIMENTAL) Delay keyboard\n"
" initialisation until after the first\n"
@@ -726,6 +728,7 @@ const struct option long_options[] = {
{"horizontal", required_argument, NULL, 0},
{"hide-cursor", required_argument, NULL, 0},
{"history", required_argument, NULL, 0},
+ {"drun-launch", required_argument, NULL, 0},
{"hint-font", required_argument, NULL, 0},
{"output", required_argument, NULL, 'o'},
{"late-keyboard-init", no_argument, NULL, 'k'},
@@ -1012,6 +1015,7 @@ int main(int argc, char *argv[])
/*
* If we were invoked as tofi-run, generate the command list.
+ * If we were invoked as tofi-drun, generate the desktop app list.
* Otherwise, just read standard input.
*/
if (strstr(argv[0], "-run")) {
@@ -1020,6 +1024,19 @@ int main(int argc, char *argv[])
tofi.window.entry.commands = compgen_cached();
log_unindent();
log_debug("Command list generated.\n");
+ } else if (strstr(argv[0], "-drun")) {
+ log_debug("Generating desktop app list.\n");
+ log_indent();
+ tofi.window.entry.drun = true;
+ struct desktop_vec apps = drun_generate_cached();
+ struct string_vec commands = string_vec_create();
+ for (size_t i = 0; i < apps.count; i++) {
+ string_vec_add(&commands, apps.buf[i].name);
+ }
+ tofi.window.entry.commands = commands;
+ tofi.window.entry.apps = apps;
+ log_unindent();
+ log_debug("App list generated.\n");
} else {
char *line = NULL;
size_t n = 0;
@@ -1035,7 +1052,7 @@ int main(int argc, char *argv[])
tofi.use_history = false;
}
if (tofi.use_history) {
- tofi.window.entry.history = history_load();
+ tofi.window.entry.history = history_load(tofi.window.entry.drun);
compgen_history_sort(&tofi.window.entry.commands, &tofi.window.entry.history);
}
tofi.window.entry.results = string_vec_copy(&tofi.window.entry.commands);
@@ -1187,12 +1204,26 @@ int main(int argc, char *argv[])
tofi.submit = false;
if (tofi.window.entry.results.count > 0) {
uint32_t selection = tofi.window.entry.selection;
- printf("%s\n", tofi.window.entry.results.buf[selection].string);
+ char *res = tofi.window.entry.results.buf[selection].string;
+ if (tofi.window.entry.drun) {
+ struct desktop_entry *app = desktop_vec_find(&tofi.window.entry.apps, res);
+ if (app == NULL) {
+ log_error("Couldn't find application file! This shouldn't happen.\n");
+ } else {
+ res = app->path;
+ }
+ };
+ if (tofi.window.entry.drun && tofi.drun_launch) {
+ drun_launch(res);
+ } else {
+ printf("%s\n", res);
+ }
if (tofi.use_history) {
history_add(
&tofi.window.entry.history,
tofi.window.entry.results.buf[selection].string);
- history_save(&tofi.window.entry.history);
+ history_save(&tofi.window.entry.history,
+ tofi.window.entry.drun);
}
break;
}
@@ -1235,6 +1266,9 @@ int main(int argc, char *argv[])
xkb_keymap_unref(tofi.xkb_keymap);
xkb_context_unref(tofi.xkb_context);
wl_registry_destroy(tofi.wl_registry);
+ if (tofi.window.entry.drun) {
+ desktop_vec_destroy(&tofi.window.entry.apps);
+ }
string_vec_destroy(&tofi.window.entry.commands);
string_vec_destroy(&tofi.window.entry.results);
if (tofi.use_history) {
diff --git a/src/string_vec.c b/src/string_vec.c
index 8806705..c337c61 100644
--- a/src/string_vec.c
+++ b/src/string_vec.c
@@ -79,6 +79,8 @@ void string_vec_add(struct string_vec *restrict vec, const char *restrict str)
vec->buf = xrealloc(vec->buf, vec->size * sizeof(vec->buf[0]));
}
vec->buf[vec->count].string = xstrdup(str);
+ vec->buf[vec->count].search_score = 0;
+ vec->buf[vec->count].history_score = 0;
vec->count++;
}
diff --git a/src/tofi.h b/src/tofi.h
index 7ad202c..4ad9f75 100644
--- a/src/tofi.h
+++ b/src/tofi.h
@@ -64,6 +64,7 @@ struct tofi {
bool hide_cursor;
bool use_history;
bool late_keyboard_init;
+ bool drun_launch;
char target_output_name[MAX_OUTPUT_NAME_LEN];
};