2 # vim:ts=4:sw=4:expandtab
11 use Scalar::Util qw(tainted);
15 AnyEvent::I3 - communicate with the i3 window manager
19 our $VERSION = '0.18';
27 This module connects to the i3 window manager using the UNIX socket based
28 IPC interface it provides (if enabled in the configuration file). You can
29 then subscribe to events or send messages and receive their replies.
31 use AnyEvent::I3 qw(:all);
35 $i3->connect->recv or die "Error connecting";
36 say "Connected to i3";
38 my $workspaces = $i3->message(TYPE_GET_WORKSPACES)->recv;
39 say "Currently, you use " . @{$workspaces} . " workspaces";
41 ...or, using the sugar methods:
45 my $workspaces = i3->get_workspaces->recv;
46 say "Currently, you use " . @{$workspaces} . " workspaces";
48 A somewhat more involved example which dumps the i3 layout tree whenever there
57 $i3->connect->recv or die "Error connecting to i3";
61 $i3->get_tree->cb(sub {
63 say "tree: " . Dumper($tree);
66 })->recv->{success} or die "Error subscribing to events";
72 =head2 $i3 = i3([ $path ]);
74 Creates a new C<AnyEvent::I3> object and returns it.
76 C<path> is an optional path of the UNIX socket to connect to. It is strongly
77 advised to NOT specify this unless you're absolutely sure you need it.
78 C<AnyEvent::I3> will automatically figure it out by querying the running i3
79 instance on the current DISPLAY which is almost always what you want.
81 =head1 SUBROUTINES/METHODS
85 use Exporter qw(import);
90 use constant TYPE_RUN_COMMAND => 0;
91 use constant TYPE_COMMAND => 0;
92 use constant TYPE_GET_WORKSPACES => 1;
93 use constant TYPE_SUBSCRIBE => 2;
94 use constant TYPE_GET_OUTPUTS => 3;
95 use constant TYPE_GET_TREE => 4;
96 use constant TYPE_GET_MARKS => 5;
97 use constant TYPE_GET_BAR_CONFIG => 6;
98 use constant TYPE_GET_VERSION => 7;
99 use constant TYPE_GET_BINDING_MODES => 8;
100 use constant TYPE_GET_CONFIG => 9;
102 our %EXPORT_TAGS = ( 'all' => [
103 qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
104 TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
105 TYPE_GET_BINDING_MODES TYPE_GET_CONFIG)
108 our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
110 my $magic = "i3-ipc";
112 # TODO: auto-generate this from the header file? (i3/ipc.h)
113 my $event_mask = (1 << 31);
115 workspace => ($event_mask | 0),
116 output => ($event_mask | 1),
117 mode => ($event_mask | 2),
118 window => ($event_mask | 3),
119 barconfig_update => ($event_mask | 4),
120 binding => ($event_mask | 5),
121 shutdown => ($event_mask | 6),
122 _error => 0xFFFFFFFF,
126 AnyEvent::I3->new(@_)
129 # Calls i3, even when running in taint mode.
133 my $path_tainted = tainted($ENV{PATH});
134 # This effectively circumvents taint mode checking for $ENV{PATH}. We
135 # do this because users might specify PATH explicitly to call i3 in a
136 # custom location (think ~/.bin/).
137 (local $ENV{PATH}) = ($ENV{PATH} =~ /(.*)/);
139 # In taint mode, we also need to remove all relative directories from
140 # PATH (like . or ../bin). We only do this in taint mode and warn the
141 # user, since this might break a real-world use case for some people.
143 my @dirs = split /:/, $ENV{PATH};
144 my @filtered = grep !/^\./, @dirs;
145 if (scalar @dirs != scalar @filtered) {
146 $ENV{PATH} = join ':', @filtered;
147 warn qq|Removed relative directories from PATH because you | .
148 qq|are running Perl with taint mode enabled. Remove -T | .
149 qq|to be able to use relative directories in PATH. | .
150 qq|New PATH is "$ENV{PATH}"|;
153 # Otherwise the qx() operator wont work:
154 delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
155 chomp(my $result = qx(i3 $args));
156 # Circumventing taint mode again: the socket can be anywhere on the
157 # system and that’s okay.
158 if ($result =~ /^([^\0]+)$/) {
162 warn "Calling i3 $args failed. Is DISPLAY set and is i3 in your PATH?";
166 =head2 $i3 = AnyEvent::I3->new([ $path ])
168 Creates a new C<AnyEvent::I3> object and returns it.
170 C<path> is an optional path of the UNIX socket to connect to. It is strongly
171 advised to NOT specify this unless you're absolutely sure you need it.
172 C<AnyEvent::I3> will automatically figure it out by querying the running i3
173 instance on the current DISPLAY which is almost always what you want.
177 my ($class, $path) = @_;
179 $path = _call_i3('--get-socketpath') unless $path;
181 # This is the old default path (v3.*). This fallback line can be removed in
182 # a year from now. -- Michael, 2012-07-09
183 $path ||= '~/.i3/ipc.sock';
185 # Check if we need to resolve ~
187 # We use getpwuid() instead of $ENV{HOME} because the latter is tainted
188 # and thus produces warnings when running tests with perl -T
189 my $home = (getpwuid($<))[7];
190 die "Could not get home directory" unless $home and -d $home;
191 $path =~ s/~/$home/g;
194 bless { path => $path } => $class;
199 Establishes the connection to i3. Returns an C<AnyEvent::CondVar> which will
200 be triggered with a boolean (true if the connection was established) as soon as
201 the connection has been established.
203 if ($i3->connect->recv) {
204 say "Connected to i3";
210 my $cv = AnyEvent->condvar;
212 tcp_connect "unix/", $self->{path}, sub {
215 return $cv->send(0) unless $fh;
217 $self->{ipchdl} = AnyEvent::Handle->new(
219 on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) },
221 my ($hdl, $fatal, $msg) = @_;
222 delete $self->{ipchdl};
225 my $cb = $self->{callbacks};
227 # Trigger all one-time callbacks with undef
228 for my $type (keys %{$cb}) {
229 next if ($type & $event_mask) == $event_mask;
234 # Trigger _error callback, if set
235 my $type = $events{_error};
236 return unless defined($cb->{$type});
237 $cb->{$type}->($msg);
247 sub _data_available {
248 my ($self, $hdl) = @_;
251 chunk => length($magic) + 4 + 4,
254 # Unpack message length and read the payload
255 my ($len, $type) = unpack("LL", substr($header, length($magic)));
258 sub { $self->_handle_i3_message($type, $_[1]) }
264 sub _handle_i3_message {
265 my ($self, $type, $payload) = @_;
267 return unless defined($self->{callbacks}->{$type});
269 my $cb = $self->{callbacks}->{$type};
270 $cb->(decode_json $payload);
272 return if ($type & $event_mask) == $event_mask;
274 # If this was a one-time callback, we delete it
275 # (when connection is lost, all one-time callbacks get triggered)
276 delete $self->{callbacks}->{$type};
279 =head2 $i3->subscribe(\%callbacks)
281 Subscribes to the given event types. This function awaits a hashref with the
282 key being the name of the event and the value being a callback.
285 workspace => sub { say "Workspaces changed" }
288 if ($i3->subscribe(\%callbacks)->recv->{success}) {
289 say "Successfully subscribed";
292 The special callback with name C<_error> is called when the connection to i3
293 is killed (because of a crash, exit or restart of i3 most likely). You can
294 use it to print an appropriate message and exit cleanly or to try to reconnect.
299 say "I am sorry. I am so sorry: $msg";
304 $i3->subscribe(\%callbacks)->recv;
308 my ($self, $callbacks) = @_;
310 # Register callbacks for each message type
311 for my $key (keys %{$callbacks}) {
312 my $type = $events{$key};
313 $self->{callbacks}->{$type} = $callbacks->{$key};
316 $self->message(TYPE_SUBSCRIBE, [ keys %{$callbacks} ])
319 =head2 $i3->message($type, $content)
321 Sends a message of the specified C<type> to i3, possibly containing the data
322 structure C<content> (or C<content>, encoded as utf8, if C<content> is a
323 scalar), if specified.
325 my $reply = $i3->message(TYPE_RUN_COMMAND, "reload")->recv;
326 if ($reply->{success}) {
327 say "Configuration successfully reloaded";
332 my ($self, $type, $content) = @_;
334 die "No message type specified" unless defined($type);
336 die "No connection to i3" unless defined($self->{ipchdl});
340 if (not ref($content)) {
341 # Convert from Perl’s internal encoding to UTF8 octets
342 $payload = encode_utf8($content);
344 $payload = encode_json $content;
347 my $message = $magic . pack("LL", length($payload), $type) . $payload;
348 $self->{ipchdl}->push_write($message);
350 my $cv = AnyEvent->condvar;
352 # We don’t preserve the old callback as it makes no sense to
353 # have a callback on message reply types (only on events)
354 $self->{callbacks}->{$type} =
358 undef $self->{callbacks}->{$type};
366 These methods intend to make your scripts as beautiful as possible. All of
367 them automatically establish a connection to i3 blockingly (if it does not
372 sub _ensure_connection {
375 return if defined($self->{ipchdl});
377 $self->connect->recv or die "Unable to connect to i3 (socket path " . $self->{path} . ")";
380 =head2 get_workspaces
382 Gets the current workspaces from i3.
384 my $ws = i3->get_workspaces->recv;
391 $self->_ensure_connection;
393 $self->message(TYPE_GET_WORKSPACES)
398 Gets the current outputs from i3.
400 my $outs = i3->get_outputs->recv;
407 $self->_ensure_connection;
409 $self->message(TYPE_GET_OUTPUTS)
414 Gets the layout tree from i3 (>= v4.0).
416 my $tree = i3->get_tree->recv;
423 $self->_ensure_connection;
425 $self->message(TYPE_GET_TREE)
430 Gets all the window identifier marks from i3 (>= v4.1).
432 my $marks = i3->get_marks->recv;
439 $self->_ensure_connection;
441 $self->message(TYPE_GET_MARKS)
444 =head2 get_bar_config
446 Gets the bar configuration for the specific bar id from i3 (>= v4.1).
448 my $config = i3->get_bar_config($id)->recv;
453 my ($self, $id) = @_;
455 $self->_ensure_connection;
457 $self->message(TYPE_GET_BAR_CONFIG, $id)
462 Gets the i3 version via IPC, with a fall-back that parses the output of i3
463 --version (for i3 < v4.3).
465 my $version = i3->get_version()->recv;
466 say "major: " . $version->{major} . ", minor = " . $version->{minor};
472 $self->_ensure_connection;
474 my $cv = AnyEvent->condvar;
476 my $version_cv = $self->message(TYPE_GET_VERSION);
478 $timeout = AnyEvent->timer(
481 warn "Falling back to i3 --version since the running i3 doesn’t support GET_VERSION yet.";
482 my $version = _call_i3('--version');
483 $version =~ s/^i3 version //;
485 my ($major, $minor) = ($version =~ /^([0-9]+)\.([0-9]+)/);
486 if ($version =~ /^[0-9]+\.[0-9]+\.([0-9]+)/) {
489 # Strip everything from the © sign on.
490 $version =~ s/ ©.*$//g;
492 major => int($major),
493 minor => int($minor),
494 patch => int($patch),
495 human_readable => $version,
500 $version_cv->cb(sub {
502 $cv->send($version_cv->recv);
510 Gets the raw last read config from i3. Requires i3 >= 4.14
516 $self->_ensure_connection;
518 $self->message(TYPE_GET_CONFIG);
522 =head2 command($content)
524 Makes i3 execute the given command
526 my $reply = i3->command("reload")->recv;
527 die "command failed" unless $reply->{success};
531 my ($self, $content) = @_;
533 $self->_ensure_connection;
535 $self->message(TYPE_RUN_COMMAND, $content)
540 Michael Stapelberg, C<< <michael at i3wm.org> >>
544 Please report any bugs or feature requests to C<bug-anyevent-i3 at
545 rt.cpan.org>, or through the web interface at
546 L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-I3>. I will be
547 notified, and then you'll automatically be notified of progress on your bug as
552 You can find documentation for this module with the perldoc command.
556 You can also look for information at:
560 =item * RT: CPAN's request tracker
562 L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3>
564 =item * The i3 window manager website
571 =head1 ACKNOWLEDGEMENTS
574 =head1 LICENSE AND COPYRIGHT
576 Copyright 2010-2012 Michael Stapelberg.
578 This program is free software; you can redistribute it and/or modify it
579 under the terms of either: the GNU General Public License as published
580 by the Free Software Foundation; or the Artistic License.
582 See https://dev.perl.org/licenses/ for more information.
587 1; # End of AnyEvent::I3