aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md18
-rw-r--r--config.c84
-rw-r--r--ini.c328
-rw-r--r--ini.h189
4 files changed, 614 insertions, 5 deletions
diff --git a/README.md b/README.md
index 16a8fb4..cb4b4c0 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,21 @@ Usage: just like dmenu
## FAQ
Q: What is an ENU?
+## Configuration
+Create your config file at `XDG_CONFIG_HOME/swenu/conf.ini` or `HOME/.config/swenu/conf.ini`.
+Example config file:
+```ini
+fancy_scroll = false
+min_width = 500
+font_size = 16
+
+[colors]
+text_color = (0.85, 0.85, 0.85, 1.0)
+highlight_color = (0.1, 0.3, 0.7, 1.0)
+background_color = (0.1, 0.1, 0.1, 1.0)
+cursor_color = (0.9, 0.9, 0.9, 0.5)
+```
+
## Todo -
- [x] "selection"
- [x] figure out whats going on with stderr and exit code
@@ -28,9 +43,10 @@ Q: What is an ENU?
- [x] emacs keybindings
- [x] "prompt"
- [x] fuzzy filter
-- [ ] config file
+- [x] config file
- [ ] scrolling in input
- [ ] unicode + text shaping
Stretch:
+- [ ] clean up the code (src folder, rename some files)
- [ ] input fluff (icons, descriptions, output names)
diff --git a/config.c b/config.c
index 698f691..94fd63d 100644
--- a/config.c
+++ b/config.c
@@ -1,35 +1,111 @@
#include "config.h"
+#include "ini.h"
+#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
+#include <regex.h>
config_t config = {
+ // default config
.fancy_scroll = false,
.min_width = 500,
.font_size = 16,
- // colors
.text_color = {0.85, 0.85, 0.85, 1.0},
.highlight_color = {0.1, 0.3, 0.7, 1.0},
.background_color = {0.1, 0.1, 0.1, 1.0},
.cursor_color = {0.9, 0.9, 0.9, 0.5}
};
-
+// get config file
+const char* conf_file_name = "/swenu/conf.ini";
char* get_conf_file() {
// get from xdg config home
const char* xdg = getenv("XDG_CONFIG_HOME");
if (xdg && *xdg) {
+ char* conf_file = NULL;
+ asprintf(&conf_file, "%s%s", xdg, conf_file_name);
+ return conf_file;
}
// get from home
const char* home = getenv("HOME");
if (home && *home) {
+ char* conf_file = NULL;
+ asprintf(&conf_file, "%s/.config%s", home, conf_file_name);
+ return conf_file;
}
return NULL;
}
+// bool parse helper
+bool parse_bool(const char* value) {
+ if (strcmp(value, "true") == 0 || strcmp(value, "t") == 0 || strcmp(value, "True") == 0 || strcmp(value, "T") == 0 || strcmp(value, "TRUE") == 0 || strcmp(value, "yes") == 0) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// color parse helper
+regex_t color_regex;
+const char *color_regex_pattern = "^\\([[:space:]]*([[:digit:].]*)[[:space:]]*,[[:space:]]*([[:digit:].]*)[[:space:]]*,[[:space:]]*([[:digit:].]*)[[:space:]]*,[[:space:]]*([[:digit:].]*)[[:space:]]*\\)$"; // this is fucking evil
+void parse_color(const char* value, color_t* color) {
+ regmatch_t matches[5]; // full match + 4 capture groups
+ if (regexec(&color_regex, value, 5, matches, 0) == 0) {
+ for (int i = 1; i <= 4; i++) {
+ size_t len = matches[i].rm_eo - matches[i].rm_so;
+ char match[len + 1];
+ memcpy(match, value + matches[i].rm_so, len);
+ match[len] = '\0';
+ *((float*)color + i - 1) = atof(match);
+ }
+ }
+}
+
+static int our_ini_handler(void* user, const char* section, const char* name,
+ const char* value)
+{
+ config_t* config = (config_t*)user;
+
+ #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
+ if (MATCH("", "fancy_scroll")) {
+ config->fancy_scroll = parse_bool(value);
+ } else if (MATCH("", "min_width")) {
+ config->min_width = atoi(value);
+ } else if (MATCH("", "font_size")) {
+ config->font_size = atoi(value);
+ } else if (MATCH("colors", "text_color")) {
+ parse_color(value, &config->text_color);
+ } else if (MATCH("colors", "highlight_color")) {
+ parse_color(value, &config->highlight_color);
+ } else if (MATCH("colors", "background_color")) {
+ parse_color(value, &config->background_color);
+ } else if (MATCH("colors", "cursor_color")) {
+ parse_color(value, &config->cursor_color);
+ } else {
+ return 0; /* unknown section/name, error */
+ }
+ return 1;
+}
+
void init_conf() {
- /* const char* conf_dir = get_conf_file(); */
+ // compile color regex
+ if (regcomp(&color_regex, color_regex_pattern, REG_EXTENDED) != 0) {
+ fprintf(stderr, "Color regex compile failed\n");
+ }
+
+ // get config dir
+ char* conf_dir = get_conf_file();
+ if (!conf_dir) return;
+
+ // parse config file
+ int ini_res = ini_parse(conf_dir, our_ini_handler, &config);
+ if (ini_res > 0) {
+ fprintf(stderr, "Error parsing config file (%s), on line %d\n", conf_dir, ini_res);
+ }
- /* free(conf_dir); */
+ free(conf_dir);
+ regfree(&color_regex);
}
diff --git a/ini.c b/ini.c
new file mode 100644
index 0000000..08333cf
--- /dev/null
+++ b/ini.c
@@ -0,0 +1,328 @@
+/* inih -- simple .INI file parser
+
+SPDX-License-Identifier: BSD-3-Clause
+
+Copyright (C) 2009-2025, Ben Hoyt
+
+inih is released under the New BSD license (see LICENSE.txt). Go to the project
+home page for more info:
+
+https://github.com/benhoyt/inih
+
+*/
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "ini.h"
+
+#if !INI_USE_STACK
+#if INI_CUSTOM_ALLOCATOR
+#include <stddef.h>
+void* ini_malloc(size_t size);
+void ini_free(void* ptr);
+void* ini_realloc(void* ptr, size_t size);
+#else
+#include <stdlib.h>
+#define ini_malloc malloc
+#define ini_free free
+#define ini_realloc realloc
+#endif
+#endif
+
+#define MAX_SECTION 50
+#define MAX_NAME 50
+
+/* Used by ini_parse_string() to keep track of string parsing state. */
+typedef struct {
+ const char* ptr;
+ size_t num_left;
+} ini_parse_string_ctx;
+
+/* Strip whitespace chars off end of given string, in place. end must be a
+ pointer to the NUL terminator at the end of the string. Return s. */
+static char* ini_rstrip(char* s, char* end)
+{
+ while (end > s && isspace((unsigned char)(*--end)))
+ *end = '\0';
+ return s;
+}
+
+/* Return pointer to first non-whitespace char in given string. */
+static char* ini_lskip(const char* s)
+{
+ while (*s && isspace((unsigned char)(*s)))
+ s++;
+ return (char*)s;
+}
+
+/* Return pointer to first char (of chars) or inline comment in given string,
+ or pointer to NUL at end of string if neither found. Inline comment must
+ be prefixed by a whitespace character to register as a comment. */
+static char* ini_find_chars_or_comment(const char* s, const char* chars)
+{
+#if INI_ALLOW_INLINE_COMMENTS
+ int was_space = 0;
+ while (*s && (!chars || !strchr(chars, *s)) &&
+ !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
+ was_space = isspace((unsigned char)(*s));
+ s++;
+ }
+#else
+ while (*s && (!chars || !strchr(chars, *s))) {
+ s++;
+ }
+#endif
+ return (char*)s;
+}
+
+/* Similar to strncpy, but ensures dest (size bytes) is
+ NUL-terminated, and doesn't pad with NULs. */
+static char* ini_strncpy0(char* dest, const char* src, size_t size)
+{
+ /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
+ size_t i;
+ for (i = 0; i < size - 1 && src[i]; i++)
+ dest[i] = src[i];
+ dest[i] = '\0';
+ return dest;
+}
+
+/* See documentation in header file. */
+int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
+ void* user)
+{
+ /* Uses a fair bit of stack (use heap instead if you need to) */
+#if INI_USE_STACK
+ char line[INI_MAX_LINE];
+ size_t max_line = INI_MAX_LINE;
+#else
+ char* line;
+ size_t max_line = INI_INITIAL_ALLOC;
+#endif
+#if INI_ALLOW_REALLOC && !INI_USE_STACK
+ char* new_line;
+#endif
+ char section[MAX_SECTION] = "";
+#if INI_ALLOW_MULTILINE
+ char prev_name[MAX_NAME] = "";
+#endif
+
+ size_t offset;
+ char* start;
+ char* end;
+ char* name;
+ char* value;
+ int lineno = 0;
+ int error = 0;
+ char abyss[16]; /* Used to consume input when a line is too long. */
+ size_t abyss_len;
+
+#if !INI_USE_STACK
+ line = (char*)ini_malloc(INI_INITIAL_ALLOC);
+ if (!line) {
+ return -2;
+ }
+#endif
+
+#if INI_HANDLER_LINENO
+#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
+#else
+#define HANDLER(u, s, n, v) handler(u, s, n, v)
+#endif
+
+ /* Scan through stream line by line */
+ while (reader(line, (int)max_line, stream) != NULL) {
+ offset = strlen(line);
+
+#if INI_ALLOW_REALLOC && !INI_USE_STACK
+ while (max_line < INI_MAX_LINE &&
+ offset == max_line - 1 && line[offset - 1] != '\n') {
+ max_line *= 2;
+ if (max_line > INI_MAX_LINE)
+ max_line = INI_MAX_LINE;
+ new_line = ini_realloc(line, max_line);
+ if (!new_line) {
+ ini_free(line);
+ return -2;
+ }
+ line = new_line;
+ if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
+ break;
+ offset += strlen(line + offset);
+ }
+#endif
+
+ lineno++;
+
+ /* If line exceeded INI_MAX_LINE bytes, discard till end of line. */
+ if (offset == max_line - 1 && line[offset - 1] != '\n') {
+ while (reader(abyss, sizeof(abyss), stream) != NULL) {
+ if (!error)
+ error = lineno;
+ abyss_len = strlen(abyss);
+ if (abyss_len > 0 && abyss[abyss_len - 1] == '\n')
+ break;
+ }
+ }
+
+ start = line;
+#if INI_ALLOW_BOM
+ if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
+ (unsigned char)start[1] == 0xBB &&
+ (unsigned char)start[2] == 0xBF) {
+ start += 3;
+ }
+#endif
+ start = ini_rstrip(ini_lskip(start), line + offset);
+
+ if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
+ /* Start-of-line comment */
+ }
+#if INI_ALLOW_MULTILINE
+ else if (*prev_name && *start && start > line) {
+#if INI_ALLOW_INLINE_COMMENTS
+ end = ini_find_chars_or_comment(start, NULL);
+ *end = '\0';
+ ini_rstrip(start, end);
+#endif
+ /* Non-blank line with leading whitespace, treat as continuation
+ of previous name's value (as per Python configparser). */
+ if (!HANDLER(user, section, prev_name, start) && !error)
+ error = lineno;
+ }
+#endif
+ else if (*start == '[') {
+ /* A "[section]" line */
+ end = ini_find_chars_or_comment(start + 1, "]");
+ if (*end == ']') {
+ *end = '\0';
+ ini_strncpy0(section, start + 1, sizeof(section));
+#if INI_ALLOW_MULTILINE
+ *prev_name = '\0';
+#endif
+#if INI_CALL_HANDLER_ON_NEW_SECTION
+ if (!HANDLER(user, section, NULL, NULL) && !error)
+ error = lineno;
+#endif
+ }
+ else if (!error) {
+ /* No ']' found on section line */
+ error = lineno;
+ }
+ }
+ else if (*start) {
+ /* Not a comment, must be a name[=:]value pair */
+ end = ini_find_chars_or_comment(start, "=:");
+ if (*end == '=' || *end == ':') {
+ *end = '\0';
+ name = ini_rstrip(start, end);
+ value = end + 1;
+#if INI_ALLOW_INLINE_COMMENTS
+ end = ini_find_chars_or_comment(value, NULL);
+ *end = '\0';
+#endif
+ value = ini_lskip(value);
+ ini_rstrip(value, end);
+
+#if INI_ALLOW_MULTILINE
+ ini_strncpy0(prev_name, name, sizeof(prev_name));
+#endif
+ /* Valid name[=:]value pair found, call handler */
+ if (!HANDLER(user, section, name, value) && !error)
+ error = lineno;
+ }
+ else {
+ /* No '=' or ':' found on name[=:]value line */
+#if INI_ALLOW_NO_VALUE
+ *end = '\0';
+ name = ini_rstrip(start, end);
+ if (!HANDLER(user, section, name, NULL) && !error)
+ error = lineno;
+#else
+ if (!error)
+ error = lineno;
+#endif
+ }
+ }
+
+#if INI_STOP_ON_FIRST_ERROR
+ if (error)
+ break;
+#endif
+ }
+
+#if !INI_USE_STACK
+ ini_free(line);
+#endif
+
+ return error;
+}
+
+/* See documentation in header file. */
+int ini_parse_file(FILE* file, ini_handler handler, void* user)
+{
+ return ini_parse_stream((ini_reader)fgets, file, handler, user);
+}
+
+/* See documentation in header file. */
+int ini_parse(const char* filename, ini_handler handler, void* user)
+{
+ FILE* file;
+ int error;
+
+ file = fopen(filename, "r");
+ if (!file)
+ return -1;
+ error = ini_parse_file(file, handler, user);
+ fclose(file);
+ return error;
+}
+
+/* An ini_reader function to read the next line from a string buffer. This
+ is the fgets() equivalent used by ini_parse_string(). */
+static char* ini_reader_string(char* str, int num, void* stream) {
+ ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
+ const char* ctx_ptr = ctx->ptr;
+ size_t ctx_num_left = ctx->num_left;
+ char* strp = str;
+ char c;
+
+ if (ctx_num_left == 0 || num < 2)
+ return NULL;
+
+ while (num > 1 && ctx_num_left != 0) {
+ c = *ctx_ptr++;
+ ctx_num_left--;
+ *strp++ = c;
+ if (c == '\n')
+ break;
+ num--;
+ }
+
+ *strp = '\0';
+ ctx->ptr = ctx_ptr;
+ ctx->num_left = ctx_num_left;
+ return str;
+}
+
+/* See documentation in header file. */
+int ini_parse_string(const char* string, ini_handler handler, void* user) {
+ return ini_parse_string_length(string, strlen(string), handler, user);
+}
+
+/* See documentation in header file. */
+int ini_parse_string_length(const char* string, size_t length,
+ ini_handler handler, void* user) {
+ ini_parse_string_ctx ctx;
+
+ ctx.ptr = string;
+ ctx.num_left = length;
+ return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
+ user);
+}
diff --git a/ini.h b/ini.h
new file mode 100644
index 0000000..07aa7f4
--- /dev/null
+++ b/ini.h
@@ -0,0 +1,189 @@
+/* inih -- simple .INI file parser
+
+SPDX-License-Identifier: BSD-3-Clause
+
+Copyright (C) 2009-2025, Ben Hoyt
+
+inih is released under the New BSD license (see LICENSE.txt). Go to the project
+home page for more info:
+
+https://github.com/benhoyt/inih
+
+*/
+
+#ifndef INI_H
+#define INI_H
+
+/* Make this header file easier to include in C++ code */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/* Nonzero if ini_handler callback should accept lineno parameter. */
+#ifndef INI_HANDLER_LINENO
+#define INI_HANDLER_LINENO 0
+#endif
+
+/* Visibility symbols, required for Windows DLLs */
+#ifndef INI_API
+#if defined _WIN32 || defined __CYGWIN__
+# ifdef INI_SHARED_LIB
+# ifdef INI_SHARED_LIB_BUILDING
+# define INI_API __declspec(dllexport)
+# else
+# define INI_API __declspec(dllimport)
+# endif
+# else
+# define INI_API
+# endif
+#else
+# if defined(__GNUC__) && __GNUC__ >= 4
+# define INI_API __attribute__ ((visibility ("default")))
+# else
+# define INI_API
+# endif
+#endif
+#endif
+
+/* Typedef for prototype of handler function.
+
+ Note that even though the value parameter has type "const char*", the user
+ may cast to "char*" and modify its content, as the value is not used again
+ after the call to ini_handler. This is not true of section and name --
+ those must not be modified.
+*/
+#if INI_HANDLER_LINENO
+typedef int (*ini_handler)(void* user, const char* section,
+ const char* name, const char* value,
+ int lineno);
+#else
+typedef int (*ini_handler)(void* user, const char* section,
+ const char* name, const char* value);
+#endif
+
+/* Typedef for prototype of fgets-style reader function. */
+typedef char* (*ini_reader)(char* str, int num, void* stream);
+
+/* Parse given INI-style file. May have [section]s, name=value pairs
+ (whitespace stripped), and comments starting with ';' (semicolon). Section
+ is "" if name=value pair parsed before any section heading. name:value
+ pairs are also supported as a concession to Python's configparser.
+
+ For each name=value pair parsed, call handler function with given user
+ pointer as well as section, name, and value (data only valid for duration
+ of handler call). Handler should return nonzero on success, zero on error.
+
+ Returns 0 on success, line number of first error on parse error (doesn't
+ stop on first error), -1 on file open error, or -2 on memory allocation
+ error (only when INI_USE_STACK is zero).
+*/
+INI_API int ini_parse(const char* filename, ini_handler handler, void* user);
+
+/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
+ close the file when it's finished -- the caller must do that. */
+INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user);
+
+/* Same as ini_parse(), but takes an ini_reader function pointer instead of
+ filename. Used for implementing custom or string-based I/O (see also
+ ini_parse_string). */
+INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
+ void* user);
+
+/* Same as ini_parse(), but takes a zero-terminated string with the INI data
+ instead of a file. Useful for parsing INI data from a network socket or
+ which is already in memory. */
+INI_API int ini_parse_string(const char* string, ini_handler handler, void* user);
+
+/* Same as ini_parse_string(), but takes a string and its length, avoiding
+ strlen(). Useful for parsing INI data from a network socket or which is
+ already in memory, or interfacing with C++ std::string_view. */
+INI_API int ini_parse_string_length(const char* string, size_t length, ini_handler handler, void* user);
+
+/* Nonzero to allow multi-line value parsing, in the style of Python's
+ configparser. If allowed, ini_parse() will call the handler with the same
+ name for each subsequent line parsed. */
+#ifndef INI_ALLOW_MULTILINE
+#define INI_ALLOW_MULTILINE 1
+#endif
+
+/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
+ the file. See https://github.com/benhoyt/inih/issues/21 */
+#ifndef INI_ALLOW_BOM
+#define INI_ALLOW_BOM 1
+#endif
+
+/* Chars that begin a start-of-line comment. Per Python configparser, allow
+ both ; and # comments at the start of a line by default. */
+#ifndef INI_START_COMMENT_PREFIXES
+#define INI_START_COMMENT_PREFIXES ";#"
+#endif
+
+/* Nonzero to allow inline comments (with valid inline comment characters
+ specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
+ Python 3.2+ configparser behaviour. */
+#ifndef INI_ALLOW_INLINE_COMMENTS
+#define INI_ALLOW_INLINE_COMMENTS 1
+#endif
+#ifndef INI_INLINE_COMMENT_PREFIXES
+#define INI_INLINE_COMMENT_PREFIXES ";"
+#endif
+
+/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
+#ifndef INI_USE_STACK
+#define INI_USE_STACK 1
+#endif
+
+/* Maximum line length for any line in INI file (stack or heap). Note that
+ this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
+#ifndef INI_MAX_LINE
+#define INI_MAX_LINE 200
+#endif
+
+/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
+ fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
+ zero. */
+#ifndef INI_ALLOW_REALLOC
+#define INI_ALLOW_REALLOC 0
+#endif
+
+/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
+ is zero. */
+#ifndef INI_INITIAL_ALLOC
+#define INI_INITIAL_ALLOC 200
+#endif
+
+/* Stop parsing on first error (default is to keep parsing). */
+#ifndef INI_STOP_ON_FIRST_ERROR
+#define INI_STOP_ON_FIRST_ERROR 0
+#endif
+
+/* Nonzero to call the handler at the start of each new section (with
+ name and value NULL). Default is to only call the handler on
+ each name=value pair. */
+#ifndef INI_CALL_HANDLER_ON_NEW_SECTION
+#define INI_CALL_HANDLER_ON_NEW_SECTION 0
+#endif
+
+/* Nonzero to allow a name without a value (no '=' or ':' on the line) and
+ call the handler with value NULL in this case. Default is to treat
+ no-value lines as an error. */
+#ifndef INI_ALLOW_NO_VALUE
+#define INI_ALLOW_NO_VALUE 0
+#endif
+
+/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory
+ allocation functions (INI_USE_STACK must also be 0). These functions must
+ have the same signatures as malloc/free/realloc and behave in a similar
+ way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */
+#ifndef INI_CUSTOM_ALLOCATOR
+#define INI_CUSTOM_ALLOCATOR 0
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* INI_H */