File Coverage

blib/lib/USB/HID/Descriptor/Class.pm
Criterion Covered Total %
statement 15 104 14.4
branch 0 32 0.0
condition 0 6 0.0
subroutine 5 16 31.2
pod 10 10 100.0
total 30 168 17.8


line stmt bran cond sub pod time code
1             package USB::HID::Descriptor::Class;
2              
3 1     1   7 use strict;
  1         2  
  1         37  
4 1     1   6 use warnings;
  1         19  
  1         26  
5 1     1   594 use USB::HID::Report;
  1         4  
  1         38  
6 1     1   888 use USB::HID::Descriptor::Report;
  1         4  
  1         75  
7              
8             our $VERSION = '2'; # Bump this when the interface changes
9              
10 1     1   11 use overload '@{}' => \&bytes;
  1         2  
  1         12  
11              
12             =head1 NAME
13              
14             USB::HID::Descriptor::Class - USB HID Class Descriptor
15              
16             =head1 SYNOPSIS
17              
18             An object representation of a USB HID class descriptor.
19              
20             use USB::HID::Descriptor::Class;
21              
22             my $class = USB::HID::Descriptor::Class->new;
23             $class->country(0);
24             $class->version('1.2.3');
25             $class->reports( [ USB::HID::Descriptor::Report->new() ] );
26             ...
27              
28             =head1 DESCRIPTION
29              
30             L represents a USB class descriptor for a
31             HID class device. Instances of L are automatically
32             created by L as needed, so there's generally no
33             reason to use this class directly.
34              
35             =head1 CONSTRUCTOR
36              
37             =over
38              
39             =item $class = USB::HID::Descriptor::Class->new(...);
40              
41             Constructs and returns a new L object using the
42             passed options. Each option key is the name of an accessor method.
43              
44             =back
45              
46             =cut
47              
48             sub new
49             {
50 0     0 1   my ($this, %options) = @_;
51 0   0       my $class = ref($this) || $this;
52              
53             # Set defaults
54 0           my $self = {
55             'bcdHID' => 0x01B0, # HID 1.11.0
56             'bCountryCode' => 0, # Non-localized
57             'page' => 0, # Undefined
58             'version' => [1,11,0], # HID 1.11.0
59             'reports' => [],
60             'usage' => 0, # Undefined
61             };
62 0           bless $self, $class;
63              
64 0           while( my ($key, $value) = each %options )
65             {
66 0           $self->$key($value);
67             }
68              
69 0           return $self;
70             }
71              
72             =head1 ARRAYIFICATION
73              
74             =over
75              
76             =item $class->bytes (or @{$class} )
77              
78             Returns an array of bytes containing all of the fields in the class descriptor.
79              
80             =back
81              
82             =cut
83              
84             sub bytes
85             {
86 0     0 1   my $s = shift;
87              
88 0           my @bytes;
89              
90 0           push @bytes, 9; # HID Class descriptors are 9 bytes long
91 0           push @bytes, 0x21; # HID class
92 0           push @bytes, $s->bcdHID & 0xFF; # bcdHID low
93 0           push @bytes, ($s->bcdHID >> 8) & 0xFF; # bcdHID high
94 0           push @bytes, $s->country; # bCountryCode
95 0           push @bytes, 1; # bNumDescriptors
96 0           push @bytes, 0x22; # bDescriptorType (report)
97              
98 0           my $length = scalar(@{$s->report_bytes});
  0            
99 0           push @bytes, $length & 0xFF; # wDescriptorLength low
100 0           push @bytes, ($length >> 8) & 0xFF; # wDescriptorLength high
101              
102 0 0         warn "Class descriptor length is wrong" unless $bytes[0] == scalar @bytes;
103              
104 0           return \@bytes;
105             }
106              
107             =head1 ATTRIBUTES
108              
109             =over
110              
111             =item $class->bcdHID
112              
113             Direct access to the B value. Don't use this unless you know what you're
114             doing.
115              
116             =item $class->country
117              
118             Get/Set the country code for localized hardware (bCountryCode). Defaults to 0.
119              
120             =item $class->report_bytes
121              
122             Returns an array of bytes containing the report descriptor.
123              
124             =item $class->report
125              
126             A convenience method that wraps a single hash reference in an array and passes
127             it to C.
128              
129             =item $class->reports
130              
131             Get/Set the array of L objects.
132              
133             =item $class->version
134              
135             Get/Set the HID specification release number (bcdHID). Defaults to '1.1.0'.
136              
137             =back
138              
139             =cut
140              
141             sub bcdHID
142             {
143 0     0 1   my $s = shift;
144 0 0         $s->{'bcdHID'} = int(shift) & 0xFFFF if scalar @_;
145 0           $s->{'bcdHID'};
146             }
147              
148             sub _sanitize_bcd_array
149             {
150 0     0     my @v = @_;
151 0           @v = map(int, @v); # Force integers
152 0 0         @v = $v[0..2] if 3 < scalar @v; # Limit the array to three elements
153 0           push @v, 0 while scalar(@v) < 3; # Append any missing trailing zeros
154              
155             # Mask out overly large numbers
156 0           $v[0] = $v[0] & 0xFF;
157 0           @v[1..2] = map { $_ & 0x0F } @v[1..2];
  0            
158              
159 0           return @v;
160             }
161              
162             sub country
163             {
164 0     0 1   my $s = shift;
165 0 0         $s->{'bCountryCode'} = int(shift) & 0xFF if scalar @_;
166 0           $s->{'bCountryCode'};
167             }
168              
169             sub report_bytes
170             {
171 0     0 1   my $s = shift;
172 0           my @bytes;
173             my %state;
174              
175             # Every Report Descriptor must begin with a UsagePage, a Usage and an
176             # Application Collection, in that order. The collection contains the
177             # remainder of the descriptor.
178 0           push @bytes, USB::HID::Descriptor::Report::UsagePage($s->page);
179 0           push @bytes, USB::HID::Descriptor::Report::Usage($s->usage);
180 0           push @bytes, USB::HID::Descriptor::Report::Collection('application');
181              
182             # Update the state
183 0           $state{'local'}{'usage'} = $s->usage;
184 0           $state{'global'}{'usage_page'} = $s->page;
185              
186             # Organize the reports by ID and type
187 0           my %reports;
188 0           for( @{$s->reports} )
  0            
189             {
190 0           my $reportID = $_->reportID;
191 0           my $type = $_->type;
192 0           $reports{$reportID}{$type} = $_;
193             }
194              
195 0           for my $reportID (sort keys %reports)
196             {
197 0           delete $state{'global'}{'report_id'};
198 0           for my $type (keys %{$reports{$reportID}} )
  0            
199             {
200 0           push @bytes, ($reports{$reportID}{$type})->bytes(\%state);
201             }
202             }
203              
204 0           push @bytes, USB::HID::Descriptor::Report::Collection('end');
205              
206 0           \@bytes;
207             }
208              
209             sub report
210             {
211 0     0 1   my $s = shift;
212 0 0 0       $s->reports([$_[0]]) if( scalar(@_) and (ref($_[0]) eq 'HASH') );
213 0           $s->{'reports'}[0];
214             }
215              
216             sub reports
217             {
218 0     0 1   my $s = shift;
219 0 0         if( scalar(@_) )
220             {
221 0 0         if( ref($_[0]) eq 'ARRAY' )
    0          
222             {
223             # Convert hash reference arguments into Report objects
224             my @reports = map
225             {
226 0 0         if( ref($_) eq 'HASH' ) # Hash reference?
  0 0          
    0          
227             {
228 0           USB::HID::Report->new(%{$_});
  0            
229             }
230             elsif( ref($_) eq 'ARRAY' ) # Array reference?
231             {
232             # Scan the array for field specifiers and add them to a hash
233             }
234             elsif( ref($_) ) # Reference to something else?
235             {
236 0           $_; # Use it
237             }
238 0           } @{$_[0]};
239 0           $s->{'reports'} = \@reports;
240             }
241             elsif( ref($_[0]) eq 'HASH' )
242             {
243             # If a hash reference was passed, let report() handle it
244 0           $s->report($_[0]);
245             }
246             }
247 0           $s->{'reports'};
248             }
249              
250             # Pass a dotted string or an array
251             # Returns a string in scalar context and an array in list context
252             sub version
253             {
254 0     0 1   my $s = shift;
255 0 0         if( scalar @_ )
256             {
257 0           my @v;
258             # Parse string arguments, otherwise hope that the argument is an array
259 0 0         if( 1 == scalar @_ )
260             {
261 0           @v = split /\./, shift;
262             }
263             else
264             {
265 0           @v = @_;
266             }
267 0           @v = _sanitize_bcd_array(@v);
268              
269 0           $s->{'bcdHID'} = ($v[0] << 8) | ($v[1] << 4) | $v[2];
270 0           $s->{'version'} = \@v;
271             }
272 0 0         wantarray ? @{$s->{'version'}} : join('.',@{$s->{'version'}});
  0            
  0            
273             }
274              
275             =head1 REPORT DESCRIPTOR ATTRIBUTES
276              
277             =over
278              
279             =item $class->page
280              
281             Get/Set the B of the interface's report descriptor. Accepts integer
282             values, or any of the B string constants defined in
283             HID/Descriptor/Report.pm.
284              
285             =item $class->usage
286              
287             Get/Set the B of the interface's report descriptor.
288              
289             =back
290              
291             =cut
292              
293             sub page
294             {
295 0     0 1   my $s = shift;
296 0 0         $s->{'page'} = shift if scalar @_;
297 0           $s->{'page'};
298             }
299              
300             sub usage
301             {
302 0     0 1   my $s = shift;
303 0 0         $s->{'usage'} = int(shift) & 0xFF if scalar @_;
304 0           $s->{'usage'};
305             }
306              
307             1;
308              
309             =head1 AUTHOR
310              
311             Brandon Fosdick, C<< >>
312              
313             =head1 BUGS
314              
315             Please report any bugs or feature requests to C, or through
316             the web interface at L. I will be notified, and then you'll
317             automatically be notified of progress on your bug as I make changes.
318              
319             =head1 SUPPORT
320              
321             You can find documentation for this module with the perldoc command.
322              
323             perldoc USB::HID::Descriptor::Class
324              
325              
326             You can also look for information at:
327              
328             =over 4
329              
330             =item * RT: CPAN's request tracker (report bugs here)
331              
332             L
333              
334             =item * AnnoCPAN: Annotated CPAN documentation
335              
336             L
337              
338             =item * CPAN Ratings
339              
340             L
341              
342             =item * Search CPAN
343              
344             L
345              
346             =back
347              
348              
349             =head1 ACKNOWLEDGEMENTS
350              
351              
352             =head1 LICENSE AND COPYRIGHT
353              
354             Copyright 2011 Brandon Fosdick.
355              
356             This program is released under the terms of the BSD License.
357              
358             =cut