Merge pull request #1697 from Airblader/feature-1695
Extend mouse commands on i3bar
This commit is contained in:
commit
696d844ffa
|
@ -1209,23 +1209,41 @@ Available modifiers are Mod1-Mod5, Shift, Control (see +xmodmap(1)+).
|
|||
=== Mouse button commands
|
||||
|
||||
Specifies a command to run when a button was pressed on i3bar to override the
|
||||
default behavior. Currently only the mouse wheel buttons are supported. This is
|
||||
useful for disabling the scroll wheel action or running scripts that implement
|
||||
custom behavior for these buttons.
|
||||
default behavior. This is useful, e.g., for disabling the scroll wheel action
|
||||
or running scripts that implement custom behavior for these buttons.
|
||||
|
||||
A button is always named +button<n>+, where 1 to 5 are default buttons as follows and higher
|
||||
numbers can be special buttons on devices offering more buttons:
|
||||
|
||||
button1::
|
||||
Left mouse button.
|
||||
button2::
|
||||
Middle mouse button.
|
||||
button3::
|
||||
Right mouse button.
|
||||
button4::
|
||||
Scroll wheel up.
|
||||
button5::
|
||||
Scroll wheel down.
|
||||
|
||||
Please note that the old +wheel_up_cmd+ and +wheel_down_cmd+ commands are deprecated
|
||||
and will be removed in a future release. We strongly recommend using the more general
|
||||
+bindsym+ with +button4+ and +button5+ instead.
|
||||
|
||||
*Syntax*:
|
||||
---------------------
|
||||
wheel_up_cmd <command>
|
||||
wheel_down_cmd <command>
|
||||
---------------------
|
||||
----------------------------
|
||||
bindsym button<n> <command>
|
||||
----------------------------
|
||||
|
||||
*Example*:
|
||||
---------------------
|
||||
---------------------------------------------------------
|
||||
bar {
|
||||
wheel_up_cmd nop
|
||||
wheel_down_cmd exec ~/.i3/scripts/custom_wheel_down
|
||||
# disable clicking on workspace buttons
|
||||
bindsym button1 nop
|
||||
# execute custom script when scrolling downwards
|
||||
bindsym button5 exec ~/.i3/scripts/custom_wheel_down
|
||||
}
|
||||
---------------------
|
||||
---------------------------------------------------------
|
||||
|
||||
=== Bar ID
|
||||
|
||||
|
|
|
@ -22,10 +22,16 @@ typedef enum { M_DOCK = 0,
|
|||
M_HIDE = 1,
|
||||
M_INVISIBLE = 2 } bar_display_mode_t;
|
||||
|
||||
typedef struct binding_t {
|
||||
int input_code;
|
||||
char *command;
|
||||
|
||||
TAILQ_ENTRY(binding_t) bindings;
|
||||
} binding_t;
|
||||
|
||||
typedef struct config_t {
|
||||
int modifier;
|
||||
char *wheel_up_cmd;
|
||||
char *wheel_down_cmd;
|
||||
TAILQ_HEAD(bindings_head, binding_t) bindings;
|
||||
position_t position;
|
||||
int verbose;
|
||||
struct xcb_color_strings_t colors;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "common.h"
|
||||
|
||||
static char *cur_key;
|
||||
static bool parsing_bindings;
|
||||
|
||||
/*
|
||||
* Parse a key.
|
||||
|
@ -34,6 +35,14 @@ static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t
|
|||
strncpy(cur_key, (const char *)keyVal, keyLen);
|
||||
cur_key[keyLen] = '\0';
|
||||
|
||||
if (strcmp(cur_key, "bindings") == 0)
|
||||
parsing_bindings = true;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int config_end_array_cb(void *params_) {
|
||||
parsing_bindings = false;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -63,6 +72,27 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
|
|||
if (!strcmp(cur_key, "id") || !strcmp(cur_key, "socket_path"))
|
||||
return 1;
|
||||
|
||||
if (parsing_bindings) {
|
||||
if (strcmp(cur_key, "command") == 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;
|
||||
}
|
||||
|
||||
if (binding->command != NULL) {
|
||||
ELOG("The binding for input_code = %d already has a command. This is a bug in i3.\n", binding->input_code);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sasprintf(&(binding->command), "%.*s", len, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ELOG("Unknown key \"%s\" while parsing bar bindings.\n", cur_key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "mode")) {
|
||||
DLOG("mode = %.*s, len = %d\n", len, val, len);
|
||||
config.hide_on_modifier = (len == 4 && !strncmp((const char *)val, "dock", strlen("dock")) ? M_DOCK
|
||||
|
@ -112,17 +142,25 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* This key was sent in <= 4.10.2. We keep it around to avoid breakage for
|
||||
* users updating from that version and restarting i3bar before i3. */
|
||||
if (!strcmp(cur_key, "wheel_up_cmd")) {
|
||||
DLOG("wheel_up_cmd = %.*s\n", len, val);
|
||||
FREE(config.wheel_up_cmd);
|
||||
sasprintf(&config.wheel_up_cmd, "%.*s", len, val);
|
||||
binding_t *binding = scalloc(sizeof(binding_t));
|
||||
binding->input_code = 4;
|
||||
sasprintf(&(binding->command), "%.*s", len, val);
|
||||
TAILQ_INSERT_TAIL(&(config.bindings), binding, bindings);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* This key was sent in <= 4.10.2. We keep it around to avoid breakage for
|
||||
* users updating from that version and restarting i3bar before i3. */
|
||||
if (!strcmp(cur_key, "wheel_down_cmd")) {
|
||||
DLOG("wheel_down_cmd = %.*s\n", len, val);
|
||||
FREE(config.wheel_down_cmd);
|
||||
sasprintf(&config.wheel_down_cmd, "%.*s", len, val);
|
||||
binding_t *binding = scalloc(sizeof(binding_t));
|
||||
binding->input_code = 5;
|
||||
sasprintf(&(binding->command), "%.*s", len, val);
|
||||
TAILQ_INSERT_TAIL(&(config.bindings), binding, bindings);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -232,11 +270,34 @@ static int config_boolean_cb(void *params_, int val) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse an integer value
|
||||
*
|
||||
*/
|
||||
static int config_integer_cb(void *params_, long long val) {
|
||||
if (parsing_bindings) {
|
||||
if (strcmp(cur_key, "input_code") == 0) {
|
||||
binding_t *binding = scalloc(sizeof(binding_t));
|
||||
binding->input_code = val;
|
||||
TAILQ_INSERT_TAIL(&(config.bindings), binding, bindings);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
ELOG("Unknown key \"%s\" while parsing bar bindings.\n", cur_key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* A datastructure to pass all these callbacks to yajl */
|
||||
static yajl_callbacks outputs_callbacks = {
|
||||
.yajl_null = config_null_cb,
|
||||
.yajl_boolean = config_boolean_cb,
|
||||
.yajl_integer = config_integer_cb,
|
||||
.yajl_string = config_string_cb,
|
||||
.yajl_end_array = config_end_array_cb,
|
||||
.yajl_map_key = config_map_key_cb,
|
||||
};
|
||||
|
||||
|
@ -249,6 +310,8 @@ void parse_config_json(char *json) {
|
|||
yajl_status state;
|
||||
handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
|
||||
|
||||
TAILQ_INIT(&(config.bindings));
|
||||
|
||||
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
|
||||
/* FIXME: Proper error handling for JSON parsing */
|
||||
|
|
|
@ -468,20 +468,23 @@ void handle_button(xcb_button_press_event_t *event) {
|
|||
return;
|
||||
}
|
||||
|
||||
/* 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_COMMAND, binding->command);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event->detail) {
|
||||
case 4:
|
||||
/* Mouse wheel up. We select the previous ws, if any.
|
||||
* If there is no more workspace, don’t even send the workspace
|
||||
* command, otherwise (with workspace auto_back_and_forth) we’d end
|
||||
* up on the wrong workspace. */
|
||||
|
||||
/* If `wheel_up_cmd [COMMAND]` was specified, it should override
|
||||
* the default behavior */
|
||||
if (config.wheel_up_cmd) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, config.wheel_up_cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cur_ws == TAILQ_FIRST(walk->workspaces))
|
||||
return;
|
||||
|
||||
|
@ -492,14 +495,6 @@ void handle_button(xcb_button_press_event_t *event) {
|
|||
* If there is no more workspace, don’t even send the workspace
|
||||
* command, otherwise (with workspace auto_back_and_forth) we’d end
|
||||
* up on the wrong workspace. */
|
||||
|
||||
/* if `wheel_down_cmd [COMMAND]` was specified, it should override
|
||||
* the default behavior */
|
||||
if (config.wheel_down_cmd) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, config.wheel_down_cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head))
|
||||
return;
|
||||
|
||||
|
|
|
@ -281,13 +281,7 @@ struct Barconfig {
|
|||
M_MOD5 = 7
|
||||
} modifier;
|
||||
|
||||
/** Command that should be run when mouse wheel up button is pressed over
|
||||
* i3bar to override the default behavior. */
|
||||
char *wheel_up_cmd;
|
||||
|
||||
/** Command that should be run when mouse wheel down button is pressed over
|
||||
* i3bar to override the default behavior. */
|
||||
char *wheel_down_cmd;
|
||||
TAILQ_HEAD(bar_bindings_head, Barbinding) bar_bindings;
|
||||
|
||||
/** Bar position (bottom by default). */
|
||||
enum { P_BOTTOM = 0,
|
||||
|
@ -353,6 +347,21 @@ struct Barconfig {
|
|||
TAILQ_ENTRY(Barconfig) configs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a mouse command to be executed instead of the default behavior when
|
||||
* clicking on the non-statusline part of i3bar.
|
||||
*
|
||||
*/
|
||||
struct Barbinding {
|
||||
/** The button to be used (e.g., 1 for "button1"). */
|
||||
int input_code;
|
||||
|
||||
/** The command which is to be executed for this button. */
|
||||
char *command;
|
||||
|
||||
TAILQ_ENTRY(Barbinding) bindings;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the configuration file to use (either the one specified by
|
||||
* override_configpath), the user’s one or the system default) and calls
|
||||
|
|
|
@ -80,6 +80,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_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);
|
||||
|
@ -90,4 +91,5 @@ CFGFUN(bar_status_command, const char *command);
|
|||
CFGFUN(bar_binding_mode_indicator, const char *value);
|
||||
CFGFUN(bar_workspace_buttons, const char *value);
|
||||
CFGFUN(bar_strip_workspace_numbers, const char *value);
|
||||
CFGFUN(bar_start);
|
||||
CFGFUN(bar_finish);
|
||||
|
|
|
@ -393,7 +393,7 @@ state BARBRACE:
|
|||
end
|
||||
->
|
||||
'{'
|
||||
-> BAR
|
||||
-> call cfg_bar_start(); BAR
|
||||
|
||||
state BAR:
|
||||
end ->
|
||||
|
@ -409,6 +409,7 @@ state BAR:
|
|||
'modifier' -> BAR_MODIFIER
|
||||
'wheel_up_cmd' -> BAR_WHEEL_UP_CMD
|
||||
'wheel_down_cmd' -> BAR_WHEEL_DOWN_CMD
|
||||
'bindsym' -> BAR_BINDSYM
|
||||
'position' -> BAR_POSITION
|
||||
'output' -> BAR_OUTPUT
|
||||
'tray_output' -> BAR_TRAY_OUTPUT
|
||||
|
@ -463,6 +464,14 @@ state BAR_WHEEL_DOWN_CMD:
|
|||
command = string
|
||||
-> call cfg_bar_wheel_down_cmd($command); BAR
|
||||
|
||||
state BAR_BINDSYM:
|
||||
button = word
|
||||
-> BAR_BINDSYM_COMMAND
|
||||
|
||||
state BAR_BINDSYM_COMMAND:
|
||||
command = string
|
||||
-> call cfg_bar_bindsym($button, $command); BAR
|
||||
|
||||
state BAR_POSITION:
|
||||
position = 'top', 'bottom'
|
||||
-> call cfg_bar_position($position); BAR
|
||||
|
|
|
@ -530,14 +530,44 @@ CFGFUN(bar_modifier, const char *modifier) {
|
|||
current_bar.modifier = M_SHIFT;
|
||||
}
|
||||
|
||||
static void bar_configure_binding(const char *button, 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;
|
||||
}
|
||||
|
||||
int input_code = atoi(button + strlen("button"));
|
||||
if (input_code < 1) {
|
||||
ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button);
|
||||
return;
|
||||
}
|
||||
|
||||
struct Barbinding *current;
|
||||
TAILQ_FOREACH(current, &(current_bar.bar_bindings), bindings) {
|
||||
if (current->input_code == input_code) {
|
||||
ELOG("command for button %s was already specified, ignoring.\n", button);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct Barbinding *new_binding = scalloc(sizeof(struct Barbinding));
|
||||
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) {
|
||||
FREE(current_bar.wheel_up_cmd);
|
||||
current_bar.wheel_up_cmd = sstrdup(command);
|
||||
ELOG("'wheel_up_cmd' is deprecated. Please us 'bindsym button4 %s' instead.\n", command);
|
||||
bar_configure_binding("button4", command);
|
||||
}
|
||||
|
||||
CFGFUN(bar_wheel_down_cmd, const char *command) {
|
||||
FREE(current_bar.wheel_down_cmd);
|
||||
current_bar.wheel_down_cmd = sstrdup(command);
|
||||
ELOG("'wheel_down_cmd' is deprecated. Please us 'bindsym button5 %s' instead.\n", command);
|
||||
bar_configure_binding("button5", command);
|
||||
}
|
||||
|
||||
CFGFUN(bar_bindsym, const char *button, const char *command) {
|
||||
bar_configure_binding(button, command);
|
||||
}
|
||||
|
||||
CFGFUN(bar_position, const char *position) {
|
||||
|
@ -611,6 +641,10 @@ CFGFUN(bar_strip_workspace_numbers, const char *value) {
|
|||
current_bar.strip_workspace_numbers = eval_boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(bar_start) {
|
||||
TAILQ_INIT(&(current_bar.bar_bindings));
|
||||
}
|
||||
|
||||
CFGFUN(bar_finish) {
|
||||
DLOG("\t new bar configuration finished, saving.\n");
|
||||
/* Generate a unique ID for this bar if not already configured */
|
||||
|
|
32
src/ipc.c
32
src/ipc.c
|
@ -469,6 +469,28 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
|
|||
y(map_close);
|
||||
}
|
||||
|
||||
static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
|
||||
if (TAILQ_EMPTY(&(config->bar_bindings)))
|
||||
return;
|
||||
|
||||
ystr("bindings");
|
||||
y(array_open);
|
||||
|
||||
struct Barbinding *current;
|
||||
TAILQ_FOREACH(current, &(config->bar_bindings), bindings) {
|
||||
y(map_open);
|
||||
|
||||
ystr("input_code");
|
||||
y(integer, current->input_code);
|
||||
ystr("command");
|
||||
ystr(current->command);
|
||||
|
||||
y(map_close);
|
||||
}
|
||||
|
||||
y(array_close);
|
||||
}
|
||||
|
||||
static void dump_bar_config(yajl_gen gen, Barconfig *config) {
|
||||
y(map_open);
|
||||
|
||||
|
@ -549,15 +571,7 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
|
|||
break;
|
||||
}
|
||||
|
||||
if (config->wheel_up_cmd) {
|
||||
ystr("wheel_up_cmd");
|
||||
ystr(config->wheel_up_cmd);
|
||||
}
|
||||
|
||||
if (config->wheel_down_cmd) {
|
||||
ystr("wheel_down_cmd");
|
||||
ystr(config->wheel_down_cmd);
|
||||
}
|
||||
dump_bar_bindings(gen, config);
|
||||
|
||||
ystr("position");
|
||||
if (config->position == P_BOTTOM)
|
||||
|
|
|
@ -688,8 +688,9 @@ bar {
|
|||
EOT
|
||||
|
||||
$expected = <<'EOT';
|
||||
cfg_bar_start()
|
||||
cfg_bar_output(LVDS-1)
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'position', 'output', 'tray_output', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'verbose', 'colors', '}'
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'verbose', 'colors', '}'
|
||||
ERROR: CONFIG: (in file <stdin>)
|
||||
ERROR: CONFIG: Line 1: bar {
|
||||
ERROR: CONFIG: Line 2: output LVDS-1
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
#
|
||||
# Please read the following documents before working on tests:
|
||||
# • http://build.i3wm.org/docs/testsuite.html
|
||||
# (or docs/testsuite)
|
||||
#
|
||||
# • http://build.i3wm.org/docs/lib-i3test.html
|
||||
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||
#
|
||||
# • http://build.i3wm.org/docs/ipc.html
|
||||
# (or docs/ipc)
|
||||
#
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Ensures that mouse bindings on the i3bar work correctly.
|
||||
# Ticket: #1695
|
||||
use i3test i3_autostart => 0;
|
||||
|
||||
my ($cv, $timer);
|
||||
sub reset_test {
|
||||
$cv = AE::cv;
|
||||
$timer = AE::timer(1, 0, sub { $cv->send(0); });
|
||||
}
|
||||
|
||||
my $config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
focus_follows_mouse no
|
||||
|
||||
bar {
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
position top
|
||||
|
||||
bindsym button1 focus left
|
||||
bindsym button2 focus right
|
||||
bindsym button3 focus left
|
||||
bindsym button4 focus right
|
||||
bindsym button5 focus left
|
||||
}
|
||||
EOT
|
||||
|
||||
SKIP: {
|
||||
qx(command -v xdotool 2> /dev/null);
|
||||
skip 'xdotool is required for this test', 1 if $?;
|
||||
|
||||
my $pid = launch_with_config($config);
|
||||
my $i3 = i3(get_socket_path());
|
||||
$i3->connect()->recv;
|
||||
my $ws = fresh_workspace;
|
||||
|
||||
reset_test;
|
||||
$i3->subscribe({
|
||||
window => sub {
|
||||
my ($event) = @_;
|
||||
if ($event->{change} eq 'focus') {
|
||||
$cv->send($event->{container});
|
||||
}
|
||||
},
|
||||
})->recv;
|
||||
|
||||
my $left = open_window;
|
||||
my $right = open_window;
|
||||
sync_with_i3;
|
||||
my $con = $cv->recv;
|
||||
is($con->{window}, $right->{id}, 'focus is initially on the right container');
|
||||
reset_test;
|
||||
|
||||
qx(xdotool mousemove 3 3 click 1);
|
||||
sync_with_i3;
|
||||
$con = $cv->recv;
|
||||
is($con->{window}, $left->{id}, 'button 1 moves focus left');
|
||||
reset_test;
|
||||
|
||||
qx(xdotool mousemove 3 3 click 2);
|
||||
sync_with_i3;
|
||||
$con = $cv->recv;
|
||||
is($con->{window}, $right->{id}, 'button 2 moves focus right');
|
||||
reset_test;
|
||||
|
||||
qx(xdotool mousemove 3 3 click 3);
|
||||
sync_with_i3;
|
||||
$con = $cv->recv;
|
||||
is($con->{window}, $left->{id}, 'button 3 moves focus left');
|
||||
reset_test;
|
||||
|
||||
qx(xdotool mousemove 3 3 click 4);
|
||||
sync_with_i3;
|
||||
$con = $cv->recv;
|
||||
is($con->{window}, $right->{id}, 'button 4 moves focus right');
|
||||
reset_test;
|
||||
|
||||
qx(xdotool mousemove 3 3 click 5);
|
||||
sync_with_i3;
|
||||
$con = $cv->recv;
|
||||
is($con->{window}, $left->{id}, 'button 5 moves focus left');
|
||||
reset_test;
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
}
|
||||
|
||||
done_testing;
|
Loading…
Reference in New Issue