Linux – VEIKK Graphic Tablet Pressure Sensitivity

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:
enter image description here

Edit2: hum, it seems that the device that sends the pressure can be read in sudo cat /dev/hidraw0 as the patterns differs depending the the pressure I apply. I don't know how to read this binary though ^^ If I can, maybe I can use uinput to map it to a new device? ^^ Note that /dev/input contains files like mouseX, eventY, and mouse1 is correlated with the tablet, but not with the pressure, and the only file in this folder that displays lot's of information linked with the tablet is the file /dev/input/by-id/usb-VEIKK.INC_A30_0000001-event-mouse. But it's less clear that the patterns match the pressure, too many information are sent here. If you know how to parse them let me know!

Edit3:
So I don't yet have a driver, but at least it seems to be easy to read the input from the device raw communication. I made this python script as a proof of concept:

#!/usr/bin/env python3
import struct
PRINT_TIMESTAMP = True
# Open the file in the read-binary mode
f = open("/dev/input/by-id/usb-VEIKK.INC_A30_0000001-event-mouse", "rb" )

while 1:
    data = f.read(24)
    # print struct.unpack('4IHHI',data)
    ###### FORMAT = ( Time Stamp_INT , 0 , Time Stamp_DEC , 0 , 
    ######   type , code ( key pressed ) , value (press/release) )
    time_int, _, time_dec, _, ev_type, ev_code, ev_val = struct.unpack('4IHHI',data)
    t = (ev_type, ev_code)
    if ((t == (0,0) and ev_val == 0)
        or (t == (4, 4) and ev_val >= 589825 and ev_val <= 589827)):
      # Redundant as it's for normal/bottom/top clicks
      # (same code for press/release), or just garbage 0,0,0
        continue
    if PRINT_TIMESTAMP:
        print("[{:.2f}] ".format(time_int + time_dec/1e6),
              end="", flush=True)
    if t == (3,0):
        print("Pos x: {} ({:.2f}%)".format(ev_val, 100*ev_val/32767), flush=True)
    elif t == (3,1):
        print("Pos y: {} ({:.2f}%)".format(ev_val, 100*ev_val/32767), flush=True)
    elif t == (3,24):
        print("Pression: {} ({:.2f}%)".format(ev_val, 100*ev_val/8191), flush=True)
    elif t == (1,272):
        print("Normal click ({})".format("press" if ev_val else "release"), flush=True)
    elif t == (1,273):
        print("click button 2 (bottom) ({})".format("press" if ev_val else "release"), flush=True)
    elif t == (1,274):
        print("click button 3 (top) ({})".format("press" if ev_val else "release"), flush=True)
    else:
        print("Unknow: type={}, code={}, value={}".format(ev_type, ev_code, ev_val), flush=True)

Démo:

[1553182025.55] Pos y: 11458 (34.97%)
[1553182025.55] Pos x: 14310 (43.67%)
[1553182025.56] Pos x: 14314 (43.68%)
[1553182025.56] Pos x: 14318 (43.70%)
[1553182025.57] Pos x: 14321 (43.71%)
[1553182025.57] Normal click (press)
[1553182025.57] Pos x: 14323 (43.71%)
[1553182025.57] Pression: 1122 (13.70%)
[1553182025.57] Pos x: 14326 (43.72%)
[1553182025.57] Pos y: 11466 (34.99%)
[1553182025.57] Pression: 1260 (15.38%)
[1553182025.58] Pos x: 14329 (43.73%)
[1553182025.58] Pression: 1337 (16.32%)
[1553182025.58] Pos x: 14330 (43.73%)
[1553182025.58] Pos y: 11494 (35.08%)
[1553182025.58] Pression: 1515 (18.50%)
[1553182025.59] Pos y: 11506 (35.11%)
[1553182025.59] Pression: 1687 (20.60%)
[1553182025.59] Pos y: 11517 (35.15%)
[1553182025.59] Pression: 1689 (20.62%)
[1553182025.59] Pos y: 11529 (35.18%)
[1553182025.59] Pression: 1789 (21.84%)
[1553182025.60] Pos y: 11536 (35.21%)
[1553182025.60] Pression: 1829 (22.33%)
[1553182025.60] Pos y: 11542 (35.22%)
[1553182025.60] Pression: 1907 (23.28%)
[1553182025.61] Pression: 2031 (24.80%)
[1553182025.61] Pos y: 11549 (35.25%)
[1553182025.61] Pression: 2140 (26.13%)

Edit 4:
Amazing page: https://digimend.github.io/support/howto/trbl/locating_failure/ However, everything works here… except at the very last step when I want to test it. I tried to test with MyPaint, and it does not detect pressure.

I also tried to do my own code that basically copies the input from the event file into a new device like that:

#!/usr/bin/env python3
import sys
import libevdev
import time

def print_capabilities(l):
    v = l.driver_version
    print("Input driver version is {}.{}.{}".format(v >> 16, (v >> 8) & 0xff, v & 0xff))
    id = l.id
    print("Input device ID: bus {:#x} vendor {:#x} product {:#x} version {:#x}".format(
        id["bustype"],
        id["vendor"],
        id["product"],
        id["version"],
    ))
    print("Input device name: {}".format(l.name))
    print("Supported events:")

    for t, cs in l.evbits.items():
        print("  Event type {} ({})".format(t.value, t.name))

        for c in cs:
            if t in [libevdev.EV_LED, libevdev.EV_SND, libevdev.EV_SW]:
                v = l.value[c]
                print("    Event code {} ({}) state {}".format(c.value, c.name, v))
            else:
                print("    Event code {} ({})".format(c.value, c.name))

            if t == libevdev.EV_ABS:
                a = l.absinfo[c]
                print("       {:10s} {:6d}".format('Value', a.value))
                print("       {:10s} {:6d}".format('Minimum', a.minimum))
                print("       {:10s} {:6d}".format('Maximum', a.maximum))
                print("       {:10s} {:6d}".format('Fuzz', a.fuzz))
                print("       {:10s} {:6d}".format('Flat', a.flat))
                print("       {:10s} {:6d}".format('Resolution', a.resolution))

    print("Properties:")
    for p in l.properties:
        print("  Property type {} ({})".format(p.value, p.name))


def print_event(e):
        print("Event: time {}.{:06d}, ".format(e.sec, e.usec), end='')
        if e.matches(libevdev.EV_SYN):
            if e.matches(libevdev.EV_SYN.SYN_MT_REPORT):
                print("++++++++++++++ {} ++++++++++++".format(e.code.name))
            elif e.matches(libevdev.EV_SYN.SYN_DROPPED):
                print(">>>>>>>>>>>>>> {} >>>>>>>>>>>>".format(e.code.name))
            else:
                print("-------------- {} ------------".format(e.code.name))
        else:
            print("type {:02x} {} code {:03x} {:20s} value {:4d}".format(e.type.value, e.type.name, e.code.value, e.code.name, e.value))


def main(args):
    path = args[1]
    dev = libevdev.Device()
    dev.name = "Combined Both Devices"
    dev.enable(libevdev.EV_ABS.ABS_X,
               libevdev.InputAbsInfo(minimum=0, maximum=32767))
    dev.enable(libevdev.EV_ABS.ABS_Y,
               libevdev.InputAbsInfo(minimum=0, maximum=32767))
    dev.enable(libevdev.EV_ABS.ABS_Z,
               libevdev.InputAbsInfo(minimum=0, maximum=8191))
    dev.enable(libevdev.EV_ABS.ABS_0B,
               libevdev.InputAbsInfo(minimum=0, maximum=8191))
    dev.enable(libevdev.EV_ABS.ABS_DISTANCE,
               libevdev.InputAbsInfo(minimum=0, maximum=8191))
    dev.enable(libevdev.EV_ABS.ABS_PRESSURE,
               libevdev.InputAbsInfo(minimum=0, maximum=8191))
    dev.enable(libevdev.EV_MSC.MSC_SCAN)
    dev.enable(libevdev.EV_KEY.BTN_LEFT)
    dev.enable(libevdev.EV_KEY.BTN_RIGHT)
    dev.enable(libevdev.EV_KEY.BTN_MIDDLE)
    dev.enable(libevdev.EV_KEY.BTN_TOUCH)
    dev.enable(libevdev.EV_SYN.SYN_REPORT)
    dev.enable(libevdev.EV_SYN.SYN_CONFIG)
    dev.enable(libevdev.EV_SYN.SYN_MT_REPORT)
    dev.enable(libevdev.EV_SYN.SYN_DROPPED)
    dev.enable(libevdev.EV_SYN.SYN_04)
    dev.enable(libevdev.EV_SYN.SYN_05)
    dev.enable(libevdev.EV_SYN.SYN_06)
    dev.enable(libevdev.EV_SYN.SYN_07)
    dev.enable(libevdev.EV_SYN.SYN_08)
    dev.enable(libevdev.EV_SYN.SYN_09)
    dev.enable(libevdev.EV_SYN.SYN_0A)
    dev.enable(libevdev.EV_SYN.SYN_0B)
    dev.enable(libevdev.EV_SYN.SYN_0C)
    dev.enable(libevdev.EV_SYN.SYN_0D)
    dev.enable(libevdev.EV_SYN.SYN_0E)
    dev.enable(libevdev.EV_SYN.SYN_MAX)
    try:
        uinput = dev.create_uinput_device()
        print("New device at {} ({})".format(uinput.devnode, uinput.syspath))
        # Sleep for a bit so udev, libinput, Xorg, Wayland, ...
        # all have had a chance to see the device and initialize
        # it. Otherwise the event will be sent by the kernel but
        # nothing is ready to listen to the device yet.
        time.sleep(1)

        with open(path, "rb") as fd:
            l = libevdev.Device(fd)
            print_capabilities(l)
            print("################################\n"
                  "#      Waiting for events      #\n"
                  "################################")
            while True:
                try:
                    ev = l.events()
                    for e in ev:
                        uinput.send_events([e])
                        print_event(e)
                        if e.matches(libevdev.EV_ABS.ABS_PRESSURE):
                            print("Pressure! Will send another packeton Z axis!")
                            uinput.send_events([libevdev.InputEvent(libevdev.EV_ABS.ABS_Z, e.value)])
                            uinput.send_events([libevdev.InputEvent(libevdev.EV_ABS.ABS_0B, e.value)])
                            uinput.send_events([libevdev.InputEvent(libevdev.EV_ABS.ABS_DISTANCE, e.value)])

                except libevdev.EventsDroppedException:
                    for e in l.sync():
                        print_event(e)
                        uinput.send_events([e])
    except KeyboardInterrupt:
        pass
    except IOError as e:
        import errno
        if e.errno == errno.EACCES:
            print("Insufficient permissions to access {}".format(path))
        elif e.errno == errno.ENOENT:
            print("Device {} does not exist".format(path))
        else:
            raise e
    except OSError as e:
        print(e)


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: {} /dev/input/eventX".format(sys.argv[0]))
        sys.exit(1)
    main(sys.argv)

and I realized that it's more or less the same as the Veikk mouse, and it does not work better.

Best Answer

Youou!!! I managed to make a python script that gives me back the pressure on a new virtual device :-D Now I can use it with Gimp/Krita/... The only remaining part to do is to write a proper C driver and load it directly in the kernel... If you have some idea let me know!

Proof:

enter image description here

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)
Related Question