What is the Mouse Doing?


Continuing from last week’s Lesson, once activated and configured, the output that mouse activity generates in a terminal window looks something like this:

^[[<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

This data follows a predictable pattern, which helps a mouse-happy program to read mouse input:

  • ^[ (\e) the escape character
  • [ left bracket
  • < less than character
  • nn mouse button data
  • ; semicolon separator
  • nn column (x) position
  • ; semicolon separator
  • nn row (y) position
  • M or m, where M is for movement or button press and m is for button release

For the mouse position, the upper left corner of the screen is location 1, 1. Values increase across to the right and vertically down, with the final value based on the screen size. For example:

^[[<35;49;15M

This data tidbit shows that the mouse is moving (or no button is pressed) and is currently at column 49, row 15.

^[[<0;46;16M^[[<0;46;16m

This pair shows mouse button zero (the left button) is clicked at column 46, row 16 and released at the same character position. Here are the mouse button values:

Value Button
0 Left
1 Middle
2 Right
35 (Movement)
64 Scroll up
65 Scroll down
+4 Shift key pressed
+8 Alt key pressed
+16 Ctrl key pressed

The following code configures the terminal window to read the mouse and generate output reporting the mouse's position and clicks as documented above:

2026_02_07-Lesson-a.c

#include <stdio.h>
#include <unistd.h>

#define mouse_enable() printf("\e[?1000h")
#define mouse_extended() printf("\e[?1006h")
#define mouse_disable() printf("\e[?1000l")

int main()
{
    char buffer[12];
    int key;

    /* enable mouse reporting */
    mouse_enable();
    mouse_extended();
    /* remove line buffering */
    setvbuf(stdin,NULL,_IONBF,0);

    /* read stdin */
    puts("Click the mouse; press Enter to end");
    while( (key=getchar()) != '\n' )
    {
        read(fileno(stdin),buffer,12);
        write(fileno(stdout),buffer,12);
    }

    /* clean-up */
    mouse_disable();    /* disable mouse reporting */
    return 0;
}

This code activates the mouse and turns on extended monitoring, which ensures that the output is something that a human can read. It also calls the setvbuf() function to disable input buffering. Normally this function is used on stdout, but keep in mind that the mouse data is reported by standard input. This aspect of reading mouse data is weird, plus it creates some interesting problems that I cover later.

The main part of the program is a while loop that uses the read() function to obtain standard input in 12-character chunks. This information is then written to standard output. The loop continues until the Enter key is pressed.

Before the program quits, the mouse_disable() macro is called. If you accidentally quit the program (press Ctrl+C), remember to use the reset command in the terminal window to stop mouse reporting from overwhelming the screen.

Here's a sample run:

Click the mouse; press Enter to end
^[[<0;1;1M^[[<0;1;1m^[[<0;1;1M^[[<0;1;1m^[[<0;11;2M^[[<0;11;2m
[<0;1;1M
2M

The garbage you see on the last two lines of output consists of leftover characters flowing from somewhere. You may need to press Enter a few times to end the program, which is why the following update provides better mouse monitoring:

2026_02_07-Lesson-b.c

#include <stdio.h>
#include <termios.h>
#include <unistd.h>

#define mouse_enable() printf("\e[?1000h")
#define mouse_extended() printf("\e[?1006h")
#define mouse_disable() printf("\e[?1000l")

int main()
{
    struct termios original,noecho;
    char buffer[12];
    int key;

    /* obtain terminal configuration */
    tcgetattr(fileno(stdin),&original);
    noecho = original;        /* duplicate */
        /* enable raw input */
    noecho.c_lflag = noecho.c_lflag ^ ICANON;
        /* disable full duplex */
    noecho.c_lflag = noecho.c_lflag ^ ECHO;
        /* update terminal definition */
    tcsetattr(fileno(stdin), TCSANOW, &noecho);

    /* enable mouse reporting */
    mouse_enable();
    mouse_extended();
    /* remove line buffering */
    setvbuf(stdin,NULL,_IONBF,0);

    /* read stdin */
    puts("Click the mouse; press Enter to end");
    while( (key=getchar()) != '\n' )
    {
        read(fileno(stdin),buffer,12);
        write(fileno(stdout),buffer,12);
        putchar('\n');
    }

    /* clean-up */
    mouse_disable();    /* disable mouse reporting */
    tcsetattr(fileno(stdin), TCSANOW, &original);
    return 0;
}

The primary update to this version of the code is adding terminal control via the tcsetattr() function. Raw data input is enabled and duplex output (local echo) is disabled. I also added a newline output in the while loop, which presents the mouse data vertically.

The program runs the same, with the output is presented in a single column:

Click the mouse; press Enter to end
[<0;64;21M
[<0;64;21m
[<0;58;21M
[<0;58;21m
[<0;31;11M
[<0;31;11m
[<0;1;1M1m
[<0;1;1m1m

This program reads the mouse data, but only clicks. In next week's Lesson, I update the code with the ANSI command to monitor mouse movement.

Leave a Reply