I recently got a new vertical mouse from Kensington and it seems the way the manufacturer expects me to hold that mouse does not fully match with what feels right for me: When I hold the mouse in a way that is comfortable for me and move it sideways, the mouse cursor on the screen moves on a slightly rotated diagonal line.

It was quickly clear that I cannot use the mouse efficiently like this, so I looked for a solution. Luckily, I found a rather simple one: On Linux, you can adjust the axes of the mouse by setting a transformation matrix using xinput.

Note: The following is tested with Ubuntu 20.04.

Step 1: Find the device name of the mouse

Run

xinput list

to get a list of input devices. Identify the mouse by first running this command with the mouse unplugged, then plug it in and list the devices again, checking which entries are new.

The name of my Kensington mouse is “CX 2.4G Receiver Mouse” but this will likely be different for other models/manufacturers so adjust the name accordingly in the following.

You can list properties of the device with

xinput list-props 'CX 2.4G Receiver Mouse'

For my mouse, it lists “Coordinate Transformation Matrix” there among other things. This is what we are going to use in the next step

Step 2: Change the transformation matrix

Use xinput set-prop to change properties of a device. For example to rotate the mouse axes by 90 degree:

xinput set-prop "CX 2.4G Receiver Mouse" "Coordinate Transformation Matrix" 0 -1.0 0.0 1.0 0 0.0 0.0 0.0 1.0

The “Coordinate Transformation Matrix” is a affine transformation matrix. To rotate by an angle α, you need the matrix

⎡cos(α)  -sin(α)  0⎤
⎢sin(α)   cos(α)  0⎥
⎣  0        0     1⎦

The values are then passed to xinput row by row as a flat list.

Since manually computing this matrix is a bit tedious, I wrote the following Python script to be able to quickly try out different angles:

#!/usr/bin/env python3
"""Use xinput to rotate the mouse axes by a given angle."""
import argparse
import os

from scipy.spatial.transform import Rotation

# Name of the xinput device.  Change this to match your device.
DEVICE_NAME = "CX 2.4G Receiver Mouse"


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("angle", type=int, help="Rotation angle in degrees.")
    args = parser.parse_args()

    # construct rotation matrix from given angle (xinput expects an affine 2d
    # transformation but since we are only rotating, this is equivalent to a
    # simple 3d rotation matrix around the z-axis).
    rot = Rotation.from_euler("z", args.angle, degrees=True).as_dcm()

    xinput_cmd = [
        "xinput",
        "set-prop",
        DEVICE_NAME,
        "Coordinate Transformation Matrix",
    ] + [str(x) for x in rot.flatten()]

    os.execvp("xinput", xinput_cmd)


if __name__ == "__main__":
    main()

To rotate the mouse axes, simply call the script with the desired angle as argument like this:

python3 rotate_mouse.py 42

Positive values result in clock-wise rotation. The rotation is always relative to the original orientation, so you can easily reset by setting the angle to 0.

With this script I tried out different angles. It seems that for the Kensington mouse a rotation of -20 degrees works well for me.

References