URI: 
       tCleanup - 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 a57cf5fb31150459013031565c1fd026e03901de
   DIR parent 5d5aaf08dc19d798f31cab139e1030918c1f62ac
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sun, 27 Dec 2020 22:03:51 +0100
       
       Cleanup
       
       Diffstat:
         M Makefile                            |       4 ++--
         M ltk.h                               |      25 -------------------------
         M ltkc.c                              |      57 ++++++++++++++++++-------------
         M ltkd.c                              |     760 ++++++++++++++++---------------
         M util.c                              |      15 +++++++++++++++
         M util.h                              |       1 +
       
       6 files changed, 455 insertions(+), 407 deletions(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       t@@ -7,8 +7,8 @@ all: ltkd ltkc
        ltkd: $(OBJ) $(COMPATOBJ)
                $(CC) -o $@ $(OBJ) $(COMPATOBJ) $(LDFLAGS)
        
       -ltkc: ltkc.o
       -        $(CC) -o $@ ltkc.o
       +ltkc: ltkc.o util.o
       +        $(CC) -o $@ ltkc.o util.o
        
        %.o: %.c
                $(CC) -c -o $@ $< $(CFLAGS)
   DIR diff --git a/ltk.h b/ltk.h
       t@@ -137,44 +137,19 @@ typedef struct ltk_window {
        } ltk_window;
        
        void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect);
       -
        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, ltk_event_type type, const char *id, const char *data);
       -
       -int ltk_mainloop(ltk_window *window);
       -
       -ltk_window *ltk_create_window(
       -    const char *theme_path, const char *title,
       -    int x, int y, unsigned int w, unsigned int h);
       -
       -void ltk_redraw_window(ltk_window *window);
       -
       -void ltk_destroy_window(ltk_window *window);
       -
       -void ltk_window_other_event(ltk_window *window, XEvent event);
       -
        int ltk_collide_rect(ltk_rect rect, int x, int y);
       -
        void ltk_remove_active_widget(ltk_widget *widget);
       -
        void ltk_set_active_widget(ltk_window *window, ltk_widget *widget);
       -
        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);
       -
        void ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event);
       -
        void ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event);
       -
        void ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event);
       -
       -void ltk_handle_event(ltk_window *window, XEvent event);
       -
        int ltk_check_widget_id_free(ltk_window *window, const char *id,
            const char *caller);
        ltk_widget *ltk_get_widget(ltk_window *window, const char *id,
   DIR diff --git a/ltkc.c b/ltkc.c
       t@@ -24,7 +24,6 @@
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
       -#include <stdint.h>
        #include <stddef.h>
        #include <unistd.h>
        #include <time.h>
       t@@ -32,24 +31,15 @@
        #include <sys/select.h>
        #include <sys/socket.h>
        #include <sys/un.h>
       +#include "util.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;
       +        char *in_buffer;  /* text that is read from stdin and written to the socket */
                int in_len;
                int in_alloc;
       -        char *out_buffer;
       +        char *out_buffer; /* text that is read from the socket and written to stdout */
                int out_len;
                int out_alloc;
        } io_buffers;
       t@@ -97,7 +87,10 @@ int main(int argc, char *argv[]) {
                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)
       +                if (!FD_ISSET(infd, &rallfds) &&
       +                    !FD_ISSET(sockfd, &rallfds) &&
       +                    io_buffers.in_len == 0 &&
       +                    io_buffers.out_len == 0)
                                break;
                        rfds = rallfds;
                        wfds = wallfds;
       t@@ -105,9 +98,14 @@ int main(int argc, char *argv[]) {
                           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);
       +                        ltk_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) {
       t@@ -117,9 +115,14 @@ int main(int argc, char *argv[]) {
                                        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);
       +                        ltk_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) {
       t@@ -128,28 +131,36 @@ int main(int argc, char *argv[]) {
                                        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 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);
       +                                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 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);
       +                                memmove(io_buffers.out_buffer,
       +                                        io_buffers.out_buffer + nwritten,
       +                                        io_buffers.out_len - nwritten);
                                        io_buffers.out_len -= nwritten;
                                }
                        }
                }
        
       -        /* FIXME: close socket */
       +        close(sockfd);
                return 0;
        }
   DIR diff --git a/ltkd.c b/ltkd.c
       t@@ -25,7 +25,6 @@
        #include <stdlib.h>
        #include <string.h>
        #include <stdint.h>
       -#include <fcntl.h>
        #include <unistd.h>
        #include <time.h>
        #include <errno.h>
       t@@ -73,356 +72,43 @@ static struct ltk_sock_info {
                struct token_list tokens;  /* current tokens */
        } sockets[MAX_SOCK_CONNS];
        
       +static int ltk_mainloop(ltk_window *window);
       +static ltk_window *ltk_create_window(const char *theme_path, const char
       +    *title, int x, int y, unsigned int w, unsigned int h);
       +static void ltk_destroy_window(ltk_window *window);
       +static void ltk_redraw_window(ltk_window *window);
       +static void ltk_window_other_event(ltk_window *window, XEvent event);
       +static void ltk_handle_event(ltk_window *window, XEvent event);
        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 push_token(struct token_list *tl, char *token);
       +static int read_sock(struct ltk_sock_info *sock);
       +static int write_sock(struct ltk_sock_info *sock);
       +static int queue_sock_write(struct ltk_sock_info *sock, const char *str, int len);
       +static int tokenize_command(struct ltk_sock_info *sock);
       +static void ltk_set_root_widget_cmd(ltk_window *window, char **tokens, int num_tokens);
       +static void process_commands(ltk_window *window, struct ltk_sock_info *sock);
       +static ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2);
       +static int add_client(int fd);
       +static int listen_sock(const char *sock_path);
       +static int accept_sock(int listenfd);
        
       -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;
       +static int maxsocket = -1;
       +static char running = 1;
       +static char sock_write_available = 0;
        
       -errout:
       -        err = errno;
       -        close(fd);
       -        errno = err;
       -        return rval;
       +int main(int argc, char *argv[]) {
       +        ltk_window *window = ltk_create_window("theme.ini", "Demo", 0, 0, 500, 500);
       +        return ltk_mainloop(window);
        }
        
        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 maxfd, listenfd;
                int rretval, wretval;
                int clifd;
                struct timeval tv;
       t@@ -448,7 +134,6 @@ ltk_mainloop(ltk_window *window) {
        
                FD_SET(listenfd, &rallfds);
                maxfd = listenfd;
       -        maxi = -1;
        
                while (running) {
                        rfds = rallfds;
       t@@ -461,11 +146,8 @@ ltk_mainloop(ltk_window *window) {
                                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 (rretval > 0 || (sock_write_available && wretval > 0)) {
                                if (FD_ISSET(listenfd, &rfds)) {
                                        if ((clifd = accept_sock(listenfd)) < 0) {
                                                fprintf(stderr, "Error accepting socket connection\n");
       t@@ -476,11 +158,11 @@ ltk_mainloop(ltk_window *window) {
                                        FD_SET(clifd, &wallfds);
                                        if (clifd > maxfd)
                                                maxfd = clifd;
       -                                if (i > maxi)
       -                                        maxi = i;
       +                                if (i > maxsocket)
       +                                        maxsocket = i;
                                        continue;
                                }
       -                        for (int i = 0; i <= maxi; i++) {
       +                        for (int i = 0; i <= maxsocket; i++) {
                                        if ((clifd = sockets[i].fd) < 0)
                                                continue;
                                        if (FD_ISSET(clifd, &rfds)) {
       t@@ -499,17 +181,19 @@ ltk_mainloop(ltk_window *window) {
                                        }
                                }
                        }
       +
                        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++) {
       +                                for (int i = 0; i <= maxsocket; 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 */
       t@@ -524,7 +208,10 @@ ltk_mainloop(ltk_window *window) {
                        } 
        
                }
       +
                for (int i = 0; i < MAX_SOCK_CONNS; i++) {
       +                if (sockets[i].fd >= 0)
       +                        close(sockets[i].fd);
                        if (sockets[i].read)
                                free(sockets[i].read);
                        if (sockets[i].to_write)
       t@@ -533,12 +220,108 @@ ltk_mainloop(ltk_window *window) {
                                free(sockets[i].tokens.tokens);
                }
        
       +        close(listenfd);
       +
                unlink("ltk.sock");
        
                return 0;
        }
        
       -void
       +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 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 void
        ltk_redraw_window(ltk_window *window) {
                ltk_widget *ptr;
                if (!window) return;
       t@@ -555,7 +338,7 @@ ltk_redraw_window(ltk_window *window) {
                ptr->draw(ptr);
        }
        
       -void
       +static void
        ltk_window_other_event(ltk_window *window, XEvent event) {
                ltk_widget *ptr = window->root_widget;
                if (event.type == ConfigureNotify) {
       t@@ -587,7 +370,7 @@ ltk_window_other_event(ltk_window *window, XEvent event) {
                }
        }
        
       -ltk_window *
       +static 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)
       t@@ -638,7 +421,7 @@ ltk_create_window(const char *theme_path, const char *title, int x, int y, unsig
                return window;
        }
        
       -void
       +static void
        ltk_destroy_window(ltk_window *window) {
                khint_t k;
                ltk_widget *ptr;
       t@@ -832,7 +615,7 @@ ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) {
                        widget->motion_notify(widget, event);
        }
        
       -void
       +static void
        ltk_handle_event(ltk_window *window, XEvent event) {
                ltk_widget *root_widget = window->root_widget;
                switch (event.type) {
       t@@ -906,7 +689,270 @@ ltk_remove_widget(ltk_window *window, const char *id) {
                }
        }
        
       -int main(int argc, char *argv[]) {
       -        ltk_window *window = ltk_create_window("theme.ini", "Demo", 0, 0, 500, 500);
       -        return ltk_mainloop(window);
       +/* Push a token onto `token_list`, resizing the buffer if necessary.
       +   Returns -1 on error, 0 otherwise.
       +   Note: The token is not copied, it is only added directly. */
       +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;
       +}
       +
       +/* Add a new client to the socket list and return the index in `sockets`.
       +   Returns -1 if there is no space for a new client. */
       +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 */
       +/* Listen on the socket at `sock_path`.
       +   Returns the file descriptor of the opened socket on success.
       +   Returns -1 if `sock_path` is too long
       +           -2 if the socket could not be created
       +           -3 if the socket could not be bound to the path
       +           -4 if the socket could not be listened on */
       +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;
       +}
       +
       +/* Accept a socket connection on the listening socket `listenfd`.
       +   Returns the file descriptor of the accepted client on success.
       +   Returns -1 if there was an error. */
       +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;
       +}
       +
       +/* Read up to READ_BLK_SIZE bytes from the socket `sock`.
       +   Returns -1 if an error occurred, 0 if the connection was closed, 1 otherwise.
       +   Note: Returning 1 on success is weird, but it could also be confusing to
       +   return 0 on success when `read` returns that to mean that the connection
       +   was closed. */
       +static int
       +read_sock(struct ltk_sock_info *sock) {
       +        int nread;
       +        char *old = sock->read;
       +        int ret = ltk_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;
       +
       +        /* check if any sockets have text to write */
       +        if (sock->write_cur == sock->write_len) {
       +                int found = 0;
       +                for (int i = 0; i < maxsocket; i++) {
       +                        if (sockets[i].fd != -1 &&
       +                            sockets[i].write_cur != sockets[i].write_len) {
       +                                found = 1;
       +                                break;
       +                        }
       +                }
       +                if (!found)
       +                        sock_write_available = 0;
       +        }
       +
       +        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 &&
       +            ltk_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;
       +
       +        sock_write_available = 1;
       +
       +        return 0;
       +}
       +
       +/* Tokenize the current read buffer in `sock`.
       +   Returns 0 immediately 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;
       +}
       +
       +/* Process the commands as they are read from the socket. */
       +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;
       +        }
        }
   DIR diff --git a/util.c b/util.c
       t@@ -46,3 +46,18 @@ ltk_read_file(const char *path, unsigned long *len) {
        
                return file_contents;
        }
       +
       +/* If `needed` is larger than `*alloc_size`, resize `*str` to
       +   `max(needed, *alloc_size * 2)`.
       +   Returns 1 on error, 0 otherwise. If an error occurs, `*str` and
       +   `*alloc_size` are not modified. */
       +int
       +ltk_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;
       +}
   DIR diff --git a/util.h b/util.h
       t@@ -29,3 +29,4 @@ strtonum(const char *numstr, long long minval, long long maxval,
        
        void ltk_err(const char *msg);
        char *ltk_read_file(const char *path, unsigned long *len);
       +int ltk_grow_string(char **str, int *alloc_size, int needed);