Skip to content

fix: cursor shape (DECSCUSR), Ctrl+V forwarding, and mouse scroll in alt screen#147

Open
jesse23 wants to merge 1 commit into
coder:mainfrom
jesse23:fix/pty-input-handling-gaps
Open

fix: cursor shape (DECSCUSR), Ctrl+V forwarding, and mouse scroll in alt screen#147
jesse23 wants to merge 1 commit into
coder:mainfrom
jesse23:fix/pty-input-handling-gaps

Conversation

@jesse23
Copy link
Copy Markdown

@jesse23 jesse23 commented Mar 28, 2026

Fixes all three issues reported in #145. Each fix is small and self-contained.

Bug 1 — DECSCUSR cursor shape silently dropped

GhosttyTerminal.getCursor() hardcoded style: 'block' and blinking: false with TODO comments. Added two new WASM exports to the patch:

  • ghostty_render_state_get_cursor_style(term)0=block, 1=bar, 2=underline (reads terminal.screens.active.cursor.cursor_style)
  • ghostty_render_state_get_cursor_blinking(term) → bool (reads terminal.modes.get(.cursor_blinking))

getCursor() now returns the live values so the renderer reflects cursor shape changes from PTY output.

Bug 2 — Ctrl+V not forwarded to PTY

handleKeyDown returned early on Ctrl+V/Cmd+V without emitting \x16, so apps that read it natively (image paste flows, readline yank) never received it. Now encodes and emits the keydown via onDataCallback before returning. The browser paste event still fires afterwards — handlePaste covers text paste unchanged.

Bug 3 — Mouse wheel sends arrow keys when mouse tracking is active

