ESC[<0;12;7M
(that means search for the character for <Escape> in the table, then input the '[', then the '<', then the ';' then the '1'then the '2', then the ';', then the '7' then the 'M'.
3. Then save that file.
4. Then input that file into the C .exe program:
decode_sgr_mouse.exe <decode_sgr_mouse.txt
5. And the output is expected:
g:\language\computer\cpp\embarcadero\borland\bcc55\bin Mon 13-10-25 13:31:48>decode_sgr_mouse.exe <decode_sgr_mouse.txtSGR mouse decoder (stdin -> events). Paste sequences like ESC[<0;12;7MTip: In many shells you can enter ESC as \e or using Ctrl+V ESC.Decoding until EOF...
BUTTON LeftBtn @ (12,7) mods=none b=0 end='M'

./decode_sgr_mouse_gcc

Awesome — here’s a self-contained Linux version that enables SGR mouse reporting in your terminal, switches stdin to raw mode, parses the incoming escape codes (SGR / DECSET 1006), and prints normalized events. It’s pure C, compiles with gcc on Ubuntu, and cleans up properly on exit (restores terminal + disables mouse reporting).
Compile:
gcc -O2 -Wall -std=c99 -o decode_sgr_mouse decode_sgr_mouse_linux.c
Run (best in xterm or a terminal that supports SGR mouse):
./decode_sgr_mouse
# move and click; press Ctrl+C to exit
/* decode_sgr_mouse_linux.c
Read xterm SGR mouse events from stdin and print decoded actions.
- Enables DECSET 1002 (button+drag) and 1006 (SGR coordinates)
- Sets terminal to raw mode; restores on exit or Ctrl+C
- Decodes:
* Left/Middle/Right press & release
* Wheel Up/Down/Left/Right (press only)
* Modifiers: SHIFT(+4), ALT(+8), CTRL(+16)
* Motion bit (+32) if present
Build: gcc -O2 -Wall -std=c99 -o decode_sgr_mouse decode_sgr_mouse_linux.c
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <termios.h>
#include <unistd.h>
#include <signal.h>
/* ---------- Minimal booleans for portability ---------- */
typedef int bool_t;
#define TRUE 1
#define FALSE 0
/* ---------- Terminal state ---------- */
static struct termios g_orig;
static bool_t g_raw = FALSE;
static void term_enable_raw(void) {
if (tcgetattr(STDIN_FILENO, &g_orig) == -1) return;
struct termios t = g_orig;
/* raw-ish: no canonical, no echo, min 1 byte, no signals */
t.c_lflag &= ~(ICANON | ECHO | IEXTEN | ISIG);
t.c_iflag &= ~(IXON | ICRNL | BRKINT | INPCK | ISTRIP);
t.c_cflag |= (CS8);
t.c_oflag &= ~(OPOST);
t.c_cc[VMIN] = 1;
t.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSANOW, &t) == -1) return;
g_raw = TRUE;
}
static void term_restore(void) {
if (g_raw) tcsetattr(STDIN_FILENO, TCSANOW, &g_orig);
g_raw = FALSE;
}
/* ---------- Mouse reporting on/off ---------- */
static void mouse_enable(void) {
/* DECSET 1002 (Btn+Drag) and 1006 (SGR enc) */
write(STDOUT_FILENO, "\x1b[?1002h\x1b[?1006h", 16);
/* flush so terminal gets it now */
fflush(stdout);
}
static void mouse_disable(void) {
write(STDOUT_FILENO, "\x1b[?1002l\x1b[?1006l", 16);
fflush(stdout);
}
/* ---------- Clean exit ---------- */
static void on_exit_cleanup(void) {
mouse_disable();
term_restore();
}
static void sigint_handler(int sig) {
(void)sig;
on_exit_cleanup();
_exit(0);
}
/* ---------- Parser for ESC [ < b ; x ; y (M|m) ---------- */
typedef enum {
ST_IDLE = 0,
ST_ESC,
ST_CSI,
ST_LT,
ST_B,
ST_SEMI1,
ST_X,
ST_SEMI2,
ST_Y,
ST_END
} State;
typedef struct {
int b, x, y;
bool_t has_b, has_x, has_y;
char end_ch; /* 'M' or 'm' */
bool_t valid;
} SgrEvent;
static void reset_event(SgrEvent *ev) {
ev->b = ev->x = ev->y = 0;
ev->has_b = ev->has_x = ev->has_y = FALSE;
ev->end_ch = 0;
ev->valid = FALSE;
}
static int read_byte(void) {
unsigned char c;
ssize_t n = read(STDIN_FILENO, &c, 1);
if (n <= 0) return -1;
return (int)c;
}
static bool_t parse_sgr_event(SgrEvent *ev) {
State st = ST_IDLE;
long num = 0;
for (;;) {
int c = read_byte();
if (c < 0) return FALSE; /* EOF */
switch (st) {
case ST_IDLE:
if (c == 0x1B) st = ST_ESC; /* ESC */
break;
case ST_ESC:
st = (c == '[') ? ST_CSI : ST_IDLE;
break;
case ST_CSI:
if (c == '<') { st = ST_LT; num = 0; }
else st = ST_IDLE;
break;
case ST_LT: /* b */
if (isdigit(c)) { num = c - '0'; st = ST_B; }
else st = ST_IDLE;
break;
case ST_B:
if (isdigit(c)) {
num = num * 10 + (c - '0');
if (num > 1000000L) num = 1000000L;
} else if (c == ';') {
ev->b = (int)num; ev->has_b = TRUE; num = 0; st = ST_SEMI1;
} else st = ST_IDLE;
break;
case ST_SEMI1: /* x */
if (isdigit(c)) { num = c - '0'; st = ST_X; }
else st = ST_IDLE;
break;
case ST_X:
if (isdigit(c)) {
num = num * 10 + (c - '0');
if (num > 1000000L) num = 1000000L;
} else if (c == ';') {
ev->x = (int)num; ev->has_x = TRUE; num = 0; st = ST_SEMI2;
} else st = ST_IDLE;
break;
case ST_SEMI2: /* y */
if (isdigit(c)) { num = c - '0'; st = ST_Y; }
else st = ST_IDLE;
break;
case ST_Y:
if (isdigit(c)) {
num = num * 10 + (c - '0');
if (num > 1000000L) num = 1000000L;
} else if (c == 'M' || c == 'm') {
ev->y = (int)num; ev->has_y = TRUE; ev->end_ch = (char)c;
ev->valid = (ev->has_b && ev->has_x && ev->has_y);
return TRUE;
} else st = ST_IDLE;
break;
default: st = ST_IDLE; break;
}
}
}
/* Decode helpers */
static const char* button_name_from_b(int b_raw, int* is_wheel, int* is_button, int* is_motion) {
int base = b_raw;
int wheel = 0, button = 0, motion = 0;
if (base & 32) { motion = 1; base -= 32; }
/* Strip modifiers for base classification */
if (base >= 16) base -= 16;
if (base >= 8) base -= 8;
if (base >= 4) base -= 4;
if (base >= 64 && base <= 67) { wheel = 1; }
else if (base >= 0 && base <= 2) { button = 1; }
if (is_wheel) *is_wheel = wheel;
if (is_button) *is_button = button;
if (is_motion) *is_motion = motion;
if (wheel) {
switch (base) {
case 64: return "WheelUp";
case 65: return "WheelDown";
case 66: return "WheelLeft";
case 67: return "WheelRight";
default: return "Wheel(?)";
}
}
if (button) {
switch (base) {
case 0: return "LeftBtn";
case 1: return "CenterBtn";
case 2: return "RightBtn";
default: return "Btn(?)";
}
}
return "Unknown";
}
static void build_mods(int b, char* out, size_t outsz) {
int hasShift = (b & 4) ? 1 : 0;
int hasAlt = (b & 8) ? 1 : 0;
int hasCtrl = (b & 16) ? 1 : 0;
char buf[64]; buf[0] = '\0';
int first = 1;
if (hasCtrl) { strcat(buf, "CTRL"); first = 0; }
if (hasAlt) { strcat(buf, first ? "ALT" : "+ALT"); first = 0; }
if (hasShift) { strcat(buf, first ? "SHIFT" : "+SHIFT"); first = 0; }
if (first) strcpy(buf, "none");
if (outsz) {
buf[sizeof(buf)-1] = '\0';
strncpy(out, buf, outsz-1);
out[outsz-1] = '\0';
}
}
static void print_event(const SgrEvent* ev) {
char mods[64];
int is_wheel=0, is_button=0, is_motion=0;
const char* name = button_name_from_b(ev->b, &is_wheel, &is_button, &is_motion);
const char* kind = is_wheel ? "WHEEL" : (is_button ? "BUTTON" : "OTHER");
const char* phase;
build_mods(ev->b, mods, sizeof(mods));
if (is_wheel) {
phase = "PRESS"; /* wheels don't send release */
} else if (is_button) {
phase = (ev->end_ch == 'm') ? "RELEASE" : (is_motion ? "DRAG" : "PRESS");
} else {
phase = (ev->end_ch == 'm') ? "RELEASE?" : "PRESS?";
}
printf("%s %-10s %-7s @ (%d,%d) mods=%-13s b=%d end='%c'%s\n",
kind, name, phase, ev->x, ev->y, mods, ev->b,
(ev->end_ch ? ev->end_ch : '?'),
is_motion ? " (MOTION)" : "");
fflush(stdout);
}
int main(void) {
/* Arrange cleanup */
signal(SIGINT, sigint_handler);
signal(SIGTERM, sigint_handler);
atexit(on_exit_cleanup);
/* Prepare terminal + mouse */
term_enable_raw();
mouse_enable();
fprintf(stderr,
"SGR mouse decoder (Linux). Raw mode enabled, SGR mouse enabled.\n"
"Click / scroll / drag in this terminal. Press Ctrl+C to exit.\n\n");
/* Main loop */
for (;;) {
SgrEvent ev;
reset_event(&ev);
if (!parse_sgr_event(&ev)) break; /* EOF */
if (ev.valid) print_event(&ev);
}
return 0;
}
Use an xterm (or any terminal that supports DECSET 1006). GNOME Terminal/Alacritty/Kitty also support SGR mouse, but some multiplexers (tmux/screen) may need mouse options enabled too.
The program enables ?1002 (button + drag tracking) and ?1006 (SGR encoding) on start, and disables both on exit.
Output examples:
BUTTON LeftBtn PRESS @ (12,7) mods=none b=0 end='M'
BUTTON LeftBtn DRAG @ (13,7) mods=SHIFT b=36 end='M' (MOTION)
BUTTON LeftBtn RELEASE @ (13,7) mods=SHIFT b=36 end='m'
WHEEL WheelUp PRESS @ (20,5) mods=CTRL+SHIFT b=84 end='M'
If you want me to emit tokens tailored for TSE (e.g., CTRLALT+WheelLeft x y) or to coalesce press/drag/release into gestures, I can fold that in too.
./decode_sgr_mouse_gcc
