ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

ltkd.c (58197B)


      1 /* FIXME: Figure out how to properly print window id */
      2 /* FIXME: error checking in tokenizer (is this necessary?) */
      3 /* FIXME: strip whitespace at end of lines in socket format */
      4 /*
      5  * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org>
      6  *
      7  * Permission to use, copy, modify, and/or distribute this software for any
      8  * purpose with or without fee is hereby granted, provided that the above
      9  * copyright notice and this permission notice appear in all copies.
     10  *
     11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     18  */
     19 
     20 #include <time.h>
     21 #include <stdio.h>
     22 #include <fcntl.h>
     23 #include <errno.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 #include <stdarg.h>
     27 #include <unistd.h>
     28 #include <signal.h>
     29 #include <stdint.h>
     30 #include <locale.h>
     31 #include <inttypes.h>
     32 
     33 #include <sys/un.h>
     34 #include <sys/stat.h>
     35 #include <sys/wait.h>
     36 #include <sys/types.h>
     37 #include <sys/select.h>
     38 #include <sys/socket.h>
     39 
     40 #include "ini.h"
     41 #include "khash.h"
     42 
     43 #include "graphics.h"
     44 #include "surface_cache.h"
     45 #include "theme.h"
     46 #include "memory.h"
     47 #include "color.h"
     48 #include "rect.h"
     49 #include "widget.h"
     50 #include "ltk.h"
     51 #include "util.h"
     52 #include "text.h"
     53 #include "grid.h"
     54 /* #include "draw.h" */
     55 #include "button.h"
     56 #include "entry.h"
     57 #include "label.h"
     58 #include "scrollbar.h"
     59 #include "box.h"
     60 #include "menu.h"
     61 #include "image.h"
     62 #include "image_widget.h"
     63 #include "macros.h"
     64 #include "config.h"
     65 
     66 #define MAX_WINDOW_FONT_SIZE 200
     67 
     68 #define MAX_SOCK_CONNS 20
     69 #define READ_BLK_SIZE 128
     70 #define WRITE_BLK_SIZE 128
     71 
     72 struct token_list {
     73 	ltk_cmd_token *tokens;
     74 	/* FIXME: size_t everywhere */
     75 	int num_tokens;
     76 	int num_alloc;
     77 };
     78 
     79 /* FIXME: switch to size_t */
     80 static struct ltk_sock_info {
     81 	int fd;                    /* file descriptor for socket connection */
     82 	int event_mask;            /* events to send to socket */
     83 	char *read;                /* text read from socket */
     84 	int read_len;              /* length of text in read buffer */
     85 	int read_alloc;            /* size of read buffer */
     86 	char *to_write;            /* text to be written to socket */
     87 	int write_len;             /* length of text in write buffer */
     88 	int write_cur;             /* length of text already written */
     89 	int write_alloc;           /* size of write buffer */
     90 	/* stuff for tokenizing */
     91 	int in_token;              /* last read char is inside token */
     92 	int offset;                /* offset from removing backslashes */
     93 	int in_str;                /* last read char is inside string */
     94 	int read_cur;              /* length of text already tokenized */
     95 	int bs;                    /* last char was non-escaped backslash */
     96 	struct token_list tokens;  /* current tokens */
     97 	uint32_t last_seq;         /* sequence number of last request processed */
     98 } sockets[MAX_SOCK_CONNS];
     99 
    100 typedef struct {
    101 	void (*callback)(void *);
    102 	void *data;
    103 	struct timespec repeat;
    104 	struct timespec remaining;
    105 	int id;
    106 } ltk_timer;
    107 
    108 static ltk_timer *timers = NULL;
    109 static size_t timers_num = 0;
    110 static size_t timers_alloc = 0;
    111 
    112 static int daemonize_flag = 1;
    113 
    114 static int ltk_mainloop(ltk_window *window);
    115 static char *get_sock_path(char *basedir, Window id);
    116 static FILE *open_log(char *dir);
    117 static void daemonize(void);
    118 static ltk_window *ltk_create_window(const char *title, int x, int y,
    119     unsigned int w, unsigned int h);
    120 static void ltk_destroy_window(ltk_window *window);
    121 static void ltk_redraw_window(ltk_window *window);
    122 static void ltk_window_other_event(ltk_window *window, ltk_event *event);
    123 static void ltk_handle_event(ltk_window *window, ltk_event *event);
    124 
    125 static void ltk_load_theme(ltk_window *window, const char *path);
    126 static void ltk_uninitialize_theme(ltk_window *window);
    127 static int ltk_ini_handler(void *window, const char *widget, const char *prop, const char *value);
    128 static int ltk_window_fill_theme_defaults(ltk_window *window);
    129 static int ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value);
    130 static void ltk_window_uninitialize_theme(ltk_window *window);
    131 
    132 static int read_sock(struct ltk_sock_info *sock);
    133 static int push_token(struct token_list *tl, char *token);
    134 static int read_sock(struct ltk_sock_info *sock);
    135 static int write_sock(struct ltk_sock_info *sock);
    136 static int tokenize_command(struct ltk_sock_info *sock);
    137 static int ltk_set_root_widget_cmd(ltk_window *window, ltk_cmd_token *tokens, int num_tokens, ltk_error *err);
    138 static int process_commands(ltk_window *window, int client);
    139 static int add_client(int fd);
    140 static int listen_sock(const char *sock_path);
    141 static int accept_sock(int listenfd);
    142 
    143 static short maxsocket = -1;
    144 static short running = 1;
    145 static short sock_write_available = 0;
    146 static char *ltk_dir = NULL;
    147 static FILE *ltk_logfile = NULL;
    148 static char *sock_path = NULL;
    149 /* Note: Most functions still take this explicitly because it wasn't
    150    global originally, but that's just the way it is. */
    151 static ltk_window *main_window = NULL;
    152 
    153 typedef struct {
    154 	char *name;
    155 	int (*ini_handler)(ltk_window *, const char *, const char *);
    156 	int (*fill_theme_defaults)(ltk_window *);
    157 	void (*uninitialize_theme)(ltk_window *);
    158 	int (*register_keypress)(const char *, size_t, ltk_keypress_binding);
    159 	int (*register_keyrelease)(const char *, size_t, ltk_keyrelease_binding);
    160 	void (*cleanup)(void);
    161 	int (*cmd)(ltk_window *, ltk_cmd_token *, size_t, ltk_error *);
    162 } ltk_widget_funcs;
    163 
    164 /* FIXME: use binary search when searching for the widget */
    165 ltk_widget_funcs widget_funcs[] = {
    166 	{
    167 		.name = "box",
    168 		.ini_handler = NULL,
    169 		.fill_theme_defaults = NULL,
    170 		.uninitialize_theme = NULL,
    171 		.register_keypress = NULL,
    172 		.register_keyrelease = NULL,
    173 		.cleanup = NULL,
    174 		.cmd = &ltk_box_cmd
    175 	},
    176 	{
    177 		.name = "button",
    178 		.ini_handler = &ltk_button_ini_handler,
    179 		.fill_theme_defaults = &ltk_button_fill_theme_defaults,
    180 		.uninitialize_theme = &ltk_button_uninitialize_theme,
    181 		.register_keypress = NULL,
    182 		.register_keyrelease = NULL,
    183 		.cleanup = NULL,
    184 		.cmd = &ltk_button_cmd
    185 	},
    186 	{
    187 		.name = "entry",
    188 		.ini_handler = &ltk_entry_ini_handler,
    189 		.fill_theme_defaults = &ltk_entry_fill_theme_defaults,
    190 		.uninitialize_theme = &ltk_entry_uninitialize_theme,
    191 		.register_keypress = &ltk_entry_register_keypress,
    192 		.register_keyrelease = &ltk_entry_register_keyrelease,
    193 		.cleanup = &ltk_entry_cleanup,
    194 		.cmd = &ltk_entry_cmd
    195 	},
    196 	{
    197 		.name = "grid",
    198 		.ini_handler = NULL,
    199 		.fill_theme_defaults = NULL,
    200 		.uninitialize_theme = NULL,
    201 		.register_keypress = NULL,
    202 		.register_keyrelease = NULL,
    203 		.cleanup = NULL,
    204 		.cmd = &ltk_grid_cmd
    205 	},
    206 	{
    207 		.name = "label",
    208 		.ini_handler = &ltk_label_ini_handler,
    209 		.fill_theme_defaults = &ltk_label_fill_theme_defaults,
    210 		.uninitialize_theme = &ltk_label_uninitialize_theme,
    211 		.register_keypress = NULL,
    212 		.register_keyrelease = NULL,
    213 		.cleanup = NULL,
    214 		.cmd = &ltk_label_cmd
    215 	},
    216 	{
    217 		.name = "image",
    218 		.ini_handler = NULL,
    219 		.fill_theme_defaults = NULL,
    220 		.uninitialize_theme = NULL,
    221 		.register_keypress = NULL,
    222 		.register_keyrelease = NULL,
    223 		.cleanup = NULL,
    224 		.cmd = &ltk_image_widget_cmd
    225 	},
    226 	{
    227 		.name = "menu",
    228 		.ini_handler = &ltk_menu_ini_handler,
    229 		.fill_theme_defaults = &ltk_menu_fill_theme_defaults,
    230 		.uninitialize_theme = &ltk_menu_uninitialize_theme,
    231 		.register_keypress = NULL,
    232 		.register_keyrelease = NULL,
    233 		.cleanup = NULL,
    234 		.cmd = &ltk_menu_cmd
    235 	},
    236 	{
    237 		.name = "menuentry",
    238 		.ini_handler = &ltk_menuentry_ini_handler,
    239 		.fill_theme_defaults = &ltk_menuentry_fill_theme_defaults,
    240 		.uninitialize_theme = &ltk_menuentry_uninitialize_theme,
    241 		.register_keypress = NULL,
    242 		.register_keyrelease = NULL,
    243 		.cleanup = NULL,
    244 		.cmd = &ltk_menuentry_cmd
    245 	},
    246 	{
    247 		.name = "submenu",
    248 		.ini_handler = &ltk_submenu_ini_handler,
    249 		.fill_theme_defaults = &ltk_submenu_fill_theme_defaults,
    250 		.uninitialize_theme = &ltk_submenu_uninitialize_theme,
    251 		.register_keypress = NULL,
    252 		.register_keyrelease = NULL,
    253 		.cleanup = NULL,
    254 		.cmd = &ltk_menu_cmd
    255 	},
    256 	{
    257 		.name = "submenuentry",
    258 		.ini_handler = &ltk_submenuentry_ini_handler,
    259 		.fill_theme_defaults = &ltk_submenuentry_fill_theme_defaults,
    260 		.uninitialize_theme = &ltk_submenuentry_uninitialize_theme,
    261 		.register_keypress = NULL,
    262 		.register_keyrelease = NULL,
    263 		.cleanup = NULL,
    264 		/* This "widget" is only needed to have separate styles for regular
    265 		   menu entries and submenu entries. "submenu" is just an alias for
    266 		   "menu" in most cases - it's just needed when creating a menu to
    267 		   decide if it's a submenu or not.
    268 		   FIXME: is that even necessary? Why can't it just decide if it's
    269 		   a submenu based on whether it has a parent or not?
    270 		   -> I guess right-click menus are also just submenus, so they
    271 		   need to set it explicitly, but wasn't there another reaseon? */
    272 		.cmd = NULL
    273 	},
    274 	{
    275 		.name = "scrollbar",
    276 		.ini_handler = &ltk_scrollbar_ini_handler,
    277 		.fill_theme_defaults = &ltk_scrollbar_fill_theme_defaults,
    278 		.uninitialize_theme = &ltk_scrollbar_uninitialize_theme,
    279 		.register_keypress = NULL,
    280 		.register_keyrelease = NULL,
    281 		.cleanup = NULL,
    282 		.cmd = NULL
    283 	},
    284 	{
    285 		/* Handler for general widget key bindings. */
    286 		.name = "widget",
    287 		.ini_handler = NULL,
    288 		.fill_theme_defaults = NULL,
    289 		.uninitialize_theme = NULL,
    290 		.register_keypress = &ltk_widget_register_keypress,
    291 		.register_keyrelease = &ltk_widget_register_keyrelease,
    292 		.cleanup = &ltk_widget_cleanup,
    293 		.cmd = NULL
    294 	},
    295 	{
    296 		/* Handler for window theme. */
    297 		.name = "window",
    298 		.ini_handler = &ltk_window_ini_handler,
    299 		.fill_theme_defaults = &ltk_window_fill_theme_defaults,
    300 		.uninitialize_theme = &ltk_window_uninitialize_theme,
    301 		.register_keypress = NULL,
    302 		.register_keyrelease = NULL,
    303 		.cleanup = NULL,
    304 		.cmd = NULL
    305 	}
    306 };
    307 
    308 int
    309 main(int argc, char *argv[]) {
    310 	setlocale(LC_CTYPE, "");
    311 	XSetLocaleModifiers("");
    312 	int ch;
    313 	char *title = "LTK Window";
    314 	while ((ch = getopt(argc, argv, "dt:")) != -1) {
    315 		switch (ch) {
    316 			case 't':
    317 				title = optarg;
    318 				break;
    319 			case 'd':
    320 				daemonize_flag = 0;
    321 				break;
    322 			default:
    323 				ltk_fatal("USAGE: ltkd [-t title]\n");
    324 		}
    325 	}
    326 
    327 	ltk_dir = ltk_setup_directory();
    328 	if (!ltk_dir) ltk_fatal_errno("Unable to setup ltk directory.\n");
    329 	ltk_logfile = open_log(ltk_dir);
    330 	if (!ltk_logfile) ltk_fatal_errno("Unable to open log file.\n");
    331 
    332 	/* FIXME: move to widget_funcs? */
    333 	ltk_widgets_init();
    334 
    335 	/* FIXME: set window size properly - I only run it in a tiling WM
    336 	   anyways, so it doesn't matter, but still... */
    337 	main_window = ltk_create_window(title, 0, 0, 500, 500);
    338 
    339 	sock_path = get_sock_path(ltk_dir, renderer_get_window_id(main_window->renderdata));
    340 	if (!sock_path) ltk_fatal_errno("Unable to allocate memory for socket path.\n");
    341 
    342 	/* Note: sockets should be initialized to 0 because it is static */
    343 	for (int i = 0; i < MAX_SOCK_CONNS; i++) {
    344 		sockets[i].fd = -1; /* socket unused */
    345 		/* initialize these just because I'm paranoid */
    346 		sockets[i].read = NULL;
    347 		sockets[i].to_write = NULL;
    348 		sockets[i].tokens.tokens = NULL;
    349 	}
    350 
    351 	return ltk_mainloop(main_window);
    352 }
    353 
    354 /* FIXME: need to recalculate maxfd when removing client */
    355 static struct {
    356 	fd_set rallfds, wallfds;
    357 	int maxfd;
    358 	int listenfd;
    359 } sock_state;
    360 
    361 /* FIXME: this is extremely dangerous right now because pretty much any command
    362    can be executed, so for instance the widget that caused the lock could also
    363    be destroyed, causing issues when this function returns */
    364 int
    365 ltk_handle_lock_client(ltk_window *window, int client) {
    366 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
    367 		return 0;
    368 	fd_set rfds, wfds, rallfds, wallfds;
    369 	int clifd = sockets[client].fd;
    370 	FD_ZERO(&rallfds);
    371 	FD_ZERO(&wallfds);
    372 	FD_SET(clifd, &rallfds);
    373 	FD_SET(clifd, &wallfds);
    374 	int retval;
    375 	struct timeval tv;
    376 	tv.tv_sec = 0;
    377 	tv.tv_usec = 0;
    378 	struct timespec now, elapsed, last, sleep_time;
    379 	clock_gettime(CLOCK_MONOTONIC, &last);
    380 	sleep_time.tv_sec = 0;
    381 	while (1) {
    382 		rfds = rallfds;
    383 		wfds = wallfds;
    384 		retval = select(clifd + 1, &rfds, &wfds, NULL, &tv);
    385 
    386 		if (retval > 0) {
    387 			if (FD_ISSET(clifd, &rfds)) {
    388 				int ret;
    389 				while ((ret = read_sock(&sockets[client])) == 1) {
    390 					int pret;
    391 					if ((pret = process_commands(window, client)) == 1)
    392 						return 1;
    393 					else if (pret == -1)
    394 						return 0;
    395 				}
    396 				/* FIXME: maybe also return on read error? or would that be dangerous? */
    397 				if (ret == 0) {
    398 					FD_CLR(clifd, &sock_state.rallfds);
    399 					FD_CLR(clifd, &sock_state.wallfds);
    400 					ltk_widget_remove_client(client);
    401 					sockets[clifd].fd = -1;
    402 					close(clifd);
    403 					int newmaxsocket = -1;
    404 					for (int j = 0; j <= maxsocket; j++) {
    405 						if (sockets[j].fd >= 0)
    406 							newmaxsocket = j;
    407 					}
    408 					maxsocket = newmaxsocket;
    409 					if (maxsocket == -1) {
    410 						ltk_quit(window);
    411 						break;
    412 					}
    413 					return 0;
    414 				}
    415 			}
    416 			if (FD_ISSET(clifd, &wfds)) {
    417 				/* FIXME: call in loop like above */
    418 				write_sock(&sockets[client]);
    419 			}
    420 		}
    421 		clock_gettime(CLOCK_MONOTONIC, &now);
    422 		ltk_timespecsub(&now, &last, &elapsed);
    423 		/* FIXME: configure framerate */
    424 		if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) {
    425 			sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec;
    426 			nanosleep(&sleep_time, NULL);
    427 		}
    428 		last = now;
    429 	}
    430 	return 0;
    431 }
    432 
    433 static int
    434 ltk_mainloop(ltk_window *window) {
    435 	ltk_event event;
    436 	fd_set rfds, wfds;
    437 	int retval;
    438 	int clifd;
    439 	struct timeval tv;
    440 	tv.tv_sec = 0;
    441 	tv.tv_usec = 0;
    442 
    443 	FD_ZERO(&sock_state.rallfds);
    444 	FD_ZERO(&sock_state.wallfds);
    445 
    446 	if ((sock_state.listenfd = listen_sock(sock_path)) < 0)
    447 		ltk_fatal_errno("Error listening on socket.\n");
    448 
    449 	FD_SET(sock_state.listenfd, &sock_state.rallfds);
    450 	sock_state.maxfd = sock_state.listenfd;
    451 
    452 	printf("%lu", renderer_get_window_id(main_window->renderdata));
    453 	fflush(stdout);
    454 	if (daemonize_flag)
    455 		daemonize();
    456 
    457 	/* FIXME: make time management smarter - maybe always figure out how long
    458 	   it will take until the next timer is due and then sleep if no other events
    459 	   are happening */
    460 	struct timespec now, elapsed, last, lasttimer, sleep_time;
    461 	clock_gettime(CLOCK_MONOTONIC, &last);
    462 	lasttimer = last;
    463 	sleep_time.tv_sec = 0;
    464 
    465 	/* initialize keyboard mapping */
    466 	ltk_generate_keyboard_event(window->renderdata, &event);
    467 	ltk_handle_event(window, &event);
    468 
    469 	int pid = -1;
    470 	int wstatus = 0;
    471 	while (running) {
    472 		if (window->cmd_caller && (pid = waitpid(window->cmd_pid, &wstatus, WNOHANG)) > 0) {
    473 			ltk_error err;
    474 			ltk_widget *cmd_caller = ltk_get_widget(window->cmd_caller, LTK_WIDGET_ANY, &err);
    475 			/* FIXME: should commands be split into read/write and block write commands during external editing? */
    476 			/* FIXME: what if a new widget with same id was created in meantime? */
    477 			if (!cmd_caller) {
    478 				ltk_warn("Widget '%s' disappeared while text was being edited in external program\n", window->cmd_caller);
    479 			} else if (cmd_caller->vtable->cmd_return) {
    480 				size_t file_len = 0;
    481 				char *errstr = NULL;
    482 				char *contents = ltk_read_file(window->cmd_tmpfile, &file_len, &errstr);
    483 				if (!contents) {
    484 					ltk_warn("Unable to read file '%s' written by external command: %s\n", window->cmd_tmpfile, errstr);
    485 				} else {
    486 					cmd_caller->vtable->cmd_return(cmd_caller, contents, file_len);
    487 					ltk_free(contents);
    488 				}
    489 			}
    490 			ltk_free(window->cmd_caller);
    491 			window->cmd_caller = NULL;
    492 			window->cmd_pid = -1;
    493 			unlink(window->cmd_tmpfile);
    494 			ltk_free(window->cmd_tmpfile);
    495 			window->cmd_tmpfile = NULL;
    496 		}
    497 		rfds = sock_state.rallfds;
    498 		wfds = sock_state.wallfds;
    499 		retval = select(sock_state.maxfd + 1, &rfds, &wfds, NULL, &tv);
    500 		while (!ltk_next_event(window->renderdata, window->clipboard, window->cur_kbd, &event))
    501 			ltk_handle_event(window, &event);
    502 
    503 		if (retval > 0) {
    504 			if (FD_ISSET(sock_state.listenfd, &rfds)) {
    505 				if ((clifd = accept_sock(sock_state.listenfd)) < 0) {
    506 					/* FIXME: Just log this! */
    507 					ltk_fatal_errno("Error accepting socket connection.\n");
    508 				}
    509 				int i = add_client(clifd);
    510 				FD_SET(clifd, &sock_state.rallfds);
    511 				FD_SET(clifd, &sock_state.wallfds);
    512 				if (clifd > sock_state.maxfd)
    513 					sock_state.maxfd = clifd;
    514 				if (i > maxsocket)
    515 					maxsocket = i;
    516 				continue;
    517 			}
    518 			for (int i = 0; i <= maxsocket; i++) {
    519 				if ((clifd = sockets[i].fd) < 0)
    520 					continue;
    521 				if (FD_ISSET(clifd, &rfds)) {
    522 					/* FIXME: better error handling - this assumes error
    523 					   is always because read would block */
    524 					/* FIXME: maybe maximum number of iterations here to
    525 					   avoid choking on a lot of data? although such a
    526 					   large amount of data would probably cause other
    527 					   problems anyways */
    528 					/* or maybe measure time and break after max time? */
    529 					int ret;
    530 					while ((ret = read_sock(&sockets[i])) == 1) {
    531 						process_commands(window, i);
    532 					}
    533 					if (ret == 0) {
    534 						ltk_widget_remove_client(i);
    535 						FD_CLR(clifd, &sock_state.rallfds);
    536 						FD_CLR(clifd, &sock_state.wallfds);
    537 						sockets[i].fd = -1;
    538 						/* FIXME: what to do on error? */
    539 						close(clifd);
    540 						int newmaxsocket = -1;
    541 						for (int j = 0; j <= maxsocket; j++) {
    542 							if (sockets[j].fd >= 0)
    543 								newmaxsocket = j;
    544 						}
    545 						maxsocket = newmaxsocket;
    546 						if (maxsocket == -1) {
    547 							ltk_quit(window);
    548 							break;
    549 						}
    550 					}
    551 				}
    552 				/* FIXME: maybe ignore SIGPIPE signal - then don't call FD_CLR
    553 				   for wallfds above but rather when write fails with EPIPE */
    554 				/* -> this would possibly allow data to be written still in the
    555 				   hypothetical scenario that only the writing end of the socket
    556 				   is closed (and ltkd wouldn't crash if only the reading end is
    557 				   closed) */
    558 				if (FD_ISSET(clifd, &wfds)) {
    559 					/* FIXME: also call in loop like reading above */
    560 					write_sock(&sockets[i]);
    561 				}
    562 			}
    563 		}
    564 
    565 		clock_gettime(CLOCK_MONOTONIC, &now);
    566 		ltk_timespecsub(&now, &lasttimer, &elapsed);
    567 		/* Note: it should be safe to give the same pointer as the first and
    568 		   last argument, as long as ltk_timespecsub/add isn't changed incompatibly */
    569 		size_t i = 0;
    570 		while (i < timers_num) {
    571 			ltk_timespecsub(&timers[i].remaining, &elapsed, &timers[i].remaining);
    572 			if (timers[i].remaining.tv_sec < 0 ||
    573 			    (timers[i].remaining.tv_sec == 0 && timers[i].remaining.tv_nsec == 0)) {
    574 				timers[i].callback(timers[i].data);
    575 				if (timers[i].repeat.tv_sec == 0 && timers[i].repeat.tv_nsec == 0) {
    576 					/* remove timer because it has no repeat */
    577 					memmove(timers + i, timers + i + 1, sizeof(ltk_timer) * (timers_num - i - 1));
    578 				} else {
    579 					ltk_timespecadd(&timers[i].remaining, &timers[i].repeat, &timers[i].remaining);
    580 					i++;
    581 				}
    582 			} else {
    583 				i++;
    584 			}
    585 		}
    586 		lasttimer = now;
    587 
    588 		if (window->dirty_rect.w != 0 && window->dirty_rect.h != 0) {
    589 			ltk_redraw_window(window);
    590 			window->dirty_rect.w = 0;
    591 			window->dirty_rect.h = 0;
    592 		}
    593 
    594 		clock_gettime(CLOCK_MONOTONIC, &now);
    595 		ltk_timespecsub(&now, &last, &elapsed);
    596 		/* FIXME: configure framerate */
    597 		if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) {
    598 			sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec;
    599 			nanosleep(&sleep_time, NULL);
    600 		}
    601 		last = now;
    602 	}
    603 
    604 	ltk_cleanup();
    605 
    606 	return 0;
    607 }
    608 
    609 /* largely copied from APUE... */
    610 /* am I breaking copyright here? */
    611 static void
    612 daemonize(void) {
    613 	pid_t pid;
    614 	struct sigaction sa;
    615 
    616 	fflush(stdout);
    617 	fflush(stderr);
    618 	fflush(ltk_logfile);
    619 
    620 	if ((pid = fork()) < 0)
    621 		ltk_fatal_errno("Can't fork.\n");
    622 	else if (pid != 0)
    623 		exit(0);
    624 	setsid();
    625 
    626 	sa.sa_handler = SIG_IGN;
    627 	sigemptyset(&sa.sa_mask);
    628 	sa.sa_flags = 0;
    629 	if (sigaction(SIGHUP, &sa, NULL) < 0)
    630 		ltk_fatal_errno("Unable to ignore SIGHUP.\n");
    631 	if ((pid  = fork()) < 0)
    632 		ltk_fatal_errno("Can't fork.\n");
    633 	else if (pid != 0)
    634 		exit(0);
    635 
    636 	if (chdir("/") < 0)
    637 		ltk_fatal_errno("Can't change directory to root.\n");
    638 
    639 	/* FIXME: why didn't I just use fclose() here? */
    640 	/* FIXME: just print log to stdout and let this take care of redirection */
    641 	close(fileno(stdin));
    642 	/*close(fileno(stdout));
    643 	close(fileno(stderr));*/
    644 	open("/dev/null", O_RDWR);
    645 	/*dup(0);
    646 	dup(0);*/
    647 	dup2(fileno(ltk_logfile), fileno(stdout));
    648 	dup2(fileno(ltk_logfile), fileno(stderr));
    649 
    650 	/* FIXME: Is it guaranteed that this will work? Will these fds
    651 	   always be opened on the lowest numbers? */
    652 }
    653 
    654 static char *
    655 get_sock_path(char *basedir, Window id) {
    656 	int len;
    657 	char *path;
    658 
    659 	len = strlen(basedir);
    660 	/* FIXME: MAKE SURE THIS IS ACTUALLY BIG ENOUGH! */
    661 	path = ltk_malloc(len + 20);
    662 	/* FIXME: also check for less than 0 */
    663 	if (snprintf(path, len + 20, "%s/%lu.sock", basedir, id) >= len + 20)
    664 		ltk_fatal("Tell lumidify to fix his code.\n");
    665 
    666 	return path;
    667 }
    668 
    669 static FILE *
    670 open_log(char *dir) {
    671 	FILE *f;
    672 	char *path;
    673 
    674 	path = ltk_strcat_useful(dir, "/ltkd.log");
    675 	if (!path)
    676 		return NULL;
    677 	f = fopen(path, "a");
    678 	if (!f) {
    679 		ltk_free(path);
    680 		return NULL;
    681 	}
    682 	ltk_free(path);
    683 
    684 	return f;
    685 }
    686 
    687 void
    688 ltk_cleanup(void) {
    689 	if (sock_state.listenfd >= 0)
    690 		close(sock_state.listenfd);
    691 	if (ltk_dir)
    692 		ltk_free(ltk_dir);
    693 	if (ltk_logfile)
    694 		fclose(ltk_logfile);
    695 	if (sock_path) {
    696 		unlink(sock_path);
    697 		ltk_free(sock_path);
    698 	}
    699 
    700 	for (int i = 0; i < MAX_SOCK_CONNS; i++) {
    701 		if (sockets[i].fd >= 0)
    702 			close(sockets[i].fd);
    703 		if (sockets[i].read)
    704 			ltk_free(sockets[i].read);
    705 		if (sockets[i].to_write)
    706 			ltk_free(sockets[i].to_write);
    707 		if (sockets[i].tokens.tokens)
    708 			ltk_free(sockets[i].tokens.tokens);
    709 	}
    710 
    711 	ltk_config_cleanup();
    712 	for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
    713 		if (widget_funcs[i].cleanup)
    714 			widget_funcs[i].cleanup();
    715 	}
    716 	ltk_events_cleanup();
    717 	if (main_window) {
    718 		ltk_uninitialize_theme(main_window);
    719 		ltk_destroy_window(main_window);
    720 	}
    721 	main_window = NULL;
    722 }
    723 
    724 void
    725 ltk_quit(ltk_window *window) {
    726 	(void)window;
    727 	running = 0;
    728 }
    729 
    730 void
    731 ltk_log_msg(const char *mode, const char *format, va_list args) {
    732 	char logtime[25]; /* FIXME: This should always be big enough, right? */
    733 	time_t clock;
    734 	struct tm *timeptr;
    735 
    736 	time(&clock);
    737 	timeptr = localtime(&clock);
    738 	strftime(logtime, 25, "%Y-%m-%d %H:%M:%S", timeptr);
    739 
    740 	if (main_window)
    741 		fprintf(stderr, "%s ltkd(%lu) %s: ", logtime, renderer_get_window_id(main_window->renderdata), mode);
    742 	else
    743 		fprintf(stderr, "%s ltkd(?) %s: ", logtime, mode);
    744 	vfprintf(stderr, format, args);
    745 }
    746 
    747 static int
    748 ltk_set_root_widget_cmd(
    749     ltk_window *window,
    750     ltk_cmd_token *tokens,
    751     int num_tokens,
    752     ltk_error *err) {
    753 	ltk_widget *widget;
    754 	if (num_tokens != 2) {
    755 		err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
    756 		err->arg = -1;
    757 		return 1;
    758 	} else if (tokens[1].contains_nul) {
    759 		err->type = ERR_INVALID_ARGUMENT;
    760 		err->arg = 1;
    761 		return 1;
    762 	}
    763 	widget = ltk_get_widget(tokens[1].text, LTK_WIDGET_ANY, err);
    764 	if (!widget) {
    765 		err->arg = 1;
    766 		return 1;
    767 	}
    768 	window->root_widget = widget;
    769 	widget->lrect.x = 0;
    770 	widget->lrect.y = 0;
    771 	widget->lrect.w = window->rect.w;
    772 	widget->lrect.h = window->rect.h;
    773 	widget->crect = widget->lrect;
    774 	ltk_window_invalidate_rect(window, widget->lrect);
    775 	ltk_widget_resize(widget);
    776 
    777 	return 0;
    778 }
    779 
    780 void
    781 ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) {
    782 	if (window->dirty_rect.w == 0 && window->dirty_rect.h == 0)
    783 		window->dirty_rect = rect;
    784 	else
    785 		window->dirty_rect = ltk_rect_union(rect, window->dirty_rect);
    786 }
    787 
    788 ltk_point
    789 ltk_widget_pos_to_global(ltk_widget *widget, int x, int y) {
    790 	ltk_widget *cur = widget;
    791 	while (cur) {
    792 		x += cur->lrect.x;
    793 		y += cur->lrect.y;
    794 		if (cur->popup)
    795 			break;
    796 		cur = cur->parent;
    797 	}
    798 	return (ltk_point){x, y};
    799 }
    800 
    801 ltk_point
    802 ltk_global_to_widget_pos(ltk_widget *widget, int x, int y) {
    803 	ltk_widget *cur = widget;
    804 	while (cur) {
    805 		x -= cur->lrect.x;
    806 		y -= cur->lrect.y;
    807 		if (cur->popup)
    808 			break;
    809 		cur = cur->parent;
    810 	}
    811 	return (ltk_point){x, y};
    812 }
    813 
    814 void
    815 ltk_window_invalidate_widget_rect(ltk_window *window, ltk_widget *widget) {
    816 	ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0);
    817 	ltk_window_invalidate_rect(window, (ltk_rect){glob.x, glob.y, widget->lrect.w, widget->lrect.h});
    818 }
    819 
    820 /* FIXME: generic event handling functions that take the actual uint32_t event type, etc. */
    821 int
    822 ltk_queue_specific_event(ltk_widget *widget, const char *type, uint32_t mask, const char *data) {
    823 	int lock_client = -1;
    824 	for (size_t i = 0; i < widget->masks_num; i++) {
    825 		if (widget->event_masks[i].lwmask & mask) {
    826 			ltk_queue_sock_write_fmt(
    827 			    widget->event_masks[i].client,
    828 			    "eventl %s %s %s\n", widget->id, type, data
    829 			);
    830 			lock_client = widget->event_masks[i].client;
    831 		} else if (widget->event_masks[i].wmask & mask) {
    832 			ltk_queue_sock_write_fmt(
    833 			    widget->event_masks[i].client,
    834 			    "event %s %s %s\n", widget->id, type, data
    835 			);
    836 		}
    837 	}
    838 	if (lock_client >= 0) {
    839 		if (ltk_handle_lock_client(widget->window, lock_client))
    840 			return 1;
    841 	}
    842 	return 0;
    843 }
    844 
    845 static void
    846 ltk_redraw_window(ltk_window *window) {
    847 	ltk_widget *ptr;
    848 	if (!window) return;
    849 	if (window->dirty_rect.x >= window->rect.w) return;
    850 	if (window->dirty_rect.y >= window->rect.h) return;
    851 	if (window->dirty_rect.x + window->dirty_rect.w > window->rect.w)
    852 		window->dirty_rect.w -= window->dirty_rect.x + window->dirty_rect.w - window->rect.w;
    853 	if (window->dirty_rect.y + window->dirty_rect.h > window->rect.h)
    854 		window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
    855 	/* FIXME: this should use window->dirty_rect, but that doesn't work
    856 	   properly with double buffering */
    857 	ltk_surface_fill_rect(window->surface, &window->theme->bg, (ltk_rect){0, 0, window->rect.w, window->rect.h});
    858 	if (window->root_widget) {
    859 		ptr = window->root_widget;
    860 		ptr->vtable->draw(ptr, window->surface, 0, 0, window->rect);
    861 	}
    862 	/* last popup is the newest one, so draw that last */
    863 	for (size_t i = 0; i < window->popups_num; i++) {
    864 		ptr = window->popups[i];
    865 		ptr->vtable->draw(ptr, window->surface, ptr->lrect.x, ptr->lrect.y, ltk_rect_relative(ptr->lrect, window->rect));
    866 	}
    867 	renderer_swap_buffers(window->renderdata);
    868 }
    869 
    870 static void
    871 ltk_window_other_event(ltk_window *window, ltk_event *event) {
    872 	ltk_widget *ptr = window->root_widget;
    873 	if (event->type == LTK_CONFIGURE_EVENT) {
    874 		ltk_window_unregister_all_popups(window);
    875 		int w, h;
    876 		w = event->configure.w;
    877 		h = event->configure.h;
    878 		int orig_w = window->rect.w;
    879 		int orig_h = window->rect.h;
    880 		if (orig_w != w || orig_h != h) {
    881 			window->rect.w = w;
    882 			window->rect.h = h;
    883 			ltk_window_invalidate_rect(window, window->rect);
    884 			ltk_surface_update_size(window->surface, w, h);
    885 			if (ptr) {
    886 				ptr->lrect.w = w;
    887 				ptr->lrect.h = h;
    888 				ptr->crect = ptr->lrect;
    889 				ltk_widget_resize(ptr);
    890 			}
    891 		}
    892 	} else if (event->type == LTK_EXPOSE_EVENT) {
    893 		ltk_rect r;
    894 		r.x = event->expose.x;
    895 		r.y = event->expose.y;
    896 		r.w = event->expose.w;
    897 		r.h = event->expose.h;
    898 		ltk_window_invalidate_rect(window, r);
    899 	} else if (event->type == LTK_WINDOWCLOSE_EVENT) {
    900 		ltk_quit(window);
    901 	}
    902 }
    903 
    904 /* FIXME: optimize timer handling - maybe also a sort of priority queue */
    905 /* FIXME: JUST USE A GENERIC DYNAMIC ARRAY ALREADY!!!!! */
    906 void
    907 ltk_unregister_timer(int timer_id) {
    908 	for (size_t i = 0; i < timers_num; i++) {
    909 		if (timers[i].id == timer_id) {
    910 			memmove(
    911 			    timers + i,
    912 			    timers + i + 1,
    913 			    sizeof(ltk_timer) * (timers_num - i - 1)
    914 			);
    915 			timers_num--;
    916 			size_t sz = ideal_array_size(timers_alloc, timers_num);
    917 			if (sz != timers_alloc) {
    918 				timers_alloc = sz;
    919 				timers = ltk_reallocarray(
    920 				    timers, sz, sizeof(ltk_timer)
    921 				);
    922 			}
    923 			return;
    924 		}
    925 	}
    926 }
    927 
    928 /* repeat <= 0 means no repeat, first <= 0 means run as soon as possible */
    929 int
    930 ltk_register_timer(long first, long repeat, void (*callback)(void *), void *data) {
    931 	if (first < 0)
    932 		first = 0;
    933 	if (repeat < 0)
    934 		repeat = 0;
    935 	if (timers_num == timers_alloc) {
    936 		timers_alloc = ideal_array_size(timers_alloc, timers_num + 1);
    937 		timers = ltk_reallocarray(
    938 		    timers, timers_alloc, sizeof(ltk_timer)
    939 		);
    940 	}
    941 	/* FIXME: better finding of id */
    942 	/* FIXME: maybe store sorted by id */
    943 	int id = 0;
    944 	for (size_t i = 0; i < timers_num; i++) {
    945 		if (timers[i].id >= id)
    946 			id = timers[i].id + 1;
    947 	}
    948 	ltk_timer *t = &timers[timers_num++];
    949 	t->callback = callback;
    950 	t->data = data;
    951 	t->repeat.tv_sec = repeat / 1000;
    952 	t->repeat.tv_nsec = (repeat % 1000) * 1000;
    953 	t->remaining.tv_sec = first / 1000;
    954 	t->remaining.tv_nsec = (first % 1000) * 1000;
    955 	t->id = id;
    956 	return id;
    957 }
    958 
    959 /* FIXME: check for duplicates? */
    960 void
    961 ltk_window_register_popup(ltk_window *window, ltk_widget *popup) {
    962 	if (window->popups_num == window->popups_alloc) {
    963 		window->popups_alloc = ideal_array_size(
    964 		    window->popups_alloc, window->popups_num + 1
    965 		);
    966 		window->popups = ltk_reallocarray(
    967 		    window->popups, window->popups_alloc, sizeof(ltk_widget *)
    968 		);
    969 	}
    970 	window->popups[window->popups_num++] = popup;
    971 	popup->popup = 1;
    972 }
    973 
    974 void
    975 ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup) {
    976 	if (window->popups_locked)
    977 		return;
    978 	for (size_t i = 0; i < window->popups_num; i++) {
    979 		if (window->popups[i] == popup) {
    980 			popup->popup = 0;
    981 			memmove(
    982 			    window->popups + i,
    983 			    window->popups + i + 1,
    984 			    sizeof(ltk_widget *) * (window->popups_num - i - 1)
    985 			);
    986 			window->popups_num--;
    987 			size_t sz = ideal_array_size(
    988 			    window->popups_alloc, window->popups_num
    989 			);
    990 			if (sz != window->popups_alloc) {
    991 				window->popups_alloc = sz;
    992 				window->popups = ltk_reallocarray(
    993 				    window->popups, sz, sizeof(ltk_widget *)
    994 				);
    995 			}
    996 			return;
    997 		}
    998 	}
    999 }
   1000 
   1001 /* FIXME: where should actual hiding happen? */
   1002 void
   1003 ltk_window_unregister_all_popups(ltk_window *window) {
   1004 	window->popups_locked = 1;
   1005 	for (size_t i = 0; i < window->popups_num; i++) {
   1006 		window->popups[i]->hidden = 1;
   1007 		window->popups[i]->popup = 0;
   1008 		ltk_widget_hide(window->popups[i]);
   1009 	}
   1010 	window->popups_num = 0;
   1011 	/* somewhat arbitrary, but should be enough for most cases */
   1012 	if (window->popups_num > 4) {
   1013 		window->popups = ltk_reallocarray(
   1014 		    window->popups, 4, sizeof(ltk_widget *)
   1015 		);
   1016 		window->popups_alloc = 4;
   1017 	}
   1018 	window->popups_locked = 0;
   1019 	/* I guess just invalidate everything instead of being smart */
   1020 	ltk_window_invalidate_rect(window, window->rect);
   1021 }
   1022 
   1023 ltk_window_theme window_theme;
   1024 static ltk_theme_parseinfo theme_parseinfo[] = {
   1025 	{"font-size", THEME_INT, {.i = &window_theme.font_size}, {.i = 15}, 0, MAX_WINDOW_FONT_SIZE, 0},
   1026 	{"font", THEME_STRING, {.str = &window_theme.font}, {.str = "Liberation Mono"}, 0, 0, 0},
   1027 	{"bg", THEME_COLOR, {.color = &window_theme.bg}, {.color = "#000000"}, 0, 0, 0},
   1028 	{"fg", THEME_COLOR, {.color = &window_theme.fg}, {.color = "#FFFFFF"}, 0, 0, 0},
   1029 };
   1030 static int theme_parseinfo_sorted = 0;
   1031 
   1032 static int
   1033 ltk_window_fill_theme_defaults(ltk_window *window) {
   1034 	return ltk_theme_fill_defaults(window, "window", theme_parseinfo, LENGTH(theme_parseinfo));
   1035 }
   1036 
   1037 static int
   1038 ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value) {
   1039 	return ltk_theme_handle_value(window, "window", prop, value, theme_parseinfo, LENGTH(theme_parseinfo), &theme_parseinfo_sorted);
   1040 }
   1041 
   1042 static void
   1043 ltk_window_uninitialize_theme(ltk_window *window) {
   1044 	ltk_theme_uninitialize(window, theme_parseinfo, LENGTH(theme_parseinfo));
   1045 }
   1046 
   1047 /* FIXME: standardize return codes - usually, 0 is returned on success, but ini.h
   1048    uses 1 on success, so this is all a bit confusing */
   1049 /* FIXME: switch away from ini.h */
   1050 static int
   1051 ltk_ini_handler(void *window, const char *widget, const char *prop, const char *value) {
   1052 	for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
   1053 		if (widget_funcs[i].ini_handler && !strcmp(widget, widget_funcs[i].name)) {
   1054 			widget_funcs[i].ini_handler(window, prop, value);
   1055 			return 1;
   1056 		}
   1057 	}
   1058 	return 0;
   1059 }
   1060 
   1061 static void
   1062 ltk_load_theme(ltk_window *window, const char *path) {
   1063 	/* FIXME: give line number in error message */
   1064 	if (ini_parse(path, ltk_ini_handler, window) != 0) {
   1065 		ltk_warn("Unable to load theme.\n");
   1066 	}
   1067 	for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
   1068 		if (widget_funcs[i].fill_theme_defaults) {
   1069 			if (widget_funcs[i].fill_theme_defaults(window)) {
   1070 				ltk_uninitialize_theme(window);
   1071 				ltk_fatal("Unable to load theme defaults.\n");
   1072 			}
   1073 		}
   1074 	}
   1075 }
   1076 
   1077 static void
   1078 ltk_uninitialize_theme(ltk_window *window) {
   1079 	for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
   1080 		if (widget_funcs[i].uninitialize_theme)
   1081 			widget_funcs[i].uninitialize_theme(window);
   1082 	}
   1083 }
   1084 
   1085 static int
   1086 handle_keypress_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keypress_binding b) {
   1087 	for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
   1088 		if (str_array_equal(widget_funcs[i].name, widget_name, wlen)) {
   1089 			if (!widget_funcs[i].register_keypress)
   1090 				return 1;
   1091 			return widget_funcs[i].register_keypress(name, nlen, b);
   1092 		}
   1093 	}
   1094 	return 1;
   1095 }
   1096 
   1097 static int
   1098 handle_keyrelease_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keyrelease_binding b) {
   1099 	for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
   1100 		if (str_array_equal(widget_funcs[i].name, widget_name, wlen)) {
   1101 			if (!widget_funcs[i].register_keyrelease)
   1102 				return 1;
   1103 			return widget_funcs[i].register_keyrelease(name, nlen, b);
   1104 		}
   1105 	}
   1106 	return 1;
   1107 }
   1108 
   1109 int
   1110 ltk_window_call_cmd(ltk_window *window, ltk_widget *caller, const char *cmd, size_t cmdlen, const char *text, size_t textlen) {
   1111 	if (window->cmd_caller) {
   1112 		/* FIXME: allow multiple programs? */
   1113 		ltk_warn("External program to edit text is already being run\n");
   1114 		return 1;
   1115 	}
   1116 	/* FIXME: support environment variable $TMPDIR */
   1117 	ltk_free(window->cmd_tmpfile);
   1118 	window->cmd_tmpfile = ltk_strdup("/tmp/ltk.XXXXXX");
   1119 	int fd = mkstemp(window->cmd_tmpfile);
   1120 	if (fd == -1) {
   1121 		ltk_warn("Unable to create temporary file while trying to run command '%.*s'\n", (int)cmdlen, cmd);
   1122 		return 1;
   1123 	}
   1124 	close(fd);
   1125 	/* FIXME: give file descriptor directly to modified version of ltk_write_file */
   1126 	char *errstr = NULL;
   1127 	if (ltk_write_file(window->cmd_tmpfile, text, textlen, &errstr)) {
   1128 		ltk_warn("Unable to write to file '%s' while trying to run command '%.*s': %s\n", window->cmd_tmpfile, (int)cmdlen, cmd, errstr);
   1129 		unlink(window->cmd_tmpfile);
   1130 		return 1;
   1131 	}
   1132 	int pid = -1;
   1133 	if ((pid = ltk_parse_run_cmd(cmd, cmdlen, window->cmd_tmpfile)) <= 0) {
   1134 		ltk_warn("Unable to run command '%.*s'\n", (int)cmdlen, cmd);
   1135 		unlink(window->cmd_tmpfile);
   1136 		return 1;
   1137 	}
   1138 	window->cmd_pid = pid;
   1139 	window->cmd_caller = ltk_strdup(caller->id);
   1140 	return 0;
   1141 }
   1142 
   1143 static ltk_window *
   1144 ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int h) {
   1145 	char *theme_path;
   1146 
   1147 	ltk_window *window = ltk_malloc(sizeof(ltk_window));
   1148 
   1149 	window->popups = NULL;
   1150 	window->popups_num = window->popups_alloc = 0;
   1151 	window->popups_locked = 0;
   1152 	window->cur_kbd = 0;
   1153 
   1154 	window->renderdata = renderer_create_window(title, x, y, w, h);
   1155 	/* FIXME: search different directories for config */
   1156 	char *config_path = ltk_strcat_useful(ltk_dir, "/ltk.cfg");
   1157 	char *errstr = NULL;
   1158 	if (ltk_config_parsefile(config_path, &handle_keypress_binding, &handle_keyrelease_binding, &errstr)) {
   1159 		if (errstr) {
   1160 			ltk_warn("Unable to load config: %s\n", errstr);
   1161 			ltk_free(errstr);
   1162 		}
   1163 		if (ltk_config_load_default(&handle_keypress_binding, &handle_keyrelease_binding, &errstr)) {
   1164 			/* FIXME: I guess errstr isn't freed here, but whatever */
   1165 			ltk_fatal("Unable to load default config: %s\n", errstr);
   1166 		}
   1167 	}
   1168 	ltk_free(config_path);
   1169 	theme_path = ltk_strcat_useful(ltk_dir, "/theme.ini");
   1170 	window->theme = &window_theme;
   1171 	ltk_load_theme(window, theme_path);
   1172 	ltk_free(theme_path);
   1173 
   1174 	/* FIXME: fix theme memory leaks when exit happens between here and the end of this function */
   1175 
   1176 	renderer_set_window_properties(window->renderdata, &window->theme->bg);
   1177 
   1178 	window->root_widget = NULL;
   1179 	window->hover_widget = NULL;
   1180 	window->active_widget = NULL;
   1181 	window->pressed_widget = NULL;
   1182 
   1183 	window->cmd_pid = -1;
   1184 	window->cmd_tmpfile = NULL;
   1185 	window->cmd_caller = NULL;
   1186 
   1187 	window->surface_cache = ltk_surface_cache_create(window->renderdata);
   1188 
   1189 	window->other_event = &ltk_window_other_event;
   1190 
   1191 	window->rect.w = w;
   1192 	window->rect.h = h;
   1193 	window->rect.x = 0;
   1194 	window->rect.y = 0;
   1195 	window->dirty_rect.w = 0;
   1196 	window->dirty_rect.h = 0;
   1197 	window->dirty_rect.x = 0;
   1198 	window->dirty_rect.y = 0;
   1199 	window->surface = ltk_surface_from_window(window->renderdata, w, h);
   1200 
   1201 	window->text_context = ltk_text_context_create(window->renderdata, window->theme->font);
   1202 	window->clipboard = ltk_clipboard_create(window->renderdata);
   1203 
   1204 	/* This hack is necessary to make the daemonization work properly when using Pango.
   1205 	   This may not be entirely accurate, but from what I gather, newer versions of Pango
   1206 	   initialize Fontconfig in a separate thread to avoid startup overhead. This leads
   1207 	   to non-deterministic behavior because the Fontconfig initialization doesn't work
   1208 	   properly after daemonization. Creating a text line and getting the size waits until
   1209 	   Fontconfig is initialized. Getting the size is important because Pango doesn't
   1210 	   actually do much until you try to use the line for something. */
   1211 	/* FIXME: I guess just calling FcInit manually in the text backend could work as well. */
   1212 	/* FIXME: Maybe just call this when actually daemonizing. */
   1213 	ltk_text_line *tmp = ltk_text_line_create(window->text_context, 10, "hi", 0, -1);
   1214 	int tw, th;
   1215 	ltk_text_line_get_size(tmp, &tw, &th);
   1216 	ltk_text_line_destroy(tmp);
   1217 	/* FIXME: cache doesn't really make any sense right now anyways
   1218 	   since images are only loaded from memory */
   1219 	ltk_image_init(window->renderdata, 0);
   1220 
   1221 	return window;
   1222 }
   1223 
   1224 static void
   1225 ltk_destroy_window(ltk_window *window) {
   1226 	ltk_free(window->cmd_tmpfile);
   1227 	ltk_clipboard_destroy(window->clipboard);
   1228 	ltk_text_context_destroy(window->text_context);
   1229 	if (window->popups)
   1230 		ltk_free(window->popups);
   1231 	ltk_surface_cache_destroy(window->surface_cache);
   1232 	ltk_surface_destroy(window->surface);
   1233 	renderer_destroy_window(window->renderdata);
   1234 	ltk_free(window);
   1235 }
   1236 
   1237 /* event must have global coordinates! */
   1238 void
   1239 ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_event *event) {
   1240 	ltk_widget *old = window->hover_widget;
   1241 	if (old == widget)
   1242 		return;
   1243 	int orig_x = event->x, orig_y = event->y;
   1244 	if (old) {
   1245 		ltk_widget_state old_state = old->state;
   1246 		old->state &= ~LTK_HOVER;
   1247 		ltk_widget_change_state(old, old_state);
   1248 		if (old->vtable->mouse_leave) {
   1249 			ltk_point local = ltk_global_to_widget_pos(old, event->x, event->y);
   1250 			event->x = local.x;
   1251 			event->y = local.y;
   1252 			old->vtable->mouse_leave(old, event);
   1253 			event->x = orig_x;
   1254 			event->y = orig_y;
   1255 		}
   1256 	}
   1257 	window->hover_widget = widget;
   1258 	if (widget) {
   1259 		if (widget->vtable->mouse_enter) {
   1260 			ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
   1261 			event->x = local.x;
   1262 			event->y = local.y;
   1263 			widget->vtable->mouse_enter(widget, event);
   1264 		}
   1265 		ltk_widget_state old_state = widget->state;
   1266 		widget->state |= LTK_HOVER;
   1267 		ltk_widget_change_state(widget, old_state);
   1268 		if ((widget->vtable->flags & LTK_HOVER_IS_ACTIVE) && widget != window->active_widget)
   1269 			ltk_window_set_active_widget(window, widget);
   1270 	}
   1271 }
   1272 
   1273 void
   1274 ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
   1275 	if (window->active_widget == widget) {
   1276 		return;
   1277 	}
   1278 	ltk_widget *old = window->active_widget;
   1279 	/* Note: this has to be set at the beginning to
   1280 	   avoid infinite recursion in some cases */
   1281 	window->active_widget = widget;
   1282 	ltk_widget *common_parent = NULL;
   1283 	if (widget) {
   1284 		ltk_widget *cur = widget;
   1285 		while (cur) {
   1286 			if (cur->state & LTK_ACTIVE) {
   1287 				common_parent = cur;
   1288 				break;
   1289 			}
   1290 			ltk_widget_state old_state = cur->state;
   1291 			cur->state |= LTK_ACTIVE;
   1292 			/* FIXME: should all be set focused? */
   1293 			if (cur == widget && !(cur->vtable->flags & LTK_NEEDS_KEYBOARD))
   1294 				widget->state |= LTK_FOCUSED;
   1295 			ltk_widget_change_state(cur, old_state);
   1296 			cur = cur->parent;
   1297 		}
   1298 	}
   1299 	/* FIXME: better variable names; generally make this nicer */
   1300 	/* special case if old is parent of new active widget */
   1301 	ltk_widget *tmp = common_parent;
   1302 	while (tmp) {
   1303 		if (tmp == old)
   1304 			return;
   1305 		tmp = tmp->parent;
   1306 	}
   1307 	if (old) {
   1308 		old->state &= ~LTK_FOCUSED;
   1309 		ltk_widget *cur = old;
   1310 		while (cur) {
   1311 			if (cur == common_parent)
   1312 				break;
   1313 			ltk_widget_state old_state = cur->state;
   1314 			cur->state &= ~LTK_ACTIVE;
   1315 			ltk_widget_change_state(cur, old_state);
   1316 			cur = cur->parent;
   1317 		}
   1318 	}
   1319 }
   1320 
   1321 void
   1322 ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget, int release) {
   1323 	if (window->pressed_widget == widget)
   1324 		return;
   1325 	if (window->pressed_widget) {
   1326 		ltk_widget_state old_state = window->pressed_widget->state;
   1327 		window->pressed_widget->state &= ~LTK_PRESSED;
   1328 		ltk_widget_change_state(window->pressed_widget, old_state);
   1329 		ltk_window_set_active_widget(window, window->pressed_widget);
   1330 		/* FIXME: this is a bit weird because the release handler for menuentry
   1331 		   indirectly calls ltk_widget_hide, which messes with the pressed widget */
   1332 		/* FIXME: isn't it redundant to check that state is pressed? */
   1333 		if (release && window->pressed_widget->vtable->release && (old_state & LTK_PRESSED)) {
   1334 			window->pressed_widget->vtable->release(window->pressed_widget);
   1335 		}
   1336 	}
   1337 	window->pressed_widget = widget;
   1338 	if (widget) {
   1339 		if (widget->vtable->press)
   1340 			widget->vtable->press(widget);
   1341 		ltk_widget_state old_state = widget->state;
   1342 		widget->state |= LTK_PRESSED;
   1343 		ltk_widget_change_state(widget, old_state);
   1344 	}
   1345 }
   1346 
   1347 static void
   1348 ltk_handle_event(ltk_window *window, ltk_event *event) {
   1349 	size_t kbd_idx;
   1350 	switch (event->type) {
   1351 	case LTK_KEYPRESS_EVENT:
   1352 		ltk_window_key_press_event(window, &event->key);
   1353 		break;
   1354 	case LTK_KEYRELEASE_EVENT:
   1355 		ltk_window_key_release_event(window, &event->key);
   1356 		break;
   1357 	case LTK_BUTTONPRESS_EVENT:
   1358 	case LTK_2BUTTONPRESS_EVENT:
   1359 	case LTK_3BUTTONPRESS_EVENT:
   1360 		ltk_window_mouse_press_event(window, &event->button);
   1361 		break;
   1362 	case LTK_SCROLL_EVENT:
   1363 		ltk_window_mouse_scroll_event(window, &event->scroll);
   1364 		break;
   1365 	case LTK_BUTTONRELEASE_EVENT:
   1366 	case LTK_2BUTTONRELEASE_EVENT:
   1367 	case LTK_3BUTTONRELEASE_EVENT:
   1368 		ltk_window_mouse_release_event(window, &event->button);
   1369 		break;
   1370 	case LTK_MOTION_EVENT:
   1371 		ltk_window_motion_notify_event(window, &event->motion);
   1372 		break;
   1373 	case LTK_KEYBOARDCHANGE_EVENT:
   1374 		/* FIXME: emit event */
   1375 		if (ltk_config_get_language_index(event->keyboard.new_kbd, &kbd_idx))
   1376 			ltk_warn("No language mapping for language \"%s\".\n", event->keyboard.new_kbd);
   1377 		else
   1378 			window->cur_kbd = kbd_idx;
   1379 		break;
   1380 	default:
   1381 		if (window->other_event)
   1382 			window->other_event(window, event);
   1383 	}
   1384 }
   1385 
   1386 /* Push a token onto `token_list`, resizing the buffer if necessary.
   1387    Returns -1 on error, 0 otherwise.
   1388    Note: The token is not copied, it is only added directly. */
   1389 static int
   1390 push_token(struct token_list *tl, char *token) {
   1391 	int new_size;
   1392 	if (tl->num_tokens >= tl->num_alloc) {
   1393 		new_size = (tl->num_alloc * 2) > (tl->num_tokens + 1) ?
   1394 			   (tl->num_alloc * 2) : (tl->num_tokens + 1);
   1395 		ltk_cmd_token *new = ltk_reallocarray(tl->tokens, new_size, sizeof(ltk_cmd_token));
   1396 		if (!new) return -1;
   1397 		tl->tokens = new;
   1398 		tl->num_alloc = new_size;
   1399 	}
   1400 	tl->tokens[tl->num_tokens].text = token;
   1401 	tl->tokens[tl->num_tokens].len = 0;
   1402 	tl->tokens[tl->num_tokens++].contains_nul = 0;
   1403 
   1404 	return 0;
   1405 }
   1406 
   1407 /* Add a new client to the socket list and return the index in `sockets`.
   1408    Returns -1 if there is no space for a new client. */
   1409 static int
   1410 add_client(int fd) {
   1411 	for (int i = 0; i < MAX_SOCK_CONNS; i++) {
   1412 		if (sockets[i].fd == -1) {
   1413 			sockets[i].fd = fd;
   1414 			sockets[i].event_mask = ~0; /* FIXME */
   1415 			sockets[i].read_len = 0;
   1416 			sockets[i].write_len = 0;
   1417 			sockets[i].write_cur = 0;
   1418 			sockets[i].offset = 0;
   1419 			sockets[i].in_str = 0;
   1420 			sockets[i].read_cur = 0;
   1421 			sockets[i].bs = 0;
   1422 			sockets[i].tokens.num_tokens = 0;
   1423 			sockets[i].last_seq = 0;
   1424 			return i;
   1425 		}
   1426 	}
   1427 
   1428 	return -1;
   1429 }
   1430 
   1431 /* largely copied from APUE */
   1432 /* Listen on the socket at `sock_path`.
   1433    Returns the file descriptor of the opened socket on success.
   1434    Returns -1 if `sock_path` is too long
   1435            -2 if the socket could not be created
   1436            -3 if the socket could not be bound to the path
   1437            -4 if the socket could not be listened on */
   1438 static int
   1439 listen_sock(const char *sock_path) {
   1440 	int fd, len, err, rval;
   1441 	struct sockaddr_un un;
   1442 
   1443 	if (strlen(sock_path) >= sizeof(un.sun_path)) {
   1444 		errno = ENAMETOOLONG;
   1445 		return -1;
   1446 	}
   1447 
   1448 	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
   1449 		return -2;
   1450 
   1451 	unlink(sock_path);
   1452 
   1453 	memset(&un, 0, sizeof(un));
   1454 	un.sun_family = AF_UNIX;
   1455 	strcpy(un.sun_path, sock_path);
   1456 	len = offsetof(struct sockaddr_un, sun_path) + strlen(sock_path);
   1457 	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
   1458 		rval = -3;
   1459 		goto errout;
   1460 	}
   1461 
   1462 	if (listen(fd, 10) < 0) {
   1463 		rval = -4;
   1464 		goto errout;
   1465 	}
   1466 
   1467 	return fd;
   1468 
   1469 errout:
   1470 	err = errno;
   1471 	close(fd);
   1472 	errno = err;
   1473 	return rval;
   1474 }
   1475 
   1476 /* Accept a socket connection on the listening socket `listenfd`.
   1477    Returns the file descriptor of the accepted client on success.
   1478    Returns -1 if there was an error. */
   1479 static int
   1480 accept_sock(int listenfd) {
   1481 	int clifd;
   1482 	socklen_t len;
   1483 	struct sockaddr_un un;
   1484 
   1485 	len = sizeof(un);
   1486 	if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
   1487 		return -1;
   1488 	}
   1489 	if (set_nonblock(clifd)) {
   1490 		/* FIXME: what could even be done if close fails? */
   1491 		close(clifd);
   1492 		return -1;
   1493 	}
   1494 
   1495 	return clifd;
   1496 }
   1497 
   1498 /* Read up to READ_BLK_SIZE bytes from the socket `sock`.
   1499    Returns -1 if an error occurred, 0 if the connection was closed, 1 otherwise.
   1500    Note: Returning 1 on success is weird, but it could also be confusing to
   1501    return 0 on success when `read` returns that to mean that the connection
   1502    was closed. */
   1503 static int
   1504 read_sock(struct ltk_sock_info *sock) {
   1505 	int nread;
   1506 	char *old = sock->read;
   1507 	ltk_grow_string(&sock->read, &sock->read_alloc, sock->read_len + READ_BLK_SIZE);
   1508 	/* move tokens to new addresses - this was added as an
   1509 	   afterthought and really needs to be cleaned up */
   1510 	if (sock->read != old) {
   1511 		for (int i = 0; i < sock->tokens.num_tokens; i++) {
   1512 			sock->tokens.tokens[i].text = sock->read + (sock->tokens.tokens[i].text - old);
   1513 		}
   1514 	}
   1515 	nread = read(sock->fd, sock->read + sock->read_len, READ_BLK_SIZE);
   1516 	if (nread == -1 || nread == 0)
   1517 		return nread;
   1518 	sock->read_len += nread;
   1519 
   1520 	return 1;
   1521 }
   1522 
   1523 /* Write up to WRITE_BLK_SIZE bytes to the socket.
   1524    Returns -1 on error, 0 otherwise. */
   1525 static int
   1526 write_sock(struct ltk_sock_info *sock) {
   1527 	if (sock->write_len == sock->write_cur)
   1528 		return 0;
   1529 	int write_len = WRITE_BLK_SIZE > sock->write_len - sock->write_cur ?
   1530 			sock->write_len - sock->write_cur : WRITE_BLK_SIZE;
   1531 	int nwritten = write(sock->fd, sock->to_write + sock->write_cur, write_len);
   1532 	if (nwritten == -1)
   1533 		return nwritten;
   1534 	sock->write_cur += nwritten;
   1535 
   1536 	/* check if any sockets have text to write */
   1537 	if (sock->write_cur == sock->write_len) {
   1538 		int found = 0;
   1539 		for (int i = 0; i < maxsocket; i++) {
   1540 			if (sockets[i].fd != -1 &&
   1541 			    sockets[i].write_cur != sockets[i].write_len) {
   1542 				found = 1;
   1543 				break;
   1544 			}
   1545 		}
   1546 		if (!found)
   1547 			sock_write_available = 0;
   1548 	}
   1549 
   1550 	return 0;
   1551 }
   1552 
   1553 static void
   1554 move_write_pos(struct ltk_sock_info *sock) {
   1555 	/* FIXME: also resize if too large */
   1556 	if (sock->write_cur > 0) {
   1557 		memmove(sock->to_write, sock->to_write + sock->write_cur,
   1558 		        sock->write_len - sock->write_cur);
   1559 		sock->write_len -= sock->write_cur;
   1560 		sock->write_cur = 0;
   1561 	}
   1562 }
   1563 
   1564 /* Queue `str` to be written to the socket. If len is < 0, it is set to `strlen(str)`.
   1565    Returns -1 on error, 0 otherwise.
   1566    Note: The string must include all '\n', etc. as defined in the protocol. This
   1567    function just adds the given string verbatim. */
   1568 int
   1569 ltk_queue_sock_write(int client, const char *str, int len) {
   1570 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
   1571 		return 1;
   1572 	/* this is always large enough to hold a uint32_t and " \0" */
   1573 	char num[12];
   1574 	struct ltk_sock_info *sock = &sockets[client];
   1575 	move_write_pos(sock);
   1576 	if (len < 0)
   1577 		len = strlen(str);
   1578 
   1579 	int numlen = snprintf(num, sizeof(num), "%"PRIu32" ", sock->last_seq);
   1580 	if (numlen < 0 || (unsigned)numlen >= sizeof(num))
   1581 		ltk_fatal("There's a bug in the universe.\n");
   1582 	if (sock->write_alloc - sock->write_len < len + numlen)
   1583 		ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len + numlen);
   1584 
   1585 	(void)strncpy(sock->to_write + sock->write_len, num, numlen);
   1586 	(void)strncpy(sock->to_write + sock->write_len + numlen, str, len);
   1587 	sock->write_len += len + numlen;
   1588 
   1589 	sock_write_available = 1;
   1590 
   1591 	return 0;
   1592 }
   1593 
   1594 int
   1595 ltk_queue_sock_write_fmt(int client, const char *fmt, ...) {
   1596 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
   1597 		return 1;
   1598 	struct ltk_sock_info *sock = &sockets[client];
   1599 	/* just to print the sequence number */
   1600 	ltk_queue_sock_write(client, "", 0);
   1601 	va_list args;
   1602 	va_start(args, fmt);
   1603 	int len = vsnprintf(sock->to_write + sock->write_len, sock->write_alloc - sock->write_len, fmt, args);
   1604 	if (len < 0) {
   1605 		ltk_fatal("Unable to print formatted text to socket.\n");
   1606 	} else if (len >= sock->write_alloc - sock->write_len) {
   1607 		va_end(args);
   1608 		va_start(args, fmt);
   1609 		/* snprintf always writes '\0', even though we don't actually need it here */
   1610 		ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len + 1);
   1611 		vsnprintf(sock->to_write + sock->write_len, sock->write_alloc - sock->write_len, fmt, args);
   1612 	}
   1613 	va_end(args);
   1614 	sock->write_len += len;
   1615 	sock_write_available = 1;
   1616 
   1617 	return 0;
   1618 }
   1619 
   1620 /* Tokenize the current read buffer in `sock`.
   1621    Returns 0 immediately if the end of a command was encountered, 1 otherwise. */
   1622 static int
   1623 tokenize_command(struct ltk_sock_info *sock) {
   1624 	for (; sock->read_cur < sock->read_len; sock->read_cur++) {
   1625 		/* FIXME: strip extra whitespace? Or maybe just have a "hard" protocol where it always has to be one space. */
   1626 		if (!sock->in_token) {
   1627 			push_token(&sock->tokens, sock->read + sock->read_cur - sock->offset);
   1628 			sock->in_token = 1;
   1629 		}
   1630 		if (sock->read[sock->read_cur] == '\\') {
   1631 			sock->bs++;
   1632 			if (sock->bs / 2)
   1633 				sock->offset++;
   1634 			else
   1635 				sock->tokens.tokens[sock->tokens.num_tokens - 1].len++;
   1636 			sock->bs %= 2;
   1637 			sock->read[sock->read_cur-sock->offset] = '\\';
   1638 		} else if (sock->read[sock->read_cur] == '\n' && !sock->in_str) {
   1639 			sock->read[sock->read_cur-sock->offset] = '\0';
   1640 			sock->read_cur++;
   1641 			sock->offset = 0;
   1642 			sock->in_token = 0;
   1643 			sock->bs = 0;
   1644 			return 0;
   1645 		} else if (sock->read[sock->read_cur] == '"') {
   1646 			sock->offset++;
   1647 			if (sock->bs) {
   1648 				sock->read[sock->read_cur-sock->offset] = '"';
   1649 				sock->bs = 0;
   1650 			} else {
   1651 				sock->in_str = !sock->in_str;
   1652 			}
   1653 		} else if (sock->read[sock->read_cur] == ' ' && !sock->in_str) {
   1654 			sock->read[sock->read_cur-sock->offset] = '\0';
   1655 			sock->in_token = !sock->in_token;
   1656 			sock->bs = 0;
   1657 		} else {
   1658 			sock->read[sock->read_cur-sock->offset] = sock->read[sock->read_cur];
   1659 			/* FIXME: assert that num_tokens > 0 */
   1660 			sock->tokens.tokens[sock->tokens.num_tokens - 1].len++;
   1661 			if (sock->read[sock->read_cur] == '\0')
   1662 				sock->tokens.tokens[sock->tokens.num_tokens - 1].contains_nul = 1;
   1663 			sock->bs = 0;
   1664 		}
   1665 	}
   1666 
   1667 	return 1;
   1668 }
   1669 
   1670 /* FIXME: currently no type-checking when setting specific widget mask */
   1671 /* FIXME: this is really ugly and inefficient right now - it will be replaced with something
   1672    more generic at some point (or maybe just with a binary protocol?) */
   1673 static int
   1674 handle_mask_command(int client, ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) {
   1675 	if (num_tokens != 4 && num_tokens != 5) {
   1676 		err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
   1677 		err->arg = -1;
   1678 		return 1;
   1679 	}
   1680 	/* FIXME: make this nicer */
   1681 	/* -> use generic cmd handling like the widgets */
   1682 	if (tokens[1].contains_nul) {
   1683 		err->type = ERR_INVALID_ARGUMENT;
   1684 		err->arg = 1;
   1685 		return 1;
   1686 	} else if (tokens[2].contains_nul) {
   1687 		err->type = ERR_INVALID_ARGUMENT;
   1688 		err->arg = 2;
   1689 		return 1;
   1690 	} else if (tokens[3].contains_nul) {
   1691 		err->type = ERR_INVALID_ARGUMENT;
   1692 		err->arg = 3;
   1693 		return 1;
   1694 	} else if (num_tokens == 5 && tokens[4].contains_nul) {
   1695 		err->type = ERR_INVALID_ARGUMENT;
   1696 		err->arg = 4;
   1697 		return 1;
   1698 	}
   1699 	uint32_t mask = 0;
   1700 	int lock = 0;
   1701 	int special = 0;
   1702 	ltk_widget *widget = ltk_get_widget(tokens[1].text, LTK_WIDGET_ANY, err);
   1703 	if (!widget) {
   1704 		err->arg = 1;
   1705 		return 1;
   1706 	}
   1707 	if (!strcmp(tokens[2].text, "widget")) {
   1708 		if (!strcmp(tokens[3].text, "mousepress")) {
   1709 			mask = LTK_PEVENTMASK_MOUSEPRESS;
   1710 		} else if (!strcmp(tokens[3].text, "mouserelease")) {
   1711 			mask = LTK_PEVENTMASK_MOUSERELEASE;
   1712 		} else if (!strcmp(tokens[3].text, "mousemotion")) {
   1713 			mask = LTK_PEVENTMASK_MOUSEMOTION;
   1714 		} else if (!strcmp(tokens[3].text, "configure")) {
   1715 			mask = LTK_PEVENTMASK_CONFIGURE;
   1716 		} else if (!strcmp(tokens[3].text, "statechange")) {
   1717 			mask = LTK_PEVENTMASK_STATECHANGE;
   1718 		} else if (!strcmp(tokens[3].text, "none")) {
   1719 			mask = LTK_PEVENTMASK_NONE;
   1720 		} else {
   1721 			err->type = ERR_INVALID_ARGUMENT;
   1722 			err->arg = 3;
   1723 			return 1;
   1724 		}
   1725 	} else if (!strcmp(tokens[2].text, "menuentry")) {
   1726 		if (!strcmp(tokens[3].text, "press")) {
   1727 			mask = LTK_PWEVENTMASK_MENUENTRY_PRESS;
   1728 		} else if (!strcmp(tokens[3].text, "none")) {
   1729 			mask = LTK_PWEVENTMASK_MENUENTRY_NONE;
   1730 		} else {
   1731 			err->type = ERR_INVALID_ARGUMENT;
   1732 			err->arg = 3;
   1733 			return 1;
   1734 		}
   1735 		special = 1;
   1736 	} else if (!strcmp(tokens[2].text, "button")) {
   1737 		if (!strcmp(tokens[3].text, "press")) {
   1738 			mask = LTK_PWEVENTMASK_BUTTON_PRESS;
   1739 		} else if (!strcmp(tokens[3].text, "none")) {
   1740 			mask = LTK_PWEVENTMASK_BUTTON_NONE;
   1741 		} else {
   1742 			err->type = ERR_INVALID_ARGUMENT;
   1743 			err->arg = 3;
   1744 			return 1;
   1745 		}
   1746 		special = 1;
   1747 	} else {
   1748 		err->type = ERR_INVALID_ARGUMENT;
   1749 		err->arg = 2;
   1750 		return 1;
   1751 	}
   1752 	if (num_tokens == 5) {
   1753 		if (!strcmp(tokens[4].text, "lock")) {
   1754 			lock = 1;
   1755 		} else {
   1756 			err->type = ERR_INVALID_ARGUMENT;
   1757 			err->arg = 4;
   1758 			return 1;
   1759 		}
   1760 	}
   1761 
   1762 	if (!strcmp(tokens[0].text, "mask-add")) {
   1763 		if (lock) {
   1764 			if (special)
   1765 				ltk_widget_add_to_event_lwmask(widget, client, mask);
   1766 			else
   1767 				ltk_widget_add_to_event_lmask(widget, client, mask);
   1768 		} else {
   1769 			if (special)
   1770 				ltk_widget_add_to_event_wmask(widget, client, mask);
   1771 			else
   1772 				ltk_widget_add_to_event_mask(widget, client, mask);
   1773 		}
   1774 	} else if (!strcmp(tokens[0].text, "mask-set")) {
   1775 		if (lock) {
   1776 			if (special)
   1777 				ltk_widget_set_event_lwmask(widget, client, mask);
   1778 			else
   1779 				ltk_widget_set_event_lmask(widget, client, mask);
   1780 		} else {
   1781 			if (special)
   1782 				ltk_widget_set_event_wmask(widget, client, mask);
   1783 			else
   1784 				ltk_widget_set_event_mask(widget, client, mask);
   1785 		}
   1786 	} else if (!strcmp(tokens[0].text, "mask-remove")) {
   1787 		if (lock) {
   1788 			if (special)
   1789 				ltk_widget_remove_from_event_lwmask(widget, client, mask);
   1790 			else
   1791 				ltk_widget_remove_from_event_lmask(widget, client, mask);
   1792 		} else {
   1793 			if (special)
   1794 				ltk_widget_remove_from_event_wmask(widget, client, mask);
   1795 			else
   1796 				ltk_widget_remove_from_event_mask(widget, client, mask);
   1797 		}
   1798 	} else {
   1799 		err->type = ERR_INVALID_COMMAND;
   1800 		err->arg = 0;
   1801 		return 1;
   1802 	}
   1803 	return 0;
   1804 }
   1805 
   1806 /* Process the commands as they are read from the socket. */
   1807 /* Returns 1 if command was 'event-unlock true',
   1808    -1 if command was 'event-unlock false', 0 otherwise. */
   1809 static int
   1810 process_commands(ltk_window *window, int client) {
   1811 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
   1812 		return 0;
   1813 	struct ltk_sock_info *sock = &sockets[client];
   1814 	ltk_cmd_token *tokens;
   1815 	int num_tokens;
   1816 	ltk_error errdetail = {ERR_NONE, -1};
   1817 	int err;
   1818 	int retval = 0;
   1819 	int last = 0;
   1820 	uint32_t seq;
   1821 	const char *errstr;
   1822 	int contains_nul = 0;
   1823 	while (!tokenize_command(sock)) {
   1824 		contains_nul = 0;
   1825 		err = 0;
   1826 		tokens = sock->tokens.tokens;
   1827 		num_tokens = sock->tokens.num_tokens;
   1828 		if (num_tokens < 2) {
   1829 			errdetail.type = ERR_INVALID_COMMAND;
   1830 			errdetail.arg = -1;
   1831 			err = 1;
   1832 		} else {
   1833 			contains_nul = tokens[0].contains_nul;
   1834 			seq = (uint32_t)ltk_strtonum(tokens[0].text, 0, UINT32_MAX, &errstr);
   1835 			tokens++;
   1836 			num_tokens--;
   1837 			if (errstr || contains_nul) {
   1838 				errdetail.type = ERR_INVALID_SEQNUM;
   1839 				errdetail.arg = -1;
   1840 				err = 1;
   1841 				seq = sock->last_seq;
   1842 			} else if (tokens[0].contains_nul) {
   1843 				errdetail.type = ERR_INVALID_ARGUMENT;
   1844 				errdetail.arg = 0;
   1845 				err = 1;
   1846 				seq = sock->last_seq;
   1847 			} else if (strcmp(tokens[0].text, "set-root-widget") == 0) {
   1848 				err = ltk_set_root_widget_cmd(window, tokens, num_tokens, &errdetail);
   1849 			} else if (strcmp(tokens[0].text, "quit") == 0) {
   1850 				ltk_quit(window);
   1851 				last = 1;
   1852 			} else if (strcmp(tokens[0].text, "destroy") == 0) {
   1853 				err = ltk_widget_destroy_cmd(window, tokens, num_tokens, &errdetail);
   1854 			} else if (strncmp(tokens[0].text, "mask", 4) == 0) {
   1855 				err = handle_mask_command(client, tokens, num_tokens, &errdetail);
   1856 			} else if (strcmp(tokens[0].text, "event-unlock") == 0) {
   1857 				if (num_tokens != 2) {
   1858 					errdetail.type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
   1859 					errdetail.arg = -1;
   1860 					err = 1;
   1861 				} else if (!tokens[1].contains_nul && strcmp(tokens[1].text, "true") == 0) {
   1862 					retval = 1;
   1863 				} else if (!tokens[1].contains_nul && strcmp(tokens[1].text, "false") == 0) {
   1864 					retval = -1;
   1865 				} else {
   1866 					err = 1;
   1867 					errdetail.type = ERR_INVALID_ARGUMENT;
   1868 					errdetail.arg = 1;
   1869 				}
   1870 				last = 1;
   1871 			} else {
   1872 				int found = 0;
   1873 				for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
   1874 					if (widget_funcs[i].cmd && !strcmp(tokens[0].text, widget_funcs[i].name)) {
   1875 						err = widget_funcs[i].cmd(window, tokens, num_tokens, &errdetail);
   1876 						found = 1;
   1877 					}
   1878 				}
   1879 				if (!found) {
   1880 					errdetail.type = ERR_INVALID_COMMAND;
   1881 					errdetail.arg = -1;
   1882 					err = 1;
   1883 				}
   1884 			}
   1885 			sock->tokens.num_tokens = 0;
   1886 			sock->last_seq = seq;
   1887 		}
   1888 		if (err) {
   1889 			const char *errmsg = errtype_to_string(errdetail.type);
   1890 			if (ltk_queue_sock_write_fmt(client, "err %d %d \"%s\"\n", errdetail.type, errdetail.arg, errmsg))
   1891 				ltk_fatal("Unable to queue socket write.\n");
   1892 		} else {
   1893 			if (ltk_queue_sock_write(client, "res ok\n", -1)) {
   1894 				ltk_fatal("Unable to queue socket write.\n");
   1895 			}
   1896 		}
   1897 		if (last)
   1898 			break;
   1899 	}
   1900 	if (sock->tokens.num_tokens > 0 && sock->tokens.tokens[0].text != sock->read) {
   1901 		memmove(sock->read, sock->tokens.tokens[0].text, sock->read + sock->read_len - sock->tokens.tokens[0].text);
   1902 		ptrdiff_t offset = sock->tokens.tokens[0].text - sock->read;
   1903 		/* Hmm, seems a bit ugly... */
   1904 		for (int i = 0; i < sock->tokens.num_tokens; i++) {
   1905 			sock->tokens.tokens[i].text -= offset;
   1906 		}
   1907 		sock->read_len -= offset;
   1908 		sock->read_cur -= offset;
   1909 	} else if (sock->tokens.num_tokens == 0) {
   1910 		sock->read_len = 0;
   1911 		sock->read_cur = 0;
   1912 	}
   1913 	return retval;
   1914 }