Here is a C program that demonstrates how to capture and display mouse
X,Y coordinates using the X10 mouse reporting mode. This is the protocol
your TSE macro is currently relying on.
This program will highlight the limitation you are experiencing with
column 94.
--- cut here: begin --------------------------------------------------
#include <stdio.h> // For printf, getchar, puts, fputs, stdout, stderr
#include <stdlib.h> // For exit
#include <unistd.h> // For STDIN_FILENO, read
#include <termios.h> // For termios functions (raw mode)
#include <string.h> // For strlen
// Function to set terminal to raw mode
// In raw mode, input is read character by character without buffering
// and special characters like Ctrl+C are not processed by the terminal.
void set_raw_mode(struct termios *old_termios) {
struct termios new_termios;
// Get current terminal attributes
if (tcgetattr(STDIN_FILENO, old_termios) == -1) {
perror("tcgetattr");
exit(EXIT_FAILURE);
}
new_termios = *old_termios; // Copy old settings
// Disable canonical mode (ICANON), echo input (ECHO),
// and signal characters (ISIG).
new_termios.c_lflag &= ~(ICANON | ECHO | ISIG);
// Disable IXON (Ctrl+S/Ctrl+Q flow control), ICRNL (map CR to NL)
new_termios.c_iflag &= ~(IXON | ICRNL);
// Disable output post-processing (OPOST)
new_termios.c_oflag &= ~(OPOST);
// Set minimum number of characters for non-canonical read (VMIN) to 1
// and timeout (VTIME) to 0. This means read will return after 1 character.
new_termios.c_cc[VMIN] = 1;
new_termios.c_cc[VTIME] = 0;
// Set new terminal attributes immediately
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) == -1) {
perror("tcsetattr");
exit(EXIT_FAILURE);
}
}
// Function to restore original terminal mode
void restore_termios(struct termios *old_termios) {
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, old_termios) == -1) {
perror("tcsetattr restore");
}
}
int main() {
struct termios old_termios;
int c;
char buffer[5]; // Buffer to hold ESC[M<btn><x><y> sequence
// Set terminal to raw mode
set_raw_mode(&old_termios);
// Enable X10 (Normal) mouse tracking
// ESC[?1000h: Enable X10 mouse protocol
// In this mode, mouse events are reported as ESC[M<button><x><y>
// where <button>, <x>, <y> are ASCII characters with their value + 32.
// X10 mode has a coordinate limit (typically 0-223 for X/Y, due to byte values).
fputs("\x1b[?1000h", stdout);
fflush(stdout); // Ensure the escape sequence is sent immediately
printf("X10 Mouse Coordinate Viewer\n");
printf("Click mouse anywhere in the terminal. Press 'q' to quit.\n");
printf("----------------------------------\n");
while (1) {
c = getchar(); // Read one character at a time
// Check for Escape character (often 27) - could be part of mouse sequence or user exit
if (c == '\x1b') {
// Read next character for mouse sequence detection
// Most escape sequences start with '['
c = getchar();
if (c == '[') {
c = getchar();
// Check for 'M' which indicates an X10 mouse report
if (c == 'M') {
// Read the 3 bytes for button, x, y
// These are raw ASCII values which represent (coordinate + 32)
int btn = getchar();
int x_char = getchar();
int y_char = getchar();
// Convert raw ASCII values back to coordinates
// Coordinates are 1-indexed
int mouse_x = x_char - 32;
int mouse_y = y_char - 32;
// Print raw ASCII values and calculated coordinates
printf("Mouse Event Detected:\n");
printf(" Raw Button Char: %d (0x%02x), Decoded Button: %d\n", btn, btn, btn);
printf(" Raw X Char: %d (0x%02x), Decoded X: %d\n", x_char, x_char, mouse_x);
printf(" Raw Y Char: %d (0x%02x), Decoded Y: %d\n", y_char, y_char, mouse_y);
printf("----------------------------------\n");
// IMPORTANT NOTE ON LIMITATION:
// If x_char or y_char values exceed 127 (e.g., for coordinates > 95),
// they might be interpreted differently or cause issues.
// Some terminals/fonts might interpret values > 127 as multi-byte
// characters, leading to corrupted output or behavior.
// This is why you likely see the behavior around column 93/94.
// (93 + 32 = 125, 94 + 32 = 126, 95 + 32 = 127).
// Characters 128-255 often behave unpredictably depending on locale.
} else {
// Not an X10 mouse report, could be another escape sequence.
// You'd need more robust parsing for other sequences.
// For simplicity, just consume remaining chars of this sequence.
while (c != EOF && c != 'M' && c != '\x1b' && c != '\n') {
c = getchar();
}
}
} else if (c == '\x1b') { // If user presses ESC twice in quick succession
break;
} else {
// If the ESC was followed by something else,
// and it's not a mouse sequence, consume characters until a known break.
// This is a simplified escape sequence parser.
char seq_buf[10]; // Small buffer for simple sequences
seq_buf[0] = '\x1b';
seq_buf[1] = c;
int i = 2;
while (i < 9 && (c = getchar()) != EOF && c != '\x1b' && c != '\n' && (c < 'A' || c > 'Z')) {
seq_buf[i++] = c;
}
seq_buf[i] = '\0';
// printf("Unknown ESC sequence: %s\n", seq_buf);
if (c == '\x1b') { // If we read another ESC while parsing, break
break;
}
}
} else if (c == 'q' || c == 'Q') {
// User pressed 'q' or 'Q' to quit
break;
}
// Other characters will just be ignored in this loop
}
printf("\nExiting. Restoring terminal settings.\n");
// Disable mouse tracking before restoring terminal settings
fputs("\x1b[?1000l", stdout);
fflush(stdout);
// Restore original terminal mode
restore_termios(&old_termios);
return 0;
}
--- cut here: end ----------------------------------------------------
How to Compile and Run:
Save: Save the code above as mouse_viewer.c (or any other .c filename).
Compile: Open your terminal (WSL/Ubuntu) and compile it using gcc:
--- cut here: begin --------------------------------------------------
gcc mouse_viewer.c -o mouse_viewer
--- cut here: end ----------------------------------------------------
Run: Execute the compiled program:
--- cut here: begin --------------------------------------------------
./mouse_viewer
--- cut here: end ----------------------------------------------------
What to Observe:
Click within columns 1-93: You should see Raw X Char values
corresponding to (column_number + 32), and Decoded X matching your
column_number. These should be relatively small ASCII values.
Click at or beyond column 94:
Observe what the Raw X Char value becomes. You will likely see it jump
to a much higher value, or the program might print unexpected
characters, or fail to parse correctly.
The Decoded X might then become 4072 - 32 = 4040 (if the Query(Key)
behavior from TSE is mimicked by the C getchar() reading the first part
of a multi-byte sequence).
This program might even produce Raw X Char values beyond 127 if your
terminal is sending them, which could further demonstrate the protocol's
fragility in that range.
This C program directly illustrates the X10 protocol's limitation and
how character encoding beyond a certain point (often ASCII 127 or
similar thresholds for extended ASCII/UTF-8 interpretation) can lead to
the "broken" behavior you're seeing in your TSE macro. The 4072 you see
in TSE's Query(Key) is almost certainly TSE trying to interpret part of
a non-X10 mouse report (like an SGR sequence) as if it were a single X10
coordinate byte.