ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit 610c7a836cb111096f1ae31d5190a2964ba25310
parent 99531db6c1821736ff1a975ec9872fc033d94df7
Author: lumidify <nobody@lumidify.org>
Date:   Fri, 24 Jun 2022 17:06:10 +0200

Add sequence numbers to protocol

Diffstat:
Msocket_format.txt | 29+++++++++++++++++++++++++----
Msrc/err.c | 1+
Msrc/err.h | 1+
Msrc/ltk.h | 9---------
Msrc/ltkc.c | 41+++++++++++++++++++++++++++++++++--------
Msrc/ltkd.c | 143+++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mtest.sh | 2+-
Mtestbox.sh | 4++--
8 files changed, 140 insertions(+), 90 deletions(-)

diff --git a/socket_format.txt b/socket_format.txt @@ -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> @@ -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: diff --git a/src/err.c b/src/err.c @@ -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])) diff --git a/src/err.h b/src/err.h @@ -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 { diff --git a/src/ltk.h b/src/ltk.h @@ -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 @@ -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; diff --git a/src/ltkc.c b/src/ltkc.c @@ -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 @@ -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> @@ -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 */ @@ -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); @@ -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]; + } } } diff --git a/src/ltkd.c b/src/ltkd.c @@ -29,6 +29,7 @@ #include <unistd.h> #include <signal.h> #include <stdint.h> +#include <inttypes.h> #include <sys/un.h> #include <sys/stat.h> @@ -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 { @@ -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(); @@ -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; @@ -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; } } @@ -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; @@ -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); @@ -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; diff --git a/test.sh b/test.sh @@ -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" ;; *) diff --git a/testbox.sh b/testbox.sh @@ -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" ;; *)