# Photograph the framebuffer console after driving it with escape sequences. # # The serial line and the framebuffer are two different consumers of console # output, and they behave differently on purpose: escapes are passed through # to the serial line verbatim (a real terminal emulator is on the other end # and understands them better than we do) and *interpreted* for the # framebuffer. So the serial transcript can never tell you whether the escape # parser works -- it contains the raw bytes either way. Only a picture can. # # tools/ttyshot.ps1 param( [int]$BootSeconds = 22, [string]$Out = "build/ttyshot.ppm", [string]$Qemu ) # QEMU, on whichever host this is. # # PowerShell 7 runs on Linux, macOS and the BSDs as well as Windows, so these # scripts are usable from all of them -- but the old default was a hardcoded # scoop path under $env:USERPROFILE, which exists on exactly one of those. PATH # first, because a package-managed qemu is on it everywhere; the scoop location # is only a fallback for the Windows install that does not add itself to PATH. function Find-Qemu { $exe = if ($IsWindows -or $env:OS -eq 'Windows_NT') { 'qemu-system-x86_64.exe' } else { 'qemu-system-x86_64' } $onPath = Get-Command $exe -ErrorAction SilentlyContinue if ($onPath) { return $onPath.Source } if ($env:USERPROFILE) { $scoop = Join-Path (Join-Path $env:USERPROFILE 'scoop/apps/qemu/current') $exe if (Test-Path $scoop) { return $scoop } } foreach ($p in @('/usr/bin', '/usr/local/bin', '/opt/homebrew/bin')) { $c = Join-Path $p $exe if (Test-Path $c) { return $c } } throw "qemu-system-x86_64 not found on PATH. Install QEMU, or pass -Qemu ." } if (-not $Qemu) { $Qemu = Find-Qemu } $ErrorActionPreference = 'Stop' # Repo root is two levels up: this script lives in tools/windows/. $root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent $hda = Join-Path $root 'schos.img' # The root filesystem. SchFS is the only root there is: the kernel mounts # device 1 in forkret() and panics if it is not a SchFS volume, so this is # not a choice and there is no -SchFS switch any more. It used to default to # build/fs.img, which stopped being bootable the moment SchFS became root. $hdb = Join-Path $root 'build/schfs.img' $log = Join-Path $root 'build/ttyshot.log' $ppm = Join-Path $root $Out $monPort = 55571 # Each line exercises one thing the console could not do before: colour, # bright colour, background colour, cursor addressing, erase-to-end-of-line, # and non-ASCII text. $commands = @( 'clear' 'printf ''\033[31mRED \033[32mGREEN \033[34mBLUE \033[35mMAGENTA \033[36mCYAN \033[0mdefault\n''' 'printf ''\033[1;31mbold red \033[1;32mbold green \033[1;34mbold blue\033[0m\n''' 'printf ''\033[37;44m white on blue \033[0m normal\n''' 'printf ''caf\303\251 \303\251t\303\251 na\303\257ve \303\274ber sch\303\266n a\303\261o\n''' 'printf ''ERASED-THIS\033[13G\033[Kkept this\n''' 'printf ''\033[12;40Hcursor addressed to row 12 col 40\n''' # Tab used to be drawn as glyph 9, which in CP437 is a hollow circle -- # so pressing Tab printed a circle instead of moving the cursor. Every # other control character had the same problem waiting behind it. 'printf ''A\tB\tC\tD -- tabs, not circles\n''' ) $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.FileName = $Qemu $psi.Arguments = @( "-drive file=`"$hda`",format=raw,index=0,media=disk" "-drive file=`"$hdb`",format=raw,index=1,media=disk" '-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2' '-display none' "-chardev stdio,id=c0,logfile=`"$log`"" '-serial chardev:c0' "-monitor tcp:127.0.0.1:$monPort,server,nowait" ) -join ' ' $psi.RedirectStandardInput = $true $psi.UseShellExecute = $false Write-Host '==> booting' -ForegroundColor Cyan $p = [System.Diagnostics.Process]::Start($psi) try { Start-Sleep -Seconds $BootSeconds foreach ($cmd in $commands) { Write-Host " > $cmd" -ForegroundColor DarkGray foreach ($ch in ($cmd + "`n").ToCharArray()) { $p.StandardInput.Write($ch) $p.StandardInput.Flush() Start-Sleep -Milliseconds 20 # serial has no flow control } Start-Sleep -Seconds 2 } Write-Host '==> capturing the framebuffer' -ForegroundColor Cyan $client = New-Object System.Net.Sockets.TcpClient('127.0.0.1', $monPort) $writer = New-Object System.IO.StreamWriter($client.GetStream()) $writer.AutoFlush = $true Start-Sleep -Milliseconds 500 $writer.WriteLine("screendump $ppm") Start-Sleep -Seconds 2 $writer.WriteLine('quit') Start-Sleep -Milliseconds 500 $client.Close() } finally { if (-not $p.HasExited) { $p.Kill() } Start-Sleep -Milliseconds 800 } if (Test-Path $ppm) { Write-Host "==> $ppm ($((Get-Item $ppm).Length) bytes)" -ForegroundColor Green } else { Write-Host 'FAIL: no screendump was produced' -ForegroundColor Red exit 1 }