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