Linux tablet input

137 views
Skip to first unread message

Jesus Luis

unread,
Jul 29, 2016, 6:40:00 AM7/29/16
to pyglet-users
Arch Linux python-pyglet 1.2.4-3

Hello,

I'm trying to implement pen tablet pressure input for my application.
Here's some code:

import pyglet

window = pyglet.window.Window()
tablets = pyglet.input.get_tablets()

pyglet.app.run()


And here's the problem:


Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.5/site-packages/pyglet/input/x11_xinput_tablet.py", line 86, in get_tablets
    devices = get_devices(display)
  File "/usr/lib/python3.5/site-packages/pyglet/input/x11_xinput.py", line 332, in get_devices
    if not _have_xinput or not _check_extension(display):
  File "/usr/lib/python3.5/site-packages/pyglet/input/x11_xinput.py", line 325, in _check_extension
    ctypes.byref(first_error))
ctypes.ArgumentError: argument 2: <class 'TypeError'>: wrong type


Anybody know what I'm doing wrong?

Benjamin Moran

unread,
Jul 29, 2016, 11:45:15 AM7/29/16
to pyglet-users
It looks like you might have hit a bug in the crowd bindings, probably due to the new Python 2/3 dual compatible codebase.

Could you try running it again with Python2, and see if it works? If so, it should be easily fixable for Python 3. Let me know how it goes.

Jesus Luis

unread,
Jul 29, 2016, 1:05:34 PM7/29/16
to pyglet-users
Hey Benjamin,

It works on python2, but it doesn't find my Wacom tablet.

Python 2.7.12 (default, Jun 28 2016, 08:31:05)
[GCC 6.1.1 20160602] on linux2
Type "help", "copyright", "credits" or "license" for more information.

>>> import pyglet
>>> window = pyglet.window.Window()
>>> tablets = pyglet.input.get_tablets()
>>> tablets
[]

Benjamin Moran

unread,
Jul 30, 2016, 1:30:21 AM7/30/16
to pyglet-users
Jesus,

give the development branch of pyglet a try. It contains a fix for the ctypes crash on Python 3. I also extended the evdev stuff on Linux a bit to detect more joysticks, so it may work with your tablet as well. Could you give that a try and let me know the results?
(The quickest way to try out the development version would be to download the repo, and just copy the pyglet folder into your project).

If the crash is gone, but your tablet is still not detected by pyglet.input.get_tablets():
Start with pyglet.input.get_devices(), and see if your tablet shows up in there. If it does, great. You should be able to open the device, and try the device.get_controls() method to see what inputs on your tablet are being detected. Tablets (and joysticks) are just a subset of "devices" that have specific controls).

Let me know how far along you get. Hopefully your tablet will work with the dev branch, but if not it should be fixable.

Jesus Luis

unread,
Jul 30, 2016, 2:19:07 AM7/30/16
to pyglet-users
Great job, it's working!

But only with get_devices(), get_tablets() still returns an empty list.


Python 3.5.2 (default, Jun 28 2016, 08:46:01)
[GCC 6.1.1 20160602] on linux

