1. Overview

In this tutorial, we’ll learn how to trigger shell scripts on mouse events. For this, we’ll assume that we’re using the Xorg or X11 implementation as our display server. The display server will capture the mouse event and trigger the shell script. With mouse events, we mainly refer to pressing a mouse button.

We’ll consider three solutions to this problem: using xbindkeys, checking cnee, and with xinput. First, let’s cover a small discussion on topics that apply to all solutions.

2. Setup Common to All Procedures

For some methods, we might need to query our system to get some information about the input devices. We need to gather the <mouse_id> with xinput:

$ xinput -list
⎡ Virtual core pointer                          id=2    [master pointer  (3)]
⎜   ↳ SynPS/2 Synaptics TouchPad                id=11   [slave  pointer  (2)]
⎜   ↳ TPPS/2 IBM TrackPoint                     id=12   [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)]
    ↳ Integrated Camera: Integrated C           id=9    [slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard              id=10   [slave  keyboard (3)]

Then, we can query the state of a given input, using the xinput command with the –query-state parameter and the <mouse_id> obtained before:

$ xinput --query-state <mouse_id>
2 classes :
ButtonClass
button[1]=up
button[2]=up
button[3]=up
button[4]=up
button[5]=up
button[6]=up
button[7]=up
ValuatorClass Mode=Relative Proximity=In
valuator[0]=1794
valuator[1]=996
valuator[2]=-68256
valuator[3]=70580

We can see that all the buttons in the mouse are tagged as up, which means that they aren’t being pressed. If we run the same command while pressing one of the mouse keys, we would get:

$ xinput --query-state <mouse_id>
2 classes :
ButtonClass
button[1]=down
button[2]=up
button[3]=up
button[4]=up
button[5]=up
button[6]=up
button[7]=up
ValuatorClass Mode=Relative Proximity=In
valuator[0]=1794
valuator[1]=996
valuator[2]=-68256
valuator[3]=70580

Thus, pressing a mouse button is an event, and releasing that mouse button is another event. We need to keep this in mind since it can be relevant to the implementation of the script.

3. Using xbindkeys

If we’ve got super-user rights, we might consider xbindkeys as the first alternative. xbindkeys allows us to associate button (including mouse) events and actions triggered by these events.

Once we have xbindkeys installed in our machine, we can create a template file that we store in our user directory:

$ xbindkeys --defaults > /home/$USER/.xbindkeysrc

xbindkeys refers to this file when we trigger events. We can modify the file with our favorite text editor to introduce a link for a mouse event:

$ vim /home/$USER/.xbindkeysrc
...
"xterm -e '$HOME/event_script.sh'"
b:1
...

We can specify the action that we want to be performed (in our case, launching the event_script.sh) and the mouse event leading to its triggering (b:1 corresponds to the left-click on the mouse). To update the xbindkeys with these changes, we can kill and start it again:

$ killall xbindkeys
$ xbindkeys

Every time we click the left mouse button, we’ll run the event_script.sh. By default, xbindbeys works on key-pressing events. However, we can also configure it to work on key release events.

4. With cnee to Track the X Session

Another simple solution from the implementation point-of-view is cnee. With cnee, we can record and replay X sessions among many other features. We can see in its output some values when we cause an event like pressing and releasing the left mouse button:

$ cnee --record --mouse
...
7,4,0,0,1,0,0,23384375,12,TPPS/2 IBM TrackPoint
6,4,0,0,1,0,0,23384375,2,Virtual core pointer
7,5,0,0,1,0,0,23384474,12,TPPS/2 IBM TrackPoint
6,5,0,0,1,0,0,23384474,2,Virtual core pointer
...

We’ve used the –record flag to display the events and the –mouse flag to select only those related to the mouse.

When pressing the mouse button, we get 7,4,0,0,1,0,0… and when releasing it we get 7,5,0,0,1,0,0… Thus, we can use this information to create a simple script to invoke other scripts when pressing and releasing the mouse button:

#!/bin/bash
cnee --record --mouse | while read line
do
  if [[ $(echo "$line" | grep '7,4,0,0,1,0,0') ]]
  then
    ./<script_button_pressing.sh>
  elif [[ $(echo "$line" | grep '7,5,0,0,1,0,0') ]]
  then
    ./<script_button_releasing.sh>
  fi
done

We used grep to search lines produced by cnee for the strings caused by the mouse events that we want. The script provided is a template and can be further customized for our needs.

5. Monitoring Mouse States With xinput

We might not have cnee or xbindkeys installed in our system, so the previous solutions are not valid if we don’t have super-user access. Still, there is one solution that doesn’t require any extra program: querying the status of the devices with xinput. We can query the mouse device twice in a short period of time and run the script if a mouse event has been detected. Based on this approach, let’s write a new script:

#!/bin/bash
STATE_1=$(xinput --query-state <mouse_id> | grep 'button' | sort)
while true
do 
  sleep 0.2 
  STATE2=$(xinput --query-state <mouse_id> | grep 'button' | sort) 
  if [[ $(comm -13 <(echo "$STATE1") <(echo "$STATE2")) ]] 
  then 
    STATE1=$STATE2 
    echo "Running another script"
  fi
done

In this script, we store the beginning mouse state in STATE1, keeping only the sorted lines containing the text “button”. Then, we enter a while loop that will continue running until we kill the script process. Inside the loop, we wait 0.2 seconds and then capture the mouse state again using the same command, only this time, we’re storing it in STATE2.

We then use comm to compare both states. The flag -13 is used to suppress lines unique to STATE1 and the lines common to both STATE1 and STATE2. If comm returns any difference, it means that there has been a mouse event. Thus, we can update the value of STATE1 for the next comparison and invoke the secondary script.

The previous script is just a template that we still need to customize for our case. First, we need to change the <mouse_id> to the one matching our system. Moreover, in this template, we’re getting the state of all the buttons and comparing them. However, we might want to limit the triggering of the script to a specific mouse button, triggering only in a given state (for example, when pressing a button and not when releasing) or to have different scripts for different buttons. We also may need to tweak the time during which we pause between loop iterations.

6. Conclusion

In this article, we’ve covered three ways to invoke shell scripts from mouse events: xbindkeys, cnee, and xinput. We should use xbindkeys if we just need press or release events. We saw that cnee allows more customization, but it’s also slightly more complex. Finally, we should use xinput if we don’t have super-user rights.