ltkd.c (58197B)
1 /* FIXME: Figure out how to properly print window id */ 2 /* FIXME: error checking in tokenizer (is this necessary?) */ 3 /* FIXME: strip whitespace at end of lines in socket format */ 4 /* 5 * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org> 6 * 7 * Permission to use, copy, modify, and/or distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <time.h> 21 #include <stdio.h> 22 #include <fcntl.h> 23 #include <errno.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <stdarg.h> 27 #include <unistd.h> 28 #include <signal.h> 29 #include <stdint.h> 30 #include <locale.h> 31 #include <inttypes.h> 32 33 #include <sys/un.h> 34 #include <sys/stat.h> 35 #include <sys/wait.h> 36 #include <sys/types.h> 37 #include <sys/select.h> 38 #include <sys/socket.h> 39 40 #include "ini.h" 41 #include "khash.h" 42 43 #include "graphics.h" 44 #include "surface_cache.h" 45 #include "theme.h" 46 #include "memory.h" 47 #include "color.h" 48 #include "rect.h" 49 #include "widget.h" 50 #include "ltk.h" 51 #include "util.h" 52 #include "text.h" 53 #include "grid.h" 54 /* #include "draw.h" */ 55 #include "button.h" 56 #include "entry.h" 57 #include "label.h" 58 #include "scrollbar.h" 59 #include "box.h" 60 #include "menu.h" 61 #include "image.h" 62 #include "image_widget.h" 63 #include "macros.h" 64 #include "config.h" 65 66 #define MAX_WINDOW_FONT_SIZE 200 67 68 #define MAX_SOCK_CONNS 20 69 #define READ_BLK_SIZE 128 70 #define WRITE_BLK_SIZE 128 71 72 struct token_list { 73 ltk_cmd_token *tokens; 74 /* FIXME: size_t everywhere */ 75 int num_tokens; 76 int num_alloc; 77 }; 78 79 /* FIXME: switch to size_t */ 80 static struct ltk_sock_info { 81 int fd; /* file descriptor for socket connection */ 82 int event_mask; /* events to send to socket */ 83 char *read; /* text read from socket */ 84 int read_len; /* length of text in read buffer */ 85 int read_alloc; /* size of read buffer */ 86 char *to_write; /* text to be written to socket */ 87 int write_len; /* length of text in write buffer */ 88 int write_cur; /* length of text already written */ 89 int write_alloc; /* size of write buffer */ 90 /* stuff for tokenizing */ 91 int in_token; /* last read char is inside token */ 92 int offset; /* offset from removing backslashes */ 93 int in_str; /* last read char is inside string */ 94 int read_cur; /* length of text already tokenized */ 95 int bs; /* last char was non-escaped backslash */ 96 struct token_list tokens; /* current tokens */ 97 uint32_t last_seq; /* sequence number of last request processed */ 98 } sockets[MAX_SOCK_CONNS]; 99 100 typedef struct { 101 void (*callback)(void *); 102 void *data; 103 struct timespec repeat; 104 struct timespec remaining; 105 int id; 106 } ltk_timer; 107 108 static ltk_timer *timers = NULL; 109 static size_t timers_num = 0; 110 static size_t timers_alloc = 0; 111 112 static int daemonize_flag = 1; 113 114 static int ltk_mainloop(ltk_window *window); 115 static char *get_sock_path(char *basedir, Window id); 116 static FILE *open_log(char *dir); 117 static void daemonize(void); 118 static ltk_window *ltk_create_window(const char *title, int x, int y, 119 unsigned int w, unsigned int h); 120 static void ltk_destroy_window(ltk_window *window); 121 static void ltk_redraw_window(ltk_window *window); 122 static void ltk_window_other_event(ltk_window *window, ltk_event *event); 123 static void ltk_handle_event(ltk_window *window, ltk_event *event); 124 125 static void ltk_load_theme(ltk_window *window, const char *path); 126 static void ltk_uninitialize_theme(ltk_window *window); 127 static int ltk_ini_handler(void *window, const char *widget, const char *prop, const char *value); 128 static int ltk_window_fill_theme_defaults(ltk_window *window); 129 static int ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value); 130 static void ltk_window_uninitialize_theme(ltk_window *window); 131 132 static int read_sock(struct ltk_sock_info *sock); 133 static int push_token(struct token_list *tl, char *token); 134 static int read_sock(struct ltk_sock_info *sock); 135 static int write_sock(struct ltk_sock_info *sock); 136 static int tokenize_command(struct ltk_sock_info *sock); 137 static int ltk_set_root_widget_cmd(ltk_window *window, ltk_cmd_token *tokens, int num_tokens, ltk_error *err); 138 static int process_commands(ltk_window *window, int client); 139 static int add_client(int fd); 140 static int listen_sock(const char *sock_path); 141 static int accept_sock(int listenfd); 142 143 static short maxsocket = -1; 144 static short running = 1; 145 static short sock_write_available = 0; 146 static char *ltk_dir = NULL; 147 static FILE *ltk_logfile = NULL; 148 static char *sock_path = NULL; 149 /* Note: Most functions still take this explicitly because it wasn't 150 global originally, but that's just the way it is. */ 151 static ltk_window *main_window = NULL; 152 153 typedef struct { 154 char *name; 155 int (*ini_handler)(ltk_window *, const char *, const char *); 156 int (*fill_theme_defaults)(ltk_window *); 157 void (*uninitialize_theme)(ltk_window *); 158 int (*register_keypress)(const char *, size_t, ltk_keypress_binding); 159 int (*register_keyrelease)(const char *, size_t, ltk_keyrelease_binding); 160 void (*cleanup)(void); 161 int (*cmd)(ltk_window *, ltk_cmd_token *, size_t, ltk_error *); 162 } ltk_widget_funcs; 163 164 /* FIXME: use binary search when searching for the widget */ 165 ltk_widget_funcs widget_funcs[] = { 166 { 167 .name = "box", 168 .ini_handler = NULL, 169 .fill_theme_defaults = NULL, 170 .uninitialize_theme = NULL, 171 .register_keypress = NULL, 172 .register_keyrelease = NULL, 173 .cleanup = NULL, 174 .cmd = <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 }