Type "help", "copyright", "credits" or "license" for more information.
>>> import pyglet
>>> window = pyglet.window.Window()
>>> tablets = pyglet.input.get_tablets()
>>> tablets
[]
>>> devices = pyglet.input.get_devices()
>>> devices
[XInputDevice(name=Virtual core pointer), XInputDevice(name=Virtual core keyboard), XInputDevice(name=Virtual core XTEST pointer), XInputDevice(name=Virtual core XTEST keyboard), XInputDevice(name=Power Button), XInputDevice(name=Video Bus), XInputDevice(name=Power Button), XInputDevice(name=USB Keyboard), XInputDevice(name=USB Keyboard), XInputDevice(name=Logitech USB Receiver), XInputDevice(name=Logitech USB Receiver), XInputDevice(name=Wacom Cintiq 13HD Pen stylus), XInputDevice(name=Wacom Cintiq 13HD Pad pad), XInputDevice(name=Logitech USB Receiver), XInputDevice(name=Wacom Cintiq 13HD Pen eraser), XInputDevice(name=00:08:E0:4B:4C:80)]
>>> devices[11].get_controls()
[Button(name=button0, raw_name=None), Button(name=button1, raw_name=None), Button(name=button2, raw_name=None), Button(name=button3, raw_name=None), Button(name=button4, raw_name=None), Button(name=button5, raw_name=None), Button(name=button6, raw_name=None), Button(name=key0, raw_name=None), Button(name=key1, raw_name=None), Button(name=key2, raw_name=None), Button(name=key3, raw_name=None), Button(name=key4, raw_name=None), Button(name=key5, raw_name=None), Button(name=key6, raw_name=None), Button(name=key7, raw_name=None), Button(name=key8, raw_name=None), Button(name=key9, raw_name=None), Button(name=key10, raw_name=None), Button(name=key11, raw_name=None), Button(name=key12, raw_name=None), Button(name=key13, raw_name=None), Button(name=key14, raw_name=None), Button(name=key15, raw_name=None), Button(name=key16, raw_name=None), Button(name=key17, raw_name=None), Button(name=key18, raw_name=None), Button(name=key19, raw_name=None), Button(name=key20, raw_name=None), Button(name=key21, raw_name=None), Button(name=key22, raw_name=None), Button(name=key23, raw_name=None), Button(name=key24, raw_name=None), Button(name=key25, raw_name=None), Button(name=key26, raw_name=None), Button(name=key27, raw_name=None), Button(name=key28, raw_name=None), Button(name=key29, raw_name=None), Button(name=key30, raw_name=None), Button(name=key31, raw_name=None), Button(name=key32, raw_name=None), Button(name=key33, raw_name=None), Button(name=key34, raw_name=None), Button(name=key35, raw_name=None), Button(name=key36, raw_name=None), Button(name=key37, raw_name=None), Button(name=key38, raw_name=None), Button(name=key39, raw_name=None), Button(name=key40, raw_name=None), Button(name=key41, raw_name=None), Button(name=key42, raw_name=None), Button(name=key43, raw_name=None), Button(name=key44, raw_name=None), Button(name=key45, raw_name=None), Button(name=key46, raw_name=None), Button(name=key47, raw_name=None), Button(name=key48, raw_name=None), Button(name=key49, raw_name=None), Button(name=key50, raw_name=None), Button(name=key51, raw_name=None), Button(name=key52, raw_name=None), Button(name=key53, raw_name=None), Button(name=key54, raw_name=None), Button(name=key55, raw_name=None), Button(name=key56, raw_name=None), Button(name=key57, raw_name=None), Button(name=key58, raw_name=None), Button(name=key59, raw_name=None), Button(name=key60, raw_name=None), Button(name=key61, raw_name=None), Button(name=key62, raw_name=None), Button(name=key63, raw_name=None), Button(name=key64, raw_name=None), Button(name=key65, raw_name=None), Button(name=key66, raw_name=None), Button(name=key67, raw_name=None), Button(name=key68, raw_name=None), Button(name=key69, raw_name=None), Button(name=key70, raw_name=None), Button(name=key71, raw_name=None), Button(name=key72, raw_name=None), Button(name=key73, raw_name=None), Button(name=key74, raw_name=None), Button(name=key75, raw_name=None), Button(name=key76, raw_name=None), Button(name=key77, raw_name=None), Button(name=key78, raw_name=None), Button(name=key79, raw_name=None), Button(name=key80, raw_name=None), Button(name=key81, raw_name=None), Button(name=key82, raw_name=None), Button(name=key83, raw_name=None), Button(name=key84, raw_name=None), Button(name=key85, raw_name=None), Button(name=key86, raw_name=None), Button(name=key87, raw_name=None), Button(name=key88, raw_name=None), Button(name=key89, raw_name=None), Button(name=key90, raw_name=None), Button(name=key91, raw_name=None), Button(name=key92, raw_name=None), Button(name=key93, raw_name=None), Button(name=key94, raw_name=None), Button(name=key95, raw_name=None), Button(name=key96, raw_name=None), Button(name=key97, raw_name=None), Button(name=key98, raw_name=None), Button(name=key99, raw_name=None), Button(name=key100, raw_name=None), Button(name=key101, raw_name=None), Button(name=key102, raw_name=None), Button(name=key103, raw_name=None), Button(name=key104, raw_name=None), Button(name=key105, raw_name=None), Button(name=key106, raw_name=None), Button(name=key107, raw_name=None), Button(name=key108, raw_name=None), Button(name=key109, raw_name=None), Button(name=key110, raw_name=None), Button(name=key111, raw_name=None), Button(name=key112, raw_name=None), Button(name=key113, raw_name=None), Button(name=key114, raw_name=None), Button(name=key115, raw_name=None), Button(name=key116, raw_name=None), Button(name=key117, raw_name=None), Button(name=key118, raw_name=None), Button(name=key119, raw_name=None), Button(name=key120, raw_name=None), Button(name=key121, raw_name=None), Button(name=key122, raw_name=None), Button(name=key123, raw_name=None), Button(name=key124, raw_name=None), Button(name=key125, raw_name=None), Button(name=key126, raw_name=None), Button(name=key127, raw_name=None), Button(name=key128, raw_name=None), Button(name=key129, raw_name=None), Button(name=key130, raw_name=None), Button(name=key131, raw_name=None), Button(name=key132, raw_name=None), Button(name=key133, raw_name=None), Button(name=key134, raw_name=None), Button(name=key135, raw_name=None), Button(name=key136, raw_name=None), Button(name=key137, raw_name=None), Button(name=key138, raw_name=None), Button(name=key139, raw_name=None), Button(name=key140, raw_name=None), Button(name=key141, raw_name=None), Button(name=key142, raw_name=None), Button(name=key143, raw_name=None), Button(name=key144, raw_name=None), Button(name=key145, raw_name=None), Button(name=key146, raw_name=None), Button(name=key147, raw_name=None), Button(name=key148, raw_name=None), Button(name=key149, raw_name=None), Button(name=key150, raw_name=None), Button(name=key151, raw_name=None), Button(name=key152, raw_name=None), Button(name=key153, raw_name=None), Button(name=key154, raw_name=None), Button(name=key155, raw_name=None), Button(name=key156, raw_name=None), Button(name=key157, raw_name=None), Button(name=key158, raw_name=None), Button(name=key159, raw_name=None), Button(name=key160, raw_name=None), Button(name=key161, raw_name=None), Button(name=key162, raw_name=None), Button(name=key163, raw_name=None), Button(name=key164, raw_name=None), Button(name=key165, raw_name=None), Button(name=key166, raw_name=None), Button(name=key167, raw_name=None), Button(name=key168, raw_name=None), Button(name=key169, raw_name=None), Button(name=key170, raw_name=None), Button(name=key171, raw_name=None), Button(name=key172, raw_name=None), Button(name=key173, raw_name=None), Button(name=key174, raw_name=None), Button(name=key175, raw_name=None), Button(name=key176, raw_name=None), Button(name=key177, raw_name=None), Button(name=key178, raw_name=None), Button(name=key179, raw_name=None), Button(name=key180, raw_name=None), Button(name=key181, raw_name=None), Button(name=key182, raw_name=None), Button(name=key183, raw_name=None), Button(name=key184, raw_name=None), Button(name=key185, raw_name=None), Button(name=key186, raw_name=None), Button(name=key187, raw_name=None), Button(name=key188, raw_name=None), Button(name=key189, raw_name=None), Button(name=key190, raw_name=None), Button(name=key191, raw_name=None), Button(name=key192, raw_name=None), Button(name=key193, raw_name=None), Button(name=key194, raw_name=None), Button(name=key195, raw_name=None), Button(name=key196, raw_name=None), Button(name=key197, raw_name=None), Button(name=key198, raw_name=None), Button(name=key199, raw_name=None), Button(name=key200, raw_name=None), Button(name=key201, raw_name=None), Button(name=key202, raw_name=None), Button(name=key203, raw_name=None), Button(name=key204, raw_name=None), Button(name=key205, raw_name=None), Button(name=key206, raw_name=None), Button(name=key207, raw_name=None), Button(name=key208, raw_name=None), Button(name=key209, raw_name=None), Button(name=key210, raw_name=None), Button(name=key211, raw_name=None), Button(name=key212, raw_name=None), Button(name=key213, raw_name=None), Button(name=key214, raw_name=None), Button(name=key215, raw_name=None), Button(name=key216, raw_name=None), Button(name=key217, raw_name=None), Button(name=key218, raw_name=None), Button(name=key219, raw_name=None), Button(name=key220, raw_name=None), Button(name=key221, raw_name=None), Button(name=key222, raw_name=None), Button(name=key223, raw_name=None), Button(name=key224, raw_name=None), Button(name=key225, raw_name=None), Button(name=key226, raw_name=None), Button(name=key227, raw_name=None), Button(name=key228, raw_name=None), Button(name=key229, raw_name=None), Button(name=key230, raw_name=None), Button(name=key231, raw_name=None), Button(name=key232, raw_name=None), Button(name=key233, raw_name=None), Button(name=key234, raw_name=None), Button(name=key235, raw_name=None), Button(name=key236, raw_name=None), Button(name=key237, raw_name=None), Button(name=key238, raw_name=None), Button(name=key239, raw_name=None), Button(name=key240, raw_name=None), Button(name=key241, raw_name=None), Button(name=key242, raw_name=None), Button(name=key243, raw_name=None), Button(name=key244, raw_name=None), Button(name=key245, raw_name=None), Button(name=key246, raw_name=None), Button(name=key247, raw_name=None), AbsoluteAxis(name=axis0, raw_name=None), AbsoluteAxis(name=axis1, raw_name=None), AbsoluteAxis(name=axis2, raw_name=None), AbsoluteAxis(name=axis3, raw_name=None), AbsoluteAxis(name=axis4, raw_name=None), AbsoluteAxis(name=axis5, raw_name=None), Button(name=proximity, raw_name=None)]

