From 7efe717cf8f3e7a888647ec2d56be1e4efcbb3fc Mon Sep 17 00:00:00 2001 From: jkooe <62494645+jkooe@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:30:23 +0800 Subject: [PATCH] =?UTF-8?q?fix(gui):=20window.list=20=E8=BF=9B=E7=A8=8B?= =?UTF-8?q?=E5=90=8D=E9=94=99=E4=B9=B1=20+=20screen.find=20UIA=20=E5=8F=AA?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=B2=BE=E7=A1=AE=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## bug 1:所有窗口的进程名都报成 powershell 根因:C# 回调里用 $pid 接 GetWindowThreadProcessId 的输出 —— 但 $pid 是 PowerShell **自动变量**(当前进程 ID),赋值被静默忽略,于是每个窗口都取到 powershell 的 PID。 改为 $procId。真机验证:cmd / hexin(同花顺) / SearchPictureTool / TextInputHost / explorer 均正确归属。 ## bug 2:UIA 路径实际是精确匹配,与文档「子串匹配」不符 根因:用 PropertyCondition(NameProperty, text, IgnoreCase) —— IgnoreCase 只影响大小写, 仍是精确相等。实测「管理员: C:\WINDOWS\system32\cmd.exe」用 '管理员' 永远匹配不到。 改为:FindAll(Descendants, [控制类型条件]) 后按 IndexOf(OrdinalIgnoreCase) 子串过滤, 并加 20000 元素扫描上限防止超大 UI 树卡住。 真机验证: '管理员' -> 命中 '管理员: C:\WINDOWS\system32\cmd.exe' @ (548,312) '同花顺' -> 命中窗口标题 + 内部文本元素 ## 顺带明确一个语义(写入文档) is_foreground = 「持有键盘焦点」,不等于「视觉上在最上层」。 实测 cmd 持有焦点(is_foreground=true)但画面被全屏的「同花顺」覆盖 —— 此时按 UIA 返回的坐标点击会打到覆盖它的窗口上。 因此:**几何信息可信,视觉遮挡需另行确认**(点击前截屏复核)。 单测 61/61、e2e 21/21 全绿 --- apps/agent/src/capabilities/window.ts | 32 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/apps/agent/src/capabilities/window.ts b/apps/agent/src/capabilities/window.ts index 336a6d1..69093e7 100644 --- a/apps/agent/src/capabilities/window.ts +++ b/apps/agent/src/capabilities/window.ts @@ -87,12 +87,14 @@ $cb = [NAWin32+EnumProc]{ if (-not [NAWin32]::GetWindowRect($h, [ref]$r)) { return $true } $w = $r.Right - $r.Left; $ht = $r.Bottom - $r.Top if ($w -le 0 -or $ht -le 0) { return $true } - $pid = 0 - [void][NAWin32]::GetWindowThreadProcessId($h, [ref]$pid) + # ⚠️ 变量名不能叫 $pid —— 它是 PowerShell 的自动变量(当前进程 ID),赋值会被忽略, + # 导致所有窗口都报成 powershell 进程(真机踩过)。 + $procId = 0 + [void][NAWin32]::GetWindowThreadProcessId($h, [ref]$procId) $pn = '' - try { $pn = (Get-Process -Id $pid -ErrorAction Stop).ProcessName } catch {} + try { $pn = (Get-Process -Id $procId -ErrorAction Stop).ProcessName } catch {} [void]$list.Add([pscustomobject]@{ - hwnd = ('0x{0:X}' -f $h.ToInt64()); title = $title; process = $pn; pid = $pid + hwnd = ('0x{0:X}' -f $h.ToInt64()); title = $title; process = $pn; pid = $procId x = $r.Left; y = $r.Top; width = $w; height = $ht is_foreground = ($h -eq $fg); is_minimized = [NAWin32]::IsIconic($h) }) @@ -260,28 +262,34 @@ if ($winTitle -ne '') { if ($w -eq $null) { Write-Output '[]'; exit 0 } $scope = $w } -$nameCond = New-Object System.Windows.Automation.PropertyCondition( - [System.Windows.Automation.AutomationElement]::NameProperty, $text, - [System.Windows.Automation.PropertyConditionFlags]::IgnoreCase) +// ⚠️ UIA 的 PropertyCondition 是**精确匹配**(IgnoreCase 只影响大小写), +// 实测「管理员: C:\WINDOWS\...」这类标题用 "管理员" 永远匹配不到。 +// 故改为遍历后按子串过滤(并限制遍历规模,避免超大 UI 树卡住)。 +$cond = [System.Windows.Automation.Condition]::TrueCondition $ct = ${JSON.stringify(ct)} if ($ct -ne '') { $ctObj = $null try { $ctObj = [System.Windows.Automation.ControlType]::$ct } catch {} if ($ctObj -ne $null) { - $ctCond = New-Object System.Windows.Automation.PropertyCondition( + $cond = New-Object System.Windows.Automation.PropertyCondition( [System.Windows.Automation.AutomationElement]::ControlTypeProperty, $ctObj) - $cond = New-Object System.Windows.Automation.AndCondition($nameCond, $ctCond) - } else { $cond = $nameCond } -} else { $cond = $nameCond } + } +} $found = $scope.FindAll([System.Windows.Automation.TreeScope]::Descendants, $cond) $out = New-Object System.Collections.ArrayList +$scanned = 0 foreach ($e in $found) { if ($out.Count -ge $limit) { break } + $scanned++ + if ($scanned -gt 20000) { break } try { + $nm = $e.Current.Name + if ([string]::IsNullOrEmpty($nm)) { continue } + if ($nm.IndexOf($text, [System.StringComparison]::OrdinalIgnoreCase) -lt 0) { continue } $r = $e.Current.BoundingRectangle if ($r.Width -le 0 -or $r.Height -le 0) { continue } $out.Add([pscustomobject]@{ - name = $e.Current.Name + name = $nm control_type = ($e.Current.ControlType.ProgrammaticName -replace 'ControlType\\\\.','') automation_id = $e.Current.AutomationId x = [int]($r.Left + $r.Width / 2)