# 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( [string]$PhoneIp = "192.168.1.180", [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" $tmp = "$env:TEMP\remote-emoji-proof" 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, ']*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 ==="