From d295e123106aa615cec93c5dabe3a9054c88c5a2 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 13 Feb 2018 20:01:56 +0100 Subject: [PATCH] wacom: create a uhid device on live mode uhid code taken from https://github.com/bentiss/hid-tools and stripped out from the not required parts here --- tuhi/uhid.py | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++ tuhi/wacom.py | 59 +++++++++++++- 2 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 tuhi/uhid.py diff --git a/tuhi/uhid.py b/tuhi/uhid.py new file mode 100644 index 0000000..f342039 --- /dev/null +++ b/tuhi/uhid.py @@ -0,0 +1,214 @@ +#!/bin/env python3 +# -*- coding: utf-8 -*- +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +from gi.repository import GObject +import os +import struct +import uuid + + +class UHIDUncompleteException(Exception): + pass + + +class UHIDDevice(GObject.Object): + __UHID_LEGACY_CREATE = 0 + UHID_DESTROY = 1 + UHID_START = 2 + UHID_STOP = 3 + UHID_OPEN = 4 + UHID_CLOSE = 5 + UHID_OUTPUT = 6 + __UHID_LEGACY_OUTPUT_EV = 7 + __UHID_LEGACY_INPUT = 8 + UHID_GET_REPORT = 9 + UHID_GET_REPORT_REPLY = 10 + UHID_CREATE2 = 11 + UHID_INPUT2 = 12 + UHID_SET_REPORT = 13 + UHID_SET_REPORT_REPLY = 14 + + UHID_FEATURE_REPORT = 0 + UHID_OUTPUT_REPORT = 1 + UHID_INPUT_REPORT = 2 + + def __init__(self, fd=None): + GObject.Object.__init__(self) + self._name = None + self._phys = '' + self._rdesc = None + self.parsed_rdesc = None + self._info = None + if fd is None: + self._fd = os.open('/dev/uhid', os.O_RDWR) + else: + self._fd = fd + self.uniq = f'uhid_{str(uuid.uuid4())}' + + def __enter__(self): + return self + + def __exit__(self, *exc_details): + os.close(self._fd) + + @GObject.Property + def fd(self): + return self._fd + + @GObject.Property + def rdesc(self): + return self._rdesc + + @rdesc.setter + def rdesc(self, rdesc): + self._rdesc = rdesc + + @GObject.Property + def phys(self): + return self._phys + + @phys.setter + def phys(self, phys): + self._phys = phys + + @GObject.Property + def name(self): + return self._name + + @name.setter + def name(self, name): + self._name = name + + @GObject.Property + def info(self): + return self._info + + @info.setter + def info(self, info): + self._info = info + + @GObject.Property + def bus(self): + return self._info[0] + + @GObject.Property + def vid(self): + return self._info[1] + + @GObject.Property + def pid(self): + return self._info[2] + + def call_set_report(self, req, err): + buf = struct.pack('< L L H', + UHIDDevice.UHID_SET_REPORT_REPLY, + req, + err) + os.write(self._fd, buf) + + def call_get_report(self, req, data, err): + data = bytes(data) + buf = struct.pack('< L L H H 4096s', + UHIDDevice.UHID_GET_REPORT_REPLY, + req, + err, + len(data), + data) + os.write(self._fd, buf) + + def call_input_event(self, data): + data = bytes(data) + buf = struct.pack('< L H 4096s', + UHIDDevice.UHID_INPUT2, + len(data), + data) + os.write(self._fd, buf) + + def create_kernel_device(self): + if (self._name is None or + self._rdesc is None or + self._info is None): + raise UHIDUncompleteException("missing uhid initialization") + + buf = struct.pack('< L 128s 64s 64s H H L L L L 4096s', + UHIDDevice.UHID_CREATE2, + bytes(self._name, 'utf-8'), # name + bytes(self._phys, 'utf-8'), # phys + bytes(self.uniq, 'utf-8'), # uniq + len(self._rdesc), # rd_size + self.bus, # bus + self.vid, # vendor + self.pid, # product + 0, # version + 0, # country + bytes(self._rdesc)) # rd_data[HID_MAX_DESCRIPTOR_SIZE] + + n = os.write(self._fd, buf) + assert n == len(buf) + self.ready = True + + def destroy(self): + self.ready = False + buf = struct.pack('< L', + UHIDDevice.UHID_DESTROY) + os.write(self._fd, buf) + + def start(self, flags): + print('start') + + def stop(self): + print('stop') + + def open(self): + print('open', self.sys_path) + + def close(self): + print('close') + + def set_report(self, req, rnum, rtype, size, data): + print('set report', req, rtype, size, [f'{d:02x}' for d in data[:size]]) + self.call_set_report(req, 1) + + def get_report(self, req, rnum, rtype): + print('get report', req, rnum, rtype) + self.call_get_report(req, [], 1) + + def output_report(self, data, size, rtype): + print('output', rtype, size, [f'{d:02x}' for d in data[:size]]) + + def process_one_event(self): + buf = os.read(self._fd, 4380) + assert len(buf) == 4380 + evtype = struct.unpack_from('< L', buf)[0] + if evtype == UHIDDevice.UHID_START: + ev, flags = struct.unpack_from('< L Q', buf) + self.start(flags) + elif evtype == UHIDDevice.UHID_OPEN: + self.open() + elif evtype == UHIDDevice.UHID_STOP: + self.stop() + elif evtype == UHIDDevice.UHID_CLOSE: + self.close() + elif evtype == UHIDDevice.UHID_SET_REPORT: + ev, req, rnum, rtype, size, data = struct.unpack_from('< L L B B H 4096s', buf) + self.set_report(req, rnum, rtype, size, data) + elif evtype == UHIDDevice.UHID_GET_REPORT: + ev, req, rnum, rtype = struct.unpack_from('< L L B B', buf) + self.get_report(req, rnum, rtype) + elif evtype == UHIDDevice.UHID_OUTPUT: + ev, data, size, rtype = struct.unpack_from('< L 4096s H B', buf) + self.output_report(data, size, rtype) diff --git a/tuhi/wacom.py b/tuhi/wacom.py index 785b2a3..57e9c65 100644 --- a/tuhi/wacom.py +++ b/tuhi/wacom.py @@ -22,6 +22,7 @@ import uuid import errno from gi.repository import GObject from .drawing import Drawing +from .uhid import UHIDDevice logger = logging.getLogger('tuhi.wacom') @@ -54,6 +55,43 @@ class DeviceMode(enum.Enum): LIVE = 3 +wacom_live_rdesc_template = [ + 0x05, 0x0d, # Usage Page (Digitizers) 0 + 0x09, 0x02, # Usage (Pen) 2 + 0xa1, 0x01, # Collection (Application) 4 + 0x85, 0x01, # .Report ID (1) 6 + 0x09, 0x20, # .Usage (Stylus) 8 + 0xa1, 0x00, # .Collection (Physical) 10 + 0x09, 0x32, # ..Usage (In Range) 12 + 0x15, 0x00, # ..Logical Minimum (0) 14 + 0x25, 0x01, # ..Logical Maximum (1) 16 + 0x95, 0x01, # ..Report Count (1) 18 + 0x75, 0x01, # ..Report Size (1) 20 + 0x81, 0x02, # ..Input (Data,Var,Abs) 22 + 0x95, 0x07, # ..Report Count (7) 24 + 0x81, 0x03, # ..Input (Cnst,Var,Abs) 26 + 0x05, 0x01, # ..Usage Page (Generic Desktop) 43 + 0x09, 0x30, # ..Usage (X) 45 + 0x75, 0x10, # ..Report Size (16) 47 + 0x95, 0x01, # ..Report Count (1) 49 + 0x55, 0x0e, # ..Unit Exponent (-2) 51 + 0x65, 0x11, # ..Unit (Centimeter,SILinear) 53 + 0x46, 0xec, 0x09, # ..Physical Maximum (2540) 55 + 0x26, 0x80, 0x25, # ..Logical Maximum (9600) 58 + 0x81, 0x02, # ..Input (Data,Var,Abs) 61 + 0x09, 0x31, # ..Usage (Y) 63 + 0x46, 0x9d, 0x06, # ..Physical Maximum (1693) 65 + 0x26, 0x20, 0x1c, # ..Logical Maximum (7200) 68 + 0x81, 0x02, # ..Input (Data,Var,Abs) 71 + 0x05, 0x0d, # ..Usage Page (Digitizers) 73 + 0x09, 0x30, # ..Usage (Tip Pressure) 75 + 0x26, 0x00, 0x01, # ..Logical Maximum (256) 77 + 0x81, 0x02, # ..Input (Data,Var,Abs) 80 + 0xc0, # .End Collection 82 + 0xc0, # End Collection 83 +] + + def signed_char_to_int(v): return int.from_bytes([v], byteorder='little', signed=True) @@ -277,6 +315,7 @@ class WacomProtocolBase(WacomProtocolLowLevelComm): self._uuid = uuid self._timestamp = 0 self.pen_data_buffer = [] + self._uhid_device = None device.connect_gatt_value(WACOM_CHRC_LIVE_PEN_DATA_UUID, self._on_pen_data_changed) @@ -307,11 +346,19 @@ class WacomProtocolBase(WacomProtocolLowLevelComm): while data: if bytes(data) == b'\xff\xff\xff\xff\xff\xff': logger.debug(f'Pen left proximity') + + if self._uhid_device is not None: + self._uhid_device.call_input_event([1, 0, 0, 0, 0, 0, 0, 0]) + else: x = int.from_bytes(data[0:2], byteorder='little') y = int.from_bytes(data[2:4], byteorder='little') pressure = int.from_bytes(data[4:6], byteorder='little') logger.debug(f'New Pen Data: ({x},{y}), pressure: {pressure}') + + if self._uhid_device is not None: + self._uhid_device.call_input_event([1, 1, *data[:6]]) + data = data[6:] self._timestamp += 5 @@ -389,10 +436,18 @@ class WacomProtocolBase(WacomProtocolLowLevelComm): expected_opcode=0xb3, arguments=args) - def start_live(self, uhid): + def start_live(self, fd): self.send_nordic_command_sync(command=0xb1, expected_opcode=0xb3) - logger.debug(f'Starting wacom live mode on fd: {uhid}') + logger.debug(f'Starting wacom live mode on fd: {fd}') + + rdesc = wacom_live_rdesc_template + uhid_device = UHIDDevice(fd) + uhid_device.rdesc = rdesc + uhid_device.name = self.device.name + uhid_device.info = (5, 0x056a, 0x0001) + uhid_device.create_kernel_device() + self._uhid_device = uhid_device def stop_live(self): args = [0x02]