From 237a4707eb4a08dc06870943bd088a10042103c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 13:58:45 +0000 Subject: [PATCH] Packaging: Windows MSI setup wizard (folder choice, progress, version, launch on finish) Double-clicking the MSI now opens WixUI_InstallDir without the licence page: Welcome, install folder (remembered for upgrades in HKLM\Software\PhotoCraft\InstallDir), Ready, progress, and a Finish page with a ticked "Launch PhotoCraft" box (WixShellExec, unelevated). The pages name the full version, pre-release tag included (photocraft.en-us.wxl). package.ps1 draws the banner and side bitmaps from the app icon, so no WiX stock art ships. msiexec /qn still installs silently. The release and ARM64 workflows install the WiX UI and Util extensions; packaging-lint checks the .wxl. Ported from VectorCraft (storytold/vectorcraft@aa548d2). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AP7ThRN3kpNmjsuJUerJui --- .github/workflows/packaging-lint.yml | 2 +- .github/workflows/release.yml | 4 ++- .github/workflows/windows-arm64.yml | 4 ++- docs/releasing.md | 10 +++++- packaging/windows/package.ps1 | 43 +++++++++++++++++++++- packaging/windows/photocraft.en-us.wxl | 11 ++++++ packaging/windows/photocraft.wxs | 50 ++++++++++++++++++++++---- 7 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 packaging/windows/photocraft.en-us.wxl diff --git a/.github/workflows/packaging-lint.yml b/.github/workflows/packaging-lint.yml index 37178444..19dfb339 100644 --- a/.github/workflows/packaging-lint.yml +++ b/.github/workflows/packaging-lint.yml @@ -49,7 +49,7 @@ jobs: ./packaging/windows/check-icons.ps1 - name: XML, desktop entry, AppStream, Flatpak manifest run: | - xmllint --noout packaging/windows/photocraft.wxs packaging/macos/*.plist* packaging/linux/*.xml* + xmllint --noout packaging/windows/photocraft.wxs packaging/windows/photocraft.en-us.wxl packaging/macos/*.plist* packaging/linux/*.xml* desktop-file-validate packaging/linux/ai.storyteller.photocraft.desktop sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' packaging/linux/ai.storyteller.photocraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.photocraft.metainfo.xml" appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.photocraft.metainfo.xml" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f2479745..d3a12758 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -159,7 +159,9 @@ jobs: run: cargo xtask version set "$PHOTOCRAFT_VERSION" - name: Install WiX shell: pwsh - run: dotnet tool install --global wix --version 5.0.2 + run: | + dotnet tool install --global wix --version 5.0.2 + wix extension add -g WixToolset.UI.wixext/5.0.2 WixToolset.Util.wixext/5.0.2 - name: Build, sign, package shell: pwsh env: diff --git a/.github/workflows/windows-arm64.yml b/.github/workflows/windows-arm64.yml index c64b56df..47e45219 100644 --- a/.github/workflows/windows-arm64.yml +++ b/.github/workflows/windows-arm64.yml @@ -41,7 +41,9 @@ jobs: key: windows-arm64-package - name: Install WiX shell: pwsh - run: dotnet tool install --global wix --version 5.0.2 + run: | + dotnet tool install --global wix --version 5.0.2 + wix extension add -g WixToolset.UI.wixext/5.0.2 WixToolset.Util.wixext/5.0.2 - name: Build and package (unsigned) shell: pwsh env: diff --git a/docs/releasing.md b/docs/releasing.md index 3e67c7b5..5a023c4c 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -140,6 +140,13 @@ goes in `CARGO_TARGET__RUSTFLAGS`, so host build scripts aren't affected "Open with" for PSD/PSB and image files. It also registers App Paths (Win+R `photocraft`). The MSI version is the numeric `X.Y.Z`, because MSI has no pre-release field. Same-version upgrades are allowed so that release candidates replace each other. +- Double-clicking the MSI opens a setup wizard (WixUI_InstallDir without the licence page): + Welcome, install folder (remembered for upgrades in `HKLM\Software\PhotoCraft\InstallDir`), + Ready, a progress page, and a Finish page that says where to find the app, with a ticked "Launch + PhotoCraft" box. The pages name the full version (`photocraft.en-us.wxl`). `package.ps1` draws + the wizard's banner and side bitmaps from the app icon, so no WiX stock art ships. `msiexec /qn` + still installs silently. Building needs the WiX UI and Util extensions: + `wix extension add -g WixToolset.UI.wixext/5.0.2 WixToolset.Util.wixext/5.0.2`. - Shortcut icon identifiers keep the executable's `.exe` extension: MSI uses the identifier as the cached icon filename, and an extensionless filename can render as a blank document icon. `packaging/windows/check-icons.ps1` checks the references and extensions in CI; @@ -159,7 +166,8 @@ goes in `CARGO_TARGET__RUSTFLAGS`, so host build scripts aren't affected The script is the single place to change when the Windows signing setup changes. -Locally on Windows: `dotnet tool install -g wix --version 5.0.2`, then +Locally on Windows: `dotnet tool install -g wix --version 5.0.2`, +`wix extension add -g WixToolset.UI.wixext/5.0.2 WixToolset.Util.wixext/5.0.2`, then `pwsh packaging/windows/package.ps1 -Arch x64`. ### Linux diff --git a/packaging/windows/package.ps1 b/packaging/windows/package.ps1 index c91cdc35..53bf2865 100644 --- a/packaging/windows/package.ps1 +++ b/packaging/windows/package.ps1 @@ -13,6 +13,7 @@ Needs: Rust (MSVC toolchain + the target), the Windows SDK (rc.exe, signtool.exe), and WiX v5: dotnet tool install --global wix --version 5.0.2 + wix extension add -g WixToolset.UI.wixext/5.0.2 WixToolset.Util.wixext/5.0.2 .EXAMPLE pwsh packaging/windows/package.ps1 -Arch x64 @@ -91,11 +92,51 @@ Copy-Item (Join-Path $Bin 'photocraft.exe'), (Join-Path $Bin 'photocraft-cli.exe & (Join-Path $PSScriptRoot 'sign.ps1') (Join-Path $Stage 'photocraft.exe') (Join-Path $Stage 'photocraft-cli.exe') # ---- MSI --------------------------------------------------------------------------------------- +# The setup wizard's art, drawn here from the app icon so the MSI shows PhotoCraft rather than +# WiX's stock bitmaps: a 493x58 banner across the top of the inner pages (the page title is drawn +# over its left side, so that stays white) and a 493x312 backdrop for the Welcome and Finish pages +# (their text sits on the right; the left 164 px are ours). Both must be 24-bit BMPs. +function New-InstallerArt([string] $IconPng, [string] $BannerOut, [string] $DialogOut) { + Add-Type -AssemblyName System.Drawing + $paper = [System.Drawing.ColorTranslator]::FromHtml('#efe9dc') + $accent = [System.Drawing.ColorTranslator]::FromHtml('#2f7bf5') + $icon = [System.Drawing.Image]::FromFile($IconPng) + try { + foreach ($spec in @(@($BannerOut, 493, 58), @($DialogOut, 493, 312))) { + $bmp = New-Object System.Drawing.Bitmap $spec[1], $spec[2], ([System.Drawing.Imaging.PixelFormat]::Format24bppRgb) + $g = [System.Drawing.Graphics]::FromImage($bmp) + try { + $g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic + $g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias + $g.Clear([System.Drawing.Color]::White) + if ($spec[2] -eq 58) { + $g.DrawImage($icon, 493 - 46 - 8, 6, 46, 46) + } else { + $panel = New-Object System.Drawing.SolidBrush $paper + $stripe = New-Object System.Drawing.SolidBrush $accent + $g.FillRectangle($panel, 0, 0, 164, 312) + $g.FillRectangle($stripe, 160, 0, 4, 312) + $g.DrawImage($icon, 18, 64, 124, 124) + $panel.Dispose(); $stripe.Dispose() + } + } finally { $g.Dispose() } + $bmp.Save($spec[0], [System.Drawing.Imaging.ImageFormat]::Bmp) + $bmp.Dispose() + } + } finally { $icon.Dispose() } +} +$Banner = Join-Path $Stage 'installer-banner.bmp' +$Dialog = Join-Path $Stage 'installer-dialog.bmp' +New-InstallerArt (Join-Path $Root 'assets\app-icon\hicolor\256x256\apps\ai.storyteller.photocraft.png') $Banner $Dialog + $Msi = Join-Path $Dist "photocraft-$Version-windows-$Arch.msi" & (Join-Path $PSScriptRoot 'check-icons.ps1') Invoke-Native 'wix build' { wix build (Join-Path $PSScriptRoot 'photocraft.wxs') -arch $Arch ` - -d "Version=$MsiVersion" -d "BinDir=$Stage" -d "IconPath=$(Join-Path $Root 'assets\app-icon\photocraft.ico')" ` + -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext ` + -culture en-US -loc (Join-Path $PSScriptRoot 'photocraft.en-us.wxl') ` + -d "Version=$MsiVersion" -d "DisplayVersion=$Version" -d "BinDir=$Stage" -d "IconPath=$(Join-Path $Root 'assets\app-icon\photocraft.ico')" ` + -d "BannerBmp=$Banner" -d "DialogBmp=$Dialog" ` -o $Msi } Invoke-Native 'MSI shortcut icon validation (ICE50)' { diff --git a/packaging/windows/photocraft.en-us.wxl b/packaging/windows/photocraft.en-us.wxl new file mode 100644 index 00000000..faa2835a --- /dev/null +++ b/packaging/windows/photocraft.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/packaging/windows/photocraft.wxs b/packaging/windows/photocraft.wxs index 7ad692b4..03b9208d 100644 --- a/packaging/windows/photocraft.wxs +++ b/packaging/windows/photocraft.wxs @@ -1,14 +1,21 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -100,6 +137,7 @@ +