| # Copyright 2014 The Chromium Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Implementation of a USB HID keyboard. | |
| Two classes are provided by this module. The KeyboardFeature class implements | |
| the core functionality of a HID keyboard and can be included in any HID gadget. | |
| The KeyboardGadget class implements an example keyboard gadget. | |
| """ | |
| import struct | |
| import hid_constants | |
| import hid_descriptors | |
| import hid_gadget | |
| import usb_constants | |
| class KeyboardFeature(hid_gadget.HidFeature): | |
| """HID feature implementation for a keyboard. | |
| REPORT_DESC provides an example HID report descriptor for a device including | |
| this functionality. | |
| """ | |
| REPORT_DESC = hid_descriptors.ReportDescriptor( | |
| hid_descriptors.UsagePage(0x01), # Generic Desktop | |
| hid_descriptors.Usage(0x06), # Keyboard | |
| hid_descriptors.Collection( | |
| hid_constants.CollectionType.APPLICATION, | |
| hid_descriptors.UsagePage(0x07), # Key Codes | |
| hid_descriptors.UsageMinimum(224), | |
| hid_descriptors.UsageMaximum(231), | |
| hid_descriptors.LogicalMinimum(0, force_length=1), | |
| hid_descriptors.LogicalMaximum(1), | |
| hid_descriptors.ReportSize(1), | |
| hid_descriptors.ReportCount(8), | |
| hid_descriptors.Input(hid_descriptors.Data, | |
| hid_descriptors.Variable, | |
| hid_descriptors.Absolute), | |
| hid_descriptors.ReportCount(1), | |
| hid_descriptors.ReportSize(8), | |
| hid_descriptors.Input(hid_descriptors.Constant), | |
| hid_descriptors.ReportCount(5), | |
| hid_descriptors.ReportSize(1), | |
| hid_descriptors.UsagePage(0x08), # LEDs | |
| hid_descriptors.UsageMinimum(1), | |
| hid_descriptors.UsageMaximum(5), | |
| hid_descriptors.Output(hid_descriptors.Data, | |
| hid_descriptors.Variable, | |
| hid_descriptors.Absolute), | |
| hid_descriptors.ReportCount(1), | |
| hid_descriptors.ReportSize(3), | |
| hid_descriptors.Output(hid_descriptors.Constant), | |
| hid_descriptors.ReportCount(6), | |
| hid_descriptors.ReportSize(8), | |
| hid_descriptors.LogicalMinimum(0, force_length=1), | |
| hid_descriptors.LogicalMaximum(101), | |
| hid_descriptors.UsagePage(0x07), # Key Codes | |
| hid_descriptors.UsageMinimum(0, force_length=1), | |
| hid_descriptors.UsageMaximum(101), | |
| hid_descriptors.Input(hid_descriptors.Data, hid_descriptors.Array) | |
| ) | |
| ) | |
| def __init__(self): | |
| super(KeyboardFeature, self).__init__() | |
| self._modifiers = 0 | |
| self._keys = [0, 0, 0, 0, 0, 0] | |
| self._leds = 0 | |
| def ModifierDown(self, modifier): | |
| self._modifiers |= modifier | |
| if self.IsConnected(): | |
| self.SendReport(self.GetInputReport()) | |
| def ModifierUp(self, modifier): | |
| self._modifiers &= ~modifier | |
| if self.IsConnected(): | |
| self.SendReport(self.GetInputReport()) | |
| def KeyDown(self, keycode): | |
| free = self._keys.index(0) | |
| self._keys[free] = keycode | |
| if self.IsConnected(): | |
| self.SendReport(self.GetInputReport()) | |
| def KeyUp(self, keycode): | |
| free = self._keys.index(keycode) | |
| self._keys[free] = 0 | |
| if self.IsConnected(): | |
| self.SendReport(self.GetInputReport()) | |
| def GetInputReport(self): | |
| """Construct an input report. | |
| See Device Class Definition for Human Interface Devices (HID) Version 1.11 | |
| Appendix B.1. | |
| Returns: | |
| A packed input report. | |
| """ | |
| return struct.pack('BBBBBBBB', self._modifiers, 0, *self._keys) | |
| def GetOutputReport(self): | |
| """Construct an output report. | |
| See Device Class Definition for Human Interface Devices (HID) Version 1.11 | |
| Appendix B.1. | |
| Returns: | |
| A packed input report. | |
| """ | |
| return struct.pack('B', self._leds) | |
| def SetOutputReport(self, data): | |
| """Handle an output report. | |
| See Device Class Definition for Human Interface Devices (HID) Version 1.11 | |
| Appendix B.1. | |
| Args: | |
| data: Report data. | |
| Returns: | |
| True on success, None to stall the pipe. | |
| """ | |
| if len(data) >= 1: | |
| self._leds, = struct.unpack('B', data) | |
| return True | |
| class KeyboardGadget(hid_gadget.HidGadget): | |
| """USB gadget implementation of a HID keyboard.""" | |
| def __init__(self, vendor_id=0x18D1, product_id=0xFF02): | |
| self._feature = KeyboardFeature() | |
| super(KeyboardGadget, self).__init__( | |
| report_desc=KeyboardFeature.REPORT_DESC, | |
| features={0: self._feature}, | |
| packet_size=8, | |
| interval_ms=1, | |
| out_endpoint=True, | |
| vendor_id=usb_constants.VendorID.GOOGLE, | |
| product_id=usb_constants.ProductID.GOOGLE_KEYBOARD_GADGET, | |
| device_version=0x0100) | |
| self.AddStringDescriptor(1, 'Google Inc.') | |
| self.AddStringDescriptor(2, 'Keyboard Gadget') | |
| def ModifierDown(self, modifier): | |
| self._feature.ModifierDown(modifier) | |
| def ModifierUp(self, modifier): | |
| self._feature.ModifierUp(modifier) | |
| def KeyDown(self, keycode): | |
| self._feature.KeyDown(keycode) | |
| def KeyUp(self, keycode): | |
| self._feature.KeyUp(keycode) | |
| def RegisterHandlers(): | |
| """Registers web request handlers with the application server.""" | |
| from tornado import web | |
| class WebConfigureHandler(web.RequestHandler): | |
| def post(self): | |
| server.SwitchGadget(KeyboardGadget()) | |
| class WebTypeHandler(web.RequestHandler): | |
| def post(self): | |
| string = self.get_argument('string') | |
| for char in string: | |
| if char in hid_constants.KEY_CODES: | |
| code = hid_constants.KEY_CODES[char] | |
| server.gadget.KeyDown(code) | |
| server.gadget.KeyUp(code) | |
| elif char in hid_constants.SHIFT_KEY_CODES: | |
| code = hid_constants.SHIFT_KEY_CODES[char] | |
| server.gadget.ModifierDown(hid_constants.ModifierKey.L_SHIFT) | |
| server.gadget.KeyDown(code) | |
| server.gadget.KeyUp(code) | |
| server.gadget.ModifierUp(hid_constants.ModifierKey.L_SHIFT) | |
| class WebPressHandler(web.RequestHandler): | |
| def post(self): | |
| code = hid_constants.KEY_CODES[self.get_argument('key')] | |
| server.gadget.KeyDown(code) | |
| server.gadget.KeyUp(code) | |
| import server | |
| server.app.add_handlers('.*$', [ | |
| (r'/keyboard/configure', WebConfigureHandler), | |
| (r'/keyboard/type', WebTypeHandler), | |
| (r'/keyboard/press', WebPressHandler), | |
| ]) |