use TAP::Harness;
use TAP::Parser;
use TAP::Parser::Aggregator;
+# these are shipped with the testsuite
+use lib qw(lib);
+use SocketActivation;
# the following modules are not shipped with Perl
use EV;
use AnyEvent;
my $activate_cv = AnyEvent->condvar;
my $time_before_start = [gettimeofday];
- my $start_i3 = sub {
- # remove the old unix socket
- unlink("/tmp/nested-$display-activation");
-
- # pass all file descriptors up to three to the children.
- # we need to set this flag before opening the socket.
- open(my $fdtest, '<', '/dev/null');
- $^F = fileno($fdtest);
- close($fdtest);
- my $socket = IO::Socket::UNIX->new(
- Listen => 1,
- Local => "/tmp/nested-$display-activation",
- );
- my $pid = fork;
- if (!defined($pid)) {
- die "could not fork()";
- }
- if ($pid == 0) {
- $ENV{LISTEN_PID} = $$;
- $ENV{LISTEN_FDS} = 1;
- $ENV{DISPLAY} = $display;
- $^F = 3;
-
- POSIX::close(3);
- POSIX::dup2(fileno($socket), 3);
-
- # now execute i3
- my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
- my $cmd = "exec $i3cmd -c $tmpfile >$logpath 2>&1";
- exec "/bin/sh", '-c', $cmd;
-
- # if we are still here, i3 could not be found or exec failed. bail out.
- exit 1;
- }
+ my $pid;
+ if (!$dont_start) {
+ $pid = activate_i3(
+ unix_socket_path => "/tmp/nested-$display-activation",
+ display => $display,
+ configfile => $tmpfile,
+ logpath => $logpath,
+ cv => $activate_cv
+ );
my $child_watcher;
$child_watcher = AnyEvent->child(pid => $pid, cb => sub {
say "child died. pid = $pid";
undef $child_watcher;
});
-
- # close the socket, the child process should be the only one which keeps a file
- # descriptor on the listening socket.
- $socket->close;
-
- # We now connect (will succeed immediately) and send a request afterwards.
- # As soon as the reply is there, i3 is considered ready.
- my $cl = IO::Socket::UNIX->new(Peer => "/tmp/nested-$display-activation");
- my $hdl;
- $hdl = AnyEvent::Handle->new(fh => $cl, on_error => sub { $activate_cv->send(0) });
-
- # send a get_tree message without payload
- $hdl->push_write('i3-ipc' . pack("LL", 0, 4));
-
- # wait for the reply
- $hdl->push_read(chunk => 1, => sub {
- my ($h, $line) = @_;
- $activate_cv->send(1);
- undef $hdl;
- });
-
- return $pid;
- };
-
- my $pid;
- $pid = $start_i3->() unless $dont_start;
+ }
my $kill_i3 = sub {
# Don’t bother killing i3 when we haven’t started it
--- /dev/null
+package SocketActivation;
+# vim:ts=4:sw=4:expandtab
+
+use IO::Socket::UNIX; # core
+use Cwd qw(abs_path); # core
+use POSIX; # core
+use AnyEvent::Handle; # not core
+use Exporter 'import';
+use v5.10;
+
+our @EXPORT = qw(activate_i3);
+
+#
+# Starts i3 using socket activation. Creates a listening socket (with bind +
+# listen) which is then passed to i3, who in turn calls accept and handles the
+# requests.
+#
+# Since the kernel buffers the connect, the parent process can connect to the
+# socket immediately after forking. It then sends a request and waits until it
+# gets an answer. Obviously, i3 has to be initialized to actually answer the
+# request.
+#
+# This way, we can wait *precisely* the amount of time which i3 waits to get
+# ready, which is a *HUGE* speed gain (and a lot more robust) in comparison to
+# using sleep() with a fixed amount of time.
+#
+# unix_socket_path: Location of the socket to use for the activation
+# display: X11 $ENV{DISPLAY}
+# configfile: path to the configuration file to use
+# logpath: path to the logfile to which i3 will append
+# cv: an AnyEvent->condvar which will be triggered once i3 is ready
+#
+sub activate_i3 {
+ my %args = @_;
+
+ # remove the old unix socket
+ unlink($args{unix_socket_path});
+
+ # pass all file descriptors up to three to the children.
+ # we need to set this flag before opening the socket.
+ open(my $fdtest, '<', '/dev/null');
+ $^F = fileno($fdtest);
+ close($fdtest);
+ my $socket = IO::Socket::UNIX->new(
+ Listen => 1,
+ Local => $args{unix_socket_path},
+ );
+
+ my $pid = fork;
+ if (!defined($pid)) {
+ die "could not fork()";
+ }
+ if ($pid == 0) {
+ $ENV{LISTEN_PID} = $$;
+ $ENV{LISTEN_FDS} = 1;
+ $ENV{DISPLAY} = $args{display};
+ $^F = 3;
+
+ POSIX::close(3);
+ POSIX::dup2(fileno($socket), 3);
+
+ # now execute i3
+ my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
+ my $cmd = "exec $i3cmd -c $args{configfile} >$args{logpath} 2>&1";
+ exec "/bin/sh", '-c', $cmd;
+
+ # if we are still here, i3 could not be found or exec failed. bail out.
+ exit 1;
+ }
+
+ # close the socket, the child process should be the only one which keeps a file
+ # descriptor on the listening socket.
+ $socket->close;
+
+ # We now connect (will succeed immediately) and send a request afterwards.
+ # As soon as the reply is there, i3 is considered ready.
+ my $cl = IO::Socket::UNIX->new(Peer => $args{unix_socket_path});
+ my $hdl;
+ $hdl = AnyEvent::Handle->new(
+ fh => $cl,
+ on_error => sub {
+ $hdl->destroy;
+ $args{cv}->send(0);
+ });
+
+ # send a get_tree message without payload
+ $hdl->push_write('i3-ipc' . pack("LL", 0, 4));
+
+ # wait for the reply
+ $hdl->push_read(chunk => 1, => sub {
+ my ($h, $line) = @_;
+ $args{cv}->send(1);
+ undef $hdl;
+ });
+
+ return $pid;
+}
+
+1