File Coverage

blib/lib/Lirc/Client.pm
Criterion Covered Total %
statement 153 167 91.6
branch 72 120 60.0
condition 16 25 64.0
subroutine 16 18 88.8
pod 7 10 70.0
total 264 340 77.6


line stmt bran cond sub pod time code
1             package Lirc::Client;
2              
3             # ABSTRACT: A client library for the Linux Infrared Remote Control
4              
5 7     7   178126 use strict;
  7         17  
  7         234  
6 7     7   37 use warnings;
  7         12  
  7         181  
7 7     7   6358 use Moo;
  7         155485  
  7         46  
8 7     7   13067 use Carp;
  7         14  
  7         551  
9 7     7   6297 use IO::Socket;
  7         214228  
  7         109  
10 7     7   10995 use File::Path::Expand;
  7         98824  
  7         26348  
11              
12             our $VERSION = '2.02';
13              
14             has prog => ( is => 'ro', required => 1 ); # the program name from lircrc file
15             has rcfile => ( is => 'ro', default => sub { "$ENV{HOME}/.lircrc" } );
16             has dev => ( is => 'ro', default => sub { '/dev/lircd' } ); # lircd device
17             has debug => ( is => 'ro', default => sub { 0 } ); # instance debug flag
18             has fake => ( is => 'ro', default => sub { 0 } ); # fake the lirc connection
19             has sock => ( is => 'rw' ); # the lircd socket
20             has mode => ( is => 'rw', default => sub { '' } );
21             has _in_block => ( is => 'rw', default => sub { 0 } );
22             has _commands => ( is => 'rw', default => sub { {} } );
23             has _startup_mode => ( is => 'rw' );
24             has _buf => ( is => 'rw', default => sub { '' } );
25              
26             sub BUILD {
27 6     6 0 78 my $self = shift;
28              
29 6 50       44 if ( $self->fake ) {
30 6         58 $self->sock( \*STDIN );
31             } else {
32 0 0       0 $self->sock(
33             IO::Socket->new(
34             Domain => &AF_UNIX,
35             Type => SOCK_STREAM,
36             Peer => $self->dev,
37             ) ) or croak "couldn't connect to $self->dev: $!";
38             }
39              
40 6         37 $self->_parse_lircrc( $self->rcfile );
41 6 100       43 $self->mode( $self->_startup_mode ) if defined $self->_startup_mode;
42 6         153 return 1;
43             }
44              
45             sub BUILDARGS {
46 6     6 0 29716 my ( $class, @args ) = @_;
47              
48 6         17 my $cfg = {};
49 6         29 my @arg_names = qw{prog rcfile dev debug fake}; # get any passed by order
50              
51 6 100 66     671 carp
52             "positional parameters for constructor is depreciated and will be removed from a future version"
53             if @args and ref $args[0] ne 'HASH';
54              
55 6   100     162 while ( @args and ref $args[0] ne 'HASH' ) {
56 6         7 my $arg_name = shift @arg_names;
57 6         6 my $arg_val = shift @args;
58 6 100       30 $cfg->{$arg_name} = $arg_val if defined $arg_val;
59             }
60              
61 6 100       30 if ( ref $args[0] eq 'HASH' ) {
62 5         14 $cfg = { %$cfg, %{ shift @args } }; # Merge the two hashes
  5         26  
63             }
64              
65 6 50       24 croak "new expects list of args or hash ref of named args" if @args;
66 6         143 return $cfg;
67             }
68              
69             sub clean_up {
70 6     6 1 12 my $self = shift;
71              
72 6 50       35 if ( defined $self->sock ) {
73 6 50       30 close $self->sock unless $self->fake;
74             }
75              
76 6         14 return;
77             }
78              
79             sub _parse_lircrc { ## no critic
80 9     9   26 my ( $self, $rcfilename ) = @_;
81              
82 9 50       369 open( my $rcfile, '<', $rcfilename )
83             or croak "couldn't open lircrc file ($rcfilename): $!";
84              
85 9         15 my $in_block = 0;
86 9         19 my $cur_mode = '';
87 9         19 my $ops = {};
88              
89 9         275 while (<$rcfile>) {
90 238         424 s/^\s*#.*$//g; # remove commented lines
91 238         254 chomp;
92 238 100       10944 print "> ($rcfilename) ($cur_mode) $_\n" if $self->debug;
93              
94             ## begin block
95 238 100       854 /^\s*begin\s*$/i && do {
96 36 50       68 $in_block && croak "Found begin inside a block in line: $_\n";
97 36         40 $in_block = 1;
98 36         127 next;
99             };
100              
101             ## end block
102 202 100       490 /^\s*end\s*$/i && do {
103 36 50       163 croak "found end outside of a block in line: $_\n" unless $in_block;
104              
105 36 100 100     113 if ( defined $ops->{flags} && $ops->{flags} =~ /\bstartup_mode\b/ )
106             {
107 1 50       3 croak "startup_mode flag given without a mode line"
108             unless defined $ops->{mode};
109 1         6 $self->_startup_mode( $ops->{mode} );
110 1         3 next;
111             }
112              
113 35 50       67 croak "end of block found without a prog code at line: $_\n"
114             unless defined $ops->{prog};
115 35   100     87 $ops->{remote} ||= '*';
116 35         92 my $key = join '-', $ops->{remote}, $ops->{button}, $cur_mode;
117 35         36 my $val = $ops;
118              
119 35         44 $in_block = 0;
120 35         50 $ops = {};
121              
122 35 50       108 next unless $val->{prog} eq $self->prog;
123              
124 35         102 $self->_commands->{$key} = $val;
125              
126 35         117 next;
127             };
128              
129             ## token = arg
130 166 100       753 /^\s*([\w-]+)\s*=\s*(.*?)\s*$/ && do {
131 138         347 my ( $tok, $act ) = ( $1, $2 );
132 138 50       538 croak "unknown token found in rc file: $_\n"
133             unless $tok =~ /^(prog|remote|button|repeat|config|mode|flags)$/i;
134 138         282 $ops->{$tok} = $act;
135              
136 138         409 next;
137             };
138              
139             ## begin mode
140 28 100       70 /^\s*begin\s*([\w-]+)\s*$/i && do {
141 4 50 33     30 croak "found embedded mode line: $_\n" if $1 && $cur_mode;
142 4 100       25 $self->_startup_mode($1) if $1 eq $self->prog;
143 4         7 $cur_mode = $1;
144 4         12 next;
145             };
146              
147             ## end mode
148 24 100       53 /^\s*end\s*([\w-]+)\s*$/i && do {
149 4 50       12 croak "end $1: found inside a begin/end block" if $in_block;
150 4 50       15 croak "end $1: found without associated begin mode"
151             unless $cur_mode eq $1;
152              
153 4         6 $cur_mode = '';
154 4         13 next;
155             };
156              
157             ## include file
158 20 100       43 /^include\s+(.*)\s*$/ && do {
159 3         9 my $file = $1;
160 3         54 $file =~ s/^["<]|[">]$//g;
161 3         7 $file = eval { expand_filename($file) };
  3         19  
162 3 50       37 croak "error parsing include statement: $_\n" if $@;
163 3 50       63 croak "could not find file ($file) in include: $_\n"
164             unless -r $file;
165 3         39 $self->_parse_lircrc($file);
166 3         9 next;
167             };
168              
169             ## blank lines
170 17 50       127 /^\s*$/ && next;
171              
172             ## unrecognized
173 0         0 croak sprintf "Couldn't parse lircrc file (%s) error in line: %s\n",
174             $self->rcfile, $_;
175             }
176 9         98 close $rcfile;
177              
178 9         41 return;
179             }
180              
181             sub recognized_commands {
182 2     2 1 1588 my $self = shift;
183              
184 2         19 return $self->_commands;
185             }
186              
187             sub _get_lines {
188 1     1   2 my $self = shift;
189              
190             # what is in the buffer now?
191 1 50       5 printf "buffer1=%s\n", $self->_buf if $self->debug;
192              
193             # read anything in the pipe
194 1         1 my $buf;
195 1         14 my $status = sysread( $self->sock, $buf, 512 );
196 1 50 0     4 ( carp "bad status from read" and return ) unless defined $status;
197              
198             # what is in the buffer after the read?
199 1         3 $self->{_buf} .= $buf;
200 1 50       4 print "buffer2=%s\n", $self->_buf if $self->debug;
201              
202             # separate the lines, leaving partial lines on _buf
203 1         2 my @lines;
204 1         16 push @lines, $1 while ( $self->{_buf} =~ s/^(.+)\n// ); ## no critic
205             # while() tests that s/// matched
206              
207 1         3 return @lines;
208             }
209              
210             sub nextcodes {
211 0     0 1 0 return shift->next_codes();
212             }
213              
214             sub next_codes {
215 1     1 1 989 my $self = shift;
216              
217 1         5 my @lines = $self->_get_lines;
218 1 0       5 print "==", join( ", ", map { defined $_ ? $_ : "undef" } @lines ), "\n"
  0 50       0  
219             if $self->debug;
220 1 50       4 return () unless scalar @lines;
221 1         1 my @commands = ();
222 1         3 for my $line (@lines) {
223 2         3 chomp $line;
224 2 50       7 print "Line: $line\n" if $self->debug;
225 2         6 my $command = $self->parse_line($line);
226 2 0       10 print "Command: ", ( defined $command ? $command : "undef" ), "\n"
    50          
227             if $self->debug;
228 2 50       7 push @commands, $command if defined $command;
229             }
230 1         5 return @commands;
231             }
232              
233             sub nextcode {
234 0     0 1 0 return shift->next_code();
235             }
236              
237             sub next_code {
238 9     9 1 15559 my $self = shift;
239              
240 9         26 my $fh = $self->sock;
241 9         43 while ( defined( my $line = <$fh> ) ) {
242 12         17 chomp $line;
243 12 50       56 print "Line: $line\n" if $self->debug;
244 12         27 my $command = $self->parse_line($line);
245 12 0       30 print "Command: ", ( defined $command ? $command : "undef" ), "\n"
    50          
246             if $self->debug;
247 12 100       242 return $command if defined $command;
248             }
249 0         0 return; # no command found and lirc exited?
250             }
251              
252             sub parse_line { ## parse a line read from lircd
253 14     14 1 18 my $self = shift;
254 14         24 $_ = shift;
255              
256 14 50       43 printf "> (%s) %s\n", $self->_in_block, $_ if $self->debug;
257              
258             # Take care of response blocks
259             ## Right Lirc::Client doesn't support LIST or VERSION, so we can ignore
260             ## Responses that come inside a block
261 14 50       39 if (/^\s*BEGIN\s*$/) {
262 0 0       0 croak "got BEGIN inside a block from lircd: $_" if $self->_in_block;
263 0         0 $self->_in_block(1);
264 0         0 return;
265             }
266 14 50       32 if (/^\s*END\s*$/) {
267 0 0       0 croak "got END outside a block from lircd: $_" if !$self->_in_block;
268 0         0 $self->_in_block(0);
269 0         0 return;
270             }
271 14 50       35 return if $self->_in_block;
272              
273             # Decipher IR Command
274             # http://www.lirc.org/html/technical.html#applications
275             #
276 14         50 my ( $hex, $repeat, $button, $remote ) = split /\s+/;
277 14 50 33     76 defined $button and length $button or do {
278 0         0 carp "Unable to decode.\n";
279 0         0 return;
280             };
281              
282 14         27 my $commands = $self->_commands;
283 14         28 my $cur_mode = $self->mode;
284 14   66     75 my $command =
285             $commands->{"$remote-$button-$cur_mode"}
286             || $commands->{"*-$button-$cur_mode"}
287             || $commands->{"$remote-*-$cur_mode"};
288 14 50       27 defined $command or return;
289              
290 14         25 my $rep_count =
291             $command->{repeat}; # default repeat count is 0 (ignore repeated keys)
292 14 50       57 return if $rep_count ? hex($repeat) % $rep_count : hex $repeat;
    50          
293              
294 14 100 66     59 if ( defined $command->{flags} && $command->{flags} =~ /\bmode\b/ ) {
295 2         7 $self->mode('');
296             }
297 14 100       27 if ( defined $command->{mode} ) { $self->mode( $command->{mode} ); }
  1         5  
298              
299 14 100       36 return unless defined $command->{config};
300 11 50       45 printf ">> %s accepted --> %s\n", $button, $command->{config}
301             if $self->debug;
302 11         32 return $command->{config};
303             }
304              
305             sub DEMOLISH {
306 6     6 0 11263 my $self = shift;
307 6 100       300 print __PACKAGE__, ": DEMOLISH\n" if $self->debug;
308              
309 6         28 $self->clean_up;
310 6         132 return;
311             }
312              
313             1;
314              
315             __END__