Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

PTY input handling gaps: cursor shape (DECSCUSR), Ctrl+V forwarding, and mouse scroll

Đang mở
#145 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
58/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Ít trao đổi
Công nghệ
typescript, wasm
Lĩnh vực
frontend

Hướng nghiên cứu

Bắt đầu với ba điểm vào được nêu tên: lib/ghostty.ts, lib/input-handler.ts và src/Terminal.ts, sử dụng các ADRs 013, 014 và 017 để làm ngữ cảnh. Xác nhận từng hành vi tại ranh giới PTY: cập nhật hình dạng con trỏ, chuyển tiếp Ctrl+V và các sự kiện con lăn SGR trong khi theo dõi chuột; hoàn tất nghĩa là cả ba bản sửa đều hoạt động mà không làm hồi quy việc dán văn bản hoặc thao tác cuộn viewport thông thường.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

We hit three input handling gaps while building webtty — a browser terminal that uses ghostty-web as its renderer. All three are places where ghostty-web behaves differently from xterm.js, and all three have clean fixes in the JS layer.


Bug 1: DECSCUSR cursor shape sequences are silently dropped

Symptom: Applications like vim, neovim, and fish emit DECSCUSR (ESC [ Ps SP q) to switch cursor shape at runtime (bar in insert mode, block in normal mode). With ghostty-web, the cursor shape never changes — it stays fixed at whatever was set at construction time.

Root cause: GhosttyTerminal.getCursor() in lib/ghostty.ts hardcodes style: 'block' with a TODO comment instead of reading cursor_visual_style from the WASM render state. The WASM binary processes DECSCUSR correctly and updates RenderState.Cursor.visual_style — the JS wrapper just never reads it back.

Workaround we ship in webtty: We intercept DECSCUSR sequences client-side before term.write() and set term.options.cursorStyle directly. It works, but it is the wrong layer.

Proposed fix: In getCursor(), read cursor_visual_style (data key 10) via ghostty_render_state_get and map it to the renderer style strings. The render loop already calls getCursor() after each write() and passes the result to renderer.setCursorStyle() — so this one change is sufficient.

// lib/ghostty.ts — GhosttyTerminal.getCursor()
// data key 10 = cursor_visual_style (0=block, 1=underline, 2=bar)
const visualStyle = this.exports.ghostty_render_state_get(this.handle, 10);
const style = visualStyle === 2 ? 'bar' : visualStyle === 1 ? 'underline' : 'block';
return { x: ..., y: ..., visible: ..., blinking: ..., style };

Full analysis and context: ADR 013 — Fix to ghostty-web


Bug 2: Ctrl+V keydown is not forwarded to the PTY

Symptom: Pasting non-text content (e.g. an image) into a TUI application running under ghostty-web does nothing. The same setup works correctly under ttyd (xterm.js). Specifically, opencode's image paste flow — which relies on receiving \x16 in the PTY stream and then reading the clipboard natively via osascript / powershell / wl-paste — never triggers.

Root cause: InputHandler.handleKeyDown in lib/input-handler.ts returns early on Ctrl+V / Cmd+V without emitting \x16 to onDataCallback:

if ((A.ctrlKey || A.metaKey) && A.code === "KeyV")
  return; // \x16 never reaches the PTY

xterm.js forwards the keydown unconditionally; ghostty-web does not. For text paste this is invisible (the paste event fires and handlePaste covers it), but when the clipboard has no text/plain, handlePaste drops the event silently and the PTY sees nothing.

Workaround we ship in webtty: We add a capture-phase paste listener that sends \x16 when clipboardData has no text/plain. It works, but intercepting the paste event to infer a missed keydown is a hack.

Proposed fix: Emit the encoded keydown before the early return, so \x16 reaches the PTY. The paste event still fires afterwards, so text paste via handlePaste is completely unaffected.

// lib/input-handler.ts — InputHandler.handleKeyDown
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyV') {
  const encoded = this.encoder.encode({ key: Key.V, mods: Mods.CTRL, action: KeyAction.PRESS });
  if (encoded.length > 0) {
    this.onDataCallback(new TextDecoder().decode(encoded)); // \x16 → PTY
  }
  return; // browser paste event fires next → handlePaste covers text
}

