summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhil Jones <philj56@gmail.com>2022-06-21 19:14:47 +0100
committerPhil Jones <philj56@gmail.com>2022-06-21 19:14:47 +0100
commit25b2c9f895fd91fb556809ce513d3681ad279938 (patch)
tree6fa7595308fb26e2e7d61bba95836be496b7557b
parent54e90f75203bbf40ec6ac6dd09227e2c6f97cc62 (diff)
Add config file handling.
Single letter style arguments have been removed.
-rw-r--r--meson.build3
-rw-r--r--src/compgen.c2
-rw-r--r--src/config.c392
-rw-r--r--src/config.h9
-rw-r--r--src/entry.h5
-rw-r--r--src/entry_backend/harfbuzz.c4
-rw-r--r--src/entry_backend/pango.c2
-rw-r--r--src/history.c2
-rw-r--r--src/history.h2
-rw-r--r--src/log.c35
-rw-r--r--src/log.h4
-rw-r--r--src/main.c297
-rw-r--r--src/string_vec.c1
-rw-r--r--src/string_vec.h4
-rw-r--r--src/tofi.h6
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);
diff --git a/src/log.c b/src/log.c
index 97dcdbe..b564a49 100644
--- a/src/log.c
+++ b/src/log.c
@@ -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)
{
diff --git a/src/log.h b/src/log.h
index 93765ec..4f06c87 100644
--- a/src/log.h
+++ b/src/log.h
@@ -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 */
diff --git a/src/main.c b/src/main.c
index 7d677cd..be5719c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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);
diff --git a/src/tofi.h b/src/tofi.h
index 519dcf6..63d9e6b 100644
--- a/src/tofi.h
+++ b/src/tofi.h
@@ -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 */