handleWheel sent \x1B[A/\x1B[B unconditionally on the alternate screen, ignoring whether the app had requested mouse events. Added a hasMouseTracking() guard: when active, emits an SGR scroll sequence (\x1b[<64/65;col;rowM) via dataEmitter instead. The arrow-key fallback is preserved for apps without mouse tracking (less, man, etc.).


Note: Bug 1 requires a WASM rebuild to take effect at runtime. The patch changes are ready; the resulting .wasm and .js bundle would need to be regenerated.

Ref: #145


Downstream pR to prove the fix works:
jesse23/webtty#21

Bug 1 — DECSCUSR cursor shape silently dropped:
Add ghostty_render_state_get_cursor_style and
ghostty_render_state_get_cursor_blinking WASM exports to the patch.
GhosttyTerminal.getCursor() now reads both from the terminal state
instead of hardcoding style:'block' and blinking:false.

Bug 2 — Ctrl+V not forwarded to PTY:
InputHandler.handleKeyDown emits the encoded \x16 byte to onDataCallback
before returning, so apps that read Ctrl+V natively (e.g. image paste
flows) receive it. The browser paste event still fires afterwards so
handlePaste continues to cover text paste unchanged.

Bug 3 — Mouse wheel sends arrow keys when mouse tracking is active:
Terminal.handleWheel checks hasMouseTracking() in the isAlternateScreen
branch. When active, it emits an SGR scroll sequence
(\x1b[<64/65;col;rowM) via dataEmitter instead of arrow keys, matching
xterm.js behaviour. The arrow-key fallback is preserved for apps without
mouse tracking (less, man, etc.).

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@jesse23 jesse23 changed the title fix: three PTY input handling gaps (DECSCUSR cursor shape, Ctrl+V, mouse scroll) fix: cursor shape (DECSCUSR), Ctrl+V forwarding, and mouse scroll in alt screen Mar 28, 2026
diegosouzapw added a commit to diegosouzapw/ghostty-web that referenced this pull request May 23, 2026
…alt screen

Three independent PTY-input gaps wrapped in a single port from upstream
coder#147 because they share the same WASM-side patch surface.

1. **DECSCUSR (cursor shape)** — apps that send the `CSI Ps SP q` DECSCUSR
   sequence to change cursor shape (block/bar/underline) and blink state
   used to be silently ignored on the JS side. WASM now exports
   `render_state_get_cursor_style` and `render_state_get_cursor_blinking`;
   the renderer queries them each frame so vim/tmux insert-mode cursor
   styles take effect.

2. **Ctrl+V forwarding** — Ctrl+V used to be intercepted and dropped so
   the browser paste event could handle it. That broke apps that read
   raw \\x16 from the PTY (e.g. opencode triggering osascript image
   paste). Ctrl+V now emits \\x16 via the Ghostty key encoder AND still
   lets the paste event fire for text content. Cmd+V on macOS behaves
   as before (no byte emitted, paste event handles it).

3. **Mouse scroll in alt screen** — wheel events while in the alt screen
   buffer (vim, less, htop) used to bypass mouse-tracking. Now they go
   through the same mouse-tracking path as the main screen, so apps that
   subscribe to wheel events receive them in alt screen too.

WASM-API patch updates:

- New exports for cursor_style / cursor_blinking
- Hunk headers in patches/ghostty-wasm-api.patch recounted to reflect
  the added lines (the original patch upstream had stale @@ headers
  that prevented `git apply` from succeeding)

The two pre-existing "Ctrl+V/Cmd+V should not emit onData" tests were
documenting the old (now-incorrect) behaviour and have been rewritten
to assert the new contract: Ctrl+V → \\x16, Cmd+V → empty (encoder
returns no bytes for Super modifier).

Co-authored-by: Jesse Peng <jesse23@gmail.com>
Inspired-by: coder#147
diegosouzapw added a commit to diegosouzapw/ghostty-web that referenced this pull request May 23, 2026
…alt screen (#13)

Three independent PTY-input gaps wrapped in a single port from upstream
coder#147 because they share the same WASM-side patch surface.

1. **DECSCUSR (cursor shape)** — apps that send the `CSI Ps SP q` DECSCUSR
   sequence to change cursor shape (block/bar/underline) and blink state
   used to be silently ignored on the JS side. WASM now exports
   `render_state_get_cursor_style` and `render_state_get_cursor_blinking`;
   the renderer queries them each frame so vim/tmux insert-mode cursor
   styles take effect.

2. **Ctrl+V forwarding** — Ctrl+V used to be intercepted and dropped so
   the browser paste event could handle it. That broke apps that read
   raw \\x16 from the PTY (e.g. opencode triggering osascript image
   paste). Ctrl+V now emits \\x16 via the Ghostty key encoder AND still
   lets the paste event fire for text content. Cmd+V on macOS behaves
   as before (no byte emitted, paste event handles it).

3. **Mouse scroll in alt screen** — wheel events while in the alt screen
   buffer (vim, less, htop) used to bypass mouse-tracking. Now they go
   through the same mouse-tracking path as the main screen, so apps that
   subscribe to wheel events receive them in alt screen too.

WASM-API patch updates:

- New exports for cursor_style / cursor_blinking
- Hunk headers in patches/ghostty-wasm-api.patch recounted to reflect
  the added lines (the original patch upstream had stale @@ headers
  that prevented `git apply` from succeeding)

The two pre-existing "Ctrl+V/Cmd+V should not emit onData" tests were
documenting the old (now-incorrect) behaviour and have been rewritten
to assert the new contract: Ctrl+V → \\x16, Cmd+V → empty (encoder
returns no bytes for Super modifier).


Inspired-by: coder#147

Co-authored-by: Jesse Peng <jesse23@gmail.com>
@diegosouzapw
Copy link
Copy Markdown

Hi @jesse23! 👋

Your work on this PR inspired a commit in my fork diegosouzapw/ghostty-web.
I ported the cursor shape (DECSCUSR), Ctrl+V forwarding, and alt screen scroll fixes, and added you as co-author in the corresponding commit — thank you for the contribution!

I'm working on OmniRoute, a project that provides free access to LLM models, and I'm planning to use ghostty-web as the terminal component there. Your work is part of what makes that possible. 🙏

Feel free to check it out — contributions and feedback are very welcome!

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.

2 participants