|
| 1 | +# Microsoft Store / MSIX pilot |
| 2 | + |
| 3 | +Trayffeine is prepared for a future Microsoft Store submission as a full-trust MSIX desktop app. |
| 4 | +This is preparation only: there is no Store listing, submission, GitHub Release upload, or WinGet |
| 5 | +change in this repository state. |
| 6 | + |
| 7 | +## What remains unchanged |
| 8 | + |
| 9 | +- The public installer remains the per-user Inno Setup `.exe` built by |
| 10 | + `packaging/windows/build.ps1`. |
| 11 | +- GitHub Release tags still publish only that installer and follow the existing WinGet update path. |
| 12 | +- The new MSIX workflow never runs on tags and never creates a GitHub Release. |
| 13 | +- No project version is changed just to create an MSIX package. |
| 14 | + |
| 15 | +The MSIX route is intentionally an x64 pilot for Trayffeine. Add other architectures only after a |
| 16 | +real Store acceptance test shows they are needed. |
| 17 | + |
| 18 | +## Package design and behavior |
| 19 | + |
| 20 | +`packaging/msix/build.ps1` creates an MSIX-only PyInstaller bundle under `dist/msix/`, stages it |
| 21 | +with `AppxManifest.xml`, and uses the Windows SDK `MakeAppx.exe` to pack and unpack the result. The |
| 22 | +unpack step verifies the identity, full-trust executable, declared startup task, and required tile |
| 23 | +assets. The script does not sign, publish, install, or submit the package. |
| 24 | + |
| 25 | +The manifest declares `Windows.FullTrustApplication` and `runFullTrust`, which keeps the existing |
| 26 | +Win32 tray implementation, native dialogs, and keep-awake backends in their full-trust desktop |
| 27 | +context. It also declares the `TrayffeineStartup` `windows.startupTask` extension. |
| 28 | + |
| 29 | +Startup behavior is intentionally channel-aware: |
| 30 | + |
| 31 | +- The existing EXE/Inno Setup/WinGet install continues to use the current-user `Run` key. |
| 32 | +- When Trayffeine has an MSIX package identity, the same tray preference uses the declared Windows |
| 33 | + startup task instead. This avoids relying on a virtualized `Run` key inside the package. |
| 34 | +- The task starts disabled. Turning on `Preferences > Start with Windows` requests its enablement. |
| 35 | + Windows exposes it in Task Manager's Startup tab. If the user disables it there, Windows does not |
| 36 | + allow the app to turn it back on programmatically; the next launch reconciles the tray preference |
| 37 | + to off. |
| 38 | + |
| 39 | +Settings and logs continue to use the existing `%LOCALAPPDATA%\Trayffeine` code path. MSIX manages |
| 40 | +that state per user and preserves it through package updates; a full uninstall can remove |
| 41 | +package-managed state. Test any desired migration from an existing EXE install explicitly during |
| 42 | +acceptance, rather than assuming that the two install channels share all state. |
| 43 | + |
| 44 | +Do not run the legacy EXE and MSIX package as simultaneous acceptance targets: both deliberately use |
| 45 | +the same single-instance mutex. Disable legacy autostart before testing MSIX autostart. |
| 46 | + |
| 47 | +## Build an unsigned preflight package locally |
| 48 | + |
| 49 | +Requirements: |
| 50 | + |
| 51 | +- Windows with the Windows 10 or 11 SDK MSIX packaging tools (`MakeAppx.exe`) |
| 52 | +- Python 3.13 |
| 53 | + |
| 54 | +```powershell |
| 55 | +py -3.13 -m venv .venv |
| 56 | +.venv\Scripts\Activate.ps1 |
| 57 | +python -m pip install -e .[build,msix] |
| 58 | +python scripts\generate_assets.py |
| 59 | +
|
| 60 | +powershell -ExecutionPolicy Bypass -File packaging\msix\build.ps1 ` |
| 61 | + -IdentityName "Trayffeine.Preview" ` |
| 62 | + -Publisher "CN=Trayffeine Preview" ` |
| 63 | + -PublisherDisplayName "Trayffeine Preview" ` |
| 64 | + -Clean |
| 65 | +``` |
| 66 | + |
| 67 | +The command creates `dist/msix/Trayffeine-<version>-x64.msix`, plus the staged package at |
| 68 | +`dist/msix/staging`. It is deliberately unsigned. The preview identity is only suitable for package |
| 69 | +structure validation; do not upload it to Partner Center. |
| 70 | + |
| 71 | +The staging and verification directories are recreated on every build, even without `-Clean`. |
| 72 | +Passing `-Clean` additionally removes the previous PyInstaller work and all MSIX output first. |
| 73 | + |
| 74 | +For an interactive local package-context smoke test, register the staged manifest from an appropriate |
| 75 | +development-enabled Windows machine: |
| 76 | + |
| 77 | +```powershell |
| 78 | +Add-AppxPackage -Register (Resolve-Path .\dist\msix\staging\AppxManifest.xml) |
| 79 | +``` |
| 80 | + |
| 81 | +This worktree's host intentionally did not perform that registration: Windows returned `0x80073CFF` |
| 82 | +because it does not allow unsigned developer package registration. Do not enable Developer Mode or |
| 83 | +change sideload/security policy solely for this pilot. This is the only unperformed local installation |
| 84 | +test; run it later on a disposable, development-approved Windows profile. |
| 85 | + |
| 86 | +Then launch Trayffeine from Start and verify tray appearance, a timed session, infinite restore, |
| 87 | +settings persistence across relaunch, and the Startup task. Test package removal separately on a |
| 88 | +non-production user profile. A signed package is only necessary for direct MSIX sideloading; it is |
| 89 | +not needed for this preflight or a Microsoft Store MSIX submission. |
| 90 | + |
| 91 | +## CI preflight |
| 92 | + |
| 93 | +`MSIX Preview Build` runs for relevant pull requests and manual dispatches on `windows-latest`. It |
| 94 | +uses a non-Store preview identity and uploads the unsigned `.msix` only as a workflow artifact. It |
| 95 | +also rebuilds without `-Clean` after adding sentinel files, verifying that stale staging and |
| 96 | +verification content is removed. It does not need Partner Center credentials, certificates, GitHub |
| 97 | +Release permissions, or WinGet credentials. |
| 98 | + |
| 99 | +## Partner Center values required later |
| 100 | + |
| 101 | +Before the first real submission, use Partner Center to create an **MSIX/PWA** product and reserve |
| 102 | +the Store name. Copy these exact package-identity values into the real build command: |
| 103 | + |
| 104 | +- `Identity Name` / package identity name |
| 105 | +- `Publisher` (the exact X.500 publisher string supplied by Partner Center) |
| 106 | +- reserved display name and the publisher display name, if they differ from the defaults |
| 107 | + |
| 108 | +The public display name is not a substitute for either identity field. Build the submission candidate |
| 109 | +only after those values are known: |
| 110 | + |
| 111 | +```powershell |
| 112 | +powershell -ExecutionPolicy Bypass -File packaging\msix\build.ps1 ` |
| 113 | + -IdentityName "<Partner Center identity name>" ` |
| 114 | + -Publisher "<Partner Center publisher>" ` |
| 115 | + -DisplayName "<Reserved Store name>" ` |
| 116 | + -PublisherDisplayName "<Partner Center publisher display name>" ` |
| 117 | + -Clean |
| 118 | +``` |
| 119 | + |
| 120 | +The script reads `pyproject.toml` by default and maps the current semantic version, for example |
| 121 | +`1.2.0`, to the four-part MSIX version `1.2.0.0`. A later Store update must increase that MSIX |
| 122 | +version; this preparation does not require a version bump. |
| 123 | + |
| 124 | +Before uploading the real submission candidate, run the current Windows App Certification Kit from |
| 125 | +an elevated command prompt in an active user session. Use the package built with the exact Partner |
| 126 | +Center identity, and review the generated report rather than assuming that a successful MakeAppx |
| 127 | +pack/unpack is equivalent to Store certification: |
| 128 | + |
| 129 | +```powershell |
| 130 | +$appCert = "${env:ProgramFiles(x86)}\Windows Kits\10\App Certification Kit\appcert.exe" |
| 131 | +& $appCert reset |
| 132 | +& $appCert test ` |
| 133 | + -appxpackagepath ".\dist\msix\Trayffeine-<version>-x64.msix" ` |
| 134 | + -reportoutputpath ".\dist\msix\wack-report.xml" |
| 135 | +``` |
| 136 | + |
| 137 | +Finish the required Partner Center metadata before submission: pricing and availability, category and |
| 138 | +capability declarations, age rating, Store description, screenshots, Store listing artwork, support |
| 139 | +details, and a privacy-policy URL if the published data practices require one. Explain the |
| 140 | +`runFullTrust` declaration as the packaging requirement for this existing Win32 tray application. |
| 141 | + |
| 142 | +For Microsoft Store MSIX distribution, Microsoft signs the submitted package after certification, so |
| 143 | +no certificate, PFX, or signing secret belongs in this repository or workflow. For private signed |
| 144 | +sideload tests, use a local test certificate whose subject exactly matches the manifest publisher and |
| 145 | +trust it only on the test machine. |
| 146 | + |
| 147 | +Useful official references: |
| 148 | + |
| 149 | +- [Manual desktop MSIX packaging](https://learn.microsoft.com/windows/msix/desktop/desktop-to-uwp-manual-conversion) |
| 150 | +- [Desktop startup task extension](https://learn.microsoft.com/windows/apps/desktop/modernize/desktop-to-uwp-extensions#start-an-executable-file-when-users-log-into-windows) |
| 151 | +- [MakeAppx command-line packaging](https://learn.microsoft.com/windows/msix/package/create-app-package-with-makeappx-tool) |
| 152 | +- [Windows App Certification Kit](https://learn.microsoft.com/windows/uwp/debug-test-perf/windows-app-certification-kit) |
| 153 | +- [MSIX signing options](https://learn.microsoft.com/windows/msix/package/signing-package-overview) |
| 154 | +- [Packaged desktop app state and AppData behavior](https://learn.microsoft.com/windows/msix/desktop/desktop-to-uwp-behind-the-scenes) |
0 commit comments