diff --git a/data/keyd-application-mapper.1.gz b/data/keyd-application-mapper.1.gz index 30aaa22..37fdccb 100644 Binary files a/data/keyd-application-mapper.1.gz and b/data/keyd-application-mapper.1.gz differ diff --git a/data/keyd.1.gz b/data/keyd.1.gz index 8985734..93cee27 100644 Binary files a/data/keyd.1.gz and b/data/keyd.1.gz differ diff --git a/docs/keyd.scdoc b/docs/keyd.scdoc index 6d16018..55090c2 100644 --- a/docs/keyd.scdoc +++ b/docs/keyd.scdoc @@ -11,11 +11,14 @@ keyd(1) # COMMANDS *monitor* - Print key events in real time. Useful for debugging. + Print key events. Useful for discovering key names and debugging. *bind [...]* Add the supplied bindings to all active configs. See _Bindings_ for details. +*listen* + Print layer state changes of the running keyd daemon to stdout. Useful for scripting. + *list-keys* List valid key names. diff --git a/src/daemon.c b/src/daemon.c index f88c17a..2da469c 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -17,6 +17,9 @@ static size_t nr_devices; static uint8_t keystate[256]; +static int listeners[32]; +static size_t nr_listeners = 0; + static void free_configs() { struct config_ent *ent = configs; @@ -53,6 +56,57 @@ static void send_key(uint8_t code, uint8_t state) vkbd_send_key(vkbd, code, state); } +static void add_listener(int con) +{ + struct timeval tv; + + /* + * In order to avoid blocking the main event loop, allow up to 50ms for + * slow clients to relieve back pressure before dropping them. + */ + tv.tv_usec = 50000; + tv.tv_sec = 0; + + if (nr_listeners == ARRAY_SIZE(listeners)) { + char s[] = "Max listeners exceeded\n"; + xwrite(con, &s, sizeof s); + + close(con); + return; + } + + setsockopt(con, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); + + listeners[nr_listeners++] = con; +} + +static void layer_observer(const char *name, int state) +{ + if (!nr_listeners) + return; + + char buf[MAX_LAYER_NAME_LEN+2]; + ssize_t bufsz = snprintf(buf, sizeof(buf), "%c%s\n", state ? '+' : '-', name); + size_t i; + + int keep[ARRAY_SIZE(listeners)]; + size_t n = 0; + + for (i = 0; i < nr_listeners; i++) { + ssize_t nw = write(listeners[i], buf, bufsz); + + if (nw == bufsz) + keep[n++] = listeners[i]; + else + close(listeners[i]); + } + + if (n != nr_listeners) { + nr_listeners = n; + memcpy(listeners, keep, n * sizeof(int)); + } +} + static void load_configs() { DIR *dh = opendir(CONFIG_DIR); @@ -82,7 +136,7 @@ static void load_configs() if (config_parse(&ent->config, path)) die("failed to parse %s", path); - ent->kbd = new_keyboard(&ent->config, send_key); + ent->kbd = new_keyboard(&ent->config, send_key, layer_observer); ent->next = configs; configs = ent; } @@ -154,10 +208,36 @@ static void reload() clear_vkbd(); } +static void send_success(int con) +{ + struct ipc_message msg; + + msg.type = IPC_SUCCESS;; + msg.sz = sprintf(msg.data, "Success"); + + xwrite(con, &msg, sizeof msg); + close(con); +} + +static void send_fail(int con, const char *fmt, ...) +{ + struct ipc_message msg; + va_list args; + + va_start(args, fmt); + + msg.type = IPC_FAIL; + msg.sz = vsnprintf(msg.data, sizeof(msg.data), fmt, args); + + xwrite(con, &msg, sizeof msg); + close(con); + + va_end(args); +} + static void handle_client(int con) { struct ipc_message msg; - size_t sz; xread(con, &msg, sizeof msg); @@ -166,36 +246,31 @@ static void handle_client(int con) int success; case IPC_RELOAD: - msg.type = IPC_SUCCESS;; - strcpy(msg.data, "Success"); - msg.sz = strlen(msg.data); - reload(); - xwrite(con, &msg, sizeof msg); + send_success(con); + break; + case IPC_LAYER_LISTEN: + add_listener(con); break; case IPC_BIND: success = 0; - msg.data[msg.sz] = 0; + for (ent = configs; ent; ent = ent->next) { if (!kbd_eval(ent->kbd, msg.data)) success = 1; } - if (success) { - msg.type = IPC_SUCCESS; - msg.sz = 0; - } else { - msg.type = IPC_FAIL; - msg.sz = snprintf(msg.data, sizeof msg.data, "ERROR: %s", errstr); - } + if (success) + send_success(con); + else + send_fail(con, "%s", errstr); + - xwrite(con, &msg, sizeof msg); break; default: + send_fail(con, "Unknown command"); break; } - - close(con); } static void remove_device(struct device *dev) @@ -307,6 +382,7 @@ int run_daemon(int argc, char *argv[]) setvbuf(stdout, NULL, _IOLBF, 0); setvbuf(stderr, NULL, _IOLBF, 0); + nice(-20); evloop_add_fd(ipcfd); diff --git a/src/keyboard.c b/src/keyboard.c index 4d1540e..f942b99 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -309,6 +309,9 @@ static void deactivate_layer(struct keyboard *kbd, int idx) assert(kbd->layer_state[idx].active > 0); kbd->layer_state[idx].active--; + + if (kbd->layer_observer) + kbd->layer_observer(kbd->config.layers[idx].name, 0); } /* @@ -323,6 +326,9 @@ static void activate_layer(struct keyboard *kbd, uint8_t code, int idx) kbd->layer_state[idx].activation_time = get_time(); kbd->layer_state[idx].active++; kbd->last_layer_code = code; + + if (kbd->layer_observer) + kbd->layer_observer(kbd->config.layers[idx].name, 1); } static void execute_command(const char *cmd) @@ -634,7 +640,9 @@ static long process_descriptor(struct keyboard *kbd, uint8_t code, return timeout; } -struct keyboard *new_keyboard(struct config *config, void (*sink) (uint8_t, uint8_t)) +struct keyboard *new_keyboard(struct config *config, + void (*sink) (uint8_t, uint8_t), + void (*layer_observer)(const char *name, int state)) { size_t i; struct keyboard *kbd; @@ -669,6 +677,7 @@ struct keyboard *new_keyboard(struct config *config, void (*sink) (uint8_t, uint } kbd->output = sink; + kbd->layer_observer = layer_observer; return kbd; } diff --git a/src/keyboard.h b/src/keyboard.h index b7f8800..a98344d 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -63,9 +63,12 @@ struct keyboard { uint8_t keystate[256]; void (*output) (uint8_t code, uint8_t state); + void (*layer_observer) (const char *layer, int state); }; -struct keyboard *new_keyboard(struct config *config, void (*sink) (uint8_t, uint8_t)); +struct keyboard *new_keyboard(struct config *config, + void (*sink) (uint8_t, uint8_t), + void (*layer_observer)(const char *name, int state)); long kbd_process_key_event(struct keyboard *kbd, uint8_t code, int pressed); int kbd_eval(struct keyboard *kbd, const char *exp); diff --git a/src/keyd.c b/src/keyd.c index f7c8606..af412f5 100644 --- a/src/keyd.c +++ b/src/keyd.c @@ -4,7 +4,6 @@ * © 2019 Raheman Vaiya (see also: LICENSE). */ - #include "keyd.h" static int ipc_exec(int type, const char *data, size_t sz) @@ -51,6 +50,7 @@ static int help(int argc, char *argv[]) " monitor Print key events in real time.\n" " list-keys Print a list of valid key names.\n" " reload Trigger a reload .\n" + " listen Print layer state changes of the running keyd daemon to stdout.\n" " bind [...] Add the supplied bindings to all loaded configs.\n" "Options:\n" " -v, --version Print the current version and exit.\n" @@ -93,6 +93,31 @@ static int add_binding(int argc, char *argv[]) return ret; } +static int layer_listen(int argc, char *argv[]) +{ + struct ipc_message msg; + + int con = ipc_connect(); + + if (con < 0) { + perror("connect"); + exit(-1); + } + + msg.type = IPC_LAYER_LISTEN; + xwrite(con, &msg, sizeof msg); + + while (1) { + char buf[512]; + ssize_t sz = read(con, buf, sizeof buf); + + if (sz <= 0) + return -1; + + write(1, buf, sz); + } +} + static int reload() { ipc_exec(IPC_RELOAD, NULL, 0); @@ -114,6 +139,8 @@ struct { {"monitor", "-m", "--monitor", monitor}, {"bind", "-e", "--expression", add_binding}, + {"listen", "", "", layer_listen}, + {"reload", "", "", reload}, {"list-keys", "", "", list_keys}, }; diff --git a/src/keyd.h b/src/keyd.h index d966e21..efe443a 100644 --- a/src/keyd.h +++ b/src/keyd.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,7 @@ struct ipc_message { IPC_BIND, IPC_RELOAD, + IPC_LAYER_LISTEN, } type; char data[MAX_IPC_MESSAGE_SIZE];