From c6d856649f0143cdf6cfed6150d35705b08e5158 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Tue, 10 Mar 2026 10:01:10 +0100 Subject: [PATCH] fix: forward wheel events with coordinates when mouse tracking is active When a TUI application enables mouse tracking (modes 1000/1002/1003), wheel events were being intercepted by the Terminal-level capture handler and converted to arrow key sequences, losing the mouse position. This meant applications like tmux or vim with split panes could not determine which zone the cursor was over, causing the wrong pane to scroll. Now, when mouse tracking is active, Terminal forwards wheel events to InputHandler.handleWheelEvent() which sends proper SGR/X10 mouse sequences with cell coordinates (button 64/65 for scroll up/down). Assisted-By: docker-agent --- lib/input-handler.ts | 26 +++++++++++++++++++++++--- lib/terminal.ts | 14 ++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/lib/input-handler.ts b/lib/input-handler.ts index 83d6f3f..dc90249 100644 --- a/lib/input-handler.ts +++ b/lib/input-handler.ts @@ -894,6 +894,29 @@ export class InputHandler { if (this.isDisposed) return; if (!this.mouseConfig?.hasMouseTracking()) return; + this.sendWheelMouseEvent(event); + + // Prevent default scrolling when mouse tracking is active + event.preventDefault(); + } + + /** + * Send a wheel event as a mouse tracking sequence. + * Public so that Terminal can forward wheel events when mouse tracking is + * active (the Terminal-level capture handler stops propagation to prevent + * browser scrolling, so this method allows explicit forwarding). + */ + handleWheelEvent(event: WheelEvent): void { + if (this.isDisposed) return; + + this.sendWheelMouseEvent(event); + } + + /** + * Encode and send a wheel event as a mouse tracking escape sequence. + * Button 64 = scroll up, button 65 = scroll down, with cell coordinates. + */ + private sendWheelMouseEvent(event: WheelEvent): void { const cell = this.pixelToCell(event); if (!cell) return; @@ -901,9 +924,6 @@ export class InputHandler { const button = event.deltaY < 0 ? 64 : 65; this.sendMouseEvent(button, cell.col, cell.row, false, event); - - // Prevent default scrolling when mouse tracking is active - event.preventDefault(); } /** diff --git a/lib/terminal.ts b/lib/terminal.ts index eeb7acd..1d685e9 100644 --- a/lib/terminal.ts +++ b/lib/terminal.ts @@ -1552,6 +1552,20 @@ export class Terminal implements ITerminalCore { return; } + // When mouse tracking is active, the application wants to receive mouse + // wheel events with coordinates so it can determine which pane/zone the + // cursor is over (critical for TUIs with multiple scroll regions like + // tmux, vim splits, etc.). Delegate to the InputHandler which sends + // proper SGR/X10 mouse sequences with the cell position. + if (this.wasmTerm?.hasMouseTracking()) { + // InputHandler.handleWheel is registered on the same container but in + // the bubbling phase. Since we already called stopPropagation() above + // (to prevent the browser from scrolling the page), we need to forward + // the event explicitly. + this.inputHandler?.handleWheelEvent(e); + return; + } + // Check if in alternate screen mode (vim, less, htop, etc.) const isAltScreen = this.wasmTerm?.isAlternateScreen() ?? false;