ltkd.c - ltk - GUI toolkit for X11 (WIP)
HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
DIR Log
DIR Files
DIR Refs
DIR README
DIR 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 = <kd_box_cmd
125 },
126 {
127 .name = "button",
128 .cmd = <kd_button_cmd
129 },
130 {
131 .name = "entry",
132 .cmd = <kd_entry_cmd
133 },
134 {
135 .name = "grid",
136 .cmd = <kd_grid_cmd
137 },
138 {
139 .name = "label",
140 .cmd = <kd_label_cmd
141 },
142 {
143 .name = "image",
144 .cmd = <kd_image_widget_cmd
145 },
146 {
147 .name = "menu",
148 .cmd = <kd_menu_cmd
149 },
150 {
151 .name = "menuentry",
152 .cmd = <kd_menuentry_cmd
153 },
154 {
155 .name = "submenu",
156 .cmd = <kd_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, <kd_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)