Hi!
It's really dumb, but every vertical mouse I saw has one crucial mistake in its design - the sensor is misaligned and when you try to hold it the proper (ergonomic!) way and move horizontally, it also moves up / down...
But we can fix it with xinput!
Steps
-
Identify your fancy mouse. Run the following command with the mouse unplugged and then plugged again to see which device you need (get name after
↳and beforeid=):
xinput listMine is called
USB OPTICAL MOUSE. -
Prepare the Python3 environment for script in next step. You'll need the
scipypackage.For NixOS just replace the shebang in this script with this one:
#!/usr/bin/env nix-shell #!nix-shell -i python3 -p python3 python313Packages.scipy -
Use python script below to experiment with rotation angle:
#!/usr/bin/env python3 # Set rotation matrix for mouse. # Original: https://luator.de/linux/2022/03/05/xinput-rotate-mouse-axes.html # # Usage example: # python3 rotate-mouse.py DEVICE_NAME ANGLE # # See all devices with: # xinput list # # The script also prints the command for selected angle, so you can add it to autostart. # # 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. """Use xinput to rotate the mouse axes by a given angle.""" import argparse import os from scipy.spatial.transform import Rotation def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("device", type=str, help="Mouse name from `xinput list`.") 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_matrix() matrix = [str(x) for x in rot.flatten()] xinput_cmd = [ "xinput", "set-prop", args.device, "Coordinate Transformation Matrix", ] + matrix xinput_str = f"xinput set-prop '{args.device}' 'Coordinate Transformation Matrix' {' '.join(matrix)} # Rotates mouse by {args.angle}°" print(xinput_str) os.execvp("xinput", xinput_cmd) if __name__ == "__main__": main()Original script was taken from Luator's Blog.
The script above also prints applied xinput command, which you can add to your autostart scripts to make it persistent after reboot.
After finding the matrix, you can ditch created above python environment.
Bonus: InputLeap struggles
I use InputLeap to share my keyboard and mouse between my machines time to time, which apparently does not go well with such xinput changes (leave a comment if I miss something). On clients the mouse comes stuck at the top right corner.
So we need to reset our transformation matrix when we go to InputLeap client (and struggle with not-so-ergonomic-mouse-with-misaligned-sensor, alas!).
As it's mentioned in the script, you can reset the matrix by running it with zero angle, or straight with xinput:
xinput set-prop 'YOUR_MOUSE_NAME' 'Coordinate Transformation Matrix' 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0
So we just need to run this reset command when we go to client and run the rotation command when we are back on server.
InputLeap has the --screen-change-script argument, which you can use... but somehow applying any arguments to input-leap on NixOS does nothing, it only respects the settings set up in its GUI.
Thus, here is a uniform way to catch when InputLeap changes screens via monitoring its log file:
-
Open InputLeap GUI, go to
InputLeaptop menu ->Change Settings->Logging- set
Logging leveltoinfo - check
Log to fileand choose the log file you want to use (i.e./tmp/inputleap.log) - hit
OKand restart it
- set
-
Save and edit script below:
- change
LOG_FILEvariable to the log file path which you set in InputLeap GUI - change
YOUR_MOUSE_NAMEwith, you guessed it, your mouse name fromxinput list - change
ROTATE_CMDandRESET_CMDwith appropriate xinput commands found above - change
SERVER_SCREENto the name of InputLeap server's screen (it's inGeneral->Screen nameof InputLeap GUI settings from previous step) - also, you can uncomment the line with
notify-sendto see the screen name when you move between them
#!/usr/bin/env bash # Rotates mouse based on current InputLeap screen. LOG_FILE="/tmp/inputleap.log" rm -f "$LOG_FILE" touch "$LOG_FILE" tail -f "$LOG_FILE" | grep "INFO: switch from" --line-buffered | while read -r line ; do SCREEN=$(echo "$line" | sed 's/.*to "\(.*\)".*/\1/g') # notify-send -a InputLeap "Switched to $SCREEN" case "$SCREEN" in "SERVER_SCREEN") ROTATE_CMD;; *) RESET_CMD;; esac done - change
Run the script above (add to autostart if needed).
Bye!
Top comments (0)