diff options
author | Phil Jones <philj56@gmail.com> | 2021-11-17 01:15:13 +0000 |
---|---|---|
committer | Phil Jones <philj56@gmail.com> | 2021-11-17 01:15:13 +0000 |
commit | 7562d7b539d8013376de2cff494231ba307f4ee1 (patch) | |
tree | 1eb4e8b28ed10da0e7a582cc4a38cfc37cef3ff8 /src | |
parent | 49c7405b6a88e56bb69e12189adb719927343e07 (diff) |
Add sorting by run frequency.
This implements a rofi-like run cache. Other smaller changes include
simplification of resize logic now that there's only one surface.
Diffstat (limited to 'src')
-rw-r--r-- | src/color.c | 8 | ||||
-rw-r--r-- | src/compgen.c | 98 | ||||
-rw-r--r-- | src/compgen.h | 3 | ||||
-rw-r--r-- | src/entry.h | 2 | ||||
-rw-r--r-- | src/history.c | 224 | ||||
-rw-r--r-- | src/history.h | 31 | ||||
-rw-r--r-- | src/main.c | 82 | ||||
-rw-r--r-- | src/string_vec.c | 16 | ||||
-rw-r--r-- | src/string_vec.h | 7 | ||||
-rw-r--r-- | src/surface.c | 4 | ||||
-rw-r--r-- | src/tofi.h | 1 | ||||
-rw-r--r-- | src/xmalloc.c | 26 | ||||
-rw-r--r-- | src/xmalloc.h | 3 |
13 files changed, 378 insertions, 127 deletions
diff --git a/src/color.c b/src/color.c index f6b3275..0ccd5b8 100644 --- a/src/color.c +++ b/src/color.c @@ -40,9 +40,9 @@ struct color hex_to_color(const char *hex) } return (struct color) { - .r = ((val & 0xFF000000u) >> 24) / 255.0f, - .g = ((val & 0x00FF0000u) >> 16) / 255.0f, - .b = ((val & 0x0000FF00u) >> 8) / 255.0f, - .a = ((val & 0x000000FFu) >> 0) / 255.0f, + .r = (float)((val & 0xFF000000u) >> 24) / 255.0f, + .g = (float)((val & 0x00FF0000u) >> 16) / 255.0f, + .b = (float)((val & 0x0000FF00u) >> 8) / 255.0f, + .a = (float)((val & 0x000000FFu) >> 0) / 255.0f, }; } diff --git a/src/compgen.c b/src/compgen.c index 8190a21..f514d66 100644 --- a/src/compgen.c +++ b/src/compgen.c @@ -4,74 +4,25 @@ #include <stdlib.h> #include <string.h> #include <sys/stat.h> +#include "history.h" #include "log.h" #include "string_vec.h" #include "xmalloc.h" -static const char *default_state_dir = ".local/state"; -static const char *histfile_basename = "tofi-history"; - -static char *get_histfile_path() { - char *histfile_name; - const char *state_path = getenv("XDG_STATE_HOME"); - if (state_path == 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_state_dir) + 1 - + strlen(histfile_basename) + 1; - histfile_name = xmalloc(len); - snprintf( - histfile_name, - len, - "%s/%s/%s", - home, - default_state_dir, - histfile_basename); - } else { - size_t len = strlen(state_path) + 1 - + strlen(histfile_basename) + 1; - histfile_name = xmalloc(len); - snprintf( - histfile_name, - len, - "%s/%s", - state_path, - histfile_basename); - } - return histfile_name; - -} - -static void load_history() -{ - char *name = get_histfile_path(); - FILE *histfile = fopen(name, "rb"); - - free(name); - - if (histfile == NULL) { - return; - } - -} - -struct string_vec compgen() +struct string_vec compgen(struct history *history) { - load_history(); log_debug("Retrieving PATH.\n"); const char *env_path = getenv("PATH"); if (env_path == NULL) { log_error("Couldn't retrieve PATH from environment.\n"); exit(EXIT_FAILURE); } + struct string_vec programs = string_vec_create(); - char *path = strdup(env_path); + char *path = xstrdup(env_path); char *saveptr = NULL; char *path_entry = strtok_r(path, ":", &saveptr); + log_debug("Scanning PATH for binaries.\n"); while (path_entry != NULL) { DIR *dir = opendir(path_entry); @@ -96,10 +47,49 @@ struct string_vec compgen() path_entry = strtok_r(NULL, ":", &saveptr); } free(path); + log_debug("Sorting results.\n"); string_vec_sort(&programs); + log_debug("Making unique.\n"); string_vec_uniq(&programs); + + log_debug("Moving already known programs to the front.\n"); + /* + * Remove any programs in our history from the generated list, and + * store which ones we found in to_add. + * Removal is done without changing the count, as we're about to re-add + * them at the front. + */ + struct string_vec to_add = string_vec_create(); + for (size_t i = 0; i < history->count; i++) { + char **res = string_vec_find(&programs, history->buf[i].name); + if (res == NULL) { + continue; + } + free(*res); + *res = NULL; + string_vec_add(&to_add, history->buf[i].name); + } + + /* Sort the vector to push the removed entries to the end. */ + string_vec_sort(&programs); + + /* + * Move the results down by the number of items we want to add. There's + * guaranteed to be enough space to do this, as we just removed that + * many items. + */ + memmove( + &programs.buf[to_add.count], + programs.buf, + (programs.count - to_add.count) * sizeof(programs.buf[0])); + + /* Add our history to the front in order. */ + for (size_t i = 0; i < to_add.count; i++) { + programs.buf[i] = xstrdup(to_add.buf[i]); + } + string_vec_destroy(&to_add); log_debug("Done.\n"); return programs; } diff --git a/src/compgen.h b/src/compgen.h index b3b54b5..502bcb5 100644 --- a/src/compgen.h +++ b/src/compgen.h @@ -1,8 +1,9 @@ #ifndef COMPGEN_H #define COMPGEN_H +#include "history.h" #include "string_vec.h" -struct string_vec compgen(); +struct string_vec compgen(struct history *history); #endif /* COMPGEN_H */ diff --git a/src/entry.h b/src/entry.h index 6d4023f..6efe773 100644 --- a/src/entry.h +++ b/src/entry.h @@ -3,6 +3,7 @@ #include <pango/pangocairo.h> #include "color.h" +#include "history.h" #include "image.h" #include "surface.h" #include "string_vec.h" @@ -30,6 +31,7 @@ struct entry { struct string_vec results; struct string_vec commands; + struct history history; /* Options */ uint32_t font_size; diff --git a/src/history.c b/src/history.c new file mode 100644 index 0000000..8cb9eaf --- /dev/null +++ b/src/history.c @@ -0,0 +1,224 @@ +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include "history.h" +#include "log.h" +#include "xmalloc.h" + +static const char *default_state_dir = ".local/state"; +static const char *histfile_basename = "tofi-history"; + +[[nodiscard]] +static struct history history_create(void); +static bool mkdirp(const char *path); + +static char *get_histfile_path() { + char *histfile_name = NULL; + const char *state_path = getenv("XDG_STATE_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_state_dir) + 1 + + strlen(histfile_basename) + 1; + histfile_name = xmalloc(len); + snprintf( + histfile_name, + len, + "%s/%s/%s", + home, + default_state_dir, + histfile_basename); + } else { + size_t len = strlen(state_path) + 1 + + strlen(histfile_basename) + 1; + histfile_name = xmalloc(len); + snprintf( + histfile_name, + len, + "%s/%s", + state_path, + histfile_basename); + } + return histfile_name; + +} + +struct history history_load() +{ + struct history vec = history_create(); + char *histfile_name = get_histfile_path(); + if (histfile_name == NULL) { + return vec; + } + + FILE *histfile = fopen(histfile_name, "rb"); + free(histfile_name); + + if (histfile == NULL) { + return vec; + } + + if (fseek(histfile, 0, SEEK_END) != 0) { + log_error("Error seeking in history file: %s.\n", strerror(errno)); + fclose(histfile); + return vec; + } + size_t len = ftell(histfile); + if (fseek(histfile, 0, SEEK_SET) != 0) { + log_error("Error seeking in history file: %s.\n", strerror(errno)); + fclose(histfile); + return vec; + } + + char *buf = xmalloc(len); + fread(buf, 1, len, histfile); + fclose(histfile); + + char *saveptr = NULL; + char *tok = strtok_r(buf, " ", &saveptr); + while (tok != NULL) { + size_t run_count = strtoull(tok, NULL, 10); + tok = strtok_r(NULL, "\n", &saveptr); + if (tok == NULL) { + break; + } + history_add(&vec, tok); + vec.buf[vec.count - 1].run_count = run_count; + tok = strtok_r(NULL, " ", &saveptr); + } + + free(buf); + return vec; +} + +void history_save(struct history *history) +{ + char *histfile_name = get_histfile_path(); + if (histfile_name == NULL) { + return; + } + + /* Create the path if necessary. */ + if (!mkdirp(histfile_name)) { + return; + } + + /* Use open rather than fopen to ensure the proper permissions. */ + int histfd = open(histfile_name, O_WRONLY | O_CREAT, 0600); + FILE *histfile = fdopen(histfd, "wb"); + if (histfile == NULL) { + return; + } + + for (size_t i = 0; i < history->count; i++) { + fprintf(histfile, "%zu %s\n", history->buf[i].run_count, history->buf[i].name); + } + + fclose(histfile); + free(histfile_name); +} + +struct history history_create(void) +{ + struct history vec = { + .count = 0, + .size = 16, + .buf = xcalloc(16, sizeof(struct program)) + }; + return vec; +} + +void history_destroy(struct history *restrict vec) +{ + for (size_t i = 0; i < vec->count; i++) { + free(vec->buf[i].name); + } + free(vec->buf); +} + +void history_add(struct history *restrict vec, const char *restrict str) +{ + /* + * If the program's already in our vector, just increment the count and + * move the program up if needed. + */ + for (size_t i = 0; i < vec->count; i++) { + if (!strcmp(vec->buf[i].name, str)) { + vec->buf[i].run_count++; + size_t count = vec->buf[i].run_count; + if (i > 0 && count <= vec->buf[i-1].run_count) { + return; + } + /* We need to move the program up the list */ + size_t j = i; + while (j > 0 && count > vec->buf[j-1].run_count) { + j--; + } + struct program tmp = vec->buf[i]; + memmove(&vec->buf[j+1], &vec->buf[j], (i - j) * sizeof(struct program)); + vec->buf[j] = tmp; + return; + } + } + + /* Otherwise add it to the end with a run count of 1 */ + if (vec->count == vec->size) { + vec->size *= 2; + vec->buf = xrealloc(vec->buf, vec->size * sizeof(vec->buf[0])); + } + vec->buf[vec->count].name = xstrdup(str); + vec->buf[vec->count].run_count = 1; + vec->count++; +} + +void history_remove(struct history *restrict vec, const char *restrict str) +{ + for (size_t i = 0; i < vec->count; i++) { + if (!strcmp(vec->buf[i].name, str)) { + free(vec->buf[i].name); + if (i < vec->count - 1) { + memmove(&vec->buf[i], &vec->buf[i+1], (vec->count - i) * sizeof(struct program)); + } + vec->count--; + return; + } + } +} + +bool mkdirp(const char *path) +{ + struct stat statbuf; + if (stat(path, &statbuf) == 0) { + /* If the history file exists, we don't need to do anything. */ + return true; + } + + /* + * Walk down the path, creating directories as we go. + * This works by repeatedly finding the next / in path, then calling + * mkdir() on the string up to that point. + */ + char *tmp = xstrdup(path); + char *cursor = tmp; + while ((cursor = strchr(cursor + 1, '/')) != NULL) { + *cursor = '\0'; + log_debug("Creating directory %s\n", tmp); + if (mkdir(tmp, 0700) != 0 && errno != EEXIST) { + log_error("Error creating history file path: %s.\n", strerror(errno)); + free(tmp); + return false; + } + *cursor = '/'; + } + free(tmp); + return true; +} diff --git a/src/history.h b/src/history.h new file mode 100644 index 0000000..3be757f --- /dev/null +++ b/src/history.h @@ -0,0 +1,31 @@ +#ifndef HISTORY_H +#define HISTORY_H + +#include <stddef.h> + +struct program { + char *restrict name; + size_t run_count; +}; + +struct history { + size_t count; + size_t size; + struct program *buf; +}; + +[[gnu::nonnull]] +void history_destroy(struct history *restrict vec); + +[[gnu::nonnull]] +void history_add(struct history *restrict vec, const char *restrict str); + +//[[gnu::nonnull]] +//void history_remove(struct history *restrict vec, const char *restrict str); + +[[nodiscard]] +struct history history_load(void); + +void history_save(struct history *history); + +#endif /* HISTORY_H */ @@ -29,32 +29,6 @@ #undef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) - -static void resize(struct tofi *tofi) -{ - struct surface *surface = &tofi->window.surface; - - /* - * Resize the main window. - * EGL wants actual pixel width / height, so we have to scale the - * values provided by Wayland. - */ - surface->width = tofi->window.width * tofi->window.scale; - surface->height = tofi->window.height * tofi->window.scale; - - /* - * Need to redraw the background at the new size. This entails a - * wl_surface_commit, so no need to do so explicitly here. - */ - tofi->window.surface.redraw = true; - - /* - * Center the entry. - * Wayland wants "surface-local" width / height, so we have to divide - * the entry's pixel size by the scale factor. - */ -} - static void zwlr_layer_surface_configure( void *data, struct zwlr_layer_surface_v1 *zwlr_layer_surface, @@ -72,7 +46,22 @@ static void zwlr_layer_surface_configure( if (width != tofi->window.width || height != tofi->window.height) { tofi->window.width = width; tofi->window.height = height; - tofi->window.resize = true; + + /* + * Resize the main window. + * EGL wants actual pixel width / height, so we have to scale the + * values provided by Wayland. + */ + tofi->window.surface.width = + tofi->window.width * tofi->window.scale; + tofi->window.surface.height = + tofi->window.height * tofi->window.scale; + + /* + * Need to redraw the background at the new size. This entails a + * wl_surface_commit, so no need to do so explicitly here. + */ + tofi->window.surface.redraw = true; } zwlr_layer_surface_v1_ack_configure( tofi->window.zwlr_layer_surface, @@ -202,8 +191,7 @@ static void wl_keyboard_key( ) { tofi->closed = true; - } else if (entry->input_length > 0 - && (sym == XKB_KEY_Return || sym == XKB_KEY_KP_Enter)) { + } else if (sym == XKB_KEY_Return || sym == XKB_KEY_KP_Enter) { tofi->submit = true; return; } @@ -535,7 +523,6 @@ static void usage() "Usage: tofi [options]\n" " -u, --user=NAME The user to login as.\n" " -c, --command=COMMAND The command to run on login.\n" -" -b, --background-image=PATH An image to use as the background.\n" " -B, --background-color=COLOR Color of the background.\n" " -o, --outline-width=VALUE Width of the border outlines in pixels.\n" " -O, --outline-color=COLOR Color of the border outlines.\n" @@ -565,7 +552,7 @@ int main(int argc, char *argv[]) .username = "nobody", .command = "false", .window = { - .background_color = {0.89, 0.8, 0.824, 1.0}, + .background_color = {0.89f, 0.8f, 0.824f, 1.0f}, .scale = 1, .width = 640, .height = 480, @@ -574,20 +561,21 @@ int main(int argc, char *argv[]) .border = { .width = 6, .outline_width = 2, - .color = {0.976, 0.149, 0.447, 1.0}, - .outline_color = {0.031, 0.031, 0.0, 1.0}, + .color = {0.976f, 0.149f, 0.447f, 1.0f}, + .outline_color = {0.031f, 0.031f, 0.0f, 1.0f}, }, .font_name = "Sans Bold", .font_size = 24, .padding = 8, .num_characters = 12, - .background_color = {0.106, 0.114, 0.118, 1.0}, - .foreground_color = {1.0, 1.0, 1.0, 1.0} + .background_color = {0.106f, 0.114f, 0.118f, 1.0f}, + .foreground_color = {1.0f, 1.0f, 1.0f, 1.0f} } } }; - tofi.window.entry.commands = compgen(); + tofi.window.entry.history = history_load(); + tofi.window.entry.commands = compgen(&tofi.window.entry.history); tofi.window.entry.results = string_vec_copy(&tofi.window.entry.commands); @@ -790,9 +778,6 @@ int main(int argc, char *argv[]) wl_display_roundtrip(tofi.wl_display); log_debug("Third roundtrip done.\n"); - /* Call resize() just to center the entry properly. */ - resize(&tofi); - /* * Initialise the Pango & Cairo structures for rendering the entry. * Cairo needs to know the size of the surface it's creating, and @@ -825,21 +810,13 @@ int main(int argc, char *argv[]) &tofi.window.entry.background_color, &tofi.window.entry.image); - /* - * We've just rendered everything and resized, so we don't need to do - * it again right now. - */ - tofi.window.resize = false; + /* We've just rendered, so we don't need to do it again right now. */ tofi.window.surface.redraw = false; while (wl_display_dispatch(tofi.wl_display) != -1) { if (tofi.closed) { break; } - if (tofi.window.resize) { - resize(&tofi); - tofi.window.resize = false; - } if (tofi.window.surface.redraw) { surface_draw( &tofi.window.surface, @@ -848,10 +825,15 @@ int main(int argc, char *argv[]) tofi.window.surface.redraw = false; } if (tofi.submit) { + tofi.submit = false; if (tofi.window.entry.results.count > 0) { printf("%s\n", tofi.window.entry.results.buf[0]); + history_add( + &tofi.window.entry.history, + tofi.window.entry.results.buf[0]); + history_save(&tofi.window.entry.history); + break; } - break; } } @@ -879,6 +861,8 @@ int main(int argc, char *argv[]) xkb_keymap_unref(tofi.xkb_keymap); xkb_context_unref(tofi.xkb_context); wl_registry_destroy(tofi.wl_registry); + string_vec_destroy(&tofi.window.entry.commands); + history_destroy(&tofi.window.entry.history); #endif /* * For release builds, skip straight to display disconnection and quit. diff --git a/src/string_vec.c b/src/string_vec.c index d3ac9fe..3c77f6f 100644 --- a/src/string_vec.c +++ b/src/string_vec.c @@ -1,4 +1,5 @@ -#define _GNU_SOURCE +#define _GNU_SOURCE /* Required for strcasecmp */ +#include <stdint.h> #include <stdlib.h> #include <string.h> #include "string_vec.h" @@ -7,8 +8,8 @@ static int cmpstringp(const void *restrict a, const void *restrict b) { /* - * We receive pointers to the array elements (which are pointers to - * char), so convert and dereference them for comparison. + * For qsort we receive pointers to the array elements (which are + * pointers to char), so convert and dereference them for comparison. */ const char *restrict str1 = *(const char **)a; const char *restrict str2 = *(const char **)b; @@ -52,7 +53,7 @@ struct string_vec string_vec_copy(struct string_vec *restrict vec) }; for (size_t i = 0; i < vec->count; i++) { - copy.buf[i] = strdup(vec->buf[i]); + copy.buf[i] = xstrdup(vec->buf[i]); } return copy; @@ -64,7 +65,7 @@ void string_vec_add(struct string_vec *restrict vec, const char *restrict str) vec->size *= 2; vec->buf = xrealloc(vec->buf, vec->size * sizeof(vec->buf[0])); } - vec->buf[vec->count] = strdup(str); + vec->buf[vec->count] = xstrdup(str); vec->count++; } @@ -87,6 +88,11 @@ void string_vec_uniq(struct string_vec *restrict vec) vec->count = count; } +char **string_vec_find(struct string_vec *restrict vec, const char * str) +{ + return bsearch(&str, vec->buf, vec->count, sizeof(vec->buf[0]), cmpstringp); +} + struct string_vec string_vec_filter( const struct string_vec *restrict vec, const char *restrict substr) diff --git a/src/string_vec.h b/src/string_vec.h index 7ebcb53..a5f00d5 100644 --- a/src/string_vec.h +++ b/src/string_vec.h @@ -3,11 +3,6 @@ #include <stddef.h> -struct program { - char *name; - size_t run_count; -}; - struct string_vec { size_t count; size_t size; @@ -27,6 +22,8 @@ void string_vec_sort(struct string_vec *restrict vec); void string_vec_uniq(struct string_vec *restrict vec); +char **string_vec_find(struct string_vec *restrict vec, const char *str); + [[nodiscard]] struct string_vec string_vec_filter( const struct string_vec *restrict vec, diff --git a/src/surface.c b/src/surface.c index cbc6c94..8052d5e 100644 --- a/src/surface.c +++ b/src/surface.c @@ -44,8 +44,8 @@ void surface_draw( (double)surface->width / texture->width, (double)surface->height / texture->height ); - uint32_t width = (uint32_t)(scale * texture->width); - uint32_t height = (uint32_t)(scale * texture->height); + int32_t width = (int32_t)(scale * texture->width); + int32_t height = (int32_t)(scale * texture->height); int32_t x = -((int32_t)width - (int32_t)surface->width) / 2; int32_t y = -((int32_t)height - (int32_t)surface->height) / 2; @@ -40,7 +40,6 @@ struct tofi { uint32_t width; uint32_t height; uint32_t scale; - bool resize; } window; /* Keyboard state */ diff --git a/src/xmalloc.c b/src/xmalloc.c index 8a08cb8..1c2a9dc 100644 --- a/src/xmalloc.c +++ b/src/xmalloc.c @@ -1,4 +1,5 @@ #include <stdio.h> +#include <string.h> #include "log.h" #include "xmalloc.h" @@ -7,10 +8,10 @@ void *xmalloc(size_t size) void *ptr = malloc(size); if (ptr != NULL) { - log_debug("Allocated %zu bytes.\n", size); + //log_debug("Allocated %zu bytes.\n", size); return ptr; } else { - fputs("Out of memory, exiting.", stderr); + log_error("Out of memory, exiting.\n"); exit(EXIT_FAILURE); } } @@ -20,10 +21,10 @@ void *xcalloc(size_t nmemb, size_t size) void *ptr = calloc(nmemb, size); if (ptr != NULL) { - log_debug("Allocated %zux%zu bytes.\n", nmemb, size); + //log_debug("Allocated %zux%zu bytes.\n", nmemb, size); return ptr; } else { - fputs("Out of memory, exiting.", stderr); + log_error("Out of memory, exiting.\n"); exit(EXIT_FAILURE); } } @@ -33,10 +34,23 @@ void *xrealloc(void *ptr, size_t size) ptr = realloc(ptr, size); if (ptr != NULL) { - log_debug("Reallocated to %zu bytes.\n", size); + //log_debug("Reallocated to %zu bytes.\n", size); return ptr; } else { - fputs("Out of memory, exiting.", stderr); + log_error("Out of memory, exiting.\n"); + exit(EXIT_FAILURE); + } +} + +char *xstrdup(const char *s) +{ + char *ptr = strdup(s); + + if (ptr != NULL) { + //log_debug("Allocated %zu bytes.\n", size); + return ptr; + } else { + log_error("Out of memory, exiting.\n"); exit(EXIT_FAILURE); } } diff --git a/src/xmalloc.h b/src/xmalloc.h index a661004..747509c 100644 --- a/src/xmalloc.h +++ b/src/xmalloc.h @@ -11,4 +11,7 @@ void *xcalloc(size_t nmemb, size_t size); void *xrealloc(void *ptr, size_t size); +__attribute__((malloc)) +char *xstrdup(const char *s); + #endif /* XMALLOC_H */ |