I just bought the graphical tablet VEIKK A30. The mouse is recognized directly. Unfortunately, I can't find how to enable pressure sensitivity on Linux. I saw one guy reporting that with some tweaking it was possible to make it work, but can't find any reference on that.
Edit: here is the output of dmesg
[mars19 01:15] usb 2-1: new full-speed USB device number 10 using xhci_hcd
[ +0,153026] usb 2-1: New USB device found, idVendor=2feb, idProduct=0002, bcdDevice= 0.00
[ +0,000006] usb 2-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ +0,000004] usb 2-1: Product: A30
[ +0,000003] usb 2-1: Manufacturer: VEIKK.INC
[ +0,000004] usb 2-1: SerialNumber: 0000001
[ +0,003052] input: VEIKK.INC A30 Mouse as /devices/pci0000:00/0000:00:14.0/usb2/2-1/2-1:1.0/0003:2FEB:0002.000C/input/input64
[ +0,064250] input: VEIKK.INC A30 as /devices/pci0000:00/0000:00:14.0/usb2/2-1/2-1:1.0/0003:2FEB:0002.000C/input/input65
[ +0,000429] hid-generic 0003:2FEB:0002.000C: input,hidraw0: USB HID v1.00 Mouse [VEIKK.INC A30] on usb-0000:00:14.0-1/input0
[ +0,001079] input: VEIKK.INC A30 as /devices/pci0000:00/0000:00:14.0/usb2/2-1/2-1:1.1/0003:2FEB:0002.000D/input/input66
[ +0,062599] hid-generic 0003:2FEB:0002.000D: input,hidraw1: USB HID v1.00 Keyboard [VEIKK.INC A30] on usb-0000:00:14.0-1/input1
[ +0,001208] hid-generic 0003:2FEB:0002.000E: hiddev0,hidraw2: USB HID v1.00 Device [VEIKK.INC A30] on usb-0000:00:14.0-1/input2
Edit2: hum, it seems that the device that sends the pressure can be read in sudo cat /dev/hidraw0
as the patterns differs depending the the pressure I apply. I don't know how to read this binary though ^^ If I can, maybe I can use uinput to map it to a new device? ^^ Note that /dev/input
contains files like mouseX, eventY, and mouse1 is correlated with the tablet, but not with the pressure, and the only file in this folder that displays lot's of information linked with the tablet is the file /dev/input/by-id/usb-VEIKK.INC_A30_0000001-event-mouse
. But it's less clear that the patterns match the pressure, too many information are sent here. If you know how to parse them let me know!
Edit3:
So I don't yet have a driver, but at least it seems to be easy to read the input from the device raw communication. I made this python script as a proof of concept:
#!/usr/bin/env python3
import struct
PRINT_TIMESTAMP = True
# Open the file in the read-binary mode
f = open("/dev/input/by-id/usb-VEIKK.INC_A30_0000001-event-mouse", "rb" )
while 1:
data = f.read(24)
# print struct.unpack('4IHHI',data)
###### FORMAT = ( Time Stamp_INT , 0 , Time Stamp_DEC , 0 ,
###### type , code ( key pressed ) , value (press/release) )
time_int, _, time_dec, _, ev_type, ev_code, ev_val = struct.unpack('4IHHI',data)
t = (ev_type, ev_code)
if ((t == (0,0) and ev_val == 0)
or (t == (4, 4) and ev_val >= 589825 and ev_val <= 589827)):
# Redundant as it's for normal/bottom/top clicks
# (same code for press/release), or just garbage 0,0,0
continue
if PRINT_TIMESTAMP:
print("[{:.2f}] ".format(time_int + time_dec/1e6),
end="", flush=True)
if t == (3,0):
print("Pos x: {} ({:.2f}%)".format(ev_val, 100*ev_val/32767), flush=True)
elif t == (3,1):
print("Pos y: {} ({:.2f}%)".format(ev_val, 100*ev_val/32767), flush=True)
elif t == (3,24):
print("Pression: {} ({:.2f}%)".format(ev_val, 100*ev_val/8191), flush=True)
elif t == (1,272):
print("Normal click ({})".format("press" if ev_val else "release"), flush=True)
elif t == (1,273):
print("click button 2 (bottom) ({})".format("press" if ev_val else "release"), flush=True)
elif t == (1,274):
print("click button 3 (top) ({})".format("press" if ev_val else "release"), flush=True)
else:
print("Unknow: type={}, code={}, value={}".format(ev_type, ev_code, ev_val), flush=True)
Démo:
[1553182025.55] Pos y: 11458 (34.97%)
[1553182025.55] Pos x: 14310 (43.67%)
[1553182025.56] Pos x: 14314 (43.68%)
[1553182025.56] Pos x: 14318 (43.70%)
[1553182025.57] Pos x: 14321 (43.71%)
[1553182025.57] Normal click (press)
[1553182025.57] Pos x: 14323 (43.71%)
[1553182025.57] Pression: 1122 (13.70%)
[1553182025.57] Pos x: 14326 (43.72%)
[1553182025.57] Pos y: 11466 (34.99%)
[1553182025.57] Pression: 1260 (15.38%)
[1553182025.58] Pos x: 14329 (43.73%)
[1553182025.58] Pression: 1337 (16.32%)
[1553182025.58] Pos x: 14330 (43.73%)
[1553182025.58] Pos y: 11494 (35.08%)
[1553182025.58] Pression: 1515 (18.50%)
[1553182025.59] Pos y: 11506 (35.11%)
[1553182025.59] Pression: 1687 (20.60%)
[1553182025.59] Pos y: 11517 (35.15%)
[1553182025.59] Pression: 1689 (20.62%)
[1553182025.59] Pos y: 11529 (35.18%)
[1553182025.59] Pression: 1789 (21.84%)
[1553182025.60] Pos y: 11536 (35.21%)
[1553182025.60] Pression: 1829 (22.33%)
[1553182025.60] Pos y: 11542 (35.22%)
[1553182025.60] Pression: 1907 (23.28%)
[1553182025.61] Pression: 2031 (24.80%)
[1553182025.61] Pos y: 11549 (35.25%)
[1553182025.61] Pression: 2140 (26.13%)
Edit 4:
Amazing page: https://digimend.github.io/support/howto/trbl/locating_failure/ However, everything works here… except at the very last step when I want to test it. I tried to test with MyPaint, and it does not detect pressure.
I also tried to do my own code that basically copies the input from the event file into a new device like that:
#!/usr/bin/env python3
import sys
import libevdev
import time
def print_capabilities(l):
v = l.driver_version
print("Input driver version is {}.{}.{}".format(v >> 16, (v >> 8) & 0xff, v & 0xff))
id = l.id
print("Input device ID: bus {:#x} vendor {:#x} product {:#x} version {:#x}".format(
id["bustype"],
id["vendor"],
id["product"],
id["version"],
))
print("Input device name: {}".format(l.name))
print("Supported events:")
for t, cs in l.evbits.items():
print(" Event type {} ({})".format(t.value, t.name))
for c in cs:
if t in [libevdev.EV_LED, libevdev.EV_SND, libevdev.EV_SW]:
v = l.value[c]
print(" Event code {} ({}) state {}".format(c.value, c.name, v))
else:
print(" Event code {} ({})".format(c.value, c.name))
if t == libevdev.EV_ABS:
a = l.absinfo[c]
print(" {:10s} {:6d}".format('Value', a.value))
print(" {:10s} {:6d}".format('Minimum', a.minimum))
print(" {:10s} {:6d}".format('Maximum', a.maximum))
print(" {:10s} {:6d}".format('Fuzz', a.fuzz))
print(" {:10s} {:6d}".format('Flat', a.flat))
print(" {:10s} {:6d}".format('Resolution', a.resolution))
print("Properties:")
for p in l.properties:
print(" Property type {} ({})".format(p.value, p.name))
def print_event(e):
print("Event: time {}.{:06d}, ".format(e.sec, e.usec), end='')
if e.matches(libevdev.EV_SYN):
if e.matches(libevdev.EV_SYN.SYN_MT_REPORT):
print("++++++++++++++ {} ++++++++++++".format(e.code.name))
elif e.matches(libevdev.EV_SYN.SYN_DROPPED):
print(">>>>>>>>>>>>>> {} >>>>>>>>>>>>".format(e.code.name))
else:
print("-------------- {} ------------".format(e.code.name))
else:
print("type {:02x} {} code {:03x} {:20s} value {:4d}".format(e.type.value, e.type.name, e.code.value, e.code.name, e.value))
def main(args):
path = args[1]
dev = libevdev.Device()
dev.name = "Combined Both Devices"
dev.enable(libevdev.EV_ABS.ABS_X,
libevdev.InputAbsInfo(minimum=0, maximum=32767))
dev.enable(libevdev.EV_ABS.ABS_Y,
libevdev.InputAbsInfo(minimum=0, maximum=32767))
dev.enable(libevdev.EV_ABS.ABS_Z,
libevdev.InputAbsInfo(minimum=0, maximum=8191))
dev.enable(libevdev.EV_ABS.ABS_0B,
libevdev.InputAbsInfo(minimum=0, maximum=8191))
dev.enable(libevdev.EV_ABS.ABS_DISTANCE,
libevdev.InputAbsInfo(minimum=0, maximum=8191))
dev.enable(libevdev.EV_ABS.ABS_PRESSURE,
libevdev.InputAbsInfo(minimum=0, maximum=8191))
dev.enable(libevdev.EV_MSC.MSC_SCAN)
dev.enable(libevdev.EV_KEY.BTN_LEFT)
dev.enable(libevdev.EV_KEY.BTN_RIGHT)
dev.enable(libevdev.EV_KEY.BTN_MIDDLE)
dev.enable(libevdev.EV_KEY.BTN_TOUCH)
dev.enable(libevdev.EV_SYN.SYN_REPORT)
dev.enable(libevdev.EV_SYN.SYN_CONFIG)
dev.enable(libevdev.EV_SYN.SYN_MT_REPORT)
dev.enable(libevdev.EV_SYN.SYN_DROPPED)
dev.enable(libevdev.EV_SYN.SYN_04)
dev.enable(libevdev.EV_SYN.SYN_05)
dev.enable(libevdev.EV_SYN.SYN_06)
dev.enable(libevdev.EV_SYN.SYN_07)
dev.enable(libevdev.EV_SYN.SYN_08)
dev.enable(libevdev.EV_SYN.SYN_09)
dev.enable(libevdev.EV_SYN.SYN_0A)
dev.enable(libevdev.EV_SYN.SYN_0B)
dev.enable(libevdev.EV_SYN.SYN_0C)
dev.enable(libevdev.EV_SYN.SYN_0D)
dev.enable(libevdev.EV_SYN.SYN_0E)
dev.enable(libevdev.EV_SYN.SYN_MAX)
try:
uinput = dev.create_uinput_device()
print("New device at {} ({})".format(uinput.devnode, uinput.syspath))
# Sleep for a bit so udev, libinput, Xorg, Wayland, ...
# all have had a chance to see the device and initialize
# it. Otherwise the event will be sent by the kernel but
# nothing is ready to listen to the device yet.
time.sleep(1)
with open(path, "rb") as fd:
l = libevdev.Device(fd)
print_capabilities(l)
print("################################\n"
"# Waiting for events #\n"
"################################")
while True:
try:
ev = l.events()
for e in ev:
uinput.send_events([e])
print_event(e)
if e.matches(libevdev.EV_ABS.ABS_PRESSURE):
print("Pressure! Will send another packeton Z axis!")
uinput.send_events([libevdev.InputEvent(libevdev.EV_ABS.ABS_Z, e.value)])
uinput.send_events([libevdev.InputEvent(libevdev.EV_ABS.ABS_0B, e.value)])
uinput.send_events([libevdev.InputEvent(libevdev.EV_ABS.ABS_DISTANCE, e.value)])
except libevdev.EventsDroppedException:
for e in l.sync():
print_event(e)
uinput.send_events([e])
except KeyboardInterrupt:
pass
except IOError as e:
import errno
if e.errno == errno.EACCES:
print("Insufficient permissions to access {}".format(path))
elif e.errno == errno.ENOENT:
print("Device {} does not exist".format(path))
else:
raise e
except OSError as e:
print(e)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: {} /dev/input/eventX".format(sys.argv[0]))
sys.exit(1)
main(sys.argv)
and I realized that it's more or less the same as the Veikk mouse, and it does not work better.
Best Answer
Youou!!! I managed to make a python script that gives me back the pressure on a new virtual device :-D Now I can use it with Gimp/Krita/... The only remaining part to do is to write a proper C driver and load it directly in the kernel... If you have some idea let me know!
Proof:
So the idea of the script is to read the input from the event file of the graphics tablet, and then create the good events (no more
BTN_LEFT
, butBTN_TOUCH
instead... see my other question/answer here for more details).To run the script (see below), save it under
combine_both.py
, and make it executable/install deps:Then, check out the available input devices:
This should give you several entries, among them one entry like
Note the id (noted as
<id veikk>
later), as well as the id of the virtual core pointer<id core>
:Then, you need to know which
/dev/input/eventX
file to pick, the easiest is to runsudo evtest
and read the name of the file corresponding to theVEIKK.INC A30 Mouse
. Then run the script with this file in argument like in:This script should output stuff when you try to click/move on the device. Moreover
xinput list
should also list this device with nameTablet alone Pen (0)
with an id<id fake tablet>
, andxinput test <id fake tablet>
should give you something like (note the 3 columns for x/y/pressure):Now, to make it work with Gimp/Krita, you want to disable the real mouse (otherwise you have conflicts between the real and fake tablet), with
and when you are done you can reattach the real device with
In gimp, don't forget to setup
Tablet alone Pen (0)
withmode=screen
inEdit/input devices
, and make sure the real tabletVEIKK.INC A30 Mouse
is disabled. Finally, chose a good dynamic, likePencil Generic
to test!Enjoy! (and I'll let you know if I write the C driver at some points)
Script: