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) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-30 20:44:35 +10:00
1 parent bd24436fbc
commit 924e37aa30
2 files changed
+11 -12

No files matched your search

+3 -3
View File
@@ -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.
+8 -9
View File
@@ -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