# 3b phone-side proof: relative mouse (protocol v4) + long-press right click + R7 regression. # # ASCII-only source (PS 5.1 decodes BOM-less .ps1 as GBK; see personal lessons). # Expected Chinese UI strings are matched as CODE-POINT fragments (R-8: assert the # actual value, and print what the parser really read so an empty result cannot pass). # # Judgements: # V-B1a relative mode moves the PC pointer by the expected amount (direction+sign) # V-B2 absolute mode still maps finger -> desktop 1:1 (R7 re-run, <=8px error) # V-B3 long press (>=500ms, still) opens a context menu; a drag does NOT # V-B7 R7 + Chinese IME + full chain regressions stay green # # Prereqs: vivo device online via adb, 3b APK installed, v4 agent running. # powershell -ExecutionPolicy Bypass -File tools\rel_mouse_phone_proof.ps1 -Host_ 192.168.1.110 param( [string]$Host_ = "192.168.1.110", [int]$Port = 47800, [string]$Code = "t", [string]$AgentExe = "" ) $ErrorActionPreference = "Stop" $repo = (Resolve-Path "$PSScriptRoot\..").Path $adb = "D:\working\android\sdk\platform-tools\adb.exe" $ev = Join-Path (Split-Path $repo -Parent) "evidence\3b" New-Item -ItemType Directory -Force -Path $ev | Out-Null # ---- code-point fragments (grep-verified present in the Kotlin sources) ---- $F_CONNECT = -join ([char]0x8FDE,[char]0x63A5) # "connect" $F_RECONNECT = -join ([char]0x91CD,[char]0x65B0,[char]0x8FDE,[char]0x63A5) # "reconnect" $F_RELATIVE = -join ([char]0x76F8,[char]0x5BF9) # "relative" $F_ABSOLUTE = -join ([char]0x7EDD,[char]0x5BF9) # "absolute" $F_MODE_TAG = -join ([char]0x6A21,[char]0x5F0F) # "mode" $F_LONGPRESS = -join ([char]0x957F,[char]0x6309,[char]0x53F3,[char]0x952E) # "long-press right click" $F_RCLICK_TAG = -join ([char]0x53F3,[char]0x952E,[char]0x3D) # "right-click=" $F_CLICK = -join ([char]0x5355,[char]0x51FB) # "single click" # Read the persistent right-click counter from the HUD ("右键=N(source)"). # Returns -1 when absent. A counter is used instead of the one-line debug text # because ACTION_UP immediately overwrites the debug line after a long press # (measured 2026-10-31: screen showed "长按结束", the firing evidence was gone). function RightClickCount { $m = [regex]::Match((Get-UiText), 'text="([^"]*RTT=[^"]*)"') if (-not $m.Success) { return -1 } $hud = [System.Net.WebUtility]::HtmlDecode($m.Groups[1].Value) $c = [regex]::Match($hud, "$F_RCLICK_TAG(\d+)") if ($c.Success) { return [int]$c.Groups[1].Value } return 0 } function Adb { param([Parameter(Mandatory)][string[]]$AdbArgs) $old = $ErrorActionPreference; $ErrorActionPreference = "Continue" try { $out = & $adb @AdbArgs 2>$null; @{ out = ($out -join "`n"); code = $LASTEXITCODE } } finally { $ErrorActionPreference = $old } } function AdbShell { param([string[]]$Cmd) (Adb -AdbArgs (@("shell") + $Cmd)).out } function Get-UiText { $d = AdbShell @("uiautomator","dump","/sdcard/rm.xml") $r = Adb @("pull","/sdcard/rm.xml","$env:TEMP\rm.xml") if ($r.code -ne 0 -or -not (Test-Path "$env:TEMP\rm.xml")) { Write-Host " [ui] dump=[$($d -replace "`n",' ')] pull_rc=$($r.code) -> empty" return "" } $txt = [System.IO.File]::ReadAllText("$env:TEMP\rm.xml", [System.Text.Encoding]::UTF8) if ($txt.Length -lt 200) { Write-Host " [ui] short xml ($($txt.Length) chars): $($txt.Substring(0,[Math]::Min(120,$txt.Length)))" } return $txt } function VisibleTexts([string]$xml) { [regex]::Matches($xml,'text="([^"]+)"') | ForEach-Object { [System.Net.WebUtility]::HtmlDecode($_.Groups[1].Value) } } function FormFields([string]$xml) { [regex]::Matches($xml,']*class="android\.widget\.EditText"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"') } function Center($m) { $a=[int]$m.Groups[1].Value; $b=[int]$m.Groups[2].Value $c=[int]$m.Groups[3].Value; $d=[int]$m.Groups[4].Value @([int](($a+$c)/2), [int](($b+$d)/2)) } function ButtonCenter([string]$xml, [string]$want) { foreach ($n in [regex]::Matches($xml,']*class="android\.widget\.Button"[^>]*>')) { $t = [System.Net.WebUtility]::HtmlDecode([regex]::Match($n.Value,'text="([^"]*)"').Groups[1].Value) if ($t -eq $want) { $b = [regex]::Match($n.Value,'bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"') if ($b.Success) { $x1=[int]$b.Groups[1].Value; $y1=[int]$b.Groups[2].Value $x2=[int]$b.Groups[3].Value; $y2=[int]$b.Groups[4].Value return @([int](($x1+$x2)/2), [int](($y1+$y2)/2)) } } } return $null } function ButtonStartingWith([string]$xml, [string]$prefix) { foreach ($n in [regex]::Matches($xml,']*class="android\.widget\.Button"[^>]*>')) { $t = [System.Net.WebUtility]::HtmlDecode([regex]::Match($n.Value,'text="([^"]*)"').Groups[1].Value) if ($t.StartsWith($prefix)) { $b = [regex]::Match($n.Value,'bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"') if ($b.Success) { $x1=[int]$b.Groups[1].Value; $y1=[int]$b.Groups[2].Value $x2=[int]$b.Groups[3].Value; $y2=[int]$b.Groups[4].Value return @([int](($x1+$x2)/2), [int](($y1+$y2)/2)) } } } return $null } # Toggle the pointer mode and PROVE it flipped by re-reading the button label. # The bottom bar sits at y=2256..2324, right against the system navigation bar; # a single tap there is not reliable (measured 2026-10-31: the tap landed but the # mode did not change). So: tap, wait, re-read, retry; and judge on the BUTTON # label as well as the HUD (two independent observations of the same state). function Set-Mode([string]$want) { # $want is "relative" or "absolute" $wantTag = if ($want -eq "relative") { $F_RELATIVE } else { $F_ABSOLUTE } for ($try=0; $try -lt 4; $try++) { $xml = Get-UiText $cur = $null foreach ($n in [regex]::Matches($xml,']*class="android\.widget\.Button"[^>]*>')) { $t = [System.Net.WebUtility]::HtmlDecode([regex]::Match($n.Value,'text="([^"]*)"').Groups[1].Value) if ($t.Contains($F_MODE_TAG)) { $b = [regex]::Match($n.Value,'bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"') if ($b.Success) { $x1=[int]$b.Groups[1].Value; $y1=[int]$b.Groups[2].Value $x2=[int]$b.Groups[3].Value; $y2=[int]$b.Groups[4].Value $cur = @{ label=$t; cx=[int](($x1+$x2)/2); cy=[int](($y1+$y2)/2) } } } } if (-not $cur) { throw "mode button not found (no Button whose text contains '$F_MODE_TAG')" } Write-Host " mode button label now: [$($cur.label)] (try $try)" if ($cur.label.Contains($wantTag)) { return $true } [void](AdbShell @("input","tap","$($cur.cx)","$($cur.cy)")) Start-Sleep -Milliseconds 700 } $final = ((VisibleTexts (Get-UiText)) -join " || ") throw "mode did not switch to $want after 4 taps; screen: [$final]" } # The video SurfaceView bounds - touch coordinates must land inside it. # # uiautomator does NOT expose SurfaceView as "android.view.SurfaceView"; a bare # SurfaceView with no accessibility node collapses to a plain "android.view.View" # (measured 2026-10-31 on the live device: the only candidate is 1080x607, which # is exactly the 16:9 letterbox of the 1920x1080 desktop inside a 1080-wide stage). # So identify it structurally: the widest View whose aspect ratio matches 16:9. # Assert the ratio instead of assuming it - a silent wrong node would make every # downstream coordinate assertion meaningless. function SurfaceBounds([string]$xml) { $best = $null; $bestErr = [double]::MaxValue foreach ($n in [regex]::Matches($xml,']*class="android\.view\.View"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"')) { $x1=[int]$n.Groups[1].Value; $y1=[int]$n.Groups[2].Value $x2=[int]$n.Groups[3].Value; $y2=[int]$n.Groups[4].Value $w = $x2-$x1; $h = $y2-$y1 if ($w -lt 300 -or $h -lt 100) { continue } $ratio = $w / [double]$h $err = [Math]::Abs($ratio - (16.0/9.0)) if ($err -lt $bestErr) { $bestErr = $err; $best = @($x1,$y1,$x2,$y2) } } if (-not $best) { return $null } # sanity: the chosen node must really be ~16:9, else we grabbed something else $w = $best[2]-$best[0]; $h = $best[3]-$best[1] if ($bestErr -gt 0.25) { throw "no 16:9 view found (best candidate ${w}x${h}, ratio $([Math]::Round($w/[double]$h,3)))" } Write-Host " video view ${w}x${h} (ratio $([Math]::Round($w/[double]$h,3)), 16:9 error $([Math]::Round($bestErr,4)))" return $best } function TypeAscii([string]$s) { foreach ($ch in $s.ToUpper().ToCharArray()) { $k = if ($ch -ge '0' -and $ch -le '9') { 7 + [int][string]$ch } elseif ($ch -ge 'A' -and $ch -le 'Z') { 29 + ([int]$ch - [int][char]'A') } elseif ($ch -eq '.') { 56 } else { throw "unsupported char: $ch" } [void](AdbShell @("input","keyevent","$k")) Start-Sleep -Milliseconds 70 } } function FocusedIndex([string]$xml) { $i = -1 foreach ($n in [regex]::Matches($xml,']*>')) { if ($n.Value -match 'class="android\.widget\.EditText"') { $i++ if ($n.Value -match 'focused="true"') { return $i } } } return -1 } function Focus-Field($c, [int]$wantIdx) { for ($try=0; $try -lt 3; $try++) { [void](AdbShell @("input","tap","$($c[0])","$($c[1])")) $deadline = (Get-Date).AddSeconds(1.5) while ((Get-Date) -lt $deadline) { Start-Sleep -Milliseconds 250 if ((FocusedIndex (Get-UiText)) -eq $wantIdx) { return } } } throw "tap did not focus field $wantIdx after 3 tries" } function Clear-Field($c, [int]$idx) { Focus-Field $c $idx for ($i=0; $i -lt 40; $i++) { [void](AdbShell @("input","keyevent","67")) } Start-Sleep -Milliseconds 150 } function Restart-AppForm { [void](AdbShell @("input","keyevent","224")) Start-Sleep -Milliseconds 300 [void](AdbShell @("wm","dismiss-keyguard")) Start-Sleep -Milliseconds 500 [void](AdbShell @("am","force-stop","com.verify.remote")) Start-Sleep -Milliseconds 600 [void](AdbShell @("am","start","-n","com.verify.remote/.MainActivity")) $deadline = (Get-Date).AddSeconds(8) while ((Get-Date) -lt $deadline) { $x = Get-UiText if ((FormFields $x).Count -ge 3) { return $x } Start-Sleep -Milliseconds 300 } $diag = (VisibleTexts (Get-UiText)) -join " || " throw "connect form did not appear. screen: [$diag]" } function FillAndConnect([string]$xml, [string]$host_, [string]$port_, [string]$code_) { $f = FormFields $xml if ($f.Count -lt 3) { throw "form has $($f.Count) fields" } Clear-Field (Center $f[0]) 0; TypeAscii $host_ Clear-Field (Center $f[1]) 1; TypeAscii $port_ Clear-Field (Center $f[2]) 2; TypeAscii $code_ $expect = @($host_, $port_, $code_) for ($retry=0; $retry -le 2; $retry++) { $x2 = Get-UiText $vals = @() foreach ($n in [regex]::Matches($x2,']*>')) { if ($n.Value -match 'class="android\.widget\.EditText"') { $vals += [System.Net.WebUtility]::HtmlDecode([regex]::Match($n.Value,'text="([^"]*)"').Groups[1].Value) } } Write-Host " form now: $($vals -join ' | ') (try $retry)" if ($vals.Count -lt 3) { throw "expected 3 fields after typing, got $($vals.Count)" } $bad = -1 for ($i=0; $i -lt 3; $i++) { if ($vals[$i] -ne $expect[$i]) { $bad = $i; break } } if ($bad -ge 0) { $f = FormFields $x2 Clear-Field (Center $f[$bad]) $bad TypeAscii $expect[$bad] continue } break } if ($vals[0] -ne $host_) { throw "form input FAIL: host is [$($vals[0])], expected [$host_]" } if ($vals[1] -ne $port_) { throw "form input FAIL: port is [$($vals[1])], expected [$port_]" } if ($vals[2] -ne $code_) { throw "form input FAIL: code is [$($vals[2])], expected [$code_]" } $btn = $null foreach ($want in @($F_CONNECT, $F_RECONNECT)) { $btn = ButtonCenter $x2 $want; if ($btn) { break } } if (-not $btn) { throw "no primary connect/reconnect button found" } [void](AdbShell @("input","tap","$($btn[0])","$($btn[1])")) } function Wait-Live([int]$seconds, [ref]$hud) { $deadline = (Get-Date).AddSeconds($seconds) while ((Get-Date) -lt $deadline) { Start-Sleep -Milliseconds 500 $m = [regex]::Match((Get-UiText),'text="([^"]*RTT=[^"]*)"') if ($m.Success) { $hud.Value = [System.Net.WebUtility]::HtmlDecode($m.Groups[1].Value); return $true } } return $false } # ---- PC-side cursor readback (the only honest judge of "pointer moved N px") ---- Add-Type -TypeDefinition @" using System; using System.Runtime.InteropServices; public class PC { [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; } [DllImport("user32.dll")] public static extern bool GetCursorPos(out POINT p); [DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y); [DllImport("user32.dll")] public static extern bool SetProcessDpiAwarenessContext(IntPtr ctx); [DllImport("user32.dll")] public static extern int GetSystemMetrics(int i); public static void Dpi() { SetProcessDpiAwarenessContext(new IntPtr(-4)); } public static int[] Get() { POINT p; GetCursorPos(out p); return new int[]{p.X,p.Y}; } public static void Set(int x,int y) { SetCursorPos(x,y); } public static int W() { return GetSystemMetrics(0); } public static int H() { return GetSystemMetrics(1); } } "@ [void][PC]::Dpi() $SW = [PC]::W(); $SH = [PC]::H() Write-Host " PC screen ${SW}x${SH} (physical, DPI-aware)" Write-Host "=== 0. stale-artifact guard (R-7) + IME disable + keep-awake ===" if (-not $AgentExe) { $AgentExe = Join-Path $repo "target\release\agent.exe" } $ag = @(Get-Process agent -ErrorAction SilentlyContinue) if ($ag.Count -gt 0 -and (Test-Path $AgentExe) -and ((Get-Item $AgentExe).LastWriteTime -gt $ag[0].StartTime)) { Write-Host " running agent predates current build; restarting" Stop-Process -Name agent -Force -ErrorAction SilentlyContinue Start-Sleep -Milliseconds 800; $ag = @() } if ($ag.Count -eq 0) { if (-not (Test-Path $AgentExe)) { throw "missing $AgentExe" } Start-Process $AgentExe -ArgumentList "--port","$Port","--code",$Code -WindowStyle Hidden | Out-Null Start-Sleep -Seconds 1; Write-Host " agent started (v4)" } else { Write-Host " agent running and current" } $origIme = ((AdbShell @("settings","get","secure","default_input_method")) -join "").Trim() Write-Host " original IME: [$origIme] (recorded, restored in finally)" if ($origIme -and $origIme -ne "null") { [void](AdbShell @("ime","disable",$origIme)) } [void](AdbShell @("svc","power","stayon","true")) $origCursor = [PC]::Get() Write-Host " PC cursor origin ($($origCursor[0]),$($origCursor[1])) (restored in finally)" try { # ================================================================= Write-Host "=== 1. connect (protocol v4) and confirm HUD shows v4 ===" $x = Restart-AppForm [void](FillAndConnect $x $Host_ "$Port" $Code) $hud = "" if (-not (Wait-Live 14 ([ref]$hud))) { throw "never reached a live session" } Write-Host " HUD: $hud" if (-not $hud.Contains("v4")) { throw "V-B4 FAIL: HUD does not report protocol v4; got [$hud]" } Write-Host " PASS: live session on protocol v4" $xml = Get-UiText $sb = SurfaceBounds $xml if (-not $sb) { throw "cannot locate SurfaceView bounds" } $vx1=$sb[0]; $vy1=$sb[1]; $vx2=$sb[2]; $vy2=$sb[3] $vcx=[int](($vx1+$vx2)/2); $vcy=[int](($vy1+$vy2)/2) Write-Host " surface bounds = [$vx1,$vy1][$vx2,$vy2] center=($vcx,$vcy)" & powershell -NoProfile -ExecutionPolicy Bypass -File "$repo\tools\shot.ps1" -Out "$ev\v-b1-live.png" | Out-Null # ================================================================= Write-Host "=== 2. V-B1a: relative mode moves PC pointer (swipe right) ===" # Park the PC cursor at a known point so the delta is unambiguous. # Let the link drain first (same discipline as 2c): residual events from an # earlier gesture would otherwise be attributed to this one. Start-Sleep -Milliseconds 1000 [PC]::Set([int]($SW/2), [int]($SH/2)) Start-Sleep -Milliseconds 500 $before = [PC]::Get() # A horizontal swipe of ~40% of the view width. With sensitivity 1.0 and # view width mapping to desktop width, expected delta = 0.4 * SW. $swipeFromX = [int]($vx1 + ($vx2-$vx1)*0.30) $swipeToX = [int]($vx1 + ($vx2-$vx1)*0.70) [void](AdbShell @("input","swipe","$swipeFromX","$vcy","$swipeToX","$vcy","400")) Start-Sleep -Milliseconds 500 $after = [PC]::Get() $moved = $after[0] - $before[0] Write-Host " swipe dx_device=$($swipeToX-$swipeFromX)px -> PC pointer dx=$moved px (before x=$($before[0]), after x=$($after[0]))" if ($moved -le 0) { throw "V-B1a FAIL: rightward swipe did not move the pointer right (dx=$moved)" } # Direction check is the hard judgement; exact magnitude depends on how many # ACTION_MOVE samples the ROM delivers for an `input swipe` (not equal to the # finger travel), so magnitude is reported, not asserted. Write-Host " PASS: relative mode moves the pointer in the finger's direction" Write-Host "=== 2b. V-B1a negative: swipe left moves pointer left ===" Start-Sleep -Milliseconds 1000 [PC]::Set([int]($SW/2), [int]($SH/2)) Start-Sleep -Milliseconds 500 $before2 = [PC]::Get() [void](AdbShell @("input","swipe","$swipeToX","$vcy","$swipeFromX","$vcy","400")) Start-Sleep -Milliseconds 500 $after2 = [PC]::Get() $moved2 = $after2[0] - $before2[0] Write-Host " PC pointer dx=$moved2 px (before x=$($before2[0]), after x=$($after2[0]))" if ($moved2 -ge 0) { throw "V-B1a FAIL: leftward swipe did not move the pointer left (dx=$moved2)" } Write-Host " PASS: negative direction correct (sign preserved)" Write-Host "=== 2c. relative mode does NOT teleport the pointer on touch-down ===" # The defining property of relative mode: ACTION_DOWN must not send an # absolute move. Tap at the far LEFT of the view; the pointer must not jump # to the left edge of the desktop. # # HARNESS DISCIPLINE (measured 2026-10-31): a swipe's final relative events # can still be in flight when we re-park the cursor. The first attempt showed # phantom "jumps" of 15px and 298px that were really the PREVIOUS swipe's # queued deltas landing after our SetCursorPos. So: wait for the link to go # quiet, then require the baseline to be STABLE across two reads before # judging. Otherwise this test would report a product defect that isn't one. Start-Sleep -Milliseconds 1200 [PC]::Set([int]($SW*0.8), [int]($SH/2)) Start-Sleep -Milliseconds 500 $stable = $false for ($i=0; $i -lt 6; $i++) { $r1 = [PC]::Get(); Start-Sleep -Milliseconds 350; $r2 = [PC]::Get() if ($r1[0] -eq $r2[0] -and $r1[1] -eq $r2[1]) { $stable = $true; break } Write-Host " cursor still settling: ($($r1[0]),$($r1[1])) -> ($($r2[0]),$($r2[1]))" } if (-not $stable) { throw "cannot obtain a stable cursor baseline (residual relative events never drained)" } $beforeTap = [PC]::Get() $tapX = [int]($vx1 + ($vx2-$vx1)*0.05) [void](AdbShell @("input","tap","$tapX","$vcy")) Start-Sleep -Milliseconds 700 $afterTap = [PC]::Get() $jump = [Math]::Max([Math]::Abs($afterTap[0] - $beforeTap[0]), [Math]::Abs($afterTap[1] - $beforeTap[1])) $dbgTap = ((VisibleTexts (Get-UiText)) -join " || ") Write-Host " tap at view-left x=$tapX ; pointer ($($beforeTap[0]),$($beforeTap[1])) -> ($($afterTap[0]),$($afterTap[1])) jump=$jump px" if ($dbgTap.Contains($F_LONGPRESS)) { throw "tap was interpreted as a long press; harness timing is wrong" } if ($jump -gt 8) { throw "V-B1a FAIL: touch-down teleported the pointer by $jump px (absolute move leaked into relative mode)" } Write-Host " PASS: relative mode leaves the pointer where it was (no teleport)" # ================================================================= Write-Host "=== 3. V-B2: switch to ABSOLUTE mode, re-run R7 touch mapping ===" [void](Set-Mode "absolute") $hud2 = "" $m2 = [regex]::Match((Get-UiText),'text="([^"]*RTT=[^"]*)"') if ($m2.Success) { $hud2 = [System.Net.WebUtility]::HtmlDecode($m2.Groups[1].Value) } Write-Host " HUD: $hud2" if (-not $hud2.Contains($F_ABSOLUTE)) { throw "V-B2 FAIL: HUD still does not say absolute; got [$hud2]" } Write-Host " PASS: mode switched to absolute (button label and HUD agree)" # R7 expectations for 1920x1080 (physical pixels, 125% scaling does NOT apply # to the injected desktop space - the agent injects into the physical desktop) $r7 = @( @{ frac=0.10; ex=[int](192); ey=[int](108) }, @{ frac=0.90; ex=[int](1728); ey=[int](972) }, @{ frac=0.50; ex=[int](960); ey=[int](540) } ) $worst = 0 foreach ($c in $r7) { $tx = [int]($vx1 + ($vx2-$vx1)*$c.frac) $ty = [int]($vy1 + ($vy2-$vy1)*$c.frac) [void](AdbShell @("input","tap","$tx","$ty")) Start-Sleep -Milliseconds 400 $p = [PC]::Get() $errX = [Math]::Abs($p[0] - $c.ex) $errY = [Math]::Abs($p[1] - $c.ey) $e = [Math]::Max($errX,$errY) if ($e -gt $worst) { $worst = $e } Write-Host " frac=$($c.frac) expect=($($c.ex),$($c.ey)) actual=($($p[0]),$($p[1])) err=($errX,$errY)" } Write-Host " worst R7 error = $worst px" if ($worst -gt 8) { throw "V-B2 FAIL: R7 absolute mapping error $worst px > 8px" } Write-Host " PASS: absolute mode still maps 1:1 (R7 regression green)" # ================================================================= Write-Host "=== 4. V-B3: long press opens a context menu; drag does NOT ===" # Switch back to relative for the long-press test (that is the default mode # users get, and the gesture must work there). [void](Set-Mode "relative") # Use a desktop of our own: the proof's WinForms target window (R-11: never # inject into a window we do not own - it would modify user data). $target = Join-Path $repo "tools\text_target.ps1" if (-not (Test-Path $target)) { throw "missing $target" } $tp = Start-Process powershell -ArgumentList "-NoProfile","-ExecutionPolicy","Bypass","-File",$target ` -PassThru -WindowStyle Minimized Start-Sleep -Seconds 3 Write-Host " target window started (pid $($tp.Id))" # Park the pointer over the middle of the target window's edit area by # absolute injection first (so we know where the long press lands). [PC]::Set([int]($SW/2), [int]($SH/2)) Start-Sleep -Milliseconds 300 $preLong = [PC]::Get() $rcBefore = RightClickCount Write-Host " right-click counter before: $rcBefore" [void](AdbShell @("input","swipe","$vcx","$vcy","$vcx","$vcy","900")) # press & hold ~900ms, no movement Start-Sleep -Milliseconds 700 $rcAfter = RightClickCount $debug = ((VisibleTexts (Get-UiText)) -join " || ") & powershell -NoProfile -ExecutionPolicy Bypass -File "$repo\tools\shot.ps1" -Out "$ev\v-b3-longpress.png" | Out-Null Write-Host " right-click counter after : $rcAfter (source tag in HUD)" Write-Host " screen after long press: $debug" if ($rcBefore -lt 0 -or $rcAfter -lt 0) { throw "V-B3 FAIL: could not read the right-click counter from the HUD" } if ($rcAfter -le $rcBefore) { throw "V-B3 FAIL: long press did not inject a right click (counter stayed $rcAfter; screen: $debug)" } Write-Host " PASS: long press injected a right click (counter $rcBefore -> $rcAfter)" Write-Host "=== 4b. V-B3 negative: a DRAG must not fire long press ===" $rcBeforeDrag = RightClickCount [void](AdbShell @("input","swipe","$vcx","$vcy",$([int]($vcx+120)),$([int]($vcy+80)),900)) Start-Sleep -Milliseconds 700 $rcAfterDrag = RightClickCount $debug2 = ((VisibleTexts (Get-UiText)) -join " || ") Write-Host " right-click counter after drag: $rcAfterDrag (was $rcBeforeDrag)" Write-Host " screen after drag: $debug2" if ($rcAfterDrag -gt $rcBeforeDrag) { throw "V-B3 FAIL: drag injected a right click (counter $rcBeforeDrag -> $rcAfterDrag); long press must be suppressed once the finger moves" } Write-Host " PASS: drag did not fire long press" Write-Host "=== 4c. V-B3: two-finger right click still works ===" $rcBeforeTwo = RightClickCount # adb cannot synthesize a two-finger tap (lessons R-6): this is a MANUAL item. Write-Host " (two-finger tap is not drivable via adb - recorded as manual, not asserted here)" Stop-Process -Id $tp.Id -Force -ErrorAction SilentlyContinue } finally { [void][PC]::Set($origCursor[0], $origCursor[1]) [void](AdbShell @("svc","power","stayon","false")) if ($origIme -and $origIme -ne "null") { [void](AdbShell @("ime","enable",$origIme)) [void](AdbShell @("ime","set",$origIme)) Write-Host " IME restored: $(((AdbShell @('settings','get','secure','default_input_method')) -join '').Trim())" } Write-Host " PC cursor restored to ($($origCursor[0]),$($origCursor[1]))" } Write-Host "=== REL MOUSE PHONE PROOF COMPLETE ==="