TSE: Linux: Mouse: Implementation: Feature: Request: Possible solutions

3 views
Skip to first unread message

knud van eeden

unread,
Oct 13, 2025, 7:32:41 AMOct 13
to SemWare TSE Pro Text Editor, S.E. Mitchell
Hello,

> Create a C program for Borland version 5.5.1 command line compiler that
> can handle those escape codes in an Linux xterm terminal. Input is thus
> the escape code and output is the mouse action detected.

Here is a single-file C program that **decodes SGR mouse escape sequences** (e.g. `\x1B[<b;x;yM` and `\x1B[<b;x;ym`) and prints the detected mouse action plus modifiers. It is written to compile cleanly with **Borland C++ 5.5.1** (no C99 features, no `stdbool.h`, no fancy I/O). It just reads bytes from **stdin**, so you can paste or pipe escape sequences into it.

It handles:

* Left / Middle / Right button **press** and **release**
* WheelUp / WheelDown / WheelLeft / WheelRight (press-only events)
* Modifier bits: **Shift** (+4), **Alt** (+8), **Ctrl** (+16)
* Motion bit (+32) if present (reported when tracking is enabled)

Save as `decode_sgr_mouse.c` and compile with:

bcc32 decode_sgr_mouse.c

Run and paste escape sequences (or pipe them) to see decoded events.

---

--- cut here: begin --------------------------------------------------

/* decode_sgr_mouse.c
   Decode xterm SGR (DECSET 1006) mouse sequences from stdin and print
   the detected action, coordinates, modifiers, and press/release/motion.

   Compatible with Borland C++ 5.5.1 (Win32 command-line tools).
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/* Simple bool replacement for old compilers */
typedef int bool_t;
#define TRUE  1
#define FALSE 0

/* Parser states for ESC [ < b ; x ; y (M|m) */
typedef enum {
    ST_IDLE = 0,
    ST_ESC,
    ST_CSI,   /* after ESC [ */
    ST_LT,    /* after ESC [ < */
    ST_B,     /* reading b */
    ST_SEMI1, /* saw first ';' */
    ST_X,     /* reading x */
    ST_SEMI2, /* saw second ';' */
    ST_Y,     /* reading y */
    ST_END    /* expect 'M' or 'm' */
} State;

/* Event representation */
typedef struct {
    int b;
    int x;
    int y;
    bool_t has_b;
    bool_t has_x;
    bool_t has_y;
    char end_ch;  /* 'M' press/drag, 'm' release (for buttons 0..2) */
    bool_t valid;
} SgrEvent;

/* Forward declarations */
static int read_byte(void);
static bool_t parse_sgr_event(SgrEvent* ev);
static void reset_event(SgrEvent* ev);
static void print_event(const SgrEvent* ev);
static const char* button_name_from_b(int b, int* is_wheel, int* is_button, int* is_motion);
static void build_mod_string(int b, char* out, size_t outsz);

int main(void)
{
    SgrEvent ev;
    fprintf(stderr,
        "SGR mouse decoder (stdin -> events). Paste sequences like ESC[<0;12;7M\n"
        "Tip: In many shells you can enter ESC as \\e or using Ctrl+V ESC.\n"
        "Decoding until EOF...\n\n");

    /* Keep parsing events from the byte stream */
    for (;;) {
        reset_event(&ev);
        if (!parse_sgr_event(&ev)) {
            /* EOF encountered while idle -> done */
            break;
        }
        if (ev.valid) {
            print_event(&ev);
        }
        /* Otherwise, if invalid, continue scanning. */
    }
    return 0;
}

/* Read one byte from stdin; return -1 on EOF */
static int read_byte(void)
{
    int c = getchar();
    if (c == EOF) return -1;
    return c & 0xFF;
}

/* Reset event fields */
static void reset_event(SgrEvent* ev)
{
    ev->b = 0;
    ev->x = 0;
    ev->y = 0;
    ev->has_b = FALSE;
    ev->has_x = FALSE;
    ev->has_y = FALSE;
    ev->end_ch = 0;
    ev->valid = FALSE;
}

