I am currently developing a drawing interface that interacts with an XYZ machine (plotter) that uses a Wacom tablet. X and Y are related to pen coordinates on the screen, and Z should be the pen pressure. So I am trying to access the data sent by the wacom tablet while holding the pen.
My program is running python with pygame on a windows machine.
I found a way to access tablet raw data with pywinusb.hid, like so:
import pywinusb.hid as hid
# detect tablet
device = None
my_tablet = "PTK-440"
for dev in hid.find_all_hid_devices():
if my_tablet == dev.product_name:
device = dev
break
# get data
def on_data(data):
log.info(f"tablet data: {data}")
if device:
try:
self.device.open()
self.device.set_raw_data_handler(on_data)
except Exception as err:
log.error(err)
This is what I get at each frame:
[213, 32, 124, 92, 152, 81, 134, 3, 0, 0, 2, 8, 16, 0, 253, 32, 128, 156, 2, 8, 16, 0, 0, 0, 0, 0, 0, 0, 96, 9, 20, 5, 156, 24, 180, 45, 0, 0]
I did not find any documentation related to the raw data.
So I tried different approaches
- Wacom gives access to an SDK but not for python users
- using Wintab and
ctypesseems a good idea, but it is way too complicated for me. - I am also aware of the
cgkit/cgkit2module combined withpygamebut the module is deprecated for a while now. - I know Qt has an event handler for such purpose (
QtTabletEvent), but it requires implementing another threaded loop, which is overcomplicated as well.
My try with Wintab:
import ctypes
from ctypes import wintypes
import pygame
from typing import List
from livegcode import log, GUI, PATH
from livegcode.utils import TimeOut
class LOGCONTEXT(ctypes.Structure):
_fields_ = [
("lcName", ctypes.c_char * 40),
("lcOptions", ctypes.c_uint),
("lcStatus", ctypes.c_uint),
("lcLocks", ctypes.c_uint),
("lcMsgBase", ctypes.c_uint),
("lcDevice", ctypes.c_uint),
("lcPktRate", ctypes.c_uint),
("lcPktData", ctypes.c_uint),
("lcPktMode", ctypes.c_uint),
("lcMoveMask", ctypes.c_uint),
("lcBtnDnMask", ctypes.c_uint),
("lcBtnUpMask", ctypes.c_uint),
("lcInOrgX", ctypes.c_long),
("lcInOrgY", ctypes.c_long),
("lcInOrgZ", ctypes.c_long),
("lcInExtX", ctypes.c_long),
("lcInExtY", ctypes.c_long),
("lcInExtZ", ctypes.c_long),
("lcOutOrgX", ctypes.c_long),
("lcOutOrgY", ctypes.c_long),
("lcOutOrgZ", ctypes.c_long),
("lcOutExtX", ctypes.c_long),
("lcOutExtY", ctypes.c_long),
("lcOutExtZ", ctypes.c_long),
("lcSensX", ctypes.c_long),
("lcSensY", ctypes.c_long),
("lcSensZ", ctypes.c_long),
("lcSysMode", ctypes.c_bool),
("lcSysOrgX", ctypes.c_int),
("lcSysOrgY", ctypes.c_int),
("lcSysExtX", ctypes.c_int),
("lcSysExtY", ctypes.c_int),
("lcSysSensX", ctypes.c_long),
("lcSysSensY", ctypes.c_long)
]
class WintabDefinitions:
def __init__(self):
self.wintab = ctypes.WinDLL('Wintab32.dll') # load wintab dll
self.window = self._get_pygame_handle_window()
self.context = self._init_wintab_context()
def _get_pygame_handle_window(self):
""" Get the window handle from pygame """
return pygame.display.get_wm_info()['window']
def close_wintab_context(self):
self.wintab.WTClose(self.context)
def _init_wintab_context(self):
self._define_functions_prototypes()
log_context = LOGCONTEXT()
log_context.lcPktData = 0x0004 | 0x0008 | 0x0010 # Include X, Y, and pressure in packets
context = self.wintab.WTOpenA(self.window, ctypes.byref(log_context), True)
if context == 0:
log.error("Failed to initialize Wintab context.")
return None
return context
def _define_functions_prototypes(self):
HCTX = wintypes.HANDLE
self.wintab.WTOpenA.argtypes = [wintypes.HWND, ctypes.POINTER(LOGCONTEXT), wintypes.BOOL]
self.wintab.WTOpenA.restype = HCTX
self.wintab.WTClose.argtypes = [HCTX]
self.wintab.WTClose.restype = wintypes.BOOL
self.wintab.WTPacket.argtypes = [HCTX, wintypes.UINT, ctypes.POINTER(ctypes.c_void_p)]
self.wintab.WTPacket.restype = wintypes.BOOL
class TabletWacom(WintabDefinitions):
def __init__(self):
super().__init__()
def get_tablet_data(self):
log.info(self.context)
if self.context:
packet = self.wintab.WTPacketsGet(self.context, 1)
if packet:
x = packet.pkX
y = packet.pkY
pressure = packet.pkNormalPressure
print(f"Tablet Data: X={x}, Y={y}, Pressure={pressure}")
def close(self):
self.close_wintab_context()
Which results with a bad context.
Does someone have an idea for resolving this problem?