remote/tools/emoji_manual_proof.ps1
郑博文 e96502f888 test(tools): 4a 横屏验收脚本 + 既有 proof 适配自动隐藏与同源缺陷修复
新增 landscape_proof.ps1:V-C1~C7 判据(自动横屏 <=2s、旋转零中断、
动画桌面下 >=45fps、控制条自动隐藏、黑边唤出零远端泄漏、四按钮可点、
方向四态循环、表单竖屏)。方向判定只解析 Display: mDisplayId=0 块内
cur=WxH——这台 vivo 无 mRotation= 行,且 dumpsys 里有常驻 1080x2400 的
虚拟屏,匹配首个 cur= 会在已横屏时误报未旋转(探针实测踩过)。

横屏自动隐藏与既有 proof 的判据自冲突(HUD 随控制条 GONE,RTT= 读不到
被误判断线/未连接),三个 proof 统一加「确认隐藏态才走黑边唤醒」:
phone_chain_proof、ime_chinese_proof、rel_mouse_phone_proof。

修 rel_mouse_phone_proof 两处:
- 2c 基线判据:横屏滑动 768px 的残余相对事件成批延迟落地,旧的「两次
  读数相等」被突发间隙骗过(实测假失败 351/1418px)。改为 6 读数沉降 +
  4 次采样判漂移 + 绝对泄漏点特征比对。
- step 4 靶窗启动缺 3 个 Mandatory 参数,子进程瞬间失败退出(3b 起一直
  如此,右键落在裸屏幕中心,靠近违反 R-11「不得注入他人窗口」)。现按
  契约启动并把指针停在自家窗口内。

R-5 同源排查:靶窗结果目录固定路径 + 启动时删自己的 stop.flag,会让
上一轮失败遗留的靶窗永远收不到退休信号、继续抢前台并写脏 received.txt
(实测收到 abc\nabc)。5 个 proof 统一改每轮唯一目录(\47512 后缀)。

ime_chinese_proof 的切换键标定失效:设备 IME 已换成搜狗(原为讯飞),
旧坐标 (146,2096) 静默点空。IME 按键视图对 uiautomator 不可见、无法机械
标定,按 R-9 转人工确认,不硬凑自动化。
2026-11-01 21:58:19 +08:00

198 lines
8.3 KiB
PowerShell