/* Parse one SGR event from the stream.
   Returns TRUE if not at EOF overall (keep running),
   sets ev->valid TRUE when a full event was decoded.
*/
static bool_t parse_sgr_event(SgrEvent* ev)
{
    State st = ST_IDLE;
    int c;
    long num = 0;

    for (;;) {
        c = read_byte();
        if (c < 0) {
            /* EOF */
            return (st != ST_IDLE); /* return FALSE only if we are idle */
        }

        switch (st) {
        case ST_IDLE:
            if (c == 0x1B) { /* ESC */
                st = ST_ESC;
            }
            /* else ignore any other input while idle */
            break;

        case ST_ESC:
            if (c == '[') {
                st = ST_CSI;
            } else {
                st = ST_IDLE; /* not a CSI; reset */
            }
            break;

        case ST_CSI:
            if (c == '<') {
                st = ST_LT;
                num = 0;
            } else {
                /* not SGR mouse; reset to idle (ignore other CSI sequences) */
                st = ST_IDLE;
            }
            break;

        case ST_LT: /* reading b (first number) */
            if (isdigit(c)) {
                num = (long)(c - '0');
                st = ST_B;
            } else {
                /* malformed; restart */
                st = ST_IDLE;
            }
            break;

        case ST_B:
            if (isdigit(c)) {
                num = num * 10 + (c - '0');
                if (num > 1000000L) num = 1000000L; /* clamp */
            } else if (c == ';') {
                ev->b = (int)num;
                ev->has_b = TRUE;
                num = 0;
                st = ST_SEMI1;
            } else {
                st = ST_IDLE; /* malformed */
            }
            break;

        case ST_SEMI1: /* reading x */
            if (isdigit(c)) {
                num = (long)(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: /* reading y */
            if (isdigit(c)) {
                num = (long)(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; /* one full event parsed */
            } else {
                /* Unexpected char; go idle */
                st = ST_IDLE;
            }
            break;

        default:
            st = ST_IDLE;
            break;
        }
    }
}

/* Map b to a human-friendly name; also tell if it is wheel/button/motion.
   Returns a static string literal.
*/
static const char* button_name_from_b(int b, int* is_wheel, int* is_button, int* is_motion)
{
    int base = b;
    int wheel = 0, button = 0, motion = 0;

    /* Motion bit (often set when dragging/moving with a button held) */
    if (base & 32) {
        motion = 1;
        base -= 32;
    }

    /* Modifiers add 4/8/16; strip them to read the base */
    if (base >= 16) base -= 16;
    if (base >= 8)  base -= 8;
    if (base >= 4)  base -= 4;

    /* Wheel events are 64..67 (after removing modifier bits above) */
    if (base >= 64 && base <= 67) {
        wheel = 1;
        button = 0;
    } else if (base >= 0 && base <= 2) {
        button = 1;
        wheel  = 0;
    } else {
        /* Unknown base (could be button 3 or other extensions) */
        wheel = 0; button = 0;
    }

    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(?)";
        }
    }
    /* Unknown/extended */
    return "Unknown";
}

/* Build modifier string like "CTRL+ALT+SHIFT" or "none" */
static void build_mod_string(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];
    int first = 1;

    buf[0] = '\0';

    if (hasCtrl) {
        strcat(buf, "CTRL");
        first = 0;
    }
    if (hasAlt) {
        if (!first) strcat(buf, "+");
        strcat(buf, "ALT");
        first = 0;
    }
    if (hasShift) {
        if (!first) strcat(buf, "+");
        strcat(buf, "SHIFT");
        first = 0;
    }
    if (first) {
        strcpy(buf, "none");
    }

    /* Copy to out safely */
    if (outsz > 0) {
        buf[sizeof(buf)-1] = '\0';
        strncpy(out, buf, outsz - 1);
        out[outsz - 1] = '\0';
    }
}

/* Pretty-print one decoded event */
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 = "UNKNOWN";
    const char* phase = "";
    char motion_note[32];

    build_mod_string(ev->b, mods, sizeof(mods));

    if (is_wheel) {
        kind = "WHEEL";
        phase = "PRESS"; /* wheels only send press-type packets */
    } else if (is_button) {
        kind = "BUTTON";
        phase = (ev->end_ch == 'm') ? "RELEASE" : "PRESS/DRAG";
    } else {
        kind = "OTHER";
        phase = (ev->end_ch == 'm') ? "RELEASE?" : "PRESS?";
    }

    if (is_motion) {
        strcpy(motion_note, " (MOTION)");
    } else {
        motion_note[0] = '\0';
    }

    printf("%s %s%s @ (%d,%d)  mods=%s  b=%d end='%c'\n",
           kind, name, motion_note, ev->x, ev->y, mods, ev->b,
           (ev->end_ch ? ev->end_ch : '?'));
}

--- cut here: end ----------------------------------------------------

===

### How to test quickly

You can feed sample sequences directly. In Windows `cmd.exe` you can
echo with a real ESC via `echo` + Alt codes is awkward; easiest is to
create a tiny text file containing the bytes. But for a quick sanity
check, you can compile and then paste sequences from a terminal that can
send them (e.g., WSL/Ubuntu xterm) into the program's stdin, or pipe
from a file captured from your Linux terminal.

Examples of sequences the program understands:

* `ESC[<0;12;7M` LeftBtn press at (12,7)
* `ESC[<20;12;7m` LeftBtn release with Ctrl+Shift (16+4 20)
* `ESC[<72;14;9M` Alt+WheelUp at (14,9)
* `ESC[<84;20;5M` Ctrl+Shift+WheelUp at (20,5)
* `ESC[<66;30;10M` WheelLeft
* `ESC[<2;40;15M`  RightBtn press
* `ESC[<2;40;15m`  RightBtn release