Benjamin Moran

unread,
Jul 30, 2016, 2:57:30 AM7/30/16
to pyglet-users
Great, at least we can say it's being detected OK.
I took a look at the pyglet.input.x11_xinput_tablet.py file, and it looks like this function might need some updating. Tablets seem to be created as a few separate devices, combined into one device. You can see it's looking for devices with the names below. Your tablet, instead, has device names like "Wacom Cintiq 13HD Pen stylus", etc.

Maybe a good test is to update this function with your actual device names, and see if it can detect and create the Tablet Device.

The real question is if the Wacom device names have changed because of the newer Linux kernel, or if only some devices have different names.



def get_tablets(display=None):
 
# Each cursor appears as a separate xinput device; find devices that look
 
# like Wacom tablet cursors and amalgamate them into a single tablet.
 cursors
= []
 devices
= get_devices(display)
 
for device in devices:
 
if device.name in ('stylus', 'cursor', 'eraser') and \
 len
(device.axes) >= 3:
 cursors
.append(XInputTabletCursor(device))

 
if cursors:
 
return [XInputTablet(cursors)]
 
return []

Jesus Luis

unread,
Jul 30, 2016, 3:40:36 AM7/30/16
to pyglet-users
Hi Benjamin,  I Made the changes you suggested:

def get_tablets(display=None):
   
