tltkd.c - ltk - Socket-based GUI for X11 (WIP)
HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
tltkd.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 = <k_box_cmd
175 },
176 {
177 .name = "button",
178 .ini_handler = <k_button_ini_handler,
179 .fill_theme_defaults = <k_button_fill_theme_defaults,
180 .uninitialize_theme = <k_button_uninitialize_theme,
181 .register_keypress = NULL,
182 .register_keyrelease = NULL,
183 .cleanup = NULL,
184 .cmd = <k_button_cmd
185 },
186 {
187 .name = "entry",
188 .ini_handler = <k_entry_ini_handler,
189 .fill_theme_defaults = <k_entry_fill_theme_defaults,
190 .uninitialize_theme = <k_entry_uninitialize_theme,
191 .register_keypress = <k_entry_register_keypress,
192 .register_keyrelease = <k_entry_register_keyrelease,
193 .cleanup = <k_entry_cleanup,
194 .cmd = <k_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 = <k_grid_cmd
205 },
206 {
207 .name = "label",
208 .ini_handler = <k_label_ini_handler,
209 .fill_theme_defaults = <k_label_fill_theme_defaults,
210 .uninitialize_theme = <k_label_uninitialize_theme,
211 .register_keypress = NULL,
212 .register_keyrelease = NULL,
213 .cleanup = NULL,
214 .cmd = <k_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 = <k_image_widget_cmd
225 },
226 {
227 .name = "menu",
228 .ini_handler = <k_menu_ini_handler,
229 .fill_theme_defaults = <k_menu_fill_theme_defaults,
230 .uninitialize_theme = <k_menu_uninitialize_theme,
231 .register_keypress = NULL,
232 .register_keyrelease = NULL,
233 .cleanup = NULL,
234 .cmd = <k_menu_cmd
235 },
236 {
237 .name = "menuentry",
238 .ini_handler = <k_menuentry_ini_handler,
239 .fill_theme_defaults = <k_menuentry_fill_theme_defaults,
240 .uninitialize_theme = <k_menuentry_uninitialize_theme,
241 .register_keypress = NULL,
242 .register_keyrelease = NULL,
243 .cleanup = NULL,
244 .cmd = <k_menuentry_cmd
245 },
246 {
247 .name = "submenu",
248 .ini_handler = <k_submenu_ini_handler,
249 .fill_theme_defaults = <k_submenu_fill_theme_defaults,
250 .uninitialize_theme = <k_submenu_uninitialize_theme,
251 .register_keypress = NULL,
252 .register_keyrelease = NULL,
253 .cleanup = NULL,
254 .cmd = <k_menu_cmd
255 },
256 {
257 .name = "submenuentry",
258 .ini_handler = <k_submenuentry_ini_handler,
259 .fill_theme_defaults = <k_submenuentry_fill_theme_defaults,
260 .uninitialize_theme = <k_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 = <k_scrollbar_ini_handler,
277 .fill_theme_defaults = <k_scrollbar_fill_theme_defaults,
278 .uninitialize_theme = <k_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 = <k_widget_register_keypress,
291 .register_keyrelease = <k_widget_register_keyrelease,
292 .cleanup = <k_widget_cleanup,
293 .cmd = NULL
294 },
295 {
296 /* Handler for window theme. */
297 .name = "window",
298 .ini_handler = <k_window_ini_handler,
299 .fill_theme_defaults = <k_window_fill_theme_defaults,
300 .uninitialize_theme = <k_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 = <k_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 }