diff --git a/src/main/capabilities/desktop.ts b/src/main/capabilities/desktop.ts index 4952ead..fc3da8c 100644 --- a/src/main/capabilities/desktop.ts +++ b/src/main/capabilities/desktop.ts @@ -135,6 +135,11 @@ export function psString(value: unknown): string { return `'${String(value ?? "").replace(/'/g, "''")}'`; } +/** 把 SendKeys 特殊字符按字面转义(+^%~(){}[] → {+} 等);SendKeys 用花括号表示字面量 */ +function sendKeysEscape(ch: string): string { + return "+^%~(){}[]".includes(ch) ? `{${ch}}` : ch; +} + const keyboardSend: CapabilityHandler = async (_ctx, node, args) => { // 字段: words=输入内容(键盘输入), keys/key=按键组合(按键系列), interval=输入间隔(V1毫秒/基础版秒), fuctype=输入方式(0后台接口/1人工模拟/2剪贴板粘贴), window_title=输入目标窗口(留空=当前焦点窗口) const keys = text(args, ["words", "keys", "key", "text", "value"]); @@ -144,17 +149,26 @@ const keyboardSend: CapabilityHandler = async (_ctx, node, args) => { const intervalMs = node.componentId === "Keyboard_Send" ? Math.max(0, Math.round(rawInterval * 1000)) : Math.max(0, Math.round(rawInterval)); const mode = text(args, ["fuctype"], "0"); const isTextSend = node.componentId === "Keyboard_Send" || node.componentId === "Keyboard_Send_V1"; - // 文本输入需转义 SendKeys 特殊字符(+^%~(){}[])按字面输入;按键组合组件(Keyboard_Press)与粘贴模式保留原文 - const escape = isTextSend && mode !== "2" ? `$t = $t -replace '[+^%~(){}[\\]]','{$0}'; ` : ""; - const send = mode === "2" - ? `Set-Clipboard -Value $t; Start-Sleep -Milliseconds 120; [System.Windows.Forms.SendKeys]::SendWait('^v')` - : mode === "1" - ? `foreach ($ch in $t.ToCharArray()) { $c = [string]$ch; if ('+^%~(){}[]'.Contains($c)) { $c = '{' + $c + '}' }; [System.Windows.Forms.SendKeys]::SendWait($c); Start-Sleep -Milliseconds ${intervalMs} }` - : `[System.Windows.Forms.SendKeys]::SendWait($t)`; // 指定目标窗口时先激活再输入,内容才会进到目标窗口而不是运行时的焦点窗口 const targetTitle = text(args, ["window_title", "target_window", "window_obj", "match_title", "select_title"]); const activate = targetTitle ? `${activateWindowScript(targetTitle)}; Start-Sleep -Milliseconds 300; ` : ""; - await powershell(`${USER32}; ${activate}Add-Type -AssemblyName System.Windows.Forms; $t = ${psString(keys)}; ${escape}${send}`); + + // 三种输入方式:脚本尽量简单,转义逻辑放 JS 侧,避免 PowerShell 字符串拼接的语法陷阱 + let send: string; + if (mode === "2") { + // 剪贴板粘贴:原文进剪贴板 + Ctrl+V,不做 SendKeys 转义 + send = `Set-Clipboard -Value ${psString(keys)}; Start-Sleep -Milliseconds 120; [System.Windows.Forms.SendKeys]::SendWait('^v')`; + } else if (mode === "1" && isTextSend) { + // 人工模拟:逐字符;JS 侧把每个字符转义成 SendKeys 字面量(含单引号用 '' 转义),PS 侧只做数组循环 + const escapedChars = Array.from(keys).map((ch) => sendKeysEscape(ch).replace(/'/g, "''")); + const arr = escapedChars.map((c) => `'${c}'`).join(","); + send = `$chars = @(${arr}); foreach ($c in $chars) { [System.Windows.Forms.SendKeys]::SendWait($c); Start-Sleep -Milliseconds ${intervalMs} }`; + } else { + // 后台接口 / 按键组合 / 人工模拟(按键组合组件):整段发送,文本特殊字符转义 + const escaped = isTextSend ? Array.from(keys).map(sendKeysEscape).join("") : keys; + send = `[System.Windows.Forms.SendKeys]::SendWait(${psString(escaped)})`; + } + await powershell(`${USER32}; ${activate}Add-Type -AssemblyName System.Windows.Forms; ${send}`); await manualDelay(); return keys; }; diff --git a/src/main/capabilities/keyboard-send-smoke.test.ts b/src/main/capabilities/keyboard-send-smoke.test.ts index 953d7ae..d2096d2 100644 --- a/src/main/capabilities/keyboard-send-smoke.test.ts +++ b/src/main/capabilities/keyboard-send-smoke.test.ts @@ -32,6 +32,12 @@ describe("键盘输入组件真实冒烟", () => { console.log("[键盘输入] 人工模拟逐字符 ✓:", out); }, 60000); + it("人工模拟模式含撇号与特殊字符(用户 09:05 踩坑场景)不抛语法错", async () => { + const out = await invoke("Keyboard_Send_V1", { words: "hhhhh' +^%~(){}[]", interval: "10", fuctype: "1" }); + expect(out).toBe("hhhhh' +^%~(){}[]"); + console.log("[键盘输入] 人工模拟+撇号+特殊字符 ✓:", out); + }, 60000); + it("剪切板粘贴模式: 原文进入剪贴板并发送 Ctrl+V", async () => { const content = `粘贴${Date.now()}`; const out = await invoke("Keyboard_Send_V1", { words: content, fuctype: "2" });