commit a57cf5fb31150459013031565c1fd026e03901de
parent 5d5aaf08dc19d798f31cab139e1030918c1f62ac
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(-)
diff --git a/Makefile b/Makefile
@@ -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)
diff --git a/ltk.h b/ltk.h
@@ -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,
diff --git a/ltkc.c b/ltkc.c
@@ -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>
@@ -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;
@@ -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;
@@ -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) {
@@ -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) {
@@ -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;
 }
diff --git a/ltkd.c b/ltkd.c
@@ -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>
@@ -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;
@@ -448,7 +134,6 @@ ltk_mainloop(ltk_window *window) {
 
 	FD_SET(listenfd, &rallfds);
 	maxfd = listenfd;
-	maxi = -1;
 
 	while (running) {
 		rfds = rallfds;
@@ -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");
@@ -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)) {
@@ -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 */
@@ -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)
@@ -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;
@@ -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) {
@@ -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)
@@ -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;
@@ -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) {
@@ -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;
+	}
 }
diff --git a/util.c b/util.c
@@ -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;
+}
diff --git a/util.h b/util.h
@@ -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);