*Syntax*:
----------------------------
-bindsym button<n> <command>
+bindsym [--release] button<n> <command>
----------------------------
*Example*:
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
}
typedef struct binding_t {
int input_code;
char *command;
+ bool release;
TAILQ_ENTRY(binding_t)
bindings;
*
*/
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;
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-
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;
/* 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;
}
}
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);
* */
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. */
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);
/** 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;
};
-> 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'
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;
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);
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) {
y(integer, current->input_code);
ystr("command");
ystr(current->command);
+ ystr("release");
+ y(bool, current->release == B_UPON_KEYRELEASE);
y(map_close);
}
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;
[ $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;