]> git.sur5r.net Git - i3/i3/blob - lib/AnyEvent/I3.pm
use constant instead of magic number
[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 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