zsui 0.3.0-alpha.2

Rust-first feature-gated native UI framework for Win32, AppKit, and Linux.
Documentation
param(
    [switch]$Locked
)

$ErrorActionPreference = "Stop"
$workspace = Split-Path -Parent $PSScriptRoot
$cargoArguments = @(
    "build",
    "--example", "native_smoke_run",
    "--no-default-features",
    "--features", "windows-win32,progress,label,native-smoke,accessibility"
)
if ($Locked) {
    $cargoArguments = @("build", "--locked") + $cargoArguments[1..($cargoArguments.Length - 1)]
}

Push-Location $workspace
try {
    & cargo @cargoArguments
    if ($LASTEXITCODE -ne 0) {
        throw "failed to build the ProgressBar accessibility probe application"
    }
} finally {
    Pop-Location
}

$targetRoot = if ([string]::IsNullOrWhiteSpace($env:CARGO_TARGET_DIR)) {
    Join-Path $workspace "target"
} elseif ([System.IO.Path]::IsPathRooted($env:CARGO_TARGET_DIR)) {
    $env:CARGO_TARGET_DIR
} else {
    Join-Path $workspace $env:CARGO_TARGET_DIR
}
$probe = Join-Path $targetRoot "debug\examples\native_smoke_run.exe"
if (-not (Test-Path -LiteralPath $probe -PathType Leaf)) {
    throw "ProgressBar accessibility probe executable was not produced: $probe"
}

Add-Type -AssemblyName UIAutomationClient
Add-Type -AssemblyName UIAutomationTypes
Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
using System.Text;

public static class ZsuiWindowsProgressAccessibilityProbeNative
{
    private delegate bool EnumWindowsCallback(IntPtr hwnd, IntPtr lparam);

    [DllImport("user32.dll")]
    private static extern bool EnumWindows(EnumWindowsCallback callback, IntPtr lparam);

    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hwnd, out uint processId);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern int GetClassName(IntPtr hwnd, StringBuilder className, int capacity);

    public static IntPtr FindMainWindow(uint expectedProcessId)
    {
        IntPtr result = IntPtr.Zero;
        EnumWindows((hwnd, _) =>
        {
            uint processId;
            GetWindowThreadProcessId(hwnd, out processId);
            if (processId != expectedProcessId)
            {
                return true;
            }

            StringBuilder className = new StringBuilder(256);
            GetClassName(hwnd, className, className.Capacity);
            if (className.ToString() != "ZsuiMainWindow")
            {
                return true;
            }

            result = hwnd;
            return false;
        }, IntPtr.Zero);
        return result;
    }
}
'@

$previousDuration = $env:ZSUI_NATIVE_PROOF_DURATION_MS
$env:ZSUI_NATIVE_PROOF_DURATION_MS = "5000"
$proofRoot = Join-Path $targetRoot "windows-progress-accessibility-proof"
$process = Start-Process -FilePath $probe -ArgumentList @(
    "windows",
    $proofRoot,
    "--progress-view"
) -PassThru -WindowStyle Hidden
try {
    $hwnd = [IntPtr]::Zero
    for ($attempt = 0; $attempt -lt 80 -and $hwnd -eq [IntPtr]::Zero; $attempt++) {
        Start-Sleep -Milliseconds 50
        if ($process.HasExited) {
            throw "ProgressBar accessibility probe exited before creating its main window"
        }
        $hwnd = [ZsuiWindowsProgressAccessibilityProbeNative]::FindMainWindow([uint32]$process.Id)
    }
    if ($hwnd -eq [IntPtr]::Zero) {
        throw "ProgressBar accessibility probe did not create a ZsuiMainWindow HWND"
    }

    $condition = New-Object System.Windows.Automation.PropertyCondition(
        [System.Windows.Automation.AutomationElement]::ControlTypeProperty,
        [System.Windows.Automation.ControlType]::ProgressBar
    )
    $elements = $null
    for ($attempt = 0; $attempt -lt 80; $attempt++) {
        $root = [System.Windows.Automation.AutomationElement]::FromHandle($hwnd)
        if ($null -ne $root) {
            $candidate = $root.FindAll(
                [System.Windows.Automation.TreeScope]::Descendants,
                $condition
            )
            if ($candidate.Count -eq 4) {
                $elements = $candidate
                break
            }
            if ($candidate.Count -gt 4) {
                throw "expected four ProgressBar elements, found $($candidate.Count)"
            }
        }
        Start-Sleep -Milliseconds 50
    }
    if ($null -eq $elements) {
        throw "UI Automation did not expose four ProgressBar elements before timeout"
    }

    $expectedValues = @(65.0, 45.0, 30.0)
    $results = @()
    for ($index = 0; $index -lt $elements.Count; $index++) {
        $element = $elements.Item($index)
        if ($element.Current.FrameworkId -ne "ZSUI") {
            throw "ProgressBar $index framework id was '$($element.Current.FrameworkId)'"
        }
        $rangeObject = $null
        $hasRange = $element.TryGetCurrentPattern(
            [System.Windows.Automation.RangeValuePattern]::Pattern,
            [ref]$rangeObject
        )
        if ($index -lt $expectedValues.Count) {
            if (-not $hasRange) {
                throw "determinate ProgressBar $index did not expose RangeValuePattern"
            }
            $range = [System.Windows.Automation.RangeValuePattern]$rangeObject
            if ($range.Current.Value -ne $expectedValues[$index] -or
                $range.Current.Minimum -ne 0.0 -or
                $range.Current.Maximum -ne 100.0 -or
                -not $range.Current.IsReadOnly) {
                throw "determinate ProgressBar $index exposed an invalid read-only range"
            }
            $results += [ordered]@{
                automation_id = $element.Current.AutomationId
                value = $range.Current.Value
                minimum = $range.Current.Minimum
                maximum = $range.Current.Maximum
                read_only = $range.Current.IsReadOnly
            }
        } elseif ($hasRange) {
            throw "indeterminate ProgressBar unexpectedly exposed RangeValuePattern"
        }
    }

    $proof = [ordered]@{
        platform = "windows"
        backend = "UI Automation"
        control_type = "ProgressBar"
        determinate = $results
        indeterminate_has_range = $false
        errors = @()
    }
    New-Item -ItemType Directory -Force -Path $proofRoot | Out-Null
    $proof | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $proofRoot "proof.json") -Encoding utf8
    if (-not $process.WaitForExit(10000)) {
        throw "ProgressBar accessibility probe did not finish after its native proof window closed"
    }
    if ($process.ExitCode -ne 0) {
        throw "ProgressBar accessibility probe exited with code $($process.ExitCode)"
    }
    $screenshot = Join-Path $proofRoot "windows\window.png"
    if (-not (Test-Path -LiteralPath $screenshot -PathType Leaf)) {
        throw "ProgressBar native proof did not produce the buffered Win32 screenshot"
    }
    Write-Output "Windows ProgressBar accessibility passed: four UIA ProgressBar nodes -> three read-only ranges plus one indeterminate role"
} finally {
    if (-not $process.HasExited) {
        Stop-Process -Id $process.Id -Force
    }
    $env:ZSUI_NATIVE_PROOF_DURATION_MS = $previousDuration
}