diff options
Diffstat (limited to 'pandora')
-rw-r--r-- | pandora/interfaces/pandora_experimental/hid.proto | 26 | ||||
-rw-r--r-- | pandora/server/bumble_experimental/hid.py | 89 |
2 files changed, 108 insertions, 7 deletions
diff --git a/pandora/interfaces/pandora_experimental/hid.proto b/pandora/interfaces/pandora_experimental/hid.proto index 7b1e7d2c8a..709d1577ff 100644 --- a/pandora/interfaces/pandora_experimental/hid.proto +++ b/pandora/interfaces/pandora_experimental/hid.proto @@ -15,8 +15,11 @@ service HID { rpc VirtualCableUnplugHost(google.protobuf.Empty) returns (google.protobuf.Empty); // Send a SET_REPORT command, acting as a HID host, to a connected HID device rpc SendHostReport(SendHostReportRequest) returns (SendHostReportResponse); + // receive Protocol Mode Event + rpc OnSetProtocolMode(google.protobuf.Empty) returns (stream ProtocolModeEvent); + // receive Report Event + rpc OnSetReport(google.protobuf.Empty) returns (stream ReportEvent); } - // Enum values match those in BluetoothHidHost.java enum HidReportType { HID_REPORT_TYPE_UNSPECIFIED = 0; @@ -24,6 +27,17 @@ enum HidReportType { HID_REPORT_TYPE_OUTPUT = 2; HID_REPORT_TYPE_FEATURE = 3; } +// Enum values match those in BluetoothHidHost.java +enum ProtocolMode { + PROTOCOL_REPORT_MODE = 0; + PROTOCOL_BOOT_MODE = 1; + PROTOCOL_UNSUPPORTED_MODE = 255; +} +enum HidReportId { + HID_KEYBD_RPT_ID = 0; + HID_MOUSE_RPT_ID = 1; + HID_INVALID_RPT_ID = 3; +} message SendHostReportRequest { bytes address = 1; @@ -34,3 +48,13 @@ message SendHostReportRequest { message SendHostReportResponse { } + +message ProtocolModeEvent { + ProtocolMode protocol_mode = 1; +} + +message ReportEvent { + HidReportType report_type = 1; + HidReportId report_id = 2; + string report_data = 3; +} diff --git a/pandora/server/bumble_experimental/hid.py b/pandora/server/bumble_experimental/hid.py index 497f5edd4a..6da25ec6e1 100644 --- a/pandora/server/bumble_experimental/hid.py +++ b/pandora/server/bumble_experimental/hid.py @@ -11,6 +11,14 @@ from google.protobuf import empty_pb2 # pytype: disable=pyi-error from pandora_experimental.hid_grpc_aio import HIDServicer from bumble.pandora import utils +from pandora_experimental.hid_pb2 import ( + ProtocolModeEvent, + ReportEvent, + PROTOCOL_REPORT_MODE, + PROTOCOL_BOOT_MODE, + PROTOCOL_UNSUPPORTED_MODE, +) + from bumble.core import ( BT_BR_EDR_TRANSPORT, BT_L2CAP_PROTOCOL_ID, @@ -489,20 +497,30 @@ def sdp_records(): # ----------------------------------------------------------------------------- def hogp_device(device): - global input_report_characteristic # Create an 'input report' characteristic to send keyboard reports to the host - input_report_characteristic = Characteristic( + input_report_kb_characteristic = Characteristic( GATT_REPORT_CHARACTERISTIC, Characteristic.Properties.READ | Characteristic.Properties.WRITE | Characteristic.Properties.NOTIFY, Characteristic.READABLE | Characteristic.WRITEABLE, - bytes([0, 0, 0, 0, 0, 0, 0, 0]), + bytes([0, 0, 0, 0, 0, 0, 0, 0, 0]), [Descriptor( GATT_REPORT_REFERENCE_DESCRIPTOR, Descriptor.READABLE, bytes([0x01, HID_INPUT_REPORT]), )], ) - + # Create an 'input report' characteristic to send mouse reports to the host + input_report_mouse_characteristic = Characteristic( + GATT_REPORT_CHARACTERISTIC, + Characteristic.Properties.READ | Characteristic.Properties.WRITE | Characteristic.Properties.NOTIFY, + Characteristic.READABLE | Characteristic.WRITEABLE, + bytes([0, 0, 0, 0]), + [Descriptor( + GATT_REPORT_REFERENCE_DESCRIPTOR, + Descriptor.READABLE, + bytes([0x02, HID_INPUT_REPORT]), + )], + ) # Create an 'output report' characteristic to receive keyboard reports from the host output_report_characteristic = Characteristic( GATT_REPORT_CHARACTERISTIC, @@ -558,7 +576,8 @@ def hogp_device(device): Characteristic.READABLE, HID_KEYBOARD_REPORT_MAP, ), - input_report_characteristic, + input_report_kb_characteristic, + input_report_mouse_characteristic, output_report_characteristic, ], ), @@ -630,6 +649,12 @@ def on_set_report_cb(report_id: int, report_type: int, report_size: int, data: b logging.info("SET_REPORT report_id: " + str(report_id) + "report_type: " + str(report_type) + "report_size " + str(report_size) + "data:" + str(data)) + report = ReportEvent() + report.report_type = report_type + report.report_id = report_id + report.report_data = str(data.hex()) + hid_report_queue.put_nowait(report) + if report_type == Message.ReportType.FEATURE_REPORT: retValue.status = hid_device.GetSetReturn.ERR_INVALID_PARAMETER elif report_type == Message.ReportType.INPUT_REPORT: @@ -657,7 +682,15 @@ def on_get_protocol_cb(): def on_set_protocol_cb(protocol: int): retValue = hid_device.GetSetStatus() # We do not support SET_PROTOCOL. - logging.info(f"SET_PROTOCOL report_id: {protocol}") + logging.info(f"SET_PROTOCOL mode: {protocol}") + mode = ProtocolModeEvent() + if protocol == PROTOCOL_REPORT_MODE: + mode.protocol_mode = PROTOCOL_REPORT_MODE + elif protocol == PROTOCOL_BOOT_MODE: + mode.protocol_mode = PROTOCOL_BOOT_MODE + else: + mode.protocol_mode = PROTOCOL_UNSUPPORTED_MODE + hid_protoMode_queue.put_nowait(mode) retValue.status = hid_device.GetSetReturn.ERR_UNSUPPORTED_REQUEST return retValue @@ -667,6 +700,9 @@ def on_virtual_cable_unplug_cb(): asyncio.create_task(handle_virtual_cable_unplug()) +hid_protoMode_queue = None + + # This class implements the Hid Pandora interface. class HIDService(HIDServicer): @@ -676,6 +712,7 @@ class HIDService(HIDServicer): super().__init__() self.device = device self.device.sdp_service_records.update(sdp_records()) + self.event_queue: Optional[asyncio.Queue[ProtocolModeEvent]] = None hogp_device(self.device) logging.info(f'Hid device register: ') global hid_device @@ -742,3 +779,43 @@ class HIDService(HIDServicer): logging.exception(f'Device does not exist') raise e return empty_pb2.Empty() + + @utils.rpc + async def OnSetProtocolMode(self, request: empty_pb2.Empty, + context: grpc.ServicerContext) -> AsyncGenerator[ProtocolModeEvent, None]: + logging.info(f'OnSetProtocolMode') + + if self.event_queue is not None: + raise RuntimeError('already streaming OnSetProtocolMode events') + + self.event_queue = asyncio.Queue() + global hid_protoMode_queue + hid_protoMode_queue = self.event_queue + + try: + while event := await hid_protoMode_queue.get(): + yield event + + finally: + self.event_queue = None + hid_protoMode_queue = None + + @utils.rpc + async def OnSetReport(self, request: empty_pb2.Empty, + context: grpc.ServicerContext) -> AsyncGenerator[ReportEvent, None]: + logging.info(f'OnSetReport') + + if self.event_queue is not None: + raise RuntimeError('already streaming OnSetReport events') + + self.event_queue = asyncio.Queue() + global hid_report_queue + hid_report_queue = self.event_queue + + try: + while event := await hid_report_queue.get(): + yield event + + finally: + self.event_queue = None + hid_report_queue = None |