diff options
| author | Lena <lena@omega> | 2026-01-01 00:00:00 +0000 |
|---|---|---|
| committer | Lena <lena@omega> | 2026-01-01 00:00:00 +0000 |
| commit | 2fa72ee0dc67d973330aec4ba98260e9f57f7821 (patch) | |
| tree | e6a25c07c8da61d29e4e1f8a72436565e63c5777 /config.c | |
| download | mouseboard-2fa72ee0dc67d973330aec4ba98260e9f57f7821.tar.gz | |
Keyboard-only pointing: a labelled grid over every monitor, type a
label to jump to a cell, bisect it by quadrant until the crosshair is
exact, then one key clicks. The coarse grid keeps labels short and
bisection keeps precision unlimited, so a hotkey and a few keystrokes
replace the mouse.
The core talks only to the backend interface in platform.h; wlroots
Wayland and Win32 backends supply the overlay, keyboard grab and
pointer.
Diffstat (limited to 'config.c')
| -rw-r--r-- | config.c | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/config.c b/config.c new file mode 100644 index 0000000..4b930df --- /dev/null +++ b/config.c @@ -0,0 +1,273 @@ +/* config.c - defaults, config file parsing, and shared key/value sets. */ +#include "config.h" + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +void config_defaults(struct config *c) +{ + /* Home row first, then the rest of the top/bottom rows: common cells + * get the easiest two-key labels. */ + strcpy(c->alphabet, "fjdkslaghrueiwoqptyvbcnxmz"); + c->cell_size = 100; + c->cols = 0; + c->rows = 0; + c->font_scale = 0; + + c->opacity_bg = 33; /* how dark the screen goes (100 = solid) */ + c->opacity_fg = 65; /* labels, grid and crosshair */ + + /* Full-alpha colours (grid deliberately fainter); opacity_bg/fg + * scale them once at startup. */ + c->color_bg = 0xff101014; /* near-black backdrop */ + c->color_grid = 0x40ffffff; /* faint white grid lines */ + c->color_label = 0xffffffff; /* white labels */ + c->color_typed = 0xff52a8ff; /* blue for the typed prefix */ + c->color_active = 0xffff5252; /* red refine box + crosshair */ + + c->k_tl = 'u'; + c->k_tr = 'i'; + c->k_bl = 'j'; + c->k_br = 'k'; + c->k_left = 'm'; + c->k_middle = ','; + c->k_right = '.'; + c->k_double = ';'; + c->k_drag = 'v'; +} + +static int parse_color(const char *s, uint32_t *out) +{ + if (*s == '#') + s++; + size_t n = strlen(s); + if (n != 6 && n != 8) + return -1; + for (size_t i = 0; i < n; i++) + if (!isxdigit((unsigned char)s[i])) + return -1; + + unsigned long v = strtoul(s, NULL, 16); + uint32_t r, g, b, a; + if (n == 6) { + r = (v >> 16) & 0xff; + g = (v >> 8) & 0xff; + b = v & 0xff; + a = 0xff; + } else { + r = (v >> 24) & 0xff; + g = (v >> 16) & 0xff; + b = (v >> 8) & 0xff; + a = v & 0xff; + } + *out = (a << 24) | (r << 16) | (g << 8) | b; + return 0; +} + +static int parse_int(const char *s, int *out) +{ + char *end; + errno = 0; + long v = strtol(s, &end, 10); + if (errno || end == s || *end || v < 0 || v > 100000) + return -1; + *out = (int)v; + return 0; +} + +/* A character the user can actually type at the grid: printable ASCII, no + * space. Uppercase is rejected because incoming keys are lowercased (so Caps + * Lock cannot break labels) - an uppercase binding would never fire. */ +static int typeable(unsigned char ch) +{ + return ch > 0x20 && ch < 0x7f && !(ch >= 'A' && ch <= 'Z'); +} + +int config_set(struct config *c, const char *key, const char *val) +{ + if (!strcmp(key, "alphabet")) { + if (strlen(val) < 2 || strlen(val) >= sizeof c->alphabet) + return -2; + for (size_t i = 0; val[i]; i++) { + if (!typeable((unsigned char)val[i])) + return -2; + if (strchr(val + i + 1, val[i])) + return -2; /* duplicate char breaks labels */ + } + strcpy(c->alphabet, val); + return 0; + } + + if (!strcmp(key, "opacity_bg")) { + int v; + if (parse_int(val, &v) || v > 100) + return -2; + c->opacity_bg = v; + return 0; + } + if (!strcmp(key, "opacity_fg")) { + int v; + if (parse_int(val, &v) || v > 100) + return -2; + c->opacity_fg = v; + return 0; + } + + if (!strcmp(key, "cell_size")) + return parse_int(val, &c->cell_size) ? -2 : 0; + if (!strcmp(key, "cols")) + return parse_int(val, &c->cols) ? -2 : 0; + if (!strcmp(key, "rows")) + return parse_int(val, &c->rows) ? -2 : 0; + if (!strcmp(key, "font_scale")) + return parse_int(val, &c->font_scale) ? -2 : 0; + + if (!strcmp(key, "color_bg")) + return parse_color(val, &c->color_bg) ? -2 : 0; + if (!strcmp(key, "color_grid")) + return parse_color(val, &c->color_grid) ? -2 : 0; + if (!strcmp(key, "color_label")) + return parse_color(val, &c->color_label) ? -2 : 0; + if (!strcmp(key, "color_typed")) + return parse_color(val, &c->color_typed) ? -2 : 0; + if (!strcmp(key, "color_active")) + return parse_color(val, &c->color_active) ? -2 : 0; + + /* Single-character key bindings. */ + struct { const char *name; char *slot; } keys[] = { + { "refine_tl", &c->k_tl }, { "refine_tr", &c->k_tr }, + { "refine_bl", &c->k_bl }, { "refine_br", &c->k_br }, + { "click_left", &c->k_left }, { "click_middle", &c->k_middle }, + { "click_right", &c->k_right }, { "click_double", &c->k_double }, + { "drag", &c->k_drag }, + }; + for (size_t i = 0; i < sizeof keys / sizeof keys[0]; i++) { + if (strcmp(key, keys[i].name)) + continue; + if (strlen(val) != 1 || !typeable((unsigned char)val[0])) + return -2; + *keys[i].slot = val[0]; + return 0; + } + + return -1; +} + +const char *config_validate(const struct config *c) +{ + if (c->cell_size < 1) + return "cell_size must be greater than zero"; + if ((c->cols > 0) != (c->rows > 0)) + return "cols and rows must be set together"; + if (c->cols > MB_MAX_GRID_DIM || c->rows > MB_MAX_GRID_DIM) + return "cols and rows must not exceed 200"; + if (c->font_scale > MB_MAX_FONT_SCALE) + return "font_scale must not exceed 64"; + + const char keys[] = { + c->k_tl, c->k_tr, c->k_bl, c->k_br, c->k_left, + c->k_middle, c->k_right, c->k_double, c->k_drag, + }; + for (size_t i = 0; i < sizeof keys; i++) + for (size_t j = i + 1; j < sizeof keys; j++) + if (keys[i] == keys[j]) + return "refine and action keys must be unique"; + return NULL; +} + +void config_apply_opacity(struct config *c) +{ + uint32_t *fg[] = { &c->color_grid, &c->color_label, + &c->color_typed, &c->color_active }; + uint32_t a = (c->color_bg >> 24) * (uint32_t)c->opacity_bg / 100; + c->color_bg = (a << 24) | (c->color_bg & 0x00ffffff); + for (size_t i = 0; i < sizeof fg / sizeof fg[0]; i++) { + a = (*fg[i] >> 24) * (uint32_t)c->opacity_fg / 100; + *fg[i] = (a << 24) | (*fg[i] & 0x00ffffff); + } +} + +static char *trim(char *s) +{ + while (*s && isspace((unsigned char)*s)) + s++; + char *e = s + strlen(s); + while (e > s && isspace((unsigned char)e[-1])) + *--e = 0; + return s; +} + +int config_load_file(struct config *c, const char *path) +{ + FILE *f = fopen(path, "r"); + if (!f) { + if (errno == ENOENT) + return -1; + fprintf(stderr, "%s: %s\n", path, strerror(errno)); + return -2; + } + + char line[256]; + int lineno = 0, rc = 0; + while (fgets(line, sizeof line, f)) { + lineno++; + size_t len = strlen(line); + if (len && line[len - 1] != '\n' && !feof(f)) { + int ch; + while ((ch = fgetc(f)) != '\n' && ch != EOF) + ; + fprintf(stderr, "%s:%d: line too long\n", path, lineno); + rc = -2; + continue; + } + /* Comments are whole lines starting with '#'; this keeps '#' + * usable inside values (colours are #RRGGBB). */ + char *body = trim(line); + if (!*body || *body == '#') + continue; + char *eq = strchr(body, '='); + if (!eq) { + fprintf(stderr, "%s:%d: missing '='\n", path, lineno); + rc = -2; + continue; + } + *eq = 0; + char *key = trim(body); + char *val = trim(eq + 1); + int r = config_set(c, key, val); + if (r == -1) + fprintf(stderr, "%s:%d: unknown key '%s'\n", path, + lineno, key); + else if (r == -2) + fprintf(stderr, "%s:%d: bad value for '%s'\n", path, + lineno, key); + if (r) + rc = -2; + } + fclose(f); + return rc; +} + +const char *config_default_path(void) +{ + static char path[512]; + const char *xdg = getenv("XDG_CONFIG_HOME"); + if (xdg && *xdg) { + snprintf(path, sizeof path, "%s/mouseboard/config", xdg); + return path; + } + /* Windows sets APPDATA, not HOME; fopen accepts the mixed slashes. */ + const char *appdata = getenv("APPDATA"); + if (appdata && *appdata) { + snprintf(path, sizeof path, "%s/mouseboard/config", appdata); + return path; + } + const char *home = getenv("HOME"); + if (!home) + home = ""; + snprintf(path, sizeof path, "%s/.config/mouseboard/config", home); + return path; +} |