From 54adc01be4d544131fab917ddd835fdd1e8a40fd Mon Sep 17 00:00:00 2001 From: Adrian Chadd Date: Sun, 8 May 2022 14:04:21 -0700 Subject: [PATCH] [libami] document how the module loop works; start thinking about how to make it async * Yeah it'd be nice to just use libevent here, alas * Figure out how all the reading/writing works and comment it * Add a periodic function that'll be called every trip through the main loop; will turn it into something more formally periodic later. There's still lots to do here before I can actually schedule periodic events and the read()s are non-blocking. Give it time! --- libami/module.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/libami/module.c b/libami/module.c index 3579380..259e01b 100644 --- a/libami/module.c +++ b/libami/module.c @@ -8,6 +8,7 @@ #ifdef HAVE_UNISTD_H #include #endif +#include #include "libami.h" #include "module.h" @@ -24,6 +25,7 @@ Window md_root = None; static int md_int_len=0; static char *md_int_buf=NULL; void (*md_broker_func)(XEvent *, unsigned long); +void (*md_periodic_func)(void); void md_exit(int signal) { @@ -54,6 +56,12 @@ static int md_write(void *ptr, int len) return tot; } +/* + * Read from the input file descriptor until len; ,populate + * our buffer. + * + * Return total read, else -1 on error. + */ static int md_read(void *ptr, int len) { char *p=ptr; @@ -78,6 +86,10 @@ static int md_read(void *ptr, int len) return tot; } +/* + * Read in "len" bytes from the window manager command path + * into md_int_buf. + */ static int md_int_load(int len) { if(len>=md_int_len) { @@ -96,6 +108,13 @@ static struct md_queued_event { struct mcmd_event e; } *event_head=NULL, *event_tail=NULL; +/* + * Process queued XEvents from the window manager. + * + * The window manager pushes subscribed XEvents down to + * modules and this pulls them out of the queue and + * calls md_broker_func() on each of them. + */ void md_process_queued_events() { struct md_queued_event *e; @@ -107,6 +126,12 @@ void md_process_queued_events() } } +/* + * Enqueue an mcmd event into the event queue. + * + * This is called when there's an XEvent being queued + * from the window manager to the module. + */ static void md_enqueue(struct mcmd_event *e) { struct md_queued_event *qe=malloc(sizeof(struct md_queued_event)); @@ -122,6 +147,13 @@ static void md_enqueue(struct mcmd_event *e) } } +/* + * Read an async XEvent from the window manager. + * + * This is called by md_handle_input() to read an XEvent. + * The "I'm an Xevent" marker is ~len, so it's de-inverted + * and then a subsequent len field match must match it. + */ static int md_get_async(int len) { if(md_int_load(len)!=len) @@ -131,18 +163,30 @@ static int md_get_async(int len) return 1; } +/* + * Read input from the window manager. + * + * This reads two chunks - the size of the request, + * and then the request itself. + * + * Negative request lengths are treated special - they're + * treated as XEvents thrown into the input stream. + */ int md_handle_input() { int res; + /* Read the length of the request */ if(md_read(&res, sizeof(res))!=sizeof(res)) return -1; if(res>=0) { if(!res) return 0; + /* Read the command */ md_int_load(res); return 0; } else { + /* Negative length; treat as an XEvent */ res=~res; if(!res) return 0; @@ -150,6 +194,16 @@ int md_handle_input() } } +/* + * Send a command from the module back to the window manager. + * + * This sends a request up to the window manager and then reads the + * response to return. If asynchronous XEvents occur in the reply + * stream then those are enqueued via md_get_async(). + * + * If there is a response, buffer is set to a memory buffer containing it. + * It is thus up to the caller to free it. + */ int md_command(XID id, int cmd, void *data, int data_len, char **buffer) { int res; @@ -161,21 +215,38 @@ int md_command(XID id, int cmd, void *data, int data_len, char **buffer) mcmd.cmd = cmd; mcmd.len = data_len; + /* + * Send header, read response code. + */ if(md_write(&mcmd, sizeof(mcmd))!=sizeof(mcmd) || md_write(data, data_len)!=data_len || md_read(&res, sizeof(res))!=sizeof(res)) return -1; + /* + * If the response code is negative (well, less than -1) + * then its treated as an async XEvent. So, queue that + * and keep reading for the response code. + */ while(res<-1) { md_get_async(~res); if(md_read(&res, sizeof(res))!=sizeof(res)) return -1; } + + /* + * If the response code is >0, then allocate a buffer + * of a suitable size and read the response into the buffer. + */ if(res>0) { *buffer=malloc(res); if(md_read(*buffer, res)!=res) return -1; } + + /* + * Return the response size. + */ return res; } @@ -230,9 +301,43 @@ char *md_init(int argc, char *argv[]) return (argc>4? argv[4]:NULL); } -void md_main_loop() +void +md_main_loop() { - do md_process_queued_events(); while(md_handle_input()>=0); + fd_set readfds; + struct timeval tv; + int ret; + + /* + * For now I'm going to use select() for up to + * one second on the input FD, even though the + * FD is blocking. + * + * That way in the main loop we at least will + * get the chance to run the periodic function. + * + * A module can then for now set its own timer + * signal to run, which should interrupt this + * select. + */ + do { + FD_ZERO(&readfds); + FD_SET(md_in_fd, &readfds); + tv.tv_sec = 1; + tv.tv_usec = 0; + + ret = select(md_in_fd + 1, &readfds, NULL, NULL, &tv); + (void) ret; + + if (md_periodic_func != NULL) { + md_periodic_func(); + } + + /* Process async XEvent events that have been read */ + md_process_queued_events(); + + /* Loop over, reading input events */ + } while(md_handle_input()>=0); } int md_connection_number()