ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | LICENSE

ltkd.c (31525B)


      1 /* FIXME: standardize functions taking window or not */
      2 /* FIXME: Figure out how to properly print window id */
      3 /* FIXME: error checking in tokenizer (is this necessary?) */
      4 /* FIXME: strip whitespace at end of lines in socket format */
      5 /*
      6  * Copyright (c) 2016-2026 lumidify <nobody@lumidify.org>
      7  *
      8  * Permission to use, copy, modify, and/or distribute this software for any
      9  * purpose with or without fee is hereby granted, provided that the above
     10  * copyright notice and this permission notice appear in all copies.
     11  *
     12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     19  */
     20 
     21 #include <time.h>
     22 #include <stdio.h>
     23 #include <fcntl.h>
     24 #include <errno.h>
     25 #include <stdlib.h>
     26 #include <string.h>
     27 #include <stdarg.h>
     28 #include <unistd.h>
     29 #include <signal.h>
     30 #include <locale.h>
     31 #include <inttypes.h>
     32 
     33 #include <sys/un.h>
     34 #include <sys/select.h>
     35 #include <sys/socket.h>
     36 
     37 #include "err.h"
     38 #include "ltkd.h"
     39 #include "util.h"
     40 #include "widget.h"
     41 #include "cmd_helpers.h"
     42 #include "proto_types.h"
     43 
     44 #include <ltk/memory.h>
     45 #include <ltk/ltk.h>
     46 #include <ltk/util.h>
     47 #include <ltk/text.h>
     48 #include <ltk/macros.h>
     49 #include <ltk/graphics.h>
     50 
     51 #define MAX_SOCK_CONNS 20
     52 #define READ_BLK_SIZE 128
     53 #define WRITE_BLK_SIZE 128
     54 
     55 /* FIXME: replace with generic array */
     56 struct token_list {
     57 	ltkd_cmd_token *tokens;
     58 	/* FIXME: size_t everywhere */
     59 	int num_tokens;
     60 	int num_alloc;
     61 };
     62 
     63 /* FIXME: switch to size_t */
     64 static struct ltkd_sock_info {
     65 	int fd;                    /* file descriptor for socket connection */
     66 	int event_mask;            /* events to send to socket */
     67 	char *read;                /* text read from socket */
     68 	int read_len;              /* length of text in read buffer */
     69 	int read_alloc;            /* size of read buffer */
     70 	char *to_write;            /* text to be written to socket */
     71 	int write_len;             /* length of text in write buffer */
     72 	int write_cur;             /* length of text already written */
     73 	int write_alloc;           /* size of write buffer */
     74 	/* stuff for tokenizing */
     75 	int in_token;              /* last read char is inside token */
     76 	int offset;                /* offset from removing backslashes */
     77 	int in_str;                /* last read char is inside string */
     78 	int read_cur;              /* length of text already tokenized */
     79 	int bs;                    /* last char was non-escaped backslash */
     80 	struct token_list tokens;  /* current tokens */
     81 	uint32_t last_seq;         /* sequence number of last request processed */
     82 } sockets[MAX_SOCK_CONNS];
     83 
     84 static int daemonize_flag = 1;
     85 
     86 static void ltkd_mainloop(void);
     87 static char *get_sock_path(char *basedir, unsigned long id);
     88 static FILE *open_log(char *dir);
     89 static void daemonize(void);
     90 static int read_sock(struct ltkd_sock_info *sock);
     91 static int push_token(struct token_list *tl, char *token);
     92 static int read_sock(struct ltkd_sock_info *sock);
     93 static int write_sock(struct ltkd_sock_info *sock);
     94 static int tokenize_command(struct ltkd_sock_info *sock);
     95 static int ltkd_set_root_widget_cmd(ltk_widget_id windowid, ltkd_cmd_token *tokens, int num_tokens, ltkd_error *err);
     96 static int process_commands(ltk_widget_id windowid, int client);
     97 static int add_client(int fd);
     98 static int listen_sock(const char *sock_path);
     99 static int accept_sock(int listenfd);
    100 static void ltkd_quit(void);
    101 static void ltkd_cleanup(void);
    102 
    103 static short maxsocket = -1;
    104 static short running = 1;
    105 static short sock_write_available = 0;
    106 static int base_dir_fd = -1;
    107 static char *ltkd_dir = NULL;
    108 static FILE *ltkd_logfile = NULL;
    109 static char *sock_path = NULL;
    110 /* Note: Most functions still take this explicitly because it wasn't
    111    global originally, but that's just the way it is. */
    112 static ltk_widget_id main_window = LTK_WIDGET_ID_NONE_STATIC;
    113 static unsigned long main_window_id = 0;
    114 
    115 typedef struct {
    116 	char *name;
    117 	int (*cmd)(ltk_widget_id, ltkd_cmd_token *, size_t, ltkd_error *);
    118 } ltkd_widget_funcs;
    119 
    120 /* FIXME: use binary search when searching for the widget */
    121 ltkd_widget_funcs widget_funcs[] = {
    122 	{
    123 		.name = "box",
    124 		.cmd = &ltkd_box_cmd
    125 	},
    126 	{
    127 		.name = "button",
    128 		.cmd = &ltkd_button_cmd
    129 	},
    130 	{
    131 		.name = "entry",
    132 		.cmd = &ltkd_entry_cmd
    133 	},
    134 	{
    135 		.name = "grid",
    136 		.cmd = &ltkd_grid_cmd
    137 	},
    138 	{
    139 		.name = "label",
    140 		.cmd = &ltkd_label_cmd
    141 	},
    142 	{
    143 		.name = "image",
    144 		.cmd = &ltkd_image_widget_cmd
    145 	},
    146 	{
    147 		.name = "menu",
    148 		.cmd = &ltkd_menu_cmd
    149 	},
    150 	{
    151 		.name = "menuentry",
    152 		.cmd = &ltkd_menuentry_cmd
    153 	},
    154 	{
    155 		.name = "submenu",
    156 		.cmd = &ltkd_menu_cmd
    157 	},
    158 };
    159 
    160 static int
    161 ltkd_window_close(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data) {
    162 	(void)self;
    163 	(void)args;
    164 	(void)data;
    165 	ltkd_quit();
    166 	return 1;
    167 }
    168 
    169 int
    170 main(int argc, char *argv[]) {
    171 	setlocale(LC_CTYPE, "");
    172 	/* FIXME: decide where to add this (currently called in ltk, but kind of weird) */
    173 	/*XSetLocaleModifiers("");*/
    174 	int ch;
    175 	char *title = "LTK Window";
    176 	while ((ch = getopt(argc, argv, "dt:")) != -1) {
    177 		switch (ch) {
    178 			case 't':
    179 				title = optarg;
    180 				break;
    181 			case 'd':
    182 				daemonize_flag = 0;
    183 				break;
    184 			default:
    185 				ltkd_fatal("USAGE: ltkd [-t title]\n");
    186 		}
    187 	}
    188 
    189 	ltkd_dir = ltk_setup_directory("LTKDDIR", ".ltkd", 1);
    190 	if (!ltkd_dir) ltkd_fatal_errno("Unable to setup ltkd directory.\n");
    191 	/* FIXME: this is only used to use unlinkat for deleting the socket in
    192 	   the end in case ltkd_dir is relative. It would probably be better
    193 	   to just use getcwd to turn that into an absolute path instead.
    194 	   Or maybe ltkd should just stay in the current directory instead of
    195 	   moving to / in daemonize(). */
    196 	base_dir_fd = open(".", O_RDONLY);
    197 	if (base_dir_fd < 0)
    198 		ltkd_fatal_errno("Unable to open current directory.\n");
    199 	ltkd_logfile = open_log(ltkd_dir);
    200 	if (!ltkd_logfile) ltkd_fatal_errno("Unable to open log file.\n");
    201 
    202 	ltk_init();
    203 	ltkd_widgets_init();
    204 
    205 	/* FIXME: set window size properly - I only run it in a tiling WM
    206 	   anyways, so it doesn't matter, but still... */
    207 	main_window = ltk_window_create(title, 0, 0, 500, 500);
    208 	ltk_widget *windoww = ltk_get_widget_from_id(main_window);
    209 	main_window_id = ltk_renderer_get_window_id(LTK_CAST_WINDOW(windoww)->renderwindow);
    210 	sock_path = get_sock_path(ltkd_dir, main_window_id);
    211 	if (!sock_path) ltkd_fatal_errno("Unable to allocate memory for socket path.\n");
    212 
    213 	ltk_widget_register_signal_handler(main_window, LTK_WINDOW_SIGNAL_CLOSE, &ltkd_window_close, LTK_ARG_VOID);
    214 
    215 	/* Note: sockets should be initialized to 0 because it is static */
    216 	for (int i = 0; i < MAX_SOCK_CONNS; i++) {
    217 		sockets[i].fd = -1; /* socket unused */
    218 		/* initialize these just because I'm paranoid */
    219 		sockets[i].read = NULL;
    220 		sockets[i].to_write = NULL;
    221 		sockets[i].tokens.tokens = NULL;
    222 	}
    223 
    224 	ltkd_mainloop();
    225 	return 0;
    226 }
    227 
    228 /* FIXME: need to recalculate maxfd when removing client */
    229 static struct {
    230 	fd_set rallfds, wallfds;
    231 	int maxfd;
    232 	int listenfd;
    233 } sock_state;
    234 
    235 /* FIXME: this is extremely dangerous right now because pretty much any command
    236    can be executed, so for instance the widget that caused the lock could also
    237    be destroyed, causing issues when this function returns */
    238 int
    239 ltkd_handle_lock_client(ltk_widget_id windowid, int client) {
    240 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
    241 		return 0;
    242 	fd_set rfds, wfds, rallfds, wallfds;
    243 	int clifd = sockets[client].fd;
    244 	FD_ZERO(&rallfds);
    245 	FD_ZERO(&wallfds);
    246 	FD_SET(clifd, &rallfds);
    247 	FD_SET(clifd, &wallfds);
    248 	int retval;
    249 	struct timeval tv;
    250 	tv.tv_sec = 0;
    251 	tv.tv_usec = 0;
    252 	struct timespec now, elapsed, last, sleep_time;
    253 	clock_gettime(CLOCK_MONOTONIC, &last);
    254 	sleep_time.tv_sec = 0;
    255 	while (1) {
    256 		rfds = rallfds;
    257 		wfds = wallfds;
    258 		retval = select(clifd + 1, &rfds, &wfds, NULL, &tv);
    259 
    260 		if (retval > 0) {
    261 			if (FD_ISSET(clifd, &rfds)) {
    262 				int ret;
    263 				while ((ret = read_sock(&sockets[client])) == 1) {
    264 					int pret;
    265 					if ((pret = process_commands(windowid, client)) == 1)
    266 						return 1;
    267 					else if (pret == -1)
    268 						return 0;
    269 				}
    270 				/* FIXME: maybe also return on read error? or would that be dangerous? */
    271 				if (ret == 0) {
    272 					FD_CLR(clifd, &sock_state.rallfds);
    273 					FD_CLR(clifd, &sock_state.wallfds);
    274 					ltkd_widget_remove_client(client);
    275 					sockets[clifd].fd = -1;
    276 					close(clifd);
    277 					int newmaxsocket = -1;
    278 					for (int j = 0; j <= maxsocket; j++) {
    279 						if (sockets[j].fd >= 0)
    280 							newmaxsocket = j;
    281 					}
    282 					maxsocket = newmaxsocket;
    283 					if (maxsocket == -1) {
    284 						ltkd_quit();
    285 						break;
    286 					}
    287 					return 0;
    288 				}
    289 			}
    290 			if (FD_ISSET(clifd, &wfds)) {
    291 				/* FIXME: call in loop like above */
    292 				write_sock(&sockets[client]);
    293 			}
    294 		}
    295 		clock_gettime(CLOCK_MONOTONIC, &now);
    296 		ltk_timespecsub(&now, &last, &elapsed);
    297 		/* FIXME: configure framerate */
    298 		if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) {
    299 			sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec;
    300 			nanosleep(&sleep_time, NULL);
    301 		}
    302 		last = now;
    303 	}
    304 	return 0;
    305 }
    306 
    307 /* FIXME: need to remove event masks from all widgets when removing client */
    308 static void
    309 ltkd_mainloop(void) {
    310 	fd_set rfds, wfds;
    311 	int retval;
    312 	int clifd;
    313 	struct timeval tv;
    314 	tv.tv_sec = 0;
    315 	tv.tv_usec = 0;
    316 
    317 	FD_ZERO(&sock_state.rallfds);
    318 	FD_ZERO(&sock_state.wallfds);
    319 
    320 	if ((sock_state.listenfd = listen_sock(sock_path)) < 0)
    321 		ltkd_fatal_errno("Error listening on socket.\n");
    322 
    323 	FD_SET(sock_state.listenfd, &sock_state.rallfds);
    324 	sock_state.maxfd = sock_state.listenfd;
    325 
    326 	printf("%lu", main_window_id);
    327 	fflush(stdout);
    328 	if (daemonize_flag)
    329 		daemonize();
    330 
    331 	ltk_mainloop_init();
    332 
    333 	while (running) {
    334 		rfds = sock_state.rallfds;
    335 		wfds = sock_state.wallfds;
    336 		retval = select(sock_state.maxfd + 1, &rfds, &wfds, NULL, &tv);
    337 		if (retval > 0) {
    338 			if (FD_ISSET(sock_state.listenfd, &rfds)) {
    339 				if ((clifd = accept_sock(sock_state.listenfd)) < 0) {
    340 					/* FIXME: Just log this! */
    341 					ltkd_fatal_errno("Error accepting socket connection.\n");
    342 				}
    343 				int i = add_client(clifd);
    344 				FD_SET(clifd, &sock_state.rallfds);
    345 				FD_SET(clifd, &sock_state.wallfds);
    346 				if (clifd > sock_state.maxfd)
    347 					sock_state.maxfd = clifd;
    348 				if (i > maxsocket)
    349 					maxsocket = i;
    350 				continue;
    351 			}
    352 			for (int i = 0; i <= maxsocket; i++) {
    353 				if ((clifd = sockets[i].fd) < 0)
    354 					continue;
    355 				if (FD_ISSET(clifd, &rfds)) {
    356 					/* FIXME: better error handling - this assumes error
    357 					   is always because read would block */
    358 					/* FIXME: maybe maximum number of iterations here to
    359 					   avoid choking on a lot of data? although such a
    360 					   large amount of data would probably cause other
    361 					   problems anyways */
    362 					/* or maybe measure time and break after max time? */
    363 					int ret;
    364 					while ((ret = read_sock(&sockets[i])) == 1) {
    365 						process_commands(main_window, i);
    366 					}
    367 					if (ret == 0) {
    368 						ltkd_widget_remove_client(i);
    369 						FD_CLR(clifd, &sock_state.rallfds);
    370 						FD_CLR(clifd, &sock_state.wallfds);
    371 						sockets[i].fd = -1;
    372 						/* FIXME: what to do on error? */
    373 						close(clifd);
    374 						int newmaxsocket = -1;
    375 						for (int j = 0; j <= maxsocket; j++) {
    376 							if (sockets[j].fd >= 0)
    377 								newmaxsocket = j;
    378 						}
    379 						maxsocket = newmaxsocket;
    380 						if (maxsocket == -1) {
    381 							ltkd_quit();
    382 							break;
    383 						}
    384 					}
    385 				}
    386 				/* FIXME: maybe ignore SIGPIPE signal - then don't call FD_CLR
    387 				   for wallfds above but rather when write fails with EPIPE */
    388 				/* -> this would possibly allow data to be written still in the
    389 				   hypothetical scenario that only the writing end of the socket
    390 				   is closed (and ltkd wouldn't crash if only the reading end is
    391 				   closed) */
    392 				if (FD_ISSET(clifd, &wfds)) {
    393 					/* FIXME: also call in loop like reading above */
    394 					write_sock(&sockets[i]);
    395 				}
    396 			}
    397 		}
    398 
    399 		ltk_mainloop_step(1);
    400 	}
    401 
    402 	ltkd_cleanup();
    403 }
    404 
    405 /* largely copied from APUE */
    406 static void
    407 daemonize(void) {
    408 	pid_t pid;
    409 	struct sigaction sa;
    410 
    411 	fflush(stdout);
    412 	fflush(stderr);
    413 	fflush(ltkd_logfile);
    414 
    415 	if ((pid = fork()) < 0)
    416 		ltkd_fatal_errno("Can't fork.\n");
    417 	else if (pid != 0)
    418 		exit(0);
    419 	setsid();
    420 
    421 	sa.sa_handler = SIG_IGN;
    422 	sigemptyset(&sa.sa_mask);
    423 	sa.sa_flags = 0;
    424 	if (sigaction(SIGHUP, &sa, NULL) < 0)
    425 		ltkd_fatal_errno("Unable to ignore SIGHUP.\n");
    426 	if ((pid  = fork()) < 0)
    427 		ltkd_fatal_errno("Can't fork.\n");
    428 	else if (pid != 0)
    429 		exit(0);
    430 
    431 	/* FIXME: does this cause any issues with the lazy initialization of pango/fontconfig? */
    432 	if (chdir("/") < 0)
    433 		ltkd_fatal_errno("Can't change directory to root.\n");
    434 
    435 	/* FIXME: error handling */
    436 	int devnull = open("/dev/null", O_RDONLY);
    437 	if (devnull >= 0)
    438 		dup2(devnull, fileno(stdin));
    439 	dup2(fileno(ltkd_logfile), fileno(stdout));
    440 	dup2(fileno(ltkd_logfile), fileno(stderr));
    441 }
    442 
    443 static char *
    444 get_sock_path(char *basedir, unsigned long id) {
    445 	int len;
    446 	char *path;
    447 
    448 	len = strlen(basedir);
    449 	/* FIXME: MAKE SURE THIS IS ACTUALLY BIG ENOUGH! */
    450 	path = ltk_malloc(len + 20);
    451 	/* FIXME: also check for less than 0 */
    452 	if (snprintf(path, len + 20, "%s/%lu.sock", basedir, id) >= len + 20)
    453 		ltkd_fatal("Tell lumidify to fix his code.\n");
    454 
    455 	return path;
    456 }
    457 
    458 static FILE *
    459 open_log(char *dir) {
    460 	FILE *f;
    461 	char *path;
    462 
    463 	path = ltk_strcat_useful(dir, "/ltkd.log");
    464 	if (!path)
    465 		return NULL;
    466 	f = fopen(path, "a");
    467 	if (!f) {
    468 		ltk_free(path);
    469 		return NULL;
    470 	}
    471 	ltk_free(path);
    472 
    473 	return f;
    474 }
    475 
    476 static void
    477 ltkd_cleanup(void) {
    478 	if (sock_path) {
    479 		/* FIXME: somewhat misleading warning message */
    480 		if (base_dir_fd >= 0)
    481 			unlinkat(base_dir_fd, sock_path, 0);
    482 		else
    483 			ltk_warn("Unable to remove socket file!\n");
    484 		ltk_free(sock_path);
    485 	}
    486 	if (base_dir_fd >= 0)
    487 		close(base_dir_fd);
    488 	if (sock_state.listenfd >= 0)
    489 		close(sock_state.listenfd);
    490 	if (ltkd_dir)
    491 		ltk_free(ltkd_dir);
    492 	if (ltkd_logfile)
    493 		fclose(ltkd_logfile);
    494 
    495 	for (int i = 0; i < MAX_SOCK_CONNS; i++) {
    496 		if (sockets[i].fd >= 0)
    497 			close(sockets[i].fd);
    498 		if (sockets[i].read)
    499 			ltk_free(sockets[i].read);
    500 		if (sockets[i].to_write)
    501 			ltk_free(sockets[i].to_write);
    502 		if (sockets[i].tokens.tokens)
    503 			ltk_free(sockets[i].tokens.tokens);
    504 	}
    505 
    506 	ltkd_widgets_cleanup();
    507 	main_window = LTK_WIDGET_ID_NONE;
    508 	main_window_id = 0;
    509 	ltk_deinit();
    510 }
    511 
    512 static void
    513 ltkd_quit(void) {
    514 	running = 0;
    515 }
    516 
    517 static void
    518 ltkd_log_msg(const char *mode, const char *format, va_list args) {
    519 	char logtime[25]; /* FIXME: This should always be big enough, right? */
    520 	time_t clock;
    521 	struct tm *timeptr;
    522 
    523 	time(&clock);
    524 	timeptr = localtime(&clock);
    525 	strftime(logtime, 25, "%Y-%m-%d %H:%M:%S", timeptr);
    526 
    527 	if (!LTK_WIDGET_ID_IS_NONE(main_window))
    528 		fprintf(stderr, "%s ltkd(%lu) %s: ", logtime, main_window_id, mode);
    529 	else
    530 		fprintf(stderr, "%s ltkd(?) %s: ", logtime, mode);
    531 	vfprintf(stderr, format, args);
    532 }
    533 
    534 static int
    535 ltkd_set_root_widget_cmd(
    536     ltk_widget_id windowid,
    537     ltkd_cmd_token *tokens,
    538     int num_tokens,
    539     ltkd_error *err) {
    540 	if (num_tokens != 2) {
    541 		err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
    542 		err->arg = -1;
    543 		return 1;
    544 	} else if (tokens[1].contains_nul) {
    545 		err->type = ERR_INVALID_ARGUMENT;
    546 		err->arg = 1;
    547 		return 1;
    548 	}
    549 	ltkd_widget *widget = ltkd_get_widget(tokens[1].text, LTK_WIDGET_UNKNOWN, err);
    550 	if (!widget) {
    551 		err->arg = 1;
    552 		return 1;
    553 	}
    554 	ltk_window_set_root_widget(windowid, widget->ltkid);
    555 
    556 	return 0;
    557 }
    558 
    559 /* Push a token onto `token_list`, resizing the buffer if necessary.
    560    Returns -1 on error, 0 otherwise.
    561    Note: The token is not copied, it is only added directly. */
    562 static int
    563 push_token(struct token_list *tl, char *token) {
    564 	int new_size;
    565 	if (tl->num_tokens >= tl->num_alloc) {
    566 		new_size = (tl->num_alloc * 2) > (tl->num_tokens + 1) ?
    567 			   (tl->num_alloc * 2) : (tl->num_tokens + 1);
    568 		ltkd_cmd_token *new = ltk_reallocarray(tl->tokens, new_size, sizeof(ltkd_cmd_token));
    569 		if (!new) return -1;
    570 		tl->tokens = new;
    571 		tl->num_alloc = new_size;
    572 	}
    573 	tl->tokens[tl->num_tokens].text = token;
    574 	tl->tokens[tl->num_tokens].len = 0;
    575 	tl->tokens[tl->num_tokens++].contains_nul = 0;
    576 
    577 	return 0;
    578 }
    579 
    580 /* Add a new client to the socket list and return the index in `sockets`.
    581    Returns -1 if there is no space for a new client. */
    582 static int
    583 add_client(int fd) {
    584 	for (int i = 0; i < MAX_SOCK_CONNS; i++) {
    585 		if (sockets[i].fd == -1) {
    586 			sockets[i].fd = fd;
    587 			sockets[i].event_mask = ~0; /* FIXME */
    588 			sockets[i].read_len = 0;
    589 			sockets[i].write_len = 0;
    590 			sockets[i].write_cur = 0;
    591 			sockets[i].offset = 0;
    592 			sockets[i].in_str = 0;
    593 			sockets[i].read_cur = 0;
    594 			sockets[i].bs = 0;
    595 			sockets[i].tokens.num_tokens = 0;
    596 			sockets[i].last_seq = 0;
    597 			return i;
    598 		}
    599 	}
    600 
    601 	return -1;
    602 }
    603 
    604 /* largely copied from APUE */
    605 /* Listen on the socket at `sock_path`.
    606    Returns the file descriptor of the opened socket on success.
    607    Returns -1 if `sock_path` is too long
    608            -2 if the socket could not be created
    609            -3 if the socket could not be bound to the path
    610            -4 if the socket could not be listened on */
    611 static int
    612 listen_sock(const char *sock_path) {
    613 	int fd, len, err, rval;
    614 	struct sockaddr_un un;
    615 
    616 	if (strlen(sock_path) >= sizeof(un.sun_path)) {
    617 		errno = ENAMETOOLONG;
    618 		return -1;
    619 	}
    620 
    621 	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    622 		return -2;
    623 
    624 	unlink(sock_path);
    625 
    626 	memset(&un, 0, sizeof(un));
    627 	un.sun_family = AF_UNIX;
    628 	strcpy(un.sun_path, sock_path);
    629 	len = offsetof(struct sockaddr_un, sun_path) + strlen(sock_path);
    630 	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
    631 		rval = -3;
    632 		goto errout;
    633 	}
    634 
    635 	if (listen(fd, 10) < 0) {
    636 		rval = -4;
    637 		goto errout;
    638 	}
    639 
    640 	return fd;
    641 
    642 errout:
    643 	err = errno;
    644 	close(fd);
    645 	errno = err;
    646 	return rval;
    647 }
    648 
    649 /* Accept a socket connection on the listening socket `listenfd`.
    650    Returns the file descriptor of the accepted client on success.
    651    Returns -1 if there was an error. */
    652 static int
    653 accept_sock(int listenfd) {
    654 	int clifd;
    655 	socklen_t len;
    656 	struct sockaddr_un un;
    657 
    658 	len = sizeof(un);
    659 	if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
    660 		return -1;
    661 	}
    662 	if (ltkd_set_nonblock(clifd)) {
    663 		/* FIXME: what could even be done if close fails? */
    664 		close(clifd);
    665 		return -1;
    666 	}
    667 
    668 	return clifd;
    669 }
    670 
    671 /* Read up to READ_BLK_SIZE bytes from the socket `sock`.
    672    Returns -1 if an error occurred, 0 if the connection was closed, 1 otherwise.
    673    Note: Returning 1 on success is weird, but it could also be confusing to
    674    return 0 on success when `read` returns that to mean that the connection
    675    was closed. */
    676 static int
    677 read_sock(struct ltkd_sock_info *sock) {
    678 	int nread;
    679 	char *old = sock->read;
    680 	ltk_grow_string(&sock->read, &sock->read_alloc, sock->read_len + READ_BLK_SIZE);
    681 	/* move tokens to new addresses - this was added as an
    682 	   afterthought and really needs to be cleaned up */
    683 	if (sock->read != old) {
    684 		for (int i = 0; i < sock->tokens.num_tokens; i++) {
    685 			sock->tokens.tokens[i].text = sock->read + (sock->tokens.tokens[i].text - old);
    686 		}
    687 	}
    688 	nread = read(sock->fd, sock->read + sock->read_len, READ_BLK_SIZE);
    689 	if (nread == -1 || nread == 0)
    690 		return nread;
    691 	sock->read_len += nread;
    692 
    693 	return 1;
    694 }
    695 
    696 /* Write up to WRITE_BLK_SIZE bytes to the socket.
    697    Returns -1 on error, 0 otherwise. */
    698 static int
    699 write_sock(struct ltkd_sock_info *sock) {
    700 	if (sock->write_len == sock->write_cur)
    701 		return 0;
    702 	int write_len = WRITE_BLK_SIZE > sock->write_len - sock->write_cur ?
    703 			sock->write_len - sock->write_cur : WRITE_BLK_SIZE;
    704 	int nwritten = write(sock->fd, sock->to_write + sock->write_cur, write_len);
    705 	if (nwritten == -1)
    706 		return nwritten;
    707 	sock->write_cur += nwritten;
    708 
    709 	/* check if any sockets have text to write */
    710 	if (sock->write_cur == sock->write_len) {
    711 		int found = 0;
    712 		for (int i = 0; i < maxsocket; i++) {
    713 			if (sockets[i].fd != -1 &&
    714 			    sockets[i].write_cur != sockets[i].write_len) {
    715 				found = 1;
    716 				break;
    717 			}
    718 		}
    719 		if (!found)
    720 			sock_write_available = 0;
    721 	}
    722 
    723 	return 0;
    724 }
    725 
    726 static void
    727 move_write_pos(struct ltkd_sock_info *sock) {
    728 	/* FIXME: also resize if too large */
    729 	if (sock->write_cur > 0) {
    730 		memmove(sock->to_write, sock->to_write + sock->write_cur,
    731 		        sock->write_len - sock->write_cur);
    732 		sock->write_len -= sock->write_cur;
    733 		sock->write_cur = 0;
    734 	}
    735 }
    736 
    737 /* Queue `str` to be written to the socket. If len is < 0, it is set to `strlen(str)`.
    738    Returns -1 on error, 0 otherwise.
    739    Note: The string must include all '\n', etc. as defined in the protocol. This
    740    function just adds the given string verbatim. */
    741 int
    742 ltkd_queue_sock_write(int client, const char *str, int len) {
    743 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
    744 		return 1;
    745 	/* this is always large enough to hold a uint32_t and " \0" */
    746 	char num[12];
    747 	struct ltkd_sock_info *sock = &sockets[client];
    748 	move_write_pos(sock);
    749 	if (len < 0)
    750 		len = strlen(str);
    751 
    752 	int numlen = snprintf(num, sizeof(num), "%"PRIu32" ", sock->last_seq);
    753 	if (numlen < 0 || (unsigned)numlen >= sizeof(num))
    754 		ltkd_fatal("There's a bug in the universe.\n");
    755 	if (sock->write_alloc - sock->write_len < len + numlen)
    756 		ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len + numlen);
    757 
    758 	(void)strncpy(sock->to_write + sock->write_len, num, numlen);
    759 	(void)strncpy(sock->to_write + sock->write_len + numlen, str, len);
    760 	sock->write_len += len + numlen;
    761 
    762 	sock_write_available = 1;
    763 
    764 	return 0;
    765 }
    766 
    767 int
    768 ltkd_queue_sock_write_fmt(int client, const char *fmt, ...) {
    769 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
    770 		return 1;
    771 	struct ltkd_sock_info *sock = &sockets[client];
    772 	/* just to print the sequence number */
    773 	ltkd_queue_sock_write(client, "", 0);
    774 	va_list args;
    775 	va_start(args, fmt);
    776 	int len = vsnprintf(sock->to_write + sock->write_len, sock->write_alloc - sock->write_len, fmt, args);
    777 	if (len < 0) {
    778 		ltkd_fatal("Unable to print formatted text to socket.\n");
    779 	} else if (len >= sock->write_alloc - sock->write_len) {
    780 		va_end(args);
    781 		va_start(args, fmt);
    782 		/* snprintf always writes '\0', even though we don't actually need it here */
    783 		ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len + 1);
    784 		vsnprintf(sock->to_write + sock->write_len, sock->write_alloc - sock->write_len, fmt, args);
    785 	}
    786 	va_end(args);
    787 	sock->write_len += len;
    788 	sock_write_available = 1;
    789 
    790 	return 0;
    791 }
    792 
    793 /* Tokenize the current read buffer in `sock`.
    794    Returns 0 immediately if the end of a command was encountered, 1 otherwise. */
    795 static int
    796 tokenize_command(struct ltkd_sock_info *sock) {
    797 	for (; sock->read_cur < sock->read_len; sock->read_cur++) {
    798 		/* FIXME: strip extra whitespace? Or maybe just have a "hard" protocol where it always has to be one space. */
    799 		if (!sock->in_token) {
    800 			push_token(&sock->tokens, sock->read + sock->read_cur - sock->offset);
    801 			sock->in_token = 1;
    802 		}
    803 		if (sock->read[sock->read_cur] == '\\') {
    804 			sock->bs++;
    805 			if (sock->bs / 2)
    806 				sock->offset++;
    807 			else
    808 				sock->tokens.tokens[sock->tokens.num_tokens - 1].len++;
    809 			sock->bs %= 2;
    810 			sock->read[sock->read_cur-sock->offset] = '\\';
    811 		} else if (sock->read[sock->read_cur] == '\n' && !sock->in_str) {
    812 			sock->read[sock->read_cur-sock->offset] = '\0';
    813 			sock->read_cur++;
    814 			sock->offset = 0;
    815 			sock->in_token = 0;
    816 			sock->bs = 0;
    817 			return 0;
    818 		} else if (sock->read[sock->read_cur] == '"') {
    819 			sock->offset++;
    820 			if (sock->bs) {
    821 				sock->read[sock->read_cur-sock->offset] = '"';
    822 				sock->bs = 0;
    823 			} else {
    824 				sock->in_str = !sock->in_str;
    825 			}
    826 		} else if (sock->read[sock->read_cur] == ' ' && !sock->in_str) {
    827 			sock->read[sock->read_cur-sock->offset] = '\0';
    828 			sock->in_token = !sock->in_token;
    829 			sock->bs = 0;
    830 		} else {
    831 			sock->read[sock->read_cur-sock->offset] = sock->read[sock->read_cur];
    832 			/* FIXME: assert that num_tokens > 0 */
    833 			sock->tokens.tokens[sock->tokens.num_tokens - 1].len++;
    834 			if (sock->read[sock->read_cur] == '\0')
    835 				sock->tokens.tokens[sock->tokens.num_tokens - 1].contains_nul = 1;
    836 			sock->bs = 0;
    837 		}
    838 	}
    839 
    840 	return 1;
    841 }
    842 
    843 /* FIXME: currently no type-checking when setting specific widget mask */
    844 /* FIXME: this is really ugly and inefficient right now - it will be replaced with something
    845    more generic at some point (or maybe just with a binary protocol?) */
    846 static int
    847 handle_mask_command(int client, ltkd_cmd_token *tokens, size_t num_tokens, ltkd_error *err) {
    848 	if (num_tokens != 4 && num_tokens != 5) {
    849 		err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
    850 		err->arg = -1;
    851 		return 1;
    852 	}
    853 	/* FIXME: make this nicer */
    854 	/* -> use generic cmd handling like the widgets */
    855 	if (tokens[1].contains_nul) {
    856 		err->type = ERR_INVALID_ARGUMENT;
    857 		err->arg = 1;
    858 		return 1;
    859 	} else if (tokens[2].contains_nul) {
    860 		err->type = ERR_INVALID_ARGUMENT;
    861 		err->arg = 2;
    862 		return 1;
    863 	} else if (tokens[3].contains_nul) {
    864 		err->type = ERR_INVALID_ARGUMENT;
    865 		err->arg = 3;
    866 		return 1;
    867 	} else if (num_tokens == 5 && tokens[4].contains_nul) {
    868 		err->type = ERR_INVALID_ARGUMENT;
    869 		err->arg = 4;
    870 		return 1;
    871 	}
    872 	uint32_t mask = 0;
    873 	int lock = 0;
    874 	int special = 0;
    875 	ltkd_widget *widget = ltkd_get_widget(tokens[1].text, LTK_WIDGET_UNKNOWN, err);
    876 	if (!widget) {
    877 		err->arg = 1;
    878 		return 1;
    879 	}
    880 	if (!strcmp(tokens[2].text, "widget")) {
    881 		if (!strcmp(tokens[3].text, "mousepress")) {
    882 			mask = LTKD_PEVENTMASK_MOUSEPRESS;
    883 		} else if (!strcmp(tokens[3].text, "mouserelease")) {
    884 			mask = LTKD_PEVENTMASK_MOUSERELEASE;
    885 		} else if (!strcmp(tokens[3].text, "mousemotion")) {
    886 			mask = LTKD_PEVENTMASK_MOUSEMOTION;
    887 		} else if (!strcmp(tokens[3].text, "resize")) {
    888 			mask = LTKD_PEVENTMASK_RESIZE;
    889 		} else if (!strcmp(tokens[3].text, "statechange")) {
    890 			mask = LTKD_PEVENTMASK_STATECHANGE;
    891 		} else if (!strcmp(tokens[3].text, "none")) {
    892 			mask = LTKD_PEVENTMASK_NONE;
    893 		} else {
    894 			err->type = ERR_INVALID_ARGUMENT;
    895 			err->arg = 3;
    896 			return 1;
    897 		}
    898 	} else if (!strcmp(tokens[2].text, "menuentry")) {
    899 		if (!strcmp(tokens[3].text, "press")) {
    900 			mask = LTKD_PWEVENTMASK_MENUENTRY_PRESS;
    901 		} else if (!strcmp(tokens[3].text, "none")) {
    902 			mask = LTKD_PWEVENTMASK_MENUENTRY_NONE;
    903 		} else {
    904 			err->type = ERR_INVALID_ARGUMENT;
    905 			err->arg = 3;
    906 			return 1;
    907 		}
    908 		special = 1;
    909 	} else if (!strcmp(tokens[2].text, "button")) {
    910 		if (!strcmp(tokens[3].text, "press")) {
    911 			mask = LTKD_PWEVENTMASK_BUTTON_PRESS;
    912 		} else if (!strcmp(tokens[3].text, "none")) {
    913 			mask = LTKD_PWEVENTMASK_BUTTON_NONE;
    914 		} else {
    915 			err->type = ERR_INVALID_ARGUMENT;
    916 			err->arg = 3;
    917 			return 1;
    918 		}
    919 		special = 1;
    920 	} else {
    921 		err->type = ERR_INVALID_ARGUMENT;
    922 		err->arg = 2;
    923 		return 1;
    924 	}
    925 	if (num_tokens == 5) {
    926 		if (!strcmp(tokens[4].text, "lock")) {
    927 			lock = 1;
    928 		} else {
    929 			err->type = ERR_INVALID_ARGUMENT;
    930 			err->arg = 4;
    931 			return 1;
    932 		}
    933 	}
    934 
    935 	if (!strcmp(tokens[0].text, "mask-add")) {
    936 		if (lock) {
    937 			if (special)
    938 				ltkd_widget_add_to_event_lwmask(widget, client, mask);
    939 			else
    940 				ltkd_widget_add_to_event_lmask(widget, client, mask);
    941 		} else {
    942 			if (special)
    943 				ltkd_widget_add_to_event_wmask(widget, client, mask);
    944 			else
    945 				ltkd_widget_add_to_event_mask(widget, client, mask);
    946 		}
    947 	} else if (!strcmp(tokens[0].text, "mask-set")) {
    948 		if (lock) {
    949 			if (special)
    950 				ltkd_widget_set_event_lwmask(widget, client, mask);
    951 			else
    952 				ltkd_widget_set_event_lmask(widget, client, mask);
    953 		} else {
    954 			if (special)
    955 				ltkd_widget_set_event_wmask(widget, client, mask);
    956 			else
    957 				ltkd_widget_set_event_mask(widget, client, mask);
    958 		}
    959 	} else if (!strcmp(tokens[0].text, "mask-remove")) {
    960 		if (lock) {
    961 			if (special)
    962 				ltkd_widget_remove_from_event_lwmask(widget, client, mask);
    963 			else
    964 				ltkd_widget_remove_from_event_lmask(widget, client, mask);
    965 		} else {
    966 			if (special)
    967 				ltkd_widget_remove_from_event_wmask(widget, client, mask);
    968 			else
    969 				ltkd_widget_remove_from_event_mask(widget, client, mask);
    970 		}
    971 	} else {
    972 		err->type = ERR_INVALID_COMMAND;
    973 		err->arg = 0;
    974 		return 1;
    975 	}
    976 	return 0;
    977 }
    978 
    979 /* Process the commands as they are read from the socket. */
    980 /* Returns 1 if command was 'event-unlock true',
    981    -1 if command was 'event-unlock false', 0 otherwise. */
    982 static int
    983 process_commands(ltk_widget_id windowid, int client) {
    984 	if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
    985 		return 0;
    986 	struct ltkd_sock_info *sock = &sockets[client];
    987 	ltkd_cmd_token *tokens;
    988 	int num_tokens;
    989 	ltkd_error errdetail = {ERR_NONE, -1};
    990 	int err;
    991 	int retval = 0;
    992 	int last = 0;
    993 	uint32_t seq;
    994 	const char *errstr;
    995 	int contains_nul = 0;
    996 	while (!tokenize_command(sock)) {
    997 		contains_nul = 0;
    998 		err = 0;
    999 		tokens = sock->tokens.tokens;
   1000 		num_tokens = sock->tokens.num_tokens;
   1001 		if (num_tokens < 2) {
   1002 			errdetail.type = ERR_INVALID_COMMAND;
   1003 			errdetail.arg = -1;
   1004 			err = 1;
   1005 		} else {
   1006 			contains_nul = tokens[0].contains_nul;
   1007 			seq = (uint32_t)ltk_strtonum(tokens[0].text, 0, UINT32_MAX, &errstr);
   1008 			tokens++;
   1009 			num_tokens--;
   1010 			if (errstr || contains_nul) {
   1011 				errdetail.type = ERR_INVALID_SEQNUM;
   1012 				errdetail.arg = -1;
   1013 				err = 1;
   1014 				seq = sock->last_seq;
   1015 			} else if (tokens[0].contains_nul) {
   1016 				errdetail.type = ERR_INVALID_ARGUMENT;
   1017 				errdetail.arg = 0;
   1018 				err = 1;
   1019 				seq = sock->last_seq;
   1020 			} else if (strcmp(tokens[0].text, "set-root-widget") == 0) {
   1021 				err = ltkd_set_root_widget_cmd(windowid, tokens, num_tokens, &errdetail);
   1022 			} else if (strcmp(tokens[0].text, "quit") == 0) {
   1023 				ltkd_quit();
   1024 				last = 1;
   1025 			} else if (strcmp(tokens[0].text, "destroy") == 0) {
   1026 				err = ltkd_widget_destroy_cmd(windowid, tokens, num_tokens, &errdetail);
   1027 			} else if (strncmp(tokens[0].text, "mask", 4) == 0) {
   1028 				err = handle_mask_command(client, tokens, num_tokens, &errdetail);
   1029 			} else if (strcmp(tokens[0].text, "event-unlock") == 0) {
   1030 				if (num_tokens != 2) {
   1031 					errdetail.type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
   1032 					errdetail.arg = -1;
   1033 					err = 1;
   1034 				} else if (!tokens[1].contains_nul && strcmp(tokens[1].text, "true") == 0) {
   1035 					retval = 1;
   1036 				} else if (!tokens[1].contains_nul && strcmp(tokens[1].text, "false") == 0) {
   1037 					retval = -1;
   1038 				} else {
   1039 					err = 1;
   1040 					errdetail.type = ERR_INVALID_ARGUMENT;
   1041 					errdetail.arg = 1;
   1042 				}
   1043 				last = 1;
   1044 			} else {
   1045 				int found = 0;
   1046 				for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
   1047 					if (widget_funcs[i].cmd && !strcmp(tokens[0].text, widget_funcs[i].name)) {
   1048 						err = widget_funcs[i].cmd(windowid, tokens, num_tokens, &errdetail);
   1049 						found = 1;
   1050 					}
   1051 				}
   1052 				if (!found) {
   1053 					errdetail.type = ERR_INVALID_COMMAND;
   1054 					errdetail.arg = -1;
   1055 					err = 1;
   1056 				}
   1057 			}
   1058 			sock->tokens.num_tokens = 0;
   1059 			sock->last_seq = seq;
   1060 		}
   1061 		if (err) {
   1062 			const char *errmsg = errtype_to_string(errdetail.type);
   1063 			if (ltkd_queue_sock_write_fmt(client, "err %d %d \"%s\"\n", errdetail.type, errdetail.arg, errmsg))
   1064 				ltkd_fatal("Unable to queue socket write.\n");
   1065 		} else {
   1066 			if (ltkd_queue_sock_write(client, "res ok\n", -1)) {
   1067 				ltkd_fatal("Unable to queue socket write.\n");
   1068 			}
   1069 		}
   1070 		if (last)
   1071 			break;
   1072 	}
   1073 	if (sock->tokens.num_tokens > 0 && sock->tokens.tokens[0].text != sock->read) {
   1074 		memmove(sock->read, sock->tokens.tokens[0].text, sock->read + sock->read_len - sock->tokens.tokens[0].text);
   1075 		ptrdiff_t offset = sock->tokens.tokens[0].text - sock->read;
   1076 		/* Hmm, seems a bit ugly... */
   1077 		for (int i = 0; i < sock->tokens.num_tokens; i++) {
   1078 			sock->tokens.tokens[i].text -= offset;
   1079 		}
   1080 		sock->read_len -= offset;
   1081 		sock->read_cur -= offset;
   1082 	} else if (sock->tokens.num_tokens == 0) {
   1083 		sock->read_len = 0;
   1084 		sock->read_cur = 0;
   1085 	}
   1086 	return retval;
   1087 }
   1088 
   1089 LTK_GEN_LOG_FUNCS(ltkd, ltkd_log_msg, ltkd_cleanup)