# Each cursor appears as a separate xinput device; find devices that look
   
# like Wacom tablet cursors and amalgamate them into a single tablet.
    cursors
= []
    devices
= get_devices(display)
   
for device in devices:

       
if device.name in ('Wacom Cintiq 13HD Pen stylus',
                           
'Wacom Cintiq 13HD Pad pad',
                           
'Wacom Cintiq 13HD Pen eraser') and \

           len
(device.axes) >= 3:
            cursors
.append(XInputTabletCursor(device))

   
if cursors:
       
return [XInputTablet(cursors)]
   
return []


And here's the test code:


import pyglet

window
= pyglet.window.Window()


tablet
= pyglet.input.get_tablets()

print(tablet[0])
# <pyglet.input.x11_xinput_tablet.XInputTablet object at 0x7f89dea640f0>

tablet
[0].open(window)


@window.event
def on_motion(cursor, x, y, pressure):
   
print(pressure)


pyglet
.app.run()


No print on the on_motion event, and when I close the windo I get a Traceback:

Exception ignored in: <bound method BaseWindow.__del__ of XlibWindow(width=1, height=1)>

Traceback (most recent call last):
  File "/home/jesus/Code/Yeh/tablet_test/pyglet/pyglet/window/__init__.py", line 582, in __del__
  File "/home/jesus/Code/Yeh/tablet_test/pyglet/pyglet/window/xlib/__init__.py", line 477, in close
  File "/home/jesus/Code/Yeh/tablet_test/pyglet/pyglet/gl/xlib.py", line 344, in destroy
  File "/home/jesus/Code/Yeh/tablet_test/pyglet/pyglet/gl/base.py", line 334, in destroy
  File "/home/jesus/Code/Yeh/tablet_test/pyglet/pyglet/gl/xlib.py", line 334, in detach
  File "/home/jesus/Code/Yeh/tablet_test/pyglet/pyglet/gl/lib.py", line 97, in errcheck
  File "<frozen importlib._bootstrap>", line 969, in _find_and_load
  File "<frozen importlib._bootstrap>", line 954, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 887, in _find_spec
TypeError: 'NoneType' object is not iterable

