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"
;;
*)