commits
tags
#include "config.h"
#include "theme.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
static const char *FONT_KEY_NAMES[FONT_COUNT] = {
"font_sans_regular", "font_sans_bold", "font_sans_italic", "font_sans_bold_italic",
"font_mono_regular", "font_mono_bold", "font_mono_italic", "font_mono_bold_italic",
};
#define DEFAULT_LANGUAGE "sv"
static char *dup_str(const char *s) {
size_t n = strlen(s);
char *copy = malloc(n + 1);
memcpy(copy, s, n + 1);
return copy;
}
static void config_set_defaults(Config *cfg) {
cfg->textColor = DEFAULT_COL_TEXT;
cfg->bgColor = DEFAULT_COL_BG;
for (int i = 0; i < FONT_COUNT; i++) cfg->fontPaths[i] = NULL;
cfg->language = dup_str(DEFAULT_LANGUAGE);
cfg->zoom = ZOOM_DEFAULT;
keymap_set_defaults(cfg->keymap);
}
void config_free(Config *cfg) {
for (int i = 0; i < FONT_COUNT; i++) {
free(cfg->fontPaths[i]);
cfg->fontPaths[i] = NULL;
}
free(cfg->language);
cfg->language = NULL;
}
static const char *trim(const char *s, int len, int *outLen) {
while (len > 0 && isspace((unsigned char)s[0])) { s++; len--; }
while (len > 0 && isspace((unsigned char)s[len - 1])) len--;
*outLen = len;
return s;
}
static bool parse_hex_byte(const char *s, unsigned char *out) {
unsigned char v = 0;
for (int i = 0; i < 2; i++) {
char c = s[i];
v <<= 4;
if (c >= '0' && c <= '9') v |= (unsigned char)(c - '0');
else if (c >= 'a' && c <= 'f') v |= (unsigned char)(c - 'a' + 10);
else if (c >= 'A' && c <= 'F') v |= (unsigned char)(c - 'A' + 10);
else return false;
}
*out = v;
return true;
}
static bool parse_hex_color(const char *v, int len, Clay_Color *out) {
if (len > 0 && v[0] == '#') { v++; len--; }
if (len != 6) return false;
unsigned char r, g, b;
if (!parse_hex_byte(v, &r) || !parse_hex_byte(v + 2, &g) || !parse_hex_byte(v + 4, &b)) return false;
out->r = r; out->g = g; out->b = b; out->a = 255;
return true;
}
static bool parse_float(const char *v, int len, float *out) {
char buf[64];
if (len <= 0 || len >= (int)sizeof buf) return false;
memcpy(buf, v, (size_t)len);
buf[len] = '\0';
char *end;
float f = strtof(buf, &end);
if (end != buf + len) return false; /* trailing garbage, e.g. "1.0x" */
*out = f;
return true;
}
bool config_parse_line(const char *line, int len, Config *cfg) {
int trimmedLen;
const char *t = trim(line, len, &trimmedLen);
if (trimmedLen == 0 || t[0] == '#') return false;
const char *eq = memchr(t, '=', (size_t)trimmedLen);
if (!eq) return false;
int keyLen;
const char *key = trim(t, (int)(eq - t), &keyLen);
int valueLen;
const char *value = trim(eq + 1, (int)(t + trimmedLen - (eq + 1)), &valueLen);
if (keyLen == 0 || valueLen == 0) return false;
if (keyLen == 10 && memcmp(key, "text_color", 10) == 0) return parse_hex_color(value, valueLen, &cfg->textColor);
if (keyLen == 8 && memcmp(key, "bg_color", 8) == 0) return parse_hex_color(value, valueLen, &cfg->bgColor);
if (keyLen == 8 && memcmp(key, "language", 8) == 0) {
free(cfg->language);
cfg->language = malloc((size_t)valueLen + 1);
memcpy(cfg->language, value, (size_t)valueLen);
cfg->language[valueLen] = '\0';
return true;
}
if (keyLen == 4 && memcmp(key, "zoom", 4) == 0) {
float z;
if (!parse_float(value, valueLen, &z)) return false;
if (z < ZOOM_MIN) z = ZOOM_MIN;
if (z > ZOOM_MAX) z = ZOOM_MAX;
cfg->zoom = z;
return true;
}
for (int i = 0; i < FONT_COUNT; i++) {
size_t nameLen = strlen(FONT_KEY_NAMES[i]);
if ((size_t)keyLen == nameLen && memcmp(key, FONT_KEY_NAMES[i], nameLen) == 0) {
free(cfg->fontPaths[i]);
cfg->fontPaths[i] = malloc((size_t)valueLen + 1);
memcpy(cfg->fontPaths[i], value, (size_t)valueLen);
cfg->fontPaths[i][valueLen] = '\0';
return true;
}
}
if (keyLen > 4 && memcmp(key, "key.", 4) == 0) {
return keymap_parse_config_line(key, keyLen, value, valueLen, cfg->keymap);
}
return false;
}
static bool build_config_path(char *buf, size_t bufSize) {
const char *xdg = getenv("XDG_CONFIG_HOME");
if (xdg && xdg[0]) { snprintf(buf, bufSize, "%s/hush/config", xdg); return true; }
const char *home = getenv("HOME");
if (!home || !home[0]) return false;
snprintf(buf, bufSize, "%s/.config/hush/config", home);
return true;
}
/* Creates every directory component of `path` (a file path; only the directory portion is
created). Tolerates components that already exist. */
static void mkdir_parents(const char *path) {
char buf[1024];
size_t n = strlen(path);
if (n >= sizeof buf) return;
memcpy(buf, path, n + 1);
for (size_t i = 1; i < n; i++) {
if (buf[i] != '/') continue;
buf[i] = '\0';
mkdir(buf, 0755);
buf[i] = '/';
}
}
static void write_default_config(const char *path) {
mkdir_parents(path);
FILE *f = fopen(path, "wb");
if (!f) return;
fprintf(f,
"# hush config file\n"
"# Uncomment and edit any line below to override the default. Unknown or malformed\n"
"# lines are ignored, so it's safe to experiment.\n"
"\n"
"# Language for the status bar and the quit/shortcuts dialogs. See assets/lang/ next to\n"
"# the hush binary for available codes (or add your own -- copy en.lang, translate it,\n"
"# name it <code>.lang, and set that code here).\n"
"# language = " DEFAULT_LANGUAGE "\n"
"\n"
"# text_color = #%02X%02X%02X\n"
"# bg_color = #%02X%02X%02X\n"
"\n"
"# Starting zoom level (Ctrl++/Ctrl+- still adjust it during the session; Ctrl+0 always\n"
"# resets to 100%% regardless of this). Range 0.5-3.0.\n"
"# zoom = 1.0\n"
"\n"
"# Font overrides (paths to .ttf files). Relative paths are resolved relative to the\n"
"# directory hush is launched from. Leave commented out to use the bundled DejaVu fonts.\n"
"# font_sans_regular = /path/to/font.ttf\n"
"# font_sans_bold = /path/to/font-bold.ttf\n"
"# font_sans_italic = /path/to/font-italic.ttf\n"
"# font_sans_bold_italic = /path/to/font-bold-italic.ttf\n"
"# font_mono_regular = /path/to/mono.ttf\n"
"# font_mono_bold = /path/to/mono-bold.ttf\n"
"# font_mono_italic = /path/to/mono-italic.ttf\n"
"# font_mono_bold_italic = /path/to/mono-bold-italic.ttf\n"
"\n"
"# Keyboard shortcuts. Each is \"modifier+modifier+key\", where modifiers are any of\n"
"# ctrl/shift/alt (in any order) and key is a single letter or digit, f1-f12, punctuation\n"
"# (e.g. \"/\" or \"slash\"), or a name like enter/escape/tab/space/home/end/up/down/left/\n"
"# right/pageup/pagedown/insert. A numpad fallback (for zoom) and F1 (for help) always\n"
"# work too, regardless of what's set here, so there's no way to lock yourself out.\n"
"# key.save = ctrl+s\n"
"# key.open = ctrl+o\n"
"# key.copy = ctrl+c\n"
"# key.paste = ctrl+v\n"
"# key.undo = ctrl+z\n"
"# key.redo = ctrl+shift+z\n"
"# key.quit = ctrl+x\n"
"# key.help = ctrl+/\n"
"# key.source_view = ctrl+m\n"
"# key.zoom_in = ctrl++\n"
"# key.zoom_out = ctrl+-\n"
"# key.zoom_reset = ctrl+0\n",
(unsigned)DEFAULT_COL_TEXT.r, (unsigned)DEFAULT_COL_TEXT.g, (unsigned)DEFAULT_COL_TEXT.b,
(unsigned)DEFAULT_COL_BG.r, (unsigned)DEFAULT_COL_BG.g, (unsigned)DEFAULT_COL_BG.b);
fclose(f);
}
bool config_load(Config *out) {
config_set_defaults(out);
char path[1024];
if (!build_config_path(path, sizeof path)) return true;
FILE *f = fopen(path, "rb");
if (!f) {
write_default_config(path);
return true;
}
char line[1024];
while (fgets(line, sizeof line, f)) {
int len = (int)strlen(line);
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) len--;
config_parse_line(line, len, out);
}
fclose(f);
return true;
}