]> git.sur5r.net Git - i3/i3/blob - lib/AnyEvent/I3.pm
Bugfix: Handle message_type == 0
[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 use constant TYPE_GET_OUTPUTS => 3;
63
64 our %EXPORT_TAGS = ( 'all' => [
65     qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS)
66 ] );
67
68 our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
69
70 my $magic = "i3-ipc";
71
72 # TODO: auto-generate this from the header file? (i3/ipc.h)
73 my $event_mask = (1 << 31);
74 my %events = (
75     workspace => ($event_mask | 0),
76     output => ($event_mask | 1),
77 );
78
79 sub _bytelength {
80     my ($scalar) = @_;
81     use bytes;
82     length($scalar)
83 }
84
85 sub i3 {
86     AnyEvent::I3->new(@_)
87 }
88
89 =head2 $i3 = AnyEvent::I3->new([ $path ])
90
91 Creates a new C<AnyEvent::I3> object and returns it. C<path> is the path of
92 the UNIX socket to connect to.
93
94 =cut
95 sub new {
96     my ($class, $path) = @_;
97
98     $path ||= '/tmp/i3-ipc.sock';
99
100     bless { path => $path } => $class;
101 }
102
103 =head2 $i3->connect
104
105 Establishes the connection to i3. Returns an C<AnyEvent::CondVar> which will
106 be triggered with a boolean (true if the connection was established) as soon as
107 the connection has been established.
108
109     if ($i3->connect->recv) {
110         say "Connected to i3";
111     }
112
113 =cut
114 sub connect {
115     my ($self) = @_;
116     my $hdl;
117     my $cv = AnyEvent->condvar;
118
119     tcp_connect "unix/", $self->{path}, sub {
120         my ($fh) = @_;
121
122         return $cv->send(0) unless $fh;
123
124         $self->{ipchdl} = AnyEvent::Handle->new(
125             fh => $fh,
126             on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) }
127         );
128
129         $cv->send(1)
130     };
131
132     $cv
133 }
134
135 sub _data_available {
136     my ($self, $hdl) = @_;
137
138     $hdl->unshift_read(
139         chunk => length($magic) + 4 + 4,
140         sub {
141             my $header = $_[1];
142             # Unpack message length and read the payload
143             my ($len, $type) = unpack("LL", substr($header, length($magic)));
144             $hdl->unshift_read(
145                 chunk => $len,
146                 sub { $self->_handle_i3_message($type, $_[1]) }
147             );
148         }
149     );
150 }
151
152 sub _handle_i3_message {
153     my ($self, $type, $payload) = @_;
154
155     return unless defined($self->{callbacks}->{$type});
156
157     my $cb = $self->{callbacks}->{$type};
158     $cb->(decode_json $payload);
159 }
160
161 =head2 $i3->subscribe(\%callbacks)
162
163 Subscribes to the given event types. This function awaits a hashref with the
164 key being the name of the event and the value being a callback.
165
166     $i3->subscribe({
167         workspace => sub { say "Workspaces changed" }
168     });
169
170 =cut
171 sub subscribe {
172     my ($self, $callbacks) = @_;
173
174     my $payload = encode_json [ keys %{$callbacks} ];
175     my $message = $magic . pack("LL", _bytelength($payload), 2) . $payload;
176     $self->{ipchdl}->push_write($message);
177
178     # Register callbacks for each message type
179     for my $key (keys %{$callbacks}) {
180         my $type = $events{$key};
181         $self->{callbacks}->{$type} = $callbacks->{$key};
182     }
183 }
184
185 =head2 $i3->message($type, $content)
186
187 Sends a message of the specified C<type> to i3, possibly containing the data
188 structure C<payload>, if specified.
189
190     my $reply = $i3->message(TYPE_COMMAND, "reload")->recv;
191     if ($reply->{success}) {
192         say "Configuration successfully reloaded";
193     }
194
195 =cut
196 sub message {
197     my ($self, $type, $content) = @_;
198
199     die "No message type specified" unless defined($type);
200
201     my $payload = "";
202     if ($content) {
203         if (ref($content) eq "SCALAR") {
204             $payload = $content;
205         } else {
206             $payload = encode_json $content;
207         }
208     }
209     my $message = $magic . pack("LL", _bytelength($payload), $type) . $payload;
210     $self->{ipchdl}->push_write($message);
211
212     my $cv = AnyEvent->condvar;
213
214     # We don’t preserve the old callback as it makes no sense to
215     # have a callback on message reply types (only on events)
216     $self->{callbacks}->{$type} =
217         sub {
218             my ($reply) = @_;
219             $cv->send($reply);
220             undef $self->{callbacks}->{$type};
221         };
222
223     $cv
224 }
225
226 =head1 AUTHOR
227
228 Michael Stapelberg, C<< <michael at stapelberg.de> >>
229
230 =head1 BUGS
231
232 Please report any bugs or feature requests to C<bug-anyevent-i3 at rt.cpan.org>, or through
233 the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-I3>.  I will be notified, and then you'll
234 automatically be notified of progress on your bug as 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