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