2

I have the following script to define the button mappings of my Wacom Intuos S 2:

/usr/local/bin/wacom_intuos_s_2_pad_button_mapping.sh

#!/bin/bash

export DISPLAY=:0
export XAUTHORITY=/home/scriptim/.Xauthority
/usr/bin/sleep 1 # wait for device to be ready
/usr/bin/xsetwacom set 'Wacom Intuos S 2 Pad pad' Button 1 'key -'
/usr/bin/xsetwacom set 'Wacom Intuos S 2 Pad pad' Button 3 'key +'
/usr/bin/xsetwacom set 'Wacom Intuos S 2 Pad pad' Button 8 'key +Ctrl z -Ctrl'
/usr/bin/xsetwacom set 'Wacom Intuos S 2 Pad pad' Button 9 'key +Ctrl +Shift z -Ctrl -Shift'

This script works fine if I run it manually.

My goal is to run this script automatically whenever the pad is plugged in. I tried that with the following udev rule:

/etc/udev/rules.d/10-wacom_intuos_s_2_pad.rules

ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="056a", ATTRS{idProduct}=="033b", RUN+="/usr/local/bin/wacom_intuos_s_2_pad_button_mapping.sh"

However, the script does not map the buttons if I plug in the pad.


Im running Arch Linux (5.5.10-arch1-1)

$ lsusb
...
Bus 004 Device 015: ID 056a:033b Wacom Co., Ltd CTL-490 [Intuos Draw (S)]
...
$ udevadm info -a -n hidraw0
...
  looking at parent device '/devices/pci0000:00/0000:00:10.0/usb4/4-2':
    KERNELS=="4-2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{authorized}=="1"
    ATTRS{bcdDevice}=="0100"
    ATTRS{bmAttributes}=="80"
    ATTRS{bMaxPower}=="498mA"
    ATTRS{manufacturer}=="Wacom Co.,Ltd."
    ATTRS{quirks}=="0x0"
    ATTRS{maxchild}=="0"
    ATTRS{bNumInterfaces}==" 3"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{devpath}=="2"
    ATTRS{ltm_capable}=="no"
    ATTRS{busnum}=="4"
    ATTRS{devnum}=="15"
    ATTRS{tx_lanes}=="1"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{speed}=="12"
    ATTRS{version}==" 2.00"
    ATTRS{product}=="Intuos PS"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{idVendor}=="056a"
    ATTRS{configuration}==""
    ATTRS{devspec}=="(null)"
    ATTRS{urbnum}=="174"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{removable}=="unknown"
    ATTRS{rx_lanes}=="1"
    ATTRS{idProduct}=="033b"
...
$ udevadm test --action="add" /devices/pci0000:00/0000:00:10.0/usb4/4-2
This program is for debugging only, it does not run any program
specified by a RUN key. It may show incorrect results, because
some values may be different, or not available at a simulation run.

Load module index
Parsed configuration file /usr/lib/systemd/network/99-default.link
Created link configuration context.
Reading rules file: /usr/lib/udev/rules.d/10-dm.rules
Reading rules file: /etc/udev/rules.d/10-wacom_intuos_s_2_pad.rules
...
Reading rules file: /usr/lib/udev/rules.d/65-libwacom.rules
...
Reading rules file: /usr/lib/udev/rules.d/wacom.rules
Invalid inotify descriptor.
DEVPATH=/devices/pci0000:00/0000:00:10.0/usb4/4-2
DEVNAME=/dev/bus/usb/004/015
DEVTYPE=usb_device
DRIVER=usb
PRODUCT=56a/33b/100
TYPE=0/0/0
BUSNUM=004
DEVNUM=015
MAJOR=189
MINOR=398
ACTION=add
SUBSYSTEM=usb
ID_VENDOR=Wacom_Co._Ltd.
ID_VENDOR_ENC=Wacom\x20Co.\x2cLtd.
ID_VENDOR_ID=056a
ID_MODEL=Intuos_PS
ID_MODEL_ENC=Intuos\x20PS
ID_MODEL_ID=033b
ID_REVISION=0100
ID_SERIAL=Wacom_Co._Ltd._Intuos_PS
ID_BUS=usb
ID_USB_INTERFACES=:030000:030102:
ID_VENDOR_FROM_DATABASE=Wacom Co., Ltd
ID_MODEL_FROM_DATABASE=CTL-490 [Intuos Draw (S)]
ID_PATH=pci-0000:00:10.0-usb-0:2
ID_PATH_TAG=pci-0000_00_10_0-usb-0_2
USEC_INITIALIZED=4102997566
run: '/usr/local/bin/wacom_intuos_s_2_pad_button_mapping.sh'
Unload module index
Unloaded link configuration context.
$ journalctl -xe
Mar 22 17:38:55 scriptim systemd-udevd[5927]: 1-1: Process '/usr/local/bin/wacom_intuos_s_2_pad_button_mapping.sh' failed with exit code 255.
  • Silly question perhaps, but did you make the script executable (chmod +x) ? – Anonymous Mar 22 at 15:13
  • @Anonymous Yes, the script is executable. – Scriptim Mar 22 at 15:29
  • Here is one post that can help: Why doesn't xsetwacom work from udev?. Obviously I can't test but what I gather from my research is that you should add an entry to your X.org configuration for the pad - assuming X is your display manager. Seeing that you are using Arch Linux the wiki also has useful info: Wacom tablet. You may have to install the xf86-input-wacom driver. – Anonymous Mar 22 at 15:46
  • @Anonymous For the question you linked, the problem is in the script itself. In my case, the script doesn't seem to be executed at all. Also, the article in the Arch wiki does not mention a need for an xorg entry. The driver is already installed. – Scriptim Mar 22 at 16:47
  • @Scriptim Try adding this to your script: printf 'from udev: PATH=%s\n' "$PATH" > /dev/kmsg; /usr/bin/env > /tmp/udev. If the script runs, it will show the $PATH in the kernel log and write the environment to /tmp/udev. – Nathaniel M. Beaver Mar 26 at 20:13
