From 82d1b8de10a036c5f2dc7c83ddd84cf7b42fcaaf Mon Sep 17 00:00:00 2001 From: "Logan.Li" Date: Sun, 26 Jul 2026 19:09:18 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=9C=A8=E7=BB=88=E7=AB=AF=E5=AE=B9?= =?UTF-8?q?=E5=99=A8=E6=8D=95=E8=8E=B7Escape=E6=8C=89=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在xterm容器捕获阶段转发Escape控制字符 - 阻止Radix与xterm继续重复处理同一事件 --- .../src/components/server/TerminalDialog.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/server/TerminalDialog.tsx b/frontend/src/components/server/TerminalDialog.tsx index b0b377d..669784a 100644 --- a/frontend/src/components/server/TerminalDialog.tsx +++ b/frontend/src/components/server/TerminalDialog.tsx @@ -132,12 +132,6 @@ export function TerminalDialog({ const ws = new WebSocket(wsUrl); wsRef.current = ws; - // xterm 在目标元素阶段直接转发 Escape,避免 Radix 捕获层吞掉 Vim 控制字符。 // Logan.Li - term.attachCustomKeyEventHandler((event) => { - if (event.key !== "Escape") return true; - if (event.type === "keydown") sendTerminalInput("\x1b"); - return false; - }); term.onData(sendTerminalInput); ws.onmessage = (event) => { @@ -189,6 +183,7 @@ export function TerminalDialog({ if (!open || !authReady) return; let cleanup: (() => void) | undefined; + let detachEscape: (() => void) | undefined; let fitFrame = 0; let retry = 0; @@ -201,6 +196,16 @@ export function TerminalDialog({ if (retry++ < 30) requestAnimationFrame(attach); return; } + // 在 xterm 目标元素之前截获 Escape,直接发送给远端并阻止 Radix/xterm 重复处理。 // Logan.Li + const forwardEscape = (event: KeyboardEvent) => { + if (event.key !== "Escape") return; + event.preventDefault(); + event.stopPropagation(); + sendTerminalInput("\x1b"); + }; + container.addEventListener("keydown", forwardEscape, true); + detachEscape = () => + container.removeEventListener("keydown", forwardEscape, true); cleanup = attachTerminalResize(container, fit, term, ws); const applyFit = () => { try { @@ -217,6 +222,7 @@ export function TerminalDialog({ attach(); return () => { if (fitFrame) cancelAnimationFrame(fitFrame); + detachEscape?.(); cleanup?.(); }; }, [open, authReady, fullscreen]);