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:
parent
d4dd672b2f
commit
d295e12310
|
@ -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)
|
|
@ -22,6 +22,7 @@ import uuid
|
||||||
import errno
|
import errno
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject
|
||||||
from .drawing import Drawing
|
from .drawing import Drawing
|
||||||
|
from .uhid import UHIDDevice
|
||||||
|
|
||||||
logger = logging.getLogger('tuhi.wacom')
|
logger = logging.getLogger('tuhi.wacom')
|
||||||
|
|
||||||
|
@ -54,6 +55,43 @@ class DeviceMode(enum.Enum):
|
||||||
LIVE = 3
|
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):
|
def signed_char_to_int(v):
|
||||||
return int.from_bytes([v], byteorder='little', signed=True)
|
return int.from_bytes([v], byteorder='little', signed=True)
|
||||||
|
|
||||||
|
@ -277,6 +315,7 @@ class WacomProtocolBase(WacomProtocolLowLevelComm):
|
||||||
self._uuid = uuid
|
self._uuid = uuid
|
||||||
self._timestamp = 0
|
self._timestamp = 0
|
||||||
self.pen_data_buffer = []
|
self.pen_data_buffer = []
|
||||||
|
self._uhid_device = None
|
||||||
|
|
||||||
device.connect_gatt_value(WACOM_CHRC_LIVE_PEN_DATA_UUID,
|
device.connect_gatt_value(WACOM_CHRC_LIVE_PEN_DATA_UUID,
|
||||||
self._on_pen_data_changed)
|
self._on_pen_data_changed)
|
||||||
|
@ -307,11 +346,19 @@ class WacomProtocolBase(WacomProtocolLowLevelComm):
|
||||||
while data:
|
while data:
|
||||||
if bytes(data) == b'\xff\xff\xff\xff\xff\xff':
|
if bytes(data) == b'\xff\xff\xff\xff\xff\xff':
|
||||||
logger.debug(f'Pen left proximity')
|
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:
|
else:
|
||||||
x = int.from_bytes(data[0:2], byteorder='little')
|
x = int.from_bytes(data[0:2], byteorder='little')
|
||||||
y = int.from_bytes(data[2:4], byteorder='little')
|
y = int.from_bytes(data[2:4], byteorder='little')
|
||||||
pressure = int.from_bytes(data[4:6], byteorder='little')
|
pressure = int.from_bytes(data[4:6], byteorder='little')
|
||||||
logger.debug(f'New Pen Data: ({x},{y}), pressure: {pressure}')
|
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:]
|
data = data[6:]
|
||||||
self._timestamp += 5
|
self._timestamp += 5
|
||||||
|
|
||||||
|
@ -389,10 +436,18 @@ class WacomProtocolBase(WacomProtocolLowLevelComm):
|
||||||
expected_opcode=0xb3,
|
expected_opcode=0xb3,
|
||||||
arguments=args)
|
arguments=args)
|
||||||
|
|
||||||
def start_live(self, uhid):
|
def start_live(self, fd):
|
||||||
self.send_nordic_command_sync(command=0xb1,
|
self.send_nordic_command_sync(command=0xb1,
|
||||||
expected_opcode=0xb3)
|
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):
|
def stop_live(self):
|
||||||
args = [0x02]
|
args = [0x02]
|
||||||
|
|
Loading…
Reference in New Issue