diff --git a/resources/desktop-picker.ps1 b/resources/desktop-picker.ps1 index 87a8b93..cafe81b 100644 --- a/resources/desktop-picker.ps1 +++ b/resources/desktop-picker.ps1 @@ -1,6 +1,6 @@ param([string]$Mode = "element") $ErrorActionPreference = "Continue" -Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes +Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes, WindowsBase Add-Type -AssemblyName System.Windows.Forms Add-Type -MemberDefinition "[DllImport(`"user32.dll`")] public static extern short GetAsyncKeyState(int key); [DllImport(`"user32.dll`", CharSet = CharSet.Unicode)] public static extern int GetWindowTextW(IntPtr h, System.Text.StringBuilder sb, int max);" -Name PickW32 -Namespace RpaPick | Out-Null @@ -15,79 +15,112 @@ if ($Mode -eq "windows") { exit 0 } -function Get-DeepestAtPoint([double]$px, [double]$py) { - $point = New-Object System.Windows.Automation.Point($px, $py) - $cur = [System.Windows.Automation.AutomationElement]::FromPoint($point) - if (-not $cur) { return $null } - for ($depth = 0; $depth -lt 30; $depth++) { - $children = $cur.FindAll([System.Windows.Automation.TreeScope]::Children, [System.Windows.Automation.Condition]::TrueCondition) - $next = $null - $nextArea = [double]::MaxValue - foreach ($c in $children) { - try { - $r = $c.Current.BoundingRectangle - if ($r.Width -gt 0 -and $r.Height -gt 0 -and $px -ge $r.X -and $px -le ($r.X + $r.Width) -and $py -ge $r.Y -and $py -le ($r.Y + $r.Height)) { - $area = $r.Width * $r.Height - if ($area -lt $nextArea) { $next = $c; $nextArea = $area } - } - } catch {} - } - if (-not $next) { break } - $cur = $next - } - return $cur -} - +# ---- 信息窗 ---- $form = New-Object System.Windows.Forms.Form $form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::None $form.TopMost = $true $form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual $form.Location = New-Object System.Drawing.Point(20, 20) -$form.Size = New-Object System.Drawing.Size(460, 130) +$form.Size = New-Object System.Drawing.Size(480, 110) $form.BackColor = [System.Drawing.Color]::FromArgb(20, 60, 120) $label = New-Object System.Windows.Forms.Label $label.Dock = [System.Windows.Forms.DockStyle]::Fill $label.ForeColor = [System.Drawing.Color]::White -$label.Font = New-Object System.Drawing.Font("Microsoft YaHei", 11) -$label.Text = "移动鼠标选择元素..." +$label.Font = New-Object System.Drawing.Font("Microsoft YaHei", 10) $form.Controls.Add($label) -$form.Show() -$prevDown = $false -$result = $null +# ---- 高亮窗(半透明红块) ---- +$hl = New-Object System.Windows.Forms.Form +$hl.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::None +$hl.TopMost = $true +$hl.ShowInTaskbar = $false +$hl.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual +$hl.BackColor = [System.Drawing.Color]::Red +$hl.Opacity = 0.35 + +# ---- 状态 ---- +$stage = "hover" +$chain = New-Object System.Collections.ArrayList +$selIdx = -1 +$prevPageUp = $false +$prevPageDown = $false +$prevSpace = $false + +function Show-Element($el) { + if ($null -eq $el) { $script:hl.Hide(); return } + $r = $el.Current.BoundingRectangle + if ($r.Width -le 0 -or $r.Height -le 0) { $script:hl.Hide(); return } + $script:hl.Location = New-Object System.Drawing.Point([int]($r.X - 2), [int]($r.Y - 2)) + $script:hl.Size = New-Object System.Drawing.Size([int]($r.Width + 4), [int]($r.Height + 4)) + $script:hl.Show() +} + +$form.Show() +$hl.Show() + while ($true) { - $ptX = [System.Windows.Forms.Cursor]::Position.X - $ptY = [System.Windows.Forms.Cursor]::Position.Y - $el = Get-DeepestAtPoint $ptX $ptY - $down = ([RpaPick.PickW32]::GetAsyncKeyState(32) -band 0x8000) -ne 0 + $space = ([RpaPick.PickW32]::GetAsyncKeyState(32) -band 0x8000) -ne 0 $esc = ([RpaPick.PickW32]::GetAsyncKeyState(27) -band 0x8000) -ne 0 + $pgUp = ([RpaPick.PickW32]::GetAsyncKeyState(33) -band 0x8000) -ne 0 + $pgDn = ([RpaPick.PickW32]::GetAsyncKeyState(34) -band 0x8000) -ne 0 + if ($esc) { break } - if ($el) { - $c = $el.Current - $ctrlName = $c.ControlType.ProgrammaticName.Replace("ControlType.", "") - $label.Text = "名称: " + $c.Name + [Environment]::NewLine + "类型: " + $ctrlName + [Environment]::NewLine + "空格=拾取 Esc=取消" - } - if ($down -and $el) { - $c = $el.Current - $rootName = "" - $walker = [System.Windows.Automation.TreeWalker]::ControlViewWalker - $anc = $walker.GetParent($el) - while ($anc -and -not $anc.Equals([System.Windows.Automation.AutomationElement]::RootElement)) { - $h = $anc.Current.NativeWindowHandle - if ($h -ne 0) { - $sb = New-Object System.Text.StringBuilder 512 - [RpaPick.PickW32]::GetWindowTextW([IntPtr]$h, $sb, 512) | Out-Null - if ($sb.ToString()) { $rootName = $sb.ToString(); break } - } - $anc = $walker.GetParent($anc) + + if ($stage -eq "hover") { + $ptX = [System.Windows.Forms.Cursor]::Position.X + $ptY = [System.Windows.Forms.Cursor]::Position.Y + $point = New-Object System.Windows.Automation.Point($ptX, $ptY) + $el = $null + try { $el = [System.Windows.Automation.AutomationElement]::FromPoint($point) } catch {} + if ($el) { + $c = $el.Current + $ctrlName = $c.ControlType.ProgrammaticName.Replace("ControlType.", "") + $label.Text = "名称: " + $c.Name + [Environment]::NewLine + "类型: " + $ctrlName + [Environment]::NewLine + "空格=锁定 Esc=取消" + Show-Element $el + } + if ($space -and -not $prevSpace -and $el) { + # 进入微调:收集从根到命中的元素链 + $walker = [System.Windows.Automation.TreeWalker]::ControlViewWalker + $chain.Clear() + $cur = $el + while ($cur -and -not $cur.Equals([System.Windows.Automation.AutomationElement]::RootElement)) { + $cc = $cur.Current + $rr = $cc.BoundingRectangle + [void]$chain.Insert(0, @{ el = $cur; name = [string]$cc.Name; automationId = [string]$cc.AutomationId; controlType = $cc.ControlType.ProgrammaticName.Replace("ControlType.", ""); x = [int]$rr.X; y = [int]$rr.Y; w = [int]$rr.Width; h = [int]$rr.Height }) + $cur = $walker.GetParent($cur) + } + $selIdx = $chain.Count - 1 + $stage = "adjust" + $label.Text = "微调模式:PageUp/PageDown 切换层级,空格确认,Esc 取消" } - $ctrlName = $c.ControlType.ProgrammaticName.Replace("ControlType.", "") - $result = @{ window = $rootName; name = $c.Name; automationId = $c.AutomationId; controlType = $ctrlName; className = $c.ClassName } - break } - $prevDown = $down - Start-Sleep -Milliseconds 80 + + if ($stage -eq "adjust") { + if ($pgUp -and -not $prevPageUp -and $selIdx -gt 0) { $selIdx -= 1 } + if ($pgDn -and -not $prevPageDown -and $selIdx -lt ($chain.Count - 1)) { $selIdx += 1 } + $item = $chain[$selIdx] + if ($item) { + $label.Text = "层级 " + ($selIdx + 1) + "/" + $chain.Count + " 名称: " + $item.name + [Environment]::NewLine + "类型: " + $item.controlType + [Environment]::NewLine + "PageUp/PageDown=切层级 空格=确认 Esc=取消" + $hl.Location = New-Object System.Drawing.Point($item.x - 2, $item.y - 2) + $hl.Size = New-Object System.Drawing.Size($item.w + 4, $item.h + 4) + $hl.Show() + } + if ($space -and -not $prevSpace) { + $item = $chain[$selIdx] + # 窗口名 = 链中第一个带窗口句柄的层 + $rootName = "" + foreach ($it in $chain) { if ($it.name) { $rootName = $it.name; break } } + $result = @{ selected = $selIdx; window = $rootName; name = $item.name; automationId = $item.automationId; controlType = $item.controlType; x = $item.x; y = $item.y; w = $item.w; h = $item.h } + break + } + } + + $prevPageUp = $pgUp + $prevPageDown = $pgDn + $prevSpace = $space + Start-Sleep -Milliseconds 60 } $form.Close() +$hl.Close() if ($result) { Write-Output (ConvertTo-Json $result -Compress) } else { Write-Output (ConvertTo-Json @{ cancelled = $true } -Compress) }