diff --git a/docs/userguide b/docs/userguide index 7a1621da..0258e2ab 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1377,7 +1377,7 @@ and will be removed in a future release. We strongly recommend using the more ge *Syntax*: ---------------------------- -bindsym button +bindsym [--release] button ---------------------------- *Example*: @@ -1385,6 +1385,8 @@ bindsym button bar { # disable clicking on workspace buttons bindsym button1 nop + # Take a screenshot by right clicking on the bar + bindsym --release button3 exec --no-startup-id import /tmp/latest-screenshot.png # execute custom script when scrolling downwards bindsym button5 exec ~/.i3/scripts/custom_wheel_down } diff --git a/i3bar/include/configuration.h b/i3bar/include/configuration.h index e77e891b..61cac7f6 100644 --- a/i3bar/include/configuration.h +++ b/i3bar/include/configuration.h @@ -27,6 +27,7 @@ typedef enum { M_DOCK = 0, typedef struct binding_t { int input_code; char *command; + bool release; TAILQ_ENTRY(binding_t) bindings; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 79e106c0..a58b9bf8 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -264,6 +264,21 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len * */ static int config_boolean_cb(void *params_, int val) { + if (parsing_bindings) { + if (strcmp(cur_key, "release") == 0) { + binding_t *binding = TAILQ_LAST(&(config.bindings), bindings_head); + if (binding == NULL) { + ELOG("There is no binding to put the current command onto. This is a bug in i3.\n"); + return 0; + } + + binding->release = val; + return 1; + } + + ELOG("Unknown key \"%s\" while parsing bar bindings.\n", cur_key); + } + if (!strcmp(cur_key, "binding_mode_indicator")) { DLOG("binding_mode_indicator = %d\n", val); config.disable_binding_mode_indicator = !val; diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 1a9240fb..77822544 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -439,6 +439,18 @@ void init_colors(const struct xcb_color_strings_t *new_colors) { xcb_flush(xcb_connection); } +static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_release) { + binding_t *binding; + TAILQ_FOREACH(binding, &(config.bindings), bindings) { + if ((binding->input_code != input_code) || (binding->release != event_is_release)) + continue; + + i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command); + return true; + } + return false; +} + /* * Handle a button press event (i.e. a mouse click on one of our bars). * We determine, whether the click occurred on a workspace button or if the scroll- @@ -460,10 +472,16 @@ void handle_button(xcb_button_press_event_t *event) { return; } - int32_t x = event->event_x >= 0 ? event->event_x : 0; - DLOG("Got button %d\n", event->detail); + /* During button release events, only check for custom commands. */ + const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE; + if (event_is_release) { + execute_custom_command(event->detail, event_is_release); + return; + } + + int32_t x = event->event_x >= 0 ? event->event_x : 0; int workspace_width = 0; i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk; @@ -516,12 +534,7 @@ void handle_button(xcb_button_press_event_t *event) { /* If a custom command was specified for this mouse button, it overrides * the default behavior. */ - binding_t *binding; - TAILQ_FOREACH(binding, &(config.bindings), bindings) { - if (binding->input_code != event->detail) - continue; - - i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command); + if (execute_custom_command(event->detail, event_is_release)) { return; } @@ -1164,6 +1177,7 @@ void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) { } break; + case XCB_BUTTON_RELEASE: case XCB_BUTTON_PRESS: /* Button press events are mouse buttons clicked on one of our bars */ handle_button((xcb_button_press_event_t *)event); @@ -1678,7 +1692,8 @@ void reconfig_windows(bool redraw_bars) { * */ values[3] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | - XCB_EVENT_MASK_BUTTON_PRESS; + XCB_EVENT_MASK_BUTTON_PRESS | + XCB_EVENT_MASK_BUTTON_RELEASE; if (config.hide_on_modifier == M_DOCK) { /* If the bar is normally visible, catch visibility change events to suspend * the status process when the bar is obscured by full-screened windows. */ diff --git a/include/config_directives.h b/include/config_directives.h index 1191a7c6..60c7a4b1 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -84,7 +84,7 @@ CFGFUN(bar_verbose, const char *verbose); CFGFUN(bar_modifier, const char *modifier); CFGFUN(bar_wheel_up_cmd, const char *command); CFGFUN(bar_wheel_down_cmd, const char *command); -CFGFUN(bar_bindsym, const char *button, const char *command); +CFGFUN(bar_bindsym, const char *button, const char *release, const char *command); CFGFUN(bar_position, const char *position); CFGFUN(bar_i3bar_command, const char *i3bar_command); CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text); diff --git a/include/configuration.h b/include/configuration.h index 8f1ce332..ac800159 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -384,6 +384,9 @@ struct Barbinding { /** The command which is to be executed for this button. */ char *command; + /** If true, the command will be executed after the button is released. */ + bool release; + TAILQ_ENTRY(Barbinding) bindings; }; diff --git a/parser-specs/config.spec b/parser-specs/config.spec index c31567a6..3d3ffb28 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -501,12 +501,16 @@ state BAR_WHEEL_DOWN_CMD: -> call cfg_bar_wheel_down_cmd($command); BAR state BAR_BINDSYM: + release = '--release' + -> button = word -> BAR_BINDSYM_COMMAND state BAR_BINDSYM_COMMAND: + release = '--release' + -> command = string - -> call cfg_bar_bindsym($button, $command); BAR + -> call cfg_bar_bindsym($button, $release, $command); BAR state BAR_POSITION: position = 'top', 'bottom' diff --git a/src/config_directives.c b/src/config_directives.c index 1cf875ef..a3b1f776 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -502,7 +502,7 @@ CFGFUN(bar_modifier, const char *modifier) { current_bar->modifier = M_NONE; } -static void bar_configure_binding(const char *button, const char *command) { +static void bar_configure_binding(const char *button, const char *release, const char *command) { if (strncasecmp(button, "button", strlen("button")) != 0) { ELOG("Bindings for a bar can only be mouse bindings, not \"%s\", ignoring.\n", button); return; @@ -513,16 +513,18 @@ static void bar_configure_binding(const char *button, const char *command) { ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button); return; } + const bool release_bool = release != NULL; struct Barbinding *current; TAILQ_FOREACH(current, &(current_bar->bar_bindings), bindings) { - if (current->input_code == input_code) { + if (current->input_code == input_code && current->release == release_bool) { ELOG("command for button %s was already specified, ignoring.\n", button); return; } } struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding)); + new_binding->release = release_bool; new_binding->input_code = input_code; new_binding->command = sstrdup(command); TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings); @@ -530,16 +532,16 @@ static void bar_configure_binding(const char *button, const char *command) { CFGFUN(bar_wheel_up_cmd, const char *command) { ELOG("'wheel_up_cmd' is deprecated. Please us 'bindsym button4 %s' instead.\n", command); - bar_configure_binding("button4", command); + bar_configure_binding("button4", NULL, command); } CFGFUN(bar_wheel_down_cmd, const char *command) { ELOG("'wheel_down_cmd' is deprecated. Please us 'bindsym button5 %s' instead.\n", command); - bar_configure_binding("button5", command); + bar_configure_binding("button5", NULL, command); } -CFGFUN(bar_bindsym, const char *button, const char *command) { - bar_configure_binding(button, command); +CFGFUN(bar_bindsym, const char *button, const char *release, const char *command) { + bar_configure_binding(button, release, command); } CFGFUN(bar_position, const char *position) { diff --git a/src/ipc.c b/src/ipc.c index 99b9e0ec..a1a72b1a 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -572,6 +572,8 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) { y(integer, current->input_code); ystr("command"); ystr(current->command); + ystr("release"); + y(bool, current->release == B_UPON_KEYRELEASE); y(map_close); } diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index 57786dea..87552785 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -30,6 +30,9 @@ bar { bindsym button3 focus left bindsym button4 focus right bindsym button5 focus left + bindsym --release button6 focus right + bindsym button7 focus left + bindsym button7 --release focus right } EOT use i3test::XTEST; @@ -142,4 +145,43 @@ subtest 'button 5 moves focus left', \&focus_subtest, [ $left->{id} ], 'button 5 moves focus left'; +# Test --release flag with bar bindsym. +# See issue: #3068. + +my $old_focus = get_focused($ws); +subtest 'button 6 does not move focus while pressed', \&focus_subtest, + sub { + xtest_button_press(6, 3, 3); + xtest_sync_with($i3bar_window); + }, + [], + 'button 6 does not move focus while pressed'; +is(get_focused($ws), $old_focus, 'focus unchanged'); + +subtest 'button 6 release moves focus right', \&focus_subtest, + sub { + xtest_button_release(6, 3, 3); + xtest_sync_with($i3bar_window); + }, + [ $right->{id} ], + 'button 6 release moves focus right'; + +# Test same bindsym button with and without --release. + +subtest 'button 7 press moves focus left', \&focus_subtest, + sub { + xtest_button_press(7, 3, 3); + xtest_sync_with($i3bar_window); + }, + [ $left->{id} ], + 'button 7 press moves focus left'; + +subtest 'button 7 release moves focus right', \&focus_subtest, + sub { + xtest_button_release(7, 3, 3); + xtest_sync_with($i3bar_window); + }, + [ $right->{id} ], + 'button 7 release moves focus right'; + done_testing;