While working on the Zyn-Fusion UI I ended up getting a touch screen to help with the testing process. After getting the screen, buying several incorrect HDMI cables, and setting up the screen I found out that the touch events weren’t working as expected. In fact they were often showing up on the wrong screen. If I disabled my primary monitor and only used the touch screen, then events were spot on, so this was only a multi-monitor setup issue.

So, what caused the problem and how can it be fixed?

Well, by default the mouse/touch events which were emitted by the new screen were scaled to the total available area treating multiple screens as a single larger screen. Fortunately X11 provides one solution through xinput. Just running the xinput tool lists out a collection of devices which provide mouse and keyboard events to X11.

mark@cvar:~$ xinput
| Virtual core pointer                          id=2    [master pointer  (3)]
|   > Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
|   > PixArt USB Optical Mouse                  id=8    [slave  pointer  (2)]
|   > ILITEK Multi-Touch-V3004                  id=11   [slave  pointer  (2)]
| Virtual core keyboard                         id=3    [master keyboard (2)]
    > Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    > Power Button                              id=6    [slave  keyboard (3)]
    > Power Button                              id=7    [slave  keyboard (3)]
    > AT Translated Set 2 keyboard              id=9    [slave  keyboard (3)]
    > Speakup                                   id=10   [slave  keyboard (3)]

In this case the monitor is device 11 which has it’s own set of properties.

mark@cvar:~$ xinput list-props 11
Device 'ILITEK Multi-Touch-V3004':
        Device Enabled (152):   1
        Coordinate Transformation Matrix (154): 1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000
        Device Accel Profile (282):     0
        Device Accel Constant Deceleration (283):       1.000000
        Device Accel Adaptive Deceleration (284):       1.000000
        Device Accel Velocity Scaling (285):    10.000000
        Device Product ID (272):        8746, 136
        Device Node (273):      "/dev/input/event13"
        Evdev Axis Inversion (286):     0, 0
        Evdev Axis Calibration (287):   <no items>
        Evdev Axes Swap (288):  0
        Axis Labels (289):      "Abs MT Position X" (689), "Abs MT Position Y" (690), "None" (0), "None" (0)
        Button Labels (290):    "Button Unknown" (275), "Button Unknown" (275), "Button Unknown" (275), "Button Wheel Up" (158), "Button Wheel Down" (159)
        Evdev Scrolling Distance (291): 0, 0, 0
        Evdev Middle Button Emulation (292):    0
        Evdev Middle Button Timeout (293):      50
        Evdev Third Button Emulation (294):     0
        Evdev Third Button Emulation Timeout (295):     1000
        Evdev Third Button Emulation Button (296):      3
        Evdev Third Button Emulation Threshold (297):   20
        Evdev Wheel Emulation (298):    0
        Evdev Wheel Emulation Axes (299):       0, 0, 4, 5
        Evdev Wheel Emulation Inertia (300):    10
        Evdev Wheel Emulation Timeout (301):    200
        Evdev Wheel Emulation Button (302):     4
        Evdev Drag Lock Buttons (303):  0

Notably xinput provides a property to describe a coordinate transformation which can be used to remap the x and y values of the cursor events. The transformation matrix here is a 3x3 matrix used to transform 2D coordinates and is a fairly common sight in computer graphics. It translates from \((x,y)\) to \((x',y')\) as defined by:

$$ \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} a & b & c\\ d & e & f\\ h & i & j \end{bmatrix} \times \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$

The transformation matrix allows for stretching, shearing, translation, flipping, scaling, etc. For the sorts of problems you may see introduced by a multi-monitor setup I would only expect people to care about translating (\(t\)) the events and then re-scaling (\(s\)) them to the offset area. Using these two parameters, the transformation matrix equation is simplified to:

$$ \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} s_x & 0 & t_x\\ 0 & s_y & s_y\\ 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$

Or without the matrix representation:

$$ \begin{aligned} x' &= s_x x + t_x\\ y' &= s_y y + t_y \end{aligned} $$

With that background out of the way, let’s see how this applied to my specific monitor setup:

2017 monitors

As I mentioned earlier the touch events were scaled to the dimensions of the larger virtual screen. Since the touch screen is larger this means the y axis is mapped correctly and the x axis is mapped for pixels 0..3200 (both screens) instead of pixels 1281..3200 (left screen only). Since the xinput scales theses parameters based upon the total screen size, we can divide by the total x size (3200) to learn that the x axis maps to 0..1 rather than 0.4..1.0. Solving the above equations we can remap the touch events using \(s_x=0.6\) and \(t_x=0.4\). This results in the transformation matrix:

$$ \begin{bmatrix} 0.6 & 0 & 0.4\\ 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix} $$

The last step is to provide the new transformation matrix to xinput:

xinput set-prop 11 'Coordinate Transformation Matrix' 0.6 0 0.4 0 1 0 0 0 1

Now cursor events map onto the correct screen accurately and the code to change the xinput properties can be easily put into a shell script.