If you want, I can extend this to:

* emit **normalized tokens** you can consume from TSE (e.g.,
  `CTRL+ALT+LeftBtn PRESS x y`)

* detect **drag/motion** explicitly when `b` contains the `+32` motion
  bit (many terminals set this during movement with a button held)

* add a small **stateful filter** to coalesce press motion release
  into a single logical gesture.

===

I compiled the C++ program and it worked out of the box:

--- cut here: begin --------------------------------------------------

g:\language\computer\cpp\embarcadero\borland\bcc55\bin Mon 13-10-25 13:27:04>bcc32 decode_sgr_mouse.c

Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland

decode_sgr_mouse.c:

Warning W8004 decode_sgr_mouse.c 224: 'button' is assigned a value that is never used in function button_name_from_b
Warning W8004 decode_sgr_mouse.c 224: 'wheel' is assigned a value that is never used in function button_name_from_b
Warning W8004 decode_sgr_mouse.c 343: 'phase' is assigned a value that is never used in function print_event
Warning W8004 decode_sgr_mouse.c 317: 'kind' is assigned a value that is never used in function print_event

Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland

g:\language\computer\cpp\embarcadero\borland\bcc55\bin Mon 13-10-25 13:27:37>dt

 Volume in drive G is KINGSTON 1 TERABYTE Serial number is c0bb:ef1a
 Directory of  G:\LANGUAGE\COMPUTER\CPP\EMBARCADERO\BORLAND\BCC55\Bin\*

13-10-2025  13:27           9,231  decode_sgr_mouse.c
13-10-2025  13:27           3,430  decode_sgr_mouse.obj
13-10-2025  13:27          55,808  decode_sgr_mouse.exe
13-10-2025  13:27         393,216  decode_sgr_mouse.tds
13-10-2025  13:27         <DIR>    .

--- cut here: end ----------------------------------------------------

To be tested further.

Thanks 
with friendly greetings
Knud van Eeden
decode_sgr_mouse.c

knud van eeden

unread,
Oct 13, 2025, 7:48:21 AMOct 13
to SemWare TSE Pro Text Editor, S.E. Mitchell
Yes, that C program works out of the box.

I tested it as follows:

1. Opened an file with the arbitrary name:

    decode_sgr_mouse.txt

2. Then to create an escape code example,
   I used the 

     TSE menu > 'Utils' > 'AsciiChart' 

   then looking for the Escape code characters 
   to input those escape codes.

E.g. 

 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.txt
SGR mouse decoder (stdin -> events). Paste sequences like ESC[<0;12;7M
Tip: 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'

with friendly greetings
Knud van Eeden


Inline image


knud van eeden

unread,
Oct 13, 2025, 8:35:50 AMOct 13
to SemWare TSE Pro Text Editor, S.E. Mitchell
Hello,

> Create a similar working C program, but which can be compiled using gcc on Linux Ubuntu.

===

I tested the resulting .c program as follows:

 1. Linux Ubuntu (tested only with non-WSL)

 2. Open a terminal in Linux Ubuntu (tested only with non-WSL)

 3. Compile the attached .c program

     gcc -O2 -Wall -std=c99 -o decode_sgr_mouse_gcc decode_sgr_mouse_gcc.c

 4. Run the resulting executable

     ./decode_sgr_mouse_gcc

 5. Press the mouse buttons to test and see the output of those actions with the mouse 
    in that terminal.

E.g. 

BUTTON LeftBtn @ (12,7)  mods=none  b=0 end='M'

with friendly greetings
Knud van Eeden

Inline image


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;
}

Notes & tips

  • 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.

with friendly greetings
Knud van Eeden




decode_sgr_mouse_gcc.c

knud van eeden

unread,
Oct 13, 2025, 9:03:22 AMOct 13
to SemWare TSE Pro Text Editor, S.E. Mitchell
Update:

It also runs inside an xterm terminal when testing in Linux Ubuntu 'WSL':

I tested the resulting .c program as follows:

 1. Linux Ubuntu (tested with WSL)

 2. Compile the attached .c program

     gcc -O2 -Wall -std=c99 -o decode_sgr_mouse_gcc decode_sgr_mouse_gcc.c

 3. You must open an xterm terminal first
    Type on the command line (without the $)

     xterm

 4. That opens an xterm terminal

 5. Run the resulting executable

     ./decode_sgr_mouse_gcc

 6. Press the mouse buttons to test and see the output of those actions with the mouse 
    in that terminal.

E.g. 

BUTTON LeftBtn @ (12,7)  mods=none  b=0 end='M'

Inline image



Reply all
Reply to author
Forward
0 new messages