aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--comet.conf5
-rw-r--r--src/any_ini.h570
-rw-r--r--src/any_log.h2
-rw-r--r--src/comet.c20
-rw-r--r--src/config.c186
-rw-r--r--src/config.h15
6 files changed, 790 insertions, 8 deletions
diff --git a/comet.conf b/comet.conf
new file mode 100644
index 0000000..4ccdf21
--- /dev/null
+++ b/comet.conf
@@ -0,0 +1,5 @@
+; vim: syn=confini ts=4 sw=4 et
+
+[bar]
+width = 1700
+height = 40
diff --git a/src/any_ini.h b/src/any_ini.h
new file mode 100644
index 0000000..f05b579
--- /dev/null
+++ b/src/any_ini.h
@@ -0,0 +1,570 @@
+// any_ini
+//
+// A single-file library that provides a simple and somewhat opinionated
+// interface for parsing ini files, either from a buffer or stream.
+//
+// To use this library you should choose a suitable file to put the
+// implementation and define ANY_INI_IMPLEMENT. For example
+//
+// #define ANY_INI_IMPLEMENT
+// #include "any_ini.h"
+//
+// Additionally, you can customize the library behavior by defining certain
+// macros in the file where you put the implementation. You can see which are
+// supported by reading the code guarded by ANY_INI_IMPLEMENT.
+//
+// This library is licensed under the terms of the MIT license.
+// A copy of the license is included at the end of this file.
+//
+
+#ifndef ANY_INI_INCLUDE
+#define ANY_INI_INCLUDE
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+// String parser (provided by any_ini_t)
+
+typedef struct {
+ const char *source;
+ size_t length;
+ size_t cursor;
+ size_t line;
+} any_ini_t;
+
+// Initialize the parser with a string.
+//
+void any_ini_init(any_ini_t *ini, const char *source, size_t length);
+
+// Check if the parser has reached the end of the string.
+//
+bool any_ini_eof(any_ini_t *ini);
+
+// Get the current line reached by the parser.
+//
+size_t any_ini_line(any_ini_t *ini);
+
+// Get the next section.
+// This function will return NULL if the parser has reached the end.
+//
+char *any_ini_next_section(any_ini_t *ini);
+
+// Get the next pair key.
+// This function will return NULL if the section has ended.
+//
+char *any_ini_next_key(any_ini_t *ini);
+
+// Get the value for the current pair.
+// This function will return NULL if nothing is found.
+//
+// NOTE: You should always call any_ini_next_key before this function.
+//
+char *any_ini_next_value(any_ini_t *ini);
+
+// Stream parser (provided by any_ini_stream_t)
+//
+// Can be disabled by defining ANY_INI_NO_STREAM.
+
+#ifndef ANY_INI_NO_STREAM
+
+// Specify the size of the temporary line buffer used by the stream parser.
+//
+#ifndef ANY_INI_BUFFER_SIZE
+#define ANY_INI_BUFFER_SIZE 512
+#endif
+
+// The stream parser can be initialized with a function similar to fgets.
+// This function should read up to size chars from stream and stop at newlines.
+//
+typedef char *(*any_ini_stream_read_t)(char *string, int size, void *stream);
+
+typedef struct {
+ char buffer[ANY_INI_BUFFER_SIZE];
+ size_t cursor;
+ size_t line;
+ any_ini_stream_read_t read;
+ void *stream;
+ bool eof;
+} any_ini_stream_t;
+
+// Initialize the parser with a read function and a stream.
+//
+void any_ini_stream_init(any_ini_stream_t *ini, any_ini_stream_read_t read, void *stream);
+
+// Initialize the parser with a file stream.
+//
+// NOTE: This is just a shorthand function equivalent to
+//
+// any_ini_stream_init(ini, fgets, file);
+//
+void any_ini_file_init(any_ini_stream_t *ini, FILE *file);
+
+// Check if the parser has reached the end of the stream.
+//
+bool any_ini_stream_eof(any_ini_stream_t *ini);
+
+// Get the current line reached by the stream parser.
+//
+size_t any_ini_stream_line(any_ini_stream_t *ini);
+
+// Get the next section from the stream.
+// This function will return NULL if the parser has reached the end.
+//
+char *any_ini_stream_next_section(any_ini_stream_t *ini);
+
+// Get the next pair key from the stream.
+// This function will return NULL if the section has ended.
+//
+char *any_ini_stream_next_key(any_ini_stream_t *ini);
+
+// Get the value for the current pair from the stream.
+// This function will return NULL if nothing is found.
+//
+// NOTE: You should always call any_ini_next_key before this function.
+//
+char *any_ini_stream_next_value(any_ini_stream_t *ini);
+
+#endif
+
+#endif
+
+#ifdef ANY_INI_IMPLEMENT
+
+#include <string.h>
+#include <ctype.h>
+
+// The strings returned from the parser are allocated with ANY_INI_MALLOC.
+// You can change allocation strategy by defining this macro in the
+// implementation file like so
+//
+// #define ANY_INI_IMPLEMENT
+// #define ANY_INI_MALLOC my_malloc
+// #define ANY_INI_REALLOC my_realloc
+// #include "any_ini.h"
+//
+// ANY_INI_MALLOC is used by the string parser and ANY_INI_REALLOC is used
+// by the stream parser. If you defined ANY_INI_NO_STREAM you can ignore
+// the former function.
+//
+#ifndef ANY_INI_MALLOC
+#include <stdlib.h>
+#define ANY_INI_MALLOC malloc
+#define ANY_INI_REALLOC realloc
+#endif
+
+// You can define ANY_INI_DELIM_COMMENT to specify which char starts a comment.
+// By default it is ';'.
+// Additionally you can specify a second character with ANY_INI_DELIM_COMMENT2.
+// For example
+//
+// #define ANY_INI_DELIM_COMMENT2 '#'
+// #include "any_ini.h"
+//
+#ifndef ANY_INI_DELIM_COMMENT
+#define ANY_INI_DELIM_COMMENT ';'
+#endif
+
+// You can define ANY_INI_DELIM_PAIR to specify which char divides a key from
+// the value in a pair.
+// By default it is '='.
+//
+#ifndef ANY_INI_DELIM_PAIR
+#define ANY_INI_DELIM_PAIR '='
+#endif
+
+// You can define ANY_INI_SECTION_START to specify which char starts a section.
+// By default it is '['.
+//
+#ifndef ANY_INI_SECTION_START
+#define ANY_INI_SECTION_START '['
+#endif
+
+// You can define ANY_INI_SECTION_END to specify which char ends a section.
+// By default it is ']'.
+//
+#ifndef ANY_INI_SECTION_END
+#define ANY_INI_SECTION_END ']'
+#endif
+
+// The parsers allow values to stretch multiple lines if a ANY_INI_LINE_ESCAPE
+// is found just before the newline character.
+// By default ANY_INI_LINE_ESCAPE is '\'.
+//
+// You can define the macro ANY_INI_NO_MULTILINE if you want to disable
+// multiline handling.
+//
+#ifndef ANY_INI_LINE_ESCAPE
+#define ANY_INI_LINE_ESCAPE '\\'
+#endif
+
+static size_t any_ini_trim(const char *source, size_t start, size_t end)
+{
+ while (isspace(source[end - 1])) end--;
+ return end - start;
+}
+
+// If ANY_INI_NO_MULTILINE is defined it copies verbatim the input, otherwise
+// it will remove the sequences '\\\r\n' and '\\\n' while copying
+static size_t any_ini_copy(char *dest, const char *source, size_t length)
+{
+#ifdef ANY_INI_NO_MULTILINE
+ memcpy(dest, source, length);
+ return length;
+#else
+ size_t i = 0, j = 0;
+ while (i < length) {
+ if (source[i] == ANY_INI_LINE_ESCAPE) {
+ if (i + 1 < length && source[i + 1] == '\n')
+ i += 2;
+ else if (i + 2 < length && source[i + 1] == '\r' && source[i + 2] == '\n')
+ i += 3;
+ continue;
+ }
+ dest[j++] = source[i++];
+ }
+ return j;
+#endif
+}
+
+static char *any_ini_slice(const char *start, size_t length)
+{
+ char *string = ANY_INI_MALLOC(length + 1);
+ if (string) {
+ length = any_ini_copy(string, start, length);
+ string[length] = '\0';
+ }
+ return string;
+}
+
+static void any_ini_skip(any_ini_t *ini)
+{
+ while (!any_ini_eof(ini)) {
+ switch (ini->source[ini->cursor]) {
+ case '\n':
+ ini->line++;
+ break;
+
+ case ANY_INI_DELIM_COMMENT:
+#ifdef ANY_INI_DELIM_COMMENT2
+ case ANY_INI_DELIM_COMMENT2:
+#endif
+ while (!any_ini_eof(ini) && ini->source[ini->cursor] != '\n')
+ ini->cursor++;
+ continue;
+
+ default:
+ if (isspace(ini->source[ini->cursor])) break;
+ return;
+ }
+ ini->cursor++;
+ }
+}
+
+static bool any_ini_skip_pair(any_ini_t *ini, bool key)
+{
+ switch (ini->source[ini->cursor]) {
+ case '\n':
+#ifndef ANY_INI_NO_MULTILINE
+ if ((ini->cursor > 2 && ini->source[ini->cursor - 1] == '\r' &&
+ ini->source[ini->cursor - 2] == ANY_INI_LINE_ESCAPE) ||
+ (ini->cursor > 1 && ini->source[ini->cursor - 1] == ANY_INI_LINE_ESCAPE)) {
+ ini->line++;
+ return true;
+ }
+#endif
+ return false;
+
+#ifndef ANY_INI_NO_INLINE_COMMENT
+ case ANY_INI_DELIM_COMMENT:
+#ifdef ANY_INI_DELIM_COMMENT2
+ case ANY_INI_DELIM_COMMENT2:
+#endif
+ if (ini->cursor != 0 && isspace(ini->source[ini->cursor - 1]))
+ return false;
+ // fallthrough
+#endif
+
+ default:
+ return !key || ini->source[ini->cursor] != ANY_INI_DELIM_PAIR;
+ }
+}
+
+void any_ini_init(any_ini_t *ini, const char *source, size_t length)
+{
+ ini->source = source;
+ ini->length = length;
+ ini->cursor = 0;
+ ini->line = 1;
+}
+
+bool any_ini_eof(any_ini_t *ini)
+{
+ return ini->cursor >= ini->length;
+}
+
+size_t any_ini_line(any_ini_t *ini)
+{
+ return ini->line;
+}
+
+char *any_ini_next_section(any_ini_t *ini)
+{
+ any_ini_skip(ini);
+
+ if (any_ini_eof(ini) || ini->source[ini->cursor] != ANY_INI_SECTION_START)
+ return NULL;
+
+ ++ini->cursor;
+ while (!any_ini_eof(ini) && isspace(ini->source[ini->cursor]))
+ ini->cursor++;
+ size_t start = ini->cursor;
+
+ while (!any_ini_eof(ini) && ini->source[ini->cursor] != '\n' && ini->source[ini->cursor] != ANY_INI_SECTION_END)
+ ini->cursor++;
+
+ size_t end = ini->cursor;
+ while (!any_ini_eof(ini) && ini->source[ini->cursor] != '\n')
+ ini->cursor++;
+
+ size_t length = any_ini_trim(ini->source, start, end);
+ return any_ini_slice(ini->source + start, length);
+}
+
+char *any_ini_next_key(any_ini_t *ini)
+{
+ any_ini_skip(ini);
+
+ if (any_ini_eof(ini) || ini->source[ini->cursor] == ANY_INI_SECTION_START)
+ return NULL;
+
+ size_t start = ini->cursor;
+ while (!any_ini_eof(ini) && any_ini_skip_pair(ini, true))
+ ini->cursor++;
+
+ size_t length = any_ini_trim(ini->source, start, ini->cursor);
+ return any_ini_slice(ini->source + start, length);
+}
+
+char *any_ini_next_value(any_ini_t *ini)
+{
+ if (any_ini_eof(ini) || ini->source[ini->cursor] != ANY_INI_DELIM_PAIR)
+ return NULL;
+
+ ++ini->cursor;
+ any_ini_skip(ini);
+
+ size_t start = ini->cursor;
+ while (!any_ini_eof(ini) && any_ini_skip_pair(ini, false))
+ ini->cursor++;
+
+ size_t length = any_ini_trim(ini->source, start, ini->cursor);
+ return any_ini_slice(ini->source + start, length);
+}
+
+#ifndef ANY_INI_NO_STREAM
+
+static void any_ini_stream_read(any_ini_stream_t *ini)
+{
+ ini->eof = ini->read(ini->buffer, ANY_INI_BUFFER_SIZE, ini->stream) == NULL;
+ ini->cursor = 0;
+}
+
+static void any_ini_stream_skip_line(any_ini_stream_t *ini)
+{
+ while (!ini->eof && ini->buffer[ini->cursor] != '\n') {
+ if (ini->buffer[ini->cursor] == '\0')
+ any_ini_stream_read(ini);
+ else
+ ini->cursor++;
+ }
+}
+
+static void any_ini_stream_skip(any_ini_stream_t *ini, bool comment)
+{
+ while (!ini->eof) {
+ switch (ini->buffer[ini->cursor]) {
+ case '\0':
+ any_ini_stream_read(ini);
+ continue;
+
+ case ANY_INI_DELIM_COMMENT:
+#ifdef ANY_INI_DELIM_COMMENT2
+ case ANY_INI_DELIM_COMMENT2:
+#endif
+ if (!comment)
+ return;
+
+ any_ini_stream_skip_line(ini);
+ // fallthrough
+
+ // Discard the current line
+ case '\n':
+ ini->line++;
+ any_ini_stream_read(ini);
+ continue;
+
+ default:
+ if (isspace(ini->buffer[ini->cursor])) break;
+ return;
+ }
+ ini->cursor++;
+ }
+}
+
+static char *any_ini_stream_until(any_ini_stream_t *ini, size_t start, char c)
+{
+ char *tmp, *value = NULL;
+ size_t size = 0;
+ char prev[2] = { 0 };
+
+ bool done = false;
+ while (!ini->eof && !done) {
+ switch (ini->buffer[ini->cursor]) {
+ // Copy current buffer and refill
+ case '\0':
+ tmp = ANY_INI_REALLOC(value, size + ini->cursor - start);
+ if (!tmp) return value;
+
+ size += any_ini_copy(tmp + size, ini->buffer + start, ini->cursor - start);
+ value = tmp;
+
+ any_ini_stream_read(ini);
+ start = 0;
+ break;
+
+ // Stop at line boundaries
+ case '\n':
+#ifndef ANY_INI_NO_MULTILINE
+ if ((prev[0] == '\r' && prev[1] == '\\') || prev[0] == '\\') {
+ ini->cursor++;
+ continue;
+ }
+#endif
+ done = true;
+ break;
+
+#ifndef ANY_INI_NO_INLINE_COMMENT
+ case ANY_INI_DELIM_COMMENT:
+#ifdef ANY_INI_DELIM_COMMENT2
+ case ANY_INI_DELIM_COMMENT2:
+#endif
+ if (isspace(prev[0])) {
+ done = true;
+ break;
+ }
+ // fallthrough
+#endif
+ default:
+ if (ini->buffer[ini->cursor] == c) {
+ done = true;
+ break;
+ }
+
+ prev[1] = prev[0];
+ prev[0] = ini->buffer[ini->cursor];
+ ini->cursor++;
+ break;
+
+ }
+ }
+
+ tmp = ANY_INI_REALLOC(value, size + ini->cursor - start);
+ if (!tmp) return value;
+
+ size += any_ini_copy(tmp + size, ini->buffer + start, ini->cursor - start);
+ size = any_ini_trim(tmp, 0, size);
+ tmp[size] = '\0';
+ return tmp;
+}
+
+void any_ini_stream_init(any_ini_stream_t *ini, any_ini_stream_read_t read, void *stream)
+{
+ ini->line = 1;
+ ini->read = read;
+ ini->stream = stream;
+
+ // Init buffer
+ memset(ini->buffer, 0, ANY_INI_BUFFER_SIZE);
+ any_ini_stream_read(ini);
+}
+
+void any_ini_file_init(any_ini_stream_t *ini, FILE *file)
+{
+ any_ini_stream_init(ini, (any_ini_stream_read_t)fgets, file);
+}
+
+bool any_ini_stream_eof(any_ini_stream_t *ini)
+{
+ return ini->eof;
+}
+
+size_t any_ini_stream_line(any_ini_stream_t *ini)
+{
+ return ini->line;
+}
+
+char *any_ini_stream_next_section(any_ini_stream_t *ini)
+{
+ any_ini_stream_skip(ini, true);
+
+ if (ini->eof || ini->buffer[ini->cursor] != ANY_INI_SECTION_START)
+ return NULL;
+
+ // Skip padding
+ ini->cursor++;
+ any_ini_stream_skip(ini, false);
+
+ char *section = any_ini_stream_until(ini, ini->cursor, ANY_INI_SECTION_END);
+ any_ini_stream_skip_line(ini);
+ return section;
+}
+
+char *any_ini_stream_next_key(any_ini_stream_t *ini)
+{
+ any_ini_stream_skip(ini, true);
+
+ if (ini->eof || ini->buffer[ini->cursor] == ANY_INI_SECTION_START
+ || ini->buffer[ini->cursor] == ANY_INI_DELIM_PAIR)
+ return NULL;
+
+ return any_ini_stream_until(ini, ini->cursor, ANY_INI_DELIM_PAIR);
+}
+
+char *any_ini_stream_next_value(any_ini_stream_t *ini)
+{
+ if (ini->eof || ini->buffer[ini->cursor] != ANY_INI_DELIM_PAIR)
+ return NULL;
+
+ // Skip padding
+ ini->cursor++;
+ any_ini_stream_skip(ini, false);
+ return any_ini_stream_until(ini, ini->cursor, '\n');
+}
+
+#endif
+
+#endif
+
+// MIT License
+//
+// Copyright (c) 2024 Federico Angelilli
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
diff --git a/src/any_log.h b/src/any_log.h
index c4f143c..1cb298a 100644
--- a/src/any_log.h
+++ b/src/any_log.h
@@ -398,7 +398,7 @@ const char **any_log_colors = any_log_colors_default;
#define ANY_LOG_PANIC_COLOR "\x1b[1;91m"
#endif
#ifndef ANY_LOG_ERROR_COLOR
-#define ANY_LOG_ERROR_COLOR "\x1b[31m"
+#define ANY_LOG_ERROR_COLOR "\x1b[1;31m"
#endif
#ifndef ANY_LOG_WARN_COLOR
#define ANY_LOG_WARN_COLOR "\x1b[1;33m"
diff --git a/src/comet.c b/src/comet.c
index e87836e..be76621 100644
--- a/src/comet.c
+++ b/src/comet.c
@@ -6,6 +6,7 @@
#include "layout.h"
#include "util.h"
#include "event.h"
+#include "config.h"
#define ANY_LOG_IMPLEMENT
#define ANY_LOG_VALUE_STRING(key, value) "%s=\"%s\"", key, value ? value : "(null)"
@@ -38,6 +39,11 @@ int main(int argc, char **argv)
setlocale(LC_CTYPE, "");
any_log_init(stdout, ANY_LOG_DEBUG);
+ FILE *config_file = fopen("comet.conf", "rb");
+
+ config_t config;
+ config_init(&config, config_file);
+
display_t display;
display_init(&display);
@@ -47,8 +53,8 @@ int main(int argc, char **argv)
int x_padding = 10;
int y_padding = 8;
- int height = 30;
- int width = display.screen_size->width - 2 * x_padding;
+ //int height = 30;
+ //int width = display.screen_size->width - 2 * x_padding;
const char *font = "Hack 12";
log_debug("Loading font '%s'", font);
@@ -57,11 +63,11 @@ int main(int argc, char **argv)
.fontdesc = pango_font_description_from_string(font),
.context = pango_cairo_create_context(window.cr),
.x_offset = 0,
- .height = height,
- .width = width,
+ .height = config.height,
+ .width = config.width,
};
- window_resize(&window, width, height);
+ window_resize(&window, config.width, config.height);
window_move(&window, x_padding, y_padding);
block_t blocks[2] = {
@@ -87,7 +93,7 @@ int main(int argc, char **argv)
block_t top = {
.hidden = false,
.color = { 0.3, 0.2, 0.5, 1 },
- .min_width = width,
+ .min_width = config.width,
.type = BLOCK_GROUP,
.group = {
.spacing = 10,
@@ -114,7 +120,7 @@ int main(int argc, char **argv)
event_dispatch(&display, &layout);
cairo_surface_t *surface = render(&layout, info);
- window_present(&window, surface, width, height);
+ window_present(&window, surface, config.width, config.height);
cairo_surface_destroy(surface);
layout_free(&layout);
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..d676a41
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,186 @@
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <string.h>
+
+#include "any_log.h"
+#include "config.h"
+
+#define ANY_INI_IMPLEMENT
+#include "any_ini.h"
+
+typedef enum {
+ CONFIG_STRING,
+ CONFIG_INT_ANY,
+ CONFIG_INT_POSITIVE,
+ CONFIG_DOUBLE,
+} config_type_t;
+
+typedef struct {
+ const char *key;
+ config_type_t type;
+ size_t offset;
+} config_entry_t;
+
+static const config_entry_t config_entries[] = {
+ { "width", CONFIG_INT_POSITIVE, offsetof(config_t, width) },
+ { "height", CONFIG_INT_POSITIVE, offsetof(config_t, height) },
+ { "font", CONFIG_STRING, offsetof(config_t, font) },
+ { "monitor", CONFIG_STRING, offsetof(config_t, monitor) },
+};
+
+static bool config_read_string(const char *value, char **result)
+{
+ if (value == NULL)
+ value = "";
+
+ size_t start = 0, end = strlen(value);
+ if (value[0] == '"' && value[end - 1] == '"') {
+ start++;
+ end--;
+ }
+
+ *result = malloc(end - start + 1);
+ memcpy(*result, value + start, end - start);
+ *result[end - start] = '\0';
+ return true;
+}
+
+static bool config_read_int(const char *value, int *result)
+{
+ char *end;
+ long n = strtol(value, &end, 0);
+ *result = n;
+
+ if (n > INT_MAX || n < INT_MIN)
+ log_debug("Integer is too big");
+
+ return *end == '\0' && n != LONG_MIN && n != LONG_MAX;
+}
+
+static bool config_read_double(const char *value, double *result)
+{
+ char *end;
+ *result = strtod(value, &end);
+ return *end == '\0';
+}
+
+static bool config_entry(config_t *config, int line, const char *section, const char *key, const char *value)
+{
+ const int n_entries = sizeof(config_entries) / sizeof(config_entry_t);
+
+ const char *types[] = {
+ "string",
+ "integer",
+ "positive integer",
+ "double float",
+ };
+
+ for (int i = 0; i < n_entries; i++) {
+ if (!strcmp(key, config_entries[i].key)) {
+ bool nullable = config_entries[i].type == CONFIG_STRING;
+
+ if ((value == NULL || *value == '\0') && !nullable) {
+ log_value_error("Empty config entry",
+ "s:section", section,
+ "s:key", key,
+ "s:expected_type", types[config_entries[i].type],
+ "i:line", line);
+ return false;
+ }
+
+ void *result = (uint8_t *)config + config_entries[i].offset;
+ switch (config_entries[i].type) {
+ case CONFIG_STRING:
+ if (config_read_string(value, (char **)result))
+ return true;
+ break;
+
+ case CONFIG_INT_ANY:
+ if (config_read_int(value, (int *)result))
+ return true;
+ break;
+
+ case CONFIG_INT_POSITIVE:
+ if (config_read_int(value, (int *)result) && *(int *)result >= 0)
+ return true;
+ break;
+
+ case CONFIG_DOUBLE:
+ if (config_read_double(value, (double *)result))
+ return true;
+ break;
+
+ default:
+ log_panic("Unreachable");
+ }
+
+ log_value_error("Invalid config entry",
+ "s:section", section,
+ "s:key", key,
+ "s:value", value,
+ "s:expected_type", types[config_entries[i].type],
+ "i:line", line);
+
+ return false;
+ }
+ }
+
+ log_value_warn("Unknown config entry",
+ "s:section", section,
+ "s:key", key,
+ "s:value", value,
+ "i:line", line);
+
+ return true;
+}
+
+static const config_t config_default = {
+ .font = "monospace 10",
+ .monitor = NULL,
+ .height = 50,
+ .width = 100,
+};
+
+void config_init(config_t *config, FILE *file)
+{
+ memcpy(config, &config_default, sizeof(config_t));
+ log_debug("Copied default config");
+
+ any_ini_stream_t ini;
+ any_ini_file_init(&ini, file);
+
+ int errors = 0;
+ char *section = NULL;
+
+ do {
+ bool unknown_section = section != NULL && strcmp(section, "bar");
+
+ if (unknown_section)
+ log_warn("Unknown section '%s'", section);
+
+ char *key;
+ while ((key = any_ini_stream_next_key(&ini)) != NULL) {
+ int line = ini.line;
+ char *value = any_ini_stream_next_value(&ini);
+
+ log_value_trace("Reading config entry",
+ "s:section", section,
+ "s:key", key,
+ "s:value", value,
+ "i:line", ini.line);
+
+ if (!unknown_section)
+ errors += !config_entry(config, ini.line, section, key, value);
+
+ free(key);
+ free(value);
+ }
+
+ free(section);
+ } while ((section = any_ini_stream_next_section(&ini)) != NULL);
+
+ if (errors > 0)
+ log_panic("Config file contained %d errors", errors);
+}
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 0000000..985ad4c
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,15 @@
+#ifndef COMET_CONFIG_H
+#define COMET_CONFIG_H
+
+#include <stdio.h>
+
+typedef struct {
+ char *font;
+ char *monitor;
+ int height;
+ int width;
+} config_t;
+
+void config_init(config_t *config, FILE *file);
+
+#endif