15f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
25f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)# found in the LICENSE file.
45f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
55f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)"""Implementation of a USB HID mouse.
65f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
75f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)Two classes are provided by this module. The MouseFeature class implements
85f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)the core functionality of a HID mouse and can be included in any HID gadget.
95f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)The MouseGadget class implements an example mouse gadget.
105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)"""
115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import struct
135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import hid_constants
155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import hid_descriptors
165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import hid_gadget
175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import usb_constants
185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class MouseFeature(hid_gadget.HidFeature):
215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  """HID feature implementation for a mouse.
225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  REPORT_DESC provides an example HID report descriptor for a device including
245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  this functionality.
255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  """
265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  REPORT_DESC = hid_descriptors.ReportDescriptor(
285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      hid_descriptors.UsagePage(0x01),  # Generic Desktop
295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      hid_descriptors.Usage(0x02),  # Mouse
305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      hid_descriptors.Collection(
315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          hid_constants.CollectionType.APPLICATION,
325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          hid_descriptors.Usage(0x01),  # Pointer
335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          hid_descriptors.Collection(
345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_constants.CollectionType.PHYSICAL,
355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.UsagePage(0x09),  # Buttons
365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.UsageMinimum(1),
375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.UsageMaximum(3),
385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.LogicalMinimum(0, force_length=1),
395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.LogicalMaximum(1),
405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.ReportCount(3),
415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.ReportSize(1),
425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.Input(hid_descriptors.Data,
435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                    hid_descriptors.Variable,
445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                    hid_descriptors.Absolute),
455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.ReportCount(1),
465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.ReportSize(5),
475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.Input(hid_descriptors.Constant),
485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.UsagePage(0x01),  # Generic Desktop
495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.Usage(0x30),  # X
505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.Usage(0x31),  # Y
515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.LogicalMinimum(0x81),  # -127
525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.LogicalMaximum(127),
535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.ReportSize(8),
545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.ReportCount(2),
555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)              hid_descriptors.Input(hid_descriptors.Data,
565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                    hid_descriptors.Variable,
575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                    hid_descriptors.Relative)
585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          )
595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      )
605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  )
615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def __init__(self):
635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    super(MouseFeature, self).__init__()
645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._buttons = 0
655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def ButtonDown(self, button):
675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._buttons |= button
685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if self.IsConnected():
695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      self.SendReport(self.EncodeInputReport())
705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def ButtonUp(self, button):
725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._buttons &= ~button
735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if self.IsConnected():
745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      self.SendReport(self.EncodeInputReport())
755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def Move(self, x_displacement, y_displacement):
775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if self.IsConnected():
785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      self.SendReport(self.EncodeInputReport(x_displacement, y_displacement))
795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def EncodeInputReport(self, x_displacement=0, y_displacement=0):
815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return struct.pack('Bbb', self._buttons, x_displacement, y_displacement)
825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def GetInputReport(self):
845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """Construct an input report.
855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    See Device Class Definition for Human Interface Devices (HID) Version 1.11
875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Appendix B.2.
885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Returns:
905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      A packed input report.
915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return self.EncodeInputReport()
935f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class MouseGadget(hid_gadget.HidGadget):
965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  """USB gadget implementation of a HID mouse."""
975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def __init__(self):
995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._feature = MouseFeature()
1005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    super(MouseGadget, self).__init__(
1015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        report_desc=MouseFeature.REPORT_DESC,
1025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        features={0: self._feature},
1035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        packet_size=8,
1045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        interval_ms=1,
1055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        out_endpoint=False,
1065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        vendor_id=usb_constants.VendorID.GOOGLE,
1075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        product_id=usb_constants.ProductID.GOOGLE_MOUSE_GADGET,
1085f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        device_version=0x0100)
1095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self.AddStringDescriptor(1, 'Google Inc.')
1105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self.AddStringDescriptor(2, 'Mouse Gadget')
1115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def ButtonDown(self, button):
1135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._feature.ButtonDown(button)
1145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def ButtonUp(self, button):
1165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._feature.ButtonUp(button)
1175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def Move(self, x_displacement, y_displacement):
1195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._feature.Move(x_displacement, y_displacement)
1205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)def RegisterHandlers():
1235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  """Registers web request handlers with the application server."""
1245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  from tornado import web
1265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  class WebConfigureHandler(web.RequestHandler):
1285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def post(self):
1305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      gadget = MouseGadget()
1315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      server.SwitchGadget(gadget)
1325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  class WebClickHandler(web.RequestHandler):
1345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def post(self):
1365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      BUTTONS = {
1375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          '1': hid_constants.Mouse.BUTTON_1,
1385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          '2': hid_constants.Mouse.BUTTON_2,
1395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          '3': hid_constants.Mouse.BUTTON_3,
1405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      }
1415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      button = BUTTONS[self.get_argument('button')]
1435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      server.gadget.ButtonDown(button)
1445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      server.gadget.ButtonUp(button)
1455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  class WebMoveHandler(web.RequestHandler):
1475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def post(self):
1495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      x = int(self.get_argument('x'))
1505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      y = int(self.get_argument('y'))
1515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      server.gadget.Move(x, y)
1525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  import server
1545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  server.app.add_handlers('.*$', [
1555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      (r'/mouse/configure', WebConfigureHandler),
1565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      (r'/mouse/move', WebMoveHandler),
1575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      (r'/mouse/click', WebClickHandler),
1585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  ])
159