From dfaa64ad39d7b86120de8c1658615f030fa09771 Mon Sep 17 00:00:00 2001 From: Gloridust Date: Sun, 14 Jun 2026 16:48:59 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(desktop):=20=E4=BF=AE=E6=97=A0=E6=84=9F?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E4=B8=A4=E5=A4=84=20bug=EF=BC=88=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E4=B8=8D=E7=94=9F=E6=95=88=20+=20"=E4=BD=A0=E5=A5=BDy?= =?UTF-8?q?=E5=91=80"=E4=B8=A2=E5=AD=97=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) 切到无感后当前会话仍是旧 enable_ime → 表现"还是打英文,要换页才行"。 setMode 现同步写 enable_ime 并 bump vncNonce 重挂 iframe,让 noVNC 立即按新模式重连。 2) "你好[空格]呀"打出"你好y呀":有序队列在中文转发期间会把下一个词的拼音首字母(y)当字面字符抢走。 改为队列活跃时只接管【数字】(原"混数字丢字"的祸首) + 回车/退格;字母绝不接管,交给输入法合成。 附带消除了之前"每个键都走 xdotool"导致的卡顿(字母回到原生合成快路径)。 Co-Authored-By: Claude Opus 4.8 --- panel/web/src/pages/Desktop.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/panel/web/src/pages/Desktop.tsx b/panel/web/src/pages/Desktop.tsx index 29c6024..e661e33 100644 --- a/panel/web/src/pages/Desktop.tsx +++ b/panel/web/src/pages/Desktop.tsx @@ -50,12 +50,14 @@ function installSeamlessIme(win: Window, doc: Document, instId: string): () => v }; // 捕获阶段(iframe window 最外层)抢先拦截,赶在 noVNC 之前 → stopImmediatePropagation 阻止它发 keysym。 + // 关键:队列活跃(有中文正在转发)时,只接管【数字】和回车/退格——它们不参与拼音合成、且是原"混数字丢字"的祸首; + // 字母绝不接管,否则会把下一个词的拼音首字母(如"呀"的 y)当成字面字符抢走,造成"你好y呀"。字母交给输入法合成。 const onKeyDownCapture = (ev: Event) => { const e = ev as KeyboardEvent; - if (e.isComposing) return; // 拼音合成中,交给输入法 + if (e.isComposing) return; // 拼音合成中,交给输入法(候选数字选词也在此放行) if (e.ctrlKey || e.altKey || e.metaKey) return; // 快捷键放行 if (!active()) return; // 没有中文在转发 → 不接管(英文/数字走原生 keysym,零延迟) - if (e.key.length === 1) { + if (/^[0-9]$/.test(e.key)) { e.preventDefault(); e.stopImmediatePropagation(); queue.push({ kind: 'text', data: e.key }); @@ -126,9 +128,14 @@ export default function InstanceView({ onOpenMenu }: { onOpenMenu: () => void }) setInputMode(m); try { window.localStorage.setItem('woc_input_mode', m); + // 同步写好 enable_ime,供下面重挂的 iframe 里 noVNC 连接时读取 + window.localStorage.setItem('enable_ime', m === 'seamless' ? 'true' : 'false'); } catch { /* 隐私模式禁用 localStorage:忽略 */ } + // 关键:重挂 iframe 让 noVNC 重新读取 enable_ime。否则当前已连接的会话仍是旧模式, + // 表现为"刚切到无感还是打出英文,要换页再回来才行"。 + setVncNonce((n) => n + 1); }; const [imeText, setImeText] = useState(''); const [imeSending, setImeSending] = useState(false); From 659d26ce68761a0d2963d8a406aecddef8f2f0e7 Mon Sep 17 00:00:00 2001 From: Gloridust Date: Sun, 14 Jun 2026 16:57:33 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(docker):=20typeInInstance=20=E5=8D=A1?= =?UTF-8?q?=20~2s=20=E6=A0=B9=E5=9B=A0=E2=80=94=E2=80=94xclip=20=E6=9C=AA?= =?UTF-8?q?=E9=87=8D=E5=AE=9A=E5=90=91=20fd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xclip -i 会 daemon 化常驻持有剪贴板选区,并继承 docker exec 的 stdout/stderr, 导致 exec 要等这俩 fd 关闭、每次中文转发卡 ~2.1s。给 xclip 重定向 >/dev/null 2>&1 后整条链路降到 ~0.08s(实测 26× 提速)。中文输入条与无感模式都受益。 Co-Authored-By: Claude Opus 4.8 --- panel/server/src/docker.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/panel/server/src/docker.ts b/panel/server/src/docker.ts index d32af72..af9c56b 100644 --- a/panel/server/src/docker.ts +++ b/panel/server/src/docker.ts @@ -576,7 +576,9 @@ export async function typeInInstance(inst: Instance, text: string): Promise/dev/null 2>&1 || { echo "xclip not installed in instance image" >&2; exit 127; }', 'command -v xdotool >/dev/null 2>&1 || { echo "xdotool not installed in instance image" >&2; exit 127; }', - `echo '${b64}' | base64 -d | xclip -selection clipboard -i`, + // xclip -i 会 daemon 化常驻持有剪贴板选区,并继承 exec 的 stdout/stderr;不重定向的话 docker exec + // 要等这俩 fd 关闭,实测每次卡 ~2s。重定向到 /dev/null 后台后,整条链路从 ~2.1s 降到 ~0.08s。 + `echo '${b64}' | base64 -d | xclip -selection clipboard -i >/dev/null 2>&1`, 'xdotool key --clearmodifiers ctrl+v', ].join('; '); await execCapture(inst, ['bash', '-c', cmd]); From 13ec0bdf8fbf33edac47e9e7d49cc58267f07d46 Mon Sep 17 00:00:00 2001 From: Gloridust Date: Sun, 14 Jun 2026 17:08:00 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(desktop):=20=E8=BE=93=E5=85=A5=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E5=88=87=E6=8D=A2=E6=94=B9=E4=B8=BA=E6=95=B4=E9=A1=B5?= =?UTF-8?q?=E9=87=8D=E8=BD=BD=EF=BC=8C=E4=BF=AE=E5=8D=A1=E6=AD=BB=20+=20?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=8D=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 切换模式时原先 bump vncNonce 在页内重挂 iframe,会让新旧两条 VNC ws 短暂并存, 概率性把实例 Xvnc 卡死(需重启容器恢复、面板重启无效),且新连接常读不到新 enable_ime (仍是英文)。改为 window.location.reload():先卸载旧页彻底关旧 ws,再以新模式干净重连, 正是用户实测唯一可靠的方式。 Co-Authored-By: Claude Opus 4.8 --- panel/web/src/pages/Desktop.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/panel/web/src/pages/Desktop.tsx b/panel/web/src/pages/Desktop.tsx index e661e33..f4fd27d 100644 --- a/panel/web/src/pages/Desktop.tsx +++ b/panel/web/src/pages/Desktop.tsx @@ -125,17 +125,17 @@ export default function InstanceView({ onOpenMenu }: { onOpenMenu: () => void }) } }); const setMode = (m: 'forward' | 'seamless') => { - setInputMode(m); try { window.localStorage.setItem('woc_input_mode', m); - // 同步写好 enable_ime,供下面重挂的 iframe 里 noVNC 连接时读取 + // 同步写好 enable_ime,重载后新页面的 noVNC 连接时即读到 window.localStorage.setItem('enable_ime', m === 'seamless' ? 'true' : 'false'); } catch { /* 隐私模式禁用 localStorage:忽略 */ } - // 关键:重挂 iframe 让 noVNC 重新读取 enable_ime。否则当前已连接的会话仍是旧模式, - // 表现为"刚切到无感还是打出英文,要换页再回来才行"。 - setVncNonce((n) => n + 1); + // 整页重载切换:先卸载旧页面(彻底关闭旧 VNC ws),再以新 enable_ime 干净重连。 + // 不能用页内 bump vncNonce 重挂 iframe——那会让新旧两条 ws 短暂并存,概率性把实例的 Xvnc 卡死 + //(需重启容器才恢复、面板重启无效),且新连接常读不到新模式(仍是英文)。整页重载是实测唯一可靠的方式。 + window.location.reload(); }; const [imeText, setImeText] = useState(''); const [imeSending, setImeSending] = useState(false);