From 45621f5679fa16009dbcca61517e40e469e5f0bb Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 5 Oct 2026 01:57:09 -0400 Subject: [PATCH 1/2] Issue #18: Windows MSI: finish dialog with Launch LightCraft Interactive MSI installs now walk Welcome -> Ready to install -> progress -> "Completed the LightCraft Setup Wizard" (Finish) with "LightCraft has been installed" text and a checked-by-default "Launch LightCraft" checkbox. Failure and cancellation end on WiX's standard FatalError / UserExit pages; silent (/qn) and basic-UI (/passive) installs show no dialogs and launch nothing. - lightcraft.wxs: custom UI set = WixUI_Minimal without the licence page (MIT OR Apache-2.0 asks nothing of installers), maintenance dialogs for repair/remove, ARPNOMODIFY. Launch uses the Util extension's WixUnelevatedShellExec via Explorer (runs as the signed-in user, never elevated), Return="ignore", only when NOT Installed and the box is ticked. - package.ps1: add WixToolset.UI/Util.wixext 5.0.2 to the global cache, pass -ext ... -culture en-us, and generate plain side/banner bitmaps (brand blue / white) so the MSI carries no WiX sample artwork. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 1c5feb7ab3098909dc936e35ad1ff59e02f8143d) --- .github/workflows/release.yml | 1 + packaging/windows/lightcraft.wxs | 85 +++++++++++++++++++++++++++++++- packaging/windows/package.ps1 | 43 +++++++++++++++- 3 files changed, 126 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b9c3a1ea..698d785b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -147,6 +147,7 @@ jobs: if: inputs.version != '' shell: bash run: cargo xtask version set "$LIGHTCRAFT_VERSION" + # package.ps1 adds the matching WixToolset.UI/Util.wixext (5.0.2) to the global extension cache. - name: Install WiX shell: pwsh run: dotnet tool install --global wix --version 5.0.2 diff --git a/packaging/windows/lightcraft.wxs b/packaging/windows/lightcraft.wxs index 37510455..b6de42cf 100644 --- a/packaging/windows/lightcraft.wxs +++ b/packaging/windows/lightcraft.wxs @@ -1,8 +1,10 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/windows/package.ps1 b/packaging/windows/package.ps1 index dae31757..c45bf885 100644 --- a/packaging/windows/package.ps1 +++ b/packaging/windows/package.ps1 @@ -13,6 +13,8 @@ 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 + The WiX UI and Util extensions (installer dialogs, "Launch LightCraft" on Finish) are added to + the global WiX extension cache by this script (wix extension add -g ..., needs network once). .EXAMPLE pwsh packaging/windows/package.ps1 -Arch x64 @@ -32,6 +34,29 @@ function Invoke-Native([string] $What, [scriptblock] $Block) { if ($LASTEXITCODE -ne 0) { throw "$What failed with exit code $LASTEXITCODE" } } +# A 24-bit BMP of two flat colours: $SplitX pixels of $Left, then $Right (colours as R,G,B). +# Used for the installer's side/banner bitmaps so the MSI ships no third-party artwork. +function New-PanelBitmap([string] $Path, [int] $Width, [int] $Height, [int] $SplitX, [byte[]] $Left, [byte[]] $Right) { + $stride = [int][Math]::Ceiling($Width * 3 / 4) * 4 + $row = New-Object byte[] $stride + for ($x = 0; $x -lt $Width; $x++) { + $c = if ($x -lt $SplitX) { $Left } else { $Right } + $row[$x * 3] = $c[2]; $row[$x * 3 + 1] = $c[1]; $row[$x * 3 + 2] = $c[0] # BMP pixels are B,G,R + } + $pixels = $stride * $Height + $ms = [IO.MemoryStream]::new() + $w = [IO.BinaryWriter]::new($ms) + $w.Write([byte]0x42); $w.Write([byte]0x4D) # 'BM' + $w.Write([uint32](54 + $pixels)); $w.Write([uint32]0); $w.Write([uint32]54) + $w.Write([uint32]40); $w.Write([int32]$Width); $w.Write([int32]$Height) # BITMAPINFOHEADER + $w.Write([uint16]1); $w.Write([uint16]24); $w.Write([uint32]0); $w.Write([uint32]$pixels) + $w.Write([int32]2835); $w.Write([int32]2835); $w.Write([uint32]0); $w.Write([uint32]0) + for ($y = 0; $y -lt $Height; $y++) { $w.Write($row) } + $w.Flush() + [IO.File]::WriteAllBytes($Path, $ms.ToArray()) + $w.Dispose() +} + # The version lives in one place: [workspace.package] version in the root Cargo.toml. $Version = $env:LIGHTCRAFT_VERSION if (-not $Version) { @@ -91,10 +116,26 @@ Copy-Item (Join-Path $Bin 'lightcraft.exe'), (Join-Path $Bin 'lightcraft-cli.exe & (Join-Path $PSScriptRoot 'sign.ps1') (Join-Path $Stage 'lightcraft.exe') (Join-Path $Stage 'lightcraft-cli.exe') # ---- MSI --------------------------------------------------------------------------------------- +# Extensions must match the WiX tool version (5.0.2, see release.yml). Re-adding is harmless. +$WixVersion = '5.0.2' +$WixExtensions = 'WixToolset.UI.wixext', 'WixToolset.Util.wixext' +foreach ($ext in $WixExtensions) { + Invoke-Native "wix extension add $ext/$WixVersion" { wix extension add -g "$ext/$WixVersion" } +} +$ExtArgs = foreach ($ext in $WixExtensions) { '-ext'; "$ext/$WixVersion" } + +# Installer dialog bitmaps (WixUI sizes): a brand-blue side panel on the welcome/finish pages and +# a plain white banner on the inner pages. Generated here, never committed. +$UiDialogBmp = Join-Path $Stage 'ui-dialog.bmp' +$UiBannerBmp = Join-Path $Stage 'ui-banner.bmp' +New-PanelBitmap $UiDialogBmp 493 312 164 ([byte[]](0x4E, 0x7B, 0xFB)) ([byte[]](0xFF, 0xFF, 0xFF)) +New-PanelBitmap $UiBannerBmp 493 58 0 ([byte[]](0xFF, 0xFF, 0xFF)) ([byte[]](0xFF, 0xFF, 0xFF)) + $Msi = Join-Path $Dist "lightcraft-$Version-windows-$Arch.msi" Invoke-Native 'wix build' { - wix build (Join-Path $PSScriptRoot 'lightcraft.wxs') -arch $Arch ` + wix build (Join-Path $PSScriptRoot 'lightcraft.wxs') -arch $Arch -culture en-us @ExtArgs ` -d "Version=$MsiVersion" -d "BinDir=$Stage" -d "IconPath=$(Join-Path $Root 'assets\app-icon\lightcraft.ico')" ` + -d "UiDialogBmp=$UiDialogBmp" -d "UiBannerBmp=$UiBannerBmp" ` -o $Msi } # wix writes its debug symbols (.wixpdb) next to the MSI; keep them out of the release assets. From 485046bf3504cc22f63bbc020942d51025b50b98 Mon Sep 17 00:00:00 2001 From: hxw <530313@qq.com> Date: Fri, 9 Oct 2026 09:04:50 -0500 Subject: [PATCH 2/2] Issue #399: Windows MSI: choose the install folder; upgrades keep it The interactive installer now walks Welcome -> Destination Folder -> Ready -> progress -> Completed (from #79). Destination Folder is WiX's InstallDirDlg: Program Files\LightCraft by default, Change... browses, and WixUIValidatePath refuses a folder that can't be used. The chosen folder is written to HKLM\Software\LightCraft\InstallFolder and read back (into PREVIOUSINSTALLFOLDER, then INSTALLFOLDER after AppSearch unless given on the command line), so a major upgrade stays in the user's folder instead of moving back to Program Files. Silent and passive installs show no dialogs and take INSTALLFOLDER=.... packaging-lint fails if lightcraft.wxs loses the folder page, the remembered folder or the completion page's launch action. Tested with wix 5.0.2 (x64, arm64, x86 build) and msiexec /qn: upgrade from 0.4.0 stays in Program Files; a custom INSTALLFOLDER install, an upgrade without it (stays), an upgrade with another INSTALLFOLDER (moves there), and uninstall (files, folder and registry value removed). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/packaging-lint.yml | 5 +++ README.md | 4 +- docs/whats-new.md | 4 ++ packaging/windows/lightcraft.wxs | 63 +++++++++++++++++++++++----- 4 files changed, 65 insertions(+), 11 deletions(-) diff --git a/.github/workflows/packaging-lint.yml b/.github/workflows/packaging-lint.yml index 0e2388a6..ed2d8f00 100644 --- a/.github/workflows/packaging-lint.yml +++ b/.github/workflows/packaging-lint.yml @@ -51,6 +51,11 @@ jobs: if grep -oE '' 'Value="InstallDirDlg"' '' \ + 'Key="Software\LightCraft" Name="InstallFolder"' 'Value="LaunchLightcraft"'; do + grep -qF "$want" packaging/windows/lightcraft.wxs || { echo "::error::lightcraft.wxs lost: $want"; exit 1; } + done desktop-file-validate packaging/linux/ai.storyteller.lightcraft.desktop sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' packaging/linux/ai.storyteller.lightcraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.lightcraft.metainfo.xml" appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.lightcraft.metainfo.xml" diff --git a/README.md b/README.md index e93683c6..ba7c960f 100644 --- a/README.md +++ b/README.md @@ -444,7 +444,9 @@ Questions, ideas or a bug you'd like to talk through first? Bring them to [Disco | arm64 (Snapdragon and other ARM PCs) | `lightcraft--windows-arm64.msi` | `lightcraft--windows-arm64-portable.zip` | | x86 (32-bit) | `lightcraft--windows-x86.msi` | `lightcraft--windows-x86-portable.zip` | -Installers and executables are code-signed. +Installers and executables are code-signed. The installer asks where to install (`C:\Program Files\LightCraft` by +default; upgrades stay in the folder you chose) and ends on a page confirming LightCraft was installed, with an +option to start it. For unattended installs: `msiexec /i lightcraft--windows-x64.msi /qn INSTALLFOLDER="D:\Apps\LightCraft\"`. ### macOS diff --git a/docs/whats-new.md b/docs/whats-new.md index 96b10a97..afcc1328 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -129,6 +129,10 @@ ) ### Reliability +- The Windows installer asks where to install LightCraft (Program Files by default; upgrades keep the folder you + chose) and ends on a page saying it was installed, with a "Launch LightCraft" box. It used to finish without a word, + so a successful install looked like nothing had happened (issues #18, #399). Silent installs (`/qn`, `/passive`) + show no dialogs and take `INSTALLFOLDER=...`. - If the desktop app can't open its window (for example when no graphics device can be used), it now says so in a message box that names the log file, instead of quitting without a trace (issue #260). - On macOS, single-key shortcuts that appear in the menu bar now work: E, C, H, M, ⇧P, I, K, D, ratings 0–5, diff --git a/packaging/windows/lightcraft.wxs b/packaging/windows/lightcraft.wxs index b6de42cf..ce98a714 100644 --- a/packaging/windows/lightcraft.wxs +++ b/packaging/windows/lightcraft.wxs @@ -6,7 +6,8 @@ wix build lightcraft.wxs -arch x64 -ext WixToolset.UI.wixext/5.0.2 -ext WixToolset.Util.wixext/5.0.2 -d Version=1.2.3 -d BinDir=... -d IconPath=... -d UiDialogBmp=... -d UiBannerBmp=... -o out.msi - Per-machine install into Program Files, a Start Menu shortcut, `.pcraft` owned by LightCraft, + Per-machine install into Program Files (or the folder chosen in the installer, or + `INSTALLFOLDER=...` on the msiexec command line; upgrades keep it), a Start Menu shortcut, `.pcraft` owned by LightCraft, and LightCraft offered under "Open with" for PSD/PSB and the image formats it reads (without taking over the user's default app for them). --> @@ -35,6 +36,14 @@ + + + + + @@ -45,12 +54,15 @@ - - + + + + + + + + + + + + + + + + + + + + @@ -131,6 +173,7 @@ +