Merge pull request #3005 from stapelberg/tick

Implement the tick event
This commit is contained in:
Michael Stapelberg 2017-09-30 10:16:43 -07:00 committed by GitHub
commit ffa228e653
32 changed files with 618 additions and 742 deletions

View File

@ -99,11 +99,12 @@ use constant TYPE_GET_BAR_CONFIG => 6;
use constant TYPE_GET_VERSION => 7; use constant TYPE_GET_VERSION => 7;
use constant TYPE_GET_BINDING_MODES => 8; use constant TYPE_GET_BINDING_MODES => 8;
use constant TYPE_GET_CONFIG => 9; use constant TYPE_GET_CONFIG => 9;
use constant TYPE_SEND_TICK => 10;
our %EXPORT_TAGS = ( 'all' => [ our %EXPORT_TAGS = ( 'all' => [
qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
TYPE_GET_BINDING_MODES TYPE_GET_CONFIG) TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK)
] ); ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
@ -120,6 +121,7 @@ my %events = (
barconfig_update => ($event_mask | 4), barconfig_update => ($event_mask | 4),
binding => ($event_mask | 5), binding => ($event_mask | 5),
shutdown => ($event_mask | 6), shutdown => ($event_mask | 6),
tick => ($event_mask | 7),
_error => 0xFFFFFFFF, _error => 0xFFFFFFFF,
); );
@ -519,6 +521,18 @@ sub get_config {
$self->message(TYPE_GET_CONFIG); $self->message(TYPE_GET_CONFIG);
} }
=head2 send_tick
Sends a tick event. Requires i3 >= 4.15
=cut
sub send_tick {
my ($self, $payload) = @_;
$self->_ensure_connection;
$self->message(TYPE_SEND_TICK, $payload);
}
=head2 command($content) =head2 command($content)

View File

@ -64,6 +64,7 @@ to do that).
| 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version. | 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version.
| 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes. | 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes.
| 9 | +GET_CONFIG+ | <<_config_reply,CONFIG>> | Returns the last loaded i3 config. | 9 | +GET_CONFIG+ | <<_config_reply,CONFIG>> | Returns the last loaded i3 config.
| 10 | +SEND_TICK+ | <<_tick_reply,TICK>> | Sends a tick event with the specified payload.
|====================================================== |======================================================
So, a typical message could look like this: So, a typical message could look like this:
@ -126,6 +127,8 @@ BINDING_MODES (8)::
Reply to the GET_BINDING_MODES message. Reply to the GET_BINDING_MODES message.
GET_CONFIG (9):: GET_CONFIG (9)::
Reply to the GET_CONFIG message. Reply to the GET_CONFIG message.
TICK (10)::
Reply to the SEND_TICK message.
[[_command_reply]] [[_command_reply]]
=== COMMAND reply === COMMAND reply
@ -637,6 +640,19 @@ which is a string containing the config file as loaded by i3 most recently.
{ "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" } { "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
------------------- -------------------
[[_tick_reply]]
=== TICK reply
The reply is a map containing the "success" member. After the reply was
received, the tick event has been written to all IPC connections which subscribe
to tick events. UNIX sockets are usually buffered, but you can be certain that
once you receive the tick event you just triggered, you must have received all
events generated prior to the +SEND_TICK+ message (happened-before relation).
*Example:*
-------------------
{ "success": true }
-------------------
== Events == Events
@ -694,6 +710,10 @@ binding (5)::
mouse mouse
shutdown (6):: shutdown (6)::
Sent when the ipc shuts down because of a restart or exit by user command Sent when the ipc shuts down because of a restart or exit by user command
tick (7)::
Sent when the ipc client subscribes to the tick event (with +"first":
true+) or when any ipc client sends a SEND_TICK message (with +"first":
false+).
*Example:* *Example:*
-------------------------------------------------------------------- --------------------------------------------------------------------
@ -866,6 +886,27 @@ because of a user action such as a +restart+ or +exit+ command. The +change
} }
--------------------------- ---------------------------
=== tick event
This event is triggered by a subscription to tick events or by a +SEND_TICK+
message.
*Example (upon subscription):*
--------------------------------------------------------------------------------
{
"first": true,
"payload": ""
}
--------------------------------------------------------------------------------
*Example (upon +SEND_TICK+ with a payload of +arbitrary string+):*
--------------------------------------------------------------------------------
{
"first": false,
"payload": "arbitrary string"
}
--------------------------------------------------------------------------------
== See also (existing libraries) == See also (existing libraries)
[[libraries]] [[libraries]]

View File

