aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-29 14:25:34 +0200
committeruser <user@clank>2026-09-29 14:25:34 +0200
commit3760c4e99aff06d09edb0575d7596d0607298e71 (patch)
treec6e1a676cdccb449e1f59309dd622bd14a7bb544 /tests
parentskill: the verbs that shipped since, and the bare form's other shape (diff)
downloadslosh-3760c4e99aff06d09edb0575d7596d0607298e71.tar.gz
slosh-3760c4e99aff06d09edb0575d7596d0607298e71.tar.xz
slosh-3760c4e99aff06d09edb0575d7596d0607298e71.zip
mouse: encode pixel reports against the cell size we advertise
A pane is told how big a cell is twice -- in mode 2048's resize report and in the XTWINOPS answers -- and the mouse encoder was told something else: a 1x1 cell, so that cell coordinates and "pixels" were the same number. That held until a program turned on SGR-pixel mouse (mode 1016) by itself. Textual does, the moment in-band resize works, and then divides every coordinate it is sent by the pixels-per-cell we advertised: a click 30 cells across and 9 down arrived as (3, 0), so every click in a Textual app landed in the top-left corner. Hand the encoder the cell size the pane already has, and put the pointer in the middle of its cell. A size that contradicts what the same pane was told about itself is the lie, not the precision.
Diffstat (limited to 'tests')
-rw-r--r--tests/test_mouse_pixels.py93
1 files changed, 93 insertions, 0 deletions
diff --git a/tests/test_mouse_pixels.py b/tests/test_mouse_pixels.py
new file mode 100644
index 0000000..c91df13
--- /dev/null
+++ b/tests/test_mouse_pixels.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+"""SGR-pixel mouse (mode 1016) must agree with the size we report.
+
+A pane is told how big a cell is twice: in mode 2048's resize report and in the
+XTWINOPS answers. If mouse reports are encoded against a *different* cell size,
+every click in a program that turned on 1016 lands somewhere else -- and a
+program can turn it on by itself: Textual does, the moment in-band resize works,
+and then divides each coordinate by the pixels-per-cell we advertised. A click
+30 cells across came out as 3.
+
+So the check is a relationship, not a number: whatever cell size the pane is
+told about, the pixel coordinates it is sent must divide back down to the cell
+that was clicked.
+"""
+
+import re
+import sys
+
+from harness import Session, check, report
+
+# Enable in-band resize + SGR mouse, then echo everything the terminal sends.
+PROBE = [
+ "/bin/sh",
+ "-c",
+ 'stty raw -echo; printf "\\033[?2048h\\033[?1000h\\033[?1006h"; %s cat -v',
+]
+
+
+def probe(pixels):
+ argv = list(PROBE)
+ argv[2] = argv[2] % ('printf "\\033[?1016h";' if pixels else "")
+ return argv
+
+
+def reported(out):
+ """(cols, rows, cell_w, cell_h) from the mode-2048 report on screen."""
+ m = re.search(r"\^\[\[48;(\d+);(\d+);(\d+);(\d+)t", out)
+ if not m:
+ return None
+ rows, cols, pix_h, pix_w = (int(g) for g in m.groups())
+ return cols, rows, pix_w // cols, pix_h // rows
+
+
+def clicked(out):
+ """(x, y) of the last SGR report on screen, 0-based."""
+ hits = re.findall(r"\^\[\[<(\d+);(\d+);(\d+)M", out)
+ if not hits:
+ return None
+ _b, x, y = hits[-1]
+ return int(x) - 1, int(y) - 1
+
+
+def test_pixel_mode_matches_the_reported_cell():
+ with Session(probe(True), cols=70, rows=14) as s:
+ s.settle()
+ p = s.pane()
+ want_x, want_y = 20, 6
+ s.click(p["content_x"] + want_x, p["content_y"] + want_y)
+ s.settle(60)
+ out = s.snapshot().pane_text(p)
+
+ size = reported(out)
+ check("the pane got a mode-2048 size report", size is not None, repr(out[:200]))
+ got = clicked(out)
+ check("and an SGR mouse report", got is not None, repr(out[:200]))
+ if not size or not got:
+ return
+ _cols, _rows, cw, ch = size
+ check("the cell size is pixels, not cells", cw > 1 and ch > 1, str(size))
+ check(
+ "a click divides back down to the cell it hit",
+ (got[0] // cw, got[1] // ch) == (want_x, want_y),
+ f"report={got} cell={cw}x{ch} -> "
+ f"{(got[0] // cw, got[1] // ch)} want={(want_x, want_y)}",
+ )
+
+
+def test_cell_mode_is_unchanged():
+ with Session(probe(False), cols=70, rows=14) as s:
+ s.settle()
+ p = s.pane()
+ want_x, want_y = 20, 6
+ s.click(p["content_x"] + want_x, p["content_y"] + want_y)
+ s.settle(60)
+ out = s.snapshot().pane_text(p)
+ got = clicked(out)
+ check("a plain SGR pane still gets cell coordinates", got == (want_x, want_y), repr(got))
+
+
+if __name__ == "__main__":
+ test_pixel_mode_matches_the_reported_cell()
+ test_cell_mode_is_unchanged()
+ sys.exit(report())