键盘输入支持选择输入目标窗口
This commit is contained in:
parent
46348413c1
commit
8d6f0ff755
@ -71044,6 +71044,19 @@
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "window_title",
|
||||
"label": "输入目标窗口",
|
||||
"widget": "text",
|
||||
"type": "string",
|
||||
"displayType": "text-expression",
|
||||
"required": false,
|
||||
"defaultValue": "",
|
||||
"placeholder": "留空则输入到当前焦点窗口",
|
||||
"description": "选择目标窗口后,运行时会先把该窗口带到前台再输入内容",
|
||||
"advanced": false,
|
||||
"valueTypes": []
|
||||
}
|
||||
],
|
||||
"output": {
|
||||
@ -71145,6 +71158,19 @@
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "window_title",
|
||||
"label": "输入目标窗口",
|
||||
"widget": "text",
|
||||
"type": "string",
|
||||
"displayType": "text-expression",
|
||||
"required": false,
|
||||
"defaultValue": "",
|
||||
"placeholder": "留空则输入到当前焦点窗口",
|
||||
"description": "选择目标窗口后,运行时会先把该窗口带到前台再输入内容",
|
||||
"advanced": false,
|
||||
"valueTypes": []
|
||||
}
|
||||
],
|
||||
"output": {
|
||||
|
||||
@ -99,7 +99,7 @@ export function psString(value: unknown): string {
|
||||
}
|
||||
|
||||
const keyboardSend: CapabilityHandler = async (_ctx, node, args) => {
|
||||
// 字段: words=输入内容(键盘输入), keys/key=按键组合(按键系列), interval=输入间隔(V1毫秒/基础版秒), fuctype=输入方式(0后台接口/1人工模拟/2剪贴板粘贴)
|
||||
// 字段: words=输入内容(键盘输入), keys/key=按键组合(按键系列), interval=输入间隔(V1毫秒/基础版秒), fuctype=输入方式(0后台接口/1人工模拟/2剪贴板粘贴), window_title=输入目标窗口(留空=当前焦点窗口)
|
||||
const keys = text(args, ["words", "keys", "key", "text", "value"]);
|
||||
if (!keys) throw new RuntimeError("validation_error", "键盘输入为空", { nodeId: node.id, componentId: node.componentId });
|
||||
await manualDelay();
|
||||
@ -114,7 +114,10 @@ const keyboardSend: CapabilityHandler = async (_ctx, node, args) => {
|
||||
: 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)`;
|
||||
await powershell(`Add-Type -AssemblyName System.Windows.Forms; $t = ${psString(keys)}; ${escape}${send}`);
|
||||
// 指定目标窗口时先激活再输入,内容才会进到目标窗口而不是运行时的焦点窗口
|
||||
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}`);
|
||||
await manualDelay();
|
||||
return keys;
|
||||
};
|
||||
@ -164,12 +167,10 @@ const mouseRoll: CapabilityHandler = async (_ctx, node, args) => {
|
||||
return amount;
|
||||
};
|
||||
|
||||
const setActiveWindow: CapabilityHandler = async (_ctx, node, args) => {
|
||||
const selector = text(args, ["window_obj", "match_title", "select_title", "title", "target_window", "window_title"]);
|
||||
if (!selector) throw new RuntimeError("validation_error", "窗口标题为空", { nodeId: node.id, componentId: node.componentId });
|
||||
const result = await powershell(`${USER32}
|
||||
$p = Get-Process | Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle -like ${psString(`*${selector}*`)} } | Select-Object -First 1
|
||||
if (-not $p) { throw '找不到窗口' }
|
||||
/** 把匹配标题的窗口带到前台(Alt trick + AttachThreadInput 兜底);拼在 USER32 之后使用 */
|
||||
function activateWindowScript(selector: string): string {
|
||||
return `$p = Get-Process | Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle -like ${psString(`*${selector}*`)} } | Select-Object -First 1
|
||||
if (-not $p) { throw '找不到窗口:${selector.replace(/'/g, "''")}' }
|
||||
Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int cmd); [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h); [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr pid); [DllImport("user32.dll")] public static extern bool AttachThreadInput(uint a, uint b, bool attach); [DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();' -Name RpaFocus2 -Namespace WinRpaFocus2 | Out-Null
|
||||
$h = $p.MainWindowHandle
|
||||
if ([WinRpaFocus2.RpaFocus2]::IsIconic($h)) { [void][WinRpaFocus2.RpaFocus2]::ShowWindow($h, 9) }
|
||||
@ -186,7 +187,14 @@ if ([WinRpaFocus2.RpaFocus2]::GetForegroundWindow() -ne $h) {
|
||||
try { [void][WinRpaFocus2.RpaFocus2]::SetForegroundWindow($h) } finally { if ($attached) { [void][WinRpaFocus2.RpaFocus2]::AttachThreadInput($thisThread, $fgThread, $false) } }
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
if ([WinRpaFocus2.RpaFocus2]::GetForegroundWindow() -ne $h) { throw '前台切换失败' }
|
||||
if ([WinRpaFocus2.RpaFocus2]::GetForegroundWindow() -ne $h) { throw '前台切换失败' }`;
|
||||
}
|
||||
|
||||
const setActiveWindow: CapabilityHandler = async (_ctx, node, args) => {
|
||||
const selector = text(args, ["window_obj", "match_title", "select_title", "title", "target_window", "window_title"]);
|
||||
if (!selector) throw new RuntimeError("validation_error", "窗口标题为空", { nodeId: node.id, componentId: node.componentId });
|
||||
const result = await powershell(`${USER32}
|
||||
${activateWindowScript(selector)}
|
||||
$p.MainWindowTitle`);
|
||||
return result;
|
||||
};
|
||||
|
||||
@ -45,4 +45,23 @@ describe("键盘输入组件真实冒烟", () => {
|
||||
await expect(invoke("Keyboard_Send_V1", { words: "", interval: "10" })).rejects.toThrow("键盘输入为空");
|
||||
console.log("[键盘输入] 空输入校验 ✓");
|
||||
}, 60000);
|
||||
|
||||
it("输入目标窗口: 先激活记事本再输入, 前台窗口切到位", async () => {
|
||||
const { execFile } = await import("node:child_process");
|
||||
const { promisify } = await import("node:util");
|
||||
const runPs = promisify(execFile);
|
||||
const ps = (cmd: string) => runPs("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", cmd], { windowsHide: true, timeout: 30000 });
|
||||
// Win11 记事本窗口标题是「无标题 - Notepad」; 沙盒内 Start-Process notepad 别名解析失败, 用完整路径
|
||||
await ps("Start-Process $env:SystemRoot\\System32\\notepad.exe; $diag=''; $ok=$false; foreach($i in 1..24){ $procs=Get-Process Notepad -ErrorAction SilentlyContinue; if($procs){ $diag=\"procs:$($procs.Count) titles:$(($procs|ForEach-Object{$_.MainWindowTitle}) -join '|')\"; $p=Get-Process | Where-Object {$_.MainWindowTitle -like '*Notepad*'}; if($p){$ok=$true; break} } else { $diag='no-process' }; Start-Sleep -Milliseconds 500 }; if(-not $ok){ throw \"notepad not visible: $diag\" }");
|
||||
try {
|
||||
await invoke("Keyboard_Send_V1", { words: "目标窗口输入", interval: "5", fuctype: "0", window_title: "Notepad" });
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
// activateWindowScript 内部已校验前台句柄=目标窗口, 这里再确认记事本进程窗口标题匹配
|
||||
const fg = await ps("[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new($false); (Get-Process Notepad | Where-Object { $_.MainWindowTitle } | Select-Object -First 1).MainWindowTitle");
|
||||
expect(fg.stdout).toContain("Notepad");
|
||||
console.log("[键盘输入] 目标窗口激活 ✓ 前台:", fg.stdout.trim());
|
||||
} finally {
|
||||
await ps("Get-Process notepad* -ErrorAction SilentlyContinue | Stop-Process -Force").catch(() => {});
|
||||
}
|
||||
}, 90000);
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user