From 7040751a5456bcfd4a4a40a7c25d2d88b2fffdb6 Mon Sep 17 00:00:00 2001 From: Valentin Klein Date: Sat, 10 Oct 2026 18:23:03 +0200 Subject: [PATCH] Windows installer: show a finish page after installing (#198) (#257) The MSI had no UI, so Windows Installer showed only its basic progress box and closed without saying the install had worked. It now uses WiX's standard WixUI_Minimal dialogs: a welcome page with the license (MIT or Apache-2.0, built at packaging time from LICENSE-MIT and LICENSE-APACHE), progress, and a finish page with an optional "Launch WordCraft" checkbox. The release and Windows ARM64 workflows install the WiX UI and Util extensions the build now needs. UpgradeCode and component GUIDs are unchanged. Co-authored-by: Claude Opus 5.5 --- .github/workflows/release.yml | 7 +++++- .github/workflows/windows-arm64.yml | 7 +++++- packaging/windows/package.ps1 | 37 ++++++++++++++++++++++++++++- packaging/windows/wordcraft.wxs | 29 ++++++++++++++++++---- 4 files changed, 73 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 19eb2b231..565a4199b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -150,7 +150,12 @@ jobs: run: cargo xtask version set "$WORDCRAFT_VERSION" - name: Install WiX shell: pwsh - run: dotnet tool install --global wix --version 5.0.2 + run: | + $PSNativeCommandUseErrorActionPreference = $true # stop at the first failing command + dotnet tool install --global wix --version 5.0.2 + # Installer dialogs and the finish page's "Launch WordCraft" (packaging/windows/wordcraft.wxs). + wix extension add -g WixToolset.UI.wixext/5.0.2 + wix extension add -g 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 19deceea7..87eb72a10 100644 --- a/.github/workflows/windows-arm64.yml +++ b/.github/workflows/windows-arm64.yml @@ -45,7 +45,12 @@ jobs: key: windows-arm64-package - name: Install WiX shell: pwsh - run: dotnet tool install --global wix --version 5.0.2 + run: | + $PSNativeCommandUseErrorActionPreference = $true # stop at the first failing command + dotnet tool install --global wix --version 5.0.2 + # Installer dialogs and the finish page's "Launch WordCraft" (packaging/windows/wordcraft.wxs). + wix extension add -g WixToolset.UI.wixext/5.0.2 + wix extension add -g WixToolset.Util.wixext/5.0.2 - name: Build and package (unsigned) shell: pwsh env: diff --git a/packaging/windows/package.ps1 b/packaging/windows/package.ps1 index 938f8e5c7..c791ffbd1 100644 --- a/packaging/windows/package.ps1 +++ b/packaging/windows/package.ps1 @@ -12,7 +12,10 @@ warning when no signing secrets are set). 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 + and WiX v5 with its UI and Util extensions (installer dialogs, "Launch WordCraft" on finish): + dotnet tool install --global wix --version 5.0.2 + wix extension add -g WixToolset.UI.wixext/5.0.2 + wix extension add -g WixToolset.Util.wixext/5.0.2 .EXAMPLE pwsh packaging/windows/package.ps1 -Arch x64 @@ -91,10 +94,42 @@ Copy-Item (Join-Path $Bin 'wordcraft.exe'), (Join-Path $Bin 'wordcraft-cli.exe') & (Join-Path $PSScriptRoot 'sign.ps1') (Join-Path $Stage 'wordcraft.exe') (Join-Path $Stage 'wordcraft-cli.exe') # ---- MSI --------------------------------------------------------------------------------------- +# The installer's welcome page shows the license: build its RTF from LICENSE-MIT and LICENSE-APACHE +# so it never drifts from them. Hard-wrapped lines are rejoined into paragraphs so the text wraps +# to the dialog's width. +function ConvertTo-RtfText([string] $Text) { + $sb = [System.Text.StringBuilder]::new() + foreach ($ch in $Text.ToCharArray()) { + $c = [int] $ch + if ($c -eq 0x5C -or $c -eq 0x7B -or $c -eq 0x7D) { [void] $sb.Append('\').Append($ch) } + elseif ($c -gt 127) { [void] $sb.Append("\u$(if ($c -gt 32767) { $c - 65536 } else { $c })?") } + else { [void] $sb.Append($ch) } + } + return $sb.ToString() +} +$paragraphs = [System.Collections.Generic.List[string]]::new() +$paragraphs.Add('\b WordCraft\b0') +$paragraphs.Add((ConvertTo-RtfText 'WordCraft is dual-licensed under the MIT License or the Apache License, Version 2.0, at your option. Both license texts follow. Bundled fonts and other third-party material keep their own open licenses, listed in NOTICE and ATTRIBUTION.md at https://github.com/storytold/wordcraft.')) +foreach ($file in 'LICENSE-MIT', 'LICENSE-APACHE') { + $title = $true # each file opens with its license's name: show it bold + foreach ($para in ((Get-Content -Raw (Join-Path $Root $file)) -split '\r?\n\s*\r?\n')) { + $joined = (($para -split '\r?\n') | ForEach-Object { $_.Trim() } | Where-Object { $_ }) -join ' ' + if (-not $joined) { continue } + $paragraphs.Add($(if ($title) { "\b $(ConvertTo-RtfText $joined)\b0" } else { ConvertTo-RtfText $joined })) + $title = $false + } +} +$LicenseRtf = Join-Path $Stage 'license.rtf' +$rtf = '{\rtf1\ansi\ansicpg1252\deff0{\fonttbl{\f0\fswiss\fcharset0 Tahoma;}}\viewkind4\uc1\pard\sa120\f0\fs16 ' + + ($paragraphs -join "\par`r`n") + "\par`r`n}`r`n" +[System.IO.File]::WriteAllText($LicenseRtf, $rtf, [System.Text.Encoding]::ASCII) + $Msi = Join-Path $Dist "wordcraft-$Version-windows-$Arch.msi" Invoke-Native 'wix build' { wix build (Join-Path $PSScriptRoot 'wordcraft.wxs') -arch $Arch ` + -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext ` -d "Version=$MsiVersion" -d "BinDir=$Stage" -d "IconPath=$(Join-Path $Root 'assets\app-icon\wordcraft.ico')" ` + -d "LicenseRtf=$LicenseRtf" ` -o $Msi } # wix writes its debug symbols (.wixpdb) next to the MSI; keep them out of the release assets. diff --git a/packaging/windows/wordcraft.wxs b/packaging/windows/wordcraft.wxs index 9557ed6ba..620f28943 100644 --- a/packaging/windows/wordcraft.wxs +++ b/packaging/windows/wordcraft.wxs @@ -1,17 +1,21 @@ - + + + + + + + + + + + +