diff options
-rw-r--r-- | meson.build | 3 | ||||
-rw-r--r-- | src/compgen.c | 2 | ||||
-rw-r--r-- | src/config.c | 392 | ||||
-rw-r--r-- | src/config.h | 9 | ||||
-rw-r--r-- | src/entry.h | 5 | ||||
-rw-r--r-- | src/entry_backend/harfbuzz.c | 4 | ||||
-rw-r--r-- | src/entry_backend/pango.c | 2 | ||||
-rw-r--r-- | src/history.c | 2 | ||||
-rw-r--r-- | src/history.h | 2 | ||||
-rw-r--r-- | src/log.c | 35 | ||||
-rw-r--r-- | src/log.h | 4 | ||||
-rw-r--r-- | src/main.c | 297 | ||||
-rw-r--r-- | src/string_vec.c | 1 | ||||
-rw-r--r-- | src/string_vec.h | 4 | ||||
-rw-r--r-- | src/tofi.h | 6 |
15 files changed, 568 insertions, 200 deletions
diff --git a/meson.build b/meson.build index b4b203a..4292a3a 100644 --- a/meson.build +++ b/meson.build @@ -43,7 +43,7 @@ add_project_arguments( #'-Wconversion', '-Wshadow', '-Wno-unused-parameter', - '-D_XOPEN_SOURCE=700', + '-D_GNU_SOURCE', '-D_FORTIFY_SOURCE=2', ], language: 'c' @@ -53,6 +53,7 @@ common_sources = files( 'src/main.c', 'src/color.c', 'src/compgen.c', + 'src/config.c', 'src/entry.c', 'src/history.c', 'src/log.c', diff --git a/src/compgen.c b/src/compgen.c index df04386..84cd323 100644 --- a/src/compgen.c +++ b/src/compgen.c @@ -15,7 +15,7 @@ static const char *default_state_dir = ".cache"; static const char *cache_basename = "tofi-compgen"; -[[nodiscard]] +[[nodiscard("memory leaked")]] static char *get_cache_path() { char *cache_name = NULL; const char *state_path = getenv("XDG_CACHE_HOME"); diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..f1bf915 --- /dev/null +++ b/src/config.c @@ -0,0 +1,392 @@ +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include "tofi.h" +#include "color.h" +#include "config.h" +#include "log.h" +#include "nelem.h" +#include "xmalloc.h" + +/* Maximum number of config file errors before we give up */ +#define MAX_ERRORS 5 + +/* Anyone with a 10M config file is doing something very wrong */ +#define MAX_CONFIG_SIZE (10*1024*1024) + +static char *strip(const char *str); +static bool parse_option(struct tofi *tofi, size_t lineno, const char *option, const char *value); +static char *get_config_path(void); + +static int parse_anchor(size_t lineno, const char *str, bool *err); +static bool parse_bool(size_t lineno, const char *str, bool *err); +static struct color parse_color(size_t lineno, const char *str, bool *err); +static uint32_t parse_uint32(size_t lineno, const char *str, bool *err); +static int32_t parse_int32(size_t lineno, const char *str, bool *err); + +/* + * Function-like macro. Yuck. + */ +#define PARSE_ERROR_NO_ARGS(lineno, fmt) \ + if ((lineno) > 0) {\ + log_error("\tLine %zu: ", (lineno));\ + log_append_error((fmt)); \ + } else {\ + log_error((fmt)); \ + } + +#define PARSE_ERROR(lineno, fmt, ...) \ + if ((lineno) > 0) {\ + log_error("\tLine %zu: ", (lineno));\ + log_append_error((fmt), __VA_ARGS__); \ + } else {\ + log_error((fmt), __VA_ARGS__); \ + } + +void config_load(struct tofi *tofi, const char *filename) +{ + char *default_filename = NULL; + if (!filename) { + default_filename = get_config_path(); + if (!default_filename) { + return; + } + filename = default_filename; + } + char *config; + FILE *fp = fopen(filename, "rb"); + if (!fp) { + if (!default_filename || errno != ENOENT) { + log_error("Failed to open config file %s: %s\n", filename, strerror(errno)); + } + goto CLEANUP_FILENAME; + } + if (fseek(fp, 0, SEEK_END)) { + log_error("Failed to seek in config file: %s\n", strerror(errno)); + fclose(fp); + goto CLEANUP_FILENAME; + } + size_t size; + { + long ssize = ftell(fp); + if (ssize < 0) { + log_error("Failed to determine config file size: %s\n", strerror(errno)); + fclose(fp); + goto CLEANUP_FILENAME; + } + if (ssize > MAX_CONFIG_SIZE) { + log_error("Config file too big (> %d MiB)! Are you sure it's a file?\n", MAX_CONFIG_SIZE / 1024 / 1024); + fclose(fp); + goto CLEANUP_FILENAME; + } + size = (size_t)ssize; + } + config = xmalloc(size + 1); + if (!config) { + log_error("Failed to malloc buffer for config file.\n"); + fclose(fp); + goto CLEANUP_FILENAME; + } + rewind(fp); + if (fread(config, 1, size, fp) != size) { + log_error("Failed to read config file: %s\n", strerror(errno)); + fclose(fp); + goto CLEANUP_CONFIG; + } + fclose(fp); + config[size] = '\0'; + + char *config_copy = strdup(config); + if (!config_copy) { + log_error("Failed to malloc second buffer for config file.\n"); + goto CLEANUP_ALL; + } + + log_debug("Loading config file %s.\n", filename); + + char *saveptr1 = NULL; + char *saveptr2 = NULL; + + char *copy_pos = config_copy; + size_t lineno = 1; + size_t num_errs = 0; + for (char *str1 = config; ; str1 = NULL, saveptr2 = NULL) { + if (num_errs > MAX_ERRORS) { + log_error("Too many config file errors (>%u), giving up.\n", MAX_ERRORS); + break; + } + char *line = strtok_r(str1, "\r\n", &saveptr1); + if (!line) { + /* We're done here */ + break; + } + while ((copy_pos - config_copy) < (line - config)) { + if (*copy_pos == '\n') { + lineno++; + } + copy_pos++; + } + { + char *line_stripped = strip(line); + if (!line_stripped) { + /* Skip blank lines */ + continue; + } + char first_char = line_stripped[0]; + free(line_stripped); + /* + * Comment characters. + * N.B. treating section headers as comments for now. + */ + switch (first_char) { + case '#': + case ';': + case '[': + continue; + } + } + if (line[0] == '=') { + PARSE_ERROR_NO_ARGS(lineno, "Missing option.\n"); + num_errs++; + continue; + } + char *option = strtok_r(line, "=", &saveptr2); + if (!option) { + char *tmp = strip(line); + PARSE_ERROR(lineno, "Config option \"%s\" missing value.\n", tmp); + num_errs++; + free(tmp); + continue; + } + char *option_stripped = strip(option); + if (!option_stripped) { + PARSE_ERROR_NO_ARGS(lineno, "Missing option.\n"); + num_errs++; + continue; + } + char *value = strtok_r(NULL, "#;\r\n", &saveptr2); + if (!value) { + PARSE_ERROR(lineno, "Config option \"%s\" missing value.\n", option_stripped); + num_errs++; + free(option_stripped); + continue; + } + char *value_stripped = strip(value); + if (!value_stripped) { + PARSE_ERROR(lineno, "Config option \"%s\" missing value.\n", option_stripped); + num_errs++; + free(option_stripped); + continue; + } + if (!parse_option(tofi, lineno, option_stripped, value_stripped)) { + num_errs++; + } + + /* Cleanup */ + free(value_stripped); + free(option_stripped); + } + +CLEANUP_ALL: + free(config_copy); +CLEANUP_CONFIG: + free(config); +CLEANUP_FILENAME: + if (default_filename) { + free(default_filename); + } +} + +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; + } + size_t len = end - start + 1; + char *buf = xcalloc(len + 1, 1); + strncpy(buf, str + start, len); + buf[len] = '\0'; + return buf; +} + +bool parse_option(struct tofi *tofi, size_t lineno, const char *option, const char *value) +{ + bool err = false; + if (strcasecmp(option, "anchor") == 0) { + tofi->anchor = parse_anchor(lineno, value, &err); + } else if (strcasecmp(option, "background-color") == 0) { + tofi->window.entry.background_color = parse_color(lineno, value, &err); + } else if (strcasecmp(option, "corner-radius") == 0) { + tofi->window.entry.corner_radius = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "entry-padding") == 0) { + tofi->window.entry.padding = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "entry-color") == 0) { + tofi->window.entry.background_color = parse_color(lineno, value, &err); + } else if (strcasecmp(option, "font-name") == 0) { + if (tofi->window.entry.font_name) { + free(tofi->window.entry.font_name); + } + tofi->window.entry.font_name = strdup(value); + } else if (strcasecmp(option, "font-size") == 0) { + tofi->window.entry.padding = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "num-results") == 0) { + tofi->window.entry.num_results = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "outline-width") == 0) { + tofi->window.entry.border.outline_width = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "outline-color") == 0) { + tofi->window.entry.border.outline_color = parse_color(lineno, value, &err); + } else if (strcasecmp(option, "prompt-text") == 0) { + if (tofi->window.entry.prompt_text) { + free(tofi->window.entry.prompt_text); + } + tofi->window.entry.prompt_text = strdup(value); + } else if (strcasecmp(option, "result-padding") == 0) { + tofi->window.entry.result_padding = parse_int32(lineno, value, &err); + } else if (strcasecmp(option, "border-width") == 0) { + tofi->window.entry.border.width = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "border-color") == 0) { + tofi->window.entry.border.color = parse_color(lineno, value, &err); + } else if (strcasecmp(option, "text-color") == 0) { + tofi->window.entry.foreground_color = parse_color(lineno, value, &err); + } else if (strcasecmp(option, "width") == 0) { + tofi->window.width = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "height") == 0) { + tofi->window.height = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "margin-top") == 0) { + tofi->window.margin_top = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "margin-bottom") == 0) { + tofi->window.margin_bottom = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "margin-left") == 0) { + tofi->window.margin_left = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "margin-right") == 0) { + tofi->window.margin_right = parse_uint32(lineno, value, &err); + } else if (strcasecmp(option, "horizontal") == 0) { + tofi->window.entry.horizontal = parse_bool(lineno, value, &err); + } else if (strcasecmp(option, "hide-cursor") == 0) { + tofi->hide_cursor = parse_bool(lineno, value, &err); + } else { + PARSE_ERROR(lineno, "Bad config file option \"%s\"\n", option); + err = true; + } + return !err; +} + +void apply_option(struct tofi *tofi, const char *option, const char *value) +{ + parse_option(tofi, 0, option, value); +} + +char *get_config_path() +{ + char *base_dir = getenv("XDG_CONFIG_HOME"); + char *ext = ""; + size_t len = strlen("/tofi/config") + 1; + if (!base_dir) { + base_dir = getenv("HOME"); + ext = "/.config"; + if (!base_dir) { + log_error("Couldn't find XDG_CONFIG_HOME or HOME envvars\n"); + return NULL; + } + } + len += strlen(base_dir) + strlen(ext) + 2; + char *name = xcalloc(len, sizeof(*name)); + snprintf(name, len, "%s%s%s", base_dir, ext, "/tofi/config"); + return name; +} + +bool parse_bool(size_t lineno, const char *str, bool *err) +{ + if (strcasecmp(str, "true") == 0) { + return true; + } else if (strcasecmp(str, "false") == 0) { + return false; + } + PARSE_ERROR(lineno, "Invalid boolean value \"%s\".\n", str); + if (err) { + *err = true; + } + return false; +} + +int parse_anchor(size_t lineno, const char *str, bool *err) +{ + if(strcasecmp(str, "top-left") == 0) { + return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP + | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; + } + if (strcasecmp(str, "top") == 0) { + return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; + } + if (strcasecmp(str, "top-right") == 0) { + return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP + | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + } + if (strcasecmp(str, "right") == 0) { + return ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + } + if (strcasecmp(str, "bottom-right") == 0) { + return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM + | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + } + if (strcasecmp(str, "bottom") == 0) { + return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + } + if (strcasecmp(str, "bottom-left") == 0) { + return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM + | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; + } + if (strcasecmp(str, "left") == 0) { + return ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; + } + if (strcasecmp(str, "center") == 0) { + return 0; + } + PARSE_ERROR(lineno, "Invalid anchor \"%s\".\n", str); + return 0; +} + +struct color parse_color(size_t lineno, const char *str, bool *err) +{ + return hex_to_color(str); +} + +uint32_t parse_uint32(size_t lineno, const char *str, bool *err) +{ + errno = 0; + char *endptr; + uint32_t ret = strtoul(str, &endptr, 0); + if (endptr == str) { + PARSE_ERROR(lineno, "Failed to parse \"%s\" as unsigned int.\n", str); + } else if (errno) { + PARSE_ERROR(lineno, "Unsigned int value \"%s\" out of range.\n", str); + } + return ret; +} + +int32_t parse_int32(size_t lineno, const char *str, bool *err) +{ + errno = 0; + char *endptr; + uint32_t ret = strtol(str, &endptr, 0); + if (endptr == str) { + PARSE_ERROR(lineno, "Failed to parse \"%s\" as int.\n", str); + } else if (errno) { + PARSE_ERROR(lineno, "Int value \"%s\" out of range.\n", str); + } + return ret; +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..65e4f4d --- /dev/null +++ b/src/config.h @@ -0,0 +1,9 @@ +#ifndef TOFI_CONFIG_H +#define TOFI_CONFIG_H + +#include "tofi.h" + +void config_load(struct tofi *tofi, const char *filename); +void apply_option(struct tofi *tofi, const char *option, const char *value); + +#endif /* TOFI_CONFIG_H */ diff --git a/src/entry.h b/src/entry.h index f450daa..1c919dc 100644 --- a/src/entry.h +++ b/src/entry.h @@ -8,6 +8,7 @@ #endif #include <cairo/cairo.h> +#include <wchar.h> #include "color.h" #include "history.h" #include "image.h" @@ -39,8 +40,8 @@ struct entry { bool horizontal; uint32_t num_results; uint32_t font_size; - const char *font_name; - const char *prompt_text; + char *font_name; + char *prompt_text; uint32_t corner_radius; uint32_t padding; int32_t result_padding; diff --git a/src/entry_backend/harfbuzz.c b/src/entry_backend/harfbuzz.c index f2e2d26..dc479d3 100644 --- a/src/entry_backend/harfbuzz.c +++ b/src/entry_backend/harfbuzz.c @@ -1,7 +1,6 @@ #include <cairo/cairo.h> #include <harfbuzz/hb-ft.h> #include <math.h> -#include <wchar.h> #include "harfbuzz.h" #include "../entry.h" #include "../log.h" @@ -149,7 +148,7 @@ void entry_backend_init( cairo_set_font_face(cr, entry->backend.cairo_face); cairo_set_font_size(cr, entry->font_size * PT_TO_DPI); cairo_font_options_t *opts = cairo_font_options_create(); - cairo_font_options_set_hint_style(opts, CAIRO_HINT_STYLE_NONE); + //cairo_font_options_set_hint_style(opts, CAIRO_HINT_STYLE_NONE); cairo_set_font_options(cr, opts); @@ -186,6 +185,7 @@ void entry_backend_destroy(struct entry *entry) { hb_buffer_destroy(entry->backend.hb_buffer); hb_font_destroy(entry->backend.hb_font); + cairo_font_face_destroy(entry->backend.cairo_face); FT_Done_Face(entry->backend.ft_face); FT_Done_FreeType(entry->backend.ft_library); } diff --git a/src/entry_backend/pango.c b/src/entry_backend/pango.c index d063147..d5dc8b4 100644 --- a/src/entry_backend/pango.c +++ b/src/entry_backend/pango.c @@ -2,7 +2,6 @@ #include <glib.h> #include <pango/pangocairo.h> #include <pango/pango.h> -#include <wchar.h> #include "../entry.h" #include "../log.h" #include "../nelem.h" @@ -16,6 +15,7 @@ void entry_backend_init(struct entry *entry, uint32_t *width, uint32_t *height, PangoContext *context = pango_cairo_create_context(cr); log_debug("Creating Pango font description.\n"); + log_debug("%s\n", entry->font_name); PangoFontDescription *font_description = pango_font_description_from_string(entry->font_name); pango_font_description_set_size( diff --git a/src/history.c b/src/history.c index 94d3f75..4438b5d 100644 --- a/src/history.c +++ b/src/history.c @@ -14,7 +14,7 @@ static const char *default_state_dir = ".local/state"; static const char *histfile_basename = "tofi-history"; -[[nodiscard]] +[[nodiscard("memory leaked")]] static struct history history_create(void); static char *get_histfile_path() { diff --git a/src/history.h b/src/history.h index 3be757f..6fedecc 100644 --- a/src/history.h +++ b/src/history.h @@ -23,7 +23,7 @@ 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]] +[[nodiscard("memory leaked")]] struct history history_load(void); void history_save(struct history *history); @@ -95,6 +95,41 @@ void log_info(const char *const fmt, ...) va_end(args); } +void log_append_error(const char *const fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + +void log_append_warning(const char *const fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + +void log_append_debug(const char *const fmt, ...) +{ +#ifndef DEBUG + return; +#endif + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + +void log_append_info(const char *const fmt, ...) +{ + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + struct timespec time_diff(struct timespec cur, struct timespec old) { @@ -7,5 +7,9 @@ void log_error(const char *const fmt, ...); void log_warning(const char *const fmt, ...); void log_debug(const char *const fmt, ...); void log_info(const char *const fmt, ...); +void log_append_error(const char *const fmt, ...); +void log_append_warning(const char *const fmt, ...); +void log_append_debug(const char *const fmt, ...); +void log_append_info(const char *const fmt, ...); #endif /* LOG_H */ @@ -15,6 +15,7 @@ #include <xkbcommon/xkbcommon.h> #include "tofi.h" #include "compgen.h" +#include "config.h" #include "entry.h" #include "image.h" #include "log.h" @@ -25,33 +26,6 @@ #undef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) -static int get_anchor(int anchor) -{ - switch (anchor) { - case 1: - return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP - | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; - case 2: - return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; - case 3: - return ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP - | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - case 4: - return ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - case 5: - return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM - | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - case 6: - return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - case 7: - return ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM - | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; - case 8: - return ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; - } - return 0; -} - static void zwlr_layer_surface_configure( void *data, struct zwlr_layer_surface_v1 *zwlr_layer_surface, @@ -569,21 +543,110 @@ static void usage() { fprintf(stderr, "Usage: tofi [options]\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" -" -r, --border-width=VALUE Width of the border in pixels.\n" -" -R, --border-color=COLOR Color of the border.\n" -" -e, --entry-padding=VALUE Padding around the entry box in pixels.\n" -" -E, --entry-color=COLOR Color of the entry box.\n" -" -f, --font-name=NAME Font to use.\n" -" -F, --font-size=VALUE Point size of text.\n" -" -T, --text-color=COLOR Color of text.\n" -" -H, --hide-cursor Hide the cursor.\n" -" -h, --help Print this message and exit.\n" +" -h, --help Print this message and exit.\n" +" -c, --config Specify a config file.\n" +" --font-name <name|path> Font to use.\n" +" --font-size <pt> Point size of text.\n" +" --background-color <color> Color of the background.\n" +" --outline-width <px> Width of the border outlines.\n" +" --outline-color <color> Color of the border outlines.\n" +" --border-width <px> Width of the border.\n" +" --border-color <color> Color of the border.\n" +" --entry-padding <px> Padding around the entry box.\n" +" --entry-color <color> Color of the entry box.\n" +" --text-color <color> Color of text.\n" +" --prompt-text <string> Prompt text.\n" +" --num-results <n> Maximum number of results to display.\n" +" --result-padding <px> Spacing between results. Can be negative.\n" +" --width <px> Width of the window.\n" +" --height <px> Height of the window.\n" +" --anchor <position> Location on screen to anchor window.\n" +" --margin-top <px> Offset from top of screen.\n" +" --margin-bottom <px> Offset from bottom of screen.\n" +" --margin-left <px> Offset from left of screen.\n" +" --margin-right <px> Offset from right of screen.\n" +" --hide-cursor <true|false> Hide the cursor.\n" ); } +static void parse_args(struct tofi *tofi, int argc, char *argv[]) +{ + /* Option parsing with getopt. */ + struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"config", required_argument, NULL, 'c'}, + {"anchor", required_argument, NULL, 0}, + {"background-color", required_argument, NULL, 0}, + {"corner-radius", required_argument, NULL, 0}, + {"entry-padding", required_argument, NULL, 0}, + {"entry-color", required_argument, NULL, 0}, + {"font-name", required_argument, NULL, 0}, + {"font-size", required_argument, NULL, 0}, + {"num-results", required_argument, NULL, 0}, + {"outline-width", required_argument, NULL, 0}, + {"outline-color", required_argument, NULL, 0}, + {"prompt-text", required_argument, NULL, 0}, + {"result-padding", required_argument, NULL, 0}, + {"border-width", required_argument, NULL, 0}, + {"border-color", required_argument, NULL, 0}, + {"text-color", required_argument, NULL, 0}, + {"width", required_argument, NULL, 0}, + {"height", required_argument, NULL, 0}, + {"margin-top", required_argument, NULL, 0}, + {"margin-bottom", required_argument, NULL, 0}, + {"margin-left", required_argument, NULL, 0}, + {"margin-right", required_argument, NULL, 0}, + {"layout-horizontal", required_argument, NULL, 0}, + {"hide-cursor", required_argument, NULL, 0}, + {NULL, 0, NULL, 0} + }; + const char *short_options = "hc:"; + + bool load_default_config = true; + int option_index = 0; + + /* First pass, just check for config file, help, and errors. */ + int opt = getopt_long(argc, argv, short_options, long_options, &option_index); + while (opt != -1) { + if (opt == 'h') { + usage(); + exit(EXIT_SUCCESS); + } else if (opt == 'c') { + config_load(tofi, optarg); + load_default_config = false; + } else if (opt == ':') { + log_error("Option %s requires an argument.\n", argv[optind - 1]); + usage(); + exit(EXIT_FAILURE); + } else if (opt == '?') { + log_error("Unknown option %s.\n", argv[optind - 1]); + usage(); + exit(EXIT_FAILURE); + } + opt = getopt_long(argc, argv, short_options, long_options, &option_index); + } + if (load_default_config) { + config_load(tofi, NULL); + } + optind = 1; + + /* Second pass, parse everything else. */ + opt = getopt_long(argc, argv, short_options, long_options, &option_index); + while (opt != -1) { + if (opt == 0) { + apply_option(tofi, long_options[option_index].name, optarg); + } + opt = getopt_long(argc, argv, short_options, long_options, &option_index); + } + //if (optind < argc) { + // log_error( + // "Unexpected non-option argument '%s'.\n", + // argv[optind]); + // usage(); + // exit(EXIT_FAILURE); + //} +} + int main(int argc, char *argv[]) { /* @@ -606,9 +669,9 @@ int main(int argc, char *argv[]) .color = {0.976f, 0.149f, 0.447f, 1.0f}, .outline_color = {0.031f, 0.031f, 0.0f, 1.0f}, }, - .font_name = "Sans Bold", + .font_name = strdup("Sans Bold"), .font_size = 24, - .prompt_text = "run: ", + .prompt_text = strdup("run: "), .num_results = 5, .padding = 8, .background_color = {0.106f, 0.114f, 0.118f, 1.0f}, @@ -626,147 +689,7 @@ int main(int argc, char *argv[]) log_unindent(); log_debug("Command list generated.\n"); - - /* Option parsing with getopt. */ - struct option long_options[] = { - {"anchor", required_argument, NULL, 'a'}, - {"background-color", required_argument, NULL, 'B'}, - {"corner-radius", required_argument, NULL, 'c'}, - {"entry-padding", required_argument, NULL, 'e'}, - {"entry-color", required_argument, NULL, 'E'}, - {"font-name", required_argument, NULL, 'f'}, - {"font-size", required_argument, NULL, 'F'}, - {"num-results", required_argument, NULL, 'n'}, - {"outline-width", required_argument, NULL, 'o'}, - {"outline-color", required_argument, NULL, 'O'}, - {"prompt-text", required_argument, NULL, 'p'}, - {"result-padding", required_argument, NULL, 'P'}, - {"border-width", required_argument, NULL, 'r'}, - {"border-color", required_argument, NULL, 'R'}, - {"text-color", required_argument, NULL, 'T'}, - {"width", required_argument, NULL, 'X'}, - {"height", required_argument, NULL, 'Y'}, - {"x-offset", required_argument, NULL, 'x'}, - {"y-offset", required_argument, NULL, 'y'}, - {"layout-horizontal", no_argument, NULL, 'l'}, - {"hide-cursor", no_argument, NULL, 'H'}, - {"help", no_argument, NULL, 'h'}, - {NULL, 0, NULL, 0} - }; - const char *short_options = ":a:B:c:e:E:f:F:hHln:o:O:p:P:r:R:T:x:X:y:Y:"; - - int opt = getopt_long(argc, argv, short_options, long_options, NULL); - while (opt != -1) { - switch (opt) { - case 'a': - tofi.anchor = - get_anchor(strtol(optarg, NULL, 0)); - break; - case 'B': - tofi.window.background_color = - hex_to_color(optarg); - break; - case 'c': - tofi.window.entry.corner_radius = - strtoul(optarg, NULL, 0); - break; - case 'r': - tofi.window.entry.border.width = - strtoul(optarg, NULL, 0); - break; - case 'R': - tofi.window.entry.border.color = - hex_to_color(optarg); - break; - case 'o': - tofi.window.entry.border.outline_width = - strtoul(optarg, NULL, 0); - break; - case 'O': - tofi.window.entry.border.outline_color = - hex_to_color(optarg); - break; - case 'e': - tofi.window.entry.padding = - strtoul(optarg, NULL, 0); - break; - case 'E': - tofi.window.entry.background_color = - hex_to_color(optarg); - break; - case 'T': - tofi.window.entry.foreground_color = - hex_to_color(optarg); - break; - case 'f': - tofi.window.entry.font_name = optarg; - break; - case 'F': - tofi.window.entry.font_size = - strtoul(optarg, NULL, 0); - break; - case 'H': - tofi.hide_cursor = true; - break; - case 'p': - tofi.window.entry.prompt_text = optarg; - break; - case 'P': - tofi.window.entry.result_padding = - strtol(optarg, NULL, 0); - break; - case 'n': - tofi.window.entry.num_results = - strtoul(optarg, NULL, 0); - break; - case 'X': - tofi.window.width = - strtoul(optarg, NULL, 0); - break; - case 'x': - tofi.window.x = - strtol(optarg, NULL, 0); - break; - case 'Y': - tofi.window.height = - strtoul(optarg, NULL, 0); - break; - case 'y': - tofi.window.y = - strtol(optarg, NULL, 0); - break; - case 'l': - tofi.window.entry.horizontal = true; - break; - case 'h': - usage(); - exit(EXIT_SUCCESS); - break; - case ':': - log_error( - "Option %s requires an argument.\n", - argv[optind - 1]); - usage(); - exit(EXIT_FAILURE); - break; - case '?': - log_error( - "Unknown option %s.\n", - argv[optind - 1]); - usage(); - exit(EXIT_FAILURE); - break; - } - opt = getopt_long(argc, argv, short_options, long_options, NULL); - } - if (optind < argc) { - log_error( - "Unexpected non-option argument '%s'.\n", - argv[optind]); - usage(); - exit(EXIT_FAILURE); - } - + parse_args(&tofi, argc, argv); /* * Initial Wayland & XKB setup. @@ -853,10 +776,10 @@ int main(int argc, char *argv[]) tofi.window.height); zwlr_layer_surface_v1_set_margin( tofi.window.zwlr_layer_surface, - tofi.window.x, - tofi.window.y, - tofi.window.x, - tofi.window.y); + tofi.window.margin_top, + tofi.window.margin_right, + tofi.window.margin_bottom, + tofi.window.margin_left); wl_surface_commit(tofi.window.surface.wl_surface); /* @@ -980,6 +903,8 @@ int main(int argc, char *argv[]) string_vec_destroy(&tofi.window.entry.commands); string_vec_destroy(&tofi.window.entry.results); history_destroy(&tofi.window.entry.history); + free(tofi.window.entry.font_name); + free(tofi.window.entry.prompt_text); #endif /* * For release builds, skip straight to display disconnection and quit. diff --git a/src/string_vec.c b/src/string_vec.c index 82b86e1..8318df6 100644 --- a/src/string_vec.c +++ b/src/string_vec.c @@ -1,4 +1,3 @@ -#define _GNU_SOURCE /* Required for strcasestr */ #include <stdint.h> #include <stdio.h> #include <stdlib.h> diff --git a/src/string_vec.h b/src/string_vec.h index 0537873..44fd5fe 100644 --- a/src/string_vec.h +++ b/src/string_vec.h @@ -10,7 +10,7 @@ struct string_vec { char **buf; }; -[[nodiscard]] +[[nodiscard("memory leaked")]] struct string_vec string_vec_create(void); void string_vec_destroy(struct string_vec *restrict vec); @@ -25,7 +25,7 @@ void string_vec_uniq(struct string_vec *restrict vec); char **string_vec_find(struct string_vec *restrict vec, const char *str); -[[nodiscard]] +[[nodiscard("memory leaked")]] struct string_vec string_vec_filter( const struct string_vec *restrict vec, const char *restrict substr); @@ -34,8 +34,10 @@ struct tofi { uint32_t width; uint32_t height; uint32_t scale; - int32_t x; - int32_t y; + int32_t margin_top; + int32_t margin_bottom; + int32_t margin_left; + int32_t margin_right; } window; /* Keyboard state */ |