From a4e3ad47c7f03f8801a4780d4a6d29c56412227d Mon Sep 17 00:00:00 2001 From: zenglingzi Date: Wed, 1 Jul 2026 08:58:03 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E7=BB=88=E7=AB=AF=20=E2=8C=98V=20?= =?UTF-8?q?=E7=B2=98=E8=B4=B4=E5=9B=BE=E7=89=87=E5=A4=B1=E6=95=88=E2=80=94?= =?UTF-8?q?=E2=80=94=E7=A9=BA=E6=96=87=E6=9C=AC=E4=B9=9F=E5=8F=91=20bracke?= =?UTF-8?q?ted-paste=20=E4=BF=A1=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #27 的复制粘贴修复给 ⌘V 加了自定义处理器,只在 readText() 拿到非空文本时 才调 xterm.paste。但图片粘贴靠的是 claude 收到粘贴信号(bracketed paste 序列)后主动读系统剪贴板取图;纯图片剪贴板的 readText() 返回空串/reject, 于是 xterm.paste 从不被调用,claude 收不到信号,图片粘贴回归失效。 改为无论文本是否为空都 xterm.paste(text || ''),reject 分支同样补发 xterm.paste(''),还原 #27 之前的行为。空 bracketed paste 对 shell/claude 是无害 no-op,仅作为让 claude 去读剪贴板的触发信号;文本粘贴与 ⌘C 复制不受影响。 Co-Authored-By: Claude Opus 4.8 --- public/app.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/public/app.js b/public/app.js index c76cb4c..5b76fcd 100644 --- a/public/app.js +++ b/public/app.js @@ -3270,11 +3270,13 @@ const term = { return true; } if (cmd && (e.key === 'v' || e.key === 'V')) { - // ⌘V 粘贴 + // ⌘V 粘贴:空文本也要 paste('') 发出 bracketed-paste 序列。 + // 图片粘贴靠的是 claude 收到粘贴信号后主动读系统剪贴板取图;若这里因「无文本」 + // 就不调 xterm.paste,终端收不到任何信号,claude 永远不会去读图 → 图片粘贴失效(#图片粘贴回归) e.preventDefault(); - navigator.clipboard.readText().then(text => { - if (text) xterm.paste(text); - }).catch(() => { /* 无权限时走Electron菜单兜底 */ }); + navigator.clipboard.readText() + .then(text => xterm.paste(text || '')) + .catch(() => xterm.paste('')); // 纯图片剪贴板 readText 会返回空/reject,同样补发空粘贴信号 return false; } if (cmd && (e.key === '=' || e.key === '+' || e.key === '0')) { From 36cf3d1415cabd696f885b0d2965baf6ca0e69cd Mon Sep 17 00:00:00 2001 From: zenglingzi Date: Wed, 1 Jul 2026 14:18:13 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E7=BB=88=E7=AB=AF=20Ctrl+V=20?= =?UTF-8?q?=E7=B2=98=E8=B4=B4=E5=9B=BE=E7=89=87=E9=87=8D=E5=A4=8D=E4=B8=A4?= =?UTF-8?q?=E6=AC=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前一提交让 ⌘V 处理器对空文本也 xterm.paste('') 发 bracketed-paste 信号 以修复图片粘贴,但撞上了 xterm 自带的原生 paste 监听:同一次按键两条路径 各发一遍空信号,claude 便读两次系统剪贴板 → 图片粘两张。Cmd+V 时 preventDefault 顺带压掉了原生事件,Ctrl+V 压不住,故只有 Ctrl+V 双发。 键盘接管粘贴时打时间戳,捕获阶段一次性拦掉紧随其后(<60ms)的那次原生 paste, 避免双发;拦截时把原生事件携带的 clipboardData 文本存下,供 readText 失败时兜底, 不丢文本粘贴。鼠标点「编辑→粘贴」无前置按键戳,不拦,保持可用。 Co-Authored-By: Claude Opus 4.8 --- public/app.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/public/app.js b/public/app.js index 5b76fcd..d8a8b6a 100644 --- a/public/app.js +++ b/public/app.js @@ -3273,10 +3273,14 @@ const term = { // ⌘V 粘贴:空文本也要 paste('') 发出 bracketed-paste 序列。 // 图片粘贴靠的是 claude 收到粘贴信号后主动读系统剪贴板取图;若这里因「无文本」 // 就不调 xterm.paste,终端收不到任何信号,claude 永远不会去读图 → 图片粘贴失效(#图片粘贴回归) + sess._kbPasteTs = Date.now(); // 标记这次粘贴由键盘接管,供下方拦掉 xterm 原生 paste 事件,防双发(Ctrl+V 图片粘两张) + sess._pasteFallback = ''; // 清掉上次的兜底文本,避免陈旧误用 e.preventDefault(); navigator.clipboard.readText() .then(text => xterm.paste(text || '')) - .catch(() => xterm.paste('')); // 纯图片剪贴板 readText 会返回空/reject,同样补发空粘贴信号 + // readText 无权限/非安全上下文会 reject:用原生 paste 事件抓到的 clipboardData 文本兜底, + // 别让这些环境下文本粘贴失效(图片剪贴板没有文本,兜底为空,仍是空信号让 claude 去读图) + .catch(() => xterm.paste(sess._pasteFallback || '')); return false; } if (cmd && (e.key === '=' || e.key === '+' || e.key === '0')) { @@ -3293,6 +3297,22 @@ const term = { return true; }); + // 防双重粘贴:⌘V/Ctrl+V 已由上面的键盘处理器接管(navigator.clipboard + xterm.paste), + // 但 xterm 自带的原生 paste 监听会对同一次按键再发一遍 bracketed-paste,claude 便读两次剪贴板 + // → 图片粘两张(Cmd+V 时 preventDefault 顺带压掉了原生事件,Ctrl+V 压不住,故只有 Ctrl+V 双发)。 + // 只吞「键盘刚接管」紧随的那一次原生 paste:一次性消费(命中即置 0),60ms 仅作兜底上限—— + // 原生 paste 是按键默认动作、几毫秒内必到,60ms 足够拦住;而人手二次粘贴间隔 >150ms, + // 故即便某次没等到原生 paste(如 Cmd+V 原生事件已被 preventDefault 取消)致标记未被消费, + // 60ms 内也活不到下一次真实粘贴 → 不会误吞紧接着的合法粘贴。 + // 拦掉前先把它携带的 clipboardData 文本存下:上面 readText 失败时用它兜底,保住文本粘贴不失效。 + host.addEventListener('paste', (e) => { + if (Date.now() - (sess._kbPasteTs || 0) < 60) { + sess._kbPasteTs = 0; // 一次性:消费掉,后续粘贴(含快速再点「编辑→粘贴」)不再受这次按键影响 + try { sess._pasteFallback = (e.clipboardData && e.clipboardData.getData('text/plain')) || ''; } catch { /* */ } + e.preventDefault(); e.stopImmediatePropagation(); + } + }, true); + // 右键菜单:复制/粘贴 host.addEventListener('contextmenu', (e) => { e.preventDefault();