URI: 
       Move theme config to main config file - ltk - GUI toolkit for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ltk.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit b44497a0e20b249923f01284f85163b61b7e7609
   DIR parent 210aa3d51d8dcc1e36c73d3404e032937271e0d6
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Thu,  2 May 2024 16:10:04 +0200
       
       Move theme config to main config file
       
       This is also still really ugly.
       
       Diffstat:
         M LICENSE                             |       2 +-
         M Makefile                            |       5 +----
         M config.example/ltk.cfg              |      32 +++++++++++++++++++++++++++++++
         D config.example/theme.ini            |      31 -------------------------------
         M src/ltk/button.c                    |      17 +++--------------
         M src/ltk/button.h                    |       4 ----
         M src/ltk/config.c                    |     528 +++++++++++++++++++++----------
         M src/ltk/config.h                    |      77 +++++++++++++++++++++++++------
         M src/ltk/entry.c                     |      23 ++++++-----------------
         M src/ltk/entry.h                     |      11 -----------
         M src/ltk/event_xlib.c                |       6 +++---
         D src/ltk/ini.c                       |     201 ------------------------------
         D src/ltk/ini.h                       |     104 -------------------------------
         M src/ltk/label.c                     |      18 +++---------------
         M src/ltk/label.h                     |       4 ----
         M src/ltk/ltk.c                       |     119 +++++--------------------------
         M src/ltk/menu.c                      |      74 +++++++------------------------
         M src/ltk/menu.h                      |      14 --------------
         M src/ltk/num.c                       |       2 +-
         M src/ltk/scrollbar.c                 |      17 +++--------------
         M src/ltk/scrollbar.h                 |       4 ----
         D src/ltk/theme.c                     |     211 -------------------------------
         D src/ltk/theme.h                     |      71 -------------------------------
         A src/ltk/widget_internal.h           |      54 +++++++++++++++++++++++++++++++
         M src/ltk/window.c                    |      37 +++++++++++--------------------
         M src/ltk/window.h                    |      15 +--------------
       
       26 files changed, 584 insertions(+), 1097 deletions(-)
       ---
   DIR diff --git a/LICENSE b/LICENSE
       @@ -1,4 +1,4 @@
       -See src/ltkd/khash.h, src/ltk/ini.*, src/ltk/stb_truetype.*, src/ltk/strtonum.c,
       +See src/ltkd/khash.h, src/ltk/stb_truetype.*, src/ltk/num.c,
        src/ltk/ctrlsel.*, and src/ltk/macros.h for third-party licenses.
        
        ISC License
   DIR diff --git a/Makefile b/Makefile
       @@ -56,9 +56,7 @@ OBJ_LTK = \
                src/ltk/rect.o \
                src/ltk/widget.o \
                src/ltk/ltk.o \
       -        src/ltk/ini.o \
                src/ltk/button.o \
       -        src/ltk/theme.o \
                src/ltk/graphics_xlib.o \
                src/ltk/surface_cache.o \
                src/ltk/event_xlib.o \
       @@ -97,7 +95,6 @@ OBJ_TEST = examples/ltk/test.o
        HDR_LTK = \
                src/ltk/button.h \
                src/ltk/color.h \
       -        src/ltk/ini.h \
                src/ltk/label.h \
                src/ltk/rect.h \
                src/ltk/widget.h \
       @@ -107,7 +104,7 @@ HDR_LTK = \
                src/ltk/stb_truetype.h \
                src/ltk/text.h \
                src/ltk/util.h \
       -        src/ltk/theme.h \
       +        src/ltk/widget_internal.h \
                src/ltk/graphics.h \
                src/ltk/surface_cache.h \
                src/ltk/macros.h \
   DIR diff --git a/config.example/ltk.cfg b/config.example/ltk.cfg
       @@ -8,6 +8,38 @@ dpi-scale = 1.0
        # In future:
        # text-editor = ...
        
       +[theme:window]
       +font-size = 12pt
       +bg = "#000000"
       +fg = "#FFFFFF"
       +font = "Liberation Mono"
       +
       +[theme:button]
       +border-width = 0.5mm
       +text-color = "#FFFFFF"
       +pad = 1mm
       +border = "#339999"
       +fill = "#113355"
       +border-pressed = "#FFFFFF"
       +fill-pressed = "#113355"
       +border-active = "#FFFFFF"
       +fill-active = "#738194"
       +border-disabled = "#FFFFFF"
       +fill-disabled = "#292929"
       +
       +[theme:label]
       +text-color = "#FFFFFF"
       +pad = 1mm
       +
       +[theme:scrollbar]
       +size = 3.5mm
       +bg = "#000000"
       +bg-disabled = "#555555"
       +fg = "#113355"
       +fg-pressed = "#113355"
       +fg-active = "#738194"
       +fg-disabled = "#292929"
       +
        [key-binding:window]
        # In future:
        # bind edit-text-external ...
   DIR diff --git a/config.example/theme.ini b/config.example/theme.ini
       @@ -1,31 +0,0 @@
       -[window]
       -font-size = 12pt
       -bg = #000000
       -fg = #FFFFFF
       -font = Liberation Mono
       -
       -[button]
       -border-width = 0.5mm
       -text-color = #FFFFFF
       -pad = 1mm
       -border = #339999
       -fill = #113355
       -border-pressed = #FFFFFF
       -fill-pressed = #113355
       -border-active = #FFFFFF
       -fill-active = #738194
       -border-disabled = #FFFFFF
       -fill-disabled = #292929
       -
       -[label]
       -text-color = #FFFFFF
       -pad = 1mm
       -
       -[scrollbar]
       -size = 3.5mm
       -bg = #000000
       -bg-disabled = #555555
       -fg = #113355
       -fg-pressed = #113355
       -fg-active = #738194
       -fg-disabled = #292929
   DIR diff --git a/src/ltk/button.c b/src/ltk/button.c
       @@ -24,7 +24,6 @@
        #include "memory.h"
        #include "rect.h"
        #include "text.h"
       -#include "theme.h"
        #include "util.h"
        #include "widget.h"
        
       @@ -97,21 +96,11 @@ static ltk_theme_parseinfo parseinfo[] = {
                {"pad", THEME_SIZE, {.size = &theme.pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_BUTTON_PADDING, 0},
                {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0},
        };
       -static int parseinfo_sorted = 0;
       -
       -int
       -ltk_button_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
       -        return ltk_theme_handle_value(data, "button", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
       -}
       -
       -int
       -ltk_button_fill_theme_defaults(ltk_renderdata *data) {
       -        return ltk_theme_fill_defaults(data, "button", parseinfo, LENGTH(parseinfo));
       -}
        
        void
       -ltk_button_uninitialize_theme(ltk_renderdata *data) {
       -        ltk_theme_uninitialize(data, parseinfo, LENGTH(parseinfo));
       +ltk_button_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
       +        *p = parseinfo;
       +        *len = LENGTH(parseinfo);
        }
        
        static void
   DIR diff --git a/src/ltk/button.h b/src/ltk/button.h
       @@ -30,10 +30,6 @@ typedef struct {
                ltk_text_line *tl;
        } ltk_button;
        
       -int ltk_button_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
       -int ltk_button_fill_theme_defaults(ltk_renderdata *data);
       -void ltk_button_uninitialize_theme(ltk_renderdata *data);
       -
        ltk_button *ltk_button_create(ltk_window *window, char *text);
        
        #endif /* LTK_BUTTON_H */
   DIR diff --git a/src/ltk/config.c b/src/ltk/config.c
       @@ -24,15 +24,38 @@
        #include "memory.h"
        #include "config.h"
        #include "sort_search.h"
       -
       -#include "entry.h"
       -#include "window.h"
       +#include "widget_internal.h"
        
        GEN_SORT_SEARCH_HELPERS(keybinding, ltk_keybinding_cb, text)
       -//GEN_SORT_SEARCH_HELPERS(theme, ltk_theme_parseinfo, key)
       +GEN_SORT_SEARCH_HELPERS(theme, ltk_theme_parseinfo, key)
        LTK_ARRAY_INIT_IMPL(keypress, ltk_keypress_cfg)
        LTK_ARRAY_INIT_IMPL(keyrelease, ltk_keyrelease_cfg)
        
       +static ltk_general_config general_config;
       +static ltk_language_mapping *mappings = NULL;
       +static size_t mappings_alloc = 0, mappings_len = 0;
       +
       +static ltk_theme_parseinfo general_parseinfo[] = {
       +        {"line-editor", THEME_STRING, {.str = &general_config.line_editor}, {.str = NULL}, 0, 0, 0},
       +        {"dpi-scale", THEME_DOUBLE, {.d = &general_config.dpi_scale}, {.d = 1.0}, 10, 10000, 0},
       +        {"explicit-focus", THEME_BOOL, {.b = &general_config.explicit_focus}, {.b = 0}, 0, 0, 0},
       +        {"all-activatable", THEME_BOOL, {.b = &general_config.all_activatable}, {.b = 0}, 0, 0, 0},
       +        {"fixed-dpi", THEME_DOUBLE, {.d = &general_config.fixed_dpi}, {.d = 96.0}, 100, 400000, 0},
       +        /* FIXME: warning if set to true but xrandr not enabled */
       +#if USE_XRANDR
       +        {"mixed-dpi", THEME_BOOL, {.b = &general_config.mixed_dpi}, {.b = 1}, 0, 0, 0},
       +#else
       +        {"mixed-dpi", THEME_BOOL, {.b = &general_config.mixed_dpi}, {.b = 0}, 0, 0, 0},
       +#endif
       +};
       +
       +/* just to use the same interface for all theme sections */
       +static void
       +ltk_general_get_theme_parseinfo(ltk_theme_parseinfo **parseinfo, size_t *len) {
       +        *parseinfo = general_parseinfo;
       +        *len = LENGTH(general_parseinfo);
       +}
       +
        static struct {
                const char *name;
                void (*get_parseinfo)(
       @@ -45,6 +68,232 @@ static struct {
                {"window", &ltk_window_get_keybinding_parseinfo},
        };
        
       +static struct theme_handlerinfo {
       +        const char *name;
       +        void (*get_parseinfo)(ltk_theme_parseinfo **parseinfo, size_t *len);
       +        const char *parent;
       +} theme_handlers[] = {
       +        {"general", &ltk_general_get_theme_parseinfo, NULL},
       +        {"theme:window", &ltk_window_get_theme_parseinfo, NULL},
       +        {"theme:button", &ltk_button_get_theme_parseinfo, "window"},
       +        {"theme:entry", &ltk_entry_get_theme_parseinfo, "window"},
       +        {"theme:label", &ltk_label_get_theme_parseinfo, "window"},
       +        {"theme:scrollbar", &ltk_scrollbar_get_theme_parseinfo, "window"},
       +        {"theme:menu", &ltk_menu_get_theme_parseinfo, "window"},
       +        {"theme:menuentry", &ltk_menuentry_get_theme_parseinfo, "window"},
       +        {"theme:submenu", &ltk_submenu_get_theme_parseinfo, "window"},
       +        {"theme:submenuentry", &ltk_submenuentry_get_theme_parseinfo, "window"},
       +};
       +
       +GEN_SORT_SEARCH_HELPERS(themehandler, struct theme_handlerinfo, name)
       +
       +static void
       +sort_themehandlers(void) {
       +        ltk_theme_parseinfo *parseinfo;
       +        size_t len;
       +        themehandler_sort(theme_handlers, LENGTH(theme_handlers));
       +        for (size_t i = 0; i < LENGTH(theme_handlers); i++) {
       +                theme_handlers[i].get_parseinfo(&parseinfo, &len);
       +                theme_sort(parseinfo, len);
       +        }
       +}
       +
       +/* FIXME: handle '#' or no '#' in color specification */
       +static int
       +handle_theme_setting(ltk_renderdata *renderdata, ltk_theme_parseinfo *entry, const char *value) {
       +        const char *errstr = NULL;
       +        char *endptr = NULL;
       +        /* FIXME: better warnings */
       +        long long ll;
       +        switch (entry->type) {
       +        case THEME_INT:
       +                if (entry->max > INT_MAX)
       +                        entry->max = INT_MAX;
       +                if (entry->min < INT_MIN)
       +                        entry->min = INT_MIN;
       +                *(entry->ptr.i) = ltk_strtonum(value, entry->min, entry->max, &errstr);
       +                if (errstr)
       +                        return 1;
       +                entry->initialized = 1;
       +                break;
       +        case THEME_DOUBLE:
       +                /* FIXME: maybe overflow prevention here as well */
       +                ll = ltk_strtoscalednum(value, entry->min, entry->max, &endptr, &errstr);
       +                if (errstr || *endptr != '\0')
       +                        return 1;
       +                *(entry->ptr.d) = ll / 100.0;
       +                entry->initialized = 1;
       +                break;
       +        case THEME_UINT:
       +                if (entry->max > INT_MAX)
       +                        entry->max = INT_MAX;
       +                if (entry->min < 0)
       +                        entry->min = 0;
       +                *(entry->ptr.u) = ltk_strtonum(value, entry->min, entry->max, &errstr);
       +                if (errstr)
       +                        return 1;
       +                entry->initialized = 1;
       +                break;
       +        case THEME_SIZE:
       +                if (entry->max > INT_MAX)
       +                        entry->max = INT_MAX;
       +                if (entry->min < INT_MIN)
       +                        entry->min = INT_MIN;
       +                entry->ptr.size->unit = LTK_UNIT_PX;
       +                entry->ptr.size->val = ltk_strtoscalednum(value, entry->min, entry->max, &endptr, &errstr);
       +                if (errstr)
       +                        return 1;
       +                if (*endptr == '\0') {
       +                        /* NOP */
       +                } else if (!strcmp(endptr, "px")) {
       +                        entry->ptr.size->unit = LTK_UNIT_PX;
       +                } else if (!strcmp(endptr, "pt")) {
       +                        entry->ptr.size->unit = LTK_UNIT_PT;
       +                } else if (!strcmp(endptr, "mm")) {
       +                        entry->ptr.size->unit = LTK_UNIT_MM;
       +                } else {
       +                        return 1;
       +                }
       +                entry->initialized = 1;
       +                break;
       +        case THEME_STRING:
       +                *(entry->ptr.str) = ltk_strdup(value);
       +                entry->initialized = 1;
       +                break;
       +        case THEME_COLOR:
       +                /* FIXME: warning message possibly misleading because this can fail for reasons
       +                   other than an invalid color specification */
       +                if (!(*(entry->ptr.color) = ltk_color_create(renderdata, value)))
       +                        return 1;
       +                entry->initialized = 1;
       +                break;
       +        case THEME_BOOL:
       +                if (strcmp(value, "true") == 0) {
       +                        *(entry->ptr.b) = 1;
       +                } else if (strcmp(value, "false") == 0) {
       +                        *(entry->ptr.b) = 0;
       +                } else {
       +                        return 1;
       +                }
       +                entry->initialized = 1;
       +                break;
       +        case THEME_BORDERSIDES:
       +                *(entry->ptr.border) = LTK_BORDER_NONE;
       +                for (const char *c = value; *c != '\0'; c++) {
       +                        switch (*c) {
       +                        case 't':
       +                                *(entry->ptr.border) |= LTK_BORDER_TOP;
       +                                break;
       +                        case 'b':
       +                                *(entry->ptr.border) |= LTK_BORDER_BOTTOM;
       +                                break;
       +                        case 'l':
       +                                *(entry->ptr.border) |= LTK_BORDER_LEFT;
       +                                break;
       +                        case 'r':
       +                                *(entry->ptr.border) |= LTK_BORDER_RIGHT;
       +                                break;
       +                        default:
       +                                return 1;
       +                        }
       +                }
       +                entry->initialized = 1;
       +                break;
       +        default:
       +                ltk_fatal("Invalid theme setting type. This should not happen.\n");
       +        }
       +        return 0;
       +}
       +
       +static int
       +fill_theme_defaults(ltk_renderdata *renderdata) {
       +        ltk_theme_parseinfo *parseinfo;
       +        size_t len;
       +        for (size_t j = 0; j < LENGTH(theme_handlers); j++) {
       +                theme_handlers[j].get_parseinfo(&parseinfo, &len);
       +                for (size_t i = 0; i < len; i++) {
       +                        ltk_theme_parseinfo *e = &parseinfo[i];
       +                        if (e->initialized)
       +                                continue;
       +                        switch (e->type) {
       +                        case THEME_INT:
       +                                *(e->ptr.i) = e->defaultval.i;
       +                                e->initialized = 1;
       +                                break;
       +                        case THEME_UINT:
       +                                *(e->ptr.u) = e->defaultval.u;
       +                                e->initialized = 1;
       +                                break;
       +                        case THEME_DOUBLE:
       +                                *(e->ptr.d) = e->defaultval.d;
       +                                e->initialized = 1;
       +                                break;
       +                        case THEME_SIZE:
       +                                *(e->ptr.size) = e->defaultval.size;
       +                                e->initialized = 1;
       +                                break;
       +                        case THEME_STRING:
       +                                if (e->defaultval.str)
       +                                        *(e->ptr.str) = ltk_strdup(e->defaultval.str);
       +                                else
       +                                        *(e->ptr.str) = NULL;
       +                                e->initialized = 1;
       +                                break;
       +                        case THEME_COLOR:
       +                                if (!(*(e->ptr.color) = ltk_color_create(renderdata, e->defaultval.color)))
       +                                        return 1;
       +                                e->initialized = 1;
       +                                break;
       +                        case THEME_BOOL:
       +                                *(e->ptr.b) = e->defaultval.b;
       +                                e->initialized = 1;
       +                                break;
       +                        case THEME_BORDERSIDES:
       +                                *(e->ptr.border) = e->defaultval.border;
       +                                e->initialized = 1;
       +                                break;
       +                        default:
       +                                ltk_fatal("Invalid theme setting type. This should not happen.\n");
       +                        }
       +                }
       +        }
       +        return 0;
       +}
       +
       +static void
       +uninitialize_theme(ltk_renderdata *renderdata) {
       +        ltk_theme_parseinfo *parseinfo;
       +        size_t len;
       +        for (size_t j = 0; j < LENGTH(theme_handlers); j++) {
       +                theme_handlers[j].get_parseinfo(&parseinfo, &len);
       +                for (size_t i = 0; i < len; i++) {
       +                        ltk_theme_parseinfo *e = &parseinfo[i];
       +                        if (!e->initialized)
       +                                continue;
       +                        switch (e->type) {
       +                        case THEME_STRING:
       +                                ltk_free(*(e->ptr.str));
       +                                e->initialized = 0;
       +                                break;
       +                        case THEME_COLOR:
       +                                ltk_color_destroy(renderdata, *(e->ptr.color));
       +                                e->initialized = 0;
       +                                break;
       +                        case THEME_SIZE:
       +                        case THEME_INT:
       +                        case THEME_UINT:
       +                        case THEME_BOOL:
       +                        case THEME_BORDERSIDES:
       +                        case THEME_DOUBLE:
       +                                e->initialized = 0;
       +                                break;
       +                        default:
       +                                ltk_fatal("Invalid theme setting type. This should not happen.\n");
       +                        }
       +                }
       +        }
       +}
       +
        static int
        register_keypress(ltk_array(keypress) *bindings, ltk_keybinding_cb *arr, size_t arrlen, const char *func_name, size_t func_len, ltk_keypress_binding b) {
                ltk_keybinding_cb *cb = keybinding_get_entry(arr, arrlen, func_name, func_len);
       @@ -139,8 +388,6 @@ ltk_keyrelease_bindings_destroy(ltk_array(keyrelease) *arr) {
        static void sort_keysyms(void);
        static int parse_keysym(char *text, size_t len, ltk_keysym *sym_ret);
        
       -ltk_config *global_config = NULL;
       -
        enum toktype {
                STRING,
                SECTION,
       @@ -524,23 +771,23 @@ error:
        }
        
        static void
       -push_lang_mapping(ltk_config *c) {
       -        if (c->mappings_alloc == c->mappings_len) {
       -                c->mappings_alloc = ideal_array_size(c->mappings_alloc, c->mappings_len + 1);
       -                c->mappings = ltk_reallocarray(c->mappings, c->mappings_alloc, sizeof(ltk_language_mapping));
       +push_lang_mapping(void) {
       +        if (mappings_alloc == mappings_len) {
       +                mappings_alloc = ideal_array_size(mappings_alloc, mappings_len + 1);
       +                mappings = ltk_reallocarray(mappings, mappings_alloc, sizeof(ltk_language_mapping));
                }
       -        c->mappings[c->mappings_len].lang = NULL;
       -        c->mappings[c->mappings_len].mappings = NULL;
       -        c->mappings[c->mappings_len].mappings_alloc = 0;
       -        c->mappings[c->mappings_len].mappings_len = 0;
       -        c->mappings_len++;
       +        mappings[mappings_len].lang = NULL;
       +        mappings[mappings_len].mappings = NULL;
       +        mappings[mappings_len].mappings_alloc = 0;
       +        mappings[mappings_len].mappings_len = 0;
       +        mappings_len++;
        }
        
        static void
       -push_text_mapping(ltk_config *c, char *text1, size_t len1, char *text2, size_t len2) {
       -        if (c->mappings_len == 0)
       +push_text_mapping(char *text1, size_t len1, char *text2, size_t len2) {
       +        if (mappings_len == 0)
                        return; /* I guess just fail silently... */
       -        ltk_language_mapping *m = &c->mappings[c->mappings_len - 1];
       +        ltk_language_mapping *m = &mappings[mappings_len - 1];
                if (m->mappings_alloc == m->mappings_len) {
                        m->mappings_alloc = ideal_array_size(m->mappings_alloc, m->mappings_len + 1);
                        m->mappings = ltk_reallocarray(m->mappings, m->mappings_alloc, sizeof(ltk_keytext_mapping));
       @@ -550,39 +797,36 @@ push_text_mapping(ltk_config *c, char *text1, size_t len1, char *text2, size_t l
                m->mappings_len++;
        }
        
       -static void
       -destroy_config(ltk_config *c) {
       -        for (size_t i = 0; i < c->mappings_len; i++) {
       -                ltk_free(c->mappings[i].lang);
       -                for (size_t j = 0; j < c->mappings[i].mappings_len; j++) {
       -                        ltk_free(c->mappings[i].mappings[j].from);
       -                        ltk_free(c->mappings[i].mappings[j].to);
       +void
       +ltk_config_cleanup(ltk_renderdata *renderdata) {
       +        if (mappings) {
       +                for (size_t i = 0; i < mappings_len; i++) {
       +                        ltk_free(mappings[i].lang);
       +                        for (size_t j = 0; j < mappings[i].mappings_len; j++) {
       +                                ltk_free(mappings[i].mappings[j].from);
       +                                ltk_free(mappings[i].mappings[j].to);
       +                        }
       +                        ltk_free(mappings[i].mappings);
                        }
       -                ltk_free(c->mappings[i].mappings);
       +                ltk_free(mappings);
       +                mappings = NULL;
       +                mappings_len = mappings_alloc = 0;
                }
       -        ltk_free(c->general.line_editor);
       -        ltk_free(c->mappings);
       -        ltk_free(c);
       +        uninitialize_theme(renderdata);
        }
        
       -void
       -ltk_config_cleanup(void) {
       -        if (global_config)
       -                destroy_config(global_config);
       -        global_config = NULL;
       -}
       -
       -ltk_config *
       -ltk_config_get(void) {
       -        return global_config;
       +/* FIXME: error if not initialized */
       +ltk_general_config *
       +ltk_config_get_general(void) {
       +        return &general_config;
        }
        
        int
        ltk_config_get_language_index(char *lang, size_t *idx_ret) {
       -        if (!global_config)
       +        if (!mappings)
                        return 1;
       -        for (size_t i = 0; i < global_config->mappings_len; i++) {
       -                if (!strcmp(lang, global_config->mappings[i].lang)) {
       +        for (size_t i = 0; i < mappings_len; i++) {
       +                if (!strcmp(lang, mappings[i].lang)) {
                                *idx_ret = i;
                                return 0;
                        }
       @@ -592,9 +836,9 @@ ltk_config_get_language_index(char *lang, size_t *idx_ret) {
        
        ltk_language_mapping *
        ltk_config_get_language_mapping(size_t idx) {
       -        if (!global_config || idx >= global_config->mappings_len)
       +        if (idx >= mappings_len)
                        return NULL;
       -        return &global_config->mappings[idx];
       +        return &mappings[idx];
        }
        
        int
       @@ -605,33 +849,32 @@ str_array_prefix(const char *str, const char *ar, size_t len) {
                return !strncmp(str, ar, slen);
        }
        
       +/* FIXME: The current model is kind of weird because most parts of the config
       +   are stored in the other object files. This makes it difficult to support
       +   reloading of the config since the old config needs to be kept until the
       +   new config has been successfully loaded, but the parseinfos include direct
       +   pointers to the config. It might be better to just have one huge config
       +   struct that includes everything. That would also make it a bit clearer when
       +   something hasn't been initialized yet. */
        /* WARNING: errstr must be freed! */
        /* FIXME: make ltk_load_file give size_t; handle errors there (copy from ledit) */
        static int
        load_from_text(
       +    ltk_renderdata *renderdata,
            const char *filename,
            char *file_contents,
            size_t len,
            char **errstr) {
       -        ltk_config *config = ltk_malloc(sizeof(ltk_config));
       -        config->mappings = NULL;
       -        config->mappings_alloc = config->mappings_len = 0;
       -        config->general.explicit_focus = 0;
       -        config->general.all_activatable = 0;
       -        config->general.line_editor = NULL;
       -        config->general.dpi_scale = 1.0;
       -        config->general.fixed_dpi = 480; /* 5 * 96 */
       -#if USE_XRANDR
       -        config->general.mixed_dpi = 1;
       -#else
       -        config->general.mixed_dpi = 0;
       -#endif
       +        sort_keysyms();
       +        sort_keybindings();
       +        sort_themehandlers();
        
                struct lexstate s = {filename, file_contents, len, 0, 1, 0};
                struct token tok = next_token(&s);
                int start_of_line = 1;
                char *msg = NULL;
                struct token secttok;
       +        txtbuf *themeval = txtbuf_new();
                while (tok.type != END) {
                        switch (tok.type) {
                        case SECTION:
       @@ -645,94 +888,7 @@ load_from_text(
                                        msg = "Section must be alone on line";
                                        goto error;
                                }
       -                        /* FIXME: generalize (at least once more options are added) */
       -                        if (str_array_equal("general", secttok.text, secttok.len)) {
       -                                struct token prev1tok, prev2tok;
       -                                while (1) {
       -                                        tok = next_token(&s);
       -                                        if (tok.type == SECTION || tok.type == END)
       -                                                break;
       -                                        else if (tok.type == NEWLINE)
       -                                                continue;
       -                                        prev2tok = tok;
       -                                        tok = next_token(&s);
       -                                        prev1tok = tok;
       -                                        tok = next_token(&s);
       -                                        if (prev2tok.type != STRING || prev1tok.type != EQUALS || tok.type != STRING) {
       -                                                msg = "Invalid assignment statement";
       -                                                goto error;
       -                                        }
       -                                        if (str_array_equal("explicit-focus", prev2tok.text, prev2tok.len)) {
       -                                                if (str_array_equal("true", tok.text, tok.len)) {
       -                                                        config->general.explicit_focus = 1;
       -                                                } else if (str_array_equal("false", tok.text, tok.len)) {
       -                                                        config->general.explicit_focus = 0;
       -                                                } else {
       -                                                        msg = "Invalid boolean setting";
       -                                                        goto error;
       -                                                }
       -                                        } else if (str_array_equal("all-activatable", prev2tok.text, prev2tok.len)) {
       -                                                if (str_array_equal("true", tok.text, tok.len)) {
       -                                                        config->general.all_activatable = 1;
       -                                                } else if (str_array_equal("false", tok.text, tok.len)) {
       -                                                        config->general.all_activatable = 0;
       -                                                } else {
       -                                                        msg = "Invalid boolean setting";
       -                                                        goto error;
       -                                                }
       -                                        /* FIXME: warning if set to true but xrandr not enabled */
       -                                        } else if (str_array_equal("mixed-dpi", prev2tok.text, prev2tok.len)) {
       -                                                if (str_array_equal("true", tok.text, tok.len)) {
       -                                                        config->general.mixed_dpi = 1;
       -                                                } else if (str_array_equal("false", tok.text, tok.len)) {
       -                                                        config->general.mixed_dpi = 0;
       -                                                } else {
       -                                                        msg = "Invalid boolean setting";
       -                                                        goto error;
       -                                                }
       -                                        } else if (str_array_equal("line-editor", prev2tok.text, prev2tok.len)) {
       -                                                config->general.line_editor = ltk_strndup(tok.text, tok.len);
       -                                        } else if (str_array_equal("fixed-dpi", prev2tok.text, prev2tok.len)) {
       -                                                /* FIXME: remove this allocation! */
       -                                                char *tmp = ltk_strndup(tok.text, tok.len);
       -                                                /* FIXME: proper min/max values for dpi */
       -                                                const char *tmp_err = NULL;
       -                                                config->general.fixed_dpi = ltk_strtonum(tmp, 10, 4000, &tmp_err);
       -                                                ltk_free(tmp);
       -                                                if (tmp_err) {
       -                                                        msg = "Invalid DPI setting";
       -                                                        goto error;
       -                                                }
       -                                                /* because of weird scaling that is currently used, see event_xlib.c */
       -                                                config->general.fixed_dpi *= 5;
       -                                        } else if (str_array_equal("dpi-scale", prev2tok.text, prev2tok.len)) {
       -                                                /* FIXME: remove this allocation! */
       -                                                char *tmp = ltk_strndup(tok.text, tok.len);
       -                                                const char *tmp_err = NULL;
       -                                                char *ep = NULL;
       -                                                /* FIXME: proper min/max values for scale */
       -                                                config->general.dpi_scale = ltk_strtoscalednum(tmp, 10, 10000, &ep, &tmp_err);
       -                                                char c = *ep;
       -                                                ltk_free(tmp);
       -                                                if (tmp_err || c != '\0') {
       -                                                        msg = "Invalid DPI scale setting";
       -                                                        goto error;
       -                                                }
       -                                                config->general.dpi_scale /= 100;
       -                                        } else {
       -                                                msg = "Invalid setting";
       -                                                goto error;
       -                                        }
       -                                        tok = next_token(&s);
       -                                        if (tok.type == END) {
       -                                                break;
       -                                        } else if (tok.type != NEWLINE) {
       -                                                msg = "Invalid assignment statement";
       -                                                goto error;
       -                                        }
       -                                        start_of_line = 1;
       -                                }
       -                        } else if (str_array_prefix("key-binding:", secttok.text, secttok.len)) {
       +                        if (str_array_prefix("key-binding:", secttok.text, secttok.len)) {
                                        int ret = 0;
                                        char *widget = secttok.text + strlen("key-binding:");
                                        size_t len = secttok.len - strlen("key-binding:");
       @@ -746,7 +902,7 @@ load_from_text(
                                        }
                                } else if (str_array_equal("key-mapping", secttok.text, secttok.len)) {
                                        int lang_init = 0;
       -                                push_lang_mapping(config);
       +                                push_lang_mapping();
                                        struct token prev1tok, prev2tok;
                                        while (1) {
                                                tok = next_token(&s);
       @@ -770,14 +926,14 @@ load_from_text(
                                                                msg = "Language already set";
                                                                goto error;
                                                        }
       -                                                config->mappings[config->mappings_len - 1].lang = ltk_strndup(tok.text, tok.len);
       +                                                mappings[mappings_len - 1].lang = ltk_strndup(tok.text, tok.len);
                                                        lang_init = 1;
                                                } else if (str_array_equal("map", prev2tok.text, prev2tok.len)) {
                                                        if (prev1tok.type != STRING || tok.type != STRING) {
                                                                msg = "Invalid map statement";
                                                                goto error;
                                                        }
       -                                                push_text_mapping(config, prev1tok.text, prev1tok.len, tok.text, tok.len);
       +                                                push_text_mapping(prev1tok.text, prev1tok.len, tok.text, tok.len);
                                                } else {
                                                        msg = "Invalid statement in language mapping";
                                                        goto error;
       @@ -796,8 +952,58 @@ load_from_text(
                                                goto error;
                                        }
                                } else {
       -                                msg = "Invalid section";
       -                                goto error;
       +                                struct token prev1tok, prev2tok;
       +                                struct theme_handlerinfo *handler = themehandler_get_entry(
       +                                        theme_handlers, LENGTH(theme_handlers), secttok.text, secttok.len
       +                                );
       +                                if (!handler) {
       +                                        msg = "Invalid section";
       +                                        goto error;
       +                                }
       +                                ltk_theme_parseinfo *parseinfo;
       +                                size_t parseinfo_len;
       +                                handler->get_parseinfo(&parseinfo, &parseinfo_len);
       +                                while (1) {
       +                                        tok = next_token(&s);
       +                                        if (tok.type == SECTION || tok.type == END)
       +                                                break;
       +                                        else if (tok.type == NEWLINE)
       +                                                continue;
       +                                        prev2tok = tok;
       +                                        tok = next_token(&s);
       +                                        prev1tok = tok;
       +                                        tok = next_token(&s);
       +                                        if (prev2tok.type != STRING || prev1tok.type != EQUALS || tok.type != STRING) {
       +                                                msg = "Syntax error in assignment statement";
       +                                                goto error;
       +                                        }
       +                                        ltk_theme_parseinfo *parse_entry = theme_get_entry(
       +                                                parseinfo, parseinfo_len, prev2tok.text, prev2tok.len
       +                                        );
       +                                        if (!parse_entry) {
       +                                                msg = "Invalid left-hand side in assignment statement";
       +                                                goto error;
       +                                        } else if (parse_entry->initialized) {
       +                                                msg = "Duplicate assignment";
       +                                                goto error;
       +                                        }
       +                                        /* temporarly copy to txtbuf so it is NUL-terminated (the alternative
       +                                           would be to use replacements for ltk_strtonum, etc. that accept
       +                                           a length parameter) */
       +                                        txtbuf_set_textn(themeval, tok.text, tok.len);
       +                                        if (handle_theme_setting(renderdata, parse_entry, themeval->text)) {
       +                                                msg = "Invalid right-hand side in assignment";
       +                                                goto error;
       +                                        }
       +                                        tok = next_token(&s);
       +                                        if (tok.type == END) {
       +                                                break;
       +                                        } else if (tok.type != NEWLINE) {
       +                                                msg = "Syntax error in assignment statement";
       +                                                goto error;
       +                                        }
       +                                        start_of_line = 1;
       +                                }
                                }
                                break;
                        case NEWLINE:
       @@ -809,7 +1015,12 @@ load_from_text(
                                break;
                        }
                }
       -        global_config = config;
       +        /* FIXME: better error reporting */
       +        if (fill_theme_defaults(renderdata)) {
       +                *errstr = ltk_strdup("Unable to load theme defaults");
       +                goto errornomsg;
       +        }
       +        txtbuf_destroy(themeval);
                return 0;
        error:
                if (msg) {
       @@ -818,22 +1029,21 @@ error:
                        );
                }
        errornomsg:
       -        destroy_config(config);
       +        ltk_config_cleanup(renderdata);
       +        txtbuf_destroy(themeval);
                return 1;
        }
        
        int
       -ltk_config_parsefile(const char *filename, char **errstr) {
       +ltk_config_parsefile(ltk_renderdata *renderdata, const char *filename, char **errstr) {
                unsigned long len = 0;
                char *ferrstr = NULL;
       -        sort_keysyms();
       -        sort_keybindings();
                char *file_contents = ltk_read_file(filename, &len, &ferrstr);
                if (!file_contents) {
                        *errstr = ltk_print_fmt("Unable to open file \"%s\": %s", filename, ferrstr);
                        return 1;
                }
       -        int ret = load_from_text(filename, file_contents, len, errstr);
       +        int ret = load_from_text(renderdata, filename, file_contents, len, errstr);
                ltk_free(file_contents);
                return ret;
        }
       @@ -856,11 +1066,9 @@ const char *default_config = "[general]\n"
        
        /* FIXME: improve this configuration */
        int
       -ltk_config_load_default(char **errstr) {
       -        sort_keysyms();
       -        sort_keybindings();
       +ltk_config_load_default(ltk_renderdata *renderdata, char **errstr) {
                char *config_copied = ltk_strdup(default_config);
       -        int ret = load_from_text("<default config>", config_copied, strlen(config_copied), errstr);
       +        int ret = load_from_text(renderdata, "<default config>", config_copied, strlen(config_copied), errstr);
                ltk_free(config_copied);
                return ret;
        }
   DIR diff --git a/src/ltk/config.h b/src/ltk/config.h
       @@ -20,7 +20,9 @@
        #include <stddef.h>
        
        #include "array.h"
       +#include "color.h"
        #include "widget.h"
       +#include "graphics.h"
        #include "eventdefs.h"
        
        typedef enum{
       @@ -56,18 +58,12 @@ typedef struct {
        typedef struct {
                char *line_editor;
                double dpi_scale;
       -        unsigned int fixed_dpi;
       -        char mixed_dpi;
       -        char explicit_focus;
       -        char all_activatable;
       +        double fixed_dpi;
       +        int mixed_dpi;
       +        int explicit_focus;
       +        int all_activatable;
        } ltk_general_config;
        
       -typedef struct {
       -        ltk_language_mapping *mappings;
       -        size_t mappings_alloc, mappings_len;
       -        ltk_general_config general;
       -} ltk_config;
       -
        typedef int (*ltk_keybinding_func)(ltk_widget *, ltk_key_event *);
        
        typedef struct {
       @@ -85,15 +81,68 @@ typedef struct {
                ltk_keybinding_cb cb;
        } ltk_keyrelease_cfg;
        
       +typedef enum {
       +        THEME_STRING,
       +        THEME_COLOR,
       +        THEME_INT,
       +        THEME_UINT,
       +        THEME_BOOL,
       +        THEME_BORDERSIDES,
       +        THEME_SIZE,
       +        THEME_DOUBLE,
       +} ltk_theme_datatype;
       +
       +typedef struct {
       +        char *key;
       +        ltk_theme_datatype type;
       +        /* Note: Bool and int are both integers, but they are
       +           separate just to make it a bit clearer */
       +        union {
       +                char **str;
       +                ltk_color **color;
       +                int *i;
       +                unsigned int *u;
       +                int *b;
       +                ltk_border_sides *border;
       +                ltk_size *size;
       +                double *d;
       +        } ptr;
       +        /* Note: The default color is also given as a string
       +           because it has to be allocated first (it is only a
       +           different entry in the union in order to make it
       +           a bit clearer) */
       +        union {
       +                char *str;
       +                char *color;
       +                int i;
       +                unsigned int u;
       +                int b;
       +                ltk_border_sides border;
       +                ltk_size size;
       +                double d;
       +        } defaultval;
       +        /* FIXME: min/max doesn't make too much sense for sizes since they
       +           can use different units, but that shouldn't matter for now because
       +           min/max is only used as a sanity check to avoid extreme sizes or
       +           negative sizes where that isn't allowed */
       +        /* only for integers, doubles, or sizes */
       +        /* doubles are weird at the moment since only two decimal places are
       +           allowed. The min/max for doubles is the min/max given here, divided
       +           by 100, i.e. to allow the range 1.0-10.0, min must be 100 and max
       +           must be 1000. */
       +        long long min, max;
       +        int initialized;
       +} ltk_theme_parseinfo;
       +
        LTK_ARRAY_INIT_DECL(keypress, ltk_keypress_cfg)
        LTK_ARRAY_INIT_DECL(keyrelease, ltk_keyrelease_cfg)
        
       -void ltk_config_cleanup(void);
       -ltk_config *ltk_config_get(void);
       +void ltk_config_cleanup(ltk_renderdata *renderdata);
       +ltk_general_config *ltk_config_get_general(void);
        int ltk_config_get_language_index(char *lang, size_t *idx_ret);
        ltk_language_mapping *ltk_config_get_language_mapping(size_t idx);
       -int ltk_config_parsefile(const char *filename, char **errstr);
       -int ltk_config_load_default(char **errstr);
       +int ltk_config_parsefile(ltk_renderdata *renderdata, const char *filename, char **errstr);
       +int ltk_config_load_default(ltk_renderdata *renderdata, char **errstr);
        
        void ltk_keypress_bindings_destroy(ltk_array(keypress) *arr);
        void ltk_keyrelease_bindings_destroy(ltk_array(keyrelease) *arr);
   DIR diff --git a/src/ltk/entry.c b/src/ltk/entry.c
       @@ -35,7 +35,6 @@
        #include "memory.h"
        #include "rect.h"
        #include "text.h"
       -#include "theme.h"
        #include "txtbuf.h"
        #include "util.h"
        #include "widget.h"
       @@ -193,21 +192,11 @@ static ltk_theme_parseinfo parseinfo[] = {
                {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"selection-color", THEME_COLOR, {.color = &theme.selection_color}, {.color = "#000000"}, 0, 0, 0},
        };
       -static int parseinfo_sorted = 0;
       -
       -int
       -ltk_entry_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
       -        return ltk_theme_handle_value(data, "entry", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
       -}
       -
       -int
       -ltk_entry_fill_theme_defaults(ltk_renderdata *data) {
       -        return ltk_theme_fill_defaults(data, "entry", parseinfo, LENGTH(parseinfo));
       -}
        
        void
       -ltk_entry_uninitialize_theme(ltk_renderdata *data) {
       -        ltk_theme_uninitialize(data, parseinfo, LENGTH(parseinfo));
       +ltk_entry_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
       +        *p = parseinfo;
       +        *len = LENGTH(parseinfo);
        }
        
        /* FIXME: draw cursor in different color on selection side that will be expanded */
       @@ -576,15 +565,15 @@ static int
        edit_external(ltk_widget *self, ltk_key_event *event) {
                (void)event;
                ltk_entry *entry = LTK_CAST_ENTRY(self);
       -        ltk_config *config = ltk_config_get();
       +        ltk_general_config *config = ltk_config_get_general();
                /* FIXME: allow arguments to key mappings - this would allow to have different key mappings
                   for different editors instead of just one command */
       -        if (!config->general.line_editor) {
       +        if (!config->line_editor) {
                        ltk_warn("Unable to run external editing command: line editor not configured\n");
                } else {
                        /* FIXME: somehow show that there was an error if this returns 1? */
                        /* FIXME: change interface to not require length of cmd */
       -                ltk_call_cmd(LTK_CAST_WIDGET(entry), config->general.line_editor, strlen(config->general.line_editor), entry->text, entry->len);
       +                ltk_call_cmd(LTK_CAST_WIDGET(entry), config->line_editor, strlen(config->line_editor), entry->text, entry->len);
                }
                return 0;
        }
   DIR diff --git a/src/ltk/entry.h b/src/ltk/entry.h
       @@ -40,15 +40,4 @@ typedef struct {
        
        ltk_entry *ltk_entry_create(ltk_window *window, char *text);
        
       -/* FIXME: these should be private to ltk */
       -void ltk_entry_cleanup(void);
       -void ltk_entry_get_keybinding_parseinfo(
       -        ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret,
       -        ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret,
       -        ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret
       -);
       -int ltk_entry_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
       -int ltk_entry_fill_theme_defaults(ltk_renderdata *data);
       -void ltk_entry_uninitialize_theme(ltk_renderdata *data);
       -
        #endif /* LTK_ENTRY_H */
   DIR diff --git a/src/ltk/event_xlib.c b/src/ltk/event_xlib.c
       @@ -235,8 +235,8 @@ ltk_recalc_renderwindow_dpi(ltk_renderwindow *window) {
        static void
        update_monitor_config(ltk_renderdata *renderdata, ltk_renderwindow **windows, size_t num_windows) {
                int nmon;
       -        ltk_config *config = ltk_config_get();
       -        if (!config->general.mixed_dpi)
       +        ltk_general_config *config = ltk_config_get_general();
       +        if (!config->mixed_dpi)
                        return;
                XRRMonitorInfo *mi = XRRGetMonitors(renderdata->dpy, renderdata->root_window, 1, &nmon);
                if (nmon > 0 && !renderdata->monitors)
       @@ -246,7 +246,7 @@ update_monitor_config(ltk_renderdata *renderdata, ltk_renderwindow **windows, si
                        /* FIXME: This only uses the width for the calculation. It should be the same if using
                           the height, but is that guaranteed? */
                        /* FIXME: can width or mwidth ever by negative? */
       -                info.dpi = (unsigned int)round(config->general.dpi_scale * (info.width / (info.mwidth / 127.0)));
       +                info.dpi = (unsigned int)round(config->dpi_scale * (info.width / (info.mwidth / 127.0)));
                        /* FIXME: need to adjust default dpi and document */
                        /* -> config file dpi should still be regular dpi */
                        /* FIXME: check for overflows in the later pixel computation */
   DIR diff --git a/src/ltk/ini.c b/src/ltk/ini.c
       @@ -1,201 +0,0 @@
       -/* inih -- simple .INI file parser
       -
       -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
       -#include <stdlib.h>
       -#endif
       -
       -#define MAX_SECTION 50
       -#define MAX_NAME 50
       -
       -/* Strip whitespace chars off end of given string, in place. Return s. */
       -static char* rstrip(char* s)
       -{
       -    char* p = s + strlen(s);
       -    while (p > s && isspace((unsigned char)(*--p)))
       -        *p = '\0';
       -    return s;
       -}
       -
       -/* Return pointer to first non-whitespace char in given string. */
       -static char* 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 null at end of string if neither found. Inline comment must
       -   be prefixed by a whitespace character to register as a comment. */
       -static char* 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;
       -}
       -
       -/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
       -static char* strncpy0(char* dest, const char* src, size_t size)
       -{
       -    strncpy(dest, src, size);
       -    dest[size - 1] = '\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];
       -#else
       -    char* line;
       -#endif
       -    char section[MAX_SECTION] = "";
       -    char prev_name[MAX_NAME] = "";
       -
       -    char* start;
       -    char* end;
       -    char* name;
       -    char* value;
       -    int lineno = 0;
       -    int error = 0;
       -
       -#if !INI_USE_STACK
       -    line = (char*)malloc(INI_MAX_LINE);
       -    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, INI_MAX_LINE, stream) != NULL) {
       -        lineno++;
       -
       -        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 = lskip(rstrip(start));
       -
       -        if (*start == ';' || *start == '#') {
       -            /* Per Python configparser, allow both ; and # comments at the
       -               start of a line */
       -        }
       -#if INI_ALLOW_MULTILINE
       -        else if (*prev_name && *start && start > line) {
       -            /* 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 = find_chars_or_comment(start + 1, "]");
       -            if (*end == ']') {
       -                *end = '\0';
       -                strncpy0(section, start + 1, sizeof(section));
       -                *prev_name = '\0';
       -            }
       -            else if (!error) {
       -                /* No ']' found on section line */
       -                error = lineno;
       -            }
       -        }
       -        else if (*start) {
       -            /* Not a comment, must be a name[=:]value pair */
       -            end = find_chars_or_comment(start, "=:");
       -            if (*end == '=' || *end == ':') {
       -                *end = '\0';
       -                name = rstrip(start);
       -                value = end + 1;
       -#if INI_ALLOW_INLINE_COMMENTS
       -                end = find_chars_or_comment(value, NULL);
       -                if (*end)
       -                    *end = '\0';
       -#endif
       -                value = lskip(value);
       -                rstrip(value);
       -
       -                /* Valid name[=:]value pair found, call handler */
       -                strncpy0(prev_name, name, sizeof(prev_name));
       -                if (!HANDLER(user, section, name, value) && !error)
       -                    error = lineno;
       -            }
       -            else if (!error) {
       -                /* No '=' or ':' found on name[=:]value line */
       -                error = lineno;
       -            }
       -        }
       -
       -#if INI_STOP_ON_FIRST_ERROR
       -        if (error)
       -            break;
       -#endif
       -    }
       -
       -#if !INI_USE_STACK
       -    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;
       -}
   DIR diff --git a/src/ltk/ini.h b/src/ltk/ini.h
       @@ -1,104 +0,0 @@
       -/* inih -- simple .INI file parser
       -
       -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
       -
       -/* Typedef for prototype of handler function. */
       -#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).
       -*/
       -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. */
       -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. */
       -int ini_parse_stream(ini_reader reader, void* stream, 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 http://code.google.com/p/inih/issues/detail?id=21 */
       -#ifndef INI_ALLOW_BOM
       -#define INI_ALLOW_BOM 1
       -#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, zero to use heap (malloc/free). */
       -#ifndef INI_USE_STACK
       -#define INI_USE_STACK 1
       -#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
       -
       -/* Maximum line length for any line in INI file. */
       -#ifndef INI_MAX_LINE
       -#define INI_MAX_LINE 200
       -#endif
       -
       -#ifdef __cplusplus
       -}
       -#endif
       -
       -#endif /* __INI_H__ */
   DIR diff --git a/src/ltk/label.c b/src/ltk/label.c
       @@ -26,7 +26,6 @@
        #include "text.h"
        #include "label.h"
        #include "graphics.h"
       -#include "theme.h"
        
        #define MAX_LABEL_PADDING 50000
        
       @@ -63,8 +62,6 @@ static struct {
                ltk_size pad;
        } theme;
        
       -int parseinfo_sorted = 0;
       -
        static ltk_theme_parseinfo parseinfo[] = {
                {"bg-color", THEME_COLOR, {.color = &theme.bg_color}, {.color = "#000000"}, 0, 0, 0},
                {"bg-color-active", THEME_COLOR, {.color = &theme.bg_color_active}, {.color = "#222222"}, 0, 0, 0},
       @@ -72,19 +69,10 @@ static ltk_theme_parseinfo parseinfo[] = {
                {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0},
        };
        
       -int
       -ltk_label_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
       -        return ltk_theme_handle_value(data, "label", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
       -}
       -
       -int
       -ltk_label_fill_theme_defaults(ltk_renderdata *data) {
       -        return ltk_theme_fill_defaults(data, "label", parseinfo, LENGTH(parseinfo));
       -}
       -
        void
       -ltk_label_uninitialize_theme(ltk_renderdata *data) {
       -        ltk_theme_uninitialize(data, parseinfo, LENGTH(parseinfo));
       +ltk_label_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
       +        *p = parseinfo;
       +        *len = LENGTH(parseinfo);
        }
        
        static void
   DIR diff --git a/src/ltk/label.h b/src/ltk/label.h
       @@ -29,10 +29,6 @@ typedef struct {
                ltk_text_line *tl;
        } ltk_label;
        
       -int ltk_label_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
       -int ltk_label_fill_theme_defaults(ltk_renderdata *data);
       -void ltk_label_uninitialize_theme(ltk_renderdata *data);
       -
        ltk_label *ltk_label_create(ltk_window *window, char *text);
        
        #endif /* LTK_LABEL_H */
   DIR diff --git a/src/ltk/ltk.c b/src/ltk/ltk.c
       @@ -33,7 +33,6 @@
        #include "eventdefs.h"
        #include "graphics.h"
        #include "image.h"
       -#include "ini.h"
        #include "label.h"
        #include "macros.h"
        #include "memory.h"
       @@ -43,8 +42,7 @@
        #include "text.h"
        #include "util.h"
        #include "widget.h"
       -
       -#define MAX_WINDOW_FONT_SIZE 200
       +#include "widget_internal.h"
        
        typedef struct {
                char *tmpfile;
       @@ -87,91 +85,56 @@ static size_t timers_num = 0;
        static size_t timers_alloc = 0;
        
        static void ltk_handle_event(ltk_event *event);
       -static void ltk_load_theme(const char *path);
       -static void ltk_uninitialize_theme(void);
       -static int ltk_ini_handler(void *renderdata, const char *widget, const char *prop, const char *value);
        
        static short running = 1;
        
        typedef struct {
                char *name;
       -        int (*ini_handler)(ltk_renderdata *, const char *, const char *);
       -        int (*fill_theme_defaults)(ltk_renderdata *);
       -        void (*uninitialize_theme)(ltk_renderdata *);
                void (*cleanup)(void);
        } ltk_widget_funcs;
        
       -/* FIXME: use binary search when searching for the widget */
       +/* FIXME: I guess the names aren't needed anymore here, but who
       +   knows if I'll need them again sometime... */
        static ltk_widget_funcs widget_funcs[] = {
                {
                        .name = "box",
       -                .ini_handler = NULL,
       -                .fill_theme_defaults = NULL,
       -                .uninitialize_theme = NULL,
                        .cleanup = NULL,
                },
                {
                        .name = "button",
       -                .ini_handler = &ltk_button_ini_handler,
       -                .fill_theme_defaults = &ltk_button_fill_theme_defaults,
       -                .uninitialize_theme = &ltk_button_uninitialize_theme,
                        .cleanup = NULL,
                },
                {
                        .name = "entry",
       -                .ini_handler = &ltk_entry_ini_handler,
       -                .fill_theme_defaults = &ltk_entry_fill_theme_defaults,
       -                .uninitialize_theme = &ltk_entry_uninitialize_theme,
                        .cleanup = &ltk_entry_cleanup,
                },
                {
                        .name = "grid",
       -                .ini_handler = NULL,
       -                .fill_theme_defaults = NULL,
       -                .uninitialize_theme = NULL,
                        .cleanup = NULL,
                },
                {
                        .name = "label",
       -                .ini_handler = &ltk_label_ini_handler,
       -                .fill_theme_defaults = &ltk_label_fill_theme_defaults,
       -                .uninitialize_theme = &ltk_label_uninitialize_theme,
                        .cleanup = NULL,
                },
                {
                        /* FIXME: this is actually image_widget */
                        .name = "image",
       -                .ini_handler = NULL,
       -                .fill_theme_defaults = NULL,
       -                .uninitialize_theme = NULL,
                        .cleanup = NULL,
                },
                {
                        .name = "menu",
       -                .ini_handler = &ltk_menu_ini_handler,
       -                .fill_theme_defaults = &ltk_menu_fill_theme_defaults,
       -                .uninitialize_theme = &ltk_menu_uninitialize_theme,
                        .cleanup = NULL,
                },
                {
                        .name = "menuentry",
       -                .ini_handler = &ltk_menuentry_ini_handler,
       -                .fill_theme_defaults = &ltk_menuentry_fill_theme_defaults,
       -                .uninitialize_theme = &ltk_menuentry_uninitialize_theme,
                        .cleanup = NULL,
                },
                {
                        .name = "submenu",
       -                .ini_handler = &ltk_submenu_ini_handler,
       -                .fill_theme_defaults = &ltk_submenu_fill_theme_defaults,
       -                .uninitialize_theme = &ltk_submenu_uninitialize_theme,
                        .cleanup = NULL,
                },
                {
                        .name = "submenuentry",
       -                .ini_handler = &ltk_submenuentry_ini_handler,
       -                .fill_theme_defaults = &ltk_submenuentry_fill_theme_defaults,
       -                .uninitialize_theme = &ltk_submenuentry_uninitialize_theme,
                        .cleanup = NULL,
                         /*
                         This "widget" is only needed to have separate styles for regular
       @@ -186,17 +149,11 @@ static ltk_widget_funcs widget_funcs[] = {
                },
                {
                        .name = "scrollbar",
       -                .ini_handler = &ltk_scrollbar_ini_handler,
       -                .fill_theme_defaults = &ltk_scrollbar_fill_theme_defaults,
       -                .uninitialize_theme = &ltk_scrollbar_uninitialize_theme,
                        .cleanup = NULL,
                },
                {
                        /* Handler for window theme. */
                        .name = "window",
       -                .ini_handler = &ltk_window_ini_handler,
       -                .fill_theme_defaults = &ltk_window_fill_theme_defaults,
       -                .uninitialize_theme = &ltk_window_uninitialize_theme,
                        .cleanup = &ltk_window_cleanup,
                }
        };
       @@ -216,32 +173,29 @@ ltk_init(void) {
                        ltk_fatal_errno("Unable to setup ltk directory.\n");
                shared_data.cur_kbd = 0;
        
       +        shared_data.renderdata = ltk_renderer_create();
       +        if (!shared_data.renderdata)
       +                return 1; /* FIXME: clean up */
       +
                /* FIXME: search different directories for config */
       -        /* FIXME: don't print error if config or theme file doesn't exist */
       +        /* FIXME: don't print error if config file doesn't exist */
                char *config_path = ltk_strcat_useful(ltk_dir, "/ltk.cfg");
       -        char *theme_path;
       +        ltk_free0(ltk_dir);
                char *errstr = NULL;
       -        if (ltk_config_parsefile(config_path, &errstr)) {
       +        if (ltk_config_parsefile(shared_data.renderdata, config_path, &errstr)) {
                        if (errstr) {
                                ltk_warn("Unable to load config: %s\n", errstr);
                                ltk_free0(errstr);
                        }
       -                if (ltk_config_load_default(&errstr)) {
       +                if (ltk_config_load_default(shared_data.renderdata, &errstr)) {
                                /* FIXME: I guess errstr isn't freed here, but whatever */
                                /* FIXME: return error instead of dying */
                                ltk_fatal("Unable to load default config: %s\n", errstr);
                        }
                }
                ltk_free0(config_path);
       -        theme_path = ltk_strcat_useful(ltk_dir, "/theme.ini");
       -        ltk_free0(ltk_dir);
       -        shared_data.renderdata = ltk_renderer_create();
       -        if (!shared_data.renderdata)
       -                return 1; /* FIXME: clean up */
       +
                ltk_events_init(shared_data.renderdata);
       -        ltk_load_theme(theme_path);
       -        ltk_free0(theme_path);
       -        /* FIXME: maybe "general" theme instead of window theme? */
                ltk_window_theme *window_theme = ltk_window_get_theme();
                shared_data.text_context = ltk_text_context_create(shared_data.renderdata, window_theme->font);
                shared_data.clipboard = ltk_clipboard_create(shared_data.renderdata);
       @@ -382,7 +336,8 @@ ltk_mainloop(void) {
        
        void
        ltk_deinit(void) {
       -        if (running)
       +        /* if renderdata is NULL, the other initialization can't have happened either */
       +        if (running || !shared_data.renderdata)
                        return;
                if (shared_data.cmds) {
                        for (size_t i = 0; i < ltk_array_len(shared_data.cmds); i++) {
       @@ -403,11 +358,11 @@ ltk_deinit(void) {
                if (shared_data.rwindows)
                        ltk_array_destroy(rwindow, shared_data.rwindows);
                shared_data.rwindows = NULL;
       -        ltk_config_cleanup();
                for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
                        if (widget_funcs[i].cleanup)
                                widget_funcs[i].cleanup();
                }
       +        ltk_config_cleanup(shared_data.renderdata);
                if (shared_data.text_context)
                        ltk_text_context_destroy(shared_data.text_context);
                shared_data.text_context = NULL;
       @@ -415,10 +370,7 @@ ltk_deinit(void) {
                        ltk_clipboard_destroy(shared_data.clipboard);
                shared_data.clipboard = NULL;
                ltk_events_cleanup();
       -        if (shared_data.renderdata) {
       -                ltk_uninitialize_theme();
       -                ltk_renderer_destroy(shared_data.renderdata);
       -        }
       +        ltk_renderer_destroy(shared_data.renderdata);
                shared_data.renderdata = NULL;
        }
        
       @@ -511,45 +463,6 @@ ltk_register_timer(long first, long repeat, void (*callback)(ltk_callback_arg da
                return id;
        }
        
       -/* FIXME: standardize return codes - usually, 0 is returned on success, but ini.h
       -   uses 1 on success, so this is all a bit confusing */
       -/* FIXME: switch away from ini.h */
       -static int
       -ltk_ini_handler(void *renderdata, const char *widget, const char *prop, const char *value) {
       -        for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
       -                if (widget_funcs[i].ini_handler && !strcmp(widget, widget_funcs[i].name)) {
       -                        widget_funcs[i].ini_handler(renderdata, prop, value);
       -                        return 1;
       -                }
       -        }
       -        return 0;
       -}
       -
       -/* FIXME: don't call ltk_fatal, instead return error from ltk_init */
       -static void
       -ltk_load_theme(const char *path) {
       -        /* FIXME: give line number in error message */
       -        if (ini_parse(path, ltk_ini_handler, shared_data.renderdata) != 0) {
       -                ltk_warn("Unable to load theme.\n");
       -        }
       -        for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
       -                if (widget_funcs[i].fill_theme_defaults) {
       -                        if (widget_funcs[i].fill_theme_defaults(shared_data.renderdata)) {
       -                                ltk_uninitialize_theme();
       -                                ltk_fatal("Unable to load theme defaults.\n");
       -                        }
       -                }
       -        }
       -}
       -
       -static void
       -ltk_uninitialize_theme(void) {
       -        for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
       -                if (widget_funcs[i].uninitialize_theme)
       -                        widget_funcs[i].uninitialize_theme(shared_data.renderdata);
       -        }
       -}
       -
        int
        ltk_call_cmd(ltk_widget *caller, const char *cmd, size_t cmdlen, const char *text, size_t textlen) {
                /* FIXME: support environment variable $TMPDIR */
   DIR diff --git a/src/ltk/menu.c b/src/ltk/menu.c
       @@ -35,7 +35,6 @@
        #include "text.h"
        #include "menu.h"
        #include "graphics.h"
       -#include "theme.h"
        
        #define MAX_MENU_BORDER_WIDTH 10000
        #define MAX_MENU_PAD 50000
       @@ -208,22 +207,6 @@ static ltk_theme_parseinfo menu_parseinfo[] = {
                {"scroll-background", THEME_COLOR, {.color = &menu_theme.scroll_background}, {.color = "#333333"}, 0, 0, 0},
                {"scroll-arrow-color", THEME_COLOR, {.color = &menu_theme.scroll_arrow_color}, {.color = "#000000"}, 0, 0, 0},
        };
       -static int menu_parseinfo_sorted = 0;
       -
       -int
       -ltk_menu_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
       -        return ltk_theme_handle_value(data, "menu", prop, value, menu_parseinfo, LENGTH(menu_parseinfo), &menu_parseinfo_sorted);
       -}
       -
       -int
       -ltk_menu_fill_theme_defaults(ltk_renderdata *data) {
       -        return ltk_theme_fill_defaults(data, "menu", menu_parseinfo, LENGTH(menu_parseinfo));
       -}
       -
       -void
       -ltk_menu_uninitialize_theme(ltk_renderdata *data) {
       -        ltk_theme_uninitialize(data, menu_parseinfo, LENGTH(menu_parseinfo));
       -}
        
        static ltk_theme_parseinfo menu_entry_parseinfo[] = {
                {"text-pad", THEME_SIZE, {.size = &menu_entry_theme.text_pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_PAD, 0},
       @@ -245,22 +228,6 @@ static ltk_theme_parseinfo menu_entry_parseinfo[] = {
                {"border-disabled", THEME_COLOR, {.color = &menu_entry_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"fill-disabled", THEME_COLOR, {.color = &menu_entry_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
        };
       -static int menu_entry_parseinfo_sorted = 0;
       -
       -int
       -ltk_menuentry_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
       -        return ltk_theme_handle_value(data, "menu-entry", prop, value, menu_entry_parseinfo, LENGTH(menu_entry_parseinfo), &menu_entry_parseinfo_sorted);
       -}
       -
       -int
       -ltk_menuentry_fill_theme_defaults(ltk_renderdata *data) {
       -        return ltk_theme_fill_defaults(data, "menu-entry", menu_entry_parseinfo, LENGTH(menu_entry_parseinfo));
       -}
       -
       -void
       -ltk_menuentry_uninitialize_theme(ltk_renderdata *data) {
       -        ltk_theme_uninitialize(data, menu_entry_parseinfo, LENGTH(menu_entry_parseinfo));
       -}
        
        static ltk_theme_parseinfo submenu_parseinfo[] = {
                {"pad", THEME_SIZE, {.size = &submenu_theme.pad}, {.size = {.val = 0, .unit = LTK_UNIT_PX}}, 0, MAX_MENU_PAD, 0},
       @@ -273,22 +240,6 @@ static ltk_theme_parseinfo submenu_parseinfo[] = {
                {"scroll-background", THEME_COLOR, {.color = &submenu_theme.scroll_background}, {.color = "#333333"}, 0, 0, 0},
                {"scroll-arrow-color", THEME_COLOR, {.color = &submenu_theme.scroll_arrow_color}, {.color = "#000000"}, 0, 0, 0},
        };
       -static int submenu_parseinfo_sorted = 0;
       -
       -int
       -ltk_submenu_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
       -        return ltk_theme_handle_value(data, "submenu", prop, value, submenu_parseinfo, LENGTH(submenu_parseinfo), &submenu_parseinfo_sorted);
       -}
       -
       -int
       -ltk_submenu_fill_theme_defaults(ltk_renderdata *data) {
       -        return ltk_theme_fill_defaults(data, "submenu", submenu_parseinfo, LENGTH(submenu_parseinfo));
       -}
       -
       -void
       -ltk_submenu_uninitialize_theme(ltk_renderdata *data) {
       -        ltk_theme_uninitialize(data, submenu_parseinfo, LENGTH(submenu_parseinfo));
       -}
        
        static ltk_theme_parseinfo submenu_entry_parseinfo[] = {
                {"text-pad", THEME_SIZE, {.size = &submenu_entry_theme.text_pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_MENU_PAD, 0},
       @@ -310,21 +261,28 @@ static ltk_theme_parseinfo submenu_entry_parseinfo[] = {
                {"border-disabled", THEME_COLOR, {.color = &submenu_entry_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
                {"fill-disabled", THEME_COLOR, {.color = &submenu_entry_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
        };
       -static int submenu_entry_parseinfo_sorted = 0;
       +void
       +ltk_menu_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
       +        *p = menu_parseinfo;
       +        *len = LENGTH(menu_parseinfo);
       +}
        
       -int
       -ltk_submenuentry_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
       -        return ltk_theme_handle_value(data, "submenu-entry", prop, value, submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo), &submenu_entry_parseinfo_sorted);
       +void
       +ltk_submenu_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
       +        *p = submenu_parseinfo;
       +        *len = LENGTH(submenu_parseinfo);
        }
        
       -int
       -ltk_submenuentry_fill_theme_defaults(ltk_renderdata *data) {
       -        return ltk_theme_fill_defaults(data, "submenu-entry", submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo));
       +void
       +ltk_menuentry_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
       +        *p = menu_entry_parseinfo;
       +        *len = LENGTH(menu_entry_parseinfo);
        }
        
        void
       -ltk_submenuentry_uninitialize_theme(ltk_renderdata *data) {
       -        ltk_theme_uninitialize(data, submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo));
       +ltk_submenuentry_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
       +        *p = submenu_entry_parseinfo;
       +        *len = LENGTH(submenu_entry_parseinfo);
        }
        
        static void
   DIR diff --git a/src/ltk/menu.h b/src/ltk/menu.h
       @@ -67,20 +67,6 @@ struct ltk_menuentry {
        should submenus also allow setting orientation?
        -> would maybe look weird in some cases */
        
       -int ltk_menu_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
       -int ltk_menu_fill_theme_defaults(ltk_renderdata *data);
       -void ltk_menu_uninitialize_theme(ltk_renderdata *data);
       -int ltk_submenu_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
       -int ltk_submenu_fill_theme_defaults(ltk_renderdata *data);
       -void ltk_submenu_uninitialize_theme(ltk_renderdata *data);
       -
       -int ltk_menuentry_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
       -int ltk_menuentry_fill_theme_defaults(ltk_renderdata *data);
       -void ltk_menuentry_uninitialize_theme(ltk_renderdata *data);
       -int ltk_submenuentry_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
       -int ltk_submenuentry_fill_theme_defaults(ltk_renderdata *data);
       -void ltk_submenuentry_uninitialize_theme(ltk_renderdata *data);
       -
        /* FIXME: allow orientation */
        ltk_menu *ltk_menu_create(ltk_window *window);
        ltk_menu *ltk_submenu_create(ltk_window *window);
   DIR diff --git a/src/ltk/num.c b/src/ltk/num.c
       @@ -249,7 +249,7 @@ ltk_strtoscalednum(
                        /* FIXME: warn if there are any more digits */
                }
        
       -        /* FIXME: decrease code duplication */
       +        /* FIXME: decrease code duplication and check that this actually works */
                long long cutoff = ll < 0 ? LLONG_MIN : LLONG_MAX;
                long long cutlim = cutoff % 100;
                cutoff /= 100;
   DIR diff --git a/src/ltk/scrollbar.c b/src/ltk/scrollbar.c
       @@ -23,7 +23,6 @@
        #include "widget.h"
        #include "util.h"
        #include "scrollbar.h"
       -#include "theme.h"
        #include "eventdefs.h"
        
        #define MAX_SCROLLBAR_WIDTH 10000 /* completely arbitrary */
       @@ -74,21 +73,11 @@ static ltk_theme_parseinfo parseinfo[] = {
                {"fg-pressed", THEME_COLOR, {.color = &theme.fg_pressed}, {.color = "#113355"}, 0, 0, 0},
                {"fg-disabled", THEME_COLOR, {.color = &theme.fg_disabled}, {.color = "#292929"}, 0, 0, 0},
        };
       -static int parseinfo_sorted = 0;
       -
       -int
       -ltk_scrollbar_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
       -        return ltk_theme_handle_value(data, "scrollbar", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
       -}
       -
       -int
       -ltk_scrollbar_fill_theme_defaults(ltk_renderdata *data) {
       -        return ltk_theme_fill_defaults(data, "scrollbar", parseinfo, LENGTH(parseinfo));
       -}
        
        void
       -ltk_scrollbar_uninitialize_theme(ltk_renderdata *data) {
       -        ltk_theme_uninitialize(data, parseinfo, LENGTH(parseinfo));
       +ltk_scrollbar_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
       +        *p = parseinfo;
       +        *len = LENGTH(parseinfo);
        }
        
        void
   DIR diff --git a/src/ltk/scrollbar.h b/src/ltk/scrollbar.h
       @@ -37,8 +37,4 @@ void ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size);
        ltk_scrollbar *ltk_scrollbar_create(ltk_window *window, ltk_orientation orient);
        void ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled);
        
       -int ltk_scrollbar_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
       -int ltk_scrollbar_fill_theme_defaults(ltk_renderdata *data);
       -void ltk_scrollbar_uninitialize_theme(ltk_renderdata *data);
       -
        #endif /* LTK_SCROLLBAR_H */
   DIR diff --git a/src/ltk/theme.c b/src/ltk/theme.c
       @@ -1,211 +0,0 @@
       -/*
       - * Copyright (c) 2022-2024 lumidify <nobody@lumidify.org>
       - *
       - * Permission to use, copy, modify, and/or distribute this software for any
       - * purpose with or without fee is hereby granted, provided that the above
       - * copyright notice and this permission notice appear in all copies.
       - *
       - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
       - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
       - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
       - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
       - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
       - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
       - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
       - */
       -
       -#include <stdlib.h>
       -#include <string.h>
       -
       -#include "graphics.h"
       -#include "util.h"
       -#include "theme.h"
       -#include "memory.h"
       -
       -/* FIXME: handle '#' or no '#' in color specification */
       -static int
       -search_helper(const void *keyv, const void *entryv) {
       -        char *key = (char *)keyv;
       -        ltk_theme_parseinfo *entry = (ltk_theme_parseinfo *)entryv;
       -        return strcmp(key, entry->key);
       -}
       -
       -static int
       -sort_helper(const void *entry1v, const void *entry2v) {
       -        ltk_theme_parseinfo *entry1 = (ltk_theme_parseinfo *)entry1v;
       -        ltk_theme_parseinfo *entry2 = (ltk_theme_parseinfo *)entry2v;
       -        return strcmp(entry1->key, entry2->key);
       -}
       -
       -/* FIXME: more information for errors */
       -int
       -ltk_theme_handle_value(ltk_renderdata *renderdata, char *debug_name, const char *prop, const char *value, ltk_theme_parseinfo *parseinfo, size_t len, int *sorted) {
       -        if (!*sorted) {
       -                qsort(parseinfo, len, sizeof(ltk_theme_parseinfo), &sort_helper);
       -                *sorted = 1;
       -        }
       -        ltk_theme_parseinfo *entry = bsearch(prop, parseinfo, len, sizeof(ltk_theme_parseinfo), &search_helper);
       -        if (!entry) {
       -                ltk_warn("Invalid property '%s:%s'.\n", debug_name, prop);
       -                return 1;
       -        } else if (entry->initialized) {
       -                ltk_warn("Duplicate setting for property '%s:%s'.\n", debug_name, prop);
       -                return 1;
       -        }
       -        const char *errstr = NULL;
       -        switch (entry->type) {
       -        case THEME_INT:
       -                *(entry->ptr.i) = ltk_strtonum(value, entry->min, entry->max, &errstr);
       -                if (errstr) {
       -                        ltk_warn("Invalid value '%s' for property '%s:%s'.\n", value, debug_name, prop);
       -                        return 1;
       -                } else {
       -                        entry->initialized = 1;
       -                }
       -                break;
       -        case THEME_SIZE:
       -                entry->ptr.size->unit = LTK_UNIT_PX;
       -                char *endptr = NULL;
       -                /* this already takes care of overflow prevention because entry->min and entry->max are int */
       -                entry->ptr.size->val = ltk_strtoscalednum(value, entry->min, entry->max, &endptr, &errstr);
       -                if (errstr) {
       -                        ltk_warn("Invalid value '%s' for property '%s:%s': %s\n", value, debug_name, prop, errstr);
       -                        return 1;
       -                }
       -                if (*endptr == '\0') {
       -                        /* NOP */
       -                } else if (!strcmp(endptr, "px")) {
       -                        entry->ptr.size->unit = LTK_UNIT_PX;
       -                } else if (!strcmp(endptr, "pt")) {
       -                        entry->ptr.size->unit = LTK_UNIT_PT;
       -                } else if (!strcmp(endptr, "mm")) {
       -                        entry->ptr.size->unit = LTK_UNIT_MM;
       -                } else {
       -                        ltk_warn("Invalid value '%s' for property '%s:%s'\n", value, debug_name, prop);
       -                        return 1;
       -                }
       -                entry->initialized = 1;
       -                break;
       -        case THEME_STRING:
       -                /* FIXME: check if already set? */
       -                *(entry->ptr.str) = ltk_strdup(value);
       -                entry->initialized = 1;
       -                break;
       -        case THEME_COLOR:
       -                if (!(*(entry->ptr.color) = ltk_color_create(renderdata, value))) {
       -                        ltk_warn("Unable to create color '%s' for property '%s:%s'.\n", value, debug_name, prop);
       -                        return 1;
       -                } else {
       -                        entry->initialized = 1;
       -                }
       -                break;
       -        case THEME_BOOL:
       -                if (strcmp(value, "true") == 0) {
       -                        *(entry->ptr.b) = 1;
       -                } else if (strcmp(value, "false") == 0) {
       -                        *(entry->ptr.b) = 0;
       -                } else {
       -                        ltk_warn("Invalid value '%s' for property '%s:%s'.\n", value, debug_name, prop);
       -                        return 1;
       -                }
       -                entry->initialized = 1;
       -                break;
       -        case THEME_BORDERSIDES:
       -                *(entry->ptr.border) = LTK_BORDER_NONE;
       -                for (const char *c = value; *c != '\0'; c++) {
       -                        switch (*c) {
       -                        case 't':
       -                                *(entry->ptr.border) |= LTK_BORDER_TOP;
       -                                break;
       -                        case 'b':
       -                                *(entry->ptr.border) |= LTK_BORDER_BOTTOM;
       -                                break;
       -                        case 'l':
       -                                *(entry->ptr.border) |= LTK_BORDER_LEFT;
       -                                break;
       -                        case 'r':
       -                                *(entry->ptr.border) |= LTK_BORDER_RIGHT;
       -                                break;
       -                        default:
       -                                ltk_warn("Invalid value '%s' for property '%s:%s'.\n", value, debug_name, prop);
       -                                return 1;
       -                        }
       -                }
       -                entry->initialized = 1;
       -                break;
       -        default:
       -                ltk_fatal("Invalid theme setting type. This should not happen.\n");
       -                /* TODO: ltk_assert(0); */
       -        }
       -        return 0;
       -}
       -
       -int
       -ltk_theme_fill_defaults(ltk_renderdata *renderdata, char *debug_name, ltk_theme_parseinfo *parseinfo, size_t len) {
       -        for (size_t i = 0; i < len; i++) {
       -                ltk_theme_parseinfo *e = &parseinfo[i];
       -                if (e->initialized)
       -                        continue;
       -                switch (e->type) {
       -                case THEME_INT:
       -                        *(e->ptr.i) = e->defaultval.i;
       -                        e->initialized = 1;
       -                        break;
       -                case THEME_SIZE:
       -                        *(e->ptr.size) = e->defaultval.size;
       -                        e->initialized = 1;
       -                        break;
       -                case THEME_STRING:
       -                        *(e->ptr.str) = ltk_strdup(e->defaultval.str);
       -                        e->initialized = 1;
       -                        break;
       -                case THEME_COLOR:
       -                        if (!(*(e->ptr.color) = ltk_color_create(renderdata, e->defaultval.color))) {
       -                                ltk_warn("Unable to create default color '%s' for property '%s:%s'.\n", e->defaultval.color, debug_name, e->key);
       -                                return 1;
       -                        } else {
       -                                e->initialized = 1;
       -                        }
       -                        break;
       -                case THEME_BOOL:
       -                        *(e->ptr.b) = e->defaultval.b;
       -                        e->initialized = 1;
       -                        break;
       -                case THEME_BORDERSIDES:
       -                        *(e->ptr.border) = e->defaultval.border;
       -                        e->initialized = 1;
       -                        break;
       -                default:
       -                        ltk_fatal("Invalid theme setting type. This should not happen.\n");
       -                        /* TODO: ltk_assert(0); */
       -                }
       -        }
       -        return 0;
       -}
       -
       -void
       -ltk_theme_uninitialize(ltk_renderdata *renderdata, ltk_theme_parseinfo *parseinfo, size_t len) {
       -        for (size_t i = 0; i < len; i++) {
       -                ltk_theme_parseinfo *e = &parseinfo[i];
       -                if (!e->initialized)
       -                        continue;
       -                switch (e->type) {
       -                case THEME_STRING:
       -                        ltk_free(*(e->ptr.str));
       -                        e->initialized = 0;
       -                        break;
       -                case THEME_COLOR:
       -                        ltk_color_destroy(renderdata, *(e->ptr.color));
       -                        e->initialized = 0;
       -                        break;
       -                case THEME_SIZE:
       -                case THEME_INT:
       -                case THEME_BOOL:
       -                case THEME_BORDERSIDES:
       -                        e->initialized = 0;
       -                        break;
       -                default:
       -                        ltk_fatal("Invalid theme setting type. This should not happen.\n");
       -                }
       -        }
       -}
   DIR diff --git a/src/ltk/theme.h b/src/ltk/theme.h
       @@ -1,71 +0,0 @@
       -/*
       - * Copyright (c) 2022-2024 lumidify <nobody@lumidify.org>
       - *
       - * Permission to use, copy, modify, and/or distribute this software for any
       - * purpose with or without fee is hereby granted, provided that the above
       - * copyright notice and this permission notice appear in all copies.
       - *
       - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
       - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
       - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
       - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
       - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
       - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
       - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
       - */
       -
       -#ifndef LTK_THEME_H
       -#define LTK_THEME_H
       -
       -#include <stddef.h>
       -#include "color.h"
       -#include "graphics.h"
       -
       -typedef enum {
       -        THEME_STRING,
       -        THEME_COLOR,
       -        THEME_INT,
       -        THEME_BOOL,
       -        THEME_BORDERSIDES,
       -        THEME_SIZE,
       -} ltk_theme_datatype;
       -
       -typedef struct {
       -        char *key;
       -        ltk_theme_datatype type;
       -        /* Note: Bool and int are both integers, but they are
       -           separate just to make it a bit clearer */
       -        union {
       -                char **str;
       -                ltk_color **color;
       -                int *i;
       -                int *b;
       -                ltk_border_sides *border;
       -                ltk_size *size;
       -        } ptr;
       -        /* Note: The default color is also given as a string
       -           because it has to be allocated first (it is only a
       -           different entry in the union in order to make it
       -           a bit clearer) */
       -        union {
       -                char *str;
       -                char *color;
       -                int i;
       -                int b;
       -                ltk_border_sides border;
       -                ltk_size size;
       -        } defaultval;
       -        /* FIXME: min/max doesn't make too much sense for sizes since they
       -           can use different units, but that shouldn't matter for now because
       -           min/max is only used as a sanity check to avoid extreme sizes or
       -           negative sizes where that isn't allowed */
       -        int min, max; /* only for integers or sizes */
       -        int initialized;
       -} ltk_theme_parseinfo;
       -
       -/* Both return 1 on error, 0 on success */
       -int ltk_theme_handle_value(ltk_renderdata *renderdata, char *debug_name, const char *prop, const char *value, ltk_theme_parseinfo *parseinfo, size_t len, int *sorted);
       -int ltk_theme_fill_defaults(ltk_renderdata *renderdata, char *debug_name, ltk_theme_parseinfo *parseinfo, size_t len);
       -void ltk_theme_uninitialize(ltk_renderdata *renderdata, ltk_theme_parseinfo *parseinfo, size_t len);
       -
       -#endif /* LTK_THEME_H */
   DIR diff --git a/src/ltk/widget_internal.h b/src/ltk/widget_internal.h
       @@ -0,0 +1,54 @@
       +/*
       + * Copyright (c) 2024 lumidify <nobody@lumidify.org>
       + *
       + * Permission to use, copy, modify, and/or distribute this software for any
       + * purpose with or without fee is hereby granted, provided that the above
       + * copyright notice and this permission notice appear in all copies.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
       + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
       + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
       + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
       + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
       + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
       + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
       + */
       +
       +#ifndef LTK_WIDGET_INTERNAL_H
       +#define LTK_WIDGET_INTERNAL_H
       +
       +#include <stddef.h>
       +
       +#include "array.h"
       +#include "config.h"
       +#include "window.h"
       +#include "graphics.h"
       +
       +void ltk_window_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
       +void ltk_button_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
       +void ltk_label_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
       +void ltk_menu_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
       +void ltk_menuentry_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
       +void ltk_submenu_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
       +void ltk_submenuentry_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
       +void ltk_scrollbar_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
       +
       +void ltk_entry_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
       +void ltk_entry_cleanup(void);
       +void ltk_entry_get_keybinding_parseinfo(
       +        ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret,
       +        ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret,
       +        ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret
       +);
       +
       +void ltk_window_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
       +ltk_window *ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, unsigned int w, unsigned int h);
       +void ltk_window_destroy_intern(ltk_window *window);
       +void ltk_window_cleanup(void);
       +void ltk_window_get_keybinding_parseinfo(
       +        ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret,
       +        ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret,
       +        ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret
       +);
       +
       +#endif /* LTK_WIDGET_INTERNAL_H */
   DIR diff --git a/src/ltk/window.c b/src/ltk/window.c
       @@ -22,7 +22,6 @@
        #include "ltk.h"
        #include "util.h"
        #include "array.h"
       -#include "theme.h"
        #include "widget.h"
        #include "window.h"
        #include "memory.h"
       @@ -126,21 +125,11 @@ static ltk_theme_parseinfo theme_parseinfo[] = {
                {"font", THEME_STRING, {.str = &theme.font}, {.str = "Monospace"}, 0, 0, 0},
                {"font-size", THEME_SIZE, {.size = &theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, MAX_WINDOW_FONT_SIZE, 0},
        };
       -static int theme_parseinfo_sorted = 0;
       -
       -int
       -ltk_window_fill_theme_defaults(ltk_renderdata *data) {
       -        return ltk_theme_fill_defaults(data, "window", theme_parseinfo, LENGTH(theme_parseinfo));
       -}
       -
       -int
       -ltk_window_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
       -        return ltk_theme_handle_value(data, "window", prop, value, theme_parseinfo, LENGTH(theme_parseinfo), &theme_parseinfo_sorted);
       -}
        
        void
       -ltk_window_uninitialize_theme(ltk_renderdata *data) {
       -        ltk_theme_uninitialize(data, theme_parseinfo, LENGTH(theme_parseinfo));
       +ltk_window_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
       +        *p = theme_parseinfo;
       +        *len = LENGTH(theme_parseinfo);
        }
        
        /* FIXME: maybe ltk_fatal if ltk not initialized? */
       @@ -631,8 +620,8 @@ ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, 
                window->popups_num = window->popups_alloc = 0;
                window->popups_locked = 0;
        
       -        ltk_config *config = ltk_config_get();
       -        unsigned int dpi = (unsigned int)round(config->general.dpi_scale * config->general.fixed_dpi);
       +        ltk_general_config *config = ltk_config_get_general();
       +        unsigned int dpi = (unsigned int)round(config->dpi_scale * config->fixed_dpi * 5);
                window->renderwindow = ltk_renderer_create_window(data, title, x, y, w, h, dpi);
                ltk_renderer_set_window_properties(window->renderwindow, theme.bg);
                window->theme = &theme;
       @@ -894,8 +883,8 @@ static int
        prev_child(ltk_window *window) {
                if (!window->root_widget)
                        return 0;
       -        ltk_config *config = ltk_config_get();
       -        ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
       +        ltk_general_config *config = ltk_config_get_general();
       +        ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
                ltk_widget *new, *cur = window->active_widget;
                int changed = 0;
                ltk_widget *prevcur = cur;
       @@ -955,8 +944,8 @@ static int
        next_child(ltk_window *window) {
                if (!window->root_widget)
                        return 0;
       -        ltk_config *config = ltk_config_get();
       -        ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
       +        ltk_general_config *config = ltk_config_get_general();
       +        ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
                ltk_widget *new, *cur = window->active_widget;
                int changed = 0;
                ltk_widget *prevcur = cur;
       @@ -1040,8 +1029,8 @@ static int
        left_top_child(ltk_window *window, int left) {
                if (!window->root_widget)
                        return 0;
       -        ltk_config *config = ltk_config_get();
       -        ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
       +        ltk_general_config *config = ltk_config_get_general();
       +        ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
                ltk_widget *new, *cur = window->active_widget;
                ltk_rect old_rect = {0, 0, 0, 0};
                ltk_widget *last_activatable = NULL;
       @@ -1103,8 +1092,8 @@ static int
        right_bottom_child(ltk_window *window, int right) {
                if (!window->root_widget)
                        return 0;
       -        ltk_config *config = ltk_config_get();
       -        ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
       +        ltk_general_config *config = ltk_config_get_general();
       +        ltk_widget_flags act_flags = config->all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
                ltk_widget *new, *cur = window->active_widget;
                int changed = 0;
                ltk_rect old_rect = {0, 0, 0, 0};
   DIR diff --git a/src/ltk/window.h b/src/ltk/window.h
       @@ -61,10 +61,7 @@ typedef struct ltk_window {
                char popups_locked;
        } ltk_window;
        
       -/* FIXME: should be private to ltk */
       -ltk_window *ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, unsigned int w, unsigned int h);
       -void ltk_window_destroy_intern(ltk_window *window);
       -
       +/* FIXME: which of these should be internal to LTK? */
        void ltk_window_handle_event(ltk_window *window, ltk_event *event);
        void ltk_window_fake_motion_event(ltk_window *window, int x, int y);
        
       @@ -84,16 +81,6 @@ void ltk_window_register_popup(ltk_window *window, ltk_widget *popup);
        void ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup);
        void ltk_window_unregister_all_popups(ltk_window *window);
        
       -/* FIXME: these should be private to ltk */
       -void ltk_window_cleanup(void);
       -void ltk_window_get_keybinding_parseinfo(
       -        ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret,
       -        ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret,
       -        ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret
       -);
       -int ltk_window_fill_theme_defaults(ltk_renderdata *data);
       -int ltk_window_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
       -void ltk_window_uninitialize_theme(ltk_renderdata *data);
        ltk_window_theme *ltk_window_get_theme(void);
        
        #endif  /* LTK_WINDOW_H */