@ -207,9 +207,11 @@ int main(int argc, char *argv[]) {
message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION; message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
} else if (strcasecmp(optarg, "get_config") == 0) { } else if (strcasecmp(optarg, "get_config") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG; message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
} else if (strcasecmp(optarg, "send_tick") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK;
} else { } else {
printf("Unknown message type\n"); printf("Unknown message type\n");
printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n"); printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config, send_tick\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} else if (o == 'q') { } else if (o == 'q') {

View File

@ -9,4 +9,5 @@ ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
ATOM_DO(_NET_SYSTEM_TRAY_COLORS) ATOM_DO(_NET_SYSTEM_TRAY_COLORS)
ATOM_DO(_XEMBED_INFO) ATOM_DO(_XEMBED_INFO)
ATOM_DO(_XEMBED) ATOM_DO(_XEMBED)
ATOM_DO(I3_SYNC)
#undef ATOM_DO #undef ATOM_DO

View File

@ -678,8 +678,26 @@ static void configure_trayclients(void) {
* *
*/ */
static void handle_client_message(xcb_client_message_event_t *event) { static void handle_client_message(xcb_client_message_event_t *event) {
if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] && if (event->type == atoms[I3_SYNC]) {
event->format == 32) { xcb_window_t window = event->data.data32[0];
uint32_t rnd = event->data.data32[1];
DLOG("[i3 sync protocol] Forwarding random value %d, X11 window 0x%08x to i3\n", rnd, window);
void *reply = scalloc(32, 1);
xcb_client_message_event_t *ev = reply;
ev->response_type = XCB_CLIENT_MESSAGE;
ev->window = window;
ev->type = atoms[I3_SYNC];
ev->format = 32;
ev->data.data32[0] = window;
ev->data.data32[1] = rnd;
xcb_send_event(conn, false, xcb_root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)ev);
xcb_flush(conn);
free(reply);
} else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
event->format == 32) {
DLOG("_NET_SYSTEM_TRAY_OPCODE received\n"); DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
/* event->data.data32[0] is the timestamp */ /* event->data.data32[0] is the timestamp */
uint32_t op = event->data.data32[1]; uint32_t op = event->data.data32[1];

View File

@ -60,6 +60,9 @@ typedef struct i3_ipc_header {
/** Request the raw last loaded i3 config. */ /** Request the raw last loaded i3 config. */
#define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9 #define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9
/** Send a tick event to all subscribers. */
#define I3_IPC_MESSAGE_TYPE_SEND_TICK 10
/* /*
* Messages from i3 to clients * Messages from i3 to clients
* *
@ -74,6 +77,7 @@ typedef struct i3_ipc_header {
#define I3_IPC_REPLY_TYPE_VERSION 7 #define I3_IPC_REPLY_TYPE_VERSION 7
#define I3_IPC_REPLY_TYPE_BINDING_MODES 8 #define I3_IPC_REPLY_TYPE_BINDING_MODES 8
#define I3_IPC_REPLY_TYPE_CONFIG 9 #define I3_IPC_REPLY_TYPE_CONFIG 9
#define I3_IPC_REPLY_TYPE_TICK 10
/* /*
* Events from i3 to clients. Events have the first bit set high. * Events from i3 to clients. Events have the first bit set high.
@ -101,3 +105,6 @@ typedef struct i3_ipc_header {
/** The shutdown event will be triggered when the ipc shuts down */ /** The shutdown event will be triggered when the ipc shuts down */
#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6) #define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6)
/** The tick event will be sent upon a tick IPC message */
#define I3_IPC_EVENT_TICK (I3_IPC_EVENT_MASK | 7)

View File

@ -31,6 +31,10 @@ typedef struct ipc_client {
int num_events; int num_events;
char **events; char **events;
/* For clients which subscribe to the tick event: whether the first tick
* event has been sent by i3. */
bool first_tick_sent;
TAILQ_ENTRY(ipc_client) TAILQ_ENTRY(ipc_client)
clients; clients;
} ipc_client; } ipc_client;

View File

@ -1046,8 +1046,9 @@ static int add_subscription(void *extra, const unsigned char *s,
memcpy(client->events[event], s, len); memcpy(client->events[event], s, len);
DLOG("client is now subscribed to:\n"); DLOG("client is now subscribed to:\n");
for (int i = 0; i < client->num_events; i++) for (int i = 0; i < client->num_events; i++) {
DLOG("event %s\n", client->events[i]); DLOG("event %s\n", client->events[i]);
}
DLOG("(done)\n"); DLOG("(done)\n");
return 1; return 1;
@ -1099,6 +1100,25 @@ IPC_HANDLER(subscribe) {
yajl_free(p); yajl_free(p);
const char *reply = "{\"success\":true}"; const char *reply = "{\"success\":true}";
ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply); ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
if (client->first_tick_sent) {
return;
}
bool is_tick = false;
for (int i = 0; i < client->num_events; i++) {
if (strcmp(client->events[i], "tick") == 0) {
is_tick = true;
break;
}
}
if (!is_tick) {
return;
}
client->first_tick_sent = true;
const char *payload = "{\"first\":true,\"payload\":\"\"}";
ipc_send_message(client->fd, strlen(payload), I3_IPC_EVENT_TICK, (const uint8_t *)payload);
} }
/* /*
@ -1122,9 +1142,35 @@ IPC_HANDLER(get_config) {
y(free); y(free);
} }
/*
* Sends the tick event from the message payload to subscribers. Establishes a
* synchronization point in event-related tests.
*/
IPC_HANDLER(send_tick) {
yajl_gen gen = ygenalloc();
y(map_open);
ystr("payload");
yajl_gen_string(gen, (unsigned char *)message, message_size);
y(map_close);
const unsigned char *payload;
ylength length;
y(get_buf, &payload, &length);
ipc_send_event("tick", I3_IPC_EVENT_TICK, (const char *)payload);
y(free);
const char *reply = "{\"success\":true}";
ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_TICK, (const uint8_t *)reply);
DLOG("Sent tick event\n");
}
/* The index of each callback function corresponds to the numeric /* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */ * value of the message type (see include/i3/ipc.h) */
handler_t handlers[10] = { handler_t handlers[11] = {
handle_run_command, handle_run_command,
handle_get_workspaces, handle_get_workspaces,
handle_subscribe, handle_subscribe,
@ -1135,6 +1181,7 @@ handler_t handlers[10] = {
handle_get_version, handle_get_version,
handle_get_binding_modes, handle_get_binding_modes,
handle_get_config, handle_get_config,
handle_send_tick,
}; };
/* /*

View File

@ -47,6 +47,8 @@ our @EXPORT = qw(
wait_for_unmap wait_for_unmap
$x $x
kill_all_windows kill_all_windows
events_for
listen_for_binding
); );
=head1 NAME =head1 NAME
@ -900,6 +902,86 @@ sub kill_all_windows {
cmd '[title=".*"] kill'; cmd '[title=".*"] kill';
} }
=head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ])
Helper function which returns an array containing all events of type $rettype
which were generated by i3 while $subscribecb was running.
Set $eventcbs to subscribe to multiple event types and/or perform your own event
aggregation.
=cut
sub events_for {
my ($subscribecb, $rettype, $eventcbs) = @_;
my @events;
$eventcbs //= {};
if (defined($rettype)) {
$eventcbs->{$rettype} = sub { push @events, shift };
}
my $subscribed = AnyEvent->condvar;
my $flushed = AnyEvent->condvar;
$eventcbs->{tick} = sub {
my ($event) = @_;
if ($event->{first}) {
$subscribed->send($event);
} else {
$flushed->send($event);
}
};
my $i3 = i3(get_socket_path(0));
$i3->connect->recv;
$i3->subscribe($eventcbs)->recv;
$subscribed->recv;
# Subscription established, run the callback.
$subscribecb->();
# Now generate a tick event, which we know well receive (and at which point
# all other events have been received).
my $nonce = int(rand(255)) + 1;
$i3->send_tick($nonce);
my $tick = $flushed->recv;
$tester->is_eq($tick->{payload}, $nonce, 'tick nonce received');
return @events;
}
=head2 listen_for_binding($cb)
Helper function to evaluate whether sending KeyPress/KeyRelease events via XTEST
triggers an i3 key binding or not. Expects key bindings to be configured in the
form bindsym <binding> nop <binding>, e.g. bindsym Mod4+Return nop
Mod4+Return.
is(listen_for_binding(
sub {
xtest_key_press(133); # Super_L
xtest_key_press(36); # Return
xtest_key_release(36); # Return
xtest_key_release(133); # Super_L
xtest_sync_with_i3;
},
),
'Mod4+Return',
'triggered the "Mod4+Return" keybinding');
=cut
sub listen_for_binding {
my ($cb) = @_;
my $triggered = AnyEvent->condvar;
my @events = events_for(
$cb,
'binding');
$tester->is_eq(scalar @events, 1, 'Received precisely one event');
$tester->is_eq($events[0]->{change}, 'run', 'change is "run"');
# We look at the command (which is “nop <binding>”) because that is easier
# than re-assembling the string representation of $event->{binding}.
my $command = $events[0]->{binding}->{command};
$command =~ s/^nop //g;
return $command;
}
=head1 AUTHOR =head1 AUTHOR
Michael Stapelberg <michael@i3wm.org> Michael Stapelberg <michael@i3wm.org>

View File

@ -14,13 +14,13 @@ use ExtUtils::PkgConfig;
use Exporter (); use Exporter ();
our @EXPORT = qw( our @EXPORT = qw(
inlinec_connect inlinec_connect
xtest_sync_with
xtest_sync_with_i3
set_xkb_group set_xkb_group
xtest_key_press xtest_key_press
xtest_key_release xtest_key_release
xtest_button_press xtest_button_press
xtest_button_release xtest_button_release
listen_for_binding
start_binding_capture
binding_events binding_events
); );
@ -38,7 +38,7 @@ i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb
# ineffective. # ineffective.
my %sn_config; my %sn_config;
BEGIN { BEGIN {
%sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest'); %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest xcb-util');
} }
use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags}; use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
@ -53,8 +53,12 @@ use Inline C => <<'END_OF_C_CODE';
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xkb.h> #include <xcb/xkb.h>
#include <xcb/xtest.h> #include <xcb/xtest.h>
#include <xcb/xcb_aux.h>
static xcb_connection_t *conn = NULL; static xcb_connection_t *conn = NULL;
static xcb_window_t sync_window;
static xcb_window_t root_window;
static xcb_atom_t i3_sync_atom;
bool inlinec_connect() { bool inlinec_connect() {
int screen; int screen;
@ -89,9 +93,94 @@ bool inlinec_connect() {
} }
free(usereply); free(usereply);
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, xcb_intern_atom(conn, 0, strlen("I3_SYNC"), "I3_SYNC"), NULL);
i3_sync_atom = reply->atom;
free(reply);
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
root_window = root_screen->root;
sync_window = xcb_generate_id(conn);
xcb_create_window(conn,
XCB_COPY_FROM_PARENT, // depth
sync_window, // window
root_window, // parent
-15, // x
-15, // y
1, // width
1, // height
0, // border_width
XCB_WINDOW_CLASS_INPUT_OUTPUT, // class
XCB_COPY_FROM_PARENT, // visual
XCB_CW_OVERRIDE_REDIRECT, // value_mask
(uint32_t[]){
1, // override_redirect
}); // value_list
return true; return true;
} }
void xtest_sync_with(int window) {
xcb_client_message_event_t ev;
memset(&ev, '\0', sizeof(xcb_client_message_event_t));
const int nonce = rand() % 255;
ev.response_type = XCB_CLIENT_MESSAGE;
ev.window = sync_window;
ev.type = i3_sync_atom;
ev.format = 32;
ev.data.data32[0] = sync_window;
ev.data.data32[1] = nonce;
xcb_send_event(conn, false, (xcb_window_t)window, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev);
xcb_flush(conn);
xcb_generic_event_t *event = NULL;
while (1) {
free(event);
if ((event = xcb_wait_for_event(conn)) == NULL) {
break;
}
if (event->response_type == 0) {
fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
continue;
}
/* Strip off the highest bit (set if the event is generated) */
const int type = (event->response_type & 0x7F);
switch (type) {
case XCB_CLIENT_MESSAGE: {
xcb_client_message_event_t *ev = (xcb_client_message_event_t *)event;
{
const uint32_t got = ev->data.data32[0];
const uint32_t want = sync_window;
if (got != want) {
fprintf(stderr, "Ignoring ClientMessage: unknown window: got %d, want %d\n", got, want);
continue;
}
}
{
const uint32_t got = ev->data.data32[1];
const uint32_t want = nonce;
if (got != want) {
fprintf(stderr, "Ignoring ClientMessage: unknown nonce: got %d, want %d\n", got, want);
continue;
}
}
return;
}
default:
fprintf(stderr, "Unexpected X11 event of type %d received (XCB_CLIENT_MESSAGE = %d)\n", type, XCB_CLIENT_MESSAGE);
break;
}
}
free(event);
}
void xtest_sync_with_i3() {
xtest_sync_with((int)root_window);
}
// NOTE: while |group| should be a uint8_t, Inline::C will not define the // NOTE: while |group| should be a uint8_t, Inline::C will not define the
// function unless we use an int. // function unless we use an int.
bool set_xkb_group(int group) { bool set_xkb_group(int group) {
@ -170,86 +259,6 @@ sub import {
=cut =cut
my $i3;
our @binding_events;
=head2 start_binding_capture()
Captures all binding events sent by i3 in the C<@binding_events> symbol, so
that you can verify the correct number of binding events was generated.
my $pid = launch_with_config($config);
start_binding_capture;
# …
sync_with_i3;
is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events');
=cut
sub start_binding_capture {
# Store a copy of each binding event so that we can count the expected
# events in test cases.
$i3 = i3(get_socket_path());
$i3->connect()->recv;
$i3->subscribe({
binding => sub {
my ($event) = @_;
@binding_events = (@binding_events, $event);
},
})->recv;
}
=head2 listen_for_binding($cb)
Helper function to evaluate whether sending KeyPress/KeyRelease events via
XTEST triggers an i3 key binding or not (with a timeout of 0.5s). Expects key
bindings to be configured in the form bindsym <binding> nop <binding>, e.g.
bindsym Mod4+Return nop Mod4+Return.
is(listen_for_binding(
sub {
xtest_key_press(133); # Super_L
xtest_key_press(36); # Return
xtest_key_release(36); # Return
xtest_key_release(133); # Super_L
},
),
'Mod4+Return',
'triggered the "Mod4+Return" keybinding');
=cut
sub listen_for_binding {
my ($cb) = @_;
my $triggered = AnyEvent->condvar;
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
$i3->subscribe({
binding => sub {
my ($event) = @_;
return unless $event->{change} eq 'run';
# We look at the command (which is “nop <binding>”) because that is
# easier than re-assembling the string representation of
# $event->{binding}.
$triggered->send($event->{binding}->{command});
},
})->recv;
my $t;
$t = AnyEvent->timer(
after => 0.5,
cb => sub {
$triggered->send('timeout');
}
);
$cb->();
my $recv = $triggered->recv;
$recv =~ s/^nop //g;
return $recv;
}
=head2 set_xkb_group($group) =head2 set_xkb_group($group)
Changes the current XKB group from the default of 1 to C<$group>, which must be Changes the current XKB group from the default of 1 to C<$group>, which must be
@ -283,6 +292,15 @@ Sends a ButtonRelease event via XTEST, with the specified C<$button>.
Returns false when there was an X11 error, true otherwise. Returns false when there was an X11 error, true otherwise.
=head2 xtest_sync_with($window)
Ensures the specified window has processed all X11 events which were triggered
by this module, provided the window response to the i3 sync protocol.
=head2 xtest_sync_with_i3()
Ensures i3 has processed all X11 events which were triggered by this module.
=head1 AUTHOR =head1 AUTHOR
Michael Stapelberg <michael@i3wm.org> Michael Stapelberg <michael@i3wm.org>

View File

@ -16,61 +16,25 @@
use i3test; use i3test;
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
################################
# Workspaces requests and events
################################
my $old_ws = get_ws(focused_ws()); my $old_ws = get_ws(focused_ws());
# Events
# We are switching to an empty workpspace from an empty workspace, so we expect # We are switching to an empty workpspace from an empty workspace, so we expect
# to receive "init", "focus", and "empty". # to receive "init", "focus", and "empty".
my $init = AnyEvent->condvar; my @events = events_for(
my $focus = AnyEvent->condvar; sub { cmd 'workspace 2' },
my $empty = AnyEvent->condvar; 'workspace');
$i3->subscribe({
workspace => sub {
my ($event) = @_;
if ($event->{change} eq 'init') {
$init->send($event);
} elsif ($event->{change} eq 'focus') {
$focus->send($event);
} elsif ($event->{change} eq 'empty') {
$empty->send($event);
}
}
})->recv;
cmd 'workspace 2';
my $t;
$t = AnyEvent->timer(
after => 0.5,
cb => sub {
$init->send(0);
$focus->send(0);
$empty->send(0);
}
);
my $init_event = $init->recv;
my $focus_event = $focus->recv;
my $empty_event = $empty->recv;
my $current_ws = get_ws(focused_ws()); my $current_ws = get_ws(focused_ws());
ok($init_event, 'workspace "init" event received'); is(scalar @events, 3, 'Received 3 events');
is($init_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the initted workspace con'); is($events[0]->{change}, 'init', 'First event has change = init');
is($events[0]->{current}->{id}, $current_ws->{id}, 'the "current" property contains the initted workspace con');
ok($focus_event, 'workspace "focus" event received'); is($events[1]->{change}, 'focus', 'Second event has change = focus');
is($focus_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con'); is($events[1]->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con');
is($focus_event->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last'); is($events[1]->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last');
ok($empty_event, 'workspace "empty" event received'); is($events[2]->{change}, 'empty', 'Third event has change = empty');
is($empty_event->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con'); is($events[2]->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
done_testing; done_testing;

View File

@ -28,24 +28,11 @@ mode "with spaces" {
} }
EOT EOT
my $i3 = i3(get_socket_path(0)); my @events = events_for(
$i3->connect->recv; sub { cmd 'mode "m1"' },
'mode');
my $cv = AnyEvent->condvar; my @changes = map { $_->{change} } @events;
is_deeply(\@changes, [ 'm1' ], 'Mode event received');
$i3->subscribe({
mode => sub {
my ($event) = @_;
$cv->send($event->{change} eq 'm1');
}
})->recv;
cmd 'mode "m1"';
# Timeout after 0.5s
my $t;
$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
ok($cv->recv, 'Mode event received');
done_testing; done_testing;

View File

@ -16,46 +16,15 @@
use i3test; use i3test;
SKIP: {
skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
################################
# Window event
################################
# Events
my $new = AnyEvent->condvar; my $new = AnyEvent->condvar;
my $focus = AnyEvent->condvar; my $focus = AnyEvent->condvar;
$i3->subscribe({
window => sub {
my ($event) = @_;
if ($event->{change} eq 'new') {
$new->send($event);
} elsif ($event->{change} eq 'focus') {
$focus->send($event);
}
}
})->recv;
open_window; my @events = events_for(
sub { open_window },
'window');
my $t; is(scalar @events, 2, 'Received 2 events');
$t = AnyEvent->timer( is($events[0]->{container}->{focused}, 0, 'Window "new" event received');
after => 0.5, is($events[1]->{container}->{focused}, 1, 'Window "focus" event received');
cb => sub {
$new->send(0);
$focus->send(0);
}
);
is($new->recv->{container}->{focused}, 0, 'Window "new" event received');
is($focus->recv->{container}->{focused}, 1, 'Window "focus" event received');
}
done_testing; done_testing;

View File

@ -16,13 +16,6 @@
use i3test; use i3test;
SKIP: {
skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
################################ ################################
# Window focus event # Window focus event
################################ ################################
@ -33,62 +26,29 @@ my $win0 = open_window;
my $win1 = open_window; my $win1 = open_window;
my $win2 = open_window; my $win2 = open_window;
my $focus = AnyEvent->condvar;
$i3->subscribe({
window => sub {
my ($event) = @_;
$focus->send($event);
}
})->recv;
my $t;
$t = AnyEvent->timer(
after => 0.5,
cb => sub {
$focus->send(0);
}
);
# ensure the rightmost window contains input focus # ensure the rightmost window contains input focus
$i3->command('[id="' . $win2->id . '"] focus')->recv; cmd '[id="' . $win2->id . '"] focus';
is($x->input_focus, $win2->id, "Window 2 focused"); is($x->input_focus, $win2->id, "Window 2 focused");
cmd 'focus left'; sub focus_subtest {
my $event = $focus->recv; my ($cmd, $name) = @_;
is($event->{change}, 'focus', 'Focus event received');
is($focus->recv->{container}->{name}, 'Window 1', 'Window 1 focused');
$focus = AnyEvent->condvar; my $focus = AnyEvent->condvar;
cmd 'focus left';
$event = $focus->recv;
is($event->{change}, 'focus', 'Focus event received');
is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
$focus = AnyEvent->condvar; my @events = events_for(
cmd 'focus right'; sub { cmd $cmd },
$event = $focus->recv; 'window');
is($event->{change}, 'focus', 'Focus event received');
is($event->{container}->{name}, 'Window 1', 'Window 1 focused');
$focus = AnyEvent->condvar;
cmd 'focus right';
$event = $focus->recv;
is($event->{change}, 'focus', 'Focus event received');
is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
$focus = AnyEvent->condvar;
cmd 'focus right';
$event = $focus->recv;
is($event->{change}, 'focus', 'Focus event received');
is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
$focus = AnyEvent->condvar;
cmd 'focus left';
$event = $focus->recv;
is($event->{change}, 'focus', 'Focus event received');
is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
is(scalar @events, 1, 'Received 1 event');
is($events[0]->{change}, 'focus', 'Focus event received');
is($events[0]->{container}->{name}, $name, "$name focused");
} }
subtest 'focus left (1)', \&focus_subtest, 'focus left', 'Window 1';
subtest 'focus left (2)', \&focus_subtest, 'focus left', 'Window 0';
subtest 'focus right (1)', \&focus_subtest, 'focus right', 'Window 1';
subtest 'focus right (2)', \&focus_subtest, 'focus right', 'Window 2';
subtest 'focus right (3)', \&focus_subtest, 'focus right', 'Window 0';
subtest 'focus left', \&focus_subtest, 'focus left', 'Window 2';
done_testing; done_testing;

View File

@ -16,42 +16,17 @@
use i3test; use i3test;
SKIP: {
skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
################################
# Window title event
################################
my $window = open_window(name => 'Window 0'); my $window = open_window(name => 'Window 0');
my $title = AnyEvent->condvar; my @events = events_for(
sub {
$window->name('New Window Title');
sync_with_i3;
},
'window');
$i3->subscribe({ is(scalar @events, 1, 'Received 1 event');
window => sub { is($events[0]->{change}, 'title', 'Window title change event received');
my ($event) = @_; is($events[0]->{container}->{name}, 'New Window Title', 'Window title changed');
$title->send($event);
}
})->recv;
$window->name('New Window Title');
my $t;
$t = AnyEvent->timer(
after => 0.5,
cb => sub {
$title->send(0);
}
);
my $event = $title->recv;
is($event->{change}, 'title', 'Window title change event received');
is($event->{container}->{name}, 'New Window Title', 'Window title changed');
}
done_testing; done_testing;

View File

@ -19,41 +19,19 @@
# Bug still in: 4.7.2-135-g7deb23c # Bug still in: 4.7.2-135-g7deb23c
use i3test; use i3test;
my $i3 = i3(get_socket_path()); open_window;
$i3->connect()->recv;
my $cv; sub fullscreen_subtest {
my $t; my ($want) = @_;
my @events = events_for(
sub { cmd 'fullscreen' },
'window');
sub reset_test { is(scalar @events, 1, 'Received 1 event');
$cv = AE::cv; is($events[0]->{container}->{fullscreen_mode}, $want, "fullscreen_mode now $want");
$t = AE::timer(0.5, 0, sub { $cv->send(0); });
} }
reset_test; subtest 'fullscreen on', \&fullscreen_subtest, 1;
subtest 'fullscreen off', \&fullscreen_subtest, 0;
$i3->subscribe({
window => sub {
my ($e) = @_;
if ($e->{change} eq 'fullscreen_mode') {
$cv->send($e->{container});
}
},
})->recv;
my $window = open_window;
cmd 'fullscreen';
my $con = $cv->recv;
ok($con, 'got fullscreen window event (on)');
is($con->{fullscreen_mode}, 1, 'window is fullscreen');
reset_test;
cmd 'fullscreen';
$con = $cv->recv;
ok($con, 'got fullscreen window event (off)');
is($con->{fullscreen_mode}, 0, 'window is not fullscreen');
done_testing; done_testing;

View File

@ -18,12 +18,8 @@
# #
use i3test; use i3test;
SKIP: {
skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
################################################################################ ################################################################################
# check that the workspace empty event is send upon workspace switch when the # check that the workspace empty event is sent upon workspace switch when the
# old workspace is empty # old workspace is empty
################################################################################ ################################################################################
subtest 'Workspace empty event upon switch', sub { subtest 'Workspace empty event upon switch', sub {
@ -35,26 +31,17 @@ subtest 'Workspace empty event upon switch', sub {
cmd '[id="' . $w1->id . '"] kill'; cmd '[id="' . $w1->id . '"] kill';
my $cond = AnyEvent->condvar; my $cond = AnyEvent->condvar;
my $client = i3(get_socket_path(0)); my @events = events_for(
$client->connect()->recv; sub { cmd "workspace $ws2" },
$client->subscribe({ 'workspace');
workspace => sub {
my ($event) = @_;
$cond->send($event);
}
})->recv;
cmd "workspace $ws2"; is(scalar @events, 2, 'Received 2 event');
is($events[1]->{change}, 'empty', '"Empty" event received upon workspace switch');
sync_with_i3; is($events[1]->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
my $event = $cond->recv;
is($event->{change}, 'empty', '"Empty" event received upon workspace switch');
is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
}; };
################################################################################ ################################################################################
# check that no workspace empty event is send upon workspace switch if the # check that no workspace empty event is sent upon workspace switch if the
# workspace is not empty # workspace is not empty
################################################################################ ################################################################################
subtest 'No workspace empty event', sub { subtest 'No workspace empty event', sub {
@ -63,36 +50,16 @@ subtest 'No workspace empty event', sub {
my $ws1 = fresh_workspace; my $ws1 = fresh_workspace;
my $w1 = open_window(); my $w1 = open_window();
my @events; my @events = events_for(
my $cond = AnyEvent->condvar; sub { cmd "workspace $ws2" },
my $client = i3(get_socket_path(0)); 'workspace');
$client->connect()->recv;
$client->subscribe({
workspace => sub {
my ($event) = @_;
push @events, $event;
}
})->recv;
# Wait for the workspace event on a new connection. Events will be delivered is(scalar @events, 1, 'Received 1 event');
# to older connections earlier, so by the time it arrives here, it should be is($events[0]->{change}, 'focus', 'Event change is "focus"');
# in @events already.
my $ws_event_block_conn = i3(get_socket_path(0));
$ws_event_block_conn->connect()->recv;
$ws_event_block_conn->subscribe({ workspace => sub { $cond->send(1) }});
cmd "workspace $ws2";
sync_with_i3;
my @expected_events = grep { $_->{change} eq 'focus' } @events;
my @empty_events = grep { $_->{change} eq 'empty' } @events;
is(@expected_events, 1, '"Focus" event received');
is(@empty_events, 0, 'No "empty" events received');
}; };
################################################################################ ################################################################################
# check that workspace empty event is send when the last window has been closed # check that workspace empty event is sent when the last window has been closed
# on invisible workspace # on invisible workspace
################################################################################ ################################################################################
subtest 'Workspace empty event upon window close', sub { subtest 'Workspace empty event upon window close', sub {
@ -101,25 +68,16 @@ subtest 'Workspace empty event upon window close', sub {
my $ws2 = fresh_workspace; my $ws2 = fresh_workspace;
my $w2 = open_window(); my $w2 = open_window();
my $cond = AnyEvent->condvar; my @events = events_for(
my $client = i3(get_socket_path(0)); sub {
$client->connect()->recv; $w1->unmap;
$client->subscribe({ sync_with_i3;
workspace => sub { },
my ($event) = @_; 'workspace');
$cond->send($event);
}
})->recv;
cmd '[id="' . $w1->id . '"] kill'; is(scalar @events, 1, 'Received 1 event');
is($events[0]->{change}, 'empty', '"Empty" event received upon window close');
sync_with_i3; is($events[0]->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
my $event = $cond->recv;
is($event->{change}, 'empty', '"Empty" event received upon window close');
is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
}; };
}
done_testing; done_testing;

View File

@ -19,41 +19,22 @@
# Bug still in: 4.8-7-gf4a8253 # Bug still in: 4.8-7-gf4a8253
use i3test; use i3test;
my $i3 = i3(get_socket_path()); sub floating_subtest {
$i3->connect->recv; my ($win, $cmd, $want) = @_;
my $cv = AnyEvent->condvar; my @events = events_for(
sub { cmd $cmd },
'window');
$i3->subscribe({ my @floating = grep { $_->{change} eq 'floating' } @events;
window => sub { is(scalar @floating, 1, 'Received 1 floating event');
my ($event) = @_; is($floating[0]->{container}->{window}, $win->{id}, "window id matches");
$cv->send($event) if $event->{change} eq 'floating'; is($floating[0]->{container}->{floating}, $want, "floating is $want");
} }
})->recv;
my $t;
$t = AnyEvent->timer(
after => 0.5,
cb => sub {
$cv->send(0);
}
);
my $win = open_window(); my $win = open_window();
cmd '[id="' . $win->{id} . '"] floating enable'; subtest 'floating enable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating enable', 'user_on';
my $e = $cv->recv; subtest 'floating disable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating disable', 'user_off';
isnt($e, 0, 'floating a container should send an ipc window event');
is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window');
is($e->{container}->{floating}, 'user_on', 'the container should be floating');
$cv = AnyEvent->condvar;
cmd '[id="' . $win->{id} . '"] floating disable';
$e = $cv->recv;
isnt($e, 0, 'disabling floating on a container should send an ipc window event');
is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window');
is($e->{container}->{floating}, 'user_off', 'the container should not be floating');
done_testing; done_testing;

View File

@ -35,51 +35,38 @@ SKIP: {
skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?; skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?;
skip "AnyEvent::I3 too old (need >= 0.16)", 1 if $AnyEvent::I3::VERSION < 0.16;
my $pid = launch_with_config($config); my $pid = launch_with_config($config);
my $i3 = i3(get_socket_path()); my $cv = AnyEvent->condvar;
$i3->connect->recv;
my $cv = AE::cv; my @events = events_for(
my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; sub {
# TODO: this is still flaky: we need to synchronize every X11
# connection with i3. Move to XTEST and synchronize that connection.
qx(xdotool key $binding_symbol);
},
'binding');
$i3->subscribe({ is(scalar @events, 1, 'Received 1 event');
binding => sub {
$cv->send(shift);
}
})->recv;
qx(xdotool key $binding_symbol); is($events[0]->{change}, 'run',
my $e = $cv->recv;
does_i3_live;
diag "Event:\n", Dumper($e);
ok($e,
'the binding event should emit when user input triggers an i3 binding event');
is($e->{change}, 'run',
'the `change` field should indicate this binding has run'); 'the `change` field should indicate this binding has run');
ok($e->{binding}, ok($events[0]->{binding},
'the `binding` field should be a hash that contains information about the binding'); 'the `binding` field should be a hash that contains information about the binding');
is($e->{binding}->{input_type}, 'keyboard', is($events[0]->{binding}->{input_type}, 'keyboard',
'the input_type field should be the input type of the binding (keyboard or mouse)'); 'the input_type field should be the input type of the binding (keyboard or mouse)');
note 'the `mods` field should contain the symbols for the modifiers of the binding'; note 'the `mods` field should contain the symbols for the modifiers of the binding';
foreach (@mods) { foreach (@mods) {
ok(grep(/$_/i, @{$e->{binding}->{mods}}), "`mods` contains the modifier $_"); ok(grep(/$_/i, @{$events[0]->{binding}->{mods}}), "`mods` contains the modifier $_");
} }
is($e->{binding}->{command}, $command, is($events[0]->{binding}->{command}, $command,
'the `command` field should contain the command the binding ran'); 'the `command` field should contain the command the binding ran');
is($e->{binding}->{input_code}, 0, is($events[0]->{binding}->{input_code}, 0,
'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero'); 'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero');
exit_gracefully($pid); exit_gracefully($pid);

View File

@ -36,14 +36,13 @@ SKIP: {
skip "setxkbmap not found", 1 if skip "setxkbmap not found", 1 if
system(q|setxkbmap -print >/dev/null|) != 0; system(q|setxkbmap -print >/dev/null|) != 0;
start_binding_capture;
system(q|setxkbmap us,ru -option grp:alt_shift_toggle|); system(q|setxkbmap us,ru -option grp:alt_shift_toggle|);
is(listen_for_binding( is(listen_for_binding(
sub { sub {
xtest_key_press(107); xtest_key_press(107);
xtest_key_release(107); xtest_key_release(107);
xtest_sync_with_i3;
}, },
), ),
'Print', 'Print',
@ -55,6 +54,7 @@ is(listen_for_binding(
xtest_key_press(36); # Return xtest_key_press(36); # Return
xtest_key_release(36); # Return xtest_key_release(36); # Return
xtest_key_release(133); # Super_L xtest_key_release(133); # Super_L
xtest_sync_with_i3;
}, },
), ),
'Mod4+Return', 'Mod4+Return',
@ -67,6 +67,7 @@ is(listen_for_binding(
sub { sub {
xtest_key_press(107); xtest_key_press(107);
xtest_key_release(107); xtest_key_release(107);
xtest_sync_with_i3;
}, },
), ),
'Print', 'Print',
@ -78,14 +79,12 @@ is(listen_for_binding(
xtest_key_press(36); # Return xtest_key_press(36); # Return
xtest_key_release(36); # Return xtest_key_release(36); # Return
xtest_key_release(133); # Super_L xtest_key_release(133); # Super_L
xtest_sync_with_i3;
}, },
), ),
'Mod4+Return', 'Mod4+Return',
'triggered the "Mod4+Return" keybinding'); 'triggered the "Mod4+Return" keybinding');
sync_with_i3;
is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events');
# Disable the grp:alt_shift_toggle option, as we use Alt+Shift in other testcases. # Disable the grp:alt_shift_toggle option, as we use Alt+Shift in other testcases.
system(q|setxkbmap us -option|); system(q|setxkbmap us -option|);

View File

@ -37,12 +37,11 @@ SKIP: {
skip "libxcb-xkb too old (need >= 1.11)", 1 unless skip "libxcb-xkb too old (need >= 1.11)", 1 unless
ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11'); ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11');
start_binding_capture;
is(listen_for_binding( is(listen_for_binding(
sub { sub {
xtest_key_press(107); # Print xtest_key_press(107); # Print
xtest_key_release(107); # Print xtest_key_release(107); # Print
xtest_sync_with_i3;
}, },
), ),
'Print', 'Print',
@ -54,6 +53,7 @@ is(listen_for_binding(
xtest_key_press(107); # Print xtest_key_press(107); # Print
xtest_key_release(107); # Print xtest_key_release(107); # Print
xtest_key_release(37); # Control_L xtest_key_release(37); # Control_L
xtest_sync_with_i3;
}, },
), ),
'Control+Print', 'Control+Print',
@ -65,6 +65,7 @@ is(listen_for_binding(
xtest_key_press(56); # b xtest_key_press(56); # b
xtest_key_release(56); # b xtest_key_release(56); # b
xtest_key_release(64); # Alt_L xtest_key_release(64); # Alt_L
xtest_sync_with_i3;
}, },
), ),
'Mod1+b', 'Mod1+b',
@ -78,14 +79,12 @@ is(listen_for_binding(
xtest_key_release(56); # b xtest_key_release(56); # b
xtest_key_release(50); # Shift_L xtest_key_release(50); # Shift_L
xtest_key_release(64); # Alt_L xtest_key_release(64); # Alt_L
xtest_sync_with_i3;
}, },
), ),
'Mod1+Shift+b release', 'Mod1+Shift+b release',
'triggered the "Mod1+Shift+b" release keybinding'); 'triggered the "Mod1+Shift+b" release keybinding');
sync_with_i3;
is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events');
} }
done_testing; done_testing;

View File

@ -28,23 +28,11 @@ EOT
cmd 'mode othermode'; cmd 'mode othermode';
my $i3 = i3(get_socket_path(0)); my @events = events_for(
$i3->connect->recv; sub { cmd 'reload' },
'mode');
my $cv = AnyEvent->condvar; is(scalar @events, 1, 'Received 1 event');
$i3->subscribe({ is($events[0]->{change}, 'default', 'change is "default"');
mode => sub {
my ($event) = @_;
$cv->send($event->{change} eq 'default');
}
})->recv;
cmd 'reload';
# Timeout after 0.5s
my $t;
$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
ok($cv->recv, 'Mode event received');
done_testing; done_testing;

View File

@ -18,27 +18,16 @@
# Ticket: #2501 # Ticket: #2501
use i3test; use i3test;
my ($i3, $timer, $event, $mark); sub mark_subtest {
my ($cmd) = @_;
$i3 = i3(get_socket_path()); my @events = events_for(
$i3->connect()->recv; sub { cmd $cmd },
'window');
$i3->subscribe({ my @mark = grep { $_->{change} eq 'mark' } @events;
window => sub { is(scalar @mark, 1, 'Received 1 window::mark event');
my ($event) = @_; }
return unless defined $mark;
return unless $event->{change} eq 'mark';
$mark->send($event);
}
})->recv;
$timer = AnyEvent->timer(
after => 0.5,
cb => sub {
$mark->send(0);
}
);
############################################################################### ###############################################################################
# Marking a container triggers a 'mark' event. # Marking a container triggers a 'mark' event.
@ -46,11 +35,7 @@ $timer = AnyEvent->timer(
fresh_workspace; fresh_workspace;
open_window; open_window;
$mark = AnyEvent->condvar; subtest 'mark', \&mark_subtest, 'mark x';
cmd 'mark x';
$event = $mark->recv;
ok($event, 'window::mark event has been received');
############################################################################### ###############################################################################
# Unmarking a container triggers a 'mark' event. # Unmarking a container triggers a 'mark' event.
@ -59,11 +44,7 @@ fresh_workspace;
open_window; open_window;
cmd 'mark x'; cmd 'mark x';
$mark = AnyEvent->condvar; subtest 'unmark', \&mark_subtest, 'unmark x';
cmd 'unmark x';
$event = $mark->recv;
ok($event, 'window::mark event has been received');
############################################################################### ###############################################################################

View File

@ -19,34 +19,17 @@
# Bug still in: 4.8-7-gf4a8253 # Bug still in: 4.8-7-gf4a8253
use i3test; use i3test;
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
my $cv;
my $t;
sub reset_test {
$cv = AE::cv;
$t = AE::timer(0.5, 0, sub { $cv->send(0); });
}
reset_test;
$i3->subscribe({
window => sub {
my ($e) = @_;
if ($e->{change} eq 'close') {
$cv->send($e->{container});
}
},
})->recv;
my $window = open_window; my $window = open_window;
cmd 'kill'; my @events = events_for(
my $con = $cv->recv; sub {
$window->unmap;
sync_with_i3;
},
'window');
ok($con, 'closing a window should send the window::close event'); my @close = grep { $_->{change} eq 'close' } @events;
is($con->{window}, $window->{id}, 'the event should contain information about the window'); is(scalar @close, 1, 'Received 1 window::close event');
is($close[0]->{container}->{window}, $window->{id}, 'the event should contain information about the window');
done_testing; done_testing;

View File

@ -19,43 +19,22 @@
# Bug still in: 4.8-7-gf4a8253 # Bug still in: 4.8-7-gf4a8253
use i3test; use i3test;
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
my $cv;
my $t;
sub reset_test {
$cv = AE::cv;
$t = AE::timer(0.5, 0, sub { $cv->send(0); });
}
reset_test;
$i3->subscribe({
window => sub {
my ($e) = @_;
if ($e->{change} eq 'move') {
$cv->send($e->{container});
}
},
})->recv;
my $dummy_window = open_window; my $dummy_window = open_window;
my $window = open_window; my $window = open_window;
cmd 'move right'; sub move_subtest {
my $con = $cv->recv; my ($cmd) = @_;
my $cv = AnyEvent->condvar;
my @events = events_for(
sub { cmd $cmd },
'window');
ok($con, 'moving a window should emit the window::move event'); my @move = grep { $_->{change} eq 'move' } @events;
is($con->{window}, $window->{id}, 'the event should contain info about the window'); is(scalar @move, 1, 'Received 1 window::move event');
is($move[0]->{container}->{window}, $window->{id}, 'window id matches');
}
reset_test; subtest 'move right', \&move_subtest, 'move right';
subtest 'move to workspace', \&move_subtest, 'move to workspace ws_new';
cmd 'move to workspace ws_new';
$con = $cv->recv;
ok($con, 'moving a window to a different workspace should emit the window::move event');
is($con->{window}, $window->{id}, 'the event should contain info about the window');
done_testing; done_testing;

View File

@ -19,50 +19,37 @@
# #
use i3test; use i3test;
my $config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
force_display_urgency_hint 0ms
EOT
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
my $cv;
$i3->subscribe({
window => sub {
my ($event) = @_;
$cv->send($event) if $event->{change} eq 'urgent';
}
})->recv;
my $t;
$t = AnyEvent->timer(
after => 0.5,
cb => sub {
$cv->send(0);
}
);
$cv = AnyEvent->condvar;
fresh_workspace; fresh_workspace;
my $win = open_window; my $win = open_window;
my $dummy_win = open_window; my $dummy_win = open_window;
$win->add_hint('urgency'); sub urgency_subtest {
my $event = $cv->recv; my ($subscribecb, $win, $want) = @_;
isnt($event, 0, 'an urgent con should emit the window::urgent event'); my @events = events_for(
is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window'); $subscribecb,
is($event->{container}->{urgent}, 1, 'the container should be urgent'); 'window');
$cv = AnyEvent->condvar; my @urgent = grep { $_->{change} eq 'urgent' } @events;
$win->delete_hint('urgency'); is(scalar @urgent, 1, 'Received 1 window::urgent event');
$event = $cv->recv; is($urgent[0]->{container}->{window}, $win->{id}, "window id matches");
is($urgent[0]->{container}->{urgent}, $want, "urgent is $want");
}
isnt($event, 0, 'an urgent con should emit the window::urgent event'); subtest "urgency set", \&urgency_subtest,
is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window'); sub {
is($event->{container}->{urgent}, 0, 'the container should not be urgent'); $win->add_hint('urgency');
sync_with_i3;
},
$win,
1;
subtest "urgency unset", \&urgency_subtest,
sub {
$win->delete_hint('urgency');
sync_with_i3;
},
$win,
0;
done_testing; done_testing;

