import { spawn } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; function readPositiveIntEnv(name, fallback) { const raw = process.env[name]; if (raw === undefined) { return fallback; } const parsed = Number(raw); if (!Number.isFinite(parsed) || parsed <= 0) { console.warn(`[cursor-native-test] ignoring invalid ${name}=${raw}; using ${fallback}`); return fallback; } return Math.floor(parsed); } const SAMPLE_INTERVAL_MS = readPositiveIntEnv("CURSOR_TEST_SAMPLE_INTERVAL_MS", 25); const DURATION_MS = readPositiveIntEnv("CURSOR_TEST_DURATION_MS", 1800); const SCREEN_FRAME_INTERVAL_MS = readPositiveIntEnv("CURSOR_TEST_SCREEN_FRAME_INTERVAL_MS", 100); const READY_TIMEOUT_MS = readPositiveIntEnv("CURSOR_TEST_READY_TIMEOUT_MS", 5000); const OUTPUT_DIR = process.env.CURSOR_TEST_OUTPUT_DIR ?? path.join(os.tmpdir(), `openscreen-cursor-native-${Date.now()}`); if (process.platform !== "win32") { console.error("This diagnostic is Windows-only."); process.exit(1); } function encodePowerShell(script) { return Buffer.from(script, "utf16le").toString("base64"); } function quotePowerShellString(value) { return `'${String(value).replaceAll("'", "''")}'`; } function runPowerShell(script) { return new Promise((resolve, reject) => { const child = spawn( "powershell.exe", [ "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encodePowerShell(script), ], { stdio: ["ignore", "pipe", "pipe"], windowsHide: true }, ); let stdout = ""; let stderr = ""; child.stdout.setEncoding("utf8"); child.stderr.setEncoding("utf8"); child.stdout.on("data", (chunk) => { stdout += chunk; }); child.stderr.on("data", (chunk) => { stderr += chunk; }); child.once("error", reject); child.once("exit", (code, signal) => { if (code === 0) { resolve(stdout); return; } reject( new Error(`PowerShell command failed (code=${code}, signal=${signal}): ${stderr.trim()}`), ); }); }); } function spawnPowerShell(script, { onStdout, onStderr } = {}) { const scriptPath = path.join( os.tmpdir(), `openscreen-powershell-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.ps1`, ); fs.writeFileSync(scriptPath, script, "utf8"); const child = spawn( "powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", scriptPath], { stdio: ["ignore", "pipe", "pipe"], windowsHide: true }, ); child.stdout.setEncoding("utf8"); child.stderr.setEncoding("utf8"); child.stdout.on("data", (chunk) => onStdout?.(chunk)); child.stderr.on("data", (chunk) => onStderr?.(chunk)); const done = new Promise((resolve, reject) => { const cleanup = () => { fs.rmSync(scriptPath, { force: true }); }; child.once("error", (error) => { cleanup(); reject(error); }); child.once("exit", (code, signal) => { cleanup(); if (code === 0 || child.killed) { resolve({ code, signal }); return; } reject(new Error(`PowerShell process failed (code=${code}, signal=${signal})`)); }); }); return { child, done }; } function buildSamplerScript() { return String.raw` $ErrorActionPreference = 'Stop' Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName System.Windows.Forms $source = @" using System; using System.Diagnostics; using System.Runtime.InteropServices; public static class OpenScreenCursorDiagnosticInterop { private const int WH_MOUSE_LL = 14; private const int WM_LBUTTONDOWN = 0x0201; private const int WM_LBUTTONUP = 0x0202; private static readonly object MouseSync = new object(); private static int LeftDownCount = 0; private static int LeftUpCount = 0; private static IntPtr MouseHook = IntPtr.Zero; private static LowLevelMouseProc MouseProcDelegate = MouseHookCallback; public delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam); public struct MouseButtonEvents { public int LeftDownCount; public int LeftUpCount; } [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; } [StructLayout(LayoutKind.Sequential)] public struct CURSORINFO { public int cbSize; public int flags; public IntPtr hCursor; public POINT ptScreenPos; } [StructLayout(LayoutKind.Sequential)] public struct ICONINFO { [MarshalAs(UnmanagedType.Bool)] public bool fIcon; public int xHotspot; public int yHotspot; public IntPtr hbmMask; public IntPtr hbmColor; } public static bool InstallMouseHook() { if (MouseHook != IntPtr.Zero) { return true; } using (Process process = Process.GetCurrentProcess()) using (ProcessModule module = process.MainModule) { MouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProcDelegate, GetModuleHandle(module.ModuleName), 0); } return MouseHook != IntPtr.Zero; } public static MouseButtonEvents ConsumeMouseButtonEvents() { lock (MouseSync) { MouseButtonEvents events = new MouseButtonEvents { LeftDownCount = LeftDownCount, LeftUpCount = LeftUpCount }; LeftDownCount = 0; LeftUpCount = 0; return events; } } private static IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0) { int message = wParam.ToInt32(); if (message == WM_LBUTTONDOWN || message == WM_LBUTTONUP) { lock (MouseSync) { if (message == WM_LBUTTONDOWN) { LeftDownCount += 1; } else { LeftUpCount += 1; } } } } return CallNextHookEx(MouseHook, nCode, wParam, lParam); } [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetCursorInfo(ref CURSORINFO pci); [DllImport("user32.dll")] public static extern short GetAsyncKeyState(int vKey); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr CopyIcon(IntPtr hIcon); [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool DestroyIcon(IntPtr hIcon); [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); [DllImport("gdi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool DeleteObject(IntPtr hObject); [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr GetModuleHandle(string lpModuleName); } "@ Add-Type -TypeDefinition $source $standardCursors = @{ arrow = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32512)) text = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32513)) wait = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32514)) crosshair = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32515)) 'up-arrow' = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32516)) 'resize-nwse' = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32642)) 'resize-nesw' = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32643)) 'resize-ew' = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32644)) 'resize-ns' = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32645)) move = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32646)) 'not-allowed' = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32648)) pointer = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32649)) 'app-starting' = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32650)) help = [OpenScreenCursorDiagnosticInterop]::LoadCursor([IntPtr]::Zero, [IntPtr]::new(32651)) } function Get-StandardCursorType($cursorHandle) { if ($cursorHandle -eq [IntPtr]::Zero) { return $null } foreach ($entry in $standardCursors.GetEnumerator()) { if ($entry.Value -eq $cursorHandle) { return $entry.Key } } return $null } function Write-JsonLine($payload) { [Console]::Out.WriteLine(($payload | ConvertTo-Json -Compress -Depth 6)) } function Get-CustomCursorType($bitmap, $hotspotX, $hotspotY) { if ($bitmap.Width -lt 24 -or $bitmap.Height -lt 24 -or $bitmap.Width -gt 64 -or $bitmap.Height -gt 64) { return $null } if ($hotspotX -lt ($bitmap.Width * 0.25) -or $hotspotX -gt ($bitmap.Width * 0.75) -or $hotspotY -lt ($bitmap.Height * 0.15) -or $hotspotY -gt ($bitmap.Height * 0.55)) { return $null } $opaquePixels = 0 $topHalfOpaquePixels = 0 $left = $bitmap.Width $top = $bitmap.Height $right = -1 $bottom = -1 for ($y = 0; $y -lt $bitmap.Height; $y++) { for ($x = 0; $x -lt $bitmap.Width; $x++) { if ($bitmap.GetPixel($x, $y).A -le 32) { continue } $opaquePixels += 1 if ($y -lt ($bitmap.Height / 2)) { $topHalfOpaquePixels += 1 } if ($x -lt $left) { $left = $x } if ($x -gt $right) { $right = $x } if ($y -lt $top) { $top = $y } if ($y -gt $bottom) { $bottom = $y } } } if ($opaquePixels -lt 90 -or $right -lt $left -or $bottom -lt $top) { return $null } $opaqueWidth = $right - $left + 1 $opaqueHeight = $bottom - $top + 1 if ($opaqueWidth -lt ($bitmap.Width * 0.35) -or $opaqueWidth -gt ($bitmap.Width * 0.9) -or $opaqueHeight -lt ($bitmap.Height * 0.45) -or $opaqueHeight -gt $bitmap.Height) { return $null } if ($top -gt ($bitmap.Height * 0.45) -or $bottom -lt ($bitmap.Height * 0.65)) { return $null } if ($topHalfOpaquePixels -gt ($opaquePixels * 0.55)) { return 'closed-hand' } return 'open-hand' } function Get-CursorAsset($cursorHandle, $cursorId) { $copiedHandle = [OpenScreenCursorDiagnosticInterop]::CopyIcon($cursorHandle) if ($copiedHandle -eq [IntPtr]::Zero) { return $null } $iconInfo = New-Object OpenScreenCursorDiagnosticInterop+ICONINFO $hasIconInfo = [OpenScreenCursorDiagnosticInterop]::GetIconInfo($copiedHandle, [ref]$iconInfo) try { $icon = [System.Drawing.Icon]::FromHandle($copiedHandle) $bitmap = New-Object System.Drawing.Bitmap $icon.Width, $icon.Height, ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb) $graphics = [System.Drawing.Graphics]::FromImage($bitmap) $memoryStream = New-Object System.IO.MemoryStream try { $graphics.Clear([System.Drawing.Color]::Transparent) $graphics.DrawIcon($icon, 0, 0) $hotspotX = if ($hasIconInfo) { $iconInfo.xHotspot } else { 0 } $hotspotY = if ($hasIconInfo) { $iconInfo.yHotspot } else { 0 } $customCursorType = Get-CustomCursorType -bitmap $bitmap -hotspotX $hotspotX -hotspotY $hotspotY $bitmap.Save($memoryStream, [System.Drawing.Imaging.ImageFormat]::Png) $base64 = [System.Convert]::ToBase64String($memoryStream.ToArray()) return @{ id = $cursorId imageDataUrl = "data:image/png;base64,$base64" width = $bitmap.Width height = $bitmap.Height hotspotX = $hotspotX hotspotY = $hotspotY cursorType = $customCursorType } } finally { $memoryStream.Dispose() $graphics.Dispose() $bitmap.Dispose() $icon.Dispose() } } finally { if ($hasIconInfo) { if ($iconInfo.hbmMask -ne [IntPtr]::Zero) { [OpenScreenCursorDiagnosticInterop]::DeleteObject($iconInfo.hbmMask) | Out-Null } if ($iconInfo.hbmColor -ne [IntPtr]::Zero) { [OpenScreenCursorDiagnosticInterop]::DeleteObject($iconInfo.hbmColor) | Out-Null } } [OpenScreenCursorDiagnosticInterop]::DestroyIcon($copiedHandle) | Out-Null } } [OpenScreenCursorDiagnosticInterop]::InstallMouseHook() | Out-Null [OpenScreenCursorDiagnosticInterop]::GetAsyncKeyState(0x01) | Out-Null Write-JsonLine @{ type = 'ready'; timestampMs = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } $lastCursorId = $null $screenBounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds while ($true) { [System.Windows.Forms.Application]::DoEvents() $mouseEvents = [OpenScreenCursorDiagnosticInterop]::ConsumeMouseButtonEvents() $cursorInfo = New-Object OpenScreenCursorDiagnosticInterop+CURSORINFO $cursorInfo.cbSize = [Runtime.InteropServices.Marshal]::SizeOf([type][OpenScreenCursorDiagnosticInterop+CURSORINFO]) if (-not [OpenScreenCursorDiagnosticInterop]::GetCursorInfo([ref]$cursorInfo)) { Write-JsonLine @{ type = 'error'; timestampMs = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds(); message = 'GetCursorInfo failed' } Start-Sleep -Milliseconds ${SAMPLE_INTERVAL_MS} continue } $visible = ($cursorInfo.flags -band 1) -ne 0 $cursorId = if ($cursorInfo.hCursor -eq [IntPtr]::Zero) { $null } else { ('0x{0:X}' -f $cursorInfo.hCursor.ToInt64()) } $cursorType = Get-StandardCursorType $cursorInfo.hCursor $leftButtonState = [OpenScreenCursorDiagnosticInterop]::GetAsyncKeyState(0x01) $leftButtonDown = ($leftButtonState -band 0x8000) -ne 0 $leftButtonPressed = ($mouseEvents.LeftDownCount -gt 0) -or (($leftButtonState -band 0x0001) -ne 0) $leftButtonReleased = $mouseEvents.LeftUpCount -gt 0 $asset = $null if ($visible -and $cursorId -and $cursorId -ne $lastCursorId) { $asset = Get-CursorAsset -cursorHandle $cursorInfo.hCursor -cursorId $cursorId if ($asset -and $cursorType) { $asset.cursorType = $cursorType } elseif ($asset -and $asset.cursorType) { $cursorType = $asset.cursorType } $lastCursorId = $cursorId } Write-JsonLine @{ type = 'sample' timestampMs = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() x = $cursorInfo.ptScreenPos.X y = $cursorInfo.ptScreenPos.Y visible = $visible handle = $cursorId cursorType = $cursorType leftButtonDown = $leftButtonDown leftButtonPressed = $leftButtonPressed leftButtonReleased = $leftButtonReleased bounds = @{ x = $screenBounds.Left y = $screenBounds.Top width = $screenBounds.Width height = $screenBounds.Height } asset = $asset } Start-Sleep -Milliseconds ${SAMPLE_INTERVAL_MS} } `; } function buildMousePathScript(durationMs) { const stepMs = 120; const steps = Math.max(8, Math.floor(durationMs / stepMs)); return String.raw` $ErrorActionPreference = 'Stop' Add-Type -AssemblyName System.Windows.Forms $source = @" using System.Runtime.InteropServices; using System; public static class OpenScreenMouseDiagnosticInterop { [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetCursorPos(int X, int Y); [DllImport("user32.dll")] public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, UIntPtr dwExtraInfo); } "@ Add-Type -TypeDefinition $source $bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds $points = @() for ($i = 0; $i -lt ${steps}; $i++) { $t = if (${steps} -le 1) { 0 } else { $i / (${steps} - 1) } $x = [int]($bounds.Left + 80 + (($bounds.Width - 160) * $t)) $wave = [Math]::Sin($t * [Math]::PI * 2) $y = [int]($bounds.Top + ($bounds.Height / 2) + ($wave * [Math]::Min(180, $bounds.Height / 4))) $points += @{ x = $x; y = $y } } for ($i = 0; $i -lt $points.Count; $i++) { $point = $points[$i] [OpenScreenMouseDiagnosticInterop]::SetCursorPos($point.x, $point.y) | Out-Null if ($i -eq [int]([Math]::Floor($points.Count / 2))) { [OpenScreenMouseDiagnosticInterop]::mouse_event(0x0002, 0, 0, 0, [UIntPtr]::Zero) Start-Sleep -Milliseconds 12 [OpenScreenMouseDiagnosticInterop]::mouse_event(0x0004, 0, 0, 0, [UIntPtr]::Zero) } Start-Sleep -Milliseconds ${stepMs} } `; } function buildScreenRecorderScript(outputDir, durationMs) { const framesDir = path.join(outputDir, "screen-frames"); return String.raw` $ErrorActionPreference = 'Stop' Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName System.Windows.Forms $framesDir = ${quotePowerShellString(framesDir)} New-Item -ItemType Directory -Force -Path $framesDir | Out-Null $bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds $targetWidth = 960 $targetHeight = [int]([Math]::Round($targetWidth * ($bounds.Height / $bounds.Width))) $frames = New-Object System.Collections.Generic.List[object] $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $index = 0 while ($stopwatch.ElapsedMilliseconds -le ${durationMs + 700}) { $sourceBitmap = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height, ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb) $graphics = [System.Drawing.Graphics]::FromImage($sourceBitmap) $scaledBitmap = New-Object System.Drawing.Bitmap $targetWidth, $targetHeight, ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb) $scaledGraphics = [System.Drawing.Graphics]::FromImage($scaledBitmap) $timestampMs = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() $fileName = ('frame_{0:D4}.png' -f $index) $path = Join-Path $framesDir $fileName try { $graphics.CopyFromScreen($bounds.Left, $bounds.Top, 0, 0, $bounds.Size) $scaledGraphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic $scaledGraphics.DrawImage($sourceBitmap, 0, 0, $targetWidth, $targetHeight) $scaledBitmap.Save($path, [System.Drawing.Imaging.ImageFormat]::Png) $frames.Add(@{ index = $index timestampMs = $timestampMs path = $path width = $targetWidth height = $targetHeight bounds = @{ x = $bounds.Left y = $bounds.Top width = $bounds.Width height = $bounds.Height } }) | Out-Null } finally { $scaledGraphics.Dispose() $scaledBitmap.Dispose() $graphics.Dispose() $sourceBitmap.Dispose() } $index += 1 Start-Sleep -Milliseconds ${SCREEN_FRAME_INTERVAL_MS} } ($frames | ConvertTo-Json -Depth 6) | Set-Content -Path (Join-Path $framesDir 'frames.json') -Encoding UTF8 `; } function createReadyWaiter() { let settled = false; let resolveReady = null; const promise = new Promise((resolve, reject) => { const timer = setTimeout(() => { if (settled) { return; } settled = true; reject(new Error("Timed out waiting for cursor sampler readiness.")); }, READY_TIMEOUT_MS); resolveReady = () => { if (settled) { return; } settled = true; clearTimeout(timer); resolve(); }; }); return { promise, resolve: () => resolveReady?.(), }; } function writeAssets(assets, outputDir) { const assetDir = path.join(outputDir, "assets"); fs.mkdirSync(assetDir, { recursive: true }); for (const asset of assets.values()) { const base64 = asset.imageDataUrl?.replace(/^data:image\/png;base64,/, ""); if (!base64) { continue; } const safeId = String(asset.id).replace(/[^a-zA-Z0-9_-]/g, "_"); fs.writeFileSync(path.join(assetDir, `${safeId}.png`), Buffer.from(base64, "base64")); } } function toRecordingData(samples, assets) { const firstTimestampMs = samples[0]?.timestampMs ?? Date.now(); let previousLeftButtonDown = false; const normalizedSamples = samples.flatMap((sample) => { const bounds = sample.bounds; if (!bounds || bounds.width <= 0 || bounds.height <= 0) { return []; } const leftButtonDown = sample.leftButtonDown === true; const leftButtonPressed = sample.leftButtonPressed === true; const leftButtonReleased = sample.leftButtonReleased === true; const interactionType = leftButtonPressed || (leftButtonDown && !previousLeftButtonDown) ? "click" : leftButtonReleased || (!leftButtonDown && previousLeftButtonDown) ? "mouseup" : "move"; previousLeftButtonDown = leftButtonDown; return [ { timeMs: Math.max(0, sample.timestampMs - firstTimestampMs), cx: (sample.x - bounds.x) / bounds.width, cy: (sample.y - bounds.y) / bounds.height, assetId: sample.handle, visible: Boolean(sample.visible), cursorType: sample.cursorType ?? null, interactionType, }, ]; }); return { version: 2, provider: assets.size > 0 ? "native" : "none", samples: normalizedSamples, assets: [...assets.values()].map((asset) => ({ id: asset.id, platform: "win32", imageDataUrl: asset.imageDataUrl, width: asset.width, height: asset.height, hotspotX: asset.hotspotX, hotspotY: asset.hotspotY, scaleFactor: 1, cursorType: asset.cursorType ?? null, })), }; } function escapeScriptJson(value) { return JSON.stringify(value).replace(/ OpenScreen native cursor diagnostic

OpenScreen native cursor diagnostic

${report.sampleCount}samples
${report.assetCount}assets
${report.uniquePositionCount}positions
${report.errorCount}errors

The red cross is the captured native hotspot. Native bitmaps are drawn at 1x, 2x, and 3x. The last cursor is a crisp vector 3x replacement anchored on the same hotspot.

`; } function readScreenFrames(outputDir, recordingStartTimestampMs) { const framesJsonPath = path.join(outputDir, "screen-frames", "frames.json"); if (!fs.existsSync(framesJsonPath)) { return []; } const rawFrames = JSON.parse(fs.readFileSync(framesJsonPath, "utf8").replace(/^\uFEFF/, "")); const frames = Array.isArray(rawFrames) ? rawFrames : [rawFrames]; return frames .filter((frame) => frame?.path && fs.existsSync(frame.path)) .map((frame) => ({ ...frame, timeMs: Math.max(0, frame.timestampMs - recordingStartTimestampMs), imageDataUrl: `data:image/png;base64,${fs.readFileSync(frame.path).toString("base64")}`, })); } function buildRealCaptureHtml(report, recordingData, screenFrames) { return ` OpenScreen native cursor real capture diagnostic

Real screen capture + reconstructed native cursor

Background frames are real Windows screenshots. Native bitmaps are reconstructed at 1x, 2x, and 3x; the last cursor is a crisp vector 3x replacement. The red cross marks the recorded hotspot.

`; } function findPlaywrightChromiumExecutable(defaultPath) { if (fs.existsSync(defaultPath)) { return defaultPath; } const baseDir = path.join(process.env.LOCALAPPDATA ?? "", "ms-playwright"); if (!baseDir || !fs.existsSync(baseDir)) { return defaultPath; } const candidates = fs .readdirSync(baseDir, { withFileTypes: true }) .filter((entry) => entry.isDirectory() && entry.name.startsWith("chromium-")) .map((entry) => ({ executablePath: path.join(baseDir, entry.name, "chrome-win64", "chrome.exe"), revision: Number.parseInt(entry.name.slice("chromium-".length), 10), })) .filter( (candidate) => Number.isFinite(candidate.revision) && fs.existsSync(candidate.executablePath), ) .sort((a, b) => b.revision - a.revision) .map((candidate) => candidate.executablePath); return candidates[0] ?? defaultPath; } async function writePreviewVideo(reportPath, outputPath) { const { chromium } = await import("playwright"); const browser = await chromium.launch({ executablePath: findPlaywrightChromiumExecutable(chromium.executablePath()), headless: true, }); try { const page = await browser.newPage({ viewport: { width: 1180, height: 760 } }); await page.goto(`file:///${reportPath.replaceAll("\\", "/")}`); const base64 = await page.evaluate(() => window.__exportWebm()); fs.writeFileSync(outputPath, Buffer.from(base64, "base64")); } finally { await browser.close(); } } function assertReport(report) { const failures = []; if (report.sampleCount < Math.floor(DURATION_MS / SAMPLE_INTERVAL_MS / 3)) { failures.push(`Too few samples: ${report.sampleCount}.`); } if (report.visibleSampleCount === 0) { failures.push("No visible cursor samples were captured."); } if (report.assetCount === 0) { failures.push("No cursor asset PNG was captured."); } if (report.uniquePositionCount < 4) { failures.push(`Cursor movement was not observed enough times: ${report.uniquePositionCount}.`); } if (report.errorCount > 0) { failures.push(`Sampler reported ${report.errorCount} error event(s).`); } if (report.leftButtonPressedSampleCount === 0 || report.clickSampleCount === 0) { failures.push("Left button click interaction was not observed."); } if (failures.length > 0) { throw new Error(failures.join(" ")); } } fs.mkdirSync(OUTPUT_DIR, { recursive: true }); const events = []; const assets = new Map(); let lineBuffer = ""; let stoppingSampler = false; const readyWaiter = createReadyWaiter(); const sampler = spawnPowerShell(buildSamplerScript(), { onStdout: (chunk) => { lineBuffer += chunk; const lines = lineBuffer.split(/\r?\n/); lineBuffer = lines.pop() ?? ""; for (const line of lines) { const trimmed = line.trim(); if (!trimmed) { continue; } let event; try { event = JSON.parse(trimmed); } catch { process.stderr.write(`[cursor-native-test] dropping non-JSON line: ${trimmed}\n`); continue; } events.push(event); if (event.type === "ready") { readyWaiter.resolve(); } if (event.asset?.id && !assets.has(event.asset.id)) { assets.set(event.asset.id, event.asset); } } }, onStderr: (chunk) => { if (!stoppingSampler && !chunk.startsWith("#< CLIXML")) { process.stderr.write(`[cursor-native-test] ${chunk}`); } }, }); let screenRecorder = null; try { await readyWaiter.promise; screenRecorder = spawnPowerShell(buildScreenRecorderScript(OUTPUT_DIR, DURATION_MS), { onStderr: (chunk) => { if (!chunk.startsWith("#< CLIXML") && !chunk.startsWith(" setTimeout(resolve, 150)); await runPowerShell(buildMousePathScript(DURATION_MS)); await new Promise((resolve) => setTimeout(resolve, Math.max(250, SAMPLE_INTERVAL_MS * 3))); await screenRecorder.done; } finally { if (!sampler.child.killed) { stoppingSampler = true; sampler.child.kill(); } if (screenRecorder && !screenRecorder.child.killed) { screenRecorder.child.kill(); } } const samples = events.filter((event) => event.type === "sample"); const errors = events.filter((event) => event.type === "error"); const recordingStartTimestampMs = samples[0]?.timestampMs ?? Date.now(); const uniquePositions = new Set(samples.map((sample) => `${sample.x},${sample.y}`)); let previousLeftButtonDown = false; let clickSampleCount = 0; for (const sample of samples) { const leftButtonDown = sample.leftButtonDown === true; const leftButtonPressed = sample.leftButtonPressed === true; if (leftButtonPressed || (leftButtonDown && !previousLeftButtonDown)) { clickSampleCount += 1; } previousLeftButtonDown = leftButtonDown; } const report = { outputDir: OUTPUT_DIR, sampleIntervalMs: SAMPLE_INTERVAL_MS, durationMs: DURATION_MS, eventCount: events.length, sampleCount: samples.length, visibleSampleCount: samples.filter((sample) => sample.visible).length, assetCount: assets.size, uniqueCursorHandleCount: new Set(samples.map((sample) => sample.handle).filter(Boolean)).size, uniquePositionCount: uniquePositions.size, leftButtonDownSampleCount: samples.filter((sample) => sample.leftButtonDown === true).length, leftButtonPressedSampleCount: samples.filter((sample) => sample.leftButtonPressed === true) .length, clickSampleCount, errorCount: errors.length, firstSample: samples[0] ?? null, lastSample: samples.at(-1) ?? null, assets: [...assets.values()].map((asset) => ({ id: asset.id, width: asset.width, height: asset.height, hotspotX: asset.hotspotX, hotspotY: asset.hotspotY, cursorType: asset.cursorType ?? null, })), }; const recordingData = toRecordingData(samples, assets); const screenFrames = readScreenFrames(OUTPUT_DIR, recordingStartTimestampMs); const reportHtmlPath = path.join(OUTPUT_DIR, "report.html"); const previewVideoPath = path.join(OUTPUT_DIR, "preview.webm"); const realCaptureHtmlPath = path.join(OUTPUT_DIR, "real-capture-report.html"); const realCaptureVideoPath = path.join(OUTPUT_DIR, "real-capture-preview.webm"); writeAssets(assets, OUTPUT_DIR); fs.writeFileSync(path.join(OUTPUT_DIR, "events.json"), JSON.stringify(events, null, 2)); fs.writeFileSync( path.join(OUTPUT_DIR, "cursor-recording-data.json"), JSON.stringify(recordingData, null, 2), ); fs.writeFileSync(path.join(OUTPUT_DIR, "report.json"), JSON.stringify(report, null, 2)); fs.writeFileSync(reportHtmlPath, buildVisualReportHtml(report, recordingData)); if (screenFrames.length > 0) { fs.writeFileSync(realCaptureHtmlPath, buildRealCaptureHtml(report, recordingData, screenFrames)); report.screenFrameCount = screenFrames.length; } try { await writePreviewVideo(reportHtmlPath, previewVideoPath); report.previewVideoPath = previewVideoPath; } catch (error) { report.previewVideoError = error instanceof Error ? error.message : String(error); } if (screenFrames.length > 0) { try { await writePreviewVideo(realCaptureHtmlPath, realCaptureVideoPath); report.realCaptureVideoPath = realCaptureVideoPath; } catch (error) { report.realCaptureVideoError = error instanceof Error ? error.message : String(error); } } fs.writeFileSync(path.join(OUTPUT_DIR, "report.json"), JSON.stringify(report, null, 2)); assertReport(report); console.log(JSON.stringify(report, null, 2));