2 # vim:ts=4:sw=4:expandtab
14 AnyEvent::I3 - communicate with the i3 window manager
18 our $VERSION = '0.01';
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("/tmp/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->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;
68 our %EXPORT_TAGS = ( 'all' => [
69 qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS)
72 our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
76 # TODO: auto-generate this from the header file? (i3/ipc.h)
77 my $event_mask = (1 << 31);
79 workspace => ($event_mask | 0),
80 output => ($event_mask | 1),
88 =head2 $i3 = AnyEvent::I3->new([ $path ])
90 Creates a new C<AnyEvent::I3> object and returns it. C<path> is the path of
91 the UNIX socket to connect to.
95 my ($class, $path) = @_;
97 $path ||= '/tmp/i3-ipc.sock';
99 bless { path => $path } => $class;
104 Establishes the connection to i3. Returns an C<AnyEvent::CondVar> which will
105 be triggered with a boolean (true if the connection was established) as soon as
106 the connection has been established.
108 if ($i3->connect->recv) {
109 say "Connected to i3";
115 my $cv = AnyEvent->condvar;
117 tcp_connect "unix/", $self->{path}, sub {
120 return $cv->send(0) unless $fh;
122 $self->{ipchdl} = AnyEvent::Handle->new(
124 on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) },
126 my ($hdl, $fatal, $msg) = @_;
127 delete $self->{ipchdl};
130 my $cb = $self->{callbacks};
132 # Trigger all one-time callbacks with undef
133 for my $type (keys %{$cb}) {
134 next if ($type & $event_mask) == $event_mask;
138 # Trigger _error callback, if set
139 my $type = $events{_error};
140 return unless defined($cb->{$type});
141 $cb->{$type}->($msg);
151 sub _data_available {
152 my ($self, $hdl) = @_;
155 chunk => length($magic) + 4 + 4,
158 # Unpack message length and read the payload
159 my ($len, $type) = unpack("LL", substr($header, length($magic)));
162 sub { $self->_handle_i3_message($type, $_[1]) }
168 sub _handle_i3_message {
169 my ($self, $type, $payload) = @_;
171 return unless defined($self->{callbacks}->{$type});
173 my $cb = $self->{callbacks}->{$type};
174 $cb->(decode_json $payload);
176 return if ($type & $event_mask) == $event_mask;
178 # If this was a one-time callback, we delete it
179 # (when connection is lost, all one-time callbacks get triggered)
180 delete $self->{callbacks}->{$type};
183 =head2 $i3->subscribe(\%callbacks)
185 Subscribes to the given event types. This function awaits a hashref with the
186 key being the name of the event and the value being a callback.
189 workspace => sub { say "Workspaces changed" }
192 if ($i3->subscribe(\%callbacks)->recv->{success})
193 say "Successfully subscribed";
196 The special callback with name C<_error> is called when the connection to i3
197 is killed (because of a crash, exit or restart of i3 most likely). You can
198 use it to print an appropriate message and exit cleanly or to try to reconnect.
203 say "I am sorry. I am so sorry: $msg";
208 $i3->subscribe(\%callbacks)->recv;
212 my ($self, $callbacks) = @_;
214 # Register callbacks for each message type
215 for my $key (keys %{$callbacks}) {
216 my $type = $events{$key};
217 $self->{callbacks}->{$type} = $callbacks->{$key};
220 $self->message(TYPE_SUBSCRIBE, [ keys %{$callbacks} ])
223 =head2 $i3->message($type, $content)
225 Sends a message of the specified C<type> to i3, possibly containing the data
226 structure C<content> (or C<content>, encoded as utf8, if C<content> is a
227 scalar), if specified.
229 my $reply = $i3->message(TYPE_COMMAND, "reload")->recv;
230 if ($reply->{success}) {
231 say "Configuration successfully reloaded";
236 my ($self, $type, $content) = @_;
238 die "No message type specified" unless defined($type);
240 die "No connection to i3" unless defined($self->{ipchdl});
244 if (not ref($content)) {
245 # Convert from Perl’s internal encoding to UTF8 octets
246 $payload = encode_utf8($content);
248 $payload = encode_json $content;
251 my $message = $magic . pack("LL", length($payload), $type) . $payload;
252 $self->{ipchdl}->push_write($message);
254 my $cv = AnyEvent->condvar;
256 # We don’t preserve the old callback as it makes no sense to
257 # have a callback on message reply types (only on events)
258 $self->{callbacks}->{$type} =
262 undef $self->{callbacks}->{$type};
270 These methods intend to make your scripts as beautiful as possible. All of
271 them automatically establish a connection to i3 blockingly (if it does not
276 sub _ensure_connection {
279 return if defined($self->{ipchdl});
281 $self->connect->recv or die "Unable to connect to i3"
284 =head2 get_workspaces
286 Gets the current workspaces from i3.
288 my $ws = i3->get_workspaces->recv;
295 $self->_ensure_connection;
297 $self->message(TYPE_GET_WORKSPACES)
302 Gets the current outputs from i3.
304 my $outs = i3->get_outputs->recv;
311 $self->_ensure_connection;
313 $self->message(TYPE_GET_OUTPUTS)
316 =head2 command($content)
318 Makes i3 execute the given command
320 my $reply = i3->command("reload")->recv;
321 die "command failed" unless $reply->{success};
325 my ($self, $content) = @_;
327 $self->_ensure_connection;
329 $self->message(TYPE_COMMAND, $content)
334 Michael Stapelberg, C<< <michael at stapelberg.de> >>
338 Please report any bugs or feature requests to C<bug-anyevent-i3 at
339 rt.cpan.org>, or through the web interface at
340 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-I3>. I will be
341 notified, and then you'll automatically be notified of progress on your bug as
346 You can find documentation for this module with the perldoc command.
350 You can also look for information at:
354 =item * RT: CPAN's request tracker
356 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3>
358 =item * The i3 window manager website
360 L<http://i3.zekjur.net/>
365 =head1 ACKNOWLEDGEMENTS
368 =head1 LICENSE AND COPYRIGHT
370 Copyright 2010 Michael Stapelberg.
372 This program is free software; you can redistribute it and/or modify it
373 under the terms of either: the GNU General Public License as published
374 by the Free Software Foundation; or the Artistic License.
376 See http://dev.perl.org/licenses/ for more information.
381 1; # End of AnyEvent::I3