From c6c6d3a952a647781c070a6783b1335c4271325a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 6 Sep 2012 17:04:31 +0200 Subject: [PATCH] naive implementation of 'bindsym --release' (and bindcode) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The implementation is naive because the user has to generate exactly the event he specified. That is, if you use this binding: bindsym --release $mod+x exec import /tmp/latest-screenshot.png Then it will only be triggered if you hit $mod, hit x, release x, release $mod. It will not be triggered if you hit $mod, hit x, release $mod, release x. The reason is that the KeyRelease event in the latter case will not have the modifier in its flags, so it doesn’t match the configured binding. --- include/config.h | 2 +- include/data.h | 4 ++++ src/cfgparse.l | 7 +++++-- src/cfgparse.y | 31 +++++++++++++++++++++---------- src/config.c | 6 +++++- src/handlers.c | 1 + src/key_press.c | 19 +++++++++++++------ 7 files changed, 50 insertions(+), 20 deletions(-) diff --git a/include/config.h b/include/config.h index ebb24864..1a48016a 100644 --- a/include/config.h +++ b/include/config.h @@ -312,7 +312,7 @@ void switch_mode(const char *new_mode); * or NULL if no such binding exists. * */ -Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode); +Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode); /** * Kills the configerror i3-nagbar process, if any. diff --git a/include/data.h b/include/data.h index d60ec3cd..4b886ae8 100644 --- a/include/data.h +++ b/include/data.h @@ -200,6 +200,10 @@ struct regex { * */ struct Binding { + /** If true, the binding should be executed upon a KeyRelease event, not a + * KeyPress (the default). */ + bool release; + /** Symbol the user specified in configfile, if any. This needs to be * stored with the binding to be able to re-convert it into a keycode * if the keyboard mapping changes (using Xmodmap for example) */ diff --git a/src/cfgparse.l b/src/cfgparse.l index 52cde189..8ee2a1da 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -66,6 +66,7 @@ EOL (\r?\n) %x BAR_COLOR %x EXEC +%x OPTRELEASE %% @@ -172,12 +173,14 @@ EOL (\r?\n) [ \t]+ { BEGIN(WANT_STRING); } --no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; } . { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); } +--release { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; } +. { printf("anything else (optrelease): *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); } [0-9-]+ { yylval.number = atoi(yytext); return NUMBER; } bar { yy_push_state(BAR); return TOK_BAR; } mode { return TOKMODE; } bind { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } -bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } -bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; } +bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } +bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; } floating_maximum_size { return TOKFLOATING_MAXIMUM_SIZE; } floating_minimum_size { return TOKFLOATING_MINIMUM_SIZE; } floating_modifier { return TOKFLOATING_MODIFIER; } diff --git a/src/cfgparse.y b/src/cfgparse.y index af7d77cf..5c166778 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -3,6 +3,8 @@ * vim:ts=4:sw=4:expandtab * */ +#undef I3__FILE__ +#define I3__FILE__ "cfgparse.y" #include #include #include @@ -782,6 +784,7 @@ void parse_file(const char *f) { %token TOK_BAR_COLOR_INACTIVE_WORKSPACE "inactive_workspace" %token TOK_BAR_COLOR_URGENT_WORKSPACE "urgent_workspace" %token TOK_NO_STARTUP_ID "--no-startup-id" +%token TOK_RELEASE "--release" %token TOK_MARK "mark" %token TOK_CLASS "class" @@ -811,6 +814,7 @@ void parse_file(const char *f) { %type bar_mode_mode %type bar_modifier_modifier %type optional_no_startup_id +%type optional_release %type command %type word_or_number %type qstring_or_number @@ -879,33 +883,40 @@ binding: ; bindcode: - binding_modifiers NUMBER command + optional_release binding_modifiers NUMBER command { - printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $3); + DLOG("bindcode: release = %d, mod = %d, key = %d, command = %s\n", $1, $2, $3, $4); Binding *new = scalloc(sizeof(Binding)); - new->keycode = $2; - new->mods = $1; - new->command = $3; + new->release = $1; + new->keycode = $3; + new->mods = $2; + new->command = $4; $$ = new; } ; bindsym: - binding_modifiers word_or_number command + optional_release binding_modifiers word_or_number command { - printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $3); + DLOG("bindsym: release = %d, mod = %d, key = %s, command = %s\n", $1, $2, $3, $4); Binding *new = scalloc(sizeof(Binding)); - new->symbol = $2; - new->mods = $1; - new->command = $3; + new->release = $1; + new->symbol = $3; + new->mods = $2; + new->command = $4; $$ = new; } ; +optional_release: + /* empty */ { $$ = false; } + | TOK_RELEASE { $$ = true; } + ; + for_window: TOK_FOR_WINDOW match command { diff --git a/src/config.c b/src/config.c index 017d40ce..41491d86 100644 --- a/src/config.c +++ b/src/config.c @@ -54,7 +54,7 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint * or NULL if no such binding exists. * */ -Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode) { +Binding *get_binding(uint16_t modifiers, bool key_release, xcb_keycode_t keycode) { Binding *bind; TAILQ_FOREACH(bind, bindings, bindings) { @@ -62,6 +62,10 @@ Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode) { if (bind->mods != modifiers) continue; + /* Check if the binding is for a KeyPress or a KeyRelease event */ + if (bind->release != key_release) + continue; + /* If a symbol was specified by the user, we need to look in * the array of translated keycodes for the event’s keycode */ if (bind->symbol != NULL) { diff --git a/src/handlers.c b/src/handlers.c index 0d087e79..21a87342 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1045,6 +1045,7 @@ void handle_event(int type, xcb_generic_event_t *event) { switch (type) { case XCB_KEY_PRESS: + case XCB_KEY_RELEASE: handle_key_press((xcb_key_press_event_t*)event); break; diff --git a/src/key_press.c b/src/key_press.c index 2a578d66..3f466a2e 100644 --- a/src/key_press.c +++ b/src/key_press.c @@ -227,15 +227,17 @@ static yajl_callbacks command_error_callbacks = { }; /* - * There was a key press. We compare this key code with our bindings table and pass - * the bound action to parse_command(). + * There was a KeyPress or KeyRelease (both events have the same fields). We + * compare this key code with our bindings table and pass the bound action to + * parse_command(). * */ void handle_key_press(xcb_key_press_event_t *event) { + bool key_release = (event->response_type == XCB_KEY_RELEASE); last_timestamp = event->time; - DLOG("Keypress %d, state raw = %d\n", event->detail, event->state); + DLOG("%s %d, state raw = %d\n", (key_release ? "KeyRelease" : "KeyPress"), event->detail, event->state); /* Remove the numlock bit, all other bits are modifiers we can bind to */ uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK); @@ -251,7 +253,7 @@ void handle_key_press(xcb_key_press_event_t *event) { DLOG("(checked mode_switch, state %d)\n", state_filtered); /* Find the binding */ - Binding *bind = get_binding(state_filtered, event->detail); + Binding *bind = get_binding(state_filtered, key_release, event->detail); /* No match? Then the user has Mode_switch enabled but does not have a * specific keybinding. Fall back to the default keybindings (without @@ -260,8 +262,13 @@ void handle_key_press(xcb_key_press_event_t *event) { if (bind == NULL) { state_filtered &= ~(BIND_MODE_SWITCH); DLOG("no match, new state_filtered = %d\n", state_filtered); - if ((bind = get_binding(state_filtered, event->detail)) == NULL) { - ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n", + if ((bind = get_binding(state_filtered, key_release, event->detail)) == NULL) { + /* This is not a real error since we can have release and + * non-release keybindings. On a KeyPress event for which there is + * only a !release-binding, but no release-binding, the + * corresponding KeyRelease event will trigger this. No problem, + * though. */ + DLOG("Could not lookup key binding (modifiers %d, keycode %d)\n", state_filtered, event->detail); return; } -- 2.39.2