naive implementation of 'bindsym --release' (and bindcode)

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.
This commit is contained in:
Michael Stapelberg 2012-09-06 17:04:31 +02:00
parent f44d4ce4b3
commit c6c6d3a952
7 changed files with 50 additions and 20 deletions

View File

@ -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.

View File

@ -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) */

View File

@ -66,6 +66,7 @@ EOL (\r?\n)
%x BAR_COLOR
%x EXEC
%x OPTRELEASE
%%
@ -172,12 +173,14 @@ EOL (\r?\n)
<ASSIGN_TARGET_COND>[ \t]+ { BEGIN(WANT_STRING); }
<EXEC>--no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; }
<EXEC>. { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
<OPTRELEASE>--release { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; }
<OPTRELEASE>. { 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; }

View File

@ -3,6 +3,8 @@
* vim:ts=4:sw=4:expandtab
*
*/
#undef I3__FILE__
#define I3__FILE__ "cfgparse.y"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
@ -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 <number> bar_mode_mode
%type <number> bar_modifier_modifier
%type <number> optional_no_startup_id
%type <number> optional_release
%type <string> command
%type <string> word_or_number
%type <string> 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
{

View File

@ -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 events keycode */
if (bind->symbol != NULL) {

View File

@ -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;

View File

@ -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;
}