diff options
author | reillyg@chromium.org <reillyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-24 16:45:03 +0000 |
---|---|---|
committer | reillyg@chromium.org <reillyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-24 16:45:03 +0000 |
commit | 3998309c7b0814eefcc33b585e8c1ae0753d61da (patch) | |
tree | 204e5f47759d4a096434f7cee29cf53f530ad7e6 /tools/usb_gadget | |
parent | 1ab15fe15d73fa8fa933d1314da5922878ef58a2 (diff) | |
download | chromium_src-3998309c7b0814eefcc33b585e8c1ae0753d61da.zip chromium_src-3998309c7b0814eefcc33b585e8c1ae0753d61da.tar.gz chromium_src-3998309c7b0814eefcc33b585e8c1ae0753d61da.tar.bz2 |
[usb_gadget p04] Add a HID keyboard gadget.
An example HID gadget implementing the standard HID keyboard protocol.
BUG=396682
R=rockot@chromium.org,rpaquay@chromium.org,kalman@chromium.org
Review URL: https://codereview.chromium.org/418583003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285298 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/usb_gadget')
-rwxr-xr-x | tools/usb_gadget/hid_descriptors_test.py | 43 | ||||
-rw-r--r-- | tools/usb_gadget/keyboard_gadget.py | 161 | ||||
-rwxr-xr-x | tools/usb_gadget/keyboard_gadget_test.py | 63 | ||||
-rw-r--r-- | tools/usb_gadget/usb_constants.py | 5 |
4 files changed, 231 insertions, 41 deletions
diff --git a/tools/usb_gadget/hid_descriptors_test.py b/tools/usb_gadget/hid_descriptors_test.py index d2313ca..ce3f0d0 100755 --- a/tools/usb_gadget/hid_descriptors_test.py +++ b/tools/usb_gadget/hid_descriptors_test.py @@ -7,51 +7,12 @@ import unittest import hid_constants import hid_descriptors +import keyboard_gadget class HidTest(unittest.TestCase): def test_keyboard_example(self): - 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) - ) - ) - expected = ''.join(chr(x) for x in [ 0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, 0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, @@ -60,7 +21,7 @@ class HidTest(unittest.TestCase): 0x91, 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65, 0x81, 0x00, 0xC0 ]) - self.assertEquals(report_desc, expected) + self.assertEquals(keyboard_gadget.KeyboardFeature.REPORT_DESC, expected) def test_mouse_example(self): report_desc = hid_descriptors.ReportDescriptor( diff --git a/tools/usb_gadget/keyboard_gadget.py b/tools/usb_gadget/keyboard_gadget.py new file mode 100644 index 0000000..ce37b44 --- /dev/null +++ b/tools/usb_gadget/keyboard_gadget.py @@ -0,0 +1,161 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# 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=False, + 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) diff --git a/tools/usb_gadget/keyboard_gadget_test.py b/tools/usb_gadget/keyboard_gadget_test.py new file mode 100755 index 0000000..d3b085f --- /dev/null +++ b/tools/usb_gadget/keyboard_gadget_test.py @@ -0,0 +1,63 @@ +#!/usr/bin/python +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +import mock + +import hid_constants +import keyboard_gadget +import usb_constants + + +class KeyboardGadgetTest(unittest.TestCase): + + def test_key_press(self): + g = keyboard_gadget.KeyboardGadget() + chip = mock.Mock() + g.Connected(chip, usb_constants.Speed.FULL) + g.KeyDown(0x04) + self.assertEqual(g.ControlRead(0xA1, 1, 0x0100, 0, 8), + '\x00\x00\x04\x00\x00\x00\x00\x00') + g.KeyUp(0x04) + self.assertEqual(g.ControlRead(0xA1, 1, 0x0100, 0, 8), + '\x00\x00\x00\x00\x00\x00\x00\x00') + chip.SendPacket.assert_has_calls([ + mock.call(0x81, '\x00\x00\x04\x00\x00\x00\x00\x00'), + mock.call(0x81, '\x00\x00\x00\x00\x00\x00\x00\x00'), + ]) + + def test_key_press_with_modifier(self): + g = keyboard_gadget.KeyboardGadget() + chip = mock.Mock() + g.Connected(chip, usb_constants.Speed.FULL) + g.ModifierDown(hid_constants.ModifierKey.L_SHIFT) + g.KeyDown(0x04) + g.KeyDown(0x05) + g.KeyUp(0x04) + g.KeyUp(0x05) + g.ModifierUp(hid_constants.ModifierKey.L_SHIFT) + chip.SendPacket.assert_has_calls([ + mock.call(0x81, '\x02\x00\x00\x00\x00\x00\x00\x00'), + mock.call(0x81, '\x02\x00\x04\x00\x00\x00\x00\x00'), + mock.call(0x81, '\x02\x00\x04\x05\x00\x00\x00\x00'), + mock.call(0x81, '\x02\x00\x00\x05\x00\x00\x00\x00'), + mock.call(0x81, '\x02\x00\x00\x00\x00\x00\x00\x00'), + mock.call(0x81, '\x00\x00\x00\x00\x00\x00\x00\x00'), + ]) + + def test_set_leds(self): + g = keyboard_gadget.KeyboardGadget() + chip = mock.Mock() + g.Connected(chip, usb_constants.Speed.FULL) + self.assertEqual(g.ControlRead(0xA1, 1, 0x0200, 0, 8), '\x00') + self.assertTrue(g.ControlWrite(0x21, 9, 0x0200, 0, '\x01')) + self.assertEqual(g.ControlRead(0xA1, 1, 0x0200, 0, 8), '\x01') + g.ReceivePacket(0x01, '\x03') + self.assertFalse(chip.HaltEndpoint.called) + self.assertEqual(g.ControlRead(0xA1, 1, 0x0200, 0, 8), '\x03') + +if __name__ == '__main__': + unittest.main() diff --git a/tools/usb_gadget/usb_constants.py b/tools/usb_gadget/usb_constants.py index fee7fc1..106b37c 100644 --- a/tools/usb_gadget/usb_constants.py +++ b/tools/usb_gadget/usb_constants.py @@ -159,3 +159,8 @@ class Speed(object): class VendorID(object): GOOGLE = 0x18D1 + + +class ProductID(object): + # TODO(reillyg): Get officially assigned IDs for these devices. + GOOGLE_KEYBOARD_GADGET = 0x2001 |