How do I automatically remap buttons on my mouse at startup?

I struggled with this over the weekend, and need to remap my mouse buttons.


Solution 1:

I have a Logitech mouse with 9 buttons, and pressing the "middle button" (#2) involves clicking the scroll wheel. I dislike this because I'm clumsy and typically end up scrolling the window I'm in when I try to click the wheel. So I wanted to automatically remap the top side button (#9 in this case) to the middle button (#2). I also wanted to map the bottom side button (#8) so that it executes a double-click of the left button (#1).

Though my aims were specific, the solutions below can be generalized to any situation in which you want to automatically remap mouse buttons at startup.

Mapping Mouse Buttons to Other Mouse Buttons

You will need xinput installed for this task. This can be done entirely in your .xsessionrc file. First, use xinput to discover the name that is assigned to your mouse, which is then correlated to an input device ID. Below is some sample output from my laptop:

⎡ Virtual core pointer                      id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer            id=4    [slave  pointer  (2)]
⎜   ↳ Logitech USB Laser Mouse              id=11   [slave  pointer  (2)]
⎣ Virtual core keyboard                     id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard           id=5    [slave  keyboard (3)]

My mouse calls itself Logitech USB Laser Mouse and is shown as id=11. Your mouse will have a different name; figuring that out is left as an exercise for the reader.

While you still know the ID of the device in this session, find out how many buttons the input handler thinks your mouse has, by using xinput list deviceID. This may be different from the number of buttons that is apparent on the device.

Logitech USB Laser Mouse                    id=11   [slave  pointer  (2)]
    Reporting 7 classes:
        Class originated from: 11. Type: XIButtonClass
        Buttons supported: 16
        Button labels: "Button Left" "Button Middle" "Button Right" "Button Wheel Up" "Button Wheel Down" "Button Horiz Wheel Left" "Button Horiz Wheel Right" "Button Side" "Button Extra" "Button Forward" "Button Back" "Button Task" "Button Unknown" "Button Unknown" "Button Unknown" "Button Unknown"

With my mouse, there are only 9 obvious physical buttons, but xinput reports 16.

Given the nature of USB, this ID can change every time you restart, so it's not enough to script something that's statically keyed to an ID you discover once. You'll have to dynamically parse this at startup and execute your re-map based on the current ID.

Now that you know its name, you can use xinput test deviceID to figure out which key to remap. Press the mouse buttons you want to map from and to, in order to get their indices. (For reference, 1, 2, and 3 "always" (i.e., usually) refer to the left, middle, and right buttons of a 3-button mouse. A common re-map reverses these to make the mouse left-handed.)

button press   2
button release 2
button press   9
button release 9

In this case I found that I want to map button #9 (side, top) to button #2 (middle).

Now that you know what your mouse is called, and which buttons you want to change, you can write an ~/.xsessionrc script that invokes xinput to execute the button re-mapping at startup. Below is my complete script.

# Map button 9 (top side button) to button 2 (middle button)
my_mouse_id=$(xinput | grep -m 1 "Logitech USB Laser Mouse" | sed 's/^.*id=\([0-9]*\)[ \t].*$/\1/')
echo $my_mouse_id > ~/temp/my_mouse_id
xinput set-button-map $my_mouse_id 1 2 3 4 5 6 7 8 2 10 11 12 13 14 15 16

The first line here sets a temporary session variable equal to the ID of the mouse as reported by xinput. This is done by greping for the known name of the mouse in the report from xinput, then using sed to extract the ID number from that id=xxx token in the report. This value is then used in an xinput set-button-map directive, which executes the re-mapping. In the example above, the only change is that button #9 is being re-mapped to mimic button #2. All others remain at their default setting.

Update: As @Lokasenna points out below, if your device reports itself as both a mouse and a keyboard, you may need to limit the result count of the grep using -m 1. This will not cause problems if the mouse doesn't report itself as both, so it's been included in the script.

Mapping Mouse Buttons to Arbitrary Functions

See also this answer.

You will need xinput, xbindkeys, and xautomation (including xte) installed for this task.

Use xinput list and xinput test to discover your mouse's device ID and the number of the button you want to assign. In my case, I wanted to map the bottom side button (#8) to a double-click of the left button (#1).

Create or edit ~/.xbindkeysrc. The format of this file is a series of paired lines. The first line is a command to be executed for an event; the second line is the event description. We will use the xte component of xautomation to send events directly to the input handler.

To create a double-click event when a button is released, I added the following:

"/usr/bin/xte 'mouseclick 1' 'mouseclick 1' &"
b:8 + Release

This configuration maps a sequence of two mouse clicks on button #1 to the release of button #8. (In theory I guess you could map any command to a mouse button, but this is the most common case. See this answer for other practical examples.)

Update for 16.04 Ubuntu

For users with multiple mice attached to your system, you need to also pass in the ID of the device. This may not apply to all users and was discovered on Ubuntu 16.04 with Unity.

xinput list

⎡ Virtual core pointer                      id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ Kensington      Kensington Expert Mouse   id=9    [slave  pointer  (2)]
⎜   ↳ SynPS/2 Synaptics TouchPad                id=13   [slave  pointer  (2)]
⎜   ↳ TPPS/2 IBM TrackPoint                     id=14   [slave  pointer  (2)]

Then modify the .xbindkeysrc file by referencing the id= value from the command output (id=9 in this example):

"/usr/bin/xte -i 9 'mouseclick 1' 'mouseclick 1' &"
b:8 + Release

Solution 2:

Short steps for this are:

There is a utility called xinput. xinput list or just xinput will show all the X input devices and theirs IDs. Here you find ID of the mouse which you want to remap.

I will use my ID as example, from my setup, which is 21, then xinput --get-button-map 21 will output

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

Now, if you want to, say, swap left and right buttons you simply run

xinput --set-button-map 21 3 2 1

Here we are, remapping is done.

For running it at startup just put this into a file:

echo "xinput --set-button-map 21 3 2 1" > leftmouseremap.sh

give it executable permission

chmod +x leftmouseremap.sh 

Finally, add this to Statrtup Application manually from GUI or , if you want it from CLI, put text below (change paths to yours) inti a file in your ~/.config/autostart, here is mine (less .config/autostart/leftmouseremap.sh.desktop):

[Desktop Entry]
Type=Application
Exec=/home/ruslan/leftmouseremap.sh
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name[en_US]=/home/ruslan/leftmouseremap.sh
Name=/home/ruslan/leftmouseremap.sh
Comment[en_US]=
Comment=

Keep in mind, that with KDE the path would be like ~/.kde/Autosart, for others Desktop managers this might be sightly different. Alternatively, startup running can be done with general approach by using /etc/rc.local.

Solution 3:

When using zerobandwidth's great answer, some devices, such as Logitech's MX Ergo, show up as both a pointer and a keyboard device:

⎡ Virtual core pointer                          id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ Logitech MX Ergo                          id=10   [slave  pointer  (2)]
⎜   ↳ SynPS/2 Synaptics TouchPad                id=14   [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)]
    ...
    ↳ Logitech MX Ergo                          id=15   [slave  keyboard (3)]

Consequently, grep "Logitech MX Ergo" ends up returning two values. The latter ends up being included as the first item in the mapping string and screws up all of your mouse buttons.

The fix is easy - just use grep's maximum-count argument, -m 1:

my_mouse_id=$(xinput | grep -m 1 "Logitech MX Ergo" | sed 's/^.*id=\([0-9]*\)[ \t].*$/\1/')
echo $my_mouse_id > ~/temp/my_mouse_id
xinput --set-button-map $my_mouse_id 1 2 3 4 5 6 7 8 2 10 11 12 13 14 15 16