wait_for_unmap
$x
kill_all_windows
+ events_for
+ listen_for_binding
);
=head1 NAME
C<Time::HiRes>’s C<sleep> and C<i3test::Test> so that all of them are available
to you in your testcase.
-See also C<i3test::Test> (L<http://build.i3wm.org/docs/lib-i3test-test.html>)
+See also C<i3test::Test> (L<https://build.i3wm.org/docs/lib-i3test-test.html>)
which provides additional test instructions (like C<ok> or C<is>).
=cut
my $i3_autostart;
END {
-
- # testcases which start i3 manually should always call exit_gracefully
- # on their own. Let’s see, whether they really did.
- if (! $i3_autostart) {
- return unless $i3_pid;
-
- $tester->ok(undef, 'testcase called exit_gracefully()');
- }
+ # Skip the remaining cleanup for testcases which set i3_autostart => 0:
+ return if !defined($i3_pid) && !$i3_autostart;
# don't trigger SIGCHLD handler
local $SIG{CHLD};
exit_gracefully($i3_pid, "/tmp/nested-$ENV{DISPLAY}");
} else {
- kill(9, $i3_pid)
+ kill(-9, $i3_pid)
or $tester->BAIL_OUT("could not kill i3");
waitpid $i3_pid, 0;
my $pkg = caller;
$i3_autostart = delete($args{i3_autostart}) // 1;
+ my $i3_config = delete($args{i3_config}) // '-default';
- my $cv = launch_with_config('-default', dont_block => 1)
+ my $cv = launch_with_config($i3_config, dont_block => 1)
if $i3_autostart;
my $test_more_args = '';
$window->map;
wait_for_map($window);
+
+ # MapWindow is sent before i3 even starts rendering: the window is placed at
+ # temporary off-screen coordinates first, and x_push_changes() sends further
+ # X11 requests to set focus etc. Hence, we sync with i3 before continuing.
+ sync_with_i3();
+
return $window;
}
This can be used to ensure the results of a cmd 'focus left' are pushed to
X11 and that C<< $x->input_focus >> returns the correct value afterwards.
-See also L<http://build.i3wm.org/docs/testsuite.html> for a longer explanation.
+See also L<https://build.i3wm.org/docs/testsuite.html> for a longer explanation.
my $window = open_window;
$window->add_hint('urgency');
$_sync_window = open_window(
rect => [ -15, -15, 10, 10 ],
override_redirect => 1,
+ dont_map => 1,
);
}
if ($cache && defined($_cached_socket_path)) {
return $_cached_socket_path;
}
-
- my $atom = $x->atom(name => 'I3_SOCKET_PATH');
- my $cookie = $x->get_property(0, $x->get_root_window(), $atom->id, GET_PROPERTY_TYPE_ANY, 0, 256);
- my $reply = $x->get_property_reply($cookie->{sequence});
- my $socketpath = $reply->{value};
- if ($socketpath eq "/tmp/nested-$ENV{DISPLAY}") {
- $socketpath .= '-activation';
- }
+ my $socketpath = i3test::Util::get_socket_path($x);
$_cached_socket_path = $socketpath;
return $socketpath;
}
my ($fh, $tmpfile) = tempfile("i3-cfg-for-$ENV{TESTNAME}-XXXXX", UNLINK => 1);
+ say $fh "ipc-socket $tmp_socket_path"
+ unless $args{dont_add_socket_path};
+
if ($config ne '-default') {
- say $fh $config;
+ print $fh $config;
} else {
open(my $conf_fh, '<', '@abs_top_srcdir@/testcases/i3-test.config')
or $tester->BAIL_OUT("could not open default config: $!");
say $fh scalar <$conf_fh>;
}
- say $fh "ipc-socket $tmp_socket_path"
- unless $args{dont_add_socket_path};
-
close($fh);
my $cv = AnyEvent->condvar;
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 <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
Michael Stapelberg <michael@i3wm.org>