Skip to content

Prototype: replace SGR mouse tracking with alternate-scroll mode (?1007h)#215

Merged
Aaronontheweb merged 23 commits into
Aaronontheweb:devfrom
codymullins:feature/alternate-scroll-mode
May 23, 2026
Merged

Prototype: replace SGR mouse tracking with alternate-scroll mode (?1007h)#215
Aaronontheweb merged 23 commits into
Aaronontheweb:devfrom
codymullins:feature/alternate-scroll-mode

Conversation

@codymullins

@codymullins codymullins commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Prototype that disambiguates mouse-wheel ticks from real arrow-key presses without breaking native click-drag text selection, by combining two terminal-protocol layers:

  1. xterm alternate-scroll mode (CSI ?1007h) routes wheel events through the cursor-key path (SS3 OA/OB under DECCKM on / CSI A/B under DECCKM off).
  2. kitty keyboard protocol with report_all_keys (CSI > 8 u) re-routes real key events through CSI ... u / CSI 1;mods X (or PUA keycodes). Because terminals implement the kitty enhancement only in their keyboard subsystem, wheel events keep their legacy shape — and the two become structurally distinguishable at the byte level.

Net effect on Ghostty / kitty / WezTerm / foot / iTerm2 ≥ 3.5: scroll-wheel scrolls a focused IScrollable, arrows act as arrows even in an always-focused text input, and the terminal's native text selection (click-drag, triple-click, middle-paste) keeps working because we never enable ?1000h/?1006h.

Restores the symptoms reported in #192 and removes the need for the F6 selection-mode toggle in #194.

Changes

Default wheel-scroll transport (?1007h)

  • AnsiCodes.EnableAlternateScroll / DisableAlternateScroll (CSI ?1007h / ?1007l)
  • IAnsiTerminal.EnableWheelScroll() / DisableWheelScroll() + implementations in AnsiTerminal, DiffingTerminal, VirtualTerminal, benchmark stub
  • TerminaApplication calls EnableWheelScroll() on startup and DisableWheelScroll() in finally instead of EnableMouse() / DisableMouse()
  • EnableMouse() / DisableMouse() and the ?1000h / ?1006h constants are kept — apps that need true clicks/drags (e.g. an in-app file picker) can still opt in explicitly

Raw-stdin UnixConsole (required so the parser can actually see the bytes)

  • New UnixConsole in src/Termina/Platform/UnixConsole.cs that bypasses Console.ReadKey (which folds CSI A and SS3 OA to the same UpArrow before any parser can see them) and reads raw bytes via libc.read after cfmakeraw + VMIN=0 VTIME=1. OPOST is preserved so post-shutdown logging isn't staircased.
  • Background reader thread, channel-based event delivery, UTF-8 decoder for multi-byte input.
  • Termios restoration on Dispose, ProcessExit, UnhandledException, and CancelKeyPress.
  • SIGWINCH-driven resize via PosixSignalRegistration, with VTIME-tick fallback when SIGWINCH isn't supported.
  • Opt-in via TERMINA_UNIX_RAW_INPUT=1 (default behavior unchanged unless the env var is set).