--
You received this message because you are subscribed to a topic in the Google Groups "pyglet-users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pyglet-users/NnF2xH_5GSY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pyglet-users...@googlegroups.com.
To post to this group, send email to pyglet...@googlegroups.com.
Visit this group at https://groups.google.com/group/pyglet-users.
For more options, visit https://groups.google.com/d/optout.

Benjamin Moran

unread,
Jul 30, 2016, 4:35:11 AM7/30/16
to pyglet-users, je...@mountainof.horse
Ah, I get the "Exception Ignored...." message sometimes on Arch. It's good to know that it affects other people as well. Maybe I'll try to track that one down when I have time and submit a patch. In any case, you can ignore that one for now, as it's harmless. I'm not sure if this affects other Linux distributions, or maybe just caused by the newer Xorg version in Arch.

Anyway, do you get any feedback at all? Anything from printing cursor, x, y? If not, from this point, I would try to remove the "... and len(device.axis) >= 3"  from the method. Maybe some of the tablet devices have a matching name, but they don't have enough axis to be included in the "cursors" list?  Or, maybe just print the cursor list in the method to see if it's picking up all of the devices.
If that doesn't work, the next step is to make sure all of the Controls are being detected and created correctly. This would be done by looking though the raw devices, though that might be a little difficult.

If none of this works, I think I have a tablet at the office I can test for you. It's an older Wacom Bamboo, but at least I can confirm if that works or not.

-Ben

Jesus Luis

unread,
Jul 30, 2016, 4:58:27 AM7/30/16
to pyglet...@googlegroups.com
I get no feedback from the others either.
No feedback when I remove "... and len(device.axis) >= 3"

printing the cursor list gives me:
[XInputTabletCursor(Wacom Cintiq 13HD Pen stylus), XInputTabletCursor(Wacom Cintiq 13HD Pad pad), XInputTabletCursor(Wacom Cintiq 13HD Pen eraser)]

I'm not quite sure how to check if the controls are being detected and created correctly however, so no data on your forth option.

Benjamin Moran

unread,
Aug 1, 2016, 5:31:52 AM8/1/16
to pyglet-users, je...@mountainof.horse
I had a chance to try the Wacom Bamboo tablet today, and the "good" news is  that mine doesn't work either,  so the problem is not just with your tablet model.

In my case, the raw device names are "Wacom Bamboo Pen" and "Wacom Bamboo Pad". At the very least, the function needs to be fixed to properly detect the newer evdev tablet names. I don't know if any of the core pyglet devs are using tablets anymore, but maybe we can fix this.

Benjamin Moran

unread,
Aug 1, 2016, 6:23:42 AM8/1/16
to pyglet-users, je...@mountainof.horse
OK, I got this working on my Wacom Bamboo.  I updated the get_tablets method to work better at detecting more tablet names. I also noticed in the docs that the tablet.open(window) method returns a "canvas device", which you need to get your events from. I think that was the last bit you were missing.  I'll post two code snippets below. One is the updated method, and the second is a working example.

def get_tablets(display=None):
# Each cursor appears as a separate xinput device; find devices that look
# like Wacom tablet cursors and amalgamate them into a single tablet.
    valid_names = ('stylus', 'cursor', 'eraser', 'wacom', 'pen', 'pad')

cursors = []
devices = get_devices(display)
for device in devices:
        dev_name = device.name.lower().split()
if any(n in dev_name for n in valid_names) and len(device.axes) >= 3:

cursors.append(XInputTabletCursor(device))

if cursors:
return [XInputTablet(cursors)]
return []

And a working example:

import pyglet


window = pyglet.window.Window()
tablet = pyglet.input.get_tablets()[0]
tablet_canvas = tablet.open(window)


@tablet_canvas.event
def on_motion(cursor_name, x, y, pressure, what, huh):
print("X: {}, Y: {}, Pressure: {}".format(x, y, pressure))


if __name__ == "__main__":
window.clear()
pyglet.app.run()

Jesus Luis

unread,
Aug 1, 2016, 8:48:31 AM8/1/16
to pyglet-users, je...@mountainof.horse
Ben. It works!

This is fantastic, thanks for working it all out.

Benjamin Moran

unread,
Aug 1, 2016, 10:57:21 PM8/1/16
to pyglet-users, je...@mountainof.horse
That's great to hear. I think I'll make a pull request with the fix.

-Ben
Reply all
Reply to author
Forward
0 new messages