URI:
       tUse sockets instead of stdin and stdout - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit d02312cfb88d3c52be0c9c4e421af28b224fd7b5
   DIR parent 55009a9b2fc3477235ddf5cae6c45446e0117802
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Thu, 24 Dec 2020 14:06:27 +0100
       
       Use sockets instead of stdin and stdout
       
       Diffstat:
         M .gitignore                          |       4 +++-
         M Makefile                            |      11 ++++++++---
         M README.md                           |       7 ++++---
         A TODO                                |      15 +++++++++++++++
         M button.c                            |       2 +-
         D ltk.c                               |     646 -------------------------------
         M ltk.h                               |      30 ++++++++++++++++++------------
         A ltkc.c                              |     154 +++++++++++++++++++++++++++++++
         A ltkd.c                              |     912 +++++++++++++++++++++++++++++++
         M socket_format.txt                   |       4 ++--
         A test.sh                             |      27 +++++++++++++++++++++++++++
         D test_anim.sh                        |      29 -----------------------------
         D test_draw.gui                       |      10 ----------
       
       13 files changed, 1144 insertions(+), 707 deletions(-)
       ---
   DIR diff --git a/.gitignore b/.gitignore
       t@@ -1,3 +1,5 @@
       -ltk
       +ltkd
       +ltkc
       +ltk.sock
        *.o
        *.core
   DIR diff --git a/Makefile b/Makefile
       t@@ -1,14 +1,19 @@
        include config.mk
        
       -OBJ += color.o util.o ltk.o ini.o grid.o button.o draw.o
       +OBJ += color.o util.o ltkd.o ini.o grid.o button.o draw.o
        
       -ltk: $(OBJ) $(COMPATOBJ)
       +all: ltkd ltkc
       +
       +ltkd: $(OBJ) $(COMPATOBJ)
                $(CC) -o $@ $(OBJ) $(COMPATOBJ) $(LDFLAGS)
        
       +ltkc: ltkc.o
       +        $(CC) -o $@ ltkc.o
       +
        %.o: %.c
                $(CC) -c -o $@ $< $(CFLAGS)
        
        .PHONY: clean
        
        clean:
       -        rm -f $(OBJ) ltk *.core
       +        rm -f $(OBJ) ltkc.o ltkd ltkc ltk.sock *.core
   DIR diff --git a/README.md b/README.md
       t@@ -11,9 +11,10 @@ because it's a bit of a hack.
        To test:
        
        make
       -./ltk < test.gui
       -./ltk < test_draw.gui
       -./test_anim.sh | ./ltk (this requires sleep(1) to support fractional seconds)
       +./test.sh
       +
       +If you click the top button, it should exit. That's all it does now.
       +Also read the comment in './test.sh'.
        
        Note: you need to uncomment "COMPATOBJ = strtonum.c" in config.mk
        if you're not using OpenBSD.
   DIR diff --git a/TODO b/TODO
       t@@ -0,0 +1,15 @@
       +Convert points to pixels for stb rendering (currently, the size between
       +pango and stb is completely different).
       +
       +Random stuff:
       +* This is not really a general-purpose GUI toolkit - imagine building
       +  a complex GUI with this. Especially things like having to respond
       +  directly to mouse movement wouldn't be very efficient due to all the
       +  protocol overhead (or would it maybe not be as bad as I think?).
       +  One idea would be to have a C API and the socket API. The C program
       +  works more or less like a regular GUI program but the GUI mainloop
       +  still opens the socket as usual and allows commands to be sent that
       +  way. That way, you would get the advantages of both sides - the C
       +  program can do the normal GUI stuff, but everything can still be
       +  edited on-the-fly, and text can be retrieved (e.g. for a screen-
       +  reader).
   DIR diff --git a/button.c b/button.c
       t@@ -154,7 +154,7 @@ ltk_button_change_state(ltk_button *button) {
        
        static void
        ltk_button_mouse_release(ltk_button *button, XEvent event) {
       -        ltk_queue_event(button->widget.window, button->widget.id, "button_click");
       +        ltk_queue_event(button->widget.window, LTK_EVENT_BUTTON, button->widget.id, "button_click");
        }
        
        static ltk_button *
   DIR diff --git a/ltk.c b/ltk.c
       t@@ -1,646 +0,0 @@
       -/*
       - * This file is part of the Lumidify ToolKit (LTK)
       - * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.org>
       - *
       - * Permission is hereby granted, free of charge, to any person obtaining a copy
       - * of this software and associated documentation files (the "Software"), to deal
       - * in the Software without restriction, including without limitation the rights
       - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       - * copies of the Software, and to permit persons to whom the Software is
       - * furnished to do so, subject to the following conditions:
       - *
       - * The above copyright notice and this permission notice shall be included in all
       - * copies or substantial portions of the Software.
       - *
       - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       - * SOFTWARE.
       - */
       -
       -#include <stdio.h>
       -#include <stdlib.h>
       -#include <string.h>
       -#include <stdint.h>
       -#include <fcntl.h>
       -#include <unistd.h>
       -#include <time.h>
       -#include <sys/stat.h>
       -#include <sys/select.h>
       -#include <X11/Xlib.h>
       -#include <X11/Xutil.h>
       -#include "util.h"
       -#include "khash.h"
       -#include "ini.h"
       -#include "text.h"
       -#include "ltk.h"
       -#include "grid.h"
       -#include "button.h"
       -#include "draw.h"
       -
       -static void ltk_load_theme(ltk_window *window, const char *path);
       -static void ltk_destroy_theme(ltk_theme *theme);
       -static ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2);
       -
       -static int running = 1;
       -static char *cmd_input = NULL;
       -static char **tokens = NULL;
       -static size_t cmd_bufsize = 0;
       -static size_t tokens_bufsize = 0;
       -static size_t cmd_len = 0;
       -static size_t tokens_len = 0;
       -
       -static ltk_rect
       -ltk_rect_union(ltk_rect r1, ltk_rect r2) {
       -        ltk_rect u;
       -        u.x = r1.x < r2.x ? r1.x : r2.x;
       -        u.y = r1.y < r2.y ? r1.y : r2.y;
       -        int x2 = r1.x + r1.w < r2.x + r2.w ? r2.x + r2.w : r1.x + r1.w;
       -        int y2 = r1.y + r1.h < r2.y + r2.h ? r2.y + r2.h : r1.y + r1.h;
       -        u.w = x2 - u.x;
       -        u.h = y2 - u.y;
       -        return u;
       -}
       -
       -void
       -ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) {
       -        if (window->dirty_rect.w == 0 && window->dirty_rect.h == 0)
       -                window->dirty_rect = rect;
       -        else
       -                window->dirty_rect = ltk_rect_union(rect, window->dirty_rect);
       -}
       -
       -void
       -ltk_clean_up(ltk_window *window) {
       -        ltk_destroy_theme(window->theme);
       -        ltk_destroy_window(window);
       -        if (tokens) free(tokens);
       -        if (cmd_input) free(cmd_input);
       -}
       -
       -void
       -ltk_quit(ltk_window *window) {
       -        ltk_clean_up(window);
       -        running = 0;
       -}
       -
       -void
       -ltk_fatal(const char *msg) {
       -        (void)fprintf(stderr, msg);
       -        /* FIXME: clean up first */
       -        exit(1);
       -};
       -
       -int
       -ltk_create_xcolor(ltk_window *window, const char *hex, XColor *col) {
       -        if (!XParseColor(window->dpy, window->cm, hex, col)) {
       -                (void)fprintf(stderr, "Invalid color: %s\n", hex);
       -                return 0;
       -        }
       -        XAllocColor(window->dpy, window->cm, col);
       -
       -        return 1;
       -}
       -
       -void
       -ltk_queue_event(ltk_window *window, const char *id, const char *name) {
       -        struct ltk_event_queue *new = malloc(sizeof(struct ltk_event_queue));
       -        if (!new) ltk_fatal("Unable to queue event.\n");
       -        new->id = strdup(id);
       -        new->name = strdup(name);
       -        new->next = window->first_event;
       -        window->first_event = new;
       -        new->prev = NULL;
       -        if (!window->last_event)
       -                window->last_event = new;
       -}
       -
       -static void
       -ltk_set_root_widget_cmd(
       -    ltk_window *window,
       -    char **tokens,
       -    int num_tokens) {
       -        ltk_widget *widget;
       -        if (num_tokens != 2) {
       -                (void)fprintf(stderr, "set-root-widget: Invalid number of arguments.\n");
       -                return;
       -        }
       -        widget = ltk_get_widget(window, tokens[1], LTK_WIDGET, "set-root-widget");
       -        if (!widget) return;
       -        window->root_widget = widget;
       -        int w = widget->rect.w;
       -        int h = widget->rect.h;
       -        widget->rect.w = window->rect.w;
       -        widget->rect.h = window->rect.h;
       -        if (widget->resize) {
       -                widget->resize(widget, w, h);
       -        }
       -}
       -
       -/* copied from suckless ii */
       -static int
       -read_cmdline(int fd) {
       -        char c = '\0';
       -        char *tmp = NULL;
       -        cmd_len = 0;
       -        do {
       -                if (cmd_len >= cmd_bufsize) {
       -                        cmd_bufsize = !cmd_bufsize ? 1 : cmd_bufsize;
       -                        tmp = realloc(cmd_input, cmd_bufsize * 2 * sizeof(char));
       -                        if (!tmp) ltk_fatal("Out of memory while reading command.\n");
       -                        cmd_input = tmp;
       -                        cmd_bufsize *= 2;
       -                }
       -                if (read(fd, &c, sizeof(char)) != sizeof(char))
       -                        return -1;
       -                cmd_input[cmd_len++] = c;
       -        } while (c != '\n');
       -        cmd_input[cmd_len - 1] = '\0'; /* eliminates '\n' */
       -        return 0;
       -}
       -
       -/* FIXME: turn this into something that doesn't, you know,
       -   explode at the slightest touch... */
       -static int
       -tokenize(void) {
       -        char *c;
       -        tokens_len = 0;
       -        if (!cmd_input || cmd_input[0] == '\0') return 0;
       -        for (c = cmd_input; *c == ' '; c++)
       -                ;
       -        size_t cur_tok = 0;
       -        int in_str = 0;
       -        char **tmp;
       -        if (tokens_bufsize)
       -                tokens[0] = c;
       -        while (*c != '\0') {
       -                if (cur_tok >= tokens_bufsize) {
       -                        if (!tokens_bufsize)
       -                                tokens_bufsize = 1;
       -                        tmp = realloc(tokens, tokens_bufsize * 2 * sizeof(char *));
       -                        if (!tmp) ltk_fatal("Out of memory while parsing command.\n");
       -                        tokens = tmp;
       -                        tokens[cur_tok] = c;
       -                        tokens_bufsize *= 2;
       -                }
       -                if (*c == '"') {
       -                        if (!in_str)
       -                                tokens[cur_tok] = c + 1;
       -                        in_str = !in_str;
       -                        *c = '\0';
       -                } else if (*c == ' ' && !in_str) {
       -                        *c = '\0';
       -                        cur_tok++;
       -                        tokens[cur_tok] = c + 1;
       -                }
       -                c++;
       -        }
       -        tokens_len = cur_tok + 1;
       -}
       -
       -static void
       -proc_cmds(ltk_window *window) {
       -        if (!tokenize()) return;
       -        if (!tokens_len) return;
       -        if (strcmp(tokens[0], "grid") == 0) {
       -                ltk_grid_cmd(window, tokens, tokens_len);
       -        } else if (strcmp(tokens[0], "button") == 0) {
       -                ltk_button_cmd(window, tokens, tokens_len);
       -        } else if (strcmp(tokens[0], "set-root-widget") == 0) {
       -                ltk_set_root_widget_cmd(window, tokens, tokens_len);
       -        } else if (strcmp(tokens[0], "draw") == 0) {
       -                ltk_draw_cmd(window, tokens, tokens_len);
       -        } else if (strcmp(tokens[0], "quit") == 0) {
       -                ltk_quit(window);
       -        } else {
       -                (void)fprintf(stderr, "Invalid command.\n");
       -        }
       -}
       -
       -int
       -ltk_mainloop(ltk_window *window) {
       -        XEvent event;
       -        struct stat st;
       -        struct timespec tick;
       -        struct timeval tv;
       -        fd_set rfds;
       -        int fd_in = fileno(stdin);
       -        int retval;
       -        tick.tv_sec = 0;
       -        tick.tv_nsec = 10000;
       -        tv.tv_sec = 0;
       -        tv.tv_usec = 0;
       -
       -        tokens = malloc(10 * sizeof(char *));
       -        cmd_input = malloc(200 * sizeof(char));
       -        if (!tokens || !cmd_input) ltk_fatal("Out of memory.\n");
       -        tokens_bufsize = 10;
       -        cmd_bufsize = 200;
       -
       -        while (running) {
       -                FD_ZERO(&rfds);
       -                FD_SET(fd_in, &rfds);
       -                retval = select(fd_in + 1, &rfds, NULL, NULL, &tv);
       -                while (XPending(window->dpy)) {
       -                        XNextEvent(window->dpy, &event);
       -                        ltk_handle_event(window, event);
       -                }
       -                if (retval > 0 && !read_cmdline(fd_in)) {
       -                        proc_cmds(window);
       -                } else if (window->dirty_rect.w != 0 && window->dirty_rect.h != 0) {
       -                        ltk_redraw_window(window);
       -                        window->dirty_rect.w = 0;
       -                        window->dirty_rect.h = 0;
       -                }
       -                if (window->last_event && running) {
       -                        struct ltk_event_queue *cur = window->last_event;
       -                        struct ltk_event_queue *last;
       -                        do {
       -                                printf("%s %s\n", cur->id, cur->name);
       -                                free(cur->id);
       -                                free(cur->name);
       -                                last = cur;
       -                                cur = cur->prev;
       -                                free(last);
       -                        } while (cur);
       -                        window->first_event = window->last_event = NULL;
       -                } 
       -                /* yes, this should be improved */
       -                nanosleep(&tick, NULL);
       -
       -        }
       -}
       -
       -void
       -ltk_redraw_window(ltk_window *window) {
       -        ltk_widget *ptr;
       -        if (!window) return;
       -        if (window->dirty_rect.x >= window->rect.w) return;
       -        if (window->dirty_rect.y >= window->rect.h) return;
       -        if (window->dirty_rect.x + window->dirty_rect.w > window->rect.w)
       -                window->dirty_rect.w -= window->dirty_rect.x + window->dirty_rect.w - window->rect.w;
       -        if (window->dirty_rect.y + window->dirty_rect.h > window->rect.h)
       -                window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
       -        XClearArea(window->dpy, window->xwindow, window->dirty_rect.x, window->dirty_rect.y, window->dirty_rect.w, window->dirty_rect.h, False);
       -        if (!window->root_widget) return;
       -        /* FIXME: actually respect the dirty rect... */
       -        ptr = window->root_widget;
       -        ptr->draw(ptr);
       -}
       -
       -ltk_window *
       -ltk_create_window(const char *theme_path, const char *title, int x, int y, unsigned int w, unsigned int h) {
       -        ltk_window *window = malloc(sizeof(ltk_window));
       -        if (!window)
       -                ltk_fatal("Not enough memory left for window!\n");
       -
       -        window->dpy = XOpenDisplay(NULL);
       -        window->screen = DefaultScreen(window->dpy);
       -        window->cm = DefaultColormap(window->dpy, window->screen);
       -        /* FIXME: validate theme properly */
       -        ltk_load_theme(window, theme_path);
       -        window->wm_delete_msg = XInternAtom(window->dpy, "WM_DELETE_WINDOW", False);
       -
       -        ltk_window_theme *wtheme = window->theme->window;
       -        window->xwindow =
       -            XCreateSimpleWindow(window->dpy, DefaultRootWindow(window->dpy), x, y,
       -                                w, h, wtheme->border_width,
       -                                wtheme->fg.pixel, wtheme->bg.pixel);
       -        window->gc = XCreateGC(window->dpy, window->xwindow, 0, 0);
       -        XSetForeground(window->dpy, window->gc, wtheme->fg.pixel);
       -        XSetBackground(window->dpy, window->gc, wtheme->bg.pixel);
       -        XSetStandardProperties(window->dpy, window->xwindow, title, NULL, None,
       -                               NULL, 0, NULL);
       -        XSetWMProtocols(window->dpy, window->xwindow, &window->wm_delete_msg, 1);
       -        window->root_widget = NULL;
       -
       -        ltk_init_text(wtheme->font, window->dpy, window->screen, window->cm);
       -
       -        window->other_event = &ltk_window_other_event;
       -
       -        window->rect.w = 0;
       -        window->rect.h = 0;
       -        window->rect.x = 0;
       -        window->rect.y = 0;
       -        window->dirty_rect.w = 0;
       -        window->dirty_rect.h = 0;
       -        window->dirty_rect.x = 0;
       -        window->dirty_rect.y = 0;
       -
       -        window->widget_hash = kh_init(widget);
       -
       -        XClearWindow(window->dpy, window->xwindow);
       -        XMapRaised(window->dpy, window->xwindow);
       -        XSelectInput(window->dpy, window->xwindow,
       -                     ExposureMask | KeyPressMask | KeyReleaseMask |
       -                     ButtonPressMask | ButtonReleaseMask |
       -                     StructureNotifyMask | PointerMotionMask);
       -
       -        return window;
       -}
       -
       -void
       -ltk_destroy_window(ltk_window *window) {
       -        khint_t k;
       -        ltk_widget *ptr;
       -        XDestroyWindow(window->dpy, window->xwindow);
       -        for (k = kh_begin(window->widget_hash); k != kh_end(window->widget_hash); k++) {
       -                if (kh_exist(window->widget_hash, k)) {
       -                        ptr = kh_value(window->widget_hash, k);
       -                        ptr->destroy(ptr, 1);
       -                }
       -        }
       -        kh_destroy(widget, window->widget_hash);
       -        ltk_cleanup_text();
       -        XCloseDisplay(window->dpy);
       -        free(window);
       -}
       -
       -void
       -ltk_window_other_event(ltk_window *window, XEvent event) {
       -        ltk_widget *ptr = window->root_widget;
       -        int retval = 0;
       -        if (event.type == ConfigureNotify) {
       -                unsigned int w, h;
       -                w = event.xconfigure.width;
       -                h = event.xconfigure.height;
       -                int orig_w = window->rect.w;
       -                int orig_h = window->rect.h;
       -                if (orig_w != w || orig_h != h) {
       -                        window->rect.w = w;
       -                        window->rect.h = h;
       -                        if (ptr && ptr->resize) {
       -                                ptr->rect.w = w;
       -                                ptr->rect.h = h;
       -                                ptr->resize(ptr, orig_w, orig_h);
       -                        }
       -                }
       -        } else if (event.type == Expose && event.xexpose.count == 0) {
       -                ltk_rect r;
       -                r.x = event.xexpose.x;
       -                r.y = event.xexpose.y;
       -                r.w = event.xexpose.width;
       -                r.h = event.xexpose.height;
       -                ltk_window_invalidate_rect(window, r);
       -        } else if (event.type == ClientMessage
       -            && event.xclient.data.l[0] == window->wm_delete_msg) {
       -                ltk_destroy_window(window);
       -                exit(0); /* FIXME */
       -        }
       -}
       -
       -void
       -ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value) {
       -        /* FIXME: A whole lot more error checking! */
       -        if (strcmp(prop, "border_width") == 0) {
       -                window->theme->window->border_width = atoi(value);
       -        } else if (strcmp(prop, "bg") == 0) {
       -                ltk_create_xcolor(window, value, &window->theme->window->bg);
       -        } else if (strcmp(prop, "fg") == 0) {
       -                ltk_create_xcolor(window, value, &window->theme->window->fg);
       -        } else if (strcmp(prop, "font") == 0) {
       -                window->theme->window->font = strdup(value);
       -        } else if (strcmp(prop, "font_size") == 0) {
       -                window->theme->window->font_size = atoi(value);
       -        }
       -}
       -
       -int
       -ltk_ini_handler(ltk_window *window, const char *widget, const char *prop, const char *value) {
       -        if (strcmp(widget, "window") == 0) {
       -                ltk_window_ini_handler(window, prop, value);
       -        } else if (strcmp(widget, "button") == 0) {
       -                ltk_button_ini_handler(window, prop, value);
       -        }
       -}
       -
       -static void
       -ltk_load_theme(ltk_window *window, const char *path) {
       -        window->theme = malloc(sizeof(ltk_theme));
       -        if (!window->theme) ltk_fatal("Unable to allocate memory for theme.\n");
       -        window->theme->window = malloc(sizeof(ltk_window_theme));
       -        if (!window->theme->window) ltk_fatal("Unable to allocate memory for window theme.\n");
       -        window->theme->button = NULL;
       -        if (ini_parse(path, ltk_ini_handler, window) < 0) {
       -                (void)fprintf(stderr, "ERROR: Can't load theme %s\n.", path);
       -                exit(1);
       -        }
       -}
       -
       -static void
       -ltk_destroy_theme(ltk_theme *theme) {
       -        free(theme->button);
       -        free(theme->window);
       -        free(theme);
       -}
       -
       -int
       -ltk_collide_rect(ltk_rect rect, int x, int y) {
       -        return (rect.x <= x && (rect.x + rect.w) >= x && rect.y <= y
       -                && (rect.y + rect.h) >= y);
       -}
       -
       -void
       -ltk_window_remove_active_widget(ltk_window *window) {
       -        ltk_widget *widget = window->active_widget;
       -        if (!widget) return;
       -        while (widget) {
       -                widget->state = LTK_NORMAL;
       -                widget->active_widget = NULL;
       -                if (widget->change_state)
       -                        widget->change_state(widget);
       -                if (widget->needs_redraw)
       -                        ltk_window_invalidate_rect(window, widget->rect);
       -                widget = widget->parent;
       -        }
       -        window->active_widget = NULL;
       -}
       -
       -void
       -ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
       -        window->active_widget = widget;
       -        ltk_widget *parent = widget->parent;
       -        widget->state = LTK_ACTIVE;
       -        while (parent) {
       -                widget->state = LTK_ACTIVE;
       -                parent->active_widget = widget;
       -                widget = parent;
       -                parent = widget->parent;
       -        }
       -}
       -
       -void
       -ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
       -    void (*draw) (void *), void (*change_state) (void *),
       -    void (*destroy) (void *, int), unsigned int needs_redraw,
       -    ltk_widget_type type) {
       -        widget->id = strdup(id);
       -        widget->window = window;
       -        widget->active_widget = NULL;
       -        widget->parent = NULL;
       -        widget->type = type;
       -
       -        widget->key_press = NULL;
       -        widget->key_release = NULL;
       -        widget->mouse_press = NULL;
       -        widget->mouse_release = NULL;
       -        widget->motion_notify = NULL;
       -        widget->mouse_enter = NULL;
       -        widget->mouse_leave = NULL;
       -
       -        widget->resize = NULL;
       -        widget->draw = draw;
       -        widget->change_state = change_state;
       -        widget->destroy = destroy;
       -
       -        widget->needs_redraw = needs_redraw;
       -        widget->state = LTK_NORMAL;
       -        widget->row = 0;
       -        widget->rect.x = 0;
       -        widget->rect.y = 0;
       -        widget->rect.w = 100;
       -        widget->rect.h = 100;
       -
       -        widget->row = NULL;
       -        widget->column = NULL;
       -        widget->row_span = NULL;
       -        widget->column_span = NULL;
       -        widget->sticky = 0;
       -}
       -
       -void
       -ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event) {
       -        if (!widget || widget->state == LTK_DISABLED)
       -                return;
       -        if (event.xbutton.button == 1) {
       -                ltk_widget *parent = widget->parent;
       -                widget->state = LTK_PRESSED;
       -                if (widget->change_state)
       -                        widget->change_state(widget);
       -                if (widget->needs_redraw)
       -                        ltk_window_invalidate_rect(widget->window, widget->rect);
       -        }
       -        if (widget->mouse_press) {
       -                widget->mouse_press(widget, event);
       -        }
       -}
       -
       -void
       -ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event) {
       -        if (!widget || widget->state == LTK_DISABLED)
       -                return;
       -        if (widget->state == LTK_PRESSED) {
       -                widget->state = LTK_ACTIVE;
       -                if (widget->change_state)
       -                        widget->change_state(widget);
       -                if (widget->needs_redraw)
       -                        ltk_window_invalidate_rect(widget->window, widget->rect);
       -        }
       -        if (widget->mouse_release) {
       -                widget->mouse_release(widget, event);
       -        }
       -}
       -
       -void
       -ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) {
       -        if (!widget) return;
       -        short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
       -        if ((widget->state == LTK_NORMAL) && !pressed) {
       -                widget->state = LTK_ACTIVE;
       -                if (widget->change_state)
       -                        widget->change_state(widget);
       -                if (widget->mouse_enter)
       -                        widget->mouse_enter(widget, event);
       -                /* FIXME: do this properly */
       -                if (widget->window->active_widget != widget && !widget->motion_notify) {
       -                        ltk_window_remove_active_widget(widget->window);
       -                        ltk_window_set_active_widget(widget->window, widget);
       -                }
       -                if (widget->needs_redraw)
       -                        ltk_window_invalidate_rect(widget->window, widget->rect);
       -        }
       -        if (widget->motion_notify)
       -                widget->motion_notify(widget, event);
       -}
       -
       -void
       -ltk_handle_event(ltk_window *window, XEvent event) {
       -        ltk_widget *root_widget = window->root_widget;
       -        switch (event.type) {
       -        case KeyPress:
       -                break;
       -        case KeyRelease:
       -                break;
       -        case ButtonPress:
       -                if (root_widget)
       -                        ltk_widget_mouse_press_event(root_widget, event);
       -                break;
       -        case ButtonRelease:
       -                if (root_widget)
       -                        ltk_widget_mouse_release_event(root_widget, event);
       -                break;
       -        case MotionNotify:
       -                if (root_widget)
       -                        ltk_widget_motion_notify_event(root_widget, event);
       -                break;
       -        default:
       -                if (window->other_event)
       -                        window->other_event(window, event);
       -        }
       -}
       -
       -int
       -ltk_check_widget_id_free(ltk_window *window, const char *id, const char *caller) {
       -        khint_t k;
       -        k = kh_get(widget, window->widget_hash, id);
       -        if (k != kh_end(window->widget_hash)) {
       -                (void)fprintf(stderr, "%s: Widget \"%s\" already exists.\n", caller, id);
       -                return 0;
       -        }
       -        return 1;
       -}
       -
       -ltk_widget *
       -ltk_get_widget(ltk_window *window, const char *id, ltk_widget_type type,
       -    const char *caller) {
       -        khint_t k;
       -        ltk_widget *widget;
       -        k = kh_get(widget, window->widget_hash, id);
       -        if (k == kh_end(window->widget_hash)) {
       -                (void)fprintf(stderr, "%s: Widget \"%s\" doesn't exist.\n", caller, id);
       -                return NULL;
       -        }
       -        widget = kh_value(window->widget_hash, k);
       -        if (type != LTK_WIDGET && widget->type != type) {
       -                (void)fprintf(stderr, "%s: Widget \"%s\" has wrong type.\n", caller, id);
       -                return NULL;
       -        }
       -        return widget;
       -}
       -
       -void
       -ltk_set_widget(ltk_window *window, ltk_widget *widget, const char *id) {
       -        int ret;
       -        khint_t k;
       -        /* apparently, khash requires the string to stay accessible */
       -        char *tmp = strdup(id);
       -        k = kh_put(widget, window->widget_hash, tmp, &ret);
       -        kh_value(window->widget_hash, k) = widget;
       -}
       -
       -void
       -ltk_remove_widget(ltk_window *window, const char *id) {
       -        khint_t k;
       -        k = kh_get(widget, window->widget_hash, id);
       -        if (k != kh_end(window->widget_hash)) {
       -                kh_del(widget, window->widget_hash, k);
       -        }
       -}
       -
       -int main(int argc, char *argv[]) {
       -        ltk_window *window = ltk_create_window("theme.ini", "Demo", 0, 0, 500, 500);
       -        ltk_mainloop(window);
       -}
   DIR diff --git a/ltk.h b/ltk.h
       t@@ -28,6 +28,12 @@
        
        #include "khash.h"
        
       +typedef enum {
       +        LTK_EVENT_RESIZE = 1 << 0,
       +        LTK_EVENT_BUTTON = 1 << 1,
       +        LTK_EVENT_KEY = 1 << 2
       +} ltk_event_type;
       +
        typedef struct {
                int x;
                int y;
       t@@ -59,10 +65,10 @@ typedef enum {
        typedef struct ltk_window ltk_window;
        
        typedef struct ltk_widget {
       +        ltk_rect rect;
                ltk_window *window;
                struct ltk_widget *active_widget;
                struct ltk_widget *parent;
       -        ltk_widget_type type;
                char *id;
        
                void (*key_press) (void *, XEvent event);
       t@@ -78,14 +84,14 @@ typedef struct ltk_widget {
                void (*change_state) (void *);
                void (*destroy) (void *, int);
        
       -        ltk_rect rect;
       -        unsigned int row;
       -        unsigned int column;
       -        unsigned int row_span;
       -        unsigned int column_span;
       -        unsigned int needs_redraw;
       +        ltk_widget_type type;
                ltk_widget_state state;
       -        unsigned short sticky;
       +        unsigned int sticky;
       +        unsigned short row;
       +        unsigned short column;
       +        unsigned short row_span;
       +        unsigned short column_span;
       +        unsigned char needs_redraw;
        } ltk_widget;
        
        typedef struct {
       t@@ -104,8 +110,8 @@ typedef struct {
        } ltk_theme;
        
        struct ltk_event_queue {
       -        char *id;
       -        char *name;
       +        ltk_event_type event_type;
       +        char *data;
                struct ltk_event_queue *prev;
                struct ltk_event_queue *next;
        };
       t@@ -121,7 +127,7 @@ typedef struct ltk_window {
                Window xwindow;
                ltk_widget *root_widget;
                ltk_widget *active_widget;
       -        int (*other_event) (ltk_window *, XEvent event);
       +        void (*other_event) (ltk_window *, XEvent event);
                ltk_rect rect;
                ltk_theme *theme;
                ltk_rect dirty_rect;
       t@@ -136,7 +142,7 @@ void ltk_fatal(const char *msg);
        
        int ltk_create_xcolor(ltk_window *window, const char *hex, XColor *col);
        
       -void ltk_queue_event(ltk_window *window, const char *id, const char *name);
       +void ltk_queue_event(ltk_window *window, ltk_event_type type, const char *id, const char *data);
        
        int ltk_mainloop(ltk_window *window);
        
   DIR diff --git a/ltkc.c b/ltkc.c
       t@@ -0,0 +1,154 @@
       +/*
       + * This file is part of the Lumidify ToolKit (LTK)
       + * Copyright (c) 2020 lumidify <nobody@lumidify.org>
       + *
       + * Permission is hereby granted, free of charge, to any person obtaining a copy
       + * of this software and associated documentation files (the "Software"), to deal
       + * in the Software without restriction, including without limitation the rights
       + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       + * copies of the Software, and to permit persons to whom the Software is
       + * furnished to do so, subject to the following conditions:
       + *
       + * The above copyright notice and this permission notice shall be included in all
       + * copies or substantial portions of the Software.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       + * SOFTWARE.
       + */
       +
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <stdint.h>
       +#include <stddef.h>
       +#include <unistd.h>
       +#include <time.h>
       +#include <errno.h>
       +#include <sys/select.h>
       +#include <sys/socket.h>
       +#include <sys/un.h>
       +
       +#define BLK_SIZE 128
       +
       +/* If `needed` is larger than `*alloc_size`, resize `*str` to `*alloc_size * 2`. */
       +static int
       +grow_string(char **str, int *alloc_size, int needed) {
       +        if (needed <= *alloc_size) return 0;
       +        char *new = realloc(*str, *alloc_size * 2);
       +        if (!new) return 1;
       +        *str = new;
       +        *alloc_size = *alloc_size * 2;
       +        return 0;
       +}
       +static struct {
       +        char *in_buffer;
       +        int in_len;
       +        int in_alloc;
       +        char *out_buffer;
       +        int out_len;
       +        int out_alloc;
       +} io_buffers;
       +
       +int main(int argc, char *argv[]) {
       +        int sockfd, maxrfd, maxwfd;
       +        int infd = fileno(stdin);
       +        int outfd = fileno(stdout);
       +        struct sockaddr_un un;
       +        fd_set rfds, wfds, rallfds, wallfds;
       +        struct timeval tv;
       +        tv.tv_sec = 0;
       +        tv.tv_usec = 15;
       +
       +        if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
       +                perror("Socket error");
       +                return -1;
       +        }
       +        memset(&un, 0, sizeof(un));
       +        un.sun_family = AF_UNIX;
       +        strcpy(un.sun_path, "ltk.sock");
       +        if (connect(sockfd, (struct sockaddr *)&un, offsetof(struct sockaddr_un, sun_path) + 9) < 0) {
       +                perror("Socket error");
       +                return -2;
       +        }
       +
       +        io_buffers.in_buffer = malloc(BLK_SIZE);
       +        if (!io_buffers.in_buffer)
       +                return 1;
       +        io_buffers.in_alloc = BLK_SIZE;
       +
       +        io_buffers.out_buffer = malloc(BLK_SIZE);
       +        if (!io_buffers.out_buffer)
       +                return 1;
       +        io_buffers.out_alloc = BLK_SIZE;
       +
       +        FD_ZERO(&rallfds);
       +        FD_ZERO(&wallfds);
       +
       +        FD_SET(sockfd, &rallfds);
       +        FD_SET(infd, &rallfds);
       +        FD_SET(sockfd, &wallfds);
       +        FD_SET(outfd, &wallfds);
       +        maxrfd = sockfd > infd ? sockfd : infd;
       +        maxwfd = sockfd > outfd ? sockfd : outfd;
       +
       +        while (1) {
       +                if (!FD_ISSET(infd, &rallfds) && !FD_ISSET(sockfd, &rallfds) && io_buffers.in_len == 0 && io_buffers.out_len == 0)
       +                        break;
       +                rfds = rallfds;
       +                wfds = wallfds;
       +                /* Separate this because the writing fds are *usually* always ready,
       +                   leading to the loop looping way too fast */
       +                select(maxrfd + 1, &rfds, NULL, NULL, &tv);
       +                select(maxwfd + 1, NULL, &wfds, NULL, &tv);
       +                if (FD_ISSET(sockfd, &rfds)) {
       +                        grow_string(&io_buffers.out_buffer, &io_buffers.out_alloc, io_buffers.out_len + BLK_SIZE);
       +                        int nread = read(sockfd, io_buffers.out_buffer + io_buffers.out_len, BLK_SIZE);
       +                        if (nread < 0) {
       +                                return 2;
       +                        } else if (nread == 0) {
       +                                FD_CLR(sockfd, &rallfds);
       +                                FD_CLR(sockfd, &wallfds);
       +                        } else {
       +                                io_buffers.out_len += nread;
       +                        }
       +                }
       +                if (FD_ISSET(infd, &rfds)) {
       +                        grow_string(&io_buffers.in_buffer, &io_buffers.in_alloc, io_buffers.in_len + BLK_SIZE);
       +                        int nread = read(infd, io_buffers.in_buffer + io_buffers.in_len, BLK_SIZE);
       +                        if (nread < 0) {
       +                                return 2;
       +                        } else if (nread == 0) {
       +                                FD_CLR(infd, &rallfds);
       +                        } else {
       +                                io_buffers.in_len += nread;
       +                        }
       +                }
       +                if (FD_ISSET(sockfd, &wfds)) {
       +                        int maxwrite = BLK_SIZE > io_buffers.in_len ? io_buffers.in_len : BLK_SIZE;
       +                        int nwritten = write(sockfd, io_buffers.in_buffer, maxwrite);
       +                        if (nwritten < 0) {
       +                                return 2;
       +                        } else {
       +                                memmove(io_buffers.in_buffer, io_buffers.in_buffer + nwritten, io_buffers.in_len - nwritten);
       +                                io_buffers.in_len -= nwritten;
       +                        }
       +                }
       +                if (FD_ISSET(outfd, &wfds)) {
       +                        int maxwrite = BLK_SIZE > io_buffers.out_len ? io_buffers.out_len : BLK_SIZE;
       +                        int nwritten = write(outfd, io_buffers.out_buffer, maxwrite);
       +                        if (nwritten < 0) {
       +                                return 2;
       +                        } else {
       +                                memmove(io_buffers.out_buffer, io_buffers.out_buffer + nwritten, io_buffers.out_len - nwritten);
       +                                io_buffers.out_len -= nwritten;
       +                        }
       +                }
       +        }
       +
       +        return 0;
       +}
   DIR diff --git a/ltkd.c b/ltkd.c
       t@@ -0,0 +1,912 @@
       +/*
       + * This file is part of the Lumidify ToolKit (LTK)
       + * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.org>
       + *
       + * Permission is hereby granted, free of charge, to any person obtaining a copy
       + * of this software and associated documentation files (the "Software"), to deal
       + * in the Software without restriction, including without limitation the rights
       + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       + * copies of the Software, and to permit persons to whom the Software is
       + * furnished to do so, subject to the following conditions:
       + *
       + * The above copyright notice and this permission notice shall be included in all
       + * copies or substantial portions of the Software.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       + * SOFTWARE.
       + */
       +
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <stdint.h>
       +#include <fcntl.h>
       +#include <unistd.h>
       +#include <time.h>
       +#include <errno.h>
       +#include <sys/stat.h>
       +#include <sys/select.h>
       +#include <sys/socket.h>
       +#include <sys/un.h>
       +#include <X11/Xlib.h>
       +#include <X11/Xutil.h>
       +#include "util.h"
       +#include "khash.h"
       +#include "ini.h"
       +#include "text.h"
       +#include "ltk.h"
       +#include "grid.h"
       +#include "button.h"
       +#include "draw.h"
       +
       +#define MAX_SOCK_CONNS 20
       +#define READ_BLK_SIZE 128
       +#define WRITE_BLK_SIZE 128
       +
       +struct token_list {
       +        char **tokens;
       +        int num_tokens;
       +        int num_alloc;
       +};
       +
       +static struct ltk_sock_info {
       +        int fd;                    /* file descriptor for socket connection */
       +        int event_mask;            /* events to send to socket */
       +        char *read;                /* text read from socket */
       +        int read_len;              /* length of text in read buffer */
       +        int read_alloc;            /* size of read buffer */
       +        char *to_write;            /* text to be written to socket */
       +        int write_len;             /* length of text in write buffer */
       +        int write_cur;             /* length of text already written */
       +        int write_alloc;           /* size of write buffer */
       +        /* stuff for tokenizing */
       +        int in_token;              /* last read char is inside token */
       +        int offset;                /* offset from removing backslashes */
       +        int in_str;                /* last read char is inside string */
       +        int read_cur;              /* length of text already tokenized */
       +        int bs;                    /* last char was non-escaped backslash */
       +        struct token_list tokens;  /* current tokens */
       +} sockets[MAX_SOCK_CONNS];
       +
       +static void ltk_load_theme(ltk_window *window, const char *path);
       +static void ltk_destroy_theme(ltk_theme *theme);
       +static ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2);
       +static int read_sock(struct ltk_sock_info *sock);
       +
       +static int running = 1;
       +
       +static int
       +push_token(struct token_list *tl, char *token) {
       +        int new_size;
       +        if (tl->num_tokens >= tl->num_alloc) {
       +                new_size = (tl->num_alloc * 2) > (tl->num_tokens + 1) ?
       +                           (tl->num_alloc * 2) : (tl->num_tokens + 1);
       +                char **new = realloc(tl->tokens, new_size * sizeof(char *));
       +                if (!new) return -1;
       +                tl->tokens = new;
       +                tl->num_alloc = new_size;
       +        }
       +        tl->tokens[tl->num_tokens++] = token;
       +
       +        return 0;
       +}
       +
       +/* If `needed` is larger than `*alloc_size`, resize `*str` to `*alloc_size * 2`. */
       +static int
       +grow_string(char **str, int *alloc_size, int needed) {
       +        if (needed <= *alloc_size) return 0;
       +        int new_size = needed > (*alloc_size * 2) ? needed : (*alloc_size * 2);
       +        char *new = realloc(*str, new_size);
       +        if (!new) return 1;
       +        *str = new;
       +        *alloc_size = new_size;
       +        return 0;
       +}
       +/* FIXME: non-blocking io? */
       +/* Read up to READ_BLK_SIZE bytes from the socket.
       +   Returns -1 if an error occurred, 0 if the connection was closed, 1 otherwise. */
       +static int
       +read_sock(struct ltk_sock_info *sock) {
       +        int nread;
       +        char *old = sock->read;
       +        int ret = grow_string(&sock->read, &sock->read_alloc, sock->read_len + READ_BLK_SIZE);
       +        if (ret) return -1; /* fixme: errno? */
       +        /* move tokens to new addresses - this was added as an
       +           afterthought and really needs to be cleaned up */
       +        if (sock->read != old) {
       +                for (int i = 0; i < sock->tokens.num_tokens; i++) {
       +                        sock->tokens.tokens[i] = sock->read + (sock->tokens.tokens[i] - old);
       +                }
       +        }
       +        nread = read(sock->fd, sock->read + sock->read_len, READ_BLK_SIZE);
       +        if (nread == -1 || nread == 0)
       +                return nread;
       +        sock->read_len += nread;
       +
       +        return 1;
       +}
       +
       +/* Write up to WRITE_BLK_SIZE bytes to the socket.
       +   Returns -1 on error, 0 otherwise. */
       +static int
       +write_sock(struct ltk_sock_info *sock) {
       +        if (!sock->write_len)
       +                return 0;
       +        int write_len = WRITE_BLK_SIZE > sock->write_len - sock->write_cur ?
       +                        sock->write_len - sock->write_cur : WRITE_BLK_SIZE;
       +        int nwritten = write(sock->fd, sock->to_write + sock->write_cur, write_len);
       +        if (nwritten == -1)
       +                return nwritten;
       +        sock->write_cur += nwritten;
       +        return 0;
       +}
       +
       +/* Queue str to be written to the socket. If len is < 0, it is set to strlen(str).
       +   Returns -1 on error, 0 otherwise.
       +   Note: The string must include all '\n', etc. as defined in the protocol. This
       +   function just adds the given string verbatim. */
       +static int
       +queue_sock_write(struct ltk_sock_info *sock, const char *str, int len) {
       +        if (sock->write_cur > 0) {
       +                memmove(sock->to_write, sock->to_write + sock->write_cur,
       +                        sock->write_len - sock->write_cur);
       +                sock->write_len -= sock->write_cur;
       +                sock->write_cur = 0;
       +        }
       +
       +        if (len < 0)
       +                len = strlen(str);
       +
       +        if (sock->write_alloc - sock->write_len < len &&
       +            grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len))
       +                return -1;
       +
       +        (void)strncpy(sock->to_write + sock->write_len, str, len);
       +        sock->write_len += len;
       +
       +        return 0;
       +}
       +
       +/* Returns 0 if the end of a command was encountered, 1 otherwise */
       +static int
       +tokenize_command(struct ltk_sock_info *sock) {
       +        for (; sock->read_cur < sock->read_len; sock->read_cur++) {
       +                if (!sock->in_token) {
       +                        push_token(&sock->tokens, sock->read + sock->read_cur - sock->offset);
       +                        sock->in_token = 1;
       +                }
       +                if (sock->read[sock->read_cur] == '\\') {
       +                        sock->bs++;
       +                        sock->bs %= 2;
       +                        sock->read[sock->read_cur-sock->offset] = '\\';
       +                } else if (sock->read[sock->read_cur] == '\n' && !sock->in_str) {
       +                        sock->read[sock->read_cur-sock->offset] = '\0';
       +                        sock->read_cur++;
       +                        sock->offset = 0;
       +                        sock->in_token = 0;
       +                        return 0;
       +                } else if (sock->read[sock->read_cur] == '"') {
       +                        sock->offset++;
       +                        if (sock->bs) {
       +                                sock->read[sock->read_cur-sock->offset] = '"';
       +                                sock->bs = 0;
       +                        } else {
       +                                sock->in_str = !sock->in_str;
       +                        }
       +                } else if (sock->read[sock->read_cur] == ' ' && !sock->in_str) {
       +                        sock->read[sock->read_cur-sock->offset] = '\0';
       +                        sock->in_token = !sock->in_token;
       +                } else {
       +                        sock->read[sock->read_cur-sock->offset] = sock->read[sock->read_cur];
       +                        sock->bs = 0;
       +                }
       +        }
       +
       +        return 1;
       +}
       +
       +void
       +ltk_clean_up(ltk_window *window) {
       +        ltk_destroy_theme(window->theme);
       +        ltk_destroy_window(window);
       +}
       +
       +void
       +ltk_quit(ltk_window *window) {
       +        ltk_clean_up(window);
       +        running = 0;
       +}
       +
       +static void
       +ltk_set_root_widget_cmd(
       +    ltk_window *window,
       +    char **tokens,
       +    int num_tokens) {
       +        ltk_widget *widget;
       +        if (num_tokens != 2) {
       +                (void)fprintf(stderr, "set-root-widget: Invalid number of arguments.\n");
       +                return;
       +        }
       +        widget = ltk_get_widget(window, tokens[1], LTK_WIDGET, "set-root-widget");
       +        if (!widget) return;
       +        window->root_widget = widget;
       +        int w = widget->rect.w;
       +        int h = widget->rect.h;
       +        widget->rect.w = window->rect.w;
       +        widget->rect.h = window->rect.h;
       +        if (widget->resize) {
       +                widget->resize(widget, w, h);
       +        }
       +}
       +
       +static void
       +process_commands(ltk_window *window, struct ltk_sock_info *sock) {
       +        char **tokens;
       +        int num_tokens;
       +        while (!tokenize_command(sock)) {
       +                tokens = sock->tokens.tokens;
       +                num_tokens = sock->tokens.num_tokens;
       +                if (num_tokens < 1)
       +                        continue;
       +                if (strcmp(tokens[0], "grid") == 0) {
       +                        ltk_grid_cmd(window, tokens, num_tokens);
       +                } else if (strcmp(tokens[0], "button") == 0) {
       +                        ltk_button_cmd(window, tokens, num_tokens);
       +                } else if (strcmp(tokens[0], "set-root-widget") == 0) {
       +                        ltk_set_root_widget_cmd(window, tokens, num_tokens);
       +                } else if (strcmp(tokens[0], "draw") == 0) {
       +                        ltk_draw_cmd(window, tokens, num_tokens);
       +                } else if (strcmp(tokens[0], "quit") == 0) {
       +                        ltk_quit(window);
       +                } else {
       +                        /* FIXME... */
       +                        (void)fprintf(stderr, "Invalid command.\n");
       +                }
       +                sock->tokens.num_tokens = 0;
       +        }
       +        if (sock->tokens.num_tokens > 0 && sock->tokens.tokens[0] != sock->read) {
       +                memmove(sock->read, sock->tokens.tokens[0], sock->read + sock->read_len - sock->tokens.tokens[0]);
       +                ptrdiff_t offset = sock->tokens.tokens[0] - sock->read;
       +                /* Hmm, seems a bit ugly... */
       +                for (int i = 0; i < sock->tokens.num_tokens; i++) {
       +                        sock->tokens.tokens[i] -= offset;
       +                }
       +                sock->read_len -= offset;
       +                sock->read_cur -= offset;
       +        } else if (sock->tokens.num_tokens == 0) {
       +                sock->read_len = 0;
       +                sock->read_cur = 0;
       +        }
       +}
       +
       +static ltk_rect
       +ltk_rect_union(ltk_rect r1, ltk_rect r2) {
       +        ltk_rect u;
       +        u.x = r1.x < r2.x ? r1.x : r2.x;
       +        u.y = r1.y < r2.y ? r1.y : r2.y;
       +        int x2 = r1.x + r1.w < r2.x + r2.w ? r2.x + r2.w : r1.x + r1.w;
       +        int y2 = r1.y + r1.h < r2.y + r2.h ? r2.y + r2.h : r1.y + r1.h;
       +        u.w = x2 - u.x;
       +        u.h = y2 - u.y;
       +        return u;
       +}
       +
       +void
       +ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) {
       +        if (window->dirty_rect.w == 0 && window->dirty_rect.h == 0)
       +                window->dirty_rect = rect;
       +        else
       +                window->dirty_rect = ltk_rect_union(rect, window->dirty_rect);
       +}
       +
       +void
       +ltk_fatal(const char *msg) {
       +        (void)fprintf(stderr, msg);
       +        /* FIXME: clean up first */
       +        exit(1);
       +};
       +
       +int
       +ltk_create_xcolor(ltk_window *window, const char *hex, XColor *col) {
       +        if (!XParseColor(window->dpy, window->cm, hex, col)) {
       +                (void)fprintf(stderr, "Invalid color: %s\n", hex);
       +                return 0;
       +        }
       +        XAllocColor(window->dpy, window->cm, col);
       +
       +        return 1;
       +}
       +
       +void
       +ltk_queue_event(ltk_window *window, ltk_event_type type, const char *id, const char *data) {
       +        /* FIXME: make it nicer and safer */
       +        struct ltk_event_queue *new = malloc(sizeof(struct ltk_event_queue));
       +        if (!new) ltk_fatal("Unable to queue event.\n");
       +        new->event_type = type;
       +        int id_len = strlen(id);
       +        int data_len = strlen(data);
       +        new->data = malloc(id_len + data_len + 3);
       +        if (!new->data) ltk_fatal("Unable to queue event.\n");
       +        strcpy(new->data, id);
       +        new->data[id_len] = ' ';
       +        strcpy(new->data + id_len + 1, data);
       +        new->data[id_len + data_len + 1] = '\n';
       +        new->data[id_len + data_len + 2] = '\0';
       +        new->next = window->first_event;
       +        window->first_event = new;
       +        new->prev = NULL;
       +        if (!window->last_event)
       +                window->last_event = new;
       +}
       +
       +static int
       +add_client(int fd) {
       +        for (int i = 0; i < MAX_SOCK_CONNS; i++) {
       +                if (sockets[i].fd == -1) {
       +                        sockets[i].fd = fd;
       +                        sockets[i].event_mask = ~0; /* FIXME */
       +                        sockets[i].read_len = 0;
       +                        sockets[i].write_len = 0;
       +                        sockets[i].write_cur = 0;
       +                        sockets[i].offset = 0;
       +                        sockets[i].in_str = 0;
       +                        sockets[i].read_cur = 0;
       +                        sockets[i].bs = 0;
       +                        sockets[i].tokens.num_tokens = 0;
       +                        return i;
       +                }
       +        }
       +
       +        return -1;
       +}
       +
       +/* largely copied from APUE */
       +static int
       +listen_sock(const char *sock_path) {
       +        int fd, len, err, rval;
       +        struct sockaddr_un un;
       +
       +        if (strlen(sock_path) >= sizeof(un.sun_path)) {
       +                errno = ENAMETOOLONG;
       +                return -1;
       +        }
       +
       +        if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
       +                return -2;
       +
       +        unlink(sock_path);
       +
       +        memset(&un, 0, sizeof(un));
       +        un.sun_family = AF_UNIX;
       +        strcpy(un.sun_path, sock_path);
       +        len = offsetof(struct sockaddr_un, sun_path) + strlen(sock_path);
       +        if (bind(fd, (struct sockaddr *)&un, len) < 0) {
       +                rval = -3;
       +                goto errout;
       +        }
       +
       +        if (listen(fd, 10) < 0) {
       +                rval = -4;
       +                goto errout;
       +        }
       +
       +        return fd;
       +
       +errout:
       +        err = errno;
       +        close(fd);
       +        errno = err;
       +        return rval;
       +}
       +
       +static int
       +accept_sock(int listenfd) {
       +        int clifd;
       +        socklen_t len;
       +        struct sockaddr_un un;
       +
       +        len = sizeof(un);
       +        if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
       +                return -1;
       +        }
       +
       +        return clifd;
       +}
       +
       +int
       +ltk_mainloop(ltk_window *window) {
       +        XEvent event;
       +        fd_set rfds, wfds, rallfds, wallfds;
       +        int maxi, maxfd, listenfd;
       +        int rretval, wretval;
       +        int clifd;
       +        struct timeval tv;
       +        tv.tv_sec = 0;
       +        tv.tv_usec = 10;
       +
       +        /* Note: sockets should be initialized to 0 because it is static */
       +        for (int i = 0; i < MAX_SOCK_CONNS; i++) {
       +                sockets[i].fd = -1; /* socket unused */
       +                /* initialize these just because I'm paranoid */
       +                sockets[i].read = NULL;
       +                sockets[i].to_write = NULL;
       +                sockets[i].tokens.tokens = NULL;
       +        }
       +
       +        FD_ZERO(&rallfds);
       +        FD_ZERO(&wallfds);
       +
       +        if ((listenfd = listen_sock("ltk.sock")) < 0) {
       +                fprintf(stderr, "Error listening on ltk.sock\n");
       +                exit(1); /* FIXME: proper error handling */
       +        }
       +
       +        FD_SET(listenfd, &rallfds);
       +        maxfd = listenfd;
       +        maxi = -1;
       +
       +        while (running) {
       +                rfds = rallfds;
       +                wfds = wallfds;
       +                /* separate these because the writing fds are usually
       +                   always ready for writing */
       +                rretval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
       +                wretval = select(maxfd + 1, NULL, &wfds, NULL, &tv);
       +                while (XPending(window->dpy)) {
       +                        XNextEvent(window->dpy, &event);
       +                        ltk_handle_event(window, event);
       +                }
       +                /* FIXME: somehow keep track of whether anything has to be written,
       +                   otherwise it always has to loop over all fds to check - the writing
       +                   fds are usually always set, so this is really run on every loop
       +                   iteration, which is bad */
       +                if (rretval > 0 || wretval > 0) {
       +                        if (FD_ISSET(listenfd, &rfds)) {
       +                                if ((clifd = accept_sock(listenfd)) < 0) {
       +                                        fprintf(stderr, "Error accepting socket connection\n");
       +                                        exit(1); /* FIXME: proper error handling */
       +                                }
       +                                int i = add_client(clifd);
       +                                FD_SET(clifd, &rallfds);
       +                                FD_SET(clifd, &wallfds);
       +                                if (clifd > maxfd)
       +                                        maxfd = clifd;
       +                                if (i > maxi)
       +                                        maxi = i;
       +                                continue;
       +                        }
       +                        for (int i = 0; i <= maxi; i++) {
       +                                if ((clifd = sockets[i].fd) < 0)
       +                                        continue;
       +                                if (FD_ISSET(clifd, &rfds)) {
       +                                        if (read_sock(&sockets[i]) == 0) {
       +                                                fprintf(stderr, "Closed socket fd %d\n", clifd); /* FIXME */
       +                                                FD_CLR(clifd, &rallfds);
       +                                                FD_CLR(clifd, &wallfds);
       +                                                sockets[i].fd = -1;
       +                                                close(clifd);
       +                                        } else {
       +                                                process_commands(window, &sockets[i]);
       +                                        }
       +                                }
       +                                if (FD_ISSET(clifd, &wfds)) {
       +                                        write_sock(&sockets[i]);
       +                                }
       +                        }
       +                }
       +                if (window->dirty_rect.w != 0 && window->dirty_rect.h != 0) {
       +                        ltk_redraw_window(window);
       +                        window->dirty_rect.w = 0;
       +                        window->dirty_rect.h = 0;
       +                }
       +                if (window->last_event && running) {
       +                        struct ltk_event_queue *cur = window->last_event;
       +                        struct ltk_event_queue *last;
       +                        do {
       +                                int event_len = strlen(cur->data);
       +                                for (int i = 0; i <= maxi; i++) {
       +                                        if (sockets[i].fd != -1 && sockets[i].event_mask & cur->event_type) {
       +                                                if (queue_sock_write(&sockets[i], cur->data, event_len) < 0)
       +                                                        exit(1); /* FIXME: error handling */
       +                                        }
       +                                }
       +                                free(cur->data);
       +                                last = cur;
       +                                cur = cur->prev;
       +                                free(last);
       +                        } while (cur);
       +                        window->first_event = window->last_event = NULL;
       +                } 
       +
       +        }
       +        for (int i = 0; i < MAX_SOCK_CONNS; i++) {
       +                if (sockets[i].read)
       +                        free(sockets[i].read);
       +                if (sockets[i].to_write)
       +                        free(sockets[i].to_write);
       +                if (sockets[i].tokens.tokens)
       +                        free(sockets[i].tokens.tokens);
       +        }
       +
       +        unlink("ltk.sock");
       +
       +        return 0;
       +}
       +
       +void
       +ltk_redraw_window(ltk_window *window) {
       +        ltk_widget *ptr;
       +        if (!window) return;
       +        if (window->dirty_rect.x >= window->rect.w) return;
       +        if (window->dirty_rect.y >= window->rect.h) return;
       +        if (window->dirty_rect.x + window->dirty_rect.w > window->rect.w)
       +                window->dirty_rect.w -= window->dirty_rect.x + window->dirty_rect.w - window->rect.w;
       +        if (window->dirty_rect.y + window->dirty_rect.h > window->rect.h)
       +                window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
       +        XClearArea(window->dpy, window->xwindow, window->dirty_rect.x, window->dirty_rect.y, window->dirty_rect.w, window->dirty_rect.h, False);
       +        if (!window->root_widget) return;
       +        /* FIXME: actually respect the dirty rect... */
       +        ptr = window->root_widget;
       +        ptr->draw(ptr);
       +}
       +
       +void
       +ltk_window_other_event(ltk_window *window, XEvent event) {
       +        ltk_widget *ptr = window->root_widget;
       +        if (event.type == ConfigureNotify) {
       +                unsigned int w, h;
       +                w = event.xconfigure.width;
       +                h = event.xconfigure.height;
       +                int orig_w = window->rect.w;
       +                int orig_h = window->rect.h;
       +                if (orig_w != w || orig_h != h) {
       +                        window->rect.w = w;
       +                        window->rect.h = h;
       +                        if (ptr && ptr->resize) {
       +                                ptr->rect.w = w;
       +                                ptr->rect.h = h;
       +                                ptr->resize(ptr, orig_w, orig_h);
       +                        }
       +                }
       +        } else if (event.type == Expose && event.xexpose.count == 0) {
       +                ltk_rect r;
       +                r.x = event.xexpose.x;
       +                r.y = event.xexpose.y;
       +                r.w = event.xexpose.width;
       +                r.h = event.xexpose.height;
       +                ltk_window_invalidate_rect(window, r);
       +        } else if (event.type == ClientMessage
       +            && event.xclient.data.l[0] == window->wm_delete_msg) {
       +                ltk_destroy_window(window);
       +                exit(0); /* FIXME */
       +        }
       +}
       +
       +ltk_window *
       +ltk_create_window(const char *theme_path, const char *title, int x, int y, unsigned int w, unsigned int h) {
       +        ltk_window *window = malloc(sizeof(ltk_window));
       +        if (!window)
       +                ltk_fatal("Not enough memory left for window!\n");
       +
       +        window->dpy = XOpenDisplay(NULL);
       +        window->screen = DefaultScreen(window->dpy);
       +        window->cm = DefaultColormap(window->dpy, window->screen);
       +        /* FIXME: validate theme properly */
       +        ltk_load_theme(window, theme_path);
       +        window->wm_delete_msg = XInternAtom(window->dpy, "WM_DELETE_WINDOW", False);
       +
       +        ltk_window_theme *wtheme = window->theme->window;
       +        window->xwindow =
       +            XCreateSimpleWindow(window->dpy, DefaultRootWindow(window->dpy), x, y,
       +                                w, h, wtheme->border_width,
       +                                wtheme->fg.pixel, wtheme->bg.pixel);
       +        window->gc = XCreateGC(window->dpy, window->xwindow, 0, 0);
       +        XSetForeground(window->dpy, window->gc, wtheme->fg.pixel);
       +        XSetBackground(window->dpy, window->gc, wtheme->bg.pixel);
       +        XSetStandardProperties(window->dpy, window->xwindow, title, NULL, None,
       +                               NULL, 0, NULL);
       +        XSetWMProtocols(window->dpy, window->xwindow, &window->wm_delete_msg, 1);
       +        window->root_widget = NULL;
       +
       +        ltk_init_text(wtheme->font, window->dpy, window->screen, window->cm);
       +
       +        window->other_event = &ltk_window_other_event;
       +
       +        window->rect.w = 0;
       +        window->rect.h = 0;
       +        window->rect.x = 0;
       +        window->rect.y = 0;
       +        window->dirty_rect.w = 0;
       +        window->dirty_rect.h = 0;
       +        window->dirty_rect.x = 0;
       +        window->dirty_rect.y = 0;
       +
       +        window->widget_hash = kh_init(widget);
       +
       +        XClearWindow(window->dpy, window->xwindow);
       +        XMapRaised(window->dpy, window->xwindow);
       +        XSelectInput(window->dpy, window->xwindow,
       +                     ExposureMask | KeyPressMask | KeyReleaseMask |
       +                     ButtonPressMask | ButtonReleaseMask |
       +                     StructureNotifyMask | PointerMotionMask);
       +
       +        return window;
       +}
       +
       +void
       +ltk_destroy_window(ltk_window *window) {
       +        khint_t k;
       +        ltk_widget *ptr;
       +        XDestroyWindow(window->dpy, window->xwindow);
       +        for (k = kh_begin(window->widget_hash); k != kh_end(window->widget_hash); k++) {
       +                if (kh_exist(window->widget_hash, k)) {
       +                        ptr = kh_value(window->widget_hash, k);
       +                        ptr->destroy(ptr, 1);
       +                }
       +        }
       +        kh_destroy(widget, window->widget_hash);
       +        ltk_cleanup_text();
       +        XCloseDisplay(window->dpy);
       +        free(window);
       +}
       +
       +void
       +ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value) {
       +        /* FIXME: A whole lot more error checking! */
       +        if (strcmp(prop, "border_width") == 0) {
       +                window->theme->window->border_width = atoi(value);
       +        } else if (strcmp(prop, "bg") == 0) {
       +                ltk_create_xcolor(window, value, &window->theme->window->bg);
       +        } else if (strcmp(prop, "fg") == 0) {
       +                ltk_create_xcolor(window, value, &window->theme->window->fg);
       +        } else if (strcmp(prop, "font") == 0) {
       +                window->theme->window->font = strdup(value);
       +        } else if (strcmp(prop, "font_size") == 0) {
       +                window->theme->window->font_size = atoi(value);
       +        }
       +}
       +
       +int
       +ltk_ini_handler(void *window, const char *widget, const char *prop, const char *value) {
       +        if (strcmp(widget, "window") == 0) {
       +                ltk_window_ini_handler(window, prop, value);
       +        } else if (strcmp(widget, "button") == 0) {
       +                ltk_button_ini_handler(window, prop, value);
       +        } else {
       +                return 0;
       +        }
       +        return 1;
       +}
       +
       +static void
       +ltk_load_theme(ltk_window *window, const char *path) {
       +        window->theme = malloc(sizeof(ltk_theme));
       +        if (!window->theme) ltk_fatal("Unable to allocate memory for theme.\n");
       +        window->theme->window = malloc(sizeof(ltk_window_theme));
       +        if (!window->theme->window) ltk_fatal("Unable to allocate memory for window theme.\n");
       +        window->theme->button = NULL;
       +        if (ini_parse(path, ltk_ini_handler, window) < 0) {
       +                (void)fprintf(stderr, "ERROR: Can't load theme %s\n.", path);
       +                exit(1);
       +        }
       +}
       +
       +static void
       +ltk_destroy_theme(ltk_theme *theme) {
       +        free(theme->button);
       +        free(theme->window);
       +        free(theme);
       +}
       +
       +int
       +ltk_collide_rect(ltk_rect rect, int x, int y) {
       +        return (rect.x <= x && (rect.x + rect.w) >= x && rect.y <= y
       +                && (rect.y + rect.h) >= y);
       +}
       +
       +void
       +ltk_window_remove_active_widget(ltk_window *window) {
       +        ltk_widget *widget = window->active_widget;
       +        if (!widget) return;
       +        while (widget) {
       +                widget->state = LTK_NORMAL;
       +                widget->active_widget = NULL;
       +                if (widget->change_state)
       +                        widget->change_state(widget);
       +                if (widget->needs_redraw)
       +                        ltk_window_invalidate_rect(window, widget->rect);
       +                widget = widget->parent;
       +        }
       +        window->active_widget = NULL;
       +}
       +
       +void
       +ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
       +        window->active_widget = widget;
       +        ltk_widget *parent = widget->parent;
       +        widget->state = LTK_ACTIVE;
       +        while (parent) {
       +                widget->state = LTK_ACTIVE;
       +                parent->active_widget = widget;
       +                widget = parent;
       +                parent = widget->parent;
       +        }
       +}
       +
       +void
       +ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
       +    void (*draw) (void *), void (*change_state) (void *),
       +    void (*destroy) (void *, int), unsigned int needs_redraw,
       +    ltk_widget_type type) {
       +        widget->id = strdup(id);
       +        widget->window = window;
       +        widget->active_widget = NULL;
       +        widget->parent = NULL;
       +        widget->type = type;
       +
       +        widget->key_press = NULL;
       +        widget->key_release = NULL;
       +        widget->mouse_press = NULL;
       +        widget->mouse_release = NULL;
       +        widget->motion_notify = NULL;
       +        widget->mouse_enter = NULL;
       +        widget->mouse_leave = NULL;
       +
       +        widget->resize = NULL;
       +        widget->draw = draw;
       +        widget->change_state = change_state;
       +        widget->destroy = destroy;
       +
       +        widget->needs_redraw = needs_redraw;
       +        widget->state = LTK_NORMAL;
       +        widget->row = 0;
       +        widget->rect.x = 0;
       +        widget->rect.y = 0;
       +        widget->rect.w = 100;
       +        widget->rect.h = 100;
       +
       +        widget->row = 0;
       +        widget->column = 0;
       +        widget->row_span = 0;
       +        widget->column_span = 0;
       +        widget->sticky = 0;
       +}
       +
       +void
       +ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event) {
       +        if (!widget || widget->state == LTK_DISABLED)
       +                return;
       +        if (event.xbutton.button == 1) {
       +                /* ltk_widget *parent = widget->parent; FIXME */
       +                widget->state = LTK_PRESSED;
       +                if (widget->change_state)
       +                        widget->change_state(widget);
       +                if (widget->needs_redraw)
       +                        ltk_window_invalidate_rect(widget->window, widget->rect);
       +        }
       +        if (widget->mouse_press) {
       +                widget->mouse_press(widget, event);
       +        }
       +}
       +
       +void
       +ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event) {
       +        if (!widget || widget->state == LTK_DISABLED)
       +                return;
       +        if (widget->state == LTK_PRESSED) {
       +                widget->state = LTK_ACTIVE;
       +                if (widget->change_state)
       +                        widget->change_state(widget);
       +                if (widget->needs_redraw)
       +                        ltk_window_invalidate_rect(widget->window, widget->rect);
       +        }
       +        if (widget->mouse_release) {
       +                widget->mouse_release(widget, event);
       +        }
       +}
       +
       +void
       +ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) {
       +        if (!widget) return;
       +        short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
       +        if ((widget->state == LTK_NORMAL) && !pressed) {
       +                widget->state = LTK_ACTIVE;
       +                if (widget->change_state)
       +                        widget->change_state(widget);
       +                if (widget->mouse_enter)
       +                        widget->mouse_enter(widget, event);
       +                /* FIXME: do this properly */
       +                if (widget->window->active_widget != widget && !widget->motion_notify) {
       +                        ltk_window_remove_active_widget(widget->window);
       +                        ltk_window_set_active_widget(widget->window, widget);
       +                }
       +                if (widget->needs_redraw)
       +                        ltk_window_invalidate_rect(widget->window, widget->rect);
       +        }
       +        if (widget->motion_notify)
       +                widget->motion_notify(widget, event);
       +}
       +
       +void
       +ltk_handle_event(ltk_window *window, XEvent event) {
       +        ltk_widget *root_widget = window->root_widget;
       +        switch (event.type) {
       +        case KeyPress:
       +                break;
       +        case KeyRelease:
       +                break;
       +        case ButtonPress:
       +                if (root_widget)
       +                        ltk_widget_mouse_press_event(root_widget, event);
       +                break;
       +        case ButtonRelease:
       +                if (root_widget)
       +                        ltk_widget_mouse_release_event(root_widget, event);
       +                break;
       +        case MotionNotify:
       +                if (root_widget)
       +                        ltk_widget_motion_notify_event(root_widget, event);
       +                break;
       +        default:
       +                if (window->other_event)
       +                        window->other_event(window, event);
       +        }
       +}
       +
       +int
       +ltk_check_widget_id_free(ltk_window *window, const char *id, const char *caller) {
       +        khint_t k;
       +        k = kh_get(widget, window->widget_hash, id);
       +        if (k != kh_end(window->widget_hash)) {
       +                (void)fprintf(stderr, "%s: Widget \"%s\" already exists.\n", caller, id);
       +                return 0;
       +        }
       +        return 1;
       +}
       +
       +ltk_widget *
       +ltk_get_widget(ltk_window *window, const char *id, ltk_widget_type type,
       +    const char *caller) {
       +        khint_t k;
       +        ltk_widget *widget;
       +        k = kh_get(widget, window->widget_hash, id);
       +        if (k == kh_end(window->widget_hash)) {
       +                (void)fprintf(stderr, "%s: Widget \"%s\" doesn't exist.\n", caller, id);
       +                return NULL;
       +        }
       +        widget = kh_value(window->widget_hash, k);
       +        if (type != LTK_WIDGET && widget->type != type) {
       +                (void)fprintf(stderr, "%s: Widget \"%s\" has wrong type.\n", caller, id);
       +                return NULL;
       +        }
       +        return widget;
       +}
       +
       +void
       +ltk_set_widget(ltk_window *window, ltk_widget *widget, const char *id) {
       +        int ret;
       +        khint_t k;
       +        /* apparently, khash requires the string to stay accessible */
       +        char *tmp = strdup(id);
       +        k = kh_put(widget, window->widget_hash, tmp, &ret);
       +        kh_value(window->widget_hash, k) = widget;
       +}
       +
       +void
       +ltk_remove_widget(ltk_window *window, const char *id) {
       +        khint_t k;
       +        k = kh_get(widget, window->widget_hash, id);
       +        if (k != kh_end(window->widget_hash)) {
       +                kh_del(widget, window->widget_hash, k);
       +        }
       +}
       +
       +int main(int argc, char *argv[]) {
       +        ltk_window *window = ltk_create_window("theme.ini", "Demo", 0, 0, 500, 500);
       +        return ltk_mainloop(window);
       +}
   DIR diff --git a/socket_format.txt b/socket_format.txt
       t@@ -1,5 +1,5 @@
       -Note: This is not implemented yet; it is just here to collect
       -my thoughts while I keep working.
       +Note: This is not fully implemented yet; it is just here to
       +collect my thoughts while I keep working.
        
        <widget type> <widget id> <command> <args>
        > grid grd1 create 2 2
   DIR diff --git a/test.sh b/test.sh
       t@@ -0,0 +1,27 @@
       +#!/bin/sh
       +
       +# This is very hacky.
       +#
       +# All events are still printed to the terminal curerntly because
       +# the second './ltkc' still prints everything - event masks aren't
       +# supported yet.
       +#
       +# Currently, everything just uses the socket 'ltk.sock'. My idea
       +# was to have a directory containing sockets for all instances of
       +# ltks, each of them named after the X Window ID used. This would
       +# allow other tools (screenreader, etc.) to determine which socket
       +# to use to communicate with a window. Probably ltkd would just
       +# print out its window ID when starting and then daemonize itself,
       +# so the calling script can just use save the output of ltkd and
       +# use that to communicate with the socket using ltkc.
       +
       +./ltkd&
       +sleep 0.2
       +
       +cat test.gui | ./ltkc | while read cmd
       +do
       +        case "$cmd" in
       +        "btn1 button_click") echo "quit"
       +        ;;
       +        esac
       +done | ./ltkc
   DIR diff --git a/test_anim.sh b/test_anim.sh
       t@@ -1,29 +0,0 @@
       -#!/bin/sh
       -
       -echo "grid grd1 create 2 2
       -grid grd1 set-row-weight 0 1
       -grid grd1 set-row-weight 1 1
       -grid grd1 set-column-weight 0 1
       -grid grd1 set-column-weight 1 1
       -set-root-widget grd1
       -draw drw1 create 100 100 #fff
       -grid grd1 add drw1 0 1 1 1 15
       -draw drw1 set-color #000
       -"
       -
       -i=0
       -while [ $i -lt 400 ]; do
       -        j=0
       -        while [ $j -lt 20 ]; do
       -                echo "draw drw1 line $((i+j)) $j $((i+j+2)) $((j+2))";
       -                sleep 0.05
       -                j=$((j+2))
       -        done
       -        j=20
       -        while [ $j -gt 0 ]; do
       -                echo "draw drw1 line $((i+40-j)) $j $((i+42-j)) $((j-2))";
       -                sleep 0.05
       -                j=$((j-2))
       -        done
       -        i=$((i+40));
       -done
   DIR diff --git a/test_draw.gui b/test_draw.gui
       t@@ -1,10 +0,0 @@
       -grid grd1 create 2 2
       -grid grd1 set-row-weight 0 1
       -grid grd1 set-row-weight 1 1
       -grid grd1 set-column-weight 0 1
       -grid grd1 set-column-weight 1 1
       -set-root-widget grd1
       -draw drw1 create 100 100 #fff
       -grid grd1 add drw1 0 1 1 1 15
       -draw drw1 set-color #000
       -draw drw1 line 0 0 100 100