# 2b V7 (device side): manual emoji acceptance helper.
# ASCII-only source (PS 5.1 + BOM-less = GBK corruption; see personal lessons).
#
# WHY MANUAL: the IME's own emoji panel is invisible to uiautomator (dump shows
# only our app's nodes), so adb cannot drive emoji selection. The human taps the
# emoji; this script only prepares the receiving window and reads back what
# actually arrived - the judgement is still made against real received bytes.
#
# What this proves: the phone IME commits a supplementary-plane character
# (surrogate pair) as ONE commitText, and protocol v3 delivers both code units
# adjacently so Windows synthesises a single character.
# The Windows half is already proven mechanically (V7' in text_injection_proof).
#
# Usage:
# powershell -ExecutionPolicy Bypass -File tools\emoji_manual_proof.ps1
# ... then, when prompted, tap an emoji on the phone's keyboard.
param(
# PC (controlled side) LAN address; DHCP-assigned - pass -PhoneIp when it
# moves (see ime_chinese_proof.ps1 for the 2026-10-31 stale-default lesson).
[string]$PhoneIp = "192.168.1.110",
[int]$Port = 47800,
[string]$Code = "t",
[int]$WaitSeconds = 120
)
$ErrorActionPreference = "Stop"
$repo = (Resolve-Path "$PSScriptRoot\..").Path
$adb = "D:\working\android\sdk\platform-tools\adb.exe"
# UNIQUE per run: a fixed path lets the startup Remove-Item below delete the
# stop.flag that leftover targets from a previous FAILED run are watching, so
# they never retire and keep writing into the shared received.txt
# (same trap fixed in phone_chain_proof, measured 2026-11-01).
$tmp = "$env:TEMP\remote-emoji-proof-$PID"
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
$pidFile = "$tmp\target.pid"
$stopFile = "$tmp\stop.flag"
$resultFile = "$tmp\received.txt"
Remove-Item $pidFile,$stopFile,$resultFile -ErrorAction SilentlyContinue
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public static class FgEmoji {
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
}
"@
function Adb-Shell {
param([Parameter(Mandatory)][string[]]$Cmd)
$old = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try {
$out = & $adb shell @Cmd 2>$null
return @{ out = ($out -join "`n"); code = $LASTEXITCODE }
}
finally { $ErrorActionPreference = $old }
}
function Wait-File {
param([string]$Path, [int]$TimeoutMs = 20000)
$t = 0
while (-not (Test-Path $Path) -and $t -lt ($TimeoutMs / 150)) { Start-Sleep -Milliseconds 150; $t++ }
if (-not (Test-Path $Path)) { throw "timeout waiting for $Path" }
}
function Read-Received {
if (-not (Test-Path $resultFile)) { return "" }
$b = [System.IO.File]::ReadAllBytes($resultFile)
$s = [System.Text.Encoding]::UTF8.GetString($b)
if ($s.Length -gt 0 -and $s[0] -eq [char]0xFEFF) { $s = $s.Substring(1) }
return $s
}
function Show-Cps([string]$s) {
($s.ToCharArray() | ForEach-Object { "U+{0:X4}" -f [int]$_ }) -join " "
}
Write-Host "=== 1. retire earlier targets, ensure fresh agent ==="
$flags = @(Get-ChildItem -Path $env:TEMP -Recurse -Filter "stop.flag" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -like "*remote*proof*" })
foreach ($f in $flags) { (Get-Item $f.FullName).LastWriteTime = Get-Date }
if ($flags.Count -gt 0) { Start-Sleep -Seconds 3 }
$agentExe = "$repo\target\release\agent.exe"
$ag = Get-Process agent -ErrorAction SilentlyContinue
if ($ag -and (Get-Item $agentExe).LastWriteTime -gt $ag.StartTime) {
Stop-Process -Name agent -Force -ErrorAction SilentlyContinue
Start-Sleep -Milliseconds 800; $ag = $null
}
if (-not $ag) {
Start-Process $agentExe -ArgumentList "--port",$Port,"--code",$Code -WindowStyle Hidden
Start-Sleep -Seconds 1
Write-Host " agent started"
}
Write-Host "=== 2. target window (receives the injected text) ==="
Start-Process powershell.exe -ArgumentList "-NoProfile","-ExecutionPolicy","Bypass",`
"-File","$repo\tools\text_target.ps1","-PidFile",$pidFile,`
"-ResultFile",$resultFile,"-StopFile",$stopFile,"-GrabForeground"
Wait-File $pidFile
$parts = @((Get-Content $pidFile -Raw) -split "`n")
$tid = [int]$parts[0].Trim()
$hwnd = [IntPtr][long]$parts[1].Trim()
Start-Sleep -Milliseconds 800
$fg = [FgEmoji]::GetForegroundWindow()
Write-Host " target pid=$tid hwnd=$($hwnd.ToInt64()) foreground=$($fg -eq $hwnd)"
Write-Host "=== 3. phone session + show keyboard ==="
[void](Adb-Shell @("am","force-stop","com.verify.remote"))
[void](Adb-Shell @("am","start","-n","com.verify.remote/.MainActivity",
"--es","host",$PhoneIp,"--ei","port","$Port","--es","code",$Code))
$deadline = (Get-Date).AddSeconds(25)
$ui = ""
while ((Get-Date) -lt $deadline) {
Start-Sleep -Milliseconds 900
[void](Adb-Shell @("uiautomator","dump","/sdcard/ui.xml"))
$old = $ErrorActionPreference; $ErrorActionPreference = "Continue"
try { & $adb pull /sdcard/ui.xml "$tmp\ui.xml" 2>$null | Out-Null } finally { $ErrorActionPreference = $old }
if (Test-Path "$tmp\ui.xml") { $ui = [System.IO.File]::ReadAllText("$tmp\ui.xml", [System.Text.Encoding]::UTF8) }
if ($ui -match "RTT=") { break }
}
if ($ui -notmatch "RTT=") { throw "phone did not connect" }
$btns = [regex]::Matches($ui, '<node[^>]*class="android\.widget\.Button"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"')
if ($btns.Count -lt 1) { throw "keyboard button not found" }
$b0 = $btns[0]
$kx = [int](([int]$b0.Groups[1].Value + [int]$b0.Groups[3].Value) / 2)
$ky = [int](([int]$b0.Groups[2].Value + [int]$b0.Groups[4].Value) / 2)
[void](Adb-Shell @("input","tap","$kx","$ky"))
Start-Sleep -Seconds 3
$ime = (Adb-Shell @("dumpsys","input_method")).out
if ($ime -notmatch "mInputShown=true") { throw "IME not shown" }
if ($ime -notmatch "mHaveConnection=true") { throw "IME not bound to our InputConnection" }
Write-Host " IME shown and bound: $([regex]::Match($ime,'mCurId=(\S+)').Groups[1].Value)"
$baseline = Read-Received
Write-Host ""
Write-Host " ------------------------------------------------------------"
Write-Host " NOW ON THE PHONE: tap the emoji key, then tap ONE emoji."
Write-Host " (Keyboard is up; the Windows target window is receiving.)"
Write-Host " Waiting up to $WaitSeconds s for a supplementary-plane character..."
Write-Host " ------------------------------------------------------------"
Write-Host ""
$deadline = (Get-Date).AddSeconds($WaitSeconds)
$got = ""
$found = $false
while ((Get-Date) -lt $deadline) {
Start-Sleep -Milliseconds 500
$got = Read-Received
if ($got.Length -gt $baseline.Length) {
$new = $got.Substring($baseline.Length)
# any code unit in the surrogate range means an emoji (or other
# supplementary-plane char) reached us
foreach ($ch in $new.ToCharArray()) {
$cp = [int]$ch
if ($cp -ge 0xD800 -and $cp -le 0xDFFF) { $found = $true; break }
}
if ($found) { break }
}
}
$final = Read-Received
$newFinal = if ($final.Length -ge $baseline.Length) { $final.Substring($baseline.Length) } else { "" }
Write-Host "=== 4. result ==="
Write-Host " baseline : [$(Show-Cps $baseline)]"
Write-Host " final : [$(Show-Cps $final)]"
Write-Host " new chars: [$(Show-Cps $newFinal)]"
Write-Host " new units: $($newFinal.Length)"
if ($found) {
# verify the pair is adjacent and forms a real code point
$units = $newFinal.ToCharArray() | ForEach-Object { [int]$_ }
$paired = $false
for ($i = 0; $i -lt $units.Count - 1; $i++) {
if ($units[$i] -ge 0xD800 -and $units[$i] -le 0xDBFF -and
$units[$i+1] -ge 0xDC00 -and $units[$i+1] -le 0xDFFF) {
$cp = 0x10000 + (($units[$i] - 0xD800) * 0x400) + ($units[$i+1] - 0xDC00)
Write-Host (" V7 PASS: adjacent surrogate pair -> U+{0:X4} ({1} code units committed as one)" -f $cp, $newFinal.Length)
$paired = $true
break
}
}
if (-not $paired) { throw "V7 FAIL: surrogate units present but not an adjacent valid pair" }
if ($newFinal.Contains([char]0xFFFD)) { throw "V7 FAIL: U+FFFD present (pair was split)" }
}
else {
Write-Host " V7 INCONCLUSIVE: no supplementary-plane character arrived within $WaitSeconds s"
Write-Host " (If you tapped an emoji and nothing arrived, that IS the finding - record it.)"
}
Write-Host "=== 5. cleanup ==="
[void](Adb-Shell @("input","keyevent","4"))
New-Item -ItemType File -Force -Path $stopFile | Out-Null
Start-Sleep -Milliseconds 500
Write-Host "=== done ==="