]> git.sur5r.net Git - i3/i3/blob - testcases/lib/SocketActivation.pm
Merge branch 'exec_nosn' into next
[i3/i3] / testcases / lib / SocketActivation.pm
1 package SocketActivation;
2 # vim:ts=4:sw=4:expandtab
3
4 use IO::Socket::UNIX; # core
5 use Cwd qw(abs_path); # core
6 use POSIX; # core
7 use AnyEvent::Handle; # not core
8 use Exporter 'import';
9 use v5.10;
10
11 our @EXPORT = qw(activate_i3);
12
13 #
14 # Starts i3 using socket activation. Creates a listening socket (with bind +
15 # listen) which is then passed to i3, who in turn calls accept and handles the
16 # requests.
17 #
18 # Since the kernel buffers the connect, the parent process can connect to the
19 # socket immediately after forking. It then sends a request and waits until it
20 # gets an answer. Obviously, i3 has to be initialized to actually answer the
21 # request.
22 #
23 # This way, we can wait *precisely* the amount of time which i3 waits to get
24 # ready, which is a *HUGE* speed gain (and a lot more robust) in comparison to
25 # using sleep() with a fixed amount of time.
26 #
27 # unix_socket_path: Location of the socket to use for the activation
28 # display: X11 $ENV{DISPLAY}
29 # configfile: path to the configuration file to use
30 # logpath: path to the logfile to which i3 will append
31 # cv: an AnyEvent->condvar which will be triggered once i3 is ready
32 #
33 sub activate_i3 {
34     my %args = @_;
35
36     # remove the old unix socket
37     unlink($args{unix_socket_path});
38
39     # pass all file descriptors up to three to the children.
40     # we need to set this flag before opening the socket.
41     open(my $fdtest, '<', '/dev/null');
42     $^F = fileno($fdtest);
43     close($fdtest);
44     my $socket = IO::Socket::UNIX->new(
45         Listen => 1,
46         Local => $args{unix_socket_path},
47     );
48
49     my $pid = fork;
50     if (!defined($pid)) {
51         die "could not fork()";
52     }
53     if ($pid == 0) {
54         $ENV{LISTEN_PID} = $$;
55         $ENV{LISTEN_FDS} = 1;
56         delete $ENV{DESKTOP_STARTUP_ID};
57         $ENV{DISPLAY} = $args{display};
58         $ENV{PATH} = join(':',
59             '../i3-nagbar',
60             '../i3-msg',
61             '../i3-config-wizard',
62             '../i3bar',
63             '..',
64             $ENV{PATH}
65         );
66         # Only pass file descriptors 0 (stdin), 1 (stdout), 2 (stderr) and
67         # 3 (socket) to the child.
68         $^F = 3;
69
70         # If the socket does not use file descriptor 3 by chance already, we
71         # close fd 3 and dup2() the socket to 3.
72         if (fileno($socket) != 3) {
73             POSIX::close(3);
74             POSIX::dup2(fileno($socket), 3);
75         }
76
77         # Construct the command to launch i3. Use maximum debug level, disable
78         # the interactive signalhandler to make it crash immediately instead.
79         my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler";
80
81         # Append to $args{logpath} instead of overwriting because i3 might be
82         # run multiple times in one testcase.
83         my $cmd = "exec $i3cmd -c $args{configfile} >>$args{logpath} 2>&1";
84
85         # We need to use the shell due to using output redirections.
86         exec "/bin/sh", '-c', $cmd;
87
88         # if we are still here, i3 could not be found or exec failed. bail out.
89         exit 1;
90     }
91
92     # close the socket, the child process should be the only one which keeps a file
93     # descriptor on the listening socket.
94     $socket->close;
95
96     # We now connect (will succeed immediately) and send a request afterwards.
97     # As soon as the reply is there, i3 is considered ready.
98     my $cl = IO::Socket::UNIX->new(Peer => $args{unix_socket_path});
99     my $hdl;
100     $hdl = AnyEvent::Handle->new(
101         fh => $cl,
102         on_error => sub {
103             $hdl->destroy;
104             $args{cv}->send(0);
105         });
106
107     # send a get_tree message without payload
108     $hdl->push_write('i3-ipc' . pack("LL", 0, 4));
109
110     # wait for the reply
111     $hdl->push_read(chunk => 1, => sub {
112         my ($h, $line) = @_;
113         $args{cv}->send(1);
114         undef $hdl;
115     });
116
117     return $pid;
118 }
119
120 1