feat(#283): Windows dev orchestrator (setup-displayxr.bat) + README quick-install promotion#291
Conversation
Both released a couple of hours after PR #291 was opened. Bumping so the first Windows e2e test exercises the freshest stack. runtime: v1.4.0 -> v1.4.1 (DisplayXRSetup-1.4.1.929.exe / DisplayXR-Installer-1.4.1.pkg) leia_plugin: v1.0.1 -> v1.0.2 (DisplayXRLeiaSRSetup-1.0.2.12.exe) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Windows e2e test plan — for the Windows-device agentThis branch ( Pinned in
Prereqswhere gh :: must resolve
gh auth status :: must say "Logged in to github.com"If either fails: Step 0 — Wipe to a clean slate (optional but ideal)Use Apps & Features (or Step 1 — Fetch + dry-run (non-elevated cmd is fine)
git fetch origin
git checkout feature/windows-dev-orchestrator
git pull
scripts\setup-displayxr.bat --help
scripts\setup-displayxr.bat --dry-run
scripts\setup-displayxr.bat --dry-run --with mcp --with-demosExpected from plain Red flags to capture:
Step 2 — Real install (ELEVATED cmd)Right-click cmd.exe → "Run as administrator" → scripts\setup-displayxr.batExpected output: Step 3 — Verify the install:: Registry markers
reg query "HKLM\Software\DisplayXR\Runtime" /v InstallPath
reg query "HKLM\Software\DisplayXR\WorkspaceControllers\shell"
reg query "HKLM\Software\DisplayXR\DisplayProcessors\leia-sr"
reg query "HKLM\Software\Khronos\OpenXR\1" /v ActiveRuntime
:: File layout
dir "C:\Program Files\DisplayXR\Runtime"
:: Expect: DisplayXRClient.dll, displayxr-service.exe, displayxr-shell.exe,
:: Uninstall.exe, plus a plugins\ subtree containing the Leia DLL.
:: Test app launched via the shell against the SYSTEM runtime
:: (Build test apps first if not already: scripts\build_windows.bat test-apps)
"C:\Program Files\DisplayXR\Runtime\displayxr-shell.exe" test_apps\cube_handle_d3d11_win\build\cube_handle_d3d11_win.exeThen open the latest log under Step 4 —
|
Re-running scripts\setup-displayxr.bat against the v1.4.1 / v1.2.5 / v1.0.2 stack surfaced three cmd.exe / NSIS quoting hazards that the prior cmd-correctness-only review couldn't catch: 1. read_pin: `Get-Content -Raw 'PATH WITH SPACES' | ConvertFrom-Json` inside `for /f usebackq` re-quotes the path such that PowerShell sees the space-split tokens as positional args after -Raw, fails to bind, and the whole pipeline aborts. Every pin reads as empty. Fix: function-call form `ConvertFrom-Json (Get-Content -Raw 'PATH')` avoids the pipe. 2. asset-attached check: `echo %%i | findstr /r ...^.exe$` — cmd's `echo X | cmd` form injects a trailing space before the pipe, so the `$` anchor never matches a real .exe asset. Every component ends up reported as "no Windows .exe asset attached". Fix: switch to a delayed-expansion substring check `if /i "!line:~-4!"==".exe"`. 3. uninstall filter: `Publisher -eq 'DisplayXR'` misses the Leia SR plug-in (its installer sets Publisher='Leia Inc.'), so --uninstall would silently leave the plug-in stranded. Fix: also match `DisplayName -like 'DisplayXR *'`. End-to-end transcripts on the v1.4.1 stack now match the expected output in the issue, including --with mcp opt-in and a clean --uninstall round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Windows e2e test results (dfattal's dev box, Windows 11 26200.8246)Ran the orchestrator end-to-end against the pinned Fixed in 0ad2b1d (this branch) and DisplayXR/displayxr-mcp#3. After the fixes, all five steps in the test plan match the expected output. Bugs found + fixed
Step 1 —
|
The preflight `net session` elevation check was gated on `ACTION=install`, so `--uninstall` would silently skip it. But --uninstall runs each component's QuietUninstallString, which the NSIS uninstallers execute against HKLM + Program Files — needs admin just like install. Without elevation the cmd /c $u calls either prompted UAC (interactive) or partial-failed silently. Drop the ACTION=install qualifier; the guard now fires for both real install and real uninstall, and still no-ops on --dry-run (which does zero privileged work). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`mcp_conn_close` calls `close(fd)` only — that wakes a blocked
`read()` in another thread on macOS but not on Linux. POSIX leaves
this undefined; Linux keeps the underlying socket alive until the
in-flight syscall returns, so close() from one thread is a no-op
for a read() blocked in another thread.
This is why the ctest smoke test has hung on Linux CI ever since
it was added (v0.2.3, 2026-05-04) while always passing on macOS in
~13s. Trace of the hang in the adapter:
1. parent fcloses pipe → adapter's stdin EOF
2. adapter's `up` pump thread exits cleanly
3. main: pthread_join(t_up) → mcp_conn_close(conn)
4. main: close(conn->fd) returns immediately, but the kernel
does NOT unblock the `down` thread's read()
5. main: pthread_join(t_down) → hangs forever
6. adapter never exits → parent's waitpid hangs → CI timeout
`mcp_listener_close` already does the right thing
(`shutdown(SHUT_RDWR)` before `close()`, with a comment explaining
why for the listener's accept()). Apply the same pattern to
`mcp_conn_close` so it works symmetrically for read()/write().
shutdown(SHUT_RDWR) on a stream socket sends FIN to the peer (so
the server's read() in serve() also unblocks with EOF) AND wakes
the local fd's pending I/O syscalls. Works on AF_UNIX the same as
AF_INET. Behaves identically on macOS, so no regression there.
Surfaced by DisplayXR/displayxr-runtime#291 (Windows orchestrator
e2e test) while investigating why MCP PR #3's CI was failing on
Ubuntu.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tall After the runtime orchestrator's `--uninstall` runs every DisplayXR-published uninstaller, an empty `HKLM\Software\DisplayXR\ Capabilities` parent was left behind because the MCP uninstaller deleted `Capabilities\MCP` but not the parent. That cascade also prevents the runtime uninstaller's existing `DeleteRegKey /ifempty HKLM "Software\DisplayXR"` at the end of its own uninstall section from succeeding — the empty Capabilities subkey keeps the parent alive, so `reg query HKLM\Software\DisplayXR` returns success (with one empty subkey) instead of the expected "key not found". The original comment "Don't DeleteRegKey /ifempty on Capabilities itself or the parent — runtime / future Capabilities\<name> entries may still own siblings" was based on a misreading of NSIS semantics: `/ifempty` is precisely the safe form — it's a no-op when subkeys still exist, so siblings are protected by construction. Surfaced by issue DisplayXR/displayxr-runtime#291 (Windows orchestrator e2e test). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tall After the runtime orchestrator's `--uninstall` runs every DisplayXR-published uninstaller, an empty `HKLM\Software\DisplayXR\ Capabilities` parent was left behind because the MCP uninstaller deleted `Capabilities\MCP` but not the parent. That cascade also prevents the runtime uninstaller's existing `DeleteRegKey /ifempty HKLM "Software\DisplayXR"` at the end of its own uninstall section from succeeding — the empty Capabilities subkey keeps the parent alive, so `reg query HKLM\Software\DisplayXR` returns success (with one empty subkey) instead of the expected "key not found". The original comment "Don't DeleteRegKey /ifempty on Capabilities itself or the parent — runtime / future Capabilities\<name> entries may still own siblings" was based on a misreading of NSIS semantics: `/ifempty` is precisely the safe form — it's a no-op when subkeys still exist, so siblings are protected by construction. Surfaced by issue DisplayXR/displayxr-runtime#291 (Windows orchestrator e2e test). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors scripts/setup-displayxr.sh on Windows via scripts/setup-displayxr.bat. Same flag surface (--with mcp, --with-demos, --dry-run, --uninstall, --help), same default-install semantics (warn-and-skip when a pinned tag has no asset for the platform). Default install set on Windows is runtime + Shell + Leia plug-in (all three have shipping .exe installers). MCP Tools stays opt-in via --with mcp. Mac default remains runtime-only (Shell + Leia have no macOS port; Leia is Windows-only by design). Pin bumps: leia_plugin: sr-sdk-v1.35.0.2011 -> v1.0.1 v1.0.0 was the first user-facing Leia plug-in release (2026-05-22); v1.0.1 (2026-05-23) is the latest. Both ship DisplayXRLeiaSRSetup-*.exe. components.sh gains COMPONENT_INSTALL_MARKER_WINDOWS_* (registry key paths) parallel to the existing MACOS markers. The .bat mirrors the table inline at its top because batch can't source bash; a header note in components.sh flags this. README "Quick Start" now leads with parallel macOS + Windows one-liners. The manual 4-installer flow is demoted to a "Manual install" subsection below, restructured as a per-component / per-platform table that also covers the Leia plug-in (which the prior "3 installers" listing omitted). docs/getting-started/full-stack-install.md extends to cover both platforms: per-OS prereqs, per-OS verification, per-OS uninstall flow (Windows uses a Publisher=DisplayXR scan of HKLM\...\Uninstall\*), and flips every "Windows today" cell in the availability matrix. Windows uninstall semantics: scan HKLM\Software\Microsoft\Windows\ CurrentVersion\Uninstall\* (+ WOW6432Node) for entries where Publisher=DisplayXR, run each QuietUninstallString (falling back to UninstallString /S). This catches runtime, Shell, Leia plug-in, and MCP Tools in a single pass regardless of exact registry subkey names. E2E verification still needs a Windows machine; bash-side smoke tests re-confirm the macOS path is unbroken by the components.sh / README edits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both released a couple of hours after PR #291 was opened. Bumping so the first Windows e2e test exercises the freshest stack. runtime: v1.4.0 -> v1.4.1 (DisplayXRSetup-1.4.1.929.exe / DisplayXR-Installer-1.4.1.pkg) leia_plugin: v1.0.1 -> v1.0.2 (DisplayXRLeiaSRSetup-1.0.2.12.exe) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-running scripts\setup-displayxr.bat against the v1.4.1 / v1.2.5 / v1.0.2 stack surfaced three cmd.exe / NSIS quoting hazards that the prior cmd-correctness-only review couldn't catch: 1. read_pin: `Get-Content -Raw 'PATH WITH SPACES' | ConvertFrom-Json` inside `for /f usebackq` re-quotes the path such that PowerShell sees the space-split tokens as positional args after -Raw, fails to bind, and the whole pipeline aborts. Every pin reads as empty. Fix: function-call form `ConvertFrom-Json (Get-Content -Raw 'PATH')` avoids the pipe. 2. asset-attached check: `echo %%i | findstr /r ...^.exe$` — cmd's `echo X | cmd` form injects a trailing space before the pipe, so the `$` anchor never matches a real .exe asset. Every component ends up reported as "no Windows .exe asset attached". Fix: switch to a delayed-expansion substring check `if /i "!line:~-4!"==".exe"`. 3. uninstall filter: `Publisher -eq 'DisplayXR'` misses the Leia SR plug-in (its installer sets Publisher='Leia Inc.'), so --uninstall would silently leave the plug-in stranded. Fix: also match `DisplayName -like 'DisplayXR *'`. End-to-end transcripts on the v1.4.1 stack now match the expected output in the issue, including --with mcp opt-in and a clean --uninstall round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The preflight `net session` elevation check was gated on `ACTION=install`, so `--uninstall` would silently skip it. But --uninstall runs each component's QuietUninstallString, which the NSIS uninstallers execute against HKLM + Program Files — needs admin just like install. Without elevation the cmd /c $u calls either prompted UAC (interactive) or partial-failed silently. Drop the ACTION=install qualifier; the guard now fires for both real install and real uninstall, and still no-ops on --dry-run (which does zero privileged work). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fb902f6 to
ace8b93
Compare
Both released a couple of hours after PR #291 was opened. Bumping so the first Windows e2e test exercises the freshest stack. runtime: v1.4.0 -> v1.4.1 (DisplayXRSetup-1.4.1.929.exe / DisplayXR-Installer-1.4.1.pkg) leia_plugin: v1.0.1 -> v1.0.2 (DisplayXRLeiaSRSetup-1.0.2.12.exe) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-running scripts\setup-displayxr.bat against the v1.4.1 / v1.2.5 / v1.0.2 stack surfaced three cmd.exe / NSIS quoting hazards that the prior cmd-correctness-only review couldn't catch: 1. read_pin: `Get-Content -Raw 'PATH WITH SPACES' | ConvertFrom-Json` inside `for /f usebackq` re-quotes the path such that PowerShell sees the space-split tokens as positional args after -Raw, fails to bind, and the whole pipeline aborts. Every pin reads as empty. Fix: function-call form `ConvertFrom-Json (Get-Content -Raw 'PATH')` avoids the pipe. 2. asset-attached check: `echo %%i | findstr /r ...^.exe$` — cmd's `echo X | cmd` form injects a trailing space before the pipe, so the `$` anchor never matches a real .exe asset. Every component ends up reported as "no Windows .exe asset attached". Fix: switch to a delayed-expansion substring check `if /i "!line:~-4!"==".exe"`. 3. uninstall filter: `Publisher -eq 'DisplayXR'` misses the Leia SR plug-in (its installer sets Publisher='Leia Inc.'), so --uninstall would silently leave the plug-in stranded. Fix: also match `DisplayName -like 'DisplayXR *'`. End-to-end transcripts on the v1.4.1 stack now match the expected output in the issue, including --with mcp opt-in and a clean --uninstall round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up: - CI(#290) attaches release artifacts via softprops/action-gh-release - ci: expose GH_TOKEN env to BuildInstaller for release attach (6257132) No installer behavior change vs v1.4.1; the orchestrator's e2e transcripts on PR #291 stay valid against v1.4.2 (NSIS /S path unchanged, registry layout unchanged). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Post-merge follow-upsPR shipped to main as 77df574. Side-effects + cleanup: Done after merge
Open follow-ups — heads-up for other agentsDocs / website coordination. The orchestrator is dev-facing (script-based). #284 is the end-user meta-installer (single
Recommendation: do not update the website now; wait for #284 to land. Website copy will need to ship the unsigned-bundle bypass instructions from #284's body (one-time SmartScreen / Gatekeeper prompt), so it's better to update once when #284 ships than to update twice. The dev-facing README + install doc are already updated by this PR. Latent items not addressed here
|
Summary
Mirrors
scripts/setup-displayxr.shon Windows viascripts/setup-displayxr.bat. Same flag surface (--with mcp,--with-demos,--dry-run,--uninstall,--help), same warn-and-skip semantics when a pinned tag has no asset for the platform.Default install set on Windows is runtime + Shell + Leia plug-in (all three have shipping
.exeinstallers as of today). MCP Tools stays opt-in via--with mcp. macOS default remains runtime-only (Shell + Leia have no macOS port; Leia is Windows-only by design).Closes the Windows half of #283.
Pin bumps
runtimev1.4.0v1.4.1(first release with the macOS .pkg + theDisplayXRSetup-1.4.1.929.exeWindows installer)leia_pluginsr-sdk-v1.35.0.2011(CI build dependency, not a user installer)v1.0.2(latest user-facing release:DisplayXRLeiaSRSetup-1.0.2.12.exe)displayxr-leia-pluginv1.0.0 (2026-05-22) was the first user-facing Leia plug-in release; v1.0.2 (2026-05-23) is the latest. All shipDisplayXRLeiaSRSetup-*.exe.Files
scripts/setup-displayxr.bat(new, ~340 lines) — Windows orchestrator. PowerShell shell-out forversions.jsonparsing;gh release download+start /wait "" "installer.exe" /Sper component; registry-key verification (HKLM\Software\DisplayXR\...).scripts/lib/components.sh— gainsCOMPONENT_INSTALL_MARKER_WINDOWS_*(registry key paths) parallel to existingMACOSmarkers. The.batmirrors the table inline at its top because batch can't source bash; a header note incomponents.shflags this.versions.json— pinned at runtimev1.4.1, leia_pluginv1.0.2.README.md— "Quick Start" now leads with parallel macOS + Windows one-liners. The manual 4-installer flow is demoted to a "Manual install" subsection below, restructured as a per-component / per-platform table that also covers the Leia plug-in (which the prior "3 installers" listing omitted).docs/getting-started/full-stack-install.md— extends to cover both platforms: per-OS prereqs, per-OS verification, per-OS uninstall flow, and flips every "Windows today" cell in the availability matrix.Platform availability matrix (post-PR)
DisplayXR-Installer-*.pkg)DisplayXRSetup-*.exe)DisplayXRShellSetup-*.exe)DisplayXRLeiaSRSetup-*.exe)DisplayXRMCPSetup-*.exe)Windows uninstall design
--uninstallscansHKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\*(plus the WOW6432Node mirror) for entries withPublisher=DisplayXRor DisplayName prefixedDisplayXR(catches Leia plug-in, which sets Publisher='Leia Inc.'), then runs each one'sQuietUninstallString(falling back toUninstallString /S). One pass covers runtime, Shell, Leia plug-in, and MCP Tools regardless of exact registry subkey names or publisher field.Bugs surfaced during E2E test (fixed in this PR)
Running the script against the real v1.4.1 / v1.2.5 / v1.0.2 / v0.3.1 stack — i.e. an actual repo path with spaces + the real release asset list — surfaced four pitfalls the prior cmd-correctness review couldn't have caught. Full transcripts in comment #4525971499. One-line summaries:
read_pin:Get-Content -Raw 'PATH WITH SPACES' | ConvertFrom-Jsoninsidefor /f usebackqbreaks PowerShell parsing. Switched to function-call form.echo X | findstr ...$injects a trailing space, breaking the$anchor. Switched to a:~-4substring check.Publisher -eq 'DisplayXR'misses the Leia plug-in. Also matchDisplayName -like 'DisplayXR *'.ACTION=installonly;--uninstallrunsQuietUninstallStringcommands that hit HKLM + Program Files too. Gate now fires for both real install and real uninstall, still no-ops on--dry-run.Cross-repo
DisplayXR/displayxr-mcp#3— the MCP installer's uninstaller left an emptyCapabilitiesparent key behind, preventing the runtime uninstaller's existingDeleteRegKey /ifempty HKLM "Software\DisplayXR"cleanup. PR addsDeleteRegKey /ifemptyon the parent keys. Lands separately on the next MCP release; cosmetic until then (reg query HKLM\Software\DisplayXRreturns success-with-empty-subkey instead of "key not found" — every functional registry trace is gone).Test plan
./scripts/setup-displayxr.sh --dry-runstill shows runtime download cleanly.python3 -m json.tool versions.jsonclean.bash -nclean on.shandcomponents.sh..batresolve to defined labels.if errorlevelchecks inside parenthesized blocks use!errorlevel!(delayed expansion).--with mcp, full--uninstallround-trip → reinstall). Surfaced + fixed the four bugs above. Transcript: comment #4525971499.🤖 Generated with Claude Code