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)
Best Answer
also check out bebop's answer here: https://askubuntu.com/questions/839161/limit-a-graphics-tablet-to-one-monitor
its a longer version with some extras. For the QUICK AND DIRTY VERSION:
Found this, it fixed my issue which sounded similar to yours.
https://forum.kde.org/viewtopic.php?f=139&t=125532
This is the code for HUION New 1060 for example: HID 256c:006e Pad
First type:
blah blah: HID 256c:006e Pad id=17 there might/should be two devices, Pad and Pen. Then do:
look for the ones showing 'connected' like HDMI-A-0 connected and maybe DisplayPort-2 connected
Then you tell xinput to stick the id's to the screen you want Krita or Photoshop on, such as if you were using a HDMI port to main screen:
that was mine. it resets after a reboot. thanks to that user timotimo!