fix(gui): window.list 进程名错乱 + screen.find UIA 只支持精确匹配

## 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 全绿
This commit is contained in:
jkooe committed 2026-09-29 13:30:23 +08:00
1 parent 2dccec9a5c
commit 7efe717cf8
1 file changed
+20 -12
+20 -12
View File
@@ -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)