commit 610c7a836cb111096f1ae31d5190a2964ba25310
parent 99531db6c1821736ff1a975ec9872fc033d94df7
Author: lumidify <nobody@lumidify.org>
Date:   Fri, 24 Jun 2022 17:06:10 +0200
Add sequence numbers to protocol
Diffstat:
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 = <k_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"
 		;;
 	*)