2 # vim:ts=4:sw=4:expandtab
14 AnyEvent::I3 - communicate with the i3 window manager
18 our $VERSION = '0.08';
26 This module connects to the i3 window manager using the UNIX socket based
27 IPC interface it provides (if enabled in the configuration file). You can
28 then subscribe to events or send messages and receive their replies.
30 use AnyEvent::I3 qw(:all);
32 my $i3 = i3("~/.i3/ipc.sock");
34 $i3->connect->recv or die "Error connecting";
35 say "Connected to i3";
37 my $workspaces = $i3->message(TYPE_GET_WORKSPACES)->recv;
38 say "Currently, you use " . @{$workspaces} . " workspaces";
40 ...or, using the sugar methods:
44 my $workspaces = i3->get_workspaces->recv;
45 say "Currently, you use " . @{$workspaces} . " workspaces";
49 =head2 $i3 = i3([ $path ]);
51 Creates a new C<AnyEvent::I3> object and returns it. C<path> is the path of
52 the UNIX socket to connect to.
54 =head1 SUBROUTINES/METHODS
58 use Exporter qw(import);
63 use constant TYPE_COMMAND => 0;
64 use constant TYPE_GET_WORKSPACES => 1;
65 use constant TYPE_SUBSCRIBE => 2;
66 use constant TYPE_GET_OUTPUTS => 3;
67 use constant TYPE_GET_TREE => 4;
68 use constant TYPE_GET_MARKS => 5;
69 use constant TYPE_GET_BAR_CONFIG => 6;
71 our %EXPORT_TAGS = ( 'all' => [
72 qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
73 TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG)
76 our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
80 # TODO: auto-generate this from the header file? (i3/ipc.h)
81 my $event_mask = (1 << 31);
83 workspace => ($event_mask | 0),
84 output => ($event_mask | 1),
92 =head2 $i3 = AnyEvent::I3->new([ $path ])
94 Creates a new C<AnyEvent::I3> object and returns it. C<path> is the path of
95 the UNIX socket to connect to.
99 my ($class, $path) = @_;
101 $path ||= '~/.i3/ipc.sock';
103 # Check if we need to resolve ~
105 # We use getpwuid() instead of $ENV{HOME} because the latter is tainted
106 # and thus produces warnings when running tests with perl -T
107 my $home = (getpwuid($<))[7];
108 die "Could not get home directory" unless $home and -d $home;
109 $path =~ s/~/$home/g;
112 bless { path => $path } => $class;
117 Establishes the connection to i3. Returns an C<AnyEvent::CondVar> which will
118 be triggered with a boolean (true if the connection was established) as soon as
119 the connection has been established.
121 if ($i3->connect->recv) {
122 say "Connected to i3";
128 my $cv = AnyEvent->condvar;
130 tcp_connect "unix/", $self->{path}, sub {
133 return $cv->send(0) unless $fh;
135 $self->{ipchdl} = AnyEvent::Handle->new(
137 on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) },
139 my ($hdl, $fatal, $msg) = @_;
140 delete $self->{ipchdl};
143 my $cb = $self->{callbacks};
145 # Trigger all one-time callbacks with undef
146 for my $type (keys %{$cb}) {
147 next if ($type & $event_mask) == $event_mask;
151 # Trigger _error callback, if set
152 my $type = $events{_error};
153 return unless defined($cb->{$type});
154 $cb->{$type}->($msg);
164 sub _data_available {
165 my ($self, $hdl) = @_;
168 chunk => length($magic) + 4 + 4,
171 # Unpack message length and read the payload
172 my ($len, $type) = unpack("LL", substr($header, length($magic)));
175 sub { $self->_handle_i3_message($type, $_[1]) }
181 sub _handle_i3_message {
182 my ($self, $type, $payload) = @_;
184 return unless defined($self->{callbacks}->{$type});
186 my $cb = $self->{callbacks}->{$type};
187 $cb->(decode_json $payload);
189 return if ($type & $event_mask) == $event_mask;
191 # If this was a one-time callback, we delete it
192 # (when connection is lost, all one-time callbacks get triggered)
193 delete $self->{callbacks}->{$type};
196 =head2 $i3->subscribe(\%callbacks)
198 Subscribes to the given event types. This function awaits a hashref with the
199 key being the name of the event and the value being a callback.
202 workspace => sub { say "Workspaces changed" }
205 if ($i3->subscribe(\%callbacks)->recv->{success})
206 say "Successfully subscribed";
209 The special callback with name C<_error> is called when the connection to i3
210 is killed (because of a crash, exit or restart of i3 most likely). You can
211 use it to print an appropriate message and exit cleanly or to try to reconnect.
216 say "I am sorry. I am so sorry: $msg";
221 $i3->subscribe(\%callbacks)->recv;
225 my ($self, $callbacks) = @_;
227 # Register callbacks for each message type
228 for my $key (keys %{$callbacks}) {
229 my $type = $events{$key};
230 $self->{callbacks}->{$type} = $callbacks->{$key};
233 $self->message(TYPE_SUBSCRIBE, [ keys %{$callbacks} ])
236 =head2 $i3->message($type, $content)
238 Sends a message of the specified C<type> to i3, possibly containing the data
239 structure C<content> (or C<content>, encoded as utf8, if C<content> is a
240 scalar), if specified.
242 my $reply = $i3->message(TYPE_COMMAND, "reload")->recv;
243 if ($reply->{success}) {
244 say "Configuration successfully reloaded";
249 my ($self, $type, $content) = @_;
251 die "No message type specified" unless defined($type);
253 die "No connection to i3" unless defined($self->{ipchdl});
257 if (not ref($content)) {
258 # Convert from Perl’s internal encoding to UTF8 octets
259 $payload = encode_utf8($content);
261 $payload = encode_json $content;
264 my $message = $magic . pack("LL", length($payload), $type) . $payload;
265 $self->{ipchdl}->push_write($message);
267 my $cv = AnyEvent->condvar;
269 # We don’t preserve the old callback as it makes no sense to
270 # have a callback on message reply types (only on events)
271 $self->{callbacks}->{$type} =
275 undef $self->{callbacks}->{$type};
283 These methods intend to make your scripts as beautiful as possible. All of
284 them automatically establish a connection to i3 blockingly (if it does not
289 sub _ensure_connection {
292 return if defined($self->{ipchdl});
294 $self->connect->recv or die "Unable to connect to i3"
297 =head2 get_workspaces
299 Gets the current workspaces from i3.
301 my $ws = i3->get_workspaces->recv;
308 $self->_ensure_connection;
310 $self->message(TYPE_GET_WORKSPACES)
315 Gets the current outputs from i3.
317 my $outs = i3->get_outputs->recv;
324 $self->_ensure_connection;
326 $self->message(TYPE_GET_OUTPUTS)
331 Gets the layout tree from i3 (>= v4.0).
333 my $tree = i3->get_tree->recv;
340 $self->_ensure_connection;
342 $self->message(TYPE_GET_TREE)
347 Gets all the window identifier marks from i3 (>= v4.1).
349 my $marks = i3->get_marks->recv;
356 $self->_ensure_connection;
358 $self->message(TYPE_GET_MARKS)
361 =head2 get_bar_config
363 Gets the bar configuration for the specific bar id from i3 (>= v4.1).
365 my $config = i3->get_bar_config($id)->recv;
370 my ($self, $id) = @_;
372 $self->_ensure_connection;
374 $self->message(TYPE_GET_BAR_CONFIG, $id)
377 =head2 command($content)
379 Makes i3 execute the given command
381 my $reply = i3->command("reload")->recv;
382 die "command failed" unless $reply->{success};
386 my ($self, $content) = @_;
388 $self->_ensure_connection;
390 $self->message(TYPE_COMMAND, $content)
395 Michael Stapelberg, C<< <michael at stapelberg.de> >>
399 Please report any bugs or feature requests to C<bug-anyevent-i3 at
400 rt.cpan.org>, or through the web interface at
401 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-I3>. I will be
402 notified, and then you'll automatically be notified of progress on your bug as
407 You can find documentation for this module with the perldoc command.
411 You can also look for information at:
415 =item * RT: CPAN's request tracker
417 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3>
419 =item * The i3 window manager website
421 L<http://i3.zekjur.net/>
426 =head1 ACKNOWLEDGEMENTS
429 =head1 LICENSE AND COPYRIGHT
431 Copyright 2010 Michael Stapelberg.
433 This program is free software; you can redistribute it and/or modify it
434 under the terms of either: the GNU General Public License as published
435 by the Free Software Foundation; or the Artistic License.
437 See http://dev.perl.org/licenses/ for more information.
442 1; # End of AnyEvent::I3