1
+50

Essentially this is a combination of a race condition and the difference in the behavior of xsetwacom when run from a udev rule compared to when run from a familiar graphical terminal, due to e.g. differences in environment variables.

Problem 1: environment variable.

Addressing the second issue first: this is similar to the situation where shell scripts behave differently when running from a cronjob.

To compare your environment to the udev environment, run either env or printenv:

printenv > my-env.txt

from a graphical terminal, and then add this the udev script:

/usr/bin/printenv > /tmp/udev-env.txt

Then you can compare my-env.txt and udev-env.txt.

To infer what environment variables xsetwacom is using, install ltrace and run a command like this:

ltrace -f -e getenv -o my-ltrace-01.log xsetwacom set 'Wacom Intuos S 2 Pad pad' Button 1 'key -'

Since I don't have a Wacom device, I can't run the full command.

$ xsetwacom set 'Wacom Intuos S 2 Pad pad' Button 1 'key -'
Cannot find device 'Wacom Intuos S 2 Pad pad'.

However, just based on how far xsetwacom gets before it exits, I can tell it uses DISPLAY and XAUTHORITY.

15447 libX11.so.6->getenv("DISPLAY")             = ":0"
15447 libxcb.so.1->getenv("DISPLAY")             = ":0"
15447 libxcb.so.1->getenv("DISPLAY")             = ":0"
15447 libXau.so.6->getenv("XAUTHORITY")          = "/home/nathaniel/.Xauthority"
15447 libX11.so.6->getenv("XLIBBUFFERSIZE")      = nil
15447 libX11.so.6->getenv("XLIB_SKIP_ARGB_VISUALS") = nil
15447 libX11.so.6->getenv("XKB_DEBUG")           = nil
15447 libX11.so.6->getenv("_XKB_OPTIONS_ENABLE") = nil
15447 libX11.so.6->getenv("XKB_DISABLE")         = nil
15447 +++ exited (status 0) +++

You can find these values by printing them.

$ printf 'DISPLAY=%q\n' "$DISPLAY"
DISPLAY=:0
$ printf 'XAUTHORITY=%q\n' "$XAUTHORITY"
XAUTHORITY=/home/nathaniel/.Xauthority

Then define them at the top of the script.

#!/bin/bash

# Just an example, yours will be different.
export DISPLAY=:0
export XAUTHORITY=/home/nathaniel/.Xauthority
sleep 1
# The rest of the script.

The script could still fail to work for other reasons, but eliminating differences in environment variables seems to have been sufficient for other folks. Another user in a related question was able to get it working on Ubuntu 18.04 in this manner:

You need to add two variable exports to your script, one for DISPLAY and XAUTHORITY. Those are used for identifying and accessing the correct X session. You can get the appropriate values by running env while logged in as your regular user.

Problem 2: Race condition.

Now for the race condition: the xsetwacom command relies on the X server recognizing the hardware, so running it before it's ready will cause it to fail. The sleep 1 was apparently sufficient at one time but is no longer. (Sometimes people use sleep 2, sleep 3, or sleep 4, with no particular reason why. Broadly speaking, it troubles me that the sleep 1 is even necessary.)

When you plug in the device:

  1. Linux detects the device and creates a device entry based on udev rules.
  2. The X server detects the device.

You cannot run xsetwacom before stage 2. Your script is failing because you're running it at stage 1, when X doesn't know the device yet.

Gilles 'SO- stop being evil', https://unix.stackexchange.com/a/65792/30049

This is all well and good, but when people try this, it doesn't work anymore:

I upvoted your answer for the details, but I am not sure this is correct for the following reason: I tried using sleep with a bunch of seconds. When plugging in, the tablet works after less than a second, so by the time the commands get executed, the device is already detected and in use by X. But still it doesn't work?

comment by Redsandro

As I don't have your hardware or OS, I can't replicate your issue. But here's what I have gleaned from other threads: the sleep delay by itself is not sufficient. Some people have workarounds such as:

Perhaps the most thorough solution I've seen is in the linked thread using systemd service files:

I ended up having to start the script with a systemd service triggered by a udev rule:

$ cat /etc/udev/rules.d/99-wacom.rules
SUBSYSTEM=="usb", ENV{ID_VENDOR_ID}=="056a", ENV{ID_MODEL_ID}=="0302", TAG+="systemd"

[...]

The TAG+="systemd" enables other systemd services (system or user) to depend on the device (registers it as a device unit, see man systemd.device).

Spelufo, https://unix.stackexchange.com/a/290940/30049

| improve this answer | |
  • The ltrace command you posted produces one line of output: 703488 +++ exited (status 0) +++. I explicitly set all environment variables that were not set by udev. But this did not change anything (apart from DISPLAY and XAUTHORITY, see the question comments). – Scriptim Mar 30 at 18:04
  • Thanks a lot. The solution with the systemd service worked for me. – Scriptim Mar 30 at 19:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.