
After I bought a mouse for my first PC, I set out to write a mouse-based program — a game. (The Microsoft Mouse manual came with the full API.) It was fun and challenging, as all programming tasks should be. Surprisingly, reading the mouse is also possible in a Linux terminal window — providing that you know the secret.
The secret: ANSI.
Yes, just as various ANSI commands let you manipulate text output and cursor position, you can also use ANSI commands to read the mouse’s location, movement, and clicks in a terminal window. But this trick isn’t without its issues.
To start, the commands to read the mouse are not part of the original ANSI standard. They are, however, available with the xterm extensions, which are implemented in most terminal windows.
Like most other ANSI commands, the mouse commands are delivered by sending a specific string of characters to standard output. The sequence starts with an Escape character (\e) followed by a left bracket and a question mark. Then comes a mode value and finally a little H or little L.
The sequence \e[?1000h activates mouse tracking.
The sequence \e[?1000l deactivates mouse tracking.
The following code implements these escape sequences as macros, mouse_enable() and mouse_disable():
2026_01_31-Lesson-a.c
#include <stdio.h>
#define mouse_enable() printf("\e[?1000h")
#define mouse_disable() printf("\e[?1000l")
int main()
{
/* enable mouse reporting */
mouse_enable();
getchar();
/* clean-up */
mouse_disable(); /* disable mouse reporting */
return 0;
}
It’s important that you disable mouse tracking before the program ends. Failing to do means that mouse activity in the terminal window continues to be monitored, which can really ruin your day. (Issue the reset command to restore the terminal to sanity.)
Running this program generates no output unless you click the mouse. When you do, output appears similar to this:
^[[M ;4^[[M#;4
Press the Enter key to terminate the program.
When you click the mouse, the output you see reports the button clicked and the mouse’s location in some cryptic format. What’s better is to also issue the ANSI “any event” tracking sequence, \e[?1003h, which provides more information about the mouse’s location and button status. The following code provides this update:
2026_01_31-Lesson-b.c
#include <stdio.h>
#define mouse_enable() printf("\e[?1000h")
#define mouse_motion() printf("\e[?1003h")
#define mouse_disable() printf("\e[?1000l")
int main()
{
/* enable mouse reporting */
mouse_enable();
mouse_motion();
getchar();
/* clean-up */
mouse_disable(); /* disable mouse reporting */
return 0;
}
The mouse_motion() macro is assigned to the \e[?1003h escape sequence. When you run the program, moving the mouse immediately generates output. You see something like this:
^[[MCY.^[[MCZ-^[[MCZ.^[[MCY/^[[MCX/^[[MCW0^[[MCV0^[[MCV1^[[MCU1^[[MCU2^[[MCU1^[[MCT1^[[MCS1^[[MCS0^[[MCR0^[[M R0^[[M#R0^[[M R0^[[M#R0
Each sequence output represents the mouse’s movement from one character position to the next, as well as any button clicks.
The output can be quite overwhelming, rapidly filling the screen. It’s also cryptic. To make the output human readable, I recommend adding a fourth macro to the code, mouse_extended(), as shown in this final update:
2026_01_31-Lesson-c.c
#include <stdio.h>
#define mouse_enable() printf("\e[?1000h")
#define mouse_motion() printf("\e[?1003h")
#define mouse_extended() printf("\e[?1006h")
#define mouse_disable() printf("\e[?1000l")
int main()
{
/* enable mouse reporting */
mouse_enable();
mouse_motion();
mouse_extended();
getchar();
/* clean-up */
mouse_disable(); /* disable mouse reporting */
return 0;
}
The mouse_extended() escape sequence is \e[?1006h. Here’s a sample run with this macro issued:
^[[<35;49;15M^[[<35;49;16M^[[<35;49;17M^[[<35;48;17M^[[<35;48;16M^[[<35;47;16M^[[<35;47;17M^[[<35;47;16M^[[<35;46;16M^[[<0;46;16M^[[<0;46;16m
The sequences generated follow a pattern that offers the mouse's button status, row and column location on the terminal screen. I cover deciphering this information in next week's Lesson.