diff --git a/ROADMAP.md b/ROADMAP.md index 168f226..468d598 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -56,8 +56,14 @@ 全部 0% CPU)。旧壳发行版始终带侧载对所以从未暴露。修复列入 P2; 运维规则:任何 nebula 系 exe 旁必须部署侧载对。 - **1b IME 清单**:输出一页人工验收清单,等用户有空亲手敲。 -- **1c 视觉对账集**:fastfetch + powerline 提示符 + TUI 边框在新旧两壳的 - 并排截图脚本与基准图。 +- **1c 视觉对账集**:`scripts/visual_parity.ps1`——固定样张(boxdraw 全家 + 桶、块/浓度/象限、Powerline 分隔符、CJK 对齐标尺、256 色/真彩)注入 + 两壳并截图(`.tmp_parity_.png`)。 + **首轮对账 2026-08-13:结构级持平**——框线闭合无缝、CJK 标尺逐列对 + 齐、Powerline 三角在 GPUI 侧(boxdraw 几何)边缘更干净;`mixed/half/ + trans` 几行两壳同样偏怪(旧壳像素栅格化的既有观感,非回归)。 + 待办:config 共享后做同主题对比;fastfetch 未安装(装上即可入集); + 放大级笔画粗细对账。 ### P2 实验场补齐终端体验(在 `nebula_gpui` 快速迭代) diff --git a/scripts/visual_parity.ps1 b/scripts/visual_parity.ps1 new file mode 100644 index 0000000..72d32f8 --- /dev/null +++ b/scripts/visual_parity.ps1 @@ -0,0 +1,141 @@ +# 视觉对账(G3 闸门):向终端注入一份固定样张,截图存档,供新旧两壳 +# 并排对比。样张覆盖 GPUI 迁移最容易翻车的渲染面: +# boxdraw 全家桶(单/重/双线、圆角、虚线、对角线)、块元素与浓度块、 +# 象限块、Powerline 分隔符(三角/箭头/圆头)、CJK 对齐标尺、 +# 256 色与真彩渐变。 +# fastfetch 若在 PATH 上则追加真实 fastfetch 输出。 +# +# 用法: +# .\scripts\visual_parity.ps1 -Exe -Tag winit-old -ExeArgs '--working-directory D:\temp_build\nebula' +# .\scripts\visual_parity.ps1 -Exe -Tag gpui-lab +param( + [Parameter(Mandatory = $true)][string]$Exe, + [Parameter(Mandatory = $true)][string]$Tag, + [string]$ExeArgs = '', + [int]$SettleSec = 4 +) + +$ErrorActionPreference = 'Stop' +$root = Split-Path -Parent $PSScriptRoot +$contentFile = Join-Path $root '.tmp_parity_content.ps1' +$injector = Join-Path $PSScriptRoot 'perf_inject.ps1' +$outPng = Join-Path $root ".tmp_parity_$Tag.png" + +# ---- 1. 样张(每次重写,保持权威版本在本脚本内)--------------------------- +$e = [char]27 +@" +`$e = [char]27 +Write-Host "== visual parity sample ==" +Write-Host '-- boxdraw: light / heavy / double / rounded / dashes / diagonals' +Write-Host '┌───┬───┐ ┏━━━┳━━━┓ ╔═══╦═══╗ ╭───╮' +Write-Host '│ a │ b │ ┃ c ┃ d ┃ ║ e ║ f ║ │ g │' +Write-Host '├───┼───┤ ┣━━━╋━━━┫ ╠═══╬═══╣ ╰───╯' +Write-Host '└───┴───┘ ┗━━━┻━━━┛ ╚═══╩═══╝ ┄┄┅┅┈┈┉┉ ╌╌╍╍' +Write-Host 'mixed: ┍┑┕┙ ┎┒┖┚ ╒╕╘╛ ╓╖╙╜ half: ╴╵╶╷╸╹╺╻ trans: ╼╽╾╿' +Write-Host 'diag: ╱╲╳╱╲╳ vertical dashes: ┆┇┊┋╎╏' +Write-Host '-- blocks / shades / quadrants' +Write-Host '▁▂▃▄▅▆▇█ ▏▎▍▌▋▊▉█ ░░▒▒▓▓██ ▀▄▌▐▔▕' +Write-Host '▖▗▘▝▚▞▙▛▜▟ legacy: 🬀🬁🬂🬃🬄🬅🬆🬇🬈🬉🬊' +Write-Host '-- powerline separators' +Write-Host "`$e[48;5;25m`$e[38;5;15m one `$e[0m`$e[38;5;25m`$e[48;5;208m$([char]0xE0B0)`$e[38;5;15m two `$e[0m`$e[38;5;208m`$e[48;5;28m$([char]0xE0B0)`$e[38;5;15m three `$e[0m`$e[38;5;28m$([char]0xE0B0)`$e[0m done" +Write-Host "chevrons: $([char]0xE0B1) $([char]0xE0B1) rtl: $([char]0xE0B2) $([char]0xE0B3) round: $([char]0xE0B4) $([char]0xE0B6)" +Write-Host '-- CJK alignment ruler (竖线必须逐列对齐)' +Write-Host '|12345678901234567890123456789012|' +Write-Host '|中文宽字符对齐测试一二三四五六七八|' +Write-Host '|あいうえおかきくけこさしすせそたち|' +Write-Host '|ab中cd文ef字gh宽ij齐kl测mn试op对qr|' +Write-Host '-- colors' +`$bar = '' +foreach (`$i in 16..51) { `$bar += "`$e[48;5;`${i}m " } +Write-Host "`$bar`$e[0m" +`$grad = '' +foreach (`$i in 0..31) { `$r = `$i * 8; `$grad += "`$e[48;2;`${r};64;`$((255 - `$r))m " } +Write-Host "`$grad`$e[0m" +if (Get-Command fastfetch -ErrorAction SilentlyContinue) { fastfetch } +Write-Host '== end of sample ==' +"@ | Set-Content -Path $contentFile -Encoding UTF8 + +# ---- 2. Win32 助手 ---------------------------------------------------------- +Add-Type @' +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +public class ParityWin { + [DllImport("user32.dll")] static extern bool EnumWindows(EnumProc cb, IntPtr lp); + [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid); + [DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h); + [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r); + [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int w, int hh, uint flags); + [DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h, IntPtr dc, uint flags); + public struct RECT { public int L, T, R, B; } + delegate bool EnumProc(IntPtr h, IntPtr lp); + public static List WindowsOf(uint pid) { + var list = new List(); + EnumWindows((h, lp) => { + uint p; GetWindowThreadProcessId(h, out p); + if (p == pid && IsWindowVisible(h)) list.Add(h); + return true; + }, IntPtr.Zero); + return list; + } +} +'@ +Add-Type -AssemblyName System.Drawing +Add-Type -MemberDefinition '[DllImport("shcore.dll")] public static extern int SetProcessDpiAwareness(int value);' -Name Dpi -Namespace ParityDpi +[void][ParityDpi.Dpi]::SetProcessDpiAwareness(2) + +function Get-MainWindow([int]$ProcessId) { + for ($i = 0; $i -lt 40; $i++) { + $wins = [ParityWin]::WindowsOf($ProcessId) + if ($wins.Count -gt 0) { + $best = $null; $bestW = -1 + foreach ($h in $wins) { + $r = New-Object ParityWin+RECT + [void][ParityWin]::GetWindowRect($h, [ref]$r) + if (($r.R - $r.L) -gt $bestW) { $bestW = $r.R - $r.L; $best = $h } + } + return $best + } + Start-Sleep -Milliseconds 500 + } + throw "no visible window for pid $ProcessId" +} + +# ---- 3. 驱动:启动 → 注入 → 截图 → 清理 ------------------------------------ +Write-Host "launching $Exe $ExeArgs" +$proc = if ($ExeArgs) { + Start-Process -FilePath $Exe -ArgumentList $ExeArgs -PassThru +} else { + Start-Process -FilePath $Exe -PassThru +} +Start-Sleep -Seconds 7 +if ($proc.HasExited) { throw "terminal exited immediately (mux hand-over? pass -ExeArgs)" } +$hwnd = Get-MainWindow -ProcessId $proc.Id +# 可见但不抢焦点(HWND_TOPMOST, SWP_NOSIZE|NOMOVE|NOACTIVATE)。 +[void][ParityWin]::SetWindowPos($hwnd, [IntPtr](-1), 0, 0, 0, 0, 0x13) + +$command = "powershell -NoProfile -ExecutionPolicy Bypass -File $contentFile" +$ok = $false +for ($attempt = 0; $attempt -lt 5; $attempt++) { + $out = & powershell -NoProfile -ExecutionPolicy Bypass -File $injector ` + -HostPid $proc.Id -Text $command -Enter 2>&1 | Out-String + if ($out -match 'ok=True') { $ok = $true; break } + Start-Sleep -Seconds 2 +} +if (-not $ok) { Stop-Process -Id $proc.Id -Force; throw "inject failed: $out" } +Start-Sleep -Seconds $SettleSec + +$r = New-Object ParityWin+RECT +[void][ParityWin]::GetWindowRect($hwnd, [ref]$r) +$w = $r.R - $r.L; $h = $r.B - $r.T +$bmp = New-Object System.Drawing.Bitmap $w, $h +$g = [System.Drawing.Graphics]::FromImage($bmp) +$dc = $g.GetHdc() +# PW_RENDERFULLCONTENT = 2:GPU 合成(D3D/GL)表面必需。 +$shot = [ParityWin]::PrintWindow($hwnd, $dc, 2) +$g.ReleaseHdc($dc); $g.Dispose() +$bmp.Save($outPng, [System.Drawing.Imaging.ImageFormat]::Png) +$bmp.Dispose() +Write-Host "shot ok=$shot ${w}x${h} -> $outPng" + +Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue