From 8337cf95d2c82da1a3e1e3b7832d824bdce5f18d Mon Sep 17 00:00:00 2001 From: Emily Ellis Date: Sat, 15 Oct 2022 15:20:34 -0400 Subject: [PATCH 1/3] add `xdotool type --aftermodifiers` option This waits for the user to release any modifier keys that are currently held before starting to type. This allows avoiding race conditions when xdotool is called from a keyboard shortcut, and starts to type characters with the modifier still held. Currently the usual way to do so is with --clearmodifiers, but this introduces its own race conditions (see e.g. issue #43) if the physical key is released while the typing is in progress. --- cmd_type.c | 19 +++++++++++++++++-- xdo.c | 32 +++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/cmd_type.c b/cmd_type.c index 0fa8213..71f76ed 100644 --- a/cmd_type.c +++ b/cmd_type.c @@ -26,15 +26,17 @@ int cmd_type(context_t *context) { /* Options */ int clear_modifiers = 0; + int after_modifiers = 0; useconds_t delay = 12000; /* 12ms between keystrokes default */ enum { - opt_unused, opt_clearmodifiers, opt_delay, opt_help, opt_window, opt_args, - opt_terminator, opt_file + opt_unused, opt_clearmodifiers, opt_aftermodifiers, opt_delay, opt_help, + opt_window, opt_args, opt_terminator, opt_file }; struct option longopts[] = { { "clearmodifiers", no_argument, NULL, opt_clearmodifiers }, + { "aftermodifiers", no_argument, NULL, opt_aftermodifiers }, { "delay", required_argument, NULL, opt_delay }, { "help", no_argument, NULL, opt_help }, { "window", required_argument, NULL, opt_window }, @@ -50,6 +52,7 @@ int cmd_type(context_t *context) { "--window - specify a window to send keys to\n" "--delay - delay between keystrokes\n" "--clearmodifiers - reset active modifiers (alt, etc) while typing\n" + "--aftermodifiers - wait for modifiers to be released before typing\n" "--args N - how many arguments to expect in the exec command. This is\n" " useful for ending an exec and continuing with more xdotool\n" " commands\n" @@ -76,6 +79,9 @@ int cmd_type(context_t *context) { case opt_clearmodifiers: clear_modifiers = 1; break; + case opt_aftermodifiers: + after_modifiers = 1; + break; case opt_help: printf(usage, cmd); consume_args(context, context->argc); @@ -180,6 +186,15 @@ int cmd_type(context_t *context) { } window_each(context, window_arg, { + if (after_modifiers) { + for (;;) { + xdo_get_active_modifiers(context->xdo, NULL, &active_mods_n); + if (active_mods_n == 0) { + break; + } + usleep(30000); + } + } if (clear_modifiers) { xdo_get_active_modifiers(context->xdo, &active_mods, &active_mods_n); xdo_clear_active_modifiers(context->xdo, window, active_mods, active_mods_n); diff --git a/xdo.c b/xdo.c index 7651573..35f98f2 100644 --- a/xdo.c +++ b/xdo.c @@ -1631,7 +1631,10 @@ void _xdo_send_modifier(const xdo_t *xdo, int modmask, int is_press) { int xdo_get_active_modifiers(const xdo_t *xdo, charcodemap_t **keys, int *nkeys) { /* For each keyboard device, if an active key is a modifier, - * then add the keycode to the keycode list */ + * then add the keycode to the keycode list. + * + * If keys is null, only count the number of modifiers pressed, + * without recording them. */ char keymap[32]; /* keycode map: 256 bits */ int keys_size = 10; @@ -1639,7 +1642,9 @@ int xdo_get_active_modifiers(const xdo_t *xdo, charcodemap_t **keys, int mod_index, mod_key; XModifierKeymap *modifiers = XGetModifierMapping(xdo->xdpy); *nkeys = 0; - *keys = malloc(keys_size * sizeof(charcodemap_t)); + if (keys) { + *keys = malloc(keys_size * sizeof(charcodemap_t)); + } XQueryKeymap(xdo->xdpy, keymap); @@ -1654,15 +1659,20 @@ int xdo_get_active_modifiers(const xdo_t *xdo, charcodemap_t **keys, * 'xdotool key --clearmodifiers ...' sometimes failed trying * to clear modifiers that didn't exist since charcodemap_t's modmask was * uninitialized */ - memset(*keys + *nkeys, 0, sizeof(charcodemap_t)); - - (*keys)[*nkeys].code = keycode; - (*nkeys)++; - - if (*nkeys == keys_size) { - keys_size *= 2; - *keys = realloc(keys, keys_size * sizeof(charcodemap_t)); - } + if (keys) { + memset(*keys + *nkeys, 0, sizeof(charcodemap_t)); + + (*keys)[*nkeys].code = keycode; + (*nkeys)++; + + if (*nkeys == keys_size) { + keys_size *= 2; + *keys = realloc(keys, keys_size * sizeof(charcodemap_t)); + } + } + else { + (*nkeys)++; + } } } } From b4db1bf5b513176c39b566b164bf24931255b903 Mon Sep 17 00:00:00 2001 From: Emily Ellis Date: Sat, 15 Oct 2022 16:26:55 -0400 Subject: [PATCH 2/3] implement --aftermodifiers for all commands that accept --clearmodifiers, and add it to the manpage --- cmd_click.c | 16 +++++++++++++--- cmd_key.c | 11 ++++++++++- cmd_mousedown.c | 14 ++++++++++++-- cmd_mousemove.c | 17 ++++++++++++++--- cmd_mousemove_relative.c | 16 +++++++++++++--- cmd_mouseup.c | 13 +++++++++++-- cmd_type.c | 8 +------- xdo.c | 11 +++++++++++ xdo.h | 5 +++++ xdotool.pod | 35 ++++++++++++++++++++++++++++++++++- 10 files changed, 124 insertions(+), 22 deletions(-) diff --git a/cmd_click.c b/cmd_click.c index fae0aaa..da590f6 100644 --- a/cmd_click.c +++ b/cmd_click.c @@ -6,6 +6,7 @@ int cmd_click(context_t *context) { char *cmd = context->argv[0]; int ret = 0; int clear_modifiers = 0; + int after_modifiers = 0; charcodemap_t *active_mods = NULL; int active_mods_n; char *window_arg = NULL; @@ -14,11 +15,12 @@ int cmd_click(context_t *context) { int c; enum { - opt_unused, opt_help, opt_clearmodifiers, opt_window, opt_delay, + opt_unused, opt_help, opt_clearmodifiers, opt_aftermodifiers, opt_window, opt_delay, opt_repeat }; static struct option longopts[] = { { "clearmodifiers", no_argument, NULL, opt_clearmodifiers }, + { "aftermodifiers", no_argument, NULL, opt_aftermodifiers }, { "help", no_argument, NULL, opt_help }, { "window", required_argument, NULL, opt_window }, { "delay", required_argument, NULL, opt_delay }, @@ -27,7 +29,8 @@ int cmd_click(context_t *context) { }; static const char *usage = "Usage: %s [options]