Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,39 @@
import sys


# Mouse-tracking residue suppression — runs BEFORE every other import on the
# TUI hot path so the terminal stops emitting SGR/X10 mouse reports while the
# Python launcher is still doing imports (≈100–300ms in cooked + echo mode,
# before the Node TUI takes stdin into raw mode). During that window any
# incoming bytes are echoed straight back to the user's shell scrollback as
# ``^[[<…M`` text. The TUI itself runs `resetTerminalModes()` again in
# `entry.tsx`; this is just the earlier cousin. ``HERMES_TUI_NO_EARLY_DISABLE``
# escapes the behaviour for diagnostics.
def _suppress_mouse_residue_early() -> None:
if os.environ.get("HERMES_TUI_NO_EARLY_DISABLE") == "1":
return
if not (os.environ.get("HERMES_TUI") == "1" or "--tui" in sys.argv[1:]):
return
try:
# Skip when stdout is redirected (`hermes --tui … >log`, CI capture):
# the bytes can't reach the terminal anyway and would just pollute
# the log with raw CSI.
if not os.isatty(1):
return
# Disable every mouse-tracking variant we know about. Idempotent and
# safe to send even when no tracking is currently asserted.
os.write(
1,
b"\x1b[?1003l\x1b[?1002l\x1b[?1001l\x1b[?1000l\x1b[?9l"
b"\x1b[?1006l\x1b[?1005l\x1b[?1015l\x1b[?1016l\x1b[?2029l",
)
Comment thread
OutThisLife marked this conversation as resolved.
Resolved
except OSError:
pass


_suppress_mouse_residue_early()


def _is_termux_startup_environment_fast() -> bool:
"""Tiny Termux check for pre-import startup shortcuts."""
prefix = os.environ.get("PREFIX", "")
Expand Down
92 changes: 92 additions & 0 deletions tests/hermes_cli/test_tui_mouse_residue_suppression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Tests for the TUI-hot-path mouse-residue suppression.

The Python launcher (`hermes --tui …`) has a ~100–300ms cold-start window
where stdin is still in cooked + echo mode. If a previous Hermes session
left DEC mouse-tracking asserted, any mouse motion during that window
echoes literal ``^[[<…M`` text into the user's scrollback.

`_suppress_mouse_residue_early()` writes the disable sequence to stdout
before the heavy imports so the terminal stops emitting events ASAP.
"""

from __future__ import annotations

import sys
from unittest.mock import patch

# Importing the module triggers `_suppress_mouse_residue_early()` at module
# scope. Under the test runner argv (`pytest …`) it's a no-op, but we import
# at file scope so individual tests don't race the import side-effect with
# their `patch("os.write")` context.
from hermes_cli.main import _suppress_mouse_residue_early

EXPECTED = (
b"\x1b[?1003l\x1b[?1002l\x1b[?1001l\x1b[?1000l\x1b[?9l"
b"\x1b[?1006l\x1b[?1005l\x1b[?1015l\x1b[?1016l\x1b[?2029l"
)


class TestEarlyMouseDisable:
def test_writes_disable_sequence_when_tui_flag_in_argv(self, monkeypatch):
monkeypatch.setattr(sys, "argv", ["hermes", "--tui", "-c", "abc"])
monkeypatch.delenv("HERMES_TUI", raising=False)
monkeypatch.delenv("HERMES_TUI_NO_EARLY_DISABLE", raising=False)

with patch("os.isatty", return_value=True), patch("os.write") as mock_write:
_suppress_mouse_residue_early()

mock_write.assert_called_once_with(1, EXPECTED)

def test_writes_disable_sequence_when_hermes_tui_env_set(self, monkeypatch):
monkeypatch.setattr(sys, "argv", ["hermes"])
monkeypatch.setenv("HERMES_TUI", "1")
monkeypatch.delenv("HERMES_TUI_NO_EARLY_DISABLE", raising=False)

with patch("os.isatty", return_value=True), patch("os.write") as mock_write:
_suppress_mouse_residue_early()

mock_write.assert_called_once_with(1, EXPECTED)

def test_no_op_on_non_tui_invocation(self, monkeypatch):
monkeypatch.setattr(sys, "argv", ["hermes", "--version"])
monkeypatch.delenv("HERMES_TUI", raising=False)
monkeypatch.delenv("HERMES_TUI_NO_EARLY_DISABLE", raising=False)

with patch("os.write") as mock_write:
_suppress_mouse_residue_early()

mock_write.assert_not_called()

def test_respects_diagnostic_escape_hatch(self, monkeypatch):
monkeypatch.setattr(sys, "argv", ["hermes", "--tui"])
monkeypatch.delenv("HERMES_TUI", raising=False)
monkeypatch.setenv("HERMES_TUI_NO_EARLY_DISABLE", "1")

with patch("os.write") as mock_write:
_suppress_mouse_residue_early()

mock_write.assert_not_called()

def test_skips_when_stdout_is_not_a_tty(self, monkeypatch):
# `hermes --tui … >log` or CI capture: pipe is fd 1, not a TTY. The
# bytes can't reach a terminal and would just pollute the log.
monkeypatch.setattr(sys, "argv", ["hermes", "--tui"])
monkeypatch.delenv("HERMES_TUI", raising=False)
monkeypatch.delenv("HERMES_TUI_NO_EARLY_DISABLE", raising=False)

with patch("os.isatty", return_value=False), patch("os.write") as mock_write:
_suppress_mouse_residue_early()

mock_write.assert_not_called()

def test_oserror_is_swallowed(self, monkeypatch):
monkeypatch.setattr(sys, "argv", ["hermes", "--tui"])
monkeypatch.delenv("HERMES_TUI", raising=False)
monkeypatch.delenv("HERMES_TUI_NO_EARLY_DISABLE", raising=False)

def boom(*_a, **_k):
raise OSError("stdout closed")

with patch("os.isatty", return_value=True), patch("os.write", side_effect=boom):
# Must not propagate — startup hot path can never break.
_suppress_mouse_residue_early()
Loading