1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Implementation of a USB HID keyboard.
6
7Two classes are provided by this module. The KeyboardFeature class implements
8the core functionality of a HID keyboard and can be included in any HID gadget.
9The KeyboardGadget class implements an example keyboard gadget.
10"""
11
12import struct
13
14import hid_constants
15import hid_descriptors
16import hid_gadget
17import usb_constants
18
19
20class KeyboardFeature(hid_gadget.HidFeature):
21  """HID feature implementation for a keyboard.
22
23  REPORT_DESC provides an example HID report descriptor for a device including
24  this functionality.
25  """
26
27  REPORT_DESC = hid_descriptors.ReportDescriptor(
28      hid_descriptors.UsagePage(0x01),  # Generic Desktop
29      hid_descriptors.Usage(0x06),  # Keyboard
30      hid_descriptors.Collection(
31          hid_constants.CollectionType.APPLICATION,
32          hid_descriptors.UsagePage(0x07),  # Key Codes
33          hid_descriptors.UsageMinimum(224),
34          hid_descriptors.UsageMaximum(231),
35          hid_descriptors.LogicalMinimum(0, force_length=1),
36          hid_descriptors.LogicalMaximum(1),
37          hid_descriptors.ReportSize(1),
38          hid_descriptors.ReportCount(8),
39          hid_descriptors.Input(hid_descriptors.Data,
40                                hid_descriptors.Variable,
41                                hid_descriptors.Absolute),
42          hid_descriptors.ReportCount(1),
43          hid_descriptors.ReportSize(8),
44          hid_descriptors.Input(hid_descriptors.Constant),
45          hid_descriptors.ReportCount(5),
46          hid_descriptors.ReportSize(1),
47          hid_descriptors.UsagePage(0x08),  # LEDs
48          hid_descriptors.UsageMinimum(1),
49          hid_descriptors.UsageMaximum(5),
50          hid_descriptors.Output(hid_descriptors.Data,
51                                 hid_descriptors.Variable,
52                                 hid_descriptors.Absolute),
53          hid_descriptors.ReportCount(1),
54          hid_descriptors.ReportSize(3),
55          hid_descriptors.Output(hid_descriptors.Constant),
56          hid_descriptors.ReportCount(6),
57          hid_descriptors.ReportSize(8),
58          hid_descriptors.LogicalMinimum(0, force_length=1),
59          hid_descriptors.LogicalMaximum(101),
60          hid_descriptors.UsagePage(0x07),  # Key Codes
61          hid_descriptors.UsageMinimum(0, force_length=1),
62          hid_descriptors.UsageMaximum(101),
63          hid_descriptors.Input(hid_descriptors.Data, hid_descriptors.Array)
64      )
65  )
66
67  def __init__(self):
68    super(KeyboardFeature, self).__init__()
69    self._modifiers = 0
70    self._keys = [0, 0, 0, 0, 0, 0]
71    self._leds = 0
72
73  def ModifierDown(self, modifier):
74    self._modifiers |= modifier
75    if self.IsConnected():
76      self.SendReport(self.GetInputReport())
77
78  def ModifierUp(self, modifier):
79    self._modifiers &= ~modifier
80    if self.IsConnected():
81      self.SendReport(self.GetInputReport())
82
83  def KeyDown(self, keycode):
84    free = self._keys.index(0)
85    self._keys[free] = keycode
86    if self.IsConnected():
87      self.SendReport(self.GetInputReport())
88
89  def KeyUp(self, keycode):
90    free = self._keys.index(keycode)
91    self._keys[free] = 0
92    if self.IsConnected():
93      self.SendReport(self.GetInputReport())
94
95  def GetInputReport(self):
96    """Construct an input report.
97
98    See Device Class Definition for Human Interface Devices (HID) Version 1.11
99    Appendix B.1.
100
101    Returns:
102      A packed input report.
103    """
104    return struct.pack('BBBBBBBB', self._modifiers, 0, *self._keys)
105
106  def GetOutputReport(self):
107    """Construct an output report.
108
109    See Device Class Definition for Human Interface Devices (HID) Version 1.11
110    Appendix B.1.
111
112    Returns:
113      A packed input report.
114    """
115    return struct.pack('B', self._leds)
116
117  def SetOutputReport(self, data):
118    """Handle an output report.
119
120    See Device Class Definition for Human Interface Devices (HID) Version 1.11
121    Appendix B.1.
122
123    Args:
124      data: Report data.
125
126    Returns:
127      True on success, None to stall the pipe.
128    """
129    if len(data) >= 1:
130      self._leds, = struct.unpack('B', data)
131    return True
132
133
134class KeyboardGadget(hid_gadget.HidGadget):
135  """USB gadget implementation of a HID keyboard."""
136
137  def __init__(self, vendor_id=0x18D1, product_id=0xFF02):
138    self._feature = KeyboardFeature()
139    super(KeyboardGadget, self).__init__(
140        report_desc=KeyboardFeature.REPORT_DESC,
141        features={0: self._feature},
142        packet_size=8,
143        interval_ms=1,
144        out_endpoint=False,
145        vendor_id=usb_constants.VendorID.GOOGLE,
146        product_id=usb_constants.ProductID.GOOGLE_KEYBOARD_GADGET,
147        device_version=0x0100)
148    self.AddStringDescriptor(1, 'Google Inc.')
149    self.AddStringDescriptor(2, 'Keyboard Gadget')
150
151  def ModifierDown(self, modifier):
152    self._feature.ModifierDown(modifier)
153
154  def ModifierUp(self, modifier):
155    self._feature.ModifierUp(modifier)
156
157  def KeyDown(self, keycode):
158    self._feature.KeyDown(keycode)
159
160  def KeyUp(self, keycode):
161    self._feature.KeyUp(keycode)
162
163
164def RegisterHandlers():
165  """Registers web request handlers with the application server."""
166
167  from tornado import web
168
169  class WebConfigureHandler(web.RequestHandler):
170
171    def post(self):
172      server.SwitchGadget(KeyboardGadget())
173
174  class WebTypeHandler(web.RequestHandler):
175
176    def post(self):
177      string = self.get_argument('string')
178      for char in string:
179        if char in hid_constants.KEY_CODES:
180          code = hid_constants.KEY_CODES[char]
181          server.gadget.KeyDown(code)
182          server.gadget.KeyUp(code)
183        elif char in hid_constants.SHIFT_KEY_CODES:
184          code = hid_constants.SHIFT_KEY_CODES[char]
185          server.gadget.ModifierDown(hid_constants.ModifierKey.L_SHIFT)
186          server.gadget.KeyDown(code)
187          server.gadget.KeyUp(code)
188          server.gadget.ModifierUp(hid_constants.ModifierKey.L_SHIFT)
189
190  class WebPressHandler(web.RequestHandler):
191
192    def post(self):
193      code = hid_constants.KEY_CODES[self.get_argument('key')]
194      server.gadget.KeyDown(code)
195      server.gadget.KeyUp(code)
196
197  import server
198  server.app.add_handlers('.*$', [
199      (r'/keyboard/configure', WebConfigureHandler),
200      (r'/keyboard/type', WebTypeHandler),
201      (r'/keyboard/press', WebPressHandler),
202  ])
203