URI: 
       tltkd.c - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tltkd.c (58197B)
       ---
            1 /* FIXME: Figure out how to properly print window id */
            2 /* FIXME: error checking in tokenizer (is this necessary?) */
            3 /* FIXME: strip whitespace at end of lines in socket format */
            4 /*
            5  * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org>
            6  *
            7  * Permission to use, copy, modify, and/or distribute this software for any
            8  * purpose with or without fee is hereby granted, provided that the above
            9  * copyright notice and this permission notice appear in all copies.
           10  *
           11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
           12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
           13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
           14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
           15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
           16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
           17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
           18  */
           19 
           20 #include <time.h>
           21 #include <stdio.h>
           22 #include <fcntl.h>
           23 #include <errno.h>
           24 #include <stdlib.h>
           25 #include <string.h>
           26 #include <stdarg.h>
           27 #include <unistd.h>
           28 #include <signal.h>
           29 #include <stdint.h>
           30 #include <locale.h>
           31 #include <inttypes.h>
           32 
           33 #include <sys/un.h>
           34 #include <sys/stat.h>
           35 #include <sys/wait.h>
           36 #include <sys/types.h>
           37 #include <sys/select.h>
           38 #include <sys/socket.h>
           39 
           40 #include "ini.h"
           41 #include "khash.h"
           42 
           43 #include "graphics.h"
           44 #include "surface_cache.h"
           45 #include "theme.h"
           46 #include "memory.h"
           47 #include "color.h"
           48 #include "rect.h"
           49 #include "widget.h"
           50 #include "ltk.h"
           51 #include "util.h"
           52 #include "text.h"
           53 #include "grid.h"
           54 /* #include "draw.h" */
           55 #include "button.h"
           56 #include "entry.h"
           57 #include "label.h"
           58 #include "scrollbar.h"
           59 #include "box.h"
           60 #include "menu.h"
           61 #include "image.h"
           62 #include "image_widget.h"
           63 #include "macros.h"
           64 #include "config.h"
           65 
           66 #define MAX_WINDOW_FONT_SIZE 200
           67 
           68 #define MAX_SOCK_CONNS 20
           69 #define READ_BLK_SIZE 128
           70 #define WRITE_BLK_SIZE 128
           71 
           72 struct token_list {
           73         ltk_cmd_token *tokens;
           74         /* FIXME: size_t everywhere */
           75         int num_tokens;
           76         int num_alloc;
           77 };
           78 
           79 /* FIXME: switch to size_t */
           80 static struct ltk_sock_info {
           81         int fd;                    /* file descriptor for socket connection */
           82         int event_mask;            /* events to send to socket */
           83         char *read;                /* text read from socket */
           84         int read_len;              /* length of text in read buffer */
           85         int read_alloc;            /* size of read buffer */
           86         char *to_write;            /* text to be written to socket */
           87         int write_len;             /* length of text in write buffer */
           88         int write_cur;             /* length of text already written */
           89         int write_alloc;           /* size of write buffer */
           90         /* stuff for tokenizing */
           91         int in_token;              /* last read char is inside token */
           92         int offset;                /* offset from removing backslashes */
           93         int in_str;                /* last read char is inside string */
           94         int read_cur;              /* length of text already tokenized */
           95         int bs;                    /* last char was non-escaped backslash */
           96         struct token_list tokens;  /* current tokens */
           97         uint32_t last_seq;         /* sequence number of last request processed */
           98 } sockets[MAX_SOCK_CONNS];
           99 
          100 typedef struct {
          101         void (*callback)(void *);
          102         void *data;
          103         struct timespec repeat;
          104         struct timespec remaining;
          105         int id;
          106 } ltk_timer;
          107 
          108 static ltk_timer *timers = NULL;
          109 static size_t timers_num = 0;
          110 static size_t timers_alloc = 0;
          111 
          112 static int daemonize_flag = 1;
          113 
          114 static int ltk_mainloop(ltk_window *window);
          115 static char *get_sock_path(char *basedir, Window id);
          116 static FILE *open_log(char *dir);
          117 static void daemonize(void);
          118 static ltk_window *ltk_create_window(const char *title, int x, int y,
          119     unsigned int w, unsigned int h);
          120 static void ltk_destroy_window(ltk_window *window);
          121 static void ltk_redraw_window(ltk_window *window);
          122 static void ltk_window_other_event(ltk_window *window, ltk_event *event);
          123 static void ltk_handle_event(ltk_window *window, ltk_event *event);
          124 
          125 static void ltk_load_theme(ltk_window *window, const char *path);
          126 static void ltk_uninitialize_theme(ltk_window *window);
          127 static int ltk_ini_handler(void *window, const char *widget, const char *prop, const char *value);
          128 static int ltk_window_fill_theme_defaults(ltk_window *window);
          129 static int ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value);
          130 static void ltk_window_uninitialize_theme(ltk_window *window);
          131 
          132 static int read_sock(struct ltk_sock_info *sock);
          133 static int push_token(struct token_list *tl, char *token);
          134 static int read_sock(struct ltk_sock_info *sock);
          135 static int write_sock(struct ltk_sock_info *sock);
          136 static int tokenize_command(struct ltk_sock_info *sock);
          137 static int ltk_set_root_widget_cmd(ltk_window *window, ltk_cmd_token *tokens, int num_tokens, ltk_error *err);
          138 static int process_commands(ltk_window *window, int client);
          139 static int add_client(int fd);
          140 static int listen_sock(const char *sock_path);
          141 static int accept_sock(int listenfd);
          142 
          143 static short maxsocket = -1;
          144 static short running = 1;
          145 static short sock_write_available = 0;
          146 static char *ltk_dir = NULL;
          147 static FILE *ltk_logfile = NULL;
          148 static char *sock_path = NULL;
          149 /* Note: Most functions still take this explicitly because it wasn't
          150    global originally, but that's just the way it is. */
          151 static ltk_window *main_window = NULL;
          152 
          153 typedef struct {
          154         char *name;
          155         int (*ini_handler)(ltk_window *, const char *, const char *);
          156         int (*fill_theme_defaults)(ltk_window *);
          157         void (*uninitialize_theme)(ltk_window *);
          158         int (*register_keypress)(const char *, size_t, ltk_keypress_binding);
          159         int (*register_keyrelease)(const char *, size_t, ltk_keyrelease_binding);
          160         void (*cleanup)(void);
          161         int (*cmd)(ltk_window *, ltk_cmd_token *, size_t, ltk_error *);
          162 } ltk_widget_funcs;
          163 
          164 /* FIXME: use binary search when searching for the widget */
          165 ltk_widget_funcs widget_funcs[] = {
          166         {
          167                 .name = "box",
          168                 .ini_handler = NULL,
          169                 .fill_theme_defaults = NULL,
          170                 .uninitialize_theme = NULL,
          171                 .register_keypress = NULL,
          172                 .register_keyrelease = NULL,
          173                 .cleanup = NULL,
          174                 .cmd = &ltk_box_cmd
          175         },
          176         {
          177                 .name = "button",
          178                 .ini_handler = &ltk_button_ini_handler,
          179                 .fill_theme_defaults = &ltk_button_fill_theme_defaults,
          180                 .uninitialize_theme = &ltk_button_uninitialize_theme,
          181                 .register_keypress = NULL,
          182                 .register_keyrelease = NULL,
          183                 .cleanup = NULL,
          184                 .cmd = &ltk_button_cmd
          185         },
          186         {
          187                 .name = "entry",
          188                 .ini_handler = &ltk_entry_ini_handler,
          189                 .fill_theme_defaults = &ltk_entry_fill_theme_defaults,
          190                 .uninitialize_theme = &ltk_entry_uninitialize_theme,
          191                 .register_keypress = &ltk_entry_register_keypress,
          192                 .register_keyrelease = &ltk_entry_register_keyrelease,
          193                 .cleanup = &ltk_entry_cleanup,
          194                 .cmd = &ltk_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 = &ltk_grid_cmd
          205         },
          206         {
          207                 .name = "label",
          208                 .ini_handler = &ltk_label_ini_handler,
          209                 .fill_theme_defaults = &ltk_label_fill_theme_defaults,
          210                 .uninitialize_theme = &ltk_label_uninitialize_theme,
          211                 .register_keypress = NULL,
          212                 .register_keyrelease = NULL,
          213                 .cleanup = NULL,
          214                 .cmd = &ltk_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 = &ltk_image_widget_cmd
          225         },
          226         {
          227                 .name = "menu",
          228                 .ini_handler = &ltk_menu_ini_handler,
          229                 .fill_theme_defaults = &ltk_menu_fill_theme_defaults,
          230                 .uninitialize_theme = &ltk_menu_uninitialize_theme,
          231                 .register_keypress = NULL,
          232                 .register_keyrelease = NULL,
          233                 .cleanup = NULL,
          234                 .cmd = &ltk_menu_cmd
          235         },
          236         {
          237                 .name = "menuentry",
          238                 .ini_handler = &ltk_menuentry_ini_handler,
          239                 .fill_theme_defaults = &ltk_menuentry_fill_theme_defaults,
          240                 .uninitialize_theme = &ltk_menuentry_uninitialize_theme,
          241                 .register_keypress = NULL,
          242                 .register_keyrelease = NULL,
          243                 .cleanup = NULL,
          244                 .cmd = &ltk_menuentry_cmd
          245         },
          246         {
          247                 .name = "submenu",
          248                 .ini_handler = &ltk_submenu_ini_handler,
          249                 .fill_theme_defaults = &ltk_submenu_fill_theme_defaults,
          250                 .uninitialize_theme = &ltk_submenu_uninitialize_theme,
          251                 .register_keypress = NULL,
          252                 .register_keyrelease = NULL,
          253                 .cleanup = NULL,
          254                 .cmd = &ltk_menu_cmd
          255         },
          256         {
          257                 .name = "submenuentry",
          258                 .ini_handler = &ltk_submenuentry_ini_handler,
          259                 .fill_theme_defaults = &ltk_submenuentry_fill_theme_defaults,
          260                 .uninitialize_theme = &ltk_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 = &ltk_scrollbar_ini_handler,
          277                 .fill_theme_defaults = &ltk_scrollbar_fill_theme_defaults,
          278                 .uninitialize_theme = &ltk_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 = &ltk_widget_register_keypress,
          291                 .register_keyrelease = &ltk_widget_register_keyrelease,
          292                 .cleanup = &ltk_widget_cleanup,
          293                 .cmd = NULL
          294         },
          295         {
          296                 /* Handler for window theme. */
          297                 .name = "window",
          298                 .ini_handler = &ltk_window_ini_handler,
          299                 .fill_theme_defaults = &ltk_window_fill_theme_defaults,
          300                 .uninitialize_theme = &ltk_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 = &ltk_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 }