Use a mouse button as a modifier in Linux by using xbindkeys
The default behavior of the extra buttons on the side of a mouse is forward/backward navigation. I rarely use that type of navigation, as I usually open every link in a new browser tab and switch between them instead. Personally, this functionality is at best useless and at worst annoying when I accidentally press "backwards" while reading a page.
There are many ways to repurpose these buttons, using manufacturer provided software or within specific applications and games. In my experience, these tools are always limited to simple mapping to keyboard combinations. I would like to move beyond that, by transforming the buttons into modifiers.
The solution
The functionality I had in mind was to control the system volume using the scroll wheel, but only while the modifier button (formerly the "backward" button) was pressed. Thankfully, I found a forum thread on LinuxQuestions.org where the user Zero Angel implemented something similar using xbindkeys.
xbindkeys, as the name suggests, binds key and mouse button events to shell commands. In addition to supporting a simple mapping from button to command, like described above, it also accepts a script as a configuration file at ~/.xbindkeysrc.scm
, making complex logic possible. The script must be written in Guile Scheme.
Included below is an adaptation of Zero Angel's script, with modifications for my music player and volume control purposes, and some helpful comments. To use it yourself, you'll want to:
- Install xbindkeys (and playerctl for the music player controls) using your package manager.
- Find out what number each mouse button corresponds to (e.g. use
xev -event button
). Usually, button 8 is "backwards", and 9 is "forwards". - Decide which button you want to use as the modifier. Here, I used
"b:8"
, i.e. the "backwards" button. Simply replace all occurrences of it in the script if you want to use another. - Define the button mappings in effect when the modifier is pressed, by modifying each
run-command
inside theenter-mouse-modifier
function. As it stands, the mappings are:- Left button
"b:1"
→ previous track - Middle button
"b:2"
→ toggle mute - Right button
"b:3"
→ next track - Scroll wheel up
"b:4"
→ volume up - Scroll wheel down
"b:5"
→ volume down - If none of the above are pressed, and the modifier
"b:8"
is released → toggle pause
- Left button
- Run
xbindkeys --nodaemon
to test your script. Press Ctrl+C to stop it, make your changes to the script, and repeat. - When you're satisfied with the result, find a way to run it automatically at startup. I added
xbindkeys
to my existing~/.xinitrc
file. If you use a desktop environment such as Gnome or KDE, look for their way of running things at startup instead.
The script
;; ~/.xbindkeysrc.scm
;; Makes a mouse button into a modifier.
;; Credit: Zero Angel at linuxquestions.org
;; Tracks whether anything was pressed
;; while the modifier was active.
(define actionperformed 0)
(define (install-modifier-binding)
;; Use button 8 as modifier
(xbindkey-function '("b:8") enter-mouse-modifier))
(define (leave-mouse-modifier)
;; Reset everything!
;; Stop processing keys
(ungrab-all-keys)
;; Remove all bindings
(remove-all-keys)
;; Reset mouse chord state
(set! actionperformed 0)
;; Reset modifier keys
(run-command
"xdotool keyup ctrl keyup alt keyup shift keyup super&")
;; Add bindings again
(install-modifier-binding)
;; Start processing keys again
(grab-all-keys))
(define (enter-mouse-modifier)
;; Stop processing keys
(ungrab-all-keys)
;; Remove all bindings
(remove-all-keys)
;; Add the relevant bindings back
;; If pressing the left button...
(xbindkey-function
'("b:1")
(lambda ()
;; Previous track.
(run-command "playerctl previous")
(set! actionperformed 1)))
;; If pressing the middle button...
(xbindkey-function
'("b:2")
(lambda ()
;; Mute.
(run-command "pactl set-sink-mute @DEFAULT_SINK@ toggle")
(set! actionperformed 1)))
;; If pressing the right button...
(xbindkey-function
'("b:3")
(lambda ()
;; Next track.
(run-command "playerctl next")
(set! actionperformed 1)))
;; If scrolling up...
(xbindkey-function
'("b:4")
(lambda ()
;; Volume up.
(run-command "pactl set-sink-volume @DEFAULT_SINK@ +5%")
(set! actionperformed 1)))
;; If scrolling down...
(xbindkey-function
'("b:5")
(lambda ()
;; Volume down.
(run-command "pactl set-sink-volume @DEFAULT_SINK@ -5%")
(set! actionperformed 1)))
;; When the modifier is released...
(xbindkey-function
'(release "b:8")
(lambda ()
;; If no other key was pressed...
(if (= actionperformed 0)
;; Pause.
(run-command "playerctl play-pause"))
;; Finally, leave the modifier.
(leave-mouse-modifier)))
;; Bindings are set,
;; start processing keys again.
(grab-all-keys))
;; Main
(install-modifier-binding)