Compare commits

...
10 changed files with 323 additions and 39 deletions
+5
View File
@@ -381,3 +381,8 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
| 2026-10-10 | major | Full re-measure; restructured to the progress-docs standard (stage banner, two numbers, dimensions, languages, upcoming, progress log); parity tables moved to `docs/target-app-parity.md`, milestones to `docs/roadmap.md` |
| 2026-10-09 | minor | Recently landed: RTL, Ukrainian and Brazilian Portuguese |
| 2026-10-06 | major | First honest estimate: 88% catalog, ~62% real parity, alpha ~85% of the way |
### Windows installer unification — release preparation, 2026-10-12
The shared craft-installer UI preserves this revision's install options and provides persistent installation, repair and Windows Settings removal completion screens. Silent modes stay unattended. Earlier Windows acceptance is recorded in packaging/windows/README.md; normal CI and production-profile upgrade qualification against pinned current releases are in progress. No release or PR has been published.
+1 -22
View File
@@ -236,7 +236,7 @@ fn save(s: &mut Session, v: &Value) -> CmdResult {
match v.get("format").filter(|f| !f.is_null()) {
None => {
// Saving over a Word 2003 XML file (one opened as such, say) keeps its format.
opts.word2003 = ext == "xml" && is_word_2003_file(&path);
opts.word2003 = ext == "xml" && crate::io::is_word_2003_file(&path);
}
Some(f) if matches!(f.as_str(), Some(FORMAT_WORD_2003 | FORMAT_FLAT_OPC)) && ext == "xml" => {}
Some(_) => {
@@ -254,27 +254,6 @@ fn save(s: &mut Session, v: &Value) -> CmdResult {
Ok(json!({"saved": true, "path": path.to_string_lossy(), "encrypted": encrypted}))
}
/// Does `path` hold a Word 2003 XML Document already (so a plain save keeps that format)?
#[cfg(not(target_arch = "wasm32"))]
fn is_word_2003_file(path: &std::path::Path) -> bool {
use std::io::Read;
if crate::file_access::check_read(&path.to_string_lossy()).is_err() {
return false;
}
let Ok(f) = std::fs::File::open(path) else { return false };
let mut head = Vec::new();
// The root element and its namespaces come first.
if f.take(64 * 1024).read_to_end(&mut head).is_err() {
return false;
}
wordcraft_docx::sniff_xml(&head) == wordcraft_docx::XmlKind::WordMl2003
}
#[cfg(target_arch = "wasm32")]
fn is_word_2003_file(_path: &std::path::Path) -> bool {
false
}
fn save_as(s: &mut Session, v: &Value) -> CmdResult {
if p::str(v, "path").is_none() {
s.ui_requests.push(json!({"open": "saveAs"}));
+21
View File
@@ -378,6 +378,27 @@ fn read_regular_file(path: &std::path::Path, limit: u64) -> Option<Vec<u8>> {
Some(out)
}
/// Does `path` hold a Word 2003 XML Document already (so a plain save keeps that format)?
#[cfg(not(target_arch = "wasm32"))]
pub fn is_word_2003_file(path: &std::path::Path) -> bool {
use std::io::Read;
if crate::file_access::check_read(&path.to_string_lossy()).is_err() {
return false;
}
let Ok(f) = std::fs::File::open(path) else { return false };
let mut head = Vec::new();
// The root element and its namespaces come first.
if f.take(64 * 1024).read_to_end(&mut head).is_err() {
return false;
}
wordcraft_docx::sniff_xml(&head) == wordcraft_docx::XmlKind::WordMl2003
}
#[cfg(target_arch = "wasm32")]
pub fn is_word_2003_file(_path: &std::path::Path) -> bool {
false
}
/// The bytes of the file at `path`, for commands that take a path (pictures, mail-merge data).
/// Every such read goes through here so the automation read root applies ([`crate::file_access`]).
#[cfg(not(target_arch = "wasm32"))]
+3
View File
@@ -1645,6 +1645,9 @@ impl WordApp {
}
let on_chrome = ctx.layer_id_at(pos).is_none_or(|l| l.order == egui::Order::Background);
if on_chrome && ctx.memory(|m| m.focused().is_none()) && !egui::Popup::is_any_open(ctx) {
// The next frame handles typing before the canvas requests egui focus.
// Restore logical focus now so that first text event is not dropped.
self.canvas.focused = true;
self.canvas.want_focus = true;
ctx.request_repaint();
}
+5
View File
@@ -88,3 +88,8 @@ languages, screen-reader access, ecosystem decisions (VBA, add-ins, cloud), an i
| 2026-10-10 | major | Created: milestones moved from ROADMAP.md with remaining hours; Current focus from gaps.md; M15–M16 added |
| 2026-10-10 | trivial | M11: ink pens and eraser landed (#307) |
| 2026-10-11 | trivial | M11: lasso, Add Pen, Ink to Shape and Ink Replay landed (#395) |
### Windows installer unification — release preparation, 2026-10-12
The shared craft-installer UI preserves this revision's install options and provides persistent installation, repair and Windows Settings removal completion screens. Silent modes stay unattended. Earlier Windows acceptance is recorded in packaging/windows/README.md; normal CI and production-profile upgrade qualification against pinned current releases are in progress. No release or PR has been published.
+2 -1
View File
@@ -1,6 +1,6 @@
# UI parity with Microsoft Word
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** trivial (no document behind the Start screen, #1129; previously trivial: equation pictures share a few textures, #1002; previously trivial: selection bar in the left margin, #856; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously major: first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** trivial (the first text event after a chrome click is retained, #1093; previously trivial: no document behind the Start screen, #1129; previously trivial: equation pictures share a few textures, #1002; previously trivial: selection bar in the left margin, #856; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously major: first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
How WordCraft looks and feels next to Word: ribbon, dialogs, panes, keyboard, mouse and touch.
Typing behaviour has its own checklist ([`typing-parity.md`](typing-parity.md)). All assets are our
@@ -47,6 +47,7 @@ own (`AGENTS.md`: no Microsoft iconography, ever).
| Date | Change | Summary |
|---|---|---|
| 2026-10-13 | trivial | The caret is black or white by contrast with the paper, page colour, dark page, shading or highlight under it (#299) |
| 2026-10-15 | trivial | Restore logical page focus when a chrome click is accepted, before the next frame handles typing; the first text event after a gallery or tab click is retained. Existing dialog, popup and text-field exclusions and immediate-typing tests remain in place (#1093) |
| 2026-10-13 | trivial | Clicks on ribbon and status-bar chrome that run no command button (Styles and style-set gallery tiles, tab names, empty ribbon space, View check boxes, the status bar's view buttons and language menu) no longer take keyboard focus from the page: the caret stays and typing continues, as in Word; text fields such as the font size box still take focus. Tabs, gallery tiles and the status bar's view buttons report their name and state to screen readers (#1093) |
| 2026-10-13 | trivial | Interface size: a chosen size is absolute ("100%" is one screen pixel per point, even where the system reports 200 % under a bare window manager) and the new default, Auto, keeps the display scale and shows the size it draws at; 75–300 %; an old saved size becomes the size it drew at; on the web a chosen size cancels browser zoom, Auto follows it (#822) |
| 2026-10-13 | trivial | Copy and Cut of a selection holding an equation put HTML with the equation as MathML on the system clipboard beside the text (desktop, through arboard), as MathML in HTML on the clipboard (Word/LibreOffice paste not yet verified; OMML is #1297) (#816). Only copies containing an equation get HTML. Web: text only |
+34
View File
@@ -0,0 +1,34 @@
# Windows installer UI
`installer-ui.wxs` is generated from the shared `craft-installer` crate in
[craft-libs](https://github.com/storytold/craft-libs/tree/main/crates/installer).
`package.ps1` compiles it with this app's package source. The common UI provides progress,
repair/remove, and explicit install/uninstall/repair success, cancellation and failure screens.
Regenerate or check from a sibling craft-libs checkout:
```sh
cargo run --locked -p craft-installer -- --write --app wordcraft ../wordcraft/packaging/windows/installer-ui.wxs
cargo run --locked -p craft-installer -- --check --app wordcraft ../wordcraft/packaging/windows/installer-ui.wxs
```
Edit the shared templates/profile and regenerate; avoid editing this snapshot by hand. The
profile retains this app's existing folder, shortcut and launch options; defaults, persistence,
components, associations and signing stay in the app-owned source. Release builds require no
sibling checkout, published crate or additional runtime dependency. `/qn`, `/qb` and `/passive`
remain unattended and do not launch the app.
Source/profile checks and native fixture MSI linking/UI table checks passed for x64, x86 and
ARM64. All 14 profiles passed full UI install, missing-file repair and cached uninstall, and
x86 silent install/removal. Folder upgrades, desktop choices and all six launch profiles also
passed their Windows fixture checks. See the
[Windows verification record](../../../craft-libs/docs/windows-installer-verification.md) for
interactive results, scope validation and remaining acceptance checks with real release payloads.
The shared crate includes reusable linking, full UI, option/upgrade/launch and x86 test runners.
Windows Settings now uses a separate MSI-owned entry whose uninstall command selects full UI,
including the completion screen. Its quiet command and direct `/qn`, `/qb`, `/passive` commands
remain unattended. The main feature references `CraftInstallerSettings`; the shared snapshot
owns this registration and hides the native MSI entry while retaining MSI repair/upgrade APIs.
The Settings display name appends `(Craft Apps)` for compatibility with older Launchers; the
hidden native MSI name stays unchanged. Existing ARP Modify policy is retained explicitly.
+242
View File
@@ -0,0 +1,242 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by craft-installer for wordcraft. Edit the template in craft-libs, then regenerate. -->
<!-- Adapted from PdfCraft installer-ui.wxs (a6bc63a), GridCraft gridcraft.wxs (5b15827),
and EffectCraft installer-ui.wxs (1b4d47f).
Copyright (c) 2026 ArtCraft Team and the PdfCraft contributors.
Copyright (c) 2026 ArtCraft Team and the GridCraft contributors.
Copyright (c) 2026 ArtCraft Team and the EffectCraft contributors.
Licensed under MIT OR Apache-2.0; see the repository's LICENSE-MIT and LICENSE-APACHE. -->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Fragment>
<!-- Windows Installer populates files-in-use lists at runtime. Keep their table even
though these dialogs have no static ListItem rows (the GridCraft UI does the same). -->
<EnsureTable Id="ListBox" />
<!-- Reuse the existing package icon as a fallback; MSI supplies its standard message icon. -->
<Binary Id="CraftInstallerErrorIcon" SourceFile="$(var.IconPath)" />
<UI Id="CraftInstallerUI">
<TextStyle Id="CraftInstallerNormal" FaceName="Segoe UI" Size="9" />
<TextStyle Id="CraftInstallerTitle" FaceName="Segoe UI" Size="12" Bold="yes" />
<Property Id="DefaultUIFont" Value="CraftInstallerNormal" />
<Property Id="ErrorDialog" Value="CraftInstallerErrorDlg" />
<Dialog Id="CraftInstallerErrorDlg" Width="320" Height="120" Title="[ProductName] Setup" ErrorDialog="yes" NoMinimize="yes">
<Control Id="ErrorText" Type="Text" X="60" Y="15" Width="245" Height="65" TabSkip="no" />
<Control Id="ErrorIcon" Type="Icon" X="15" Y="15" Width="32" Height="32" FixedSize="yes" IconSize="32" TabSkip="yes" Text="CraftInstallerErrorIcon" />
<Control Id="A" Type="PushButton" X="15" Y="90" Width="65" Height="20" Text="Abort" TabSkip="yes">
<Publish Event="EndDialog" Value="ErrorAbort" />
</Control>
<Control Id="C" Type="PushButton" X="15" Y="90" Width="65" Height="20" Text="Cancel" TabSkip="yes">
<Publish Event="EndDialog" Value="ErrorCancel" />
</Control>
<Control Id="I" Type="PushButton" X="15" Y="90" Width="65" Height="20" Text="Ignore" TabSkip="yes">
<Publish Event="EndDialog" Value="ErrorIgnore" />
</Control>
<Control Id="N" Type="PushButton" X="15" Y="90" Width="65" Height="20" Text="No" TabSkip="yes">
<Publish Event="EndDialog" Value="ErrorNo" />
</Control>
<Control Id="O" Type="PushButton" X="15" Y="90" Width="65" Height="20" Text="OK" TabSkip="yes">
<Publish Event="EndDialog" Value="ErrorOk" />
</Control>
<Control Id="R" Type="PushButton" X="15" Y="90" Width="65" Height="20" Text="Retry" TabSkip="yes">
<Publish Event="EndDialog" Value="ErrorRetry" />
</Control>
<Control Id="Y" Type="PushButton" X="15" Y="90" Width="65" Height="20" Text="Yes" TabSkip="yes">
<Publish Event="EndDialog" Value="ErrorYes" />
</Control>
</Dialog>
<Dialog Id="FilesInUse" Width="320" Height="190" Title="[ProductName] Setup" NoMinimize="yes">
<Control Id="Description" Type="Text" X="15" Y="15" Width="290" Height="35"
Text="Close the applications listed below, then choose Retry. Ignore may require a restart." />
<Control Id="Files" Type="ListBox" X="15" Y="55" Width="290" Height="85" Property="FileInUseProcess" />
<Control Id="Retry" Type="PushButton" X="95" Y="155" Width="65" Height="20" Default="yes" Text="Retry">
<Publish Event="EndDialog" Value="Retry" />
</Control>
<Control Id="Ignore" Type="PushButton" X="167" Y="155" Width="65" Height="20" Text="Ignore">
<Publish Event="EndDialog" Value="Ignore" />
</Control>
<Control Id="Cancel" Type="PushButton" X="240" Y="155" Width="65" Height="20" Cancel="yes" Text="Cancel">
<Publish Event="EndDialog" Value="Exit" />
</Control>
</Dialog>
<Dialog Id="InstallLicense" Width="370" Height="270" Title="[ProductName] Setup">
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\CraftInstallerTitle}License" />
<Control Id="License" Type="ScrollableText" X="20" Y="55" Width="330" Height="130" Sunken="yes" TabSkip="no">
<Text SourceFile="$(var.LicenseRtf)" />
</Control>
<Control Id="Accept" Type="CheckBox" X="20" Y="195" Width="330" Height="20" Property="CraftLicenseAccepted" CheckBoxValue="1" Text="I &amp;accept the license terms" />
<Control Id="Install" Type="PushButton" X="195" Y="235" Width="75" Height="23" Default="yes" ElevationShield="yes" Text="&amp;Install" Disabled="yes" EnableCondition="CraftLicenseAccepted = &quot;1&quot;" DisableCondition="NOT CraftLicenseAccepted">
<Publish Event="EndDialog" Value="Return" Condition="CraftLicenseAccepted = &quot;1&quot;" />
</Control>
<Control Id="Cancel" Type="PushButton" X="280" Y="235" Width="70" Height="23" Cancel="yes" Text="Cancel">
<Publish Event="SpawnDialog" Value="ConfirmCancel" />
</Control>
</Dialog>
<Dialog Id="InstallWelcome" Width="370" Height="270" Title="[ProductName] Setup">
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\CraftInstallerTitle}Install [ProductName]" />
<Control Id="Description" Type="Text" X="20" Y="55" Width="330" Height="40"
Text="Setup will install [ProductName] for all users and add it to the Start menu. Click Next to review the license." />
<Control Id="Install" Type="PushButton" X="195" Y="235" Width="75" Height="23" Default="yes" ElevationShield="no" Text="&amp;Next">
<Publish Event="NewDialog" Value="InstallLicense" Order="99" />
</Control>
<Control Id="Cancel" Type="PushButton" X="280" Y="235" Width="70" Height="23" Cancel="yes" Text="Cancel">
<Publish Event="EndDialog" Value="Exit" />
</Control>
</Dialog>
<Dialog Id="InstallMaintenance" Width="370" Height="270" Title="[ProductName] Setup">
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\CraftInstallerTitle}Maintain [ProductName]" />
<Control Id="Description" Type="Text" X="20" Y="55" Width="330" Height="70"
Text="[ProductName] is already installed. Choose Repair to restore missing files and shortcuts, or Remove to uninstall it." />
<Control Id="Repair" Type="PushButton" X="110" Y="235" Width="75" Height="23" Default="yes" ElevationShield="yes" Text="&amp;Repair">
<!-- Feature events do not retain REINSTALL/REMOVE in the client UI session. -->
<Publish Property="CraftInstallerOperation" Value="repair" Order="1" />
<Publish Event="ReinstallMode" Value="amus" Order="2" />
<Publish Event="Reinstall" Value="ALL" Order="3" />
<Publish Event="EndDialog" Value="Return" Order="4" />
</Control>
<Control Id="Remove" Type="PushButton" X="195" Y="235" Width="75" Height="23" ElevationShield="yes" Text="R&amp;emove">
<Publish Property="CraftInstallerOperation" Value="remove" Order="1" />
<Publish Event="Remove" Value="ALL" Order="2" />
<Publish Event="EndDialog" Value="Return" Order="3" />
</Control>
<Control Id="Cancel" Type="PushButton" X="280" Y="235" Width="70" Height="23" Cancel="yes" Text="Cancel">
<Publish Event="EndDialog" Value="Exit" />
</Control>
</Dialog>
<Dialog Id="InstallProgress" Width="370" Height="270" Title="[ProductName] Setup" Modeless="yes">
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\CraftInstallerTitle}Setting up [ProductName]" />
<!-- No ActionText table is authored: subscribing would blank this message (#305). -->
<Control Id="Status" Type="Text" X="20" Y="60" Width="330" Height="40" Text="Please wait while setup completes." />
<Control Id="Progress" Type="ProgressBar" X="20" Y="110" Width="330" Height="15" ProgressBlocks="yes">
<Subscribe Event="SetProgress" Attribute="Progress" />
</Control>
<Control Id="Cancel" Type="PushButton" X="280" Y="235" Width="70" Height="23" Cancel="yes" Text="Cancel">
<Publish Event="SpawnDialog" Value="ConfirmCancel" />
</Control>
</Dialog>
<Dialog Id="ConfirmCancel" Width="300" Height="110" Title="Cancel setup">
<Control Id="Description" Type="Text" X="20" Y="20" Width="260" Height="35" Text="Cancel [ProductName] setup?" />
<Control Id="Yes" Type="PushButton" X="130" Y="70" Width="70" Height="23" Text="&amp;Yes">
<Publish Event="EndDialog" Value="Exit" />
</Control>
<Control Id="No" Type="PushButton" X="210" Y="70" Width="70" Height="23" Default="yes" Cancel="yes" Text="&amp;No">
<Publish Event="EndDialog" Value="Return" />
</Control>
</Dialog>
<Dialog Id="InstallComplete" Width="370" Height="270" Title="[ProductName] Setup">
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\CraftInstallerTitle}Setup completed successfully" HideCondition="REMOVE = &quot;ALL&quot; OR CraftInstallerOperation = &quot;remove&quot;" />
<Control Id="RemovedTitle" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\CraftInstallerTitle}Uninstall completed successfully" Hidden="yes" ShowCondition="REMOVE = &quot;ALL&quot; OR CraftInstallerOperation = &quot;remove&quot;" />
<Control Id="Description" Type="Text" X="20" Y="55" Width="330" Height="70"
Text="[ProductName] is installed. Open it from the Start menu. Click Finish to close setup." HideCondition="REMOVE = &quot;ALL&quot; OR REINSTALL OR CraftInstallerOperation OR ACTION = &quot;ADMIN&quot;" />
<Control Id="Removed" Type="Text" X="20" Y="55" Width="330" Height="70" Hidden="yes"
Text="[ProductName] was uninstalled. Your documents and settings were kept. Click Finish to close setup."
ShowCondition="REMOVE = &quot;ALL&quot; OR CraftInstallerOperation = &quot;remove&quot;" />
<Control Id="Repaired" Type="Text" X="20" Y="55" Width="330" Height="70" Hidden="yes"
Text="[ProductName] was repaired. Open it from the Start menu. Click Finish to close setup."
ShowCondition="(REINSTALL OR CraftInstallerOperation = &quot;repair&quot;) AND NOT REMOVE AND CraftInstallerOperation &lt;&gt; &quot;remove&quot; AND ACTION &lt;&gt; &quot;ADMIN&quot;" />
<Control Id="AdminComplete" Type="Text" X="20" Y="55" Width="330" Height="70" Hidden="yes"
Text="The administrative image was created. Click Finish to close setup." ShowCondition="ACTION = &quot;ADMIN&quot;" />
<Control Id="Finish" Type="PushButton" X="280" Y="235" Width="70" Height="23" Default="yes" Cancel="yes" Text="&amp;Finish">
<Publish Event="DoAction" Value="LaunchWordcraft" Order="1" Condition="WIXUI_EXITDIALOGOPTIONALCHECKBOX = &quot;1&quot; AND NOT Installed AND NOT REMOVE AND NOT REINSTALL AND NOT CraftInstallerOperation AND ACTION &lt;&gt; &quot;ADMIN&quot;" />
<Publish Event="EndDialog" Value="Return" Order="999" />
</Control>
<Control Id="Launch" Type="CheckBox" X="20" Y="155" Width="330" Height="20" Property="WIXUI_EXITDIALOGOPTIONALCHECKBOX" CheckBoxValue="1" Text="[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]" HideCondition="Installed OR REMOVE OR REINSTALL OR CraftInstallerOperation OR ACTION = &quot;ADMIN&quot;" />
</Dialog>
<Dialog Id="InstallFailed" Width="370" Height="270" Title="[ProductName] Setup">
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\CraftInstallerTitle}Setup failed" />
<Control Id="Description" Type="Text" X="20" Y="55" Width="330" Height="70"
Text="[ProductName] setup could not be completed. Resolve the reported error and run setup again." />
<Control Id="Finish" Type="PushButton" X="280" Y="235" Width="70" Height="23" Default="yes" Cancel="yes" Text="&amp;Finish">
<Publish Event="EndDialog" Value="Return" />
</Control>
</Dialog>
<Dialog Id="InstallCancelled" Width="370" Height="270" Title="[ProductName] Setup">
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\CraftInstallerTitle}Setup cancelled" />
<Control Id="Description" Type="Text" X="20" Y="55" Width="330" Height="70"
Text="[ProductName] setup was cancelled. Run setup again when you are ready." />
<Control Id="Finish" Type="PushButton" X="280" Y="235" Width="70" Height="23" Default="yes" Cancel="yes" Text="&amp;Finish">
<Publish Event="EndDialog" Value="Return" />
</Control>
</Dialog>
<!-- Shown by Windows Installer (Restart Manager) when an upgrade or repair must replace files
that a running app holds open. OK closes those programs (MSIRMSHUTDOWN=2) and
continues; Cancel ends setup. -->
<Dialog Id="MsiRMFilesInUse" Width="370" Height="200" Title="[ProductName] Setup" KeepModeless="yes">
<Control Id="Title" Type="Text" X="20" Y="15" Width="330" Height="25" Text="{\CraftInstallerTitle}Files in use" />
<Control Id="Description" Type="Text" X="20" Y="45" Width="330" Height="30"
Text="These programs are using files that setup needs to update. Click OK to close them and continue, or Cancel to stop setup." />
<Control Id="List" Type="ListBox" X="20" Y="80" Width="330" Height="55" Property="FileInUseProcess" Sunken="yes" TabSkip="yes" />
<Control Id="OK" Type="PushButton" X="195" Y="165" Width="75" Height="23" Default="yes" Text="OK">
<Publish Property="MSIRMSHUTDOWN" Value="2" Order="1" />
<Publish Event="EndDialog" Value="Return" Order="2" />
</Control>
<Control Id="Cancel" Type="PushButton" X="280" Y="165" Width="70" Height="23" Cancel="yes" Text="Cancel">
<Publish Event="EndDialog" Value="Exit" />
</Control>
</Dialog>
<InstallUISequence>
<Show Dialog="InstallWelcome" Before="CostInitialize" Condition="UILevel = 5 AND NOT Installed AND NOT REMOVE AND NOT REINSTALL" />
<!-- Reinstall/Remove control events require the selection manager initialized by costing. -->
<Show Dialog="InstallMaintenance" After="CostFinalize" Condition="UILevel = 5 AND Installed AND NOT REMOVE AND NOT REINSTALL" />
<Show Dialog="InstallProgress" Before="ExecuteAction" Condition="UILevel = 5" />
<Show Dialog="InstallComplete" OnExit="success" Condition="UILevel = 5" />
<Show Dialog="InstallFailed" OnExit="error" Condition="UILevel = 5" />
<Show Dialog="InstallCancelled" OnExit="cancel" Condition="UILevel = 5" />
</InstallUISequence>
<AdminUISequence>
<Show Dialog="InstallProgress" Before="ExecuteAction" Condition="UILevel = 5" />
<Show Dialog="InstallComplete" OnExit="success" Condition="UILevel = 5" />
<Show Dialog="InstallFailed" OnExit="error" Condition="UILevel = 5" />
<Show Dialog="InstallCancelled" OnExit="cancel" Condition="UILevel = 5" />
</AdminUISequence>
</UI>
</Fragment>
<Fragment>
<!-- Keep MSI registration for repair, upgrades and managed removal, but let Settings
invoke our explicit UI command instead of Windows Installer's basic-UI ARP path. -->
<Property Id="ARPSYSTEMCOMPONENT" Value="1" />
<?if $(sys.BUILDARCH) = x86 ?>
<?define CraftMsiSystemFolder = SystemFolder ?>
<?else ?>
<?define CraftMsiSystemFolder = System64Folder ?>
<?endif ?>
<ComponentGroup Id="CraftInstallerSettings" Directory="INSTALLFOLDER">
<Component Id="CraftInstallerSettingsEntry" Guid="*">
<RegistryKey Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Craft.wordcraft" ForceDeleteOnUninstall="yes">
<!-- Legacy Launchers match the canonical MSI name exactly; keep this alias distinct. -->
<RegistryValue Name="DisplayName" Type="string" Value="[ProductName] (Craft Apps)" KeyPath="yes" />
<RegistryValue Name="DisplayVersion" Type="string" Value="[ProductVersion]" />
<RegistryValue Name="Publisher" Type="string" Value="[Manufacturer]" />
<RegistryValue Name="DisplayIcon" Type="string" Value="&quot;[INSTALLFOLDER]wordcraft.exe&quot;,0" />
<RegistryValue Name="InstallLocation" Type="string" Value="[INSTALLFOLDER]" />
<RegistryValue Name="URLInfoAbout" Type="string" Value="[ARPURLINFOABOUT]" />
<RegistryValue Name="URLUpdateInfo" Type="string" Value="[ARPURLUPDATEINFO]" />
<RegistryValue Name="HelpLink" Type="string" Value="[ARPHELPLINK]" />
<!-- Deliberately omit WindowsInstaller: Settings must honor UninstallString. -->
<RegistryValue Name="UninstallString" Type="string" Value="&quot;[$(var.CraftMsiSystemFolder)]msiexec.exe&quot; /x [ProductCode] /qf /norestart" />
<RegistryValue Name="QuietUninstallString" Type="string" Value="&quot;[$(var.CraftMsiSystemFolder)]msiexec.exe&quot; /x [ProductCode] /qn /norestart" />
<RegistryValue Name="ModifyPath" Type="string" Value="&quot;[$(var.CraftMsiSystemFolder)]msiexec.exe&quot; /i [ProductCode] /qf /norestart" />
</RegistryKey>
</Component>
<!-- Preserve the app-owned ARPNOMODIFY policy instead of inventing a new default. -->
<Component Id="CraftInstallerSettingsNoModify" Guid="*" Condition="ARPNOMODIFY = &quot;1&quot;">
<RegistryValue Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\Craft.wordcraft"
Name="NoModify" Type="integer" Value="1" KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>
+3 -4
View File
@@ -12,9 +12,8 @@
warning when no signing secrets are set).
Needs: Rust (MSVC toolchain + the target), the Windows SDK (rc.exe, signtool.exe),
and WiX v5 with its UI and Util extensions (installer dialogs, "Launch WordCraft" on finish):
and WiX v5 with its Util extension (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
@@ -126,8 +125,8 @@ $rtf = '{\rtf1\ansi\ansicpg1252\deff0{\fonttbl{\f0\fswiss\fcharset0 Tahoma;}}\vi
$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 `
wix build (Join-Path $PSScriptRoot 'wordcraft.wxs') (Join-Path $PSScriptRoot 'installer-ui.wxs') -arch $Arch `
-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
+7 -12
View File
@@ -4,7 +4,7 @@
`wix extension add -g WixToolset.UI.wixext/5.0.2` and the same for WixToolset.Util.wixext).
Built by package.ps1:
wix build wordcraft.wxs -arch x64 -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext
wix build wordcraft.wxs installer-ui.wxs -arch x64 -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext
-d Version=1.2.3 -d BinDir=... -d IconPath=... -d LicenseRtf=... -o out.msi
Per-machine install into Program Files with a Start Menu shortcut. The installer shows the
@@ -37,22 +37,12 @@
<Property Id="ARPURLUPDATEINFO" Value="https://getartcraft.com/apps/wordcraft" />
<Property Id="ARPHELPLINK" Value="https://discord.gg/artcraft" />
<!-- Installer UI (#198): WixUI_Minimal is welcome + license, progress, then a finish page so
people can see the install worked. Its dialogs and bitmaps are WiX's own
(WixToolset.UI.wixext); package.ps1 builds the license RTF from LICENSE-MIT and
LICENSE-APACHE. The finish page offers to launch WordCraft (ticked by default) through
WixShellExec (WixToolset.Util.wixext); silent installs (/qn) never show it. -->
<WixVariable Id="WixUILicenseRtf" Value="$(var.LicenseRtf)" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Launch WordCraft" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1" />
<Property Id="WixShellExecTarget" Value="[#WordcraftExe]" />
<CustomAction Id="LaunchWordcraft" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixShellExec"
Impersonate="yes" Return="ignore" />
<UI>
<ui:WixUI Id="WixUI_Minimal" />
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchWordcraft"
Condition="WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 AND NOT Installed" />
</UI>
<StandardDirectory Id="ProgramFiles6432Folder">
<Directory Id="INSTALLFOLDER" Name="WordCraft" />
@@ -61,7 +51,12 @@
<Feature Id="Main" Title="WordCraft" Level="1">
<ComponentGroupRef Id="WordcraftFiles" />
<ComponentGroupRef Id="CraftInstallerSettings" />
</Feature>
<!-- Shared full UI; app installation policy remains below. -->
<!-- Preserve the previous stock WiX wizard's ARP Modify policy. -->
<Property Id="ARPNOMODIFY" Value="1" />
<UIRef Id="CraftInstallerUI" />
</Package>
<Fragment>