3

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.
Scriptim
  • 215
  • Silly question perhaps, but did you make the script executable (chmod +x) ? – Kate Mar 22 '20 at 15:13
  • @Anonymous Yes, the script is executable. – Scriptim Mar 22 '20 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. – Kate Mar 22 '20 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 '20 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 '20 at 20:13
  • @Scriptim Also, on my system, this script runs 7 times in a row when I plug in a USB mouse. Not sure if it's the same on Arch, though. – Nathaniel M. Beaver Mar 26 '20 at 20:22
  • @NathanielM.Beaver I have placed a line at the beginning of the script to write test output to a file. But since nothing is written to that file, I assume that the script is not executed at all. – Scriptim Mar 26 '20 at 21:44
  • @Scriptim Yes, I saw. But how did you write to that file? Did you e.g. use an executable that might not be in $PATH for the environment of the udev rule? Based on the journalctl -xe output it looks like the script is running, it's just exiting with a failure code. – Nathaniel M. Beaver Mar 27 '20 at 02:18
  • You can try exit 17 or similar at the end and see if the exit code changes. – Nathaniel M. Beaver Mar 27 '20 at 02:29
  • @NathanielM.Beaver You were right, the script is executed. The exit status changes if I put exit 17 at the end. – Scriptim Mar 27 '20 at 11:43
  • @Scriptim: if you're sure that script is running check if $DISPLAY is set. See what happens when it's not set: unset DISPLAY; xsetwacom set 'Wacom Intuos S 2 Pad pad' Button 1 'key -' – Arkadiusz Drabczyk Mar 27 '20 at 13:02
  • @Scriptim: also check $?, it's 255, so it might be it. – Arkadiusz Drabczyk Mar 27 '20 at 13:10
  • @ArkadiuszDrabczyk Thanks, we're getting closer. The script indeed exits with 255 when I unset DISPLAY. However, when I set DISPLAY (and XAUTHORITY), there is no error message in the journal anymore, but the buttons still don't work. – Scriptim Mar 27 '20 at 16:31
  • @Scriptim: ok, update your script in the question. You can check $? after each xsetwacom, the script is running in the background of course so try something like echo $? >> /tmp/XWACOM_RESULTS – Arkadiusz Drabczyk Mar 27 '20 at 17:13
  • @Scriptim: and try unset DISPLAY; /usr/local/bin/wacom_intuos_s_2_pad_button_mapping.sh – Arkadiusz Drabczyk Mar 27 '20 at 17:14
  • @ArkadiuszDrabczyk I echoed the status code into a file after each xsetwacom command (so four times) as you advised. After plugging in the Pad, the file contains 64 lines with 0. The buttons still don't work, though. – Scriptim Mar 30 '20 at 17:44

1 Answers1

3

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

  • 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 '20 at 18:04
  • Thanks a lot. The solution with the systemd service worked for me. – Scriptim Mar 30 '20 at 19:02