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