Graphic Tablet VEIKK: pressure sensitivity on LinuxSimulate graphics tablet using libeventDoes anyone know how to configure a Wacom Bamboo tablet to work left-handled?Linux on a tegra tablet for programmingBuild an Affordable Linux TabletTablet evdev button event calibrationInstalling Linux on a tabletNative Linux installation on Allwinner tabletMatch graphic tablet active area to monitor subarea in a dual monitor setupDesktop Linux on LG V500 android tabletRestrict graphic tablet to primary displayGraphic tablet buttons
How do I go from 300 unfinished/half written blog posts, to published posts?
How to run a prison with the smallest amount of guards?
How can a function with a hole (removable discontinuity) equal a function with no hole?
How to write papers efficiently when English isn't my first language?
How can we prove that any integral in the set of non-elementary integrals cannot be expressed in the form of elementary functions?
What does "I’d sit this one out, Cap," imply or mean in the context?
Detecting if an element is found inside a container
Is HostGator storing my password in plaintext?
Trouble understanding the speech of overseas colleagues
What does the word "Atten" mean?
How do I extract a value from a time formatted value in excel?
How to be diplomatic in refusing to write code that breaches the privacy of our users
when is out of tune ok?
How do I find the solutions of the following equation?
Purchasing a ticket for someone else in another country?
Implement the Thanos sorting algorithm
Go Pregnant or Go Home
Would a high gravity rocky planet be guaranteed to have an atmosphere?
CREATE opcode: what does it really do?
Was Spock the First Vulcan in Starfleet?
How does the UK government determine the size of a mandate?
Why didn't Theresa May consult with Parliament before negotiating a deal with the EU?
How to safely derail a train during transit?
How long to clear the 'suck zone' of a turbofan after start is initiated?
Graphic Tablet VEIKK: pressure sensitivity on Linux
Simulate graphics tablet using libeventDoes anyone know how to configure a Wacom Bamboo tablet to work left-handled?Linux on a tegra tablet for programmingBuild an Affordable Linux TabletTablet evdev button event calibrationInstalling Linux on a tabletNative Linux installation on Allwinner tabletMatch graphic tablet active area to monitor subarea in a dual monitor setupDesktop Linux on LG V500 android tabletRestrict graphic tablet to primary displayGraphic tablet buttons
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
Gimp display:
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.
tablet
add a comment |
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
Gimp display:
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.
tablet
add a comment |
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
Gimp display:
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.
tablet
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
Gimp display:
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.
tablet
tablet
edited Mar 22 at 21:33
Rui F Ribeiro
41.8k1483142
41.8k1483142
asked Mar 21 at 10:48
tobiasBoratobiasBora
264312
264312
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
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, but BTN_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:
$ chmod +x combine_both.py
$ sudo pip3 install libevdev
Then, check out the available input devices:
$ xinput list
This should give you several entries, among them one entry like
VEIKK.INC A30 Mouse id=12 [slave pointer (2)]
Note the id (noted as <id veikk> later), as well as the id of the virtual core pointer <id core>:
Virtual core pointer id=2 [master pointer (3)]
Then, you need to know which /dev/input/eventX file to pick, the easiest is to run sudo evtest and read the name of the file corresponding to the VEIKK.INC A30 Mouse. Then run the script with this file in argument like in:
sudo ./combine_both.py /dev/input/event7
This script should output stuff when you try to click/move on the device. Moreover xinput list should also list this device with name Tablet alone Pen (0) with an id <id fake tablet>, and xinput test <id fake tablet> should give you something like (note the 3 columns for x/y/pressure):
$ xinput test 21
[...]
motion a[0]=4151295 a[1]=4151295 a[2]=241
motion a[0]=4060671 a[1]=4060671 a[2]=226
motion a[0]=3969535 a[1]=3969535 a[2]=211
motion a[0]=3878399 a[1]=3878399 a[2]=196
motion a[0]=3787775 a[1]=3787775 a[2]=181
motion a[0]=3696639 a[1]=3696639 a[2]=166
motion a[0]=3605503 a[1]=3605503 a[2]=151
motion a[0]=3514879 a[1]=3514879 a[2]=137
motion a[0]=3423743 a[1]=3423743 a[2]=122
motion a[0]=3332607 a[1]=3332607 a[2]=107
motion a[0]=3241983 a[1]=3241983 a[2]=92
motion a[0]=3150847 a[1]=3150847 a[2]=77
motion a[0]=3059711 a[1]=3059711 a[2]=62
motion a[0]=2969087 a[1]=2969087 a[2]=47
motion a[0]=2877951 a[1]=2877951 a[2]=32
motion a[0]=2650623 a[1]=2650623 a[2]=17
button release 1
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
xinput float <id veikk>
and when you are done you can reattach the real device with
xinput reattach <id veikk> <id core>
In gimp, don't forget to setup Tablet alone Pen (0) with mode=screen in Edit/input devices, and make sure the real tablet VEIKK.INC A30 Mouse is disabled. Finally, chose a good dynamic, like Pencil Generic to test!
Enjoy! (and I'll let you know if I write the C driver at some points)
Script:
#!/usr/bin/env python3
import sys
import libevdev
import datetime
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))
class Tablet():
def __init__(self, tablet_name):
self.tablet_name = tablet_name
def __enter__(self):
self.dev = libevdev.Device()
self.dev.name = "Tablet alone"
### NB: all the following information needs to be enabled
### in order to recognize the device as a tablet.
# Say that the device will send "absolute" values
self.dev.enable(libevdev.INPUT_PROP_DIRECT)
# Say that we are using the pen (not the erasor), and should be set to 1 when we are at proximity to the device.
# See http://www.infradead.org/~mchehab/kernel_docs_pdf/linux-input.pdf page 9 (=13) and guidelines page 12 (=16), or the https://github.com/linuxwacom/input-wacom/blob/master/4.5/wacom_w8001.c (rdy=proximity)
self.dev.enable(libevdev.EV_KEY.BTN_TOOL_PEN)
self.dev.enable(libevdev.EV_KEY.BTN_TOOL_RUBBER)
# Click
self.dev.enable(libevdev.EV_KEY.BTN_TOUCH)
# Press button 1 on pen
self.dev.enable(libevdev.EV_KEY.BTN_STYLUS)
# Press button 2 on pen, see great doc
self.dev.enable(libevdev.EV_KEY.BTN_STYLUS2)
# Send absolute X coordinate
self.dev.enable(libevdev.EV_ABS.ABS_X,
libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
# Send absolute Y coordinate
self.dev.enable(libevdev.EV_ABS.ABS_Y,
libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
# Send absolute pressure
self.dev.enable(libevdev.EV_ABS.ABS_PRESSURE,
libevdev.InputAbsInfo(minimum=0, maximum=8191))
# Use to confirm that we finished to send the informations
# (to be sent after every burst of information, otherwise
# the kernel does not proceed the information)
self.dev.enable(libevdev.EV_SYN.SYN_REPORT)
# Report buffer overflow
self.dev.enable(libevdev.EV_SYN.SYN_DROPPED)
self.uinput = self.dev.create_uinput_device()
print("New device at ()".format(self.uinput.devnode, self.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. And it
# will never be detected in the futur ;-)
time.sleep(1)
# self.simulate_first_click()
self.reset_state()
return self
def __exit__(self, type, value, traceback):
pass
def reset_state(self):
self.is_away = True
self.is_touching = False
self.pressed_button_1 = False
self.pressed_button_2 = False
self.lastmodif = datetime.datetime.now()
def send_events(self, events, is_away=False):
self.lastmodif = datetime.datetime.now()
self.is_away = is_away
self.uinput.send_events(events)
def simulate_first_click(self):
"""Useful only the first time to make sure
xinput detected the input"""
# Reports that the PEN is close to the surface
# Important to make sure xinput can detect (and list)
# the pen. Otherwise, it won't write anything in gimp.
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=1),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
# Says that the pen it out of range of the tablet. Useful
# to make sure you can move your mouse, and to avoid
# strange things during the first draw.
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=0),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def send_state_no_pos(self, is_away=False):
self.lastmodif = datetime.datetime.now()
self.is_away = is_away
print("Away: , Touching: ".format(self.is_away, self.is_touching))
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=1 if self.is_touching else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=1 if not self.is_away else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS,
value=1 if self.pressed_button_1 else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2,
value=1 if self.pressed_button_2 else 0),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def touch_press(self):
self.is_touching = True
self.send_state_no_pos()
def touch_release(self):
self.is_touching = False
self.send_state_no_pos()
def button_1_press(self):
self.pressed_button_1 = True
self.send_state_no_pos()
def button_1_release(self):
self.pressed_button_1 = False
self.send_state_no_pos()
def button_2_press(self):
self.pressed_button_2 = True
self.send_state_no_pos()
def button_2_release(self):
self.pressed_button_2 = False
self.send_state_no_pos()
def move_x(self, abs_x):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_X,
value=abs_x),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def move_y(self, abs_y):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_Y,
value=abs_y),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def change_pressure(self, pressure):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_PRESSURE,
value=pressure),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def handle_event(self, e):
if e.matches(libevdev.EV_ABS.ABS_PRESSURE):
self.change_pressure(e.value)
elif e.matches(libevdev.EV_ABS.ABS_X):
self.move_x(e.value)
elif e.matches(libevdev.EV_ABS.ABS_Y):
self.move_y(e.value)
elif e.matches(libevdev.EV_KEY.BTN_LEFT):
if e.value == 1:
self.touch_press()
else:
self.touch_release()
elif e.matches(libevdev.EV_SYN.SYN_REPORT):
pass
else:
print("Unkown event:")
print_event(e)
def main(args):
path = args[1]
try:
with Tablet("Tablet alone") as tablet:
### Read the events from real graphics tablet
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:
print_event(e)
tablet.handle_event(e)
except libevdev.EventsDroppedException:
for e in l.sync():
print_event(e)
tablet.handle_event(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: sudo /dev/input/eventX".format(sys.argv[0]))
print(" $ sudo evtest")
print("can help you to know which file to use.")
sys.exit(1)
main(sys.argv)
add a comment |
Your Answer
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "106"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f507687%2fgraphic-tablet-veikk-pressure-sensitivity-on-linux%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
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, but BTN_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:
$ chmod +x combine_both.py
$ sudo pip3 install libevdev
Then, check out the available input devices:
$ xinput list
This should give you several entries, among them one entry like
VEIKK.INC A30 Mouse id=12 [slave pointer (2)]
Note the id (noted as <id veikk> later), as well as the id of the virtual core pointer <id core>:
Virtual core pointer id=2 [master pointer (3)]
Then, you need to know which /dev/input/eventX file to pick, the easiest is to run sudo evtest and read the name of the file corresponding to the VEIKK.INC A30 Mouse. Then run the script with this file in argument like in:
sudo ./combine_both.py /dev/input/event7
This script should output stuff when you try to click/move on the device. Moreover xinput list should also list this device with name Tablet alone Pen (0) with an id <id fake tablet>, and xinput test <id fake tablet> should give you something like (note the 3 columns for x/y/pressure):
$ xinput test 21
[...]
motion a[0]=4151295 a[1]=4151295 a[2]=241
motion a[0]=4060671 a[1]=4060671 a[2]=226
motion a[0]=3969535 a[1]=3969535 a[2]=211
motion a[0]=3878399 a[1]=3878399 a[2]=196
motion a[0]=3787775 a[1]=3787775 a[2]=181
motion a[0]=3696639 a[1]=3696639 a[2]=166
motion a[0]=3605503 a[1]=3605503 a[2]=151
motion a[0]=3514879 a[1]=3514879 a[2]=137
motion a[0]=3423743 a[1]=3423743 a[2]=122
motion a[0]=3332607 a[1]=3332607 a[2]=107
motion a[0]=3241983 a[1]=3241983 a[2]=92
motion a[0]=3150847 a[1]=3150847 a[2]=77
motion a[0]=3059711 a[1]=3059711 a[2]=62
motion a[0]=2969087 a[1]=2969087 a[2]=47
motion a[0]=2877951 a[1]=2877951 a[2]=32
motion a[0]=2650623 a[1]=2650623 a[2]=17
button release 1
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
xinput float <id veikk>
and when you are done you can reattach the real device with
xinput reattach <id veikk> <id core>
In gimp, don't forget to setup Tablet alone Pen (0) with mode=screen in Edit/input devices, and make sure the real tablet VEIKK.INC A30 Mouse is disabled. Finally, chose a good dynamic, like Pencil Generic to test!
Enjoy! (and I'll let you know if I write the C driver at some points)
Script:
#!/usr/bin/env python3
import sys
import libevdev
import datetime
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))
class Tablet():
def __init__(self, tablet_name):
self.tablet_name = tablet_name
def __enter__(self):
self.dev = libevdev.Device()
self.dev.name = "Tablet alone"
### NB: all the following information needs to be enabled
### in order to recognize the device as a tablet.
# Say that the device will send "absolute" values
self.dev.enable(libevdev.INPUT_PROP_DIRECT)
# Say that we are using the pen (not the erasor), and should be set to 1 when we are at proximity to the device.
# See http://www.infradead.org/~mchehab/kernel_docs_pdf/linux-input.pdf page 9 (=13) and guidelines page 12 (=16), or the https://github.com/linuxwacom/input-wacom/blob/master/4.5/wacom_w8001.c (rdy=proximity)
self.dev.enable(libevdev.EV_KEY.BTN_TOOL_PEN)
self.dev.enable(libevdev.EV_KEY.BTN_TOOL_RUBBER)
# Click
self.dev.enable(libevdev.EV_KEY.BTN_TOUCH)
# Press button 1 on pen
self.dev.enable(libevdev.EV_KEY.BTN_STYLUS)
# Press button 2 on pen, see great doc
self.dev.enable(libevdev.EV_KEY.BTN_STYLUS2)
# Send absolute X coordinate
self.dev.enable(libevdev.EV_ABS.ABS_X,
libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
# Send absolute Y coordinate
self.dev.enable(libevdev.EV_ABS.ABS_Y,
libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
# Send absolute pressure
self.dev.enable(libevdev.EV_ABS.ABS_PRESSURE,
libevdev.InputAbsInfo(minimum=0, maximum=8191))
# Use to confirm that we finished to send the informations
# (to be sent after every burst of information, otherwise
# the kernel does not proceed the information)
self.dev.enable(libevdev.EV_SYN.SYN_REPORT)
# Report buffer overflow
self.dev.enable(libevdev.EV_SYN.SYN_DROPPED)
self.uinput = self.dev.create_uinput_device()
print("New device at ()".format(self.uinput.devnode, self.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. And it
# will never be detected in the futur ;-)
time.sleep(1)
# self.simulate_first_click()
self.reset_state()
return self
def __exit__(self, type, value, traceback):
pass
def reset_state(self):
self.is_away = True
self.is_touching = False
self.pressed_button_1 = False
self.pressed_button_2 = False
self.lastmodif = datetime.datetime.now()
def send_events(self, events, is_away=False):
self.lastmodif = datetime.datetime.now()
self.is_away = is_away
self.uinput.send_events(events)
def simulate_first_click(self):
"""Useful only the first time to make sure
xinput detected the input"""
# Reports that the PEN is close to the surface
# Important to make sure xinput can detect (and list)
# the pen. Otherwise, it won't write anything in gimp.
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=1),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
# Says that the pen it out of range of the tablet. Useful
# to make sure you can move your mouse, and to avoid
# strange things during the first draw.
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=0),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def send_state_no_pos(self, is_away=False):
self.lastmodif = datetime.datetime.now()
self.is_away = is_away
print("Away: , Touching: ".format(self.is_away, self.is_touching))
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=1 if self.is_touching else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=1 if not self.is_away else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS,
value=1 if self.pressed_button_1 else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2,
value=1 if self.pressed_button_2 else 0),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def touch_press(self):
self.is_touching = True
self.send_state_no_pos()
def touch_release(self):
self.is_touching = False
self.send_state_no_pos()
def button_1_press(self):
self.pressed_button_1 = True
self.send_state_no_pos()
def button_1_release(self):
self.pressed_button_1 = False
self.send_state_no_pos()
def button_2_press(self):
self.pressed_button_2 = True
self.send_state_no_pos()
def button_2_release(self):
self.pressed_button_2 = False
self.send_state_no_pos()
def move_x(self, abs_x):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_X,
value=abs_x),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def move_y(self, abs_y):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_Y,
value=abs_y),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def change_pressure(self, pressure):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_PRESSURE,
value=pressure),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def handle_event(self, e):
if e.matches(libevdev.EV_ABS.ABS_PRESSURE):
self.change_pressure(e.value)
elif e.matches(libevdev.EV_ABS.ABS_X):
self.move_x(e.value)
elif e.matches(libevdev.EV_ABS.ABS_Y):
self.move_y(e.value)
elif e.matches(libevdev.EV_KEY.BTN_LEFT):
if e.value == 1:
self.touch_press()
else:
self.touch_release()
elif e.matches(libevdev.EV_SYN.SYN_REPORT):
pass
else:
print("Unkown event:")
print_event(e)
def main(args):
path = args[1]
try:
with Tablet("Tablet alone") as tablet:
### Read the events from real graphics tablet
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:
print_event(e)
tablet.handle_event(e)
except libevdev.EventsDroppedException:
for e in l.sync():
print_event(e)
tablet.handle_event(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: sudo /dev/input/eventX".format(sys.argv[0]))
print(" $ sudo evtest")
print("can help you to know which file to use.")
sys.exit(1)
main(sys.argv)
add a comment |
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, but BTN_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:
$ chmod +x combine_both.py
$ sudo pip3 install libevdev
Then, check out the available input devices:
$ xinput list
This should give you several entries, among them one entry like
VEIKK.INC A30 Mouse id=12 [slave pointer (2)]
Note the id (noted as <id veikk> later), as well as the id of the virtual core pointer <id core>:
Virtual core pointer id=2 [master pointer (3)]
Then, you need to know which /dev/input/eventX file to pick, the easiest is to run sudo evtest and read the name of the file corresponding to the VEIKK.INC A30 Mouse. Then run the script with this file in argument like in:
sudo ./combine_both.py /dev/input/event7
This script should output stuff when you try to click/move on the device. Moreover xinput list should also list this device with name Tablet alone Pen (0) with an id <id fake tablet>, and xinput test <id fake tablet> should give you something like (note the 3 columns for x/y/pressure):
$ xinput test 21
[...]
motion a[0]=4151295 a[1]=4151295 a[2]=241
motion a[0]=4060671 a[1]=4060671 a[2]=226
motion a[0]=3969535 a[1]=3969535 a[2]=211
motion a[0]=3878399 a[1]=3878399 a[2]=196
motion a[0]=3787775 a[1]=3787775 a[2]=181
motion a[0]=3696639 a[1]=3696639 a[2]=166
motion a[0]=3605503 a[1]=3605503 a[2]=151
motion a[0]=3514879 a[1]=3514879 a[2]=137
motion a[0]=3423743 a[1]=3423743 a[2]=122
motion a[0]=3332607 a[1]=3332607 a[2]=107
motion a[0]=3241983 a[1]=3241983 a[2]=92
motion a[0]=3150847 a[1]=3150847 a[2]=77
motion a[0]=3059711 a[1]=3059711 a[2]=62
motion a[0]=2969087 a[1]=2969087 a[2]=47
motion a[0]=2877951 a[1]=2877951 a[2]=32
motion a[0]=2650623 a[1]=2650623 a[2]=17
button release 1
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
xinput float <id veikk>
and when you are done you can reattach the real device with
xinput reattach <id veikk> <id core>
In gimp, don't forget to setup Tablet alone Pen (0) with mode=screen in Edit/input devices, and make sure the real tablet VEIKK.INC A30 Mouse is disabled. Finally, chose a good dynamic, like Pencil Generic to test!
Enjoy! (and I'll let you know if I write the C driver at some points)
Script:
#!/usr/bin/env python3
import sys
import libevdev
import datetime
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))
class Tablet():
def __init__(self, tablet_name):
self.tablet_name = tablet_name
def __enter__(self):
self.dev = libevdev.Device()
self.dev.name = "Tablet alone"
### NB: all the following information needs to be enabled
### in order to recognize the device as a tablet.
# Say that the device will send "absolute" values
self.dev.enable(libevdev.INPUT_PROP_DIRECT)
# Say that we are using the pen (not the erasor), and should be set to 1 when we are at proximity to the device.
# See http://www.infradead.org/~mchehab/kernel_docs_pdf/linux-input.pdf page 9 (=13) and guidelines page 12 (=16), or the https://github.com/linuxwacom/input-wacom/blob/master/4.5/wacom_w8001.c (rdy=proximity)
self.dev.enable(libevdev.EV_KEY.BTN_TOOL_PEN)
self.dev.enable(libevdev.EV_KEY.BTN_TOOL_RUBBER)
# Click
self.dev.enable(libevdev.EV_KEY.BTN_TOUCH)
# Press button 1 on pen
self.dev.enable(libevdev.EV_KEY.BTN_STYLUS)
# Press button 2 on pen, see great doc
self.dev.enable(libevdev.EV_KEY.BTN_STYLUS2)
# Send absolute X coordinate
self.dev.enable(libevdev.EV_ABS.ABS_X,
libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
# Send absolute Y coordinate
self.dev.enable(libevdev.EV_ABS.ABS_Y,
libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
# Send absolute pressure
self.dev.enable(libevdev.EV_ABS.ABS_PRESSURE,
libevdev.InputAbsInfo(minimum=0, maximum=8191))
# Use to confirm that we finished to send the informations
# (to be sent after every burst of information, otherwise
# the kernel does not proceed the information)
self.dev.enable(libevdev.EV_SYN.SYN_REPORT)
# Report buffer overflow
self.dev.enable(libevdev.EV_SYN.SYN_DROPPED)
self.uinput = self.dev.create_uinput_device()
print("New device at ()".format(self.uinput.devnode, self.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. And it
# will never be detected in the futur ;-)
time.sleep(1)
# self.simulate_first_click()
self.reset_state()
return self
def __exit__(self, type, value, traceback):
pass
def reset_state(self):
self.is_away = True
self.is_touching = False
self.pressed_button_1 = False
self.pressed_button_2 = False
self.lastmodif = datetime.datetime.now()
def send_events(self, events, is_away=False):
self.lastmodif = datetime.datetime.now()
self.is_away = is_away
self.uinput.send_events(events)
def simulate_first_click(self):
"""Useful only the first time to make sure
xinput detected the input"""
# Reports that the PEN is close to the surface
# Important to make sure xinput can detect (and list)
# the pen. Otherwise, it won't write anything in gimp.
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=1),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
# Says that the pen it out of range of the tablet. Useful
# to make sure you can move your mouse, and to avoid
# strange things during the first draw.
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=0),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def send_state_no_pos(self, is_away=False):
self.lastmodif = datetime.datetime.now()
self.is_away = is_away
print("Away: , Touching: ".format(self.is_away, self.is_touching))
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=1 if self.is_touching else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=1 if not self.is_away else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS,
value=1 if self.pressed_button_1 else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2,
value=1 if self.pressed_button_2 else 0),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def touch_press(self):
self.is_touching = True
self.send_state_no_pos()
def touch_release(self):
self.is_touching = False
self.send_state_no_pos()
def button_1_press(self):
self.pressed_button_1 = True
self.send_state_no_pos()
def button_1_release(self):
self.pressed_button_1 = False
self.send_state_no_pos()
def button_2_press(self):
self.pressed_button_2 = True
self.send_state_no_pos()
def button_2_release(self):
self.pressed_button_2 = False
self.send_state_no_pos()
def move_x(self, abs_x):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_X,
value=abs_x),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def move_y(self, abs_y):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_Y,
value=abs_y),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def change_pressure(self, pressure):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_PRESSURE,
value=pressure),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def handle_event(self, e):
if e.matches(libevdev.EV_ABS.ABS_PRESSURE):
self.change_pressure(e.value)
elif e.matches(libevdev.EV_ABS.ABS_X):
self.move_x(e.value)
elif e.matches(libevdev.EV_ABS.ABS_Y):
self.move_y(e.value)
elif e.matches(libevdev.EV_KEY.BTN_LEFT):
if e.value == 1:
self.touch_press()
else:
self.touch_release()
elif e.matches(libevdev.EV_SYN.SYN_REPORT):
pass
else:
print("Unkown event:")
print_event(e)
def main(args):
path = args[1]
try:
with Tablet("Tablet alone") as tablet:
### Read the events from real graphics tablet
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:
print_event(e)
tablet.handle_event(e)
except libevdev.EventsDroppedException:
for e in l.sync():
print_event(e)
tablet.handle_event(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: sudo /dev/input/eventX".format(sys.argv[0]))
print(" $ sudo evtest")
print("can help you to know which file to use.")
sys.exit(1)
main(sys.argv)
add a comment |
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, but BTN_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:
$ chmod +x combine_both.py
$ sudo pip3 install libevdev
Then, check out the available input devices:
$ xinput list
This should give you several entries, among them one entry like
VEIKK.INC A30 Mouse id=12 [slave pointer (2)]
Note the id (noted as <id veikk> later), as well as the id of the virtual core pointer <id core>:
Virtual core pointer id=2 [master pointer (3)]
Then, you need to know which /dev/input/eventX file to pick, the easiest is to run sudo evtest and read the name of the file corresponding to the VEIKK.INC A30 Mouse. Then run the script with this file in argument like in:
sudo ./combine_both.py /dev/input/event7
This script should output stuff when you try to click/move on the device. Moreover xinput list should also list this device with name Tablet alone Pen (0) with an id <id fake tablet>, and xinput test <id fake tablet> should give you something like (note the 3 columns for x/y/pressure):
$ xinput test 21
[...]
motion a[0]=4151295 a[1]=4151295 a[2]=241
motion a[0]=4060671 a[1]=4060671 a[2]=226
motion a[0]=3969535 a[1]=3969535 a[2]=211
motion a[0]=3878399 a[1]=3878399 a[2]=196
motion a[0]=3787775 a[1]=3787775 a[2]=181
motion a[0]=3696639 a[1]=3696639 a[2]=166
motion a[0]=3605503 a[1]=3605503 a[2]=151
motion a[0]=3514879 a[1]=3514879 a[2]=137
motion a[0]=3423743 a[1]=3423743 a[2]=122
motion a[0]=3332607 a[1]=3332607 a[2]=107
motion a[0]=3241983 a[1]=3241983 a[2]=92
motion a[0]=3150847 a[1]=3150847 a[2]=77
motion a[0]=3059711 a[1]=3059711 a[2]=62
motion a[0]=2969087 a[1]=2969087 a[2]=47
motion a[0]=2877951 a[1]=2877951 a[2]=32
motion a[0]=2650623 a[1]=2650623 a[2]=17
button release 1
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
xinput float <id veikk>
and when you are done you can reattach the real device with
xinput reattach <id veikk> <id core>
In gimp, don't forget to setup Tablet alone Pen (0) with mode=screen in Edit/input devices, and make sure the real tablet VEIKK.INC A30 Mouse is disabled. Finally, chose a good dynamic, like Pencil Generic to test!
Enjoy! (and I'll let you know if I write the C driver at some points)
Script:
#!/usr/bin/env python3
import sys
import libevdev
import datetime
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))
class Tablet():
def __init__(self, tablet_name):
self.tablet_name = tablet_name
def __enter__(self):
self.dev = libevdev.Device()
self.dev.name = "Tablet alone"
### NB: all the following information needs to be enabled
### in order to recognize the device as a tablet.
# Say that the device will send "absolute" values
self.dev.enable(libevdev.INPUT_PROP_DIRECT)
# Say that we are using the pen (not the erasor), and should be set to 1 when we are at proximity to the device.
# See http://www.infradead.org/~mchehab/kernel_docs_pdf/linux-input.pdf page 9 (=13) and guidelines page 12 (=16), or the https://github.com/linuxwacom/input-wacom/blob/master/4.5/wacom_w8001.c (rdy=proximity)
self.dev.enable(libevdev.EV_KEY.BTN_TOOL_PEN)
self.dev.enable(libevdev.EV_KEY.BTN_TOOL_RUBBER)
# Click
self.dev.enable(libevdev.EV_KEY.BTN_TOUCH)
# Press button 1 on pen
self.dev.enable(libevdev.EV_KEY.BTN_STYLUS)
# Press button 2 on pen, see great doc
self.dev.enable(libevdev.EV_KEY.BTN_STYLUS2)
# Send absolute X coordinate
self.dev.enable(libevdev.EV_ABS.ABS_X,
libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
# Send absolute Y coordinate
self.dev.enable(libevdev.EV_ABS.ABS_Y,
libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
# Send absolute pressure
self.dev.enable(libevdev.EV_ABS.ABS_PRESSURE,
libevdev.InputAbsInfo(minimum=0, maximum=8191))
# Use to confirm that we finished to send the informations
# (to be sent after every burst of information, otherwise
# the kernel does not proceed the information)
self.dev.enable(libevdev.EV_SYN.SYN_REPORT)
# Report buffer overflow
self.dev.enable(libevdev.EV_SYN.SYN_DROPPED)
self.uinput = self.dev.create_uinput_device()
print("New device at ()".format(self.uinput.devnode, self.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. And it
# will never be detected in the futur ;-)
time.sleep(1)
# self.simulate_first_click()
self.reset_state()
return self
def __exit__(self, type, value, traceback):
pass
def reset_state(self):
self.is_away = True
self.is_touching = False
self.pressed_button_1 = False
self.pressed_button_2 = False
self.lastmodif = datetime.datetime.now()
def send_events(self, events, is_away=False):
self.lastmodif = datetime.datetime.now()
self.is_away = is_away
self.uinput.send_events(events)
def simulate_first_click(self):
"""Useful only the first time to make sure
xinput detected the input"""
# Reports that the PEN is close to the surface
# Important to make sure xinput can detect (and list)
# the pen. Otherwise, it won't write anything in gimp.
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=1),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
# Says that the pen it out of range of the tablet. Useful
# to make sure you can move your mouse, and to avoid
# strange things during the first draw.
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=0),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def send_state_no_pos(self, is_away=False):
self.lastmodif = datetime.datetime.now()
self.is_away = is_away
print("Away: , Touching: ".format(self.is_away, self.is_touching))
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=1 if self.is_touching else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=1 if not self.is_away else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS,
value=1 if self.pressed_button_1 else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2,
value=1 if self.pressed_button_2 else 0),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def touch_press(self):
self.is_touching = True
self.send_state_no_pos()
def touch_release(self):
self.is_touching = False
self.send_state_no_pos()
def button_1_press(self):
self.pressed_button_1 = True
self.send_state_no_pos()
def button_1_release(self):
self.pressed_button_1 = False
self.send_state_no_pos()
def button_2_press(self):
self.pressed_button_2 = True
self.send_state_no_pos()
def button_2_release(self):
self.pressed_button_2 = False
self.send_state_no_pos()
def move_x(self, abs_x):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_X,
value=abs_x),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def move_y(self, abs_y):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_Y,
value=abs_y),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def change_pressure(self, pressure):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_PRESSURE,
value=pressure),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def handle_event(self, e):
if e.matches(libevdev.EV_ABS.ABS_PRESSURE):
self.change_pressure(e.value)
elif e.matches(libevdev.EV_ABS.ABS_X):
self.move_x(e.value)
elif e.matches(libevdev.EV_ABS.ABS_Y):
self.move_y(e.value)
elif e.matches(libevdev.EV_KEY.BTN_LEFT):
if e.value == 1:
self.touch_press()
else:
self.touch_release()
elif e.matches(libevdev.EV_SYN.SYN_REPORT):
pass
else:
print("Unkown event:")
print_event(e)
def main(args):
path = args[1]
try:
with Tablet("Tablet alone") as tablet:
### Read the events from real graphics tablet
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:
print_event(e)
tablet.handle_event(e)
except libevdev.EventsDroppedException:
for e in l.sync():
print_event(e)
tablet.handle_event(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: sudo /dev/input/eventX".format(sys.argv[0]))
print(" $ sudo evtest")
print("can help you to know which file to use.")
sys.exit(1)
main(sys.argv)
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, but BTN_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:
$ chmod +x combine_both.py
$ sudo pip3 install libevdev
Then, check out the available input devices:
$ xinput list
This should give you several entries, among them one entry like
VEIKK.INC A30 Mouse id=12 [slave pointer (2)]
Note the id (noted as <id veikk> later), as well as the id of the virtual core pointer <id core>:
Virtual core pointer id=2 [master pointer (3)]
Then, you need to know which /dev/input/eventX file to pick, the easiest is to run sudo evtest and read the name of the file corresponding to the VEIKK.INC A30 Mouse. Then run the script with this file in argument like in:
sudo ./combine_both.py /dev/input/event7
This script should output stuff when you try to click/move on the device. Moreover xinput list should also list this device with name Tablet alone Pen (0) with an id <id fake tablet>, and xinput test <id fake tablet> should give you something like (note the 3 columns for x/y/pressure):
$ xinput test 21
[...]
motion a[0]=4151295 a[1]=4151295 a[2]=241
motion a[0]=4060671 a[1]=4060671 a[2]=226
motion a[0]=3969535 a[1]=3969535 a[2]=211
motion a[0]=3878399 a[1]=3878399 a[2]=196
motion a[0]=3787775 a[1]=3787775 a[2]=181
motion a[0]=3696639 a[1]=3696639 a[2]=166
motion a[0]=3605503 a[1]=3605503 a[2]=151
motion a[0]=3514879 a[1]=3514879 a[2]=137
motion a[0]=3423743 a[1]=3423743 a[2]=122
motion a[0]=3332607 a[1]=3332607 a[2]=107
motion a[0]=3241983 a[1]=3241983 a[2]=92
motion a[0]=3150847 a[1]=3150847 a[2]=77
motion a[0]=3059711 a[1]=3059711 a[2]=62
motion a[0]=2969087 a[1]=2969087 a[2]=47
motion a[0]=2877951 a[1]=2877951 a[2]=32
motion a[0]=2650623 a[1]=2650623 a[2]=17
button release 1
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
xinput float <id veikk>
and when you are done you can reattach the real device with
xinput reattach <id veikk> <id core>
In gimp, don't forget to setup Tablet alone Pen (0) with mode=screen in Edit/input devices, and make sure the real tablet VEIKK.INC A30 Mouse is disabled. Finally, chose a good dynamic, like Pencil Generic to test!
Enjoy! (and I'll let you know if I write the C driver at some points)
Script:
#!/usr/bin/env python3
import sys
import libevdev
import datetime
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))
class Tablet():
def __init__(self, tablet_name):
self.tablet_name = tablet_name
def __enter__(self):
self.dev = libevdev.Device()
self.dev.name = "Tablet alone"
### NB: all the following information needs to be enabled
### in order to recognize the device as a tablet.
# Say that the device will send "absolute" values
self.dev.enable(libevdev.INPUT_PROP_DIRECT)
# Say that we are using the pen (not the erasor), and should be set to 1 when we are at proximity to the device.
# See http://www.infradead.org/~mchehab/kernel_docs_pdf/linux-input.pdf page 9 (=13) and guidelines page 12 (=16), or the https://github.com/linuxwacom/input-wacom/blob/master/4.5/wacom_w8001.c (rdy=proximity)
self.dev.enable(libevdev.EV_KEY.BTN_TOOL_PEN)
self.dev.enable(libevdev.EV_KEY.BTN_TOOL_RUBBER)
# Click
self.dev.enable(libevdev.EV_KEY.BTN_TOUCH)
# Press button 1 on pen
self.dev.enable(libevdev.EV_KEY.BTN_STYLUS)
# Press button 2 on pen, see great doc
self.dev.enable(libevdev.EV_KEY.BTN_STYLUS2)
# Send absolute X coordinate
self.dev.enable(libevdev.EV_ABS.ABS_X,
libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
# Send absolute Y coordinate
self.dev.enable(libevdev.EV_ABS.ABS_Y,
libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
# Send absolute pressure
self.dev.enable(libevdev.EV_ABS.ABS_PRESSURE,
libevdev.InputAbsInfo(minimum=0, maximum=8191))
# Use to confirm that we finished to send the informations
# (to be sent after every burst of information, otherwise
# the kernel does not proceed the information)
self.dev.enable(libevdev.EV_SYN.SYN_REPORT)
# Report buffer overflow
self.dev.enable(libevdev.EV_SYN.SYN_DROPPED)
self.uinput = self.dev.create_uinput_device()
print("New device at ()".format(self.uinput.devnode, self.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. And it
# will never be detected in the futur ;-)
time.sleep(1)
# self.simulate_first_click()
self.reset_state()
return self
def __exit__(self, type, value, traceback):
pass
def reset_state(self):
self.is_away = True
self.is_touching = False
self.pressed_button_1 = False
self.pressed_button_2 = False
self.lastmodif = datetime.datetime.now()
def send_events(self, events, is_away=False):
self.lastmodif = datetime.datetime.now()
self.is_away = is_away
self.uinput.send_events(events)
def simulate_first_click(self):
"""Useful only the first time to make sure
xinput detected the input"""
# Reports that the PEN is close to the surface
# Important to make sure xinput can detect (and list)
# the pen. Otherwise, it won't write anything in gimp.
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=1),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
# Says that the pen it out of range of the tablet. Useful
# to make sure you can move your mouse, and to avoid
# strange things during the first draw.
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=0),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def send_state_no_pos(self, is_away=False):
self.lastmodif = datetime.datetime.now()
self.is_away = is_away
print("Away: , Touching: ".format(self.is_away, self.is_touching))
self.uinput.send_events([
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
value=1 if self.is_touching else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
value=1 if not self.is_away else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS,
value=1 if self.pressed_button_1 else 0),
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2,
value=1 if self.pressed_button_2 else 0),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def touch_press(self):
self.is_touching = True
self.send_state_no_pos()
def touch_release(self):
self.is_touching = False
self.send_state_no_pos()
def button_1_press(self):
self.pressed_button_1 = True
self.send_state_no_pos()
def button_1_release(self):
self.pressed_button_1 = False
self.send_state_no_pos()
def button_2_press(self):
self.pressed_button_2 = True
self.send_state_no_pos()
def button_2_release(self):
self.pressed_button_2 = False
self.send_state_no_pos()
def move_x(self, abs_x):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_X,
value=abs_x),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def move_y(self, abs_y):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_Y,
value=abs_y),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def change_pressure(self, pressure):
self.send_events([
libevdev.InputEvent(libevdev.EV_ABS.ABS_PRESSURE,
value=pressure),
libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
value=0),
])
def handle_event(self, e):
if e.matches(libevdev.EV_ABS.ABS_PRESSURE):
self.change_pressure(e.value)
elif e.matches(libevdev.EV_ABS.ABS_X):
self.move_x(e.value)
elif e.matches(libevdev.EV_ABS.ABS_Y):
self.move_y(e.value)
elif e.matches(libevdev.EV_KEY.BTN_LEFT):
if e.value == 1:
self.touch_press()
else:
self.touch_release()
elif e.matches(libevdev.EV_SYN.SYN_REPORT):
pass
else:
print("Unkown event:")
print_event(e)
def main(args):
path = args[1]
try:
with Tablet("Tablet alone") as tablet:
### Read the events from real graphics tablet
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:
print_event(e)
tablet.handle_event(e)
except libevdev.EventsDroppedException:
for e in l.sync():
print_event(e)
tablet.handle_event(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: sudo /dev/input/eventX".format(sys.argv[0]))
print(" $ sudo evtest")
print("can help you to know which file to use.")
sys.exit(1)
main(sys.argv)
answered yesterday
tobiasBoratobiasBora
264312
264312
add a comment |
add a comment |
Thanks for contributing an answer to Unix & Linux Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f507687%2fgraphic-tablet-veikk-pressure-sensitivity-on-linux%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
-tablet