View File

@ -30,7 +30,7 @@ fresh_workspace;
xtest_button_press(4, 50, 50); xtest_button_press(4, 50, 50);
xtest_button_release(4, 50, 50); xtest_button_release(4, 50, 50);
sync_with_i3; xtest_sync_with_i3;
is(focused_ws(), 'special', 'the binding was triggered'); is(focused_ws(), 'special', 'the binding was triggered');

View File

@ -23,13 +23,13 @@
# Bug still in: 4.12-46-g2123888 # Bug still in: 4.12-46-g2123888
use i3test; use i3test;
SKIP: { # We cannot use events_for in this test as we cannot send events after
skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17; # issuing the restart/shutdown command.
my $i3 = i3(get_socket_path()); my $i3 = i3(get_socket_path());
$i3->connect->recv; $i3->connect->recv;
my $cv = AE::cv; my $cv = AnyEvent->condvar;
my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
$i3->subscribe({ $i3->subscribe({
@ -50,7 +50,7 @@ is($e->{change}, 'restart', 'the `change` field should tell the reason for the s
$i3 = i3(get_socket_path()); $i3 = i3(get_socket_path());
$i3->connect->recv; $i3->connect->recv;
$cv = AE::cv; $cv = AnyEvent->condvar;
$timer = AE::timer 0.5, 0, sub { $cv->send(0); }; $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
$i3->subscribe({ $i3->subscribe({
@ -66,6 +66,5 @@ $e = $cv->recv;
diag "Event:\n", Dumper($e); diag "Event:\n", Dumper($e);
ok($e, 'the shutdown event should emit when the ipc is exited by command'); ok($e, 'the shutdown event should emit when the ipc is exited by command');
is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown'); is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown');
}
done_testing; done_testing;

View File

@ -51,12 +51,11 @@ EOT
my $pid = launch_with_config($config); my $pid = launch_with_config($config);
start_binding_capture;
is(listen_for_binding( is(listen_for_binding(
sub { sub {
xtest_key_press(87); # KP_End xtest_key_press(87); # KP_End
xtest_key_release(87); # KP_End xtest_key_release(87); # KP_End
xtest_sync_with_i3;
}, },
), ),
'KP_End', 'KP_End',
@ -70,6 +69,7 @@ is(listen_for_binding(
xtest_key_release(87); # KP_1 xtest_key_release(87); # KP_1
xtest_key_press(77); # disable Num_Lock xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock
xtest_sync_with_i3;
}, },
), ),
'KP_1', 'KP_1',
@ -81,6 +81,7 @@ is(listen_for_binding(
xtest_key_press(38); # a xtest_key_press(38); # a
xtest_key_release(38); # a xtest_key_release(38); # a
xtest_key_release(133); # Super_L xtest_key_release(133); # Super_L
xtest_sync_with_i3;
}, },
), ),
'a', 'a',
@ -96,6 +97,7 @@ is(listen_for_binding(
xtest_key_release(133); # Super_L xtest_key_release(133); # Super_L
xtest_key_press(77); # disable Num_Lock xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock
xtest_sync_with_i3;
}, },
), ),
'a', 'a',
@ -105,6 +107,7 @@ is(listen_for_binding(
sub { sub {
xtest_key_press(9); # Escape xtest_key_press(9); # Escape
xtest_key_release(9); # Escape xtest_key_release(9); # Escape
xtest_sync_with_i3;
}, },
), ),
'Escape', 'Escape',
@ -118,6 +121,7 @@ is(listen_for_binding(
xtest_key_release(9); # Escape xtest_key_release(9); # Escape
xtest_key_press(77); # disable Num_Lock xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock
xtest_sync_with_i3;
}, },
), ),
'Escape', 'Escape',
@ -129,6 +133,7 @@ is(listen_for_binding(
xtest_key_press(9); # Escape xtest_key_press(9); # Escape
xtest_key_release(9); # Escape xtest_key_release(9); # Escape
xtest_key_release(50); # Shift_L xtest_key_release(50); # Shift_L
xtest_sync_with_i3;
}, },
), ),
'Shift+Escape', 'Shift+Escape',
@ -144,6 +149,7 @@ is(listen_for_binding(
xtest_key_release(50); # Shift_L xtest_key_release(50); # Shift_L
xtest_key_press(77); # disable Num_Lock xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock
xtest_sync_with_i3;
}, },
), ),
'Shift+Escape', 'Shift+Escape',
@ -157,6 +163,7 @@ is(listen_for_binding(
xtest_key_release(24); # q xtest_key_release(24); # q
xtest_key_release(64); # Alt_L xtest_key_release(64); # Alt_L
xtest_key_release(50); # Shift_L xtest_key_release(50); # Shift_L
xtest_sync_with_i3;
}, },
), ),
'Mod1+Shift+q', 'Mod1+Shift+q',
@ -174,6 +181,7 @@ is(listen_for_binding(
xtest_key_release(50); # Shift_L xtest_key_release(50); # Shift_L
xtest_key_press(77); # disable Num_Lock xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock
xtest_sync_with_i3;
}, },
), ),
'Mod1+Shift+q', 'Mod1+Shift+q',
@ -183,6 +191,7 @@ is(listen_for_binding(
sub { sub {
xtest_key_press(39); # s xtest_key_press(39); # s
xtest_key_release(39); # s xtest_key_release(39); # s
xtest_sync_with_i3;
}, },
), ),
's', 's',
@ -196,14 +205,12 @@ is(listen_for_binding(
xtest_key_release(39); # s xtest_key_release(39); # s
xtest_key_press(77); # disable Num_Lock xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock
xtest_sync_with_i3;
}, },
), ),
's', 's',
'triggered the "s" keybinding with Num_Lock'); 'triggered the "s" keybinding with Num_Lock');
sync_with_i3;
is(scalar @i3test::XTEST::binding_events, 12, 'Received exactly 12 binding events');
exit_gracefully($pid); exit_gracefully($pid);
################################################################################ ################################################################################
@ -222,12 +229,11 @@ EOT
$pid = launch_with_config($config); $pid = launch_with_config($config);
start_binding_capture;
is(listen_for_binding( is(listen_for_binding(
sub { sub {
xtest_key_press(133); # Super_L xtest_key_press(133); # Super_L
xtest_key_release(133); # Super_L xtest_key_release(133); # Super_L
xtest_sync_with_i3;
}, },
), ),
'Super_L', 'Super_L',
@ -241,6 +247,7 @@ is(listen_for_binding(
xtest_key_release(133); # Super_L xtest_key_release(133); # Super_L
xtest_key_press(77); # disable Num_Lock xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock
xtest_sync_with_i3;
}, },
), ),
'Super_L', 'Super_L',
@ -252,6 +259,7 @@ is(listen_for_binding(
xtest_key_press(36); # Return xtest_key_press(36); # Return
xtest_key_release(36); # Return xtest_key_release(36); # Return
xtest_key_release(133); # Super_L xtest_key_release(133); # Super_L
xtest_sync_with_i3;
}, },
), ),
'Return', 'Return',
@ -267,14 +275,12 @@ is(listen_for_binding(
xtest_key_release(133); # Super_L xtest_key_release(133); # Super_L
xtest_key_press(77); # disable Num_Lock xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock
xtest_sync_with_i3;
}, },
), ),
'Return', 'Return',
'triggered the "Return" keybinding with Num_Lock'); 'triggered the "Return" keybinding with Num_Lock');
sync_with_i3;
is(scalar @i3test::XTEST::binding_events, 16, 'Received exactly 16 binding events');
exit_gracefully($pid); exit_gracefully($pid);
################################################################################ ################################################################################
@ -291,12 +297,11 @@ EOT
$pid = launch_with_config($config); $pid = launch_with_config($config);
start_binding_capture;
is(listen_for_binding( is(listen_for_binding(
sub { sub {
xtest_key_press(87); # KP_End xtest_key_press(87); # KP_End
xtest_key_release(87); # KP_End xtest_key_release(87); # KP_End
xtest_sync_with_i3;
}, },
), ),
'KP_End', 'KP_End',
@ -306,12 +311,13 @@ is(listen_for_binding(
sub { sub {
xtest_key_press(88); # KP_Down xtest_key_press(88); # KP_Down
xtest_key_release(88); # KP_Down xtest_key_release(88); # KP_Down
xtest_sync_with_i3;
}, },
), ),
'KP_Down', 'KP_Down',
'triggered the "KP_Down" keybinding'); 'triggered the "KP_Down" keybinding');
is(listen_for_binding( my @unexpected = events_for(
sub { sub {
xtest_key_press(77); # enable Num_Lock xtest_key_press(77); # enable Num_Lock
xtest_key_release(77); # enable Num_Lock xtest_key_release(77); # enable Num_Lock
@ -319,12 +325,12 @@ is(listen_for_binding(
xtest_key_release(87); # KP_1 xtest_key_release(87); # KP_1
xtest_key_press(77); # disable Num_Lock xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock
xtest_sync_with_i3;
}, },
), 'binding');
'timeout', is(scalar @unexpected, 0, 'Did not trigger the KP_End keybinding with KP_1');
'Did not trigger the KP_End keybinding with KP_1');
is(listen_for_binding( my @unexpected2 = events_for(
sub { sub {
xtest_key_press(77); # enable Num_Lock xtest_key_press(77); # enable Num_Lock
xtest_key_release(77); # enable Num_Lock xtest_key_release(77); # enable Num_Lock
@ -332,16 +338,14 @@ is(listen_for_binding(
xtest_key_release(88); # KP_2 xtest_key_release(88); # KP_2
xtest_key_press(77); # disable Num_Lock xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock
xtest_sync_with_i3;
}, },
), 'binding');
'timeout',
'Did not trigger the KP_Down keybinding with KP_2'); is(scalar @unexpected2, 0, 'Did not trigger the KP_Down keybinding with KP_2');
# TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2. # TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2.
sync_with_i3;
is(scalar @i3test::XTEST::binding_events, 18, 'Received exactly 18 binding events');
exit_gracefully($pid); exit_gracefully($pid);
################################################################################ ################################################################################
@ -359,8 +363,6 @@ $pid = launch_with_config($config);
my $win = open_window; my $win = open_window;
start_binding_capture;
is(listen_for_binding( is(listen_for_binding(
sub { sub {
xtest_key_press(77); # enable Num_Lock xtest_key_press(77); # enable Num_Lock
@ -369,6 +371,7 @@ is(listen_for_binding(
xtest_button_release(4, 50, 50); xtest_button_release(4, 50, 50);
xtest_key_press(77); # disable Num_Lock xtest_key_press(77); # disable Num_Lock
xtest_key_release(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock
xtest_sync_with_i3;
}, },
), ),
'button4', 'button4',
@ -376,8 +379,9 @@ is(listen_for_binding(
is(listen_for_binding( is(listen_for_binding(
sub { sub {
xtest_button_press(4, 50, 50); xtest_button_press(4, 50, 50);
xtest_button_release(4, 50, 50); xtest_button_release(4, 50, 50);
xtest_sync_with_i3;
}, },
), ),
'button4', 'button4',

View File

@ -13,7 +13,7 @@
# #
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl) # (unless you are already familiar with Perl)
# #
# Ticket: #990 # Ticket: #990
# Bug still in: 4.5.1-23-g82b5978 # Bug still in: 4.5.1-23-g82b5978
@ -23,46 +23,17 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
fake-outputs 1024x768+0+0,1024x768+1024+0 fake-outputs 1024x768+0+0,1024x768+1024+0
EOT EOT
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
################################
# Workspaces requests and events
################################
my $old_ws = get_ws(focused_ws); my $old_ws = get_ws(focused_ws);
# Events
# We are switching to an empty workpspace on the output to the right from an empty workspace on the output on the left, so we expect
# to receive "init", "focus", and "empty".
my $focus = AnyEvent->condvar; my $focus = AnyEvent->condvar;
$i3->subscribe({ my @events = events_for(
workspace => sub { sub { cmd 'focus output right' },
my ($event) = @_; 'workspace');
if ($event->{change} eq 'focus') {
$focus->send($event);
}
}
})->recv;
my $t;
$t = AnyEvent->timer(
after => 0.5,
cb => sub {
$focus->send(0);
}
);
cmd 'focus output right';
my $event = $focus->recv;
my $current_ws = get_ws(focused_ws); my $current_ws = get_ws(focused_ws);
ok($event, 'Workspace "focus" event received'); is(scalar @events, 1, 'Received 1 event');
is($event->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace'); is($events[0]->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace');
is($event->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace'); is($events[0]->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace');
done_testing; done_testing;

View File

@ -27,51 +27,29 @@ workspace ws-left output fake-0
workspace ws-right output fake-1 workspace ws-right output fake-1
EOT EOT
my $i3 = i3(get_socket_path());
$i3->connect()->recv;
# subscribe to the 'focus' ipc event
my $focus = AnyEvent->condvar;
$i3->subscribe({
workspace => sub {
my ($event) = @_;
if ($event->{change} eq 'focus') {
$focus->send($event);
}
}
})->recv;
# give up after 0.5 seconds
my $timer = AnyEvent->timer(
after => 0.5,
cb => sub {
$focus->send(0);
}
);
# open two windows on the left output # open two windows on the left output
cmd 'workspace ws-left'; cmd 'workspace ws-left';
open_window; open_window;
open_window; open_window;
sub focus_subtest {
my ($cmd, $want) = @_;
my @events = events_for(
sub { cmd $cmd },
'workspace');
my @focus = grep { $_->{change} eq 'focus' } @events;
is(scalar @focus, 1, 'Received 1 workspace::focus event');
is($focus[0]->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
is(@{$focus[0]->{current}->{nodes}}, $want, 'focus event gave the right number of windows on the workspace');
}
# move a window over to the right output # move a window over to the right output
cmd 'move right'; subtest 'move right (1)', \&focus_subtest, 'move right', 1;
my $event = $focus->recv;
ok($event, 'moving from workspace with two windows triggered focus ipc event'); # move another window
is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
is(@{$event->{current}->{nodes}}, 1, 'focus event gave the right number of windows on the workspace');
# reset and try again
$focus = AnyEvent->condvar;
cmd 'workspace ws-left'; cmd 'workspace ws-left';
$focus->recv; subtest 'move right (2)', \&focus_subtest, 'move right', 2;
$focus = AnyEvent->condvar;
cmd 'move right';
$event = $focus->recv;
ok($event, 'moving from workspace with one window triggered focus ipc event');
is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
is(@{$event->{current}->{nodes}}, 2, 'focus event gave the right number of windows on the workspace');
done_testing; done_testing;

View File

@ -34,17 +34,12 @@ bar {
EOT EOT
use i3test::XTEST; use i3test::XTEST;
my ($cv, $timer);
sub reset_test {
$cv = AE::cv;
$timer = AE::timer(1, 0, sub { $cv->send(0); });
}
my $i3 = i3(get_socket_path()); my $i3 = i3(get_socket_path());
$i3->connect()->recv; $i3->connect()->recv;
my $ws = fresh_workspace; my $ws = fresh_workspace;
reset_test; my $cv = AnyEvent->condvar;
my $timer = AnyEvent->timer(1, 0, sub { $cv->send(0) });
$i3->subscribe({ $i3->subscribe({
window => sub { window => sub {
my ($event) = @_; my ($event) = @_;
@ -60,15 +55,13 @@ $i3->subscribe({
}, },
})->recv; })->recv;
my $con;
sub i3bar_present { sub i3bar_present {
my ($nodes) = @_; my ($nodes) = @_;
for my $node (@{$nodes}) { for my $node (@{$nodes}) {
my $props = $node->{window_properties}; my $props = $node->{window_properties};
if (defined($props) && $props->{class} eq 'i3bar') { if (defined($props) && $props->{class} eq 'i3bar') {
return 1; return $node->{window};
} }
} }
@ -80,53 +73,75 @@ sub i3bar_present {
return i3bar_present(\@children); return i3bar_present(\@children);
} }
if (i3bar_present($i3->get_tree->recv->{nodes})) { my $i3bar_window = i3bar_present($i3->get_tree->recv->{nodes});
if ($i3bar_window) {
ok(1, 'i3bar present'); ok(1, 'i3bar present');
} else { } else {
$con = $cv->recv; my $con = $cv->recv;
ok($con, 'i3bar appeared'); ok($con, 'i3bar appeared');
$i3bar_window = $con->{window};
} }
diag('i3bar window = ' . $i3bar_window);
my $left = open_window; my $left = open_window;
my $right = open_window; my $right = open_window;
sync_with_i3; sync_with_i3;
$con = $cv->recv; my $con = $cv->recv;
is($con->{window}, $right->{id}, 'focus is initially on the right container'); is($con->{window}, $right->{id}, 'focus is initially on the right container');
reset_test;
xtest_button_press(1, 3, 3); sub focus_subtest {
xtest_button_release(1, 3, 3); my ($subscribecb, $want, $msg) = @_;
sync_with_i3; my @events = events_for(
$con = $cv->recv; $subscribecb,
is($con->{window}, $left->{id}, 'button 1 moves focus left'); 'window');
reset_test; my @focus = map { $_->{container}->{window} } grep { $_->{change} eq 'focus' } @events;
is_deeply(\@focus, $want, $msg);
}
xtest_button_press(2, 3, 3); subtest 'button 1 moves focus left', \&focus_subtest,
xtest_button_release(2, 3, 3); sub {
sync_with_i3; xtest_button_press(1, 3, 3);
$con = $cv->recv; xtest_button_release(1, 3, 3);
is($con->{window}, $right->{id}, 'button 2 moves focus right'); xtest_sync_with($i3bar_window);
reset_test; },
[ $left->{id} ],
'button 1 moves focus left';
xtest_button_press(3, 3, 3); subtest 'button 2 moves focus right', \&focus_subtest,
xtest_button_release(3, 3, 3); sub {
sync_with_i3; xtest_button_press(2, 3, 3);
$con = $cv->recv; xtest_button_release(2, 3, 3);
is($con->{window}, $left->{id}, 'button 3 moves focus left'); xtest_sync_with($i3bar_window);
reset_test; },
[ $right->{id} ],
'button 2 moves focus right';
xtest_button_press(4, 3, 3); subtest 'button 3 moves focus left', \&focus_subtest,
xtest_button_release(4, 3, 3); sub {
sync_with_i3; xtest_button_press(3, 3, 3);
$con = $cv->recv; xtest_button_release(3, 3, 3);
is($con->{window}, $right->{id}, 'button 4 moves focus right'); xtest_sync_with($i3bar_window);
reset_test; },
[ $left->{id} ],
'button 3 moves focus left';
xtest_button_press(5, 3, 3); subtest 'button 4 moves focus right', \&focus_subtest,
xtest_button_release(5, 3, 3); sub {
sync_with_i3; xtest_button_press(4, 3, 3);
$con = $cv->recv; xtest_button_release(4, 3, 3);
is($con->{window}, $left->{id}, 'button 5 moves focus left'); xtest_sync_with($i3bar_window);
reset_test; },
[ $right->{id} ],
'button 4 moves focus right';
subtest 'button 5 moves focus left', \&focus_subtest,
sub {
xtest_button_press(5, 3, 3);
xtest_button_release(5, 3, 3);
xtest_sync_with($i3bar_window);
},
[ $left->{id} ],
'button 5 moves focus left';
done_testing; done_testing;