— exactly 6 characters total
+ assertThat(AnsiParser.parse(supplyString("\u001b[M !\""))).isEqualTo("\u001b[M !\"");
+ }
+
+ @Test
+ void x10MouseEventDataBytesInFinalByteRangeAreNotSpecial() throws IOException {
+ // data bytes that look like CSI final bytes (e.g. 'M', 'A') must still be consumed as data
+ assertThat(AnsiParser.parse(supplyString("\u001b[MMAM"))).isEqualTo("\u001b[MMAM");
+ }
+
+ @Test
+ void mNotAfterCsiStartIsOrdinaryFinalByte() throws IOException {
+ // ESC [ 1 M – 'M' is not the first CSI char here, so it's a normal final byte
+ assertThat(AnsiParser.parse(supplyString("\u001b[1M"))).isEqualTo("\u001b[1M");
+ }
+
+ @Test
+ void x10MouseEventTruncatedByEof() throws IOException {
+ // ESC [ M then EOF – returns what was accumulated so far
+ assertThat(AnsiParser.parse(supply(0x1B, '[', 'M', 'b', -1))).isEqualTo("\u001b[Mb");
+ }
+
// ── OSC sequences ─────────────────────────────────────────────────────────
@Test
diff --git a/examples/PrintMouse.java b/examples/PrintMouse.java
index 4b447cd..af7b3c3 100755
--- a/examples/PrintMouse.java
+++ b/examples/PrintMouse.java
@@ -18,14 +18,18 @@ public class PrintMouse {
private static final String CURSOR_UP = CSI + "A";
private static final String ERASE_EOL = CSI + "K";
+ private static final int VIEW_LINES = 6;
+
public static void main(String[] args) {
try (Terminal terminal = Terminal.create()) {
terminal.enableRawMode();
MouseTracking.enable(terminal, MouseTracking.Protocol.ANY_MOTION);
MouseTracking.enableEncoding(terminal, MouseTracking.Encoding.SGR);
+ boolean pixelMode = false;
+ MouseEvent lastEv = null;
try {
- // Reserve 5 lines and print the initial (empty) view
- printView(terminal, null);
+ // Reserve the view lines and print the initial (empty) view
+ printView(terminal, null, pixelMode);
AnsiReader reader = new AnsiReader(() -> terminal.read(-1));
String token;
@@ -33,13 +37,24 @@ public static void main(String[] args) {
if (token.isEmpty()) continue;
if (!token.startsWith("\033") && token.charAt(0) == 3) break; // Ctrl+C
if (MouseTracking.isMouseEvent(token)) {
- MouseEvent ev = MouseTracking.parse(token);
- // Move cursor back up 5 lines to overwrite the previous view
- terminal.write(CURSOR_UP.repeat(5));
- printView(terminal, ev);
+ lastEv = MouseTracking.parse(token);
+ } else if (!token.startsWith("\033") && (token.equals("t") || token.equals("T"))) {
+ pixelMode = !pixelMode;
+ if (pixelMode) {
+ MouseTracking.enableEncoding(terminal, MouseTracking.Encoding.SGR_PIXELS);
+ } else {
+ MouseTracking.disableEncoding(terminal, MouseTracking.Encoding.SGR_PIXELS);
+ MouseTracking.enableEncoding(terminal, MouseTracking.Encoding.SGR);
+ }
+ } else {
+ continue;
}
+ // Move cursor back up to overwrite the previous view
+ terminal.write(CURSOR_UP.repeat(VIEW_LINES));
+ printView(terminal, lastEv, pixelMode);
}
} finally {
+ MouseTracking.disableEncoding(terminal, MouseTracking.Encoding.SGR_PIXELS);
MouseTracking.disableEncoding(terminal, MouseTracking.Encoding.SGR);
MouseTracking.disable(terminal, MouseTracking.Protocol.ANY_MOTION);
}
@@ -48,16 +63,18 @@ public static void main(String[] args) {
}
}
- private static void printView(Terminal terminal, MouseEvent ev) throws IOException {
+ private static void printView(Terminal terminal, MouseEvent ev, boolean pixelMode) throws IOException {
String type = ev == null ? "-" : ev.type().name();
String button = ev == null ? "-" : ev.button().name();
String position = ev == null ? "-" : ev.x() + ", " + ev.y();
String mods = ev == null ? "-" : modifiers(ev);
+ String unit = pixelMode ? "PIXEL" : "CELL";
writeLine(terminal, "Type: " + type);
writeLine(terminal, "Button: " + button);
writeLine(terminal, "Position: " + position);
writeLine(terminal, "Modifiers: " + mods);
+ writeLine(terminal, "Coordinates: " + unit + " (press T to toggle cell/pixel coordinates)");
writeLine(terminal, "(move the mouse, click or scroll — Ctrl+C to exit)");
}
diff --git a/mousetrack/README.md b/mousetrack/README.md
index 96ccc04..6f40ab5 100644
--- a/mousetrack/README.md
+++ b/mousetrack/README.md
@@ -128,6 +128,13 @@ try {
Not all terminals support mode 1016; check the terminal's documentation before relying on it.
+Some terminals reset `SGR` as a side effect of disabling `SGR_PIXELS`. If you toggle `SGR_PIXELS` off at runtime, re-enable `SGR` right after, e.g.:
+
+```java
+MouseTracking.disableEncoding(terminal, MouseTracking.Encoding.SGR_PIXELS);
+MouseTracking.enableEncoding(terminal, MouseTracking.Encoding.SGR);
+```
+
## Adding the dependency
### JBang
diff --git a/mousetrack/src/main/java/org/codejive/miniterm/mousetrack/MouseEvent.java b/mousetrack/src/main/java/org/codejive/miniterm/mousetrack/MouseEvent.java
index 07f2da0..34fec9d 100644
--- a/mousetrack/src/main/java/org/codejive/miniterm/mousetrack/MouseEvent.java
+++ b/mousetrack/src/main/java/org/codejive/miniterm/mousetrack/MouseEvent.java
@@ -80,18 +80,28 @@ public Button button() {
}
/**
- * Returns the 1-based column at which the event occurred.
+ * Returns the horizontal coordinate at which the event occurred.
*
- * @return column, ≥ 1
+ * By default this is the 1-based column of the character cell. If {@link
+ * MouseTracking.Encoding#SGR_PIXELS} was enabled when the event was reported, this is instead a
+ * 0-based pixel offset from the left edge of the terminal window. This class cannot tell the
+ * two apart — callers must track which encoding is active.
+ *
+ * @return column or pixel offset, depending on the active encoding
*/
public int x() {
return x;
}
/**
- * Returns the 1-based row at which the event occurred.
+ * Returns the vertical coordinate at which the event occurred.
+ *
+ *
By default this is the 1-based row of the character cell. If {@link
+ * MouseTracking.Encoding#SGR_PIXELS} was enabled when the event was reported, this is instead a
+ * 0-based pixel offset from the top edge of the terminal window. This class cannot tell the two
+ * apart — callers must track which encoding is active.
*
- * @return row, ≥ 1
+ * @return row or pixel offset, depending on the active encoding
*/
public int y() {
return y;
diff --git a/mousetrack/src/main/java/org/codejive/miniterm/mousetrack/MouseTracking.java b/mousetrack/src/main/java/org/codejive/miniterm/mousetrack/MouseTracking.java
index 139dcee..ac97c38 100644
--- a/mousetrack/src/main/java/org/codejive/miniterm/mousetrack/MouseTracking.java
+++ b/mousetrack/src/main/java/org/codejive/miniterm/mousetrack/MouseTracking.java
@@ -53,6 +53,16 @@
*
*
{@link Encoding#SGR_PIXELS} (DEC mode 1016) extends {@link Encoding#SGR} to report pixel-level
* coordinates instead of cell-based coordinates. Enable it together with {@link Encoding#SGR}.
+ *
+ *
The wire format is unchanged by {@link Encoding#SGR_PIXELS} — only the meaning of the {@code
+ * Px}/{@code Py} fields changes, from 1-based character-cell column/row to pixel offsets from the
+ * terminal's top-left corner. {@link #parse(String)} decodes both the same way; callers must track
+ * which encoding is active to know how to interpret {@link MouseEvent#x()} and {@link
+ * MouseEvent#y()}.
+ *
+ *
Some terminals reset {@link Encoding#SGR} as a side effect of disabling {@link
+ * Encoding#SGR_PIXELS}. If you toggle {@link Encoding#SGR_PIXELS} off at runtime, re-enable {@link
+ * Encoding#SGR} right after to be safe.
*/
public final class MouseTracking {