diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm index 08b1c0a7..198c41c9 100644 --- a/AnyEvent-I3/lib/AnyEvent/I3.pm +++ b/AnyEvent-I3/lib/AnyEvent/I3.pm @@ -99,11 +99,12 @@ use constant TYPE_GET_BAR_CONFIG => 6; use constant TYPE_GET_VERSION => 7; use constant TYPE_GET_BINDING_MODES => 8; use constant TYPE_GET_CONFIG => 9; +use constant TYPE_SEND_TICK => 10; our %EXPORT_TAGS = ( 'all' => [ 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_BINDING_MODES TYPE_GET_CONFIG) + TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -120,6 +121,7 @@ my %events = ( barconfig_update => ($event_mask | 4), binding => ($event_mask | 5), shutdown => ($event_mask | 6), + tick => ($event_mask | 7), _error => 0xFFFFFFFF, ); @@ -519,6 +521,18 @@ sub 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) diff --git a/docs/ipc b/docs/ipc index 526c8e69..8b767ade 100644 --- a/docs/ipc +++ b/docs/ipc @@ -64,6 +64,7 @@ to do that). | 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. | 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: @@ -126,6 +127,8 @@ BINDING_MODES (8):: Reply to the GET_BINDING_MODES message. GET_CONFIG (9):: Reply to the GET_CONFIG message. +TICK (10):: + Reply to the SEND_TICK message. [[_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" } ------------------- +[[_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 @@ -694,6 +710,10 @@ binding (5):: mouse shutdown (6):: 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:* -------------------------------------------------------------------- @@ -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) [[libraries]] diff --git a/i3-msg/main.c b/i3-msg/main.c index 8907a6f7..a4f94850 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -207,9 +207,11 @@ int main(int argc, char *argv[]) { message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION; } else if (strcasecmp(optarg, "get_config") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG; + } else if (strcasecmp(optarg, "send_tick") == 0) { + message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK; } else { 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); } } else if (o == 'q') { diff --git a/i3bar/include/xcb_atoms.def b/i3bar/include/xcb_atoms.def index 65a147c4..453dd0ce 100644 --- a/i3bar/include/xcb_atoms.def +++ b/i3bar/include/xcb_atoms.def @@ -9,4 +9,5 @@ ATOM_DO(_NET_SYSTEM_TRAY_OPCODE) ATOM_DO(_NET_SYSTEM_TRAY_COLORS) ATOM_DO(_XEMBED_INFO) ATOM_DO(_XEMBED) +ATOM_DO(I3_SYNC) #undef ATOM_DO diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index fed969df..dfa66ee2 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -678,8 +678,26 @@ static void configure_trayclients(void) { * */ static void handle_client_message(xcb_client_message_event_t *event) { - if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] && - event->format == 32) { + if (event->type == atoms[I3_SYNC]) { + 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"); /* event->data.data32[0] is the timestamp */ uint32_t op = event->data.data32[1]; diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 993a2a24..9e0280c9 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -60,6 +60,9 @@ typedef struct i3_ipc_header { /** Request the raw last loaded i3 config. */ #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 * @@ -74,6 +77,7 @@ typedef struct i3_ipc_header { #define I3_IPC_REPLY_TYPE_VERSION 7 #define I3_IPC_REPLY_TYPE_BINDING_MODES 8 #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. @@ -101,3 +105,6 @@ typedef struct i3_ipc_header { /** The shutdown event will be triggered when the ipc shuts down */ #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) diff --git a/include/ipc.h b/include/ipc.h index 7ffbf7a8..c6ad35c7 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -31,6 +31,10 @@ typedef struct ipc_client { int num_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) clients; } ipc_client; diff --git a/src/ipc.c b/src/ipc.c index 759665fe..99b9e0ec 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -1046,8 +1046,9 @@ static int add_subscription(void *extra, const unsigned char *s, memcpy(client->events[event], s, len); 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("(done)\n"); return 1; @@ -1099,6 +1100,25 @@ IPC_HANDLER(subscribe) { yajl_free(p); const char *reply = "{\"success\":true}"; 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); } +/* + * 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 * value of the message type (see include/i3/ipc.h) */ -handler_t handlers[10] = { +handler_t handlers[11] = { handle_run_command, handle_get_workspaces, handle_subscribe, @@ -1135,6 +1181,7 @@ handler_t handlers[10] = { handle_get_version, handle_get_binding_modes, handle_get_config, + handle_send_tick, }; /* diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index a484c91a..5b24ab39 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -47,6 +47,8 @@ our @EXPORT = qw( wait_for_unmap $x kill_all_windows + events_for + listen_for_binding ); =head1 NAME @@ -900,6 +902,86 @@ sub kill_all_windows { 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 we’ll 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 nop ”, 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 ”) 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 Michael Stapelberg diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm index 1ca964b1..4c464c5e 100644 --- a/testcases/lib/i3test/XTEST.pm +++ b/testcases/lib/i3test/XTEST.pm @@ -14,13 +14,13 @@ use ExtUtils::PkgConfig; use Exporter (); our @EXPORT = qw( inlinec_connect + xtest_sync_with + xtest_sync_with_i3 set_xkb_group xtest_key_press xtest_key_release xtest_button_press xtest_button_release - listen_for_binding - start_binding_capture binding_events ); @@ -38,7 +38,7 @@ i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb # ineffective. my %sn_config; 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}; @@ -53,8 +53,12 @@ use Inline C => <<'END_OF_C_CODE'; #include #include #include +#include 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() { int screen; @@ -89,9 +93,94 @@ bool inlinec_connect() { } 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; } +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 // function unless we use an int. bool set_xkb_group(int group) { @@ -170,86 +259,6 @@ sub import { =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 nop ”, 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 ”) 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) 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. +=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 Michael Stapelberg diff --git a/testcases/t/115-ipc-workspaces.t b/testcases/t/115-ipc-workspaces.t index 34ce0781..b0c4354e 100644 --- a/testcases/t/115-ipc-workspaces.t +++ b/testcases/t/115-ipc-workspaces.t @@ -16,61 +16,25 @@ use i3test; -my $i3 = i3(get_socket_path()); -$i3->connect()->recv; - -################################ -# Workspaces requests and events -################################ - my $old_ws = get_ws(focused_ws()); -# Events - # We are switching to an empty workpspace from an empty workspace, so we expect # to receive "init", "focus", and "empty". -my $init = AnyEvent->condvar; -my $focus = AnyEvent->condvar; -my $empty = AnyEvent->condvar; -$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 @events = events_for( + sub { cmd 'workspace 2' }, + 'workspace'); my $current_ws = get_ws(focused_ws()); -ok($init_event, 'workspace "init" event received'); -is($init_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the initted workspace con'); +is(scalar @events, 3, 'Received 3 events'); +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($focus_event->{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]->{change}, 'focus', 'Second event has change = focus'); +is($events[1]->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con'); +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($empty_event->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con'); +is($events[2]->{change}, 'empty', 'Third event has change = empty'); +is($events[2]->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con'); done_testing; diff --git a/testcases/t/199-ipc-mode-event.t b/testcases/t/199-ipc-mode-event.t index 959ff6c4..0e4f8960 100644 --- a/testcases/t/199-ipc-mode-event.t +++ b/testcases/t/199-ipc-mode-event.t @@ -28,24 +28,11 @@ mode "with spaces" { } EOT -my $i3 = i3(get_socket_path(0)); -$i3->connect->recv; +my @events = events_for( + sub { cmd 'mode "m1"' }, + 'mode'); -my $cv = AnyEvent->condvar; - -$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'); +my @changes = map { $_->{change} } @events; +is_deeply(\@changes, [ 'm1' ], 'Mode event received'); done_testing; diff --git a/testcases/t/205-ipc-windows.t b/testcases/t/205-ipc-windows.t index ca7db153..bafd155f 100644 --- a/testcases/t/205-ipc-windows.t +++ b/testcases/t/205-ipc-windows.t @@ -16,46 +16,15 @@ 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 $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; -$t = AnyEvent->timer( - after => 0.5, - 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'); - -} +is(scalar @events, 2, 'Received 2 events'); +is($events[0]->{container}->{focused}, 0, 'Window "new" event received'); +is($events[1]->{container}->{focused}, 1, 'Window "focus" event received'); done_testing; diff --git a/testcases/t/219-ipc-window-focus.t b/testcases/t/219-ipc-window-focus.t index ae778187..5baf68a8 100644 --- a/testcases/t/219-ipc-window-focus.t +++ b/testcases/t/219-ipc-window-focus.t @@ -16,13 +16,6 @@ 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 ################################ @@ -33,62 +26,29 @@ my $win0 = open_window; my $win1 = 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 -$i3->command('[id="' . $win2->id . '"] focus')->recv; +cmd '[id="' . $win2->id . '"] focus'; is($x->input_focus, $win2->id, "Window 2 focused"); -cmd 'focus left'; -my $event = $focus->recv; -is($event->{change}, 'focus', 'Focus event received'); -is($focus->recv->{container}->{name}, 'Window 1', 'Window 1 focused'); +sub focus_subtest { + my ($cmd, $name) = @_; -$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'); + my $focus = AnyEvent->condvar; -$focus = AnyEvent->condvar; -cmd 'focus right'; -$event = $focus->recv; -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'); + my @events = events_for( + sub { cmd $cmd }, + 'window'); + 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; diff --git a/testcases/t/220-ipc-window-title.t b/testcases/t/220-ipc-window-title.t index c751350a..b5d14e23 100644 --- a/testcases/t/220-ipc-window-title.t +++ b/testcases/t/220-ipc-window-title.t @@ -16,42 +16,17 @@ 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 $title = AnyEvent->condvar; +my @events = events_for( + sub { + $window->name('New Window Title'); + sync_with_i3; + }, + 'window'); -$i3->subscribe({ - window => sub { - my ($event) = @_; - $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'); - -} +is(scalar @events, 1, 'Received 1 event'); +is($events[0]->{change}, 'title', 'Window title change event received'); +is($events[0]->{container}->{name}, 'New Window Title', 'Window title changed'); done_testing; diff --git a/testcases/t/225-ipc-window-fullscreen.t b/testcases/t/225-ipc-window-fullscreen.t index aeabe953..bc150546 100644 --- a/testcases/t/225-ipc-window-fullscreen.t +++ b/testcases/t/225-ipc-window-fullscreen.t @@ -19,41 +19,19 @@ # Bug still in: 4.7.2-135-g7deb23c use i3test; -my $i3 = i3(get_socket_path()); -$i3->connect()->recv; +open_window; -my $cv; -my $t; +sub fullscreen_subtest { + my ($want) = @_; + my @events = events_for( + sub { cmd 'fullscreen' }, + 'window'); -sub reset_test { - $cv = AE::cv; - $t = AE::timer(0.5, 0, sub { $cv->send(0); }); + is(scalar @events, 1, 'Received 1 event'); + is($events[0]->{container}->{fullscreen_mode}, $want, "fullscreen_mode now $want"); } -reset_test; - -$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'); +subtest 'fullscreen on', \&fullscreen_subtest, 1; +subtest 'fullscreen off', \&fullscreen_subtest, 0; done_testing; diff --git a/testcases/t/227-ipc-workspace-empty.t b/testcases/t/227-ipc-workspace-empty.t index fe8e03c0..b1f517ef 100644 --- a/testcases/t/227-ipc-workspace-empty.t +++ b/testcases/t/227-ipc-workspace-empty.t @@ -18,12 +18,8 @@ # 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 ################################################################################ subtest 'Workspace empty event upon switch', sub { @@ -35,26 +31,17 @@ subtest 'Workspace empty event upon switch', sub { cmd '[id="' . $w1->id . '"] kill'; my $cond = AnyEvent->condvar; - my $client = i3(get_socket_path(0)); - $client->connect()->recv; - $client->subscribe({ - workspace => sub { - my ($event) = @_; - $cond->send($event); - } - })->recv; + my @events = events_for( + sub { cmd "workspace $ws2" }, + 'workspace'); - cmd "workspace $ws2"; - - sync_with_i3; - - 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'); + is(scalar @events, 2, 'Received 2 event'); + is($events[1]->{change}, 'empty', '"Empty" event received upon workspace switch'); + is($events[1]->{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 ################################################################################ subtest 'No workspace empty event', sub { @@ -63,36 +50,16 @@ subtest 'No workspace empty event', sub { my $ws1 = fresh_workspace; my $w1 = open_window(); - my @events; - my $cond = AnyEvent->condvar; - my $client = i3(get_socket_path(0)); - $client->connect()->recv; - $client->subscribe({ - workspace => sub { - my ($event) = @_; - push @events, $event; - } - })->recv; + my @events = events_for( + sub { cmd "workspace $ws2" }, + 'workspace'); - # Wait for the workspace event on a new connection. Events will be delivered - # to older connections earlier, so by the time it arrives here, it should be - # 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'); + is(scalar @events, 1, 'Received 1 event'); + is($events[0]->{change}, 'focus', 'Event change is "focus"'); }; ################################################################################ -# 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 ################################################################################ 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 $w2 = open_window(); - my $cond = AnyEvent->condvar; - my $client = i3(get_socket_path(0)); - $client->connect()->recv; - $client->subscribe({ - workspace => sub { - my ($event) = @_; - $cond->send($event); - } - })->recv; + my @events = events_for( + sub { + $w1->unmap; + sync_with_i3; + }, + 'workspace'); - cmd '[id="' . $w1->id . '"] kill'; - - sync_with_i3; - - 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'); + is(scalar @events, 1, 'Received 1 event'); + is($events[0]->{change}, 'empty', '"Empty" event received upon window close'); + is($events[0]->{current}->{name}, $ws1, '"current" property should be set to the workspace con'); }; -} - done_testing; diff --git a/testcases/t/231-ipc-floating-event.t b/testcases/t/231-ipc-floating-event.t index e38a1876..96c94a49 100644 --- a/testcases/t/231-ipc-floating-event.t +++ b/testcases/t/231-ipc-floating-event.t @@ -19,41 +19,22 @@ # Bug still in: 4.8-7-gf4a8253 use i3test; -my $i3 = i3(get_socket_path()); -$i3->connect->recv; +sub floating_subtest { + my ($win, $cmd, $want) = @_; -my $cv = AnyEvent->condvar; + my @events = events_for( + sub { cmd $cmd }, + 'window'); -$i3->subscribe({ - window => sub { - my ($event) = @_; - $cv->send($event) if $event->{change} eq 'floating'; - } - })->recv; - -my $t; -$t = AnyEvent->timer( - after => 0.5, - cb => sub { - $cv->send(0); - } -); + my @floating = grep { $_->{change} eq 'floating' } @events; + is(scalar @floating, 1, 'Received 1 floating event'); + is($floating[0]->{container}->{window}, $win->{id}, "window id matches"); + is($floating[0]->{container}->{floating}, $want, "floating is $want"); +} my $win = open_window(); -cmd '[id="' . $win->{id} . '"] floating enable'; -my $e = $cv->recv; - -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'); +subtest 'floating enable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating enable', 'user_on'; +subtest 'floating disable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating disable', 'user_off'; done_testing; diff --git a/testcases/t/238-ipc-binding-event.t b/testcases/t/238-ipc-binding-event.t index af3f4d2f..bec95a23 100644 --- a/testcases/t/238-ipc-binding-event.t +++ b/testcases/t/238-ipc-binding-event.t @@ -35,51 +35,38 @@ SKIP: { 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 $i3 = i3(get_socket_path()); - $i3->connect->recv; + my $cv = AnyEvent->condvar; - my $cv = AE::cv; - my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + my @events = events_for( + 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({ - binding => sub { - $cv->send(shift); - } - })->recv; + is(scalar @events, 1, 'Received 1 event'); - qx(xdotool key $binding_symbol); - - 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', + is($events[0]->{change}, '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'); - 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)'); note 'the `mods` field should contain the symbols for the modifiers of the binding'; 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'); - 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'); exit_gracefully($pid); diff --git a/testcases/t/257-keypress-group1-fallback.t b/testcases/t/257-keypress-group1-fallback.t index f9166fad..bc08aa2f 100644 --- a/testcases/t/257-keypress-group1-fallback.t +++ b/testcases/t/257-keypress-group1-fallback.t @@ -36,14 +36,13 @@ SKIP: { skip "setxkbmap not found", 1 if system(q|setxkbmap -print >/dev/null|) != 0; -start_binding_capture; - system(q|setxkbmap us,ru -option grp:alt_shift_toggle|); is(listen_for_binding( sub { xtest_key_press(107); xtest_key_release(107); + xtest_sync_with_i3; }, ), 'Print', @@ -55,6 +54,7 @@ is(listen_for_binding( xtest_key_press(36); # Return xtest_key_release(36); # Return xtest_key_release(133); # Super_L + xtest_sync_with_i3; }, ), 'Mod4+Return', @@ -67,6 +67,7 @@ is(listen_for_binding( sub { xtest_key_press(107); xtest_key_release(107); + xtest_sync_with_i3; }, ), 'Print', @@ -78,14 +79,12 @@ is(listen_for_binding( 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'); -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. system(q|setxkbmap us -option|); diff --git a/testcases/t/258-keypress-release.t b/testcases/t/258-keypress-release.t index 72bfc862..8bca0d86 100644 --- a/testcases/t/258-keypress-release.t +++ b/testcases/t/258-keypress-release.t @@ -37,12 +37,11 @@ SKIP: { skip "libxcb-xkb too old (need >= 1.11)", 1 unless ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11'); -start_binding_capture; - is(listen_for_binding( sub { xtest_key_press(107); # Print xtest_key_release(107); # Print + xtest_sync_with_i3; }, ), 'Print', @@ -54,6 +53,7 @@ is(listen_for_binding( xtest_key_press(107); # Print xtest_key_release(107); # Print xtest_key_release(37); # Control_L + xtest_sync_with_i3; }, ), 'Control+Print', @@ -65,6 +65,7 @@ is(listen_for_binding( xtest_key_press(56); # b xtest_key_release(56); # b xtest_key_release(64); # Alt_L + xtest_sync_with_i3; }, ), 'Mod1+b', @@ -78,14 +79,12 @@ is(listen_for_binding( xtest_key_release(56); # b xtest_key_release(50); # Shift_L xtest_key_release(64); # Alt_L + xtest_sync_with_i3; }, ), 'Mod1+Shift+b release', 'triggered the "Mod1+Shift+b" release keybinding'); -sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events'); - } done_testing; diff --git a/testcases/t/263-config-reload-reverts-bind-mode.t b/testcases/t/263-config-reload-reverts-bind-mode.t index ba95897c..1416b6b9 100644 --- a/testcases/t/263-config-reload-reverts-bind-mode.t +++ b/testcases/t/263-config-reload-reverts-bind-mode.t @@ -28,23 +28,11 @@ EOT cmd 'mode othermode'; -my $i3 = i3(get_socket_path(0)); -$i3->connect->recv; +my @events = events_for( + sub { cmd 'reload' }, + 'mode'); -my $cv = AnyEvent->condvar; -$i3->subscribe({ - 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'); +is(scalar @events, 1, 'Received 1 event'); +is($events[0]->{change}, 'default', 'change is "default"'); done_testing; diff --git a/testcases/t/265-ipc-mark.t b/testcases/t/265-ipc-mark.t index 06d8d83d..a101944e 100644 --- a/testcases/t/265-ipc-mark.t +++ b/testcases/t/265-ipc-mark.t @@ -18,27 +18,16 @@ # Ticket: #2501 use i3test; -my ($i3, $timer, $event, $mark); +sub mark_subtest { + my ($cmd) = @_; -$i3 = i3(get_socket_path()); -$i3->connect()->recv; + my @events = events_for( + sub { cmd $cmd }, + 'window'); -$i3->subscribe({ - window => sub { - 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); - } -); + my @mark = grep { $_->{change} eq 'mark' } @events; + is(scalar @mark, 1, 'Received 1 window::mark event'); +} ############################################################################### # Marking a container triggers a 'mark' event. @@ -46,11 +35,7 @@ $timer = AnyEvent->timer( fresh_workspace; open_window; -$mark = AnyEvent->condvar; -cmd 'mark x'; - -$event = $mark->recv; -ok($event, 'window::mark event has been received'); +subtest 'mark', \&mark_subtest, 'mark x'; ############################################################################### # Unmarking a container triggers a 'mark' event. @@ -59,11 +44,7 @@ fresh_workspace; open_window; cmd 'mark x'; -$mark = AnyEvent->condvar; -cmd 'unmark x'; - -$event = $mark->recv; -ok($event, 'window::mark event has been received'); +subtest 'unmark', \&mark_subtest, 'unmark x'; ############################################################################### diff --git a/testcases/t/275-ipc-window-close.t b/testcases/t/275-ipc-window-close.t index bf9d4f8b..ccaf4440 100644 --- a/testcases/t/275-ipc-window-close.t +++ b/testcases/t/275-ipc-window-close.t @@ -19,34 +19,17 @@ # Bug still in: 4.8-7-gf4a8253 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; -cmd 'kill'; -my $con = $cv->recv; +my @events = events_for( + sub { + $window->unmap; + sync_with_i3; + }, + 'window'); -ok($con, 'closing a window should send the window::close event'); -is($con->{window}, $window->{id}, 'the event should contain information about the window'); +my @close = grep { $_->{change} eq 'close' } @events; +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; diff --git a/testcases/t/276-ipc-window-move.t b/testcases/t/276-ipc-window-move.t index a3c82534..f3606b4e 100644 --- a/testcases/t/276-ipc-window-move.t +++ b/testcases/t/276-ipc-window-move.t @@ -19,43 +19,22 @@ # Bug still in: 4.8-7-gf4a8253 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 $window = open_window; -cmd 'move right'; -my $con = $cv->recv; +sub move_subtest { + 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'); -is($con->{window}, $window->{id}, 'the event should contain info about the window'); + my @move = grep { $_->{change} eq 'move' } @events; + is(scalar @move, 1, 'Received 1 window::move event'); + is($move[0]->{container}->{window}, $window->{id}, 'window id matches'); +} -reset_test; - -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'); +subtest 'move right', \&move_subtest, 'move right'; +subtest 'move to workspace', \&move_subtest, 'move to workspace ws_new'; done_testing; diff --git a/testcases/t/277-ipc-window-urgent.t b/testcases/t/277-ipc-window-urgent.t index 2af29dac..4eea2cdc 100644 --- a/testcases/t/277-ipc-window-urgent.t +++ b/testcases/t/277-ipc-window-urgent.t @@ -19,50 +19,37 @@ # use i3test; -my $config = <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; my $win = open_window; my $dummy_win = open_window; -$win->add_hint('urgency'); -my $event = $cv->recv; +sub urgency_subtest { + my ($subscribecb, $win, $want) = @_; -isnt($event, 0, 'an urgent con should emit the window::urgent event'); -is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window'); -is($event->{container}->{urgent}, 1, 'the container should be urgent'); + my @events = events_for( + $subscribecb, + 'window'); -$cv = AnyEvent->condvar; -$win->delete_hint('urgency'); -$event = $cv->recv; + my @urgent = grep { $_->{change} eq 'urgent' } @events; + is(scalar @urgent, 1, 'Received 1 window::urgent event'); + 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'); -is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window'); -is($event->{container}->{urgent}, 0, 'the container should not be urgent'); +subtest "urgency set", \&urgency_subtest, + sub { + $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; diff --git a/testcases/t/286-root-window-mouse-binding.t b/testcases/t/286-root-window-mouse-binding.t index 6c1b5d6a..0da373b7 100644 --- a/testcases/t/286-root-window-mouse-binding.t +++ b/testcases/t/286-root-window-mouse-binding.t @@ -30,7 +30,7 @@ fresh_workspace; xtest_button_press(4, 50, 50); xtest_button_release(4, 50, 50); -sync_with_i3; +xtest_sync_with_i3; is(focused_ws(), 'special', 'the binding was triggered'); diff --git a/testcases/t/289-ipc-shutdown-event.t b/testcases/t/289-ipc-shutdown-event.t index 0cf347aa..f2b52460 100644 --- a/testcases/t/289-ipc-shutdown-event.t +++ b/testcases/t/289-ipc-shutdown-event.t @@ -23,13 +23,13 @@ # Bug still in: 4.12-46-g2123888 use i3test; -SKIP: { - skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17; +# We cannot use events_for in this test as we cannot send events after +# issuing the restart/shutdown command. my $i3 = i3(get_socket_path()); $i3->connect->recv; -my $cv = AE::cv; +my $cv = AnyEvent->condvar; my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; $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->connect->recv; -$cv = AE::cv; +$cv = AnyEvent->condvar; $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; $i3->subscribe({ @@ -66,6 +66,5 @@ $e = $cv->recv; diag "Event:\n", Dumper($e); 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'); -} done_testing; diff --git a/testcases/t/290-keypress-numlock.t b/testcases/t/290-keypress-numlock.t index 5137c35f..94a5747d 100644 --- a/testcases/t/290-keypress-numlock.t +++ b/testcases/t/290-keypress-numlock.t @@ -51,12 +51,11 @@ EOT my $pid = launch_with_config($config); -start_binding_capture; - is(listen_for_binding( sub { xtest_key_press(87); # KP_End xtest_key_release(87); # KP_End + xtest_sync_with_i3; }, ), 'KP_End', @@ -70,6 +69,7 @@ is(listen_for_binding( xtest_key_release(87); # KP_1 xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'KP_1', @@ -81,6 +81,7 @@ is(listen_for_binding( xtest_key_press(38); # a xtest_key_release(38); # a xtest_key_release(133); # Super_L + xtest_sync_with_i3; }, ), 'a', @@ -96,6 +97,7 @@ is(listen_for_binding( xtest_key_release(133); # Super_L xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'a', @@ -105,6 +107,7 @@ is(listen_for_binding( sub { xtest_key_press(9); # Escape xtest_key_release(9); # Escape + xtest_sync_with_i3; }, ), 'Escape', @@ -118,6 +121,7 @@ is(listen_for_binding( xtest_key_release(9); # Escape xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'Escape', @@ -129,6 +133,7 @@ is(listen_for_binding( xtest_key_press(9); # Escape xtest_key_release(9); # Escape xtest_key_release(50); # Shift_L + xtest_sync_with_i3; }, ), 'Shift+Escape', @@ -144,6 +149,7 @@ is(listen_for_binding( xtest_key_release(50); # Shift_L xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'Shift+Escape', @@ -157,6 +163,7 @@ is(listen_for_binding( xtest_key_release(24); # q xtest_key_release(64); # Alt_L xtest_key_release(50); # Shift_L + xtest_sync_with_i3; }, ), 'Mod1+Shift+q', @@ -174,6 +181,7 @@ is(listen_for_binding( xtest_key_release(50); # Shift_L xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'Mod1+Shift+q', @@ -183,6 +191,7 @@ is(listen_for_binding( sub { xtest_key_press(39); # s xtest_key_release(39); # s + xtest_sync_with_i3; }, ), 's', @@ -196,14 +205,12 @@ is(listen_for_binding( xtest_key_release(39); # s xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 's', '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); ################################################################################ @@ -222,12 +229,11 @@ EOT $pid = launch_with_config($config); -start_binding_capture; - is(listen_for_binding( sub { xtest_key_press(133); # Super_L xtest_key_release(133); # Super_L + xtest_sync_with_i3; }, ), 'Super_L', @@ -241,6 +247,7 @@ is(listen_for_binding( xtest_key_release(133); # Super_L xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'Super_L', @@ -252,6 +259,7 @@ is(listen_for_binding( xtest_key_press(36); # Return xtest_key_release(36); # Return xtest_key_release(133); # Super_L + xtest_sync_with_i3; }, ), 'Return', @@ -267,14 +275,12 @@ is(listen_for_binding( xtest_key_release(133); # Super_L xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'Return', '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); ################################################################################ @@ -291,12 +297,11 @@ EOT $pid = launch_with_config($config); -start_binding_capture; - is(listen_for_binding( sub { xtest_key_press(87); # KP_End xtest_key_release(87); # KP_End + xtest_sync_with_i3; }, ), 'KP_End', @@ -306,12 +311,13 @@ is(listen_for_binding( sub { xtest_key_press(88); # KP_Down xtest_key_release(88); # KP_Down + xtest_sync_with_i3; }, ), 'KP_Down', 'triggered the "KP_Down" keybinding'); -is(listen_for_binding( +my @unexpected = events_for( sub { xtest_key_press(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_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, - ), - 'timeout', - 'Did not trigger the KP_End keybinding with KP_1'); + 'binding'); +is(scalar @unexpected, 0, 'Did not trigger the KP_End keybinding with KP_1'); -is(listen_for_binding( +my @unexpected2 = events_for( sub { xtest_key_press(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_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, - ), - 'timeout', - 'Did not trigger the KP_Down keybinding with KP_2'); + 'binding'); + +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. -sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 18, 'Received exactly 18 binding events'); - exit_gracefully($pid); ################################################################################ @@ -359,8 +363,6 @@ $pid = launch_with_config($config); my $win = open_window; -start_binding_capture; - is(listen_for_binding( sub { xtest_key_press(77); # enable Num_Lock @@ -369,6 +371,7 @@ is(listen_for_binding( xtest_button_release(4, 50, 50); xtest_key_press(77); # disable Num_Lock xtest_key_release(77); # disable Num_Lock + xtest_sync_with_i3; }, ), 'button4', @@ -376,8 +379,9 @@ is(listen_for_binding( is(listen_for_binding( sub { - xtest_button_press(4, 50, 50); - xtest_button_release(4, 50, 50); + xtest_button_press(4, 50, 50); + xtest_button_release(4, 50, 50); + xtest_sync_with_i3; }, ), 'button4', diff --git a/testcases/t/514-ipc-workspace-multi-monitor.t b/testcases/t/514-ipc-workspace-multi-monitor.t index e3753bec..ac918fe3 100644 --- a/testcases/t/514-ipc-workspace-multi-monitor.t +++ b/testcases/t/514-ipc-workspace-multi-monitor.t @@ -13,7 +13,7 @@ # # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf # (unless you are already familiar with Perl) -# +# # Ticket: #990 # 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 EOT -my $i3 = i3(get_socket_path()); - -$i3->connect()->recv; - -################################ -# Workspaces requests and events -################################ - 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; -$i3->subscribe({ - workspace => sub { - my ($event) = @_; - 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 @events = events_for( + sub { cmd 'focus output right' }, + 'workspace'); my $current_ws = get_ws(focused_ws); -ok($event, 'Workspace "focus" event received'); -is($event->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace'); -is($event->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace'); +is(scalar @events, 1, 'Received 1 event'); +is($events[0]->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace'); +is($events[0]->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace'); done_testing; diff --git a/testcases/t/517-regress-move-direction-ipc.t b/testcases/t/517-regress-move-direction-ipc.t index 217d898c..2f7f2b27 100644 --- a/testcases/t/517-regress-move-direction-ipc.t +++ b/testcases/t/517-regress-move-direction-ipc.t @@ -27,51 +27,29 @@ workspace ws-left output fake-0 workspace ws-right output fake-1 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 cmd 'workspace ws-left'; 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 -cmd 'move right'; -my $event = $focus->recv; +subtest 'move right (1)', \&focus_subtest, 'move right', 1; -ok($event, 'moving from workspace with two windows triggered focus ipc event'); -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; +# move another window cmd 'workspace ws-left'; -$focus->recv; - -$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'); +subtest 'move right (2)', \&focus_subtest, 'move right', 2; done_testing; diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index ff34328c..d3216d97 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -34,17 +34,12 @@ bar { EOT 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()); $i3->connect()->recv; my $ws = fresh_workspace; -reset_test; +my $cv = AnyEvent->condvar; +my $timer = AnyEvent->timer(1, 0, sub { $cv->send(0) }); $i3->subscribe({ window => sub { my ($event) = @_; @@ -60,15 +55,13 @@ $i3->subscribe({ }, })->recv; -my $con; - sub i3bar_present { my ($nodes) = @_; for my $node (@{$nodes}) { my $props = $node->{window_properties}; if (defined($props) && $props->{class} eq 'i3bar') { - return 1; + return $node->{window}; } } @@ -80,53 +73,75 @@ sub i3bar_present { 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'); } else { - $con = $cv->recv; + my $con = $cv->recv; ok($con, 'i3bar appeared'); + $i3bar_window = $con->{window}; } +diag('i3bar window = ' . $i3bar_window); + my $left = open_window; my $right = open_window; sync_with_i3; -$con = $cv->recv; +my $con = $cv->recv; is($con->{window}, $right->{id}, 'focus is initially on the right container'); -reset_test; -xtest_button_press(1, 3, 3); -xtest_button_release(1, 3, 3); -sync_with_i3; -$con = $cv->recv; -is($con->{window}, $left->{id}, 'button 1 moves focus left'); -reset_test; +sub focus_subtest { + my ($subscribecb, $want, $msg) = @_; + my @events = events_for( + $subscribecb, + 'window'); + my @focus = map { $_->{container}->{window} } grep { $_->{change} eq 'focus' } @events; + is_deeply(\@focus, $want, $msg); +} -xtest_button_press(2, 3, 3); -xtest_button_release(2, 3, 3); -sync_with_i3; -$con = $cv->recv; -is($con->{window}, $right->{id}, 'button 2 moves focus right'); -reset_test; +subtest 'button 1 moves focus left', \&focus_subtest, + sub { + xtest_button_press(1, 3, 3); + xtest_button_release(1, 3, 3); + xtest_sync_with($i3bar_window); + }, + [ $left->{id} ], + 'button 1 moves focus left'; -xtest_button_press(3, 3, 3); -xtest_button_release(3, 3, 3); -sync_with_i3; -$con = $cv->recv; -is($con->{window}, $left->{id}, 'button 3 moves focus left'); -reset_test; +subtest 'button 2 moves focus right', \&focus_subtest, + sub { + xtest_button_press(2, 3, 3); + xtest_button_release(2, 3, 3); + xtest_sync_with($i3bar_window); + }, + [ $right->{id} ], + 'button 2 moves focus right'; -xtest_button_press(4, 3, 3); -xtest_button_release(4, 3, 3); -sync_with_i3; -$con = $cv->recv; -is($con->{window}, $right->{id}, 'button 4 moves focus right'); -reset_test; +subtest 'button 3 moves focus left', \&focus_subtest, + sub { + xtest_button_press(3, 3, 3); + xtest_button_release(3, 3, 3); + xtest_sync_with($i3bar_window); + }, + [ $left->{id} ], + 'button 3 moves focus left'; -xtest_button_press(5, 3, 3); -xtest_button_release(5, 3, 3); -sync_with_i3; -$con = $cv->recv; -is($con->{window}, $left->{id}, 'button 5 moves focus left'); -reset_test; +subtest 'button 4 moves focus right', \&focus_subtest, + sub { + xtest_button_press(4, 3, 3); + xtest_button_release(4, 3, 3); + xtest_sync_with($i3bar_window); + }, + [ $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;