diff --git a/data/keyd-application-mapper.1.gz b/data/keyd-application-mapper.1.gz index 2ad7980..586f887 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 ef7b68d..0204a56 100644 Binary files a/data/keyd.1.gz and b/data/keyd.1.gz differ diff --git a/docs/keyd-application-mapper.scdoc b/docs/keyd-application-mapper.scdoc index d0fff91..48c4c82 100644 --- a/docs/keyd-application-mapper.scdoc +++ b/docs/keyd-application-mapper.scdoc @@ -76,8 +76,8 @@ to */var/run/keyd.socket* (i.e be a member of the *keyd* group). # A note on security -Any user which can interact with a program that directly controls input devices -should be assumed to have full access to the system. +*Any user which can interact with a program that directly controls input devices +should be assumed to have full access to the system.* While keyd offers slightly better isolation compared to other remappers by dint of mediating access through an IPC mechanism (rather than granting users diff --git a/docs/keyd.scdoc b/docs/keyd.scdoc index 59b6f6c..bc57fa3 100644 --- a/docs/keyd.scdoc +++ b/docs/keyd.scdoc @@ -372,6 +372,15 @@ arguments. macro2(400, 50, macro(Hello space World)) +*command()* + Execute the given shell command. + + E.G + + command(brightness down) + +*NOTE:* Commands are executed by root, which means any user which has access +to the keyd socket should be assumed to have root access. *noop* Do nothing. diff --git a/src/descriptor.c b/src/descriptor.c index d0e7244..933ad78 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -14,7 +14,9 @@ #define MAX_ARGS 5 -/* modifies the input string */ +/* TODO: Make this a bit nicer. */ + +/* Modifies the input string */ static int parse_fn(char *s, char **name, char *args[MAX_ARGS], @@ -314,13 +316,12 @@ static size_t escape(char *s) return n; } - static int parse_macro(const char *exp, struct macro *macro) { - char s[MAX_MACROEXP_LEN]; + char s[MAX_MACROEXP_LEN+1]; int len = strlen(exp); - if (len >= MAX_MACROEXP_LEN) { + if (len > MAX_MACROEXP_LEN) { err("macro exceeds maximum macro length (%d)", MAX_MACROEXP_LEN); return -1; } @@ -394,7 +395,7 @@ int layer_table_lookup(const struct layer_table *lt, const char *name) int layer_table_add_entry(struct layer_table *lt, const char *exp) { uint8_t code1, code2; - char *keystr, *descstr, *c, *s; + char *keystr, *descstr, *dot, *paren, *s; char *layername = "main"; struct descriptor d; struct layer *layer; @@ -410,10 +411,13 @@ int layer_table_add_entry(struct layer_table *lt, const char *exp) strcpy(buf, exp); s = buf; - if ((c = strchr(s, '.'))) { + dot = strchr(s, '.'); + paren = strchr(s, '('); + + if (dot && (!paren || dot < paren)) { layername = s; - *c = 0; - s = c+1; + *dot = 0; + s = dot+1; } if (parse_kvp(s, &keystr, &descstr) < 0) { @@ -518,6 +522,36 @@ int create_layer(struct layer *layer, const char *desc, const struct layer_table return 0; } +int set_command_arg(struct descriptor *d, int idx, + struct layer_table *lt, const char *exp) +{ + struct command *command = <->commands[lt->nr_commands]; + int len = strlen(exp); + + if (len == 0 || strstr(exp, "command(") != exp || exp[len-1] != ')') + return -1; + + if (lt->nr_commands >= MAX_COMMANDS) { + err("maximum number of commands exceeded"); + return 1; + } + + if (len > MAX_COMMAND_LEN) { + err("maximum command length exceeded"); + return 1; + } + + strcpy(command->cmd, exp+8); + command->cmd[len-9] = 0; + + command->cmd[len-1] = 0; + escape(command->cmd); + + d->args[0].idx = lt->nr_commands; + lt->nr_commands++; + return 0; +} + /* * Returns: * @@ -586,6 +620,11 @@ int parse_descriptor(const char *descstr, if (keycode_to_mod(code)) fprintf(stderr, "WARNING: mapping modifier keycodes directly may produce unintended results, you probably want layer() instead\n"); + } else if ((ret=set_command_arg(d, 0, lt, descstr)) >= 0) { + if (ret > 0) + return -1; + else + d->op = OP_COMMAND; } else if ((ret=set_macro_arg(d, 0, lt, descstr, -1, -1)) >= 0) { if (ret > 0) return -1; diff --git a/src/descriptor.h b/src/descriptor.h index d882cc4..e66994e 100644 --- a/src/descriptor.h +++ b/src/descriptor.h @@ -29,6 +29,7 @@ enum op { OP_TOGGLE, OP_MACRO, + OP_COMMAND, OP_TIMEOUT }; diff --git a/src/device.c b/src/device.c index d1a5151..f8e7bf5 100644 --- a/src/device.c +++ b/src/device.c @@ -68,7 +68,7 @@ static int device_init(const char *path, struct device *dev) int fd; int type; - if ((fd = open(path, O_RDWR | O_NONBLOCK, 0600)) < 0) { + if ((fd = open(path, O_RDWR | O_NONBLOCK | O_CLOEXEC, 0600)) < 0) { fprintf(stderr, "failed to open %s\n", path); return -1; } @@ -167,7 +167,7 @@ int devmon_create() assert(!init); init = 1; - int fd = inotify_init1(IN_NONBLOCK); + int fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); if (fd < 0) { perror("inotify"); exit(-1); diff --git a/src/keyboard.c b/src/keyboard.c index f5baf79..608f090 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -324,6 +324,33 @@ static void activate_layer(struct keyboard *kbd, uint8_t code, struct layer *lay kbd->last_layer_code = code; } +static void execute_command(const char *cmd) +{ + int fd; + + dbg("Executing command: %s", cmd); + + if (fork()) + return; + + fd = open("/dev/null", O_RDWR); + + if (fd < 0) { + perror("open"); + exit(-1); + } + + close(0); + close(1); + close(2); + + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + + execl("/bin/sh", "/bin/sh", "-c", cmd); +} + static void clear_oneshot(struct keyboard *kbd) { size_t i = 0; @@ -348,6 +375,7 @@ static long process_descriptor(struct keyboard *kbd, uint8_t code, struct macro *macros = kbd->layer_table.macros; struct timeout *timeouts = kbd->layer_table.timeouts; struct layer *layers = kbd->layer_table.layers; + struct command *commands = kbd->layer_table.commands; switch (d->op) { struct macro *macro; @@ -476,6 +504,10 @@ static long process_descriptor(struct keyboard *kbd, uint8_t code, timeout = kbd->pending_timeout.t.timeout; } break; + case OP_COMMAND: + if (pressed) + execute_command(commands[d->args[0].idx].cmd); + break; case OP_SWAP: layer = &layers[d->args[0].idx]; macro = d->args[1].idx == -1 ? NULL : ¯os[d->args[1].idx]; diff --git a/src/layer.h b/src/layer.h index 53cd369..7172caa 100644 --- a/src/layer.h +++ b/src/layer.h @@ -16,6 +16,9 @@ #define MAX_MACRO_SIZE 64 #define MAX_MACROS 256 +#define MAX_COMMAND_LEN 256 +#define MAX_COMMANDS 64 + #define LT_NORMAL 0 #define LT_LAYOUT 1 #define LT_COMPOSITE 2 @@ -32,7 +35,7 @@ */ struct layer { - char name[MAX_LAYER_NAME_LEN]; + char name[MAX_LAYER_NAME_LEN+1]; size_t nr_layers; int layers[MAX_COMPOSITE_LAYERS]; @@ -48,6 +51,10 @@ struct layer { long activation_time; }; +struct command { + char cmd[MAX_COMMAND_LEN+1]; +}; + struct timeout { uint16_t timeout; struct descriptor d1; @@ -84,9 +91,11 @@ struct layer_table { struct timeout timeouts[MAX_TIMEOUTS]; struct macro macros[MAX_MACROS]; + struct command commands[MAX_COMMANDS]; size_t nr_macros; size_t nr_timeouts; + size_t nr_commands; }; #endif diff --git a/src/vkbd/uinput.c b/src/vkbd/uinput.c index b07109e..f6e91ff 100644 --- a/src/vkbd/uinput.c +++ b/src/vkbd/uinput.c @@ -36,7 +36,7 @@ static int create_virtual_keyboard(const char *name) size_t code; struct uinput_user_dev udev = {0}; - int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); + int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK | O_CLOEXEC); if (fd < 0) { perror("open uinput"); exit(-1); @@ -93,7 +93,7 @@ static int create_virtual_pointer(const char *name) uint16_t code; struct uinput_user_dev udev = {0}; - int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); + int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK | O_CLOEXEC); if (fd < 0) { perror("open"); exit(-1);