Add '--release' flag for bindsym in the bar block
i3bar's handle_button is modified to also handle XCB_BUTTON_RELEASE events. During these button release events, only custom commands are checked to avoid sending multiple workspace ipc messages. The way this patch is implemented will allow to assign a custom command for both the press and release of the same button: bar { ... bindsym buttonX exec command1 bindsym --release buttonX exec command2 } Fixes #3068.
This commit is contained in:
parent
ee0c016091
commit
315ff17563
|
@ -1377,7 +1377,7 @@ and will be removed in a future release. We strongly recommend using the more ge
|
||||||
|
|
||||||
*Syntax*:
|
*Syntax*:
|
||||||
----------------------------
|
----------------------------
|
||||||
bindsym button<n> <command>
|
bindsym [--release] button<n> <command>
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
*Example*:
|
*Example*:
|
||||||
|
@ -1385,6 +1385,8 @@ bindsym button<n> <command>
|
||||||
bar {
|
bar {
|
||||||
# disable clicking on workspace buttons
|
# disable clicking on workspace buttons
|
||||||
bindsym button1 nop
|
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
|
# execute custom script when scrolling downwards
|
||||||
bindsym button5 exec ~/.i3/scripts/custom_wheel_down
|
bindsym button5 exec ~/.i3/scripts/custom_wheel_down
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ typedef enum { M_DOCK = 0,
|
||||||
typedef struct binding_t {
|
typedef struct binding_t {
|
||||||
int input_code;
|
int input_code;
|
||||||
char *command;
|
char *command;
|
||||||
|
bool release;
|
||||||
|
|
||||||
TAILQ_ENTRY(binding_t)
|
TAILQ_ENTRY(binding_t)
|
||||||
bindings;
|
bindings;
|
||||||
|
|
|
@ -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) {
|
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")) {
|
if (!strcmp(cur_key, "binding_mode_indicator")) {
|
||||||
DLOG("binding_mode_indicator = %d\n", val);
|
DLOG("binding_mode_indicator = %d\n", val);
|
||||||
config.disable_binding_mode_indicator = !val;
|
config.disable_binding_mode_indicator = !val;
|
||||||
|
|
|
@ -439,6 +439,18 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
|
||||||
xcb_flush(xcb_connection);
|
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).
|
* 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-
|
* 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t x = event->event_x >= 0 ? event->event_x : 0;
|
|
||||||
|
|
||||||
DLOG("Got button %d\n", event->detail);
|
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;
|
int workspace_width = 0;
|
||||||
i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
|
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
|
/* If a custom command was specified for this mouse button, it overrides
|
||||||
* the default behavior. */
|
* the default behavior. */
|
||||||
binding_t *binding;
|
if (execute_custom_command(event->detail, event_is_release)) {
|
||||||
TAILQ_FOREACH(binding, &(config.bindings), bindings) {
|
|
||||||
if (binding->input_code != event->detail)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1164,6 +1177,7 @@ void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case XCB_BUTTON_RELEASE:
|
||||||
case XCB_BUTTON_PRESS:
|
case XCB_BUTTON_PRESS:
|
||||||
/* Button press events are mouse buttons clicked on one of our bars */
|
/* Button press events are mouse buttons clicked on one of our bars */
|
||||||
handle_button((xcb_button_press_event_t *)event);
|
handle_button((xcb_button_press_event_t *)event);
|
||||||
|
@ -1678,7 +1692,8 @@ void reconfig_windows(bool redraw_bars) {
|
||||||
* */
|
* */
|
||||||
values[3] = XCB_EVENT_MASK_EXPOSURE |
|
values[3] = XCB_EVENT_MASK_EXPOSURE |
|
||||||
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
|
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 (config.hide_on_modifier == M_DOCK) {
|
||||||
/* If the bar is normally visible, catch visibility change events to suspend
|
/* If the bar is normally visible, catch visibility change events to suspend
|
||||||
* the status process when the bar is obscured by full-screened windows. */
|
* the status process when the bar is obscured by full-screened windows. */
|
||||||
|
|
|
@ -84,7 +84,7 @@ CFGFUN(bar_verbose, const char *verbose);
|
||||||
CFGFUN(bar_modifier, const char *modifier);
|
CFGFUN(bar_modifier, const char *modifier);
|
||||||
CFGFUN(bar_wheel_up_cmd, const char *command);
|
CFGFUN(bar_wheel_up_cmd, const char *command);
|
||||||
CFGFUN(bar_wheel_down_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_position, const char *position);
|
||||||
CFGFUN(bar_i3bar_command, const char *i3bar_command);
|
CFGFUN(bar_i3bar_command, const char *i3bar_command);
|
||||||
CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);
|
CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);
|
||||||
|
|
|
@ -384,6 +384,9 @@ struct Barbinding {
|
||||||
/** The command which is to be executed for this button. */
|
/** The command which is to be executed for this button. */
|
||||||
char *command;
|
char *command;
|
||||||
|
|
||||||
|
/** If true, the command will be executed after the button is released. */
|
||||||
|
bool release;
|
||||||
|
|
||||||
TAILQ_ENTRY(Barbinding)
|
TAILQ_ENTRY(Barbinding)
|
||||||
bindings;
|
bindings;
|
||||||
};
|
};
|
||||||
|
|
|
@ -501,12 +501,16 @@ state BAR_WHEEL_DOWN_CMD:
|
||||||
-> call cfg_bar_wheel_down_cmd($command); BAR
|
-> call cfg_bar_wheel_down_cmd($command); BAR
|
||||||
|
|
||||||
state BAR_BINDSYM:
|
state BAR_BINDSYM:
|
||||||
|
release = '--release'
|
||||||
|
->
|
||||||
button = word
|
button = word
|
||||||
-> BAR_BINDSYM_COMMAND
|
-> BAR_BINDSYM_COMMAND
|
||||||
|
|
||||||
state BAR_BINDSYM_COMMAND:
|
state BAR_BINDSYM_COMMAND:
|
||||||
|
release = '--release'
|
||||||
|
->
|
||||||
command = string
|
command = string
|
||||||
-> call cfg_bar_bindsym($button, $command); BAR
|
-> call cfg_bar_bindsym($button, $release, $command); BAR
|
||||||
|
|
||||||
state BAR_POSITION:
|
state BAR_POSITION:
|
||||||
position = 'top', 'bottom'
|
position = 'top', 'bottom'
|
||||||
|
|
|
@ -502,7 +502,7 @@ CFGFUN(bar_modifier, const char *modifier) {
|
||||||
current_bar->modifier = M_NONE;
|
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) {
|
if (strncasecmp(button, "button", strlen("button")) != 0) {
|
||||||
ELOG("Bindings for a bar can only be mouse bindings, not \"%s\", ignoring.\n", button);
|
ELOG("Bindings for a bar can only be mouse bindings, not \"%s\", ignoring.\n", button);
|
||||||
return;
|
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);
|
ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const bool release_bool = release != NULL;
|
||||||
|
|
||||||
struct Barbinding *current;
|
struct Barbinding *current;
|
||||||
TAILQ_FOREACH(current, &(current_bar->bar_bindings), bindings) {
|
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);
|
ELOG("command for button %s was already specified, ignoring.\n", button);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding));
|
struct Barbinding *new_binding = scalloc(1, sizeof(struct Barbinding));
|
||||||
|
new_binding->release = release_bool;
|
||||||
new_binding->input_code = input_code;
|
new_binding->input_code = input_code;
|
||||||
new_binding->command = sstrdup(command);
|
new_binding->command = sstrdup(command);
|
||||||
TAILQ_INSERT_TAIL(&(current_bar->bar_bindings), new_binding, bindings);
|
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) {
|
CFGFUN(bar_wheel_up_cmd, const char *command) {
|
||||||
ELOG("'wheel_up_cmd' is deprecated. Please us 'bindsym button4 %s' instead.\n", 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) {
|
CFGFUN(bar_wheel_down_cmd, const char *command) {
|
||||||
ELOG("'wheel_down_cmd' is deprecated. Please us 'bindsym button5 %s' instead.\n", 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) {
|
CFGFUN(bar_bindsym, const char *button, const char *release, const char *command) {
|
||||||
bar_configure_binding(button, command);
|
bar_configure_binding(button, release, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
CFGFUN(bar_position, const char *position) {
|
CFGFUN(bar_position, const char *position) {
|
||||||
|
|
|
@ -572,6 +572,8 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
|
||||||
y(integer, current->input_code);
|
y(integer, current->input_code);
|
||||||
ystr("command");
|
ystr("command");
|
||||||
ystr(current->command);
|
ystr(current->command);
|
||||||
|
ystr("release");
|
||||||
|
y(bool, current->release == B_UPON_KEYRELEASE);
|
||||||
|
|
||||||
y(map_close);
|
y(map_close);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,9 @@ bar {
|
||||||
bindsym button3 focus left
|
bindsym button3 focus left
|
||||||
bindsym button4 focus right
|
bindsym button4 focus right
|
||||||
bindsym button5 focus left
|
bindsym button5 focus left
|
||||||
|
bindsym --release button6 focus right
|
||||||
|
bindsym button7 focus left
|
||||||
|
bindsym button7 --release focus right
|
||||||
}
|
}
|
||||||
EOT
|
EOT
|
||||||
use i3test::XTEST;
|
use i3test::XTEST;
|
||||||
|
@ -142,4 +145,43 @@ subtest 'button 5 moves focus left', \&focus_subtest,
|
||||||
[ $left->{id} ],
|
[ $left->{id} ],
|
||||||
'button 5 moves focus left';
|
'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;
|
done_testing;
|
||||||
|
|
Loading…
Reference in New Issue