]> git.sur5r.net Git - i3/i3/blob - lib/AnyEvent/I3.pm
d2fae1c7a1f5d3d19cd2ba2f8664aff93d905550
[i3/i3] / lib / AnyEvent / I3.pm
1 package AnyEvent::I3;
2 # vim:ts=4:sw=4:expandtab
3
4 use strict;
5 use warnings;
6 use JSON::XS;
7 use AnyEvent::Handle;
8 use AnyEvent::Socket;
9 use AnyEvent;
10
11 =head1 NAME
12
13 AnyEvent::I3 - communicate with the i3 window manager
14
15 =cut
16
17 our $VERSION = '0.01';
18
19 =head1 VERSION
20
21 Version 0.01
22
23 =head1 SYNOPSIS
24
25 This module connects to the i3 window manager using the UNIX socket based
26 IPC interface it provides (if enabled in the configuration file). You can
27 then subscribe to events or send messages and receive their replies.
28
29 Note that as soon as you subscribe to some kind of event, you should B<NOT>
30 send any more messages as race conditions might occur. Instead, open another
31 connection for that.
32
33     use AnyEvent::I3 qw(:all);
34
35     my $i3 = i3("/tmp/i3-ipc.sock");
36
37     $i3->connect->recv;
38     say "Connected to i3";
39
40     my $workspaces = $i3->message(TYPE_GET_WORKSPACES)->recv;
41     say "Currently, you use " . @{$workspaces} . " workspaces";
42
43 =head1 EXPORT
44
45 =head2 $i3 = i3([ $path ]);
46
47 Creates a new C<AnyEvent::I3> object and returns it. C<path> is the path of
48 the UNIX socket to connect to.
49
50 =head1 SUBROUTINES/METHODS
51
52 =cut
53
54 use Exporter;
55 use base 'Exporter';
56
57 our @EXPORT = qw(i3);
58
59 use constant TYPE_COMMAND => 0;
60 use constant TYPE_GET_WORKSPACES => 1;
61 use constant TYPE_SUBSCRIBE => 2;
62
63 our %EXPORT_TAGS = ( 'all' => [
64     qw(TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE)
65 ] );
66
67 our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
68
69 my $magic = "i3-ipc";
70
71 # TODO: auto-generate this from the header file? (i3/ipc.h)
72 my $event_mask = (1 << 31);
73 my %events = (
74     workspace => ($event_mask | 0),
75 );
76
77 sub _bytelength {
78     my ($scalar) = @_;
79     use bytes;
80     length($scalar)
81 }
82
83 sub i3 {
84     AnyEvent::I3->new(@_)
85 }
86
87 =head2 $i3 = AnyEvent::I3->new([ $path ])
88
89 Creates a new C<AnyEvent::I3> object and returns it. C<path> is the path of
90 the UNIX socket to connect to.
91
92 =cut
93 sub new {
94     my ($class, $path) = @_;
95
96     $path ||= '/tmp/i3-ipc.sock';
97
98     bless { path => $path } => $class;
99 }
100
101 =head2 $i3->connect
102
103 Establishes the connection to i3. Returns an C<AnyEvent::CondVar> which will
104 be triggered with a boolean (true if the connection was established) as soon as
105 the connection has been established.
106
107     if ($i3->connect->recv) {
108         say "Connected to i3";
109     }
110
111 =cut
112 sub connect {
113     my ($self) = @_;
114     my $hdl;
115     my $cv = AnyEvent->condvar;
116
117     tcp_connect "unix/", $self->{path}, sub {
118         my ($fh) = @_;
119
120         return $cv->send(0) unless $fh;
121
122         $self->{ipchdl} = AnyEvent::Handle->new(
123             fh => $fh,
124             on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) }
125         );
126
127         $cv->send(1)
128     };
129
130     $cv
131 }
132
133 sub _data_available {
134     my ($self, $hdl) = @_;
135
136     $hdl->unshift_read(
137         chunk => length($magic) + 4 + 4,
138         sub {
139             my $header = $_[1];
140             # Unpack message length and read the payload
141             my ($len, $type) = unpack("LL", substr($header, length($magic)));
142             $hdl->unshift_read(
143                 chunk => $len,
144                 sub { $self->_handle_i3_message($type, $_[1]) }
145             );
146         }
147     );
148 }
149
150 sub _handle_i3_message {
151     my ($self, $type, $payload) = @_;
152
153     return unless defined($self->{callbacks}->{$type});
154
155     my $cb = $self->{callbacks}->{$type};
156     $cb->(decode_json $payload);
157 }
158
159 =head2 $i3->subscribe(\%callbacks)
160
161 Subscribes to the given event types. This function awaits a hashref with the
162 key being the name of the event and the value being a callback.
163
164     $i3->subscribe({
165         workspace => sub { say "Workspaces changed" }
166     });
167
168 =cut
169 sub subscribe {
170     my ($self, $callbacks) = @_;
171
172     my $payload = encode_json [ keys %{$callbacks} ];
173     my $message = $magic . pack("LL", _bytelength($payload), 2) . $payload;
174     $self->{ipchdl}->push_write($message);
175
176     # Register callbacks for each message type
177     for my $key (keys %{$callbacks}) {
178         my $type = $events{$key};
179         $self->{callbacks}->{$type} = $callbacks->{$key};
180     }
181 }
182
183 =head2 $i3->message($type, $content)
184
185 Sends a message of the specified C<type> to i3, possibly containing the data
186 structure C<payload>, if specified.
187
188     my $reply = $i3->message(TYPE_COMMAND, "reload")->recv;
189     if ($reply->{success}) {
190         say "Configuration successfully reloaded";
191     }
192
193 =cut
194 sub message {
195     my ($self, $type, $content) = @_;
196
197     die "No message type specified" unless $type;
198
199     my $payload = "";
200     if ($content) {
201         if (ref($content) eq "SCALAR") {
202             $payload = $content;
203         } else {
204             $payload = encode_json $content;
205         }
206     }
207     my $message = $magic . pack("LL", _bytelength($payload), $type) . $payload;
208     $self->{ipchdl}->push_write($message);
209
210     my $cv = AnyEvent->condvar;
211
212     # We don’t preserve the old callback as it makes no sense to
213     # have a callback on message reply types (only on events)
214     $self->{callbacks}->{$type} =
215         sub {
216             my ($reply) = @_;
217             $cv->send($reply);
218             undef $self->{callbacks}->{$type};
219         };
220
221     $cv
222 }
223
224 =head1 AUTHOR
225
226 Michael Stapelberg, C<< <michael at stapelberg.de> >>
227
228 =head1 BUGS
229
230 Please report any bugs or feature requests to C<bug-anyevent-i3 at rt.cpan.org>, or through
231 the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-I3>.  I will be notified, and then you'll
232 automatically be notified of progress on your bug as I make changes.
233
234 =head1 SUPPORT
235
236 You can find documentation for this module with the perldoc command.
237
238     perldoc AnyEvent::I3
239
240 You can also look for information at:
241
242 =over 2
243
244 =item * RT: CPAN's request tracker
245
246 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3>
247
248 =item * The i3 window manager website
249
250 L<http://i3.zekjur.net/>
251
252 =back
253
254
255 =head1 ACKNOWLEDGEMENTS
256
257
258 =head1 LICENSE AND COPYRIGHT
259
260 Copyright 2010 Michael Stapelberg.
261
262 This program is free software; you can redistribute it and/or modify it
263 under the terms of either: the GNU General Public License as published
264 by the Free Software Foundation; or the Artistic License.
265
266 See http://dev.perl.org/licenses/ for more information.
267
268
269 =cut
270
271 1; # End of AnyEvent::I3