Full analysis and context: ADR 014 — Fix to ghostty-web


Bug 3: Mouse wheel sends arrow keys instead of SGR scroll sequences when mouse tracking is active

Symptom: When a TUI app (e.g. vim with set mouse=a) enables mouse tracking, scrolling the mouse wheel moves the cursor up/down instead of scrolling the buffer. The same vim in xterm.js-based terminals (VSCode, iTerm2) scrolls correctly.

Root cause: Terminal.handleWheel in src/Terminal.ts is registered on the canvas with capture: true and calls stopPropagation() unconditionally — which prevents InputHandler.handleWheel (the handler that correctly checks hasMouseTracking() and sends SGR sequences) from ever running. It then sends arrow keys regardless of whether the app has requested mouse events:

// src/Terminal.ts — Terminal.handleWheel (current, broken)
this.handleWheel = (e: WheelEvent) => {
  e.preventDefault();
  e.stopPropagation();                          // ← blocks InputHandler
  if (this.customWheelEventHandler?.(e)) return;

  if (this.wasmTerm?.isAlternateScreen()) {
    const dir = e.deltaY > 0 ? 'down' : 'up';
    const lines = Math.min(Math.abs(Math.round(e.deltaY / 33)), 5);
    for (let i = 0; i < lines; i++)
      this.dataEmitter.fire(dir === 'up' ? '\x1B[A' : '\x1B[B'); // ← always, ignores mouse tracking
  } else {
    // scroll viewport
  }
};

xterm.js checks ctx.requestedEvents.wheel before falling back to arrow keys — ghostty-web skips this check entirely.

Workaround we ship in webtty: We use attachCustomWheelEventHandler to intercept wheel events when hasMouseTracking() is true and send SGR scroll sequences (\x1b[<64;col;rowM / \x1b[<65;col;rowM) directly to the PTY, returning true to prevent the arrow-key path from running.

Proposed fix: In the isAlternateScreen() branch, guard the arrow-key loop with hasMouseTracking(). When mouse tracking is active, emit the SGR scroll sequence via dataEmitter instead. The canvas and renderer are already available on this:

// src/Terminal.ts — Terminal.handleWheel (fixed)
if (this.wasmTerm?.isAlternateScreen()) {
  if (this.wasmTerm.hasMouseTracking()) {
    // App negotiated mouse tracking — send SGR scroll sequence, not arrow keys.
    const metrics = this.renderer?.getMetrics();
    if (metrics && this.canvas) {
      const rect = this.canvas.getBoundingClientRect();
      const col = Math.max(1, Math.floor((e.clientX - rect.left) / metrics.width) + 1);
      const row = Math.max(1, Math.floor((e.clientY - rect.top) / metrics.height) + 1);
      const btn = e.deltaY < 0 ? 64 : 65;
      this.dataEmitter.fire(`\x1b[<${btn};${col};${row}M`);
    }
    return;
  }
  // No mouse tracking: arrow-key fallback for apps like `less`.
  const dir = e.deltaY > 0 ? 'down' : 'up';
  const lines = Math.min(Math.abs(Math.round(e.deltaY / 33)), 5);
  for (let i = 0; i < lines; i++)
    this.dataEmitter.fire(dir === 'up' ? '\x1B[A' : '\x1B[B');
} else {
  // scroll viewport (unchanged)
}

Full analysis and context: ADR 017 — Fix to ghostty-web


Contribution

All three fixes are small and self-contained. We are happy to submit PRs for any or all of them if the approach looks right to you — just say the word.

Ngôn ngữ chính
TypeScript
Star
2.9k
Fork
179
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của coder/ghostty-web

Tất cả issue của coder/ghostty-web

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.