Original Site  

July 2005

Shell Corner: Mouse Reporting in a Shell Script

Hosted by Ed Schaefer

This month, Chris F.A. Johnson presents "Mouse Reporting in a Shell Script", which senses mouse clicks in xterm-type terminals under Linux, FreeBSD, and NetBSD.

Mouse Reporting in a Shell Script
by Chris F.A. Johnson

While reading the Linux console_codes man page, I came across a section labeled MOUSE TRACKING. Interesting! I read on:

The mouse tracking facility is intended to return xterm-compatible mouse status reports.

Does that mean, I wondered, that I can use the mouse in shell scripts?

According to the man page, mouse tracking is available in two modes: X10 compatibility mode, which sends an escape sequence on button press; and normal tracking mode, which sends an escape sequence on both button press and release. Both modes also send modifier information.

To test this, I went to a terminal window and entered printf "\e[?9h". I pressed the mouse button and the computer beeped at me and printed  FB. Repeating the mouse click at various points on the screen netted me more beeps and  &% -( 5. =2 H7 T= ]C fG rJ }M.

A mouse click sends 6 characters: ESC, [, M, <b>, <x>, <y>. The first 3 characters are common to all mouse events, the second 3 contain the button pressed, and the x and y locations of the mouse. To confirm this, I saved the input in a variable and piped it to hexdump:

$ printf "\e[?9h"
$ read x
$ printf "$x" | hexdump -C
00000000  1b 5b 4d 21 4d 4f                       |.[M!MO|

The first 3 appear as expected, but what are the final 3? According to the man page, the lower 2 bits of the button character tell which button has been pressed; the upper bits identify the active modifiers. The x and y coordinates are the ASCII values to which 32 has been added to take them out of the range of control characters. ! is 1, " is 2, etc..

That gives us a 1 for the mouse button (which means button 2, since 0 to 2 are buttons 1, 2, and 3 respectively, and 4 is release). The x and y coordinates are 45 ( M=77; 77−32=45) and 47.

Surprisingly, since I read about mouse tracking in a Linux console_codes man page, these escape codes do not work in any Linux console that I have tried. They work in xterm, rxvt, and gnome-terminal on Linux and FreeBSD. I've used them on FreeBSD and NetBSD, via ssh from a Linux rxvt terminal window. They do not work in a konsole window.

We now know that mouse reporting works (in most xterm windows), and we can get information from a mouse click on the standard input. That leaves two questions: how do we read the information into a variable (without having to press return), and how can the button and x,y information be decoded in a shell script?

With bash, the read command can take an argument to specify the number of characters:

read -sn6 x

More portably, stty and dd can be used:

_STTY=$(stty -g)          ## Save current terminal settings
printf "\e[?9h"           ## Turn on mouse reporting
stty -echo -icanon        ## Turn off echo and line buffering,
x=$(dd bs=1 count=6 2>/dev/null) ## Read six characters
echo "$x" | hexdump -C    ## Display the characters in hex
printf "\e[?9l"           ## Turn off mouse reporting
stty "$_STTY"             ## Restore terminal
Neither of these is adequate for a real script (not all input will be mouse clicks, and we will want to get single keystrokes), but these suffice to demonstrate the concept.

The next step is to decode the input. For the purpose of this demonstration, we will assume that the 6 characters do indeed represent a mouse click, and that the first 3 characters are ESC, [, and M. We are only interested in the last 3, so we extract them into three separate variables using POSIX parameter expansion:

m1=${x#???}    ## Remove the first 3 characters
m2=${x#????}   ## Remove the first 4 characters
m3=${x#?????}  ## Remove the first 5 characters

Then we convert the first character of each variable to its ASCII value. This uses a POSIX printf extension, "If the leading character is a single-quote or double-quote, the value shall be the numeric value in the underlying codeset of the character following the single-quote or double-quote."

Since command substitution is slow in all shells except KornShell93, the 3 assignments are grouped in a single eval statement:

eval "$(printf "mb=%d mx=%d my=%d" "'$m1" "'$m2" "'$m3")"

Finally, we interpret the ASCII values. For the mouse button, we do a bitwise AND 3. For the x and y coordinates, we subtract 32:

## Values > 127 are signed, so fix if less than 0
[ $mx -lt 0 ] && mx=$(( 255 + $mx ))
[ $my -lt 0 ] && my=$(( 255 + $my ))

BUTTON=$(( ($mb & 3) + 1 ))
MOUSEX=$(( $mx - 32 ))
MOUSEY=$(( $my - 32 ))
Putting it all together, the script in Listing 1 prints the mouse's coordinates at that location whenever you press a mouse button.

There are 2 buttons on the top row. Clicking the left button toggles the mouse reporting mode between reporting only a button press and reporting the release as well. Clicking the right-hand button exits the script.

You can add a button to clear the screen by making two simple changes to the script. Change:

print_buttons "$mv_str" "Exit"


print_buttons "$mv_str" "Clear" "Exit"


2) break ;;


2) clear ;; 3) break ;;
Copyright 2005 UnixReview.com, UnixReview.com's Privacy Policy. Comments about the Web site: webmaster@unixreview.com