From 924e37aa30d048f9de6640b4c5f3ba553b0ee94d Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:44:35 +1000 Subject: [PATCH] windows-vm.sh: serialise clicks with a named mutex instead of a lock file Windows releases the mutex when the request's process ends, so there's no stale lock to recover and no window where two requests can take each other's. Requests sent together now run one after another. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/testing.md | 6 +++--- scripts/windows-vm.sh | 17 ++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/testing.md b/docs/testing.md index 3b6db77..ee32c2d 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -205,9 +205,9 @@ scripts/windows-vm.sh down # shut Windows down - **Clicks go through a scheduled task.** Commands over SSH run in a session with no desktop, so `click` and `scroll` write the position to a file, and a scheduled task running as the signed-in user replays it. The - VM's screen must be signed in; it is after `up`. One click or scroll at a - time: they share that task. QEMU's own `mouse_move` is relative and drifts, - so the script doesn't use it. + VM's screen must be signed in; it is after `up`. Clicks and scrolls sent at + the same time run one after another. QEMU's own `mouse_move` is relative + and drifts, so the script doesn't use it. - **Screenshots may not show the pointer.** Check the result of a click (a menu that opens, a button that changes) rather than the pointer's position. - **Windows' `ssh` waits for stdin.** The script closes it for every command. diff --git a/scripts/windows-vm.sh b/scripts/windows-vm.sh index 2065d6b..be4f077 100755 --- a/scripts/windows-vm.sh +++ b/scripts/windows-vm.sh @@ -69,16 +69,15 @@ Remove-Item "$PSScriptRoot\input.txt"' pointer() { # x y click|wheel [notches] local b64=$(print -rn -- $INPUT_PS1 | base64 | tr -d '\n') vm_ps '$ErrorActionPreference = "Stop" -$d = Join-Path $env:LOCALAPPDATA "windows-vm"; $req = "$d\input.txt"; $held = $false -trap { [Console]::Error.WriteLine("windows-vm: $_"); if ($held) { Remove-Item $req -ErrorAction SilentlyContinue }; exit 1 } +$d = Join-Path $env:LOCALAPPDATA "windows-vm"; $req = "$d\input.txt"; $mine = $false +trap { [Console]::Error.WriteLine("windows-vm: $_"); if ($mine) { Remove-Item $req -ErrorAction SilentlyContinue }; exit 1 } +# One request at a time: the helper, the task and input.txt are shared. Windows +# frees the mutex when this process ends, however it ends. +$lock = New-Object Threading.Mutex($false, "windows-vm-input") +try { $mine = $lock.WaitOne(15000) } catch [Threading.AbandonedMutexException] { $mine = $true } +if (-not $mine) { [Console]::Error.WriteLine("windows-vm: another click or scroll is still in progress"); exit 1 } New-Item -ItemType Directory -Force $d | Out-Null -$old = Get-Item $req -ErrorAction SilentlyContinue # left behind by a run that died -if ($old -and $old.LastWriteTime -lt (Get-Date).AddSeconds(-15)) { Remove-Item $req } -# Creating input.txt is the lock: CreateNew fails if another request already has it. -try { $f = [IO.File]::Open($req, "CreateNew", "Write") } -catch { [Console]::Error.WriteLine("windows-vm: another click or scroll is in progress"); exit 1 } -$held = $true -$text = [Text.Encoding]::ASCII.GetBytes("'"$*"'"); $f.Write($text, 0, $text.Length); $f.Close() +[IO.File]::WriteAllText($req, "'"$*"'") [IO.File]::WriteAllText("$d\input.ps1", [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String("'$b64'"))) $act = New-ScheduledTaskAction -Execute powershell.exe -Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$d\input.ps1`"" $who = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive