Persistent Touchscreen Configuration Using udev and systemd

· udik's blog


I bought a portable 15-inch display with HDMI and DisplayPort over USB-C input ports. The exact model is:

 1$ cat /sys/devices/pci0000:00/0000:00:02.0/drm/card1/card1-DP-3/edid | edid-decode
 2
 3----------------
 4
 5Block 0, Base EDID:
 6  EDID Structure Version & Revision: 1.4
 7  Vendor & Product Identification:
 8    Manufacturer: RTK
 9    Model: 9557
10
11...

What’s more interesting is that it has touchscreen capabilities. After plugging in the display using the appropriate USB-C cable (with a lightning symbol on it), the display was correctly recognized, and the extended desktop image appeared. However, after testing the touchscreen, I found that it needs to be calibrated.

To be more specific, the Coordinate Transformation Matrix of this xinput device has to be adjusted. In my case, the touchscreen appears in the system as two nearly identical devices with ID=17 and ID=18, both under the name wch.cn TouchScreen:

1$ xinput list
2
3...
4⎜   ↳ wch.cn TouchScreen                      	id=17	[slave  pointer  (2)]
5⎜   ↳ wch.cn TouchScreen                      	id=18	[slave  pointer  (2)]
6...
7

The transformation matrix #

Both of these devices have the default Coordinate Transformation Matrix:

$ xinput list-props 17 | grep "Coordinate Transformation Matrix"
Coordinate Transformation Matrix (192):	1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000

This matrix represents an affine transformation. Let’s calculate the correct values, given that:

With the default matrix (the output from xinput list-props shows this matrix in a single line, row by row):

1 0 0
0 1 0
0 0 1

The effective area of the touchscreen extends across the entire desktop area of 3800x1200, but the actual area "under the touchscreen" is 1900x1080, with a 1900px left padding (because the touchscreen display is positioned on the right in my setup). Note that the origin of the coordinates is the top-left corner, with the X-axis extending from left to right and the Y-axis extending from top to bottom.

Based on these conditions, the transformation matrix should be:

1900/(1900+1900)        0         1900/(1900+1900)
        0           1080/1200            0
        0               0                1

or:

0.5  0   0.5
0   0.9   0
0    0    1

This new matrix can be applied as follows (remember I have two touchscreen input devices with IDs 17 and 18):

1$ xinput set-prop 17  "Coordinate Transformation Matrix" 0.5 0 0.5 0 0.9 0 0 0 1
2$ xinput set-prop 18  "Coordinate Transformation Matrix" 0.5 0 0.5 0 0.9 0 0 0 1

Now, the touchscreen works as expected.

Persistent configuration #

There’s one issue: if the display is unplugged and then plugged back in, the transformation matrix resets, and I have to run xinput set-prop each time this happens. Let's automate this process.

To detect when the display is plugged in, we’ll use udev. To set this up, we need to identify the idVendor:idProduct of the touchscreen:

1$ lsusb
2...
3Bus 003 Device 057: ID 27c0:0859 Cadwell Laboratories, Inc. TouchScreen
4...

In my case idVendor=27c0, idProduct=0859.

Create udev rule:

1$ sudo nano /etc/udev/rules.d/99-touchscreen.rules

with the following content:

ACTION=="add", ATTRS{idVendor}=="27c0", ATTRS{idProduct}=="0859", TAG+="systemd", ENV{SYSTEMD_WANTS}="set-touchscreen.service"

This will trigger the systemd script set-touchscreen.service when the display is plugged in.

Create set-touchscreen.service:

1$ sudo nano /etc/systemd/system/set-touchscreen.service

With the following content:

[Unit]
Description=Set touchscreen calibration for my Chinese portable monitor
After=display-manager.service

[Service]
Type=oneshot

# To allow access to X.Server for xinput from ExecStart
Environment=DISPLAY=:0
User=your_user_name
Environment=XAUTHORITY=/home/your_user_name/.Xauthority

# This script triggers at the moment display plugs in. The touchscreen device could not have time to initialize, so we need to wait extra time
ExecStartPre=/bin/sleep 5

# Replace the name of your touchscreen in grep stage
ExecStart=/bin/bash -c 'DEVICE_IDS=$(xinput list | grep "wch.cn TouchScreen" | awk "{print \$5}" | sed "s/id=//"); for id in $DEVICE_IDS; do if [ -n "$id" ]; then xinput set-prop "$id" "Coordinate Transformation Matrix" 0.5 0 0.5 0 0.9 0 0 0 1; fi; done'

[Install]
WantedBy=multi-user.target

The xinput command, when executed by systemd, requires access to the X server. This can be achieved by adding the following lines (make sure to replace your_user_name with your actual username):

Environment=DISPLAY=:0
User=your_user_name
Environment=XAUTHORITY=/home/your_user_name/.Xauthority

The script in the ExecStart variable iterates through xinput devices that have the name wch.cn TouchScreen (as mentioned earlier in this article). Adjust this based on your specific setup.

To apply the changes, run:

1sudo systemctl daemon-reload
2sudo udevadm control --reload-rules

After this, the correct transformation matrix configuration should be automatically applied when the display is plugged in (after about 5 seconds).

Conclusion #

It's quite simple to set up a persistent configuration for a touchscreen using udev and systemd. In this scenario, udev is used to trigger a systemd script, which in turn executes the command that we initially had to enter manually every time the display connects.

It’s also possible to use udev alone with a basic bash script (without involving systemd), for example:

/etc/udev/rules.d/99-touchscreen.rules:

ACTION=="add", ATTRS{idVendor}=="1234", ATTRS{idProduct}=="5678", RUN+="/usr/local/bin/set-touchscreen.sh"

However, I personally prefer to use systemd for tasks like this due to the "free" logs, easier configuration management (such as enabling/disabling), and better control overall.