URI: 
       tAdd sequence numbers to protocol - 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
       ---
   DIR commit 610c7a836cb111096f1ae31d5190a2964ba25310
   DIR parent 99531db6c1821736ff1a975ec9872fc033d94df7
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Fri, 24 Jun 2022 17:06:10 +0200
       
       Add sequence numbers to protocol
       
       Diffstat:
         M socket_format.txt                   |      29 +++++++++++++++++++++++++----
         M src/err.c                           |       1 +
         M src/err.h                           |       1 +
         M src/ltk.h                           |       9 ---------
         M src/ltkc.c                          |      41 +++++++++++++++++++++++++------
         M src/ltkd.c                          |     143 +++++++++++++++++--------------
         M test.sh                             |       2 +-
         M testbox.sh                          |       4 ++--
       
       8 files changed, 140 insertions(+), 90 deletions(-)
       ---
   DIR diff --git a/socket_format.txt b/socket_format.txt
       t@@ -1,3 +1,21 @@
       +General:
       +
       +All requests, responses, errors, and events start with a sequence number.
       +This number starts at 0 and is incremented by the client with each request.
       +When the server sends a response, error, or event, it starts with the last
       +sequence number that the client sent. The client 'ltkc' adds sequence
       +numbers automatically (perhaps it should hide them in its output as well,
       +but I'm too lazy to implement that right now). A more advanced client could
       +use the numbers to properly check that requests really were received.
       +It isn't clear yet what should happen in the pathological case that the
       +uint32_t used to store the sequence number overflows before any of the
       +requests have been handled (i.e. it isn't clear anymore which request a
       +reply is for). I guess this could technically happen when running over a
       +broken connection or something, but I don't have a solution right now.
       +It doesn't seem like a very realistic scenario, though, considering that
       +all the requests/responses would need to be buffered somewhere, which
       +would be somewhat unrealistic considering the size of uint32_t.
       +
        Requests:
        
        <widget type> <widget id> <command> <args>
       t@@ -17,12 +35,15 @@ Essentially, the individual messages are separated by line
        breaks (\n), but line breaks within strings don't break the
        message.
        
       -Replies:
       +Responses:
        
        Not properly implemented yet.
       -The requests will probably have to include a sequence number eventually, which
       -the replies will also give back so it's possible to know when the appropriate
       -reply has been received.
       +Currently, all requests that don't generate errors just get the response
       +"<sequence> res ok". Of course, this will be changed once other response
       +types are available (e.g. get-text and others).
       +
       +It might be good to allow ignoring responses to avoid lots of useless traffic.
       +On the client side, the usage could be similar to XCB's checked/unchecked.
        
        Errors:
        
   DIR diff --git a/src/err.c b/src/err.c
       t@@ -14,6 +14,7 @@ static const char *errtable[] = {
                "Menu is not submenu",
                "Menu entry already contains submenu",
                "Invalid grid position",
       +        "Invalid sequence number",
        };
        
        #define LENGTH(X) (sizeof(X) / sizeof(X[0]))
   DIR diff --git a/src/err.h b/src/err.h
       t@@ -18,6 +18,7 @@ typedef enum {
                ERR_MENU_NOT_SUBMENU = 10,
                ERR_MENU_ENTRY_CONTAINS_SUBMENU = 11,
                ERR_GRID_INVALID_POSITION = 12,
       +        ERR_INVALID_SEQNUM = 13,
        } ltk_errtype;
        
        typedef struct {
   DIR diff --git a/src/ltk.h b/src/ltk.h
       t@@ -27,13 +27,6 @@ typedef enum {
                LTK_EVENT_MENU = 1 << 3
        } ltk_userevent_type;
        
       -struct ltk_event_queue {
       -        ltk_userevent_type event_type;
       -        char *data;
       -        struct ltk_event_queue *prev;
       -        struct ltk_event_queue *next;
       -};
       -
        /*
          Historical note concerning ltk_window: This code was originally copied
          from my previous attempt at creating a GUI library, which was meant to
       t@@ -67,8 +60,6 @@ struct ltk_window {
                ltk_rect rect;
                ltk_window_theme *theme;
                ltk_rect dirty_rect;
       -        struct ltk_event_queue *first_event;
       -        struct ltk_event_queue *last_event;
                /* FIXME: generic array */
                ltk_widget **popups;
                size_t popups_num;
   DIR diff --git a/src/ltkc.c b/src/ltkc.c
       t@@ -1,5 +1,5 @@
        /*
       - * Copyright (c) 2021 lumidify <nobody@lumidify.org>
       + * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
         *
         * Permission to use, copy, modify, and/or distribute this software for any
         * purpose with or without fee is hereby granted, provided that the above
       t@@ -22,6 +22,7 @@
        #include <unistd.h>
        #include <time.h>
        #include <errno.h>
       +#include <inttypes.h>
        #include <sys/select.h>
        #include <sys/socket.h>
        #include <sys/un.h>
       t@@ -29,6 +30,7 @@
        #include "memory.h"
        
        #define BLK_SIZE 128
       +char tmp_buf[BLK_SIZE];
        
        static struct {
                char *in_buffer;  /* text that is read from stdin and written to the socket */
       t@@ -44,6 +46,9 @@ static char *sock_path = NULL;
        static int sockfd = -1;
        
        int main(int argc, char *argv[]) {
       +        char num[12];
       +        int last_newline = 1;
       +        uint32_t seq = 0;
                int maxrfd, maxwfd;
                int infd = fileno(stdin);
                int outfd = fileno(stdout);
       t@@ -136,18 +141,38 @@ int main(int argc, char *argv[]) {
                        }
        
                        if (FD_ISSET(infd, &rfds)) {
       -                        ltk_grow_string(&io_buffers.in_buffer,
       -                                        &io_buffers.in_alloc,
       -                                        io_buffers.in_len + BLK_SIZE);
       -                        int nread = read(infd,
       -                                         io_buffers.in_buffer + io_buffers.in_len,
       -                                         BLK_SIZE);
       +                        int nread = read(infd, tmp_buf, BLK_SIZE);
                                if (nread < 0) {
                                        return 2;
                                } else if (nread == 0) {
                                        FD_CLR(infd, &rallfds);
                                } else {
       -                                io_buffers.in_len += nread;
       +                                for (int i = 0; i < nread; i++) {
       +                                        if (last_newline) {
       +                                                int numlen = snprintf(num, sizeof(num), "%"PRIu32" ", seq);
       +                                                if (numlen < 0 || (unsigned)numlen >= sizeof(num))
       +                                                        ltk_fatal("There's a bug in the universe.\n");
       +                                                ltk_grow_string(
       +                                                    &io_buffers.in_buffer,
       +                                                    &io_buffers.in_alloc,
       +                                                    io_buffers.in_len + numlen
       +                                                );
       +                                                memcpy(io_buffers.in_buffer + io_buffers.in_len, num, numlen);
       +                                                io_buffers.in_len += numlen;
       +                                                last_newline = 0;
       +                                                seq++;
       +                                        }
       +                                        if (tmp_buf[i] == '\n')
       +                                                last_newline = 1;
       +                                        if (io_buffers.in_len == io_buffers.in_alloc) {
       +                                                ltk_grow_string(
       +                                                    &io_buffers.in_buffer,
       +                                                    &io_buffers.in_alloc,
       +                                                    io_buffers.in_len + 1
       +                                                );
       +                                        }
       +                                        io_buffers.in_buffer[io_buffers.in_len++] = tmp_buf[i];
       +                                }
                                }
                        }
        
   DIR diff --git a/src/ltkd.c b/src/ltkd.c
       t@@ -29,6 +29,7 @@
        #include <unistd.h>
        #include <signal.h>
        #include <stdint.h>
       +#include <inttypes.h>
        
        #include <sys/un.h>
        #include <sys/stat.h>
       t@@ -87,6 +88,7 @@ static struct ltk_sock_info {
                int read_cur;              /* length of text already tokenized */
                int bs;                    /* last char was non-escaped backslash */
                struct token_list tokens;  /* current tokens */
       +        uint32_t last_seq;         /* sequence number of last request processed */
        } sockets[MAX_SOCK_CONNS];
        
        typedef struct {
       t@@ -370,26 +372,6 @@ ltk_mainloop(ltk_window *window) {
                                window->dirty_rect.w = 0;
                                window->dirty_rect.h = 0;
                        }
       -
       -                if (window->last_event && running) {
       -                        struct ltk_event_queue *cur = window->last_event;
       -                        struct ltk_event_queue *last;
       -                        do {
       -                                int event_len = strlen(cur->data);
       -                                for (int i = 0; i <= maxsocket; i++) {
       -                                        if (sockets[i].fd != -1 && sockets[i].event_mask & cur->event_type) {
       -                                                if (ltk_queue_sock_write(i, cur->data, event_len) < 0)
       -                                                        ltk_fatal_errno("Unable to queue event.\n");
       -                                        }
       -                                }
       -                                ltk_free(cur->data);
       -                                last = cur;
       -                                cur = cur->prev;
       -                                ltk_free(last);
       -                        } while (cur);
       -                        window->first_event = window->last_event = NULL;
       -                } 
       -
                }
        
                ltk_cleanup();
       t@@ -870,7 +852,6 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int 
                window->surface_cache = ltk_surface_cache_create(window->renderdata);
        
                window->other_event = &ltk_window_other_event;
       -        window->first_event = window->last_event = NULL;
        
                window->rect.w = w;
                window->rect.h = h;
       t@@ -1045,6 +1026,7 @@ add_client(int fd) {
                                sockets[i].read_cur = 0;
                                sockets[i].bs = 0;
                                sockets[i].tokens.num_tokens = 0;
       +                        sockets[i].last_seq = 0;
                                return i;
                        }
                }
       t@@ -1187,16 +1169,22 @@ int
        ltk_queue_sock_write(int client, const char *str, int len) {
                if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
                        return 1;
       +        /* this is always large enough to hold a uint32_t and " \0" */
       +        char num[12];
                struct ltk_sock_info *sock = &sockets[client];
                move_write_pos(sock);
                if (len < 0)
                        len = strlen(str);
        
       -        if (sock->write_alloc - sock->write_len < len)
       -                ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len);
       +        int numlen = snprintf(num, sizeof(num), "%"PRIu32" ", sock->last_seq);
       +        if (numlen < 0 || (unsigned)numlen >= sizeof(num))
       +                ltk_fatal("There's a bug in the universe.\n");
       +        if (sock->write_alloc - sock->write_len < len + numlen)
       +                ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len + numlen);
        
       -        (void)strncpy(sock->to_write + sock->write_len, str, len);
       -        sock->write_len += len;
       +        (void)strncpy(sock->to_write + sock->write_len, num, numlen);
       +        (void)strncpy(sock->to_write + sock->write_len + numlen, str, len);
       +        sock->write_len += len + numlen;
        
                sock_write_available = 1;
        
       t@@ -1208,7 +1196,8 @@ ltk_queue_sock_write_fmt(int client, const char *fmt, ...) {
                if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
                        return 1;
                struct ltk_sock_info *sock = &sockets[client];
       -        move_write_pos(sock);
       +        /* just to print the sequence number */
       +        ltk_queue_sock_write(client, "", 0);
                va_list args;
                va_start(args, fmt);
                int len = vsnprintf(sock->to_write + sock->write_len, sock->write_alloc - sock->write_len, fmt, args);
       t@@ -1397,58 +1386,80 @@ process_commands(ltk_window *window, int client) {
                int err;
                int retval = 0;
                int last = 0;
       +        uint32_t seq;
       +        const char *errstr;
                while (!tokenize_command(sock)) {
                        err = 0;
                        tokens = sock->tokens.tokens;
                        num_tokens = sock->tokens.num_tokens;
       -                if (num_tokens < 1)
       -                        continue;
       -                if (strcmp(tokens[0], "grid") == 0) {
       -                        err = ltk_grid_cmd(window, tokens, num_tokens, &errdetail);
       -                } else if (strcmp(tokens[0], "box") == 0) {
       -                        err = ltk_box_cmd(window, tokens, num_tokens, &errdetail);
       -                } else if (strcmp(tokens[0], "button") == 0) {
       -                        err = ltk_button_cmd(window, tokens, num_tokens, &errdetail);
       -                } else if (strcmp(tokens[0], "label") == 0) {
       -                        err = ltk_label_cmd(window, tokens, num_tokens, &errdetail);
       -                } else if (strcmp(tokens[0], "menu") == 0) {
       -                        err = ltk_menu_cmd(window, tokens, num_tokens, &errdetail);
       -                } else if (strcmp(tokens[0], "submenu") == 0) {
       -                        err = ltk_menu_cmd(window, tokens, num_tokens, &errdetail);
       -                } else if (strcmp(tokens[0], "menuentry") == 0) {
       -                        err = ltk_menuentry_cmd(window, tokens, num_tokens, &errdetail);
       -                } else if (strcmp(tokens[0], "set-root-widget") == 0) {
       -                        err = ltk_set_root_widget_cmd(window, tokens, num_tokens, &errdetail);
       -                } else if (strcmp(tokens[0], "quit") == 0) {
       -                        ltk_quit(window);
       -                } else if (strcmp(tokens[0], "destroy") == 0) {
       -                        err = ltk_widget_destroy_cmd(window, tokens, num_tokens, &errdetail);
       -                } else if (strncmp(tokens[0], "mask", 4) == 0) {
       -                        err = handle_mask_command(client, tokens, num_tokens, &errdetail);
       -                } else if (strcmp(tokens[0], "event-unlock") == 0) {
       -                        if (num_tokens != 2) {
       -                                errdetail.type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                if (num_tokens < 2) {
       +                        errdetail.type = ERR_INVALID_COMMAND;
       +                        errdetail.arg = -1;
       +                        err = 1;
       +                } else {
       +                        seq = (uint32_t)ltk_strtonum(tokens[0], 0, UINT32_MAX, &errstr);
       +                        tokens++;
       +                        num_tokens--;
       +                        if (errstr) {
       +                                errdetail.type = ERR_INVALID_SEQNUM;
       +                                errdetail.arg = -1;
                                        err = 1;
       -                        } else if (strcmp(tokens[1], "true") == 0) {
       -                                retval = 1;
       -                        } else if (strcmp(tokens[1], "false") == 0) {
       -                                retval = -1;
       +                                seq = sock->last_seq;
       +                        } else if (strcmp(tokens[0], "grid") == 0) {
       +                                err = ltk_grid_cmd(window, tokens, num_tokens, &errdetail);
       +                        } else if (strcmp(tokens[0], "box") == 0) {
       +                                err = ltk_box_cmd(window, tokens, num_tokens, &errdetail);
       +                        } else if (strcmp(tokens[0], "button") == 0) {
       +                                err = ltk_button_cmd(window, tokens, num_tokens, &errdetail);
       +                        } else if (strcmp(tokens[0], "label") == 0) {
       +                                err = ltk_label_cmd(window, tokens, num_tokens, &errdetail);
       +                        } else if (strcmp(tokens[0], "menu") == 0) {
       +                                err = ltk_menu_cmd(window, tokens, num_tokens, &errdetail);
       +                        } else if (strcmp(tokens[0], "submenu") == 0) {
       +                                err = ltk_menu_cmd(window, tokens, num_tokens, &errdetail);
       +                        } else if (strcmp(tokens[0], "menuentry") == 0) {
       +                                err = ltk_menuentry_cmd(window, tokens, num_tokens, &errdetail);
       +                        } else if (strcmp(tokens[0], "set-root-widget") == 0) {
       +                                err = ltk_set_root_widget_cmd(window, tokens, num_tokens, &errdetail);
       +                        } else if (strcmp(tokens[0], "quit") == 0) {
       +                                ltk_quit(window);
       +                                last = 1;
       +                        } else if (strcmp(tokens[0], "destroy") == 0) {
       +                                err = ltk_widget_destroy_cmd(window, tokens, num_tokens, &errdetail);
       +                        } else if (strncmp(tokens[0], "mask", 4) == 0) {
       +                                err = handle_mask_command(client, tokens, num_tokens, &errdetail);
       +                        } else if (strcmp(tokens[0], "event-unlock") == 0) {
       +                                if (num_tokens != 2) {
       +                                        errdetail.type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
       +                                        errdetail.arg = -1;
       +                                        err = 1;
       +                                } else if (strcmp(tokens[1], "true") == 0) {
       +                                        retval = 1;
       +                                } else if (strcmp(tokens[1], "false") == 0) {
       +                                        retval = -1;
       +                                } else {
       +                                        err = 1;
       +                                        errdetail.type = ERR_INVALID_ARGUMENT;
       +                                        errdetail.arg = -1;
       +                                        errdetail.arg = 1;
       +                                }
       +                                last = 1;
                                } else {
       +                                errdetail.type = ERR_INVALID_COMMAND;
       +                                errdetail.arg = -1;
                                        err = 1;
       -                                errdetail.type = ERR_INVALID_ARGUMENT;
       -                                errdetail.arg = 1;
                                }
       -                        last = 1;
       -                } else {
       -                        errdetail.type = ERR_INVALID_COMMAND;
       -                        errdetail.arg = -1;
       -                        err = 1;
       +                        sock->tokens.num_tokens = 0;
       +                        sock->last_seq = seq;
                        }
       -                sock->tokens.num_tokens = 0;
                        if (err) {
                                const char *errmsg = errtype_to_string(errdetail.type);
       -                        if (ltk_queue_sock_write_fmt(client, "err %d %d \"%s\"\n", errdetail.type, errdetail.arg, errmsg) < 0)
       +                        if (ltk_queue_sock_write_fmt(client, "err %d %d \"%s\"\n", errdetail.type, errdetail.arg, errmsg))
       +                                ltk_fatal("Unable to queue socket write.\n");
       +                } else {
       +                        if (ltk_queue_sock_write(client, "res ok\n", -1)) {
                                        ltk_fatal("Unable to queue socket write.\n");
       +                        }
                        }
                        if (last)
                                break;
   DIR diff --git a/test.sh b/test.sh
       t@@ -16,7 +16,7 @@ fi
        cat test.gui | ./src/ltkc $ltk_id | while read cmd
        do
                case "$cmd" in
       -        "event btn1 button press")
       +        *"event btn1 button press")
                        echo "quit"
                        ;;
                *)
   DIR diff --git a/testbox.sh b/testbox.sh
       t@@ -16,10 +16,10 @@ mask-add btn0 button press"
        echo "$cmds" | ./src/ltkc $ltk_id | while read cmd
        do
                case "$cmd" in
       -        "event exit_btn button press")
       +        *"event exit_btn button press")
                        echo "quit"
                        ;;
       -        "event btn0 button press")
       +        *"event btn0 button press")
                        echo "button bla create \"safhaskfldshk\"\nbox box1 add bla w"
                        ;;
                *)