Kitty keyboard protocol opt-in

  • UnixConsole pushes CSI > N u after entering raw mode and pops CSI < u before restoring termios, gated on TERMINA_KITTY_KEYBOARD=<flags> (typical value 8 for report_all_keys). Exposed as UnixConsole.KittyKeyboardActive.
  • PlatformInputSource propagates the flag to EscapeSequenceParser.KittyKeyboardActive.
  • EscapeSequenceParser extended to:
    • Route bare CSI A/B/C/D/H/F/P-S as KeyPressed when kitty is active (kitty canonical no-mod form) and as MouseScrollEvent when kitty is inactive (?1007h wheel emission).
    • Invert SS3 OA/OB to MouseScrollEvent when kitty is active (Ghostty's mouse subsystem ignores kitty, so wheel still emits the DECCKM-controlled SS3 form). SS3 OC/OD stay as KeyPressed (wheel has no horizontal axis).
    • Parse kitty CSI 1;<mods>[:<event>] X second form.
    • Handle CSI-u :event-type subfield (swallow non-press), ;text-codepoints trailer, and PUA modifier-alone keycodes (57441-57454).
    • Map PUA functional keycodes (57344-57375 — Esc / Enter / Tab / Backspace / arrows / Insert / Delete / PgUp / PgDn / Home / End / F1-F12) to ConsoleKey.

Framework Ctrl+C double-press to quit

  • First Ctrl+C shows a Press Ctrl+C again to quit toast (ToastPosition.BottomCenter, 2s).
  • Second Ctrl+C within the window calls Shutdown().
  • Handled at the top of TerminaApplication.ProcessEvent, above page / focus routing, so users can always exit even from a focus-trapping input (e.g. an AI-chat text box). Replaces the per-demo Ctrl+Q convention; required under cfmakeraw (ISIG cleared, so SIGINT no longer fires).

Demo

  • demos/Termina.Demo.KittyScroll — AI-harness layout: pre-populated StreamingTextNode history, status line with last-key + per-class counters, focused TextInputNode at the bottom. Enter echoes the message back into the history. Run with TERMINA_UNIX_RAW_INPUT=1 TERMINA_KITTY_KEYBOARD=8 dotnet run --project demos/Termina.Demo.KittyScroll.

Tradeoffs / caveats

  1. Default path on non-kitty terminals. Without TERMINA_KITTY_KEYBOARD, bare CSI A/B is still treated as wheel (i.e. an app with an always-focused text input still sees ambiguity on terminals that don't implement the kitty protocol — macOS Terminal.app being the main casualty). Apps targeting those terminals can fall back to PgUp/PgDn (still wired in StreamingTextNode.HandleInput).
  2. Console.ReadKey path unchanged. The kitty disambiguation only works when TERMINA_UNIX_RAW_INPUT=1 is set, because Console.ReadKey folds the two arrow forms before the parser runs. Flipping the default is a follow-up.
  3. Legacy conhost.exe ignores ?1007h. Modern Windows Terminal and ConPTY support it; the kitty path is Unix-only.

Verification

  • dotnet build Termina.slnx — succeeds (incl. demos and benchmarks)
  • dotnet test1092 / 1092 passing, including 30 new EscapeSequenceParserKittyTests, UnixConsoleByteMappingTests, and updated AnsiCodes / DiffingTerminal / VirtualTerminal tests
  • Verified end-to-end in Ghostty: wheel scrolls history, arrows move input cursor, click-drag selection preserved, Ctrl+C×2 quits cleanly with CSI < u popped before termios restore.

Suggested follow-ups (out of scope here)

  • Decide framing. Three options for landing this:
    • Land as opt-in (current state — TERMINA_UNIX_RAW_INPUT=1 + TERMINA_KITTY_KEYBOARD=8).
    • Make kitty default-on when $TERM / $TERM_PROGRAM indicates a kitty-capable terminal (Ghostty / kitty / foot / wezterm / iTerm2 ≥ 3.5); opt-out via env var.
    • Promote raw-stdin + kitty to the default on Unix and drop the Console.ReadKey path.
  • Close Add F6 selection mode to hand mouse back to terminal #194 in favor of this approach (or keep it as an explicit EnableMouse() opt-in for apps that want app-owned selection).
  • Update docs/concepts/input-handling.md and docs/components/streaming-text-node.md once framing is decided.

Closes (potentially) #192. Supersedes (potentially) #194.

codymullins and others added 19 commits May 18, 2026 14:31
…racking

Replaces the application's startup call to EnableMouse() (which enabled
CSI ?1000h + CSI ?1006h to capture mouse-wheel events) with a new
EnableWheelScroll() call that emits CSI ?1007h. While in the alternate
screen buffer, the terminal translates wheel events into cursor up/down
key sequences without any mouse tracking, so:

 - Native click-drag text selection works again (fixes Aaronontheweb#192).
 - Triple-click word/line selection works.
 - Middle-click paste works.
 - OS clipboard integration via the terminal works.
 - PR Aaronontheweb#194's F6 selection-mode toggle becomes unnecessary.
 - The tmux ESC-keypress crash class that drove the ?1002h → ?1000h
   migration goes away — the terminal sends no mouse escape sequences.

EnableMouse()/DisableMouse() are kept for apps that genuinely need
clicks/drags (e.g. an in-app file picker) and can opt in explicitly.
EscapeSequenceParser keeps its silent consumption of SGR mouse events
as a defensive safety net.

Caveats:
 - Wheel events arrive as bare UpArrow/DownArrow keypresses and are
   indistinguishable from real keyboard arrows. Apps with always-focused
   text inputs (e.g. chat) may want to treat Shift+Wheel separately or
   focus the scrollable explicitly. The Streaming demo's existing
   MouseScrollEvent subscription will no longer fire under the default
   wheel-scroll mode; PgUp/PgDn is the documented keyboard fallback.
 - Legacy conhost.exe ignores ?1007h. Modern Windows Terminal supports
   it. Affected users keep keyboard scrolling as a fallback.

Adds tests for the new VirtualTerminal/DiffingTerminal/AnsiCodes
surfaces. Full suite: 1026 / 1026 passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Demonstrates the wheel-vs-arrow ambiguity introduced by switching from
SGR mouse tracking (?1000h + ?1006h) to xterm alternate-scroll mode
(?1007h). The terminal translates wheel events into bare Up/Down arrow
keypresses, which a focused TextInputNode happily consumes as cursor
movement — so spinning the wheel over a scrollable history panel moves
the input cursor instead of scrolling the history.

Layout:
- Top: pre-populated StreamingTextNode (100 lines, scrollable)
- Middle: pre-filled TextInputNode (always focused)
- Bottom: live counters for bare Up/Down arrows and MouseScrollEvents

PgUp/PgDn still scrolls the history (keyboard fallback), Ctrl+Q quits.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
BuildLayout() runs inside base.OnNavigatedTo() before any code after
the base call executes, so _history/_input were still null when the
layout was first built. Move node construction to OnBound() (called at
bind time, before navigation) and keep input subscriptions in
OnNavigatedTo so they're re-created on each visit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Counterpart to Termina.Demo.WheelAmbiguity. The ViewModel injects
IAnsiTerminal and calls EnableMouse() to re-enable full SGR mouse mode
(?1000h + ?1006h) on top of the framework's default ?1007h. The
EscapeSequenceParser then emits MouseScrollEvent for wheel ticks, which
the page forwards to the (non-focusable) StreamingTextNode.

This demonstrates that the framework's auto-routing of MouseScrollEvent
only fires when the focused widget is itself IScrollable; for
chat-shaped pages where TextInputNode is focused, the page must
subscribe and forward manually.

Tradeoff documented in code: while EnableMouse() is active, the host
terminal stops handling click-drag text selection (Shift/Option to
override).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Disambiguate wheel-as-arrow events from real keyboard arrows by also
enabling DECCKM (CSI ?1h) when alternate scroll is turned on:

  - Real keyboard arrows: SS3 form (ESC O A/B), decoded by
    Console.ReadKey directly to ConsoleKey.UpArrow/DownArrow. Never
    reach the EscapeSequenceParser.
  - Mouse wheel under ?1007h: CSI form (ESC [ A/B), unchanged by
    DECCKM, falls through Console.ReadKey as raw bytes.

The parser now recognizes bare CSI A/B in InBracketSequence as
MouseScrollEvent(+1)/(-1) — the same event type already routed by
TerminaApplication to the focused IScrollable. Pages can keep their
existing MouseScrollEvent subscriptions; they will now fire for
wheel ticks while native click-drag text selection remains available.

  - AnsiCodes: add Enable/DisableCursorKeyApplicationMode constants
  - AnsiTerminal.EnableWheelScroll: emit ?1007h + ?1h (disable both
    on teardown)
  - EscapeSequenceParser: recognize ESC[A / ESC[B as MouseScrollEvent
  - Tests: parser tests for the new wheel-as-CSI-arrow path,
    AnsiCodes tests for the new constants

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Console.ReadKey on Unix has hardcoded recognition for both CSI (ESC[A)
and SS3 (ESC OA) arrow forms, so DECCKM (?1h) alone can't disambiguate
mouse-wheel-as-arrow events from real arrow keypresses — the raw bytes
never reach EscapeSequenceParser. To make ?1007h alternate-scroll mode
actually scroll, we have to bypass Console.ReadKey entirely.

UnixConsole now puts the terminal into raw mode via termios+cfmakeraw
(preserving OPOST so logging output isn't staircased), reads bytes
directly from Console.OpenStandardInput, and emits one ConsoleKeyEvent
per byte. EscapeSequenceParser gets a new InSs3Sequence state so that
real arrow keys (which now arrive as raw ESC O A/B/C/D bytes under
DECCKM) are reassembled into normal KeyPressed events while wheel
ticks (ESC [ A/B) continue to map to MouseScrollEvent.

PlatformConsoleFactory wires UnixConsole on Linux/macOS when stdin is
a TTY; piped/redirected scenarios still use FallbackConsole.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The raw-termios + raw-stdin path made interactive behavior worse on
macOS Terminal (per user report). Likely culprits include termios c_cc
offsets, OPOST handling, ReadAsync vs VMIN/VTIME interaction, and
turning Ctrl+C into a byte (cfmakeraw clears ISIG) which breaks app
shutdown for demos that don't bind a quit shortcut.

Reverting UnixConsole back to the issue-Aaronontheweb#80 stub and reverting
PlatformConsoleFactory to fall through to FallbackConsole on Unix.
The SS3-arrow and CSI-arrow parser cases (and their tests) stay —
they're inert until we get a raw-byte input source and are useful for
the future fix.

Net result for now: ?1007h alternate-scroll mode is still not usable
on its own through Console.ReadKey. Use Termina.Demo.WheelScrollWorks
(which opts into ?1000h via IAnsiTerminal.EnableMouse) as the
recommended wheel-scroll path. A proper Unix raw-stdin implementation
remains tracked under issue Aaronontheweb#80.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Standalone raw-stdin probe — no Termina dependency — used to verify
termios layout and raw byte streams on the user's actual terminal before
the real UnixConsole implementation lands. Prints computed VMIN/VTIME/
c_oflag offsets, enters raw mode, reads bytes directly via libc.read(),
and annotates recognized wheel/arrow escape sequences so the outcome of
?1007h + DECCKM is readable at a glance.

Restores termios on Dispose / ProcessExit / Console.CancelKeyPress /
unhandled exception. Quits on 'q' or Ctrl+C (0x03 byte, since cfmakeraw
clears ISIG).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the NotImplementedException stub with a real implementation
that bypasses Console.ReadKey on Unix so ?1007h alternate-scroll-mode
wheel events can be distinguished from real arrow keys (DECCKM SS3
forms). Hidden behind the TERMINA_UNIX_RAW_INPUT=1 env var until the
opt-in default is flipped in a later change.

Design points learned from the prior reverted attempt (8be2d4e):

* Direct libc.read() on a dedicated background thread instead of
  Console.OpenStandardInput().ReadAsync — the Stream wrapper's
  interaction with VMIN/VTIME wasn't reliable.
* termios offsets (c_oflag, c_cc base, VMIN, VTIME) match what the
  RawStdinProbe runtime-verifies via tcgetattr readback.
* OPOST re-enabled after cfmakeraw so logging isn't staircased.
* ISIG cleared by cfmakeraw — Ctrl+C arrives as 0x03 and is mapped to
  KeyPressed(C, Control), matching FallbackConsole's
  TreatControlCAsInput behavior (user-confirmed in plan).
* UTF-8 multi-byte text input reassembled via System.Text.Decoder so
  typing non-ASCII chars produces one ConsoleKeyEvent per codepoint;
  ASCII bytes (incl. all escape-sequence bytes) flow one-per-event so
  EscapeSequenceParser can keep reassembling CSI/SS3.
* Channel<IConsoleInputEvent> for the reader→consumer queue;
  cancellation flows through Channel.ReadAsync naturally.
* termios restored on Dispose, AppDomain.ProcessExit,
  UnhandledException, and Console.CancelKeyPress.

Resize detection is still poll-based at the 100 ms VTIME cadence;
SIGWINCH integration is the next change.

26 new tests cover byte→KeyInfo mapping for control/printable/Ctrl+
letter/high-bit bytes, CSI arrow → MouseScrollEvent integration, SS3
arrow → KeyPressed integration, and UTF-8 multi-byte decoding through
the same one-byte-at-a-time path the reader thread uses.

Full suite: 1062/1062 passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use PosixSignalRegistration.Create(PosixSignal.SIGWINCH, ...) so
terminal resizes are observed immediately instead of waiting for the
next 100 ms VTIME tick in the reader thread. The handler runs on a
thread-pool thread, so CheckResize() now takes a lock to coordinate
with the reader thread's opportunistic polling. The VTIME-tick poll is
kept as a belt-and-suspenders fallback for environments where the
signal registration is unavailable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per xterm.ctlseqs, ?1007h alternate-scroll-mode only emits CSI A/B for
wheel ticks while on the alternate screen buffer. The real Termina
framework enters alt screen via ?1049h on startup, so wheel-as-arrow
works there; the bare probe was missing it, which is why a Ghostty test
run showed arrows + typing working perfectly but wheel emitted nothing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds opt-in 'CSI > <flags> u' push (and matching 'CSI < u' pop on exit) gated
on the TERMINA_KITTY_FLAGS env var. Extends AnnotateSequence to recognize:

  CSI <num>;<mods>[:event] u          (kitty CSI-u key events)
  CSI 1;<mods> [ABCDEFHPQRS]          (kitty second-form arrows / Home/End / F1-F4)
  CSI [ABCD] with no params           (wheel under ?1007h OR plain arrow)

and decodes the standard kitty PUA functional keycodes (Up=57352..) and
modifier bitmask. Default behavior unchanged when env var is unset.

Usage:
  TERMINA_KITTY_FLAGS=8 dotnet run --project demos/Termina.Demo.RawStdinProbe

Goal: empirically determine which kitty flag combo produces distinguishable
bytes for wheel-up vs Up-arrow in Ghostty under ?1007h.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add KittyKeyboardActive flag to EscapeSequenceParser. When active:
- Bare CSI A/B/C/D/H/F/P-S route as KeyPressed (kitty canonical no-mod form)
- SS3 OA/OB invert to MouseScrollEvent (wheel under ?1007h+DECCKM)
- SS3 OC/OD stay as KeyPressed (wheel has no horizontal axis)

Extend TryParseCsiU with optional :event-type subfield (swallow non-press)
and trailing ;text-codepoints. Swallow PUA modifier-alone keycodes
(57441-57454). Map PUA functional keycodes (57344-57375) to ConsoleKey.

Add TryParseKittySecondForm for CSI 1;<mods>[:<event>] [ABCDEFHPQRS].

25 new tests in EscapeSequenceParserKittyTests.cs cover bare-CSI routing
in both modes, all functional finals, SS3 inversion, second-form modifier
combos, event-type swallowing, PUA arrows / F5 / modifier-alone swallow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When TERMINA_KITTY_KEYBOARD is set to a positive int (e.g. 8 for
report_all_keys), UnixConsole pushes 'CSI > N u' after entering raw mode
and pops 'CSI < u' before restoring termios. UnixConsole exposes the
state as KittyKeyboardActive; PlatformInputSource propagates it to
EscapeSequenceParser.KittyKeyboardActive so the parser routes bare CSI
A/B as KeyPressed (real arrows) and SS3 OA/OB as MouseScrollEvent (wheel
under ?1007h+DECCKM), giving structurally correct wheel-vs-arrow
disambiguation while preserving native click-drag text selection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
History panel scrolls on wheel, arrow keys only tick counters; native
click-drag selection still works because no mouse tracking is enabled.

Run:
  TERMINA_UNIX_RAW_INPUT=1 TERMINA_KITTY_KEYBOARD=8 \
    dotnet run --project demos/Termina.Demo.KittyScroll

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
First Ctrl+C shows a 'Press Ctrl+C again to quit' toast at the bottom
center for 2 seconds; a second Ctrl+C within that window calls
Shutdown(). Sits above page / focus handling so users can always exit,
even from a focus-trapping input control. Under raw-mode UnixConsole
(cfmakeraw clears ISIG) Ctrl+C arrives as a KeyPressed(C, Control)
event, which this handler intercepts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
History panel + status line + focused input box. Enter echoes the
message back into the history. Arrows move the input cursor (focused);
wheel still scrolls history (falls through to the page since the input
is not IScrollable). Removed the local Ctrl+Q binding — use the new
framework Ctrl+C×2 to quit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
FocusPolicy.FirstFocusable so the TextInputNode actually receives
keystrokes when the page loads. Without this the page defaulted to
FocusPolicy.Manual and typing went nowhere visible.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…RawStdinProbe)

Keep only Termina.Demo.KittyScroll — the final prototype demo
demonstrating wheel-vs-arrow disambiguation via the kitty keyboard
protocol + ?1007h, with a focused TextInput / AI-harness layout and
Ctrl+C×2 to quit.

The three removed demos were intermediate diagnostic/illustrative
projects used during the investigation:
- RawStdinProbe: standalone byte-level termios probe
- WheelAmbiguity: reproduction of the original ?1007h ambiguity
- WheelScrollWorks: intermediate proof that wheel-as-arrow scrolled
  in non-focus-trapping pages

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Aaronontheweb Aaronontheweb marked this pull request as ready for review May 18, 2026 21:26
Copilot AI review requested due to automatic review settings May 19, 2026 02:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prototypes a new input stack that preserves terminal-native text selection while still supporting scroll-wheel-driven UI scrolling, by switching from SGR mouse tracking to xterm alternate-scroll mode (?1007h) and optionally using the kitty keyboard protocol + raw-byte stdin to disambiguate wheel ticks from real arrow keys.

Changes:

  • Replace default mouse-wheel transport from SGR mouse tracking (?1000h/?1006h) to alternate-scroll mode (?1007h) via new EnableWheelScroll() / DisableWheelScroll() APIs.
  • Add raw-byte input consoles (Unix termios + read(), Windows raw-VT + ReadFile) and propagate negotiated capabilities (kitty active) into EscapeSequenceParser.
  • Extend EscapeSequenceParser to handle SS3 sequences, kitty CSI-u / second-form parsing, and wheel-vs-arrow routing; add a new demo and extensive tests.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/Termina.Tests/Terminal/VirtualTerminalTests.cs Adds coverage for EnableWheelScroll / DisableWheelScroll flags on VirtualTerminal.
tests/Termina.Tests/Terminal/DiffingTerminalTests.cs Verifies DiffingTerminal passes wheel-scroll enable/disable through to the inner terminal.
tests/Termina.Tests/Terminal/AnsiCodesTests.cs Adds assertions for new ANSI constants (?1007h/l, ?1h/l).
tests/Termina.Tests/Platform/UnixConsoleByteMappingTests.cs New tests validating raw-byte → ConsoleKeyInfo mapping and parser behavior without a TTY.
tests/Termina.Tests/Input/EscapeSequenceParserTests.cs Adds baseline tests for ?1007h CSI-arrow wheel behavior and SS3 arrow key behavior.
tests/Termina.Tests/Input/EscapeSequenceParserKittyTests.cs New test suite for kitty keyboard protocol parsing and kitty-vs-wheel routing.
Termina.slnx Adds the new KittyScroll demo project to the solution.
src/Termina/Terminal/VirtualTerminal.cs Introduces WheelScrollEnabled and implements EnableWheelScroll/DisableWheelScroll.
src/Termina/Terminal/IAnsiTerminal.cs Expands mouse docs and adds wheel-only scrolling API surface.
src/Termina/Terminal/DiffingTerminal.cs Pass-through implementations for wheel-only scrolling toggles.
src/Termina/Terminal/AnsiTerminal.cs Implements ?1007h + DECCKM toggling and ensures cleanup on Dispose().
src/Termina/Terminal/AnsiCodes.cs Adds ?1007h/l and DECCKM ?1h/l constants and documentation.
src/Termina/TerminaApplication.cs Switches startup/shutdown to wheel-only scrolling and adds Ctrl+C double-press shutdown handling.
src/Termina/Platform/WindowsConsole.cs Adds opt-in raw-VT input mode, byte reader thread/channel, UTF-8 input CP handling, and capability reporting.
src/Termina/Platform/UnixConsole.cs Implements raw stdin console using termios + read(), background reader thread/channel, SIGWINCH resize, and capability reporting.
src/Termina/Platform/TerminalCapabilities.cs New capability snapshot type (currently kitty-active flag) for parser configuration.
src/Termina/Platform/RawByteKeyMapper.cs New shared raw-byte → ConsoleKeyInfo mapping helper.
src/Termina/Platform/PlatformConsoleFactory.cs Adds cross-platform TERMINA_RAW_INPUT opt-in (with TERMINA_UNIX_RAW_INPUT alias) and selects raw consoles accordingly.
src/Termina/Platform/KittyKeyboardEnhancement.cs Adds env-var-driven push/pop of kitty keyboard enhancement flags.
src/Termina/Platform/IPlatformConsole.cs Adds default Capabilities property to surface negotiated terminal features.
src/Termina/Input/PlatformInputSource.cs Wires console capabilities into EscapeSequenceParser.KittyKeyboardActive.
src/Termina/Input/EscapeSequenceParser.cs Adds SS3 state, kitty second-form parsing, CSI-u enhancements, and wheel-vs-arrow routing logic.
demos/Termina.Demo.KittyScroll/Termina.Demo.KittyScroll.csproj New demo project for validating the new wheel/arrow disambiguation approach.
demos/Termina.Demo.KittyScroll/README.md Demo documentation and recommended env var settings across platforms/terminals.
demos/Termina.Demo.KittyScroll/Program.cs Demo host setup and explanatory comments.
demos/Termina.Demo.KittyScroll/Pages/KittyScrollViewModel.cs Tracks per-event counters and last key to visualize disambiguation correctness.
demos/Termina.Demo.KittyScroll/Pages/KittyScrollPage.cs Demo UI: scrollable history + focused input, wheel routes to history, arrows to input.
benchmarks/Termina.Benchmarks/RenderingBenchmarks.cs Updates benchmark terminal stub to satisfy new interface methods.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Termina/Input/EscapeSequenceParser.cs
Comment on lines +41 to +42
/// Resize detection is currently polling-based (matches <see cref="FallbackConsole"/>); SIGWINCH
/// integration is a follow-up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Comment on lines +239 to +246
if (_restored || !_rawModeEntered) return;
_restored = true;
TryLeaveKittyKeyboard();
var h = GCHandle.Alloc(_savedTermios, GCHandleType.Pinned);
try { _ = tcsetattr(StdInFd, Tcsanow, h.AddrOfPinnedObject()); }
catch { /* ignore */ }
finally { h.Free(); }
TerminaTrace.Platform.Debug(this, "UnixConsole termios restored");
Comment thread demos/Termina.Demo.KittyScroll/Program.cs Outdated
codymullins and others added 2 commits May 19, 2026 11:08
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@codymullins codymullins force-pushed the feature/alternate-scroll-mode branch from 81112a7 to e47414b Compare May 19, 2026 15:19
@codymullins

Copy link
Copy Markdown
Contributor Author

FYI, I'm not entirely sure this is the right thing to roll out, been looking at the other TUIs (OpenCode/Claude) and it seems they likely use SGR mouse tracking.

@Aaronontheweb

Copy link
Copy Markdown
Owner

I pushed a stacked hardening branch on top of this PR: fix/pr-215-hardening (00b4e0e).

Summary of what changed:

  • restored legacy mouse tracking as the framework default
  • gated alternate-scroll behind explicit runtime opt-in plus active raw input
  • moved kitty keyboard negotiation to a single owner (TerminaApplication) instead of negotiating it in multiple layers
  • tightened parser behavior so wheel-vs-arrow disambiguation only flips when kitty report_all_keys is actually visible on the raw-byte path
  • added a Unix non-TTY guard so raw input falls back cleanly instead of failing in tcgetattr(stdin)
  • scoped double-press Ctrl+C to raw-input mode by default
  • hardened FallbackConsole for non-interactive environments
  • added docs for runtime input modes, raw input, alternate-scroll, kitty flags, and tmux passthrough
  • added regression tests for the runtime/input behavior

Validation on the stacked branch:

  • dotnet build Termina.slnx passed
  • dotnet test Termina.slnx passed

Branch for review:

  • fix/pr-215-hardening

@Aaronontheweb Aaronontheweb merged commit 7ab0d2f into Aaronontheweb:dev May 23, 2026
8 checks passed
Aaronontheweb added a commit that referenced this pull request May 23, 2026
- Update version to 0.10.0
- Add release notes for v0.10.0 with PRs #220, #219, #215, #217
- Update PackageReleaseNotes in Directory.Build.props
- Update RELEASE_NOTES.md with full release notes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants