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
This commit is contained in:
Benjamin Tissoires 2018-02-13 20:01:56 +01:00 committed by Peter Hutterer
parent d4dd672b2f
commit d295e12310
2 changed files with 271 additions and 2 deletions

214
tuhi/uhid.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
#
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)

View File

@ -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]