Compare commits

..

13 Commits

19 changed files with 1087 additions and 7 deletions

View File

@@ -14,6 +14,7 @@ on:
- 'resources/**'
- ".github/workflows/build_*.yml"
- 'scripts/flatpak/**'
- 'scripts/msix/**'
pull_request:
branches:
@@ -30,6 +31,7 @@ on:
- 'build_release_vs2022.bat'
- 'build_release_macos.sh'
- 'scripts/flatpak/**'
- 'scripts/msix/**'
schedule:

View File

@@ -371,6 +371,25 @@ jobs:
asset_content_type: application/x-msdownload
max_releases: 1
- name: Build MSIX Store package Win
if: runner.os == 'Windows' && !vars.SELF_HOSTED
working-directory: ${{ github.workspace }}
shell: pwsh
run: |
./scripts/msix/build_msix.ps1 `
-InstallDir "${{ github.workspace }}/build/OrcaSlicer" `
-OutputPath "${{ github.workspace }}/build/OrcaSlicer_Windows_MSIX_${{ env.ver }}.msix" `
-IdentityName "${{ vars.ORCA_MSIX_IDENTITY_NAME || 'OrcaSlicer.Dev' }}" `
-Publisher "${{ vars.ORCA_MSIX_PUBLISHER || 'CN=00000000-0000-0000-0000-000000000000' }}" `
-PublisherDisplayName "${{ vars.ORCA_MSIX_PUBLISHER_DISPLAY_NAME || 'OrcaSlicer (dev)' }}"
- name: Upload artifacts Win MSIX
if: runner.os == 'Windows' && !vars.SELF_HOSTED
uses: actions/upload-artifact@v7
with:
name: OrcaSlicer_Windows_MSIX_${{ env.ver }}
path: ${{ github.workspace }}/build/OrcaSlicer_Windows_MSIX_${{ env.ver }}.msix
# Ubuntu
- name: Apt-Install Dependencies
if: runner.os == 'Linux' && !vars.SELF_HOSTED

View File

@@ -73,8 +73,9 @@ jobs:
- name: Download release artifacts from build run
run: |
# Windows_V* (not Windows_*) keeps the MSIX Store artifact out: it goes to Partner Center, not GitHub releases.
gh run download "$RUN_ID" --repo "$GITHUB_REPOSITORY" --dir artifacts \
-p 'OrcaSlicer_Windows_*' \
-p 'OrcaSlicer_Windows_V*' \
-p 'OrcaSlicer_Mac_universal_*' \
-p 'OrcaSlicer_Linux_ubuntu_*' \
-p 'OrcaSlicer-Linux-flatpak_*' \

View File

@@ -0,0 +1,645 @@
# MSIX Microsoft Store Build Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build an unsigned MSIX Store package of OrcaSlicer in CI from the existing Windows install tree, and make the app behave correctly in packaged context (no self-update, no runtime registry-based file associations).
**Architecture:** A hand-written `AppxManifest.xml` template + PowerShell pack script under `scripts/msix/` (precedent: `scripts/flatpak/`), one new CI step in the Windows job of `.github/workflows/build_orca.yml`, and a small packaged-context detection helper in `src/slic3r/GUI/GUI_Utils.{hpp,cpp}` used to gate the updater and association code. Everything is additive: NSIS/portable outputs are untouched, all runtime gates are no-ops in classic builds.
**Tech Stack:** PowerShell 5.1+ (System.Drawing, makeappx from Windows SDK), GitHub Actions, C++17/wxWidgets (Win32 `GetCurrentPackageFullName`/`GetCurrentPackageFamilyName` from `appmodel.h`, kernel32).
**Spec:** `docs/superpowers/specs/2026-06-11-msix-store-build-design.md`
**Branch:** `msix-store-build` (already created; spec is committed on it)
**Why no TDD here:** none of this is unit-testable in this repo — the C++ changes live in the wxWidgets GUI module which no test target links (tests cover `libslic3r`), and the behavior under test is Windows shell/packaging behavior. Each task instead has concrete verification steps (script output checks, XML well-formedness, full app build), and Task 8 collects the manual verification documented in the PR per the repo's review guidelines.
**Windows build command** (required for any task touching C++; plain shells fail without the VS environment — run via PowerShell):
```
cmd /c '"C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvars64.bat" >nul && cmake --build build --config RelWithDebInfo --target OrcaSlicer 2>&1'
```
The `'vswhere.exe' is not recognized` line vcvars prints is harmless. Expected: build completes with exit code 0 (warnings OK). Incremental builds are fast when warm.
---
### Task 1: MSIX logo assets + generator script
**Files:**
- Create: `scripts/msix/generate_assets.ps1`
- Create (generated): `scripts/msix/assets/Square150x150Logo.png`, `scripts/msix/assets/Square44x44Logo.png`, `scripts/msix/assets/Square44x44Logo.targetsize-44_altform-unplated.png`, `scripts/msix/assets/StoreLogo.png`
- [ ] **Step 1: Write the generator script**
Create `scripts/msix/generate_assets.ps1` with exactly this content:
```powershell
# Generates the MSIX package logo assets from the 192px master logo.
# Run once on Windows (re-run if the logo changes), then commit the PNGs in assets/.
# Scale-200 variants would need a >192px master; regenerate from OrcaSlicer.svg if ever needed.
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
$source = Join-Path $repoRoot 'resources\images\OrcaSlicer_192px.png'
$outDir = Join-Path $PSScriptRoot 'assets'
New-Item -ItemType Directory -Force $outDir | Out-Null
$sizes = @{
'Square150x150Logo.png' = 150
'Square44x44Logo.png' = 44
'Square44x44Logo.targetsize-44_altform-unplated.png' = 44
'StoreLogo.png' = 50
}
$src = [System.Drawing.Image]::FromFile($source)
foreach ($name in $sizes.Keys) {
$px = $sizes[$name]
$bmp = New-Object System.Drawing.Bitmap($px, $px)
$gfx = [System.Drawing.Graphics]::FromImage($bmp)
$gfx.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$gfx.DrawImage($src, 0, 0, $px, $px)
$gfx.Dispose()
$bmp.Save((Join-Path $outDir $name), [System.Drawing.Imaging.ImageFormat]::Png)
$bmp.Dispose()
Write-Output "Wrote $name (${px}x${px})"
}
$src.Dispose()
```
- [ ] **Step 2: Run it**
Run (PowerShell, from repo root): `./scripts/msix/generate_assets.ps1`
Expected output: four `Wrote <name> (<px>x<px>)` lines, no errors.
- [ ] **Step 3: Verify the generated dimensions**
```powershell
Add-Type -AssemblyName System.Drawing
Get-ChildItem scripts/msix/assets/*.png | ForEach-Object { $i = [System.Drawing.Image]::FromFile($_.FullName); "$($_.Name): $($i.Width)x$($i.Height)"; $i.Dispose() }
```
Expected: `Square150x150Logo.png: 150x150`, `Square44x44Logo.png: 44x44`, `Square44x44Logo.targetsize-44_altform-unplated.png: 44x44`, `StoreLogo.png: 50x50`.
- [ ] **Step 4: Commit**
```bash
git add scripts/msix/generate_assets.ps1 scripts/msix/assets
git commit -m "ci: add MSIX logo asset generator and generated assets"
```
---
### Task 2: AppxManifest.xml template
**Files:**
- Create: `scripts/msix/AppxManifest.xml`
- [ ] **Step 1: Write the manifest template**
Create `scripts/msix/AppxManifest.xml` with exactly this content. The four `@...@` tokens are substituted by `build_msix.ps1` (Task 3). Namespace URIs and the virtualization/MigrationProgIds element shapes were verified against learn.microsoft.com (flexible-virtualization and element-rescap3-migrationprogids pages) on 2026-06-11. `MigrationProgId` is `Orca.Slicer.1` — note the classic build writes the registry ProgID with a leading space (`GUI_App.cpp:9119`), which MigrationProgIds cannot express, so migration from existing NSIS installs is best-effort (Windows shows both apps in "Open with"); deliberately NOT fixed here.
```xml
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:rescap3="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/3"
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10"
IgnorableNamespaces="uap rescap rescap3 desktop6 virtualization">
<Identity Name="@MSIX_IDENTITY_NAME@"
Publisher="@MSIX_PUBLISHER@"
Version="@MSIX_VERSION@"
ProcessorArchitecture="x64" />
<Properties>
<DisplayName>OrcaSlicer</DisplayName>
<PublisherDisplayName>@MSIX_PUBLISHER_DISPLAY_NAME@</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
<!-- Keep config in the real %APPDATA%\OrcaSlicer so it survives uninstall and
is shared with the classic (NSIS/portable) install.
Win10 1903+: coarse switch disables AppData write virtualization entirely.
Win11+: fine-grained exclusion below takes precedence over the coarse switch. -->
<desktop6:FileSystemWriteVirtualization>disabled</desktop6:FileSystemWriteVirtualization>
<virtualization:FileSystemWriteVirtualization>
<virtualization:ExcludedDirectories>
<virtualization:ExcludedDirectory>$(KnownFolder:RoamingAppData)\OrcaSlicer</virtualization:ExcludedDirectory>
</virtualization:ExcludedDirectories>
</virtualization:FileSystemWriteVirtualization>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.26100.0" />
</Dependencies>
<Resources>
<Resource Language="en-us" />
</Resources>
<Applications>
<Application Id="OrcaSlicer" Executable="orca-slicer.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="OrcaSlicer"
Description="Open-source slicer for FDM 3D printers"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png" />
<Extensions>
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="orcaslicer-models">
<uap:SupportedFileTypes>
<uap:FileType>.3mf</uap:FileType>
<uap:FileType>.stl</uap:FileType>
<uap:FileType>.step</uap:FileType>
<uap:FileType>.stp</uap:FileType>
<uap:FileType>.gcode</uap:FileType>
<uap:FileType>.drc</uap:FileType>
</uap:SupportedFileTypes>
<rescap3:MigrationProgIds>
<rescap3:MigrationProgId>Orca.Slicer.1</rescap3:MigrationProgId>
</rescap3:MigrationProgIds>
</uap:FileTypeAssociation>
</uap:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="orcaslicer" />
</uap:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources" />
</Capabilities>
</Package>
```
- [ ] **Step 2: Verify XML is well-formed**
Run (PowerShell): `[xml](Get-Content scripts/msix/AppxManifest.xml -Raw) | Out-Null; 'XML OK'`
Expected: `XML OK` (no exception).
- [ ] **Step 3: Commit**
```bash
git add scripts/msix/AppxManifest.xml
git commit -m "ci: add MSIX AppxManifest template"
```
---
### Task 3: build_msix.ps1 pack script
**Files:**
- Create: `scripts/msix/build_msix.ps1`
- [ ] **Step 1: Write the pack script**
Create `scripts/msix/build_msix.ps1` with exactly this content:
```powershell
<#
Builds the unsigned MSIX Store package from an existing install tree.
The package is intentionally NOT signed: the Microsoft Store strips and
re-signs uploads with Microsoft's certificate. For local installs use
Developer Mode loose-layout registration instead:
./scripts/msix/build_msix.ps1 -StageOnly
Add-AppxPackage -Register <staging>\AppxManifest.xml
Requires the Windows SDK (makeappx.exe) unless -StageOnly is used.
#>
param(
[string]$InstallDir = "build/OrcaSlicer",
[string]$OutputPath = "build/OrcaSlicer_Windows_MSIX.msix",
[string]$StagingDir = "",
[switch]$StageOnly,
[string]$IdentityName = "OrcaSlicer.Dev",
[string]$Publisher = "CN=00000000-0000-0000-0000-000000000000",
[string]$PublisherDisplayName = "OrcaSlicer (dev)"
)
$ErrorActionPreference = 'Stop'
$repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
# MSIX version = MAJOR.MINOR.PATCH.0 from the SoftFever_VERSION semver triplet
# (Store requires the revision field to be 0).
$versionContent = Get-Content (Join-Path $repoRoot 'version.inc') -Raw
if ($versionContent -notmatch 'set\(SoftFever_VERSION "(\d+)\.(\d+)\.(\d+)') {
throw "Could not parse SoftFever_VERSION from version.inc"
}
$msixVersion = "$($Matches[1]).$($Matches[2]).$($Matches[3]).0"
Write-Output "MSIX version: $msixVersion"
if (-not (Test-Path (Join-Path $InstallDir 'orca-slicer.exe'))) {
throw "orca-slicer.exe not found in '$InstallDir' - build the install tree first"
}
if ([string]::IsNullOrEmpty($StagingDir)) {
$StagingDir = Join-Path ([System.IO.Path]::GetTempPath()) 'orca-msix-staging'
}
if (Test-Path $StagingDir) { Remove-Item $StagingDir -Recurse -Force }
New-Item -ItemType Directory -Force $StagingDir | Out-Null
Copy-Item -Path (Join-Path $InstallDir '*') -Destination $StagingDir -Recurse
Copy-Item -Path (Join-Path $PSScriptRoot 'assets') -Destination (Join-Path $StagingDir 'Assets') -Recurse
$manifest = Get-Content (Join-Path $PSScriptRoot 'AppxManifest.xml') -Raw
$manifest = $manifest.Replace('@MSIX_VERSION@', $msixVersion)
$manifest = $manifest.Replace('@MSIX_IDENTITY_NAME@', $IdentityName)
$manifest = $manifest.Replace('@MSIX_PUBLISHER@', $Publisher)
$manifest = $manifest.Replace('@MSIX_PUBLISHER_DISPLAY_NAME@', $PublisherDisplayName)
Set-Content -Path (Join-Path $StagingDir 'AppxManifest.xml') -Value $manifest -Encoding utf8
if ($StageOnly) {
Write-Output "Staged loose layout at: $StagingDir"
exit 0
}
$makeappx = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.*\x64\makeappx.exe" -ErrorAction SilentlyContinue |
Sort-Object { [version]$_.Directory.Parent.Name } -Descending |
Select-Object -First 1 -ExpandProperty FullName
if (-not $makeappx) {
throw "makeappx.exe not found under '${env:ProgramFiles(x86)}\Windows Kits\10\bin' - install the Windows SDK"
}
Write-Output "Using makeappx: $makeappx"
& $makeappx pack /d $StagingDir /p $OutputPath /o
if ($LASTEXITCODE -ne 0) { throw "makeappx pack failed with exit code $LASTEXITCODE" }
Write-Output "Packed: $OutputPath"
```
- [ ] **Step 2: Verify locally (conditional)**
If `build/OrcaSlicer/orca-slicer.exe` exists on this machine, run from repo root:
`./scripts/msix/build_msix.ps1 -OutputPath build/OrcaSlicer_test.msix`
Expected: `MSIX version: 2.4.0.0` (current `version.inc`), `Using makeappx: ...`, makeappx output ending in package creation succeeded, `Packed: build/OrcaSlicer_test.msix`. Then delete the test package: `Remove-Item build/OrcaSlicer_test.msix`.
If the install tree does NOT exist, run only the parse/staging guard paths instead:
`./scripts/msix/build_msix.ps1 -InstallDir nonexistent` — expected: throws `orca-slicer.exe not found in 'nonexistent' - build the install tree first`. Full pack then gets verified by the CI run (Task 8).
- [ ] **Step 3: Commit**
```bash
git add scripts/msix/build_msix.ps1
git commit -m "ci: add MSIX packaging script"
```
---
### Task 4: CI step in build_orca.yml
**Files:**
- Modify: `.github/workflows/build_orca.yml` (insert after the "Create installer Win" step, currently lines 295-299, before "Pack app")
- [ ] **Step 1: Add the MSIX build + upload steps**
In `.github/workflows/build_orca.yml`, directly after this existing block:
```yaml
- name: Create installer Win
if: runner.os == 'Windows' && !vars.SELF_HOSTED
working-directory: ${{ github.workspace }}/build
run: |
cpack -G NSIS
```
insert:
```yaml
- name: Build MSIX Store package Win
if: runner.os == 'Windows' && !vars.SELF_HOSTED
working-directory: ${{ github.workspace }}
shell: pwsh
run: |
./scripts/msix/build_msix.ps1 `
-InstallDir "${{ github.workspace }}/build/OrcaSlicer" `
-OutputPath "${{ github.workspace }}/build/OrcaSlicer_Windows_MSIX_${{ env.ver }}.msix" `
-IdentityName "${{ vars.ORCA_MSIX_IDENTITY_NAME || 'OrcaSlicer.Dev' }}" `
-Publisher "${{ vars.ORCA_MSIX_PUBLISHER || 'CN=00000000-0000-0000-0000-000000000000' }}" `
-PublisherDisplayName "${{ vars.ORCA_MSIX_PUBLISHER_DISPLAY_NAME || 'OrcaSlicer (dev)' }}"
```
and after the existing "Upload artifacts Win installer" step (uploads `build/OrcaSlicer*.exe`), insert:
```yaml
- name: Upload artifacts Win MSIX
if: runner.os == 'Windows' && !vars.SELF_HOSTED
uses: actions/upload-artifact@v7
with:
name: OrcaSlicer_Windows_MSIX_${{ env.ver }}
path: ${{ github.workspace }}/build/OrcaSlicer_Windows_MSIX_${{ env.ver }}.msix
```
Notes: gating mirrors the NSIS installer step (`!vars.SELF_HOSTED` — github-hosted runners are guaranteed to have the Windows SDK). The unset-variable fallbacks (`|| '...'`) keep fork/pre-reservation builds green with placeholder identity. Do NOT touch any existing step.
- [ ] **Step 2: Verify YAML parses**
Run (PowerShell): `python -c "import yaml; yaml.safe_load(open('.github/workflows/build_orca.yml', encoding='utf-8')); print('YAML OK')"`
Expected: `YAML OK`. (If python/pyyaml is unavailable, fall back to careful diff review — `git diff .github/workflows/build_orca.yml` — checking indentation matches sibling steps at 6 spaces; final validation is the CI run in Task 8.)
- [ ] **Step 3: Commit**
```bash
git add .github/workflows/build_orca.yml
git commit -m "ci: build MSIX Store package in Windows job"
```
---
### Task 5: packaged-context helpers in GUI_Utils
**Files:**
- Modify: `src/slic3r/GUI/GUI_Utils.hpp` (declarations after `format_nozzle_diameter`, ~line 69)
- Modify: `src/slic3r/GUI/GUI_Utils.cpp` (include + implementations after `format_nozzle_diameter`, ~line 47)
- [ ] **Step 1: Declare the helpers**
In `src/slic3r/GUI/GUI_Utils.hpp`, after the line
`wxString format_nozzle_diameter(float diameter);`
add:
```cpp
// True when running inside an MSIX package (Microsoft Store build); always false on non-Windows.
bool is_running_in_msix();
// Opens the Microsoft Store product page for the current package. No-op when not packaged.
void open_ms_store_product_page();
```
- [ ] **Step 2: Implement them**
In `src/slic3r/GUI/GUI_Utils.cpp`, extend the existing Windows include block (lines 10-14)
```cpp
#ifdef _WIN32
#include <Windows.h>
#include "libslic3r/AppConfig.hpp"
#include <wx/msw/registry.h>
#endif // _WIN32
```
to
```cpp
#ifdef _WIN32
#include <Windows.h>
#include <appmodel.h>
#include "libslic3r/AppConfig.hpp"
#include <wx/msw/registry.h>
#endif // _WIN32
```
and add `#include <wx/utils.h>` to the wx include group (after `#include <wx/display.h>`, line 26).
Then, after the `format_nozzle_diameter` function body (ends ~line 47), add:
```cpp
bool is_running_in_msix()
{
#ifdef _WIN32
// Null-buffer probe: returns ERROR_INSUFFICIENT_BUFFER when packaged,
// APPMODEL_ERROR_NO_PACKAGE when running unpackaged.
static const bool packaged = []() {
UINT32 length = 0;
return ::GetCurrentPackageFullName(&length, nullptr) != APPMODEL_ERROR_NO_PACKAGE;
}();
return packaged;
#else
return false;
#endif
}
void open_ms_store_product_page()
{
#ifdef _WIN32
UINT32 length = 0;
if (::GetCurrentPackageFamilyName(&length, nullptr) != ERROR_INSUFFICIENT_BUFFER)
return;
std::wstring family_name(length, L'\0');
if (::GetCurrentPackageFamilyName(&length, family_name.data()) != ERROR_SUCCESS)
return;
family_name.resize(length > 0 ? length - 1 : 0); // drop the terminating null
wxLaunchDefaultBrowser(wxString(L"ms-windows-store://pdp/?PFN=") + family_name.c_str());
#endif
}
```
- [ ] **Step 3: Build**
Run the Windows build command from the plan header (target `OrcaSlicer`). Expected: exit code 0.
- [ ] **Step 4: Commit**
```bash
git add src/slic3r/GUI/GUI_Utils.hpp src/slic3r/GUI/GUI_Utils.cpp
git commit -m "feat: add MSIX packaged-context detection helpers"
```
---
### Task 6: gate the updater in packaged context
**Files:**
- Modify: `src/slic3r/GUI/GUI_App.cpp` (~line 934, startup auto-check)
- Modify: `src/slic3r/GUI/MainFrame.cpp` (~line 2578, Help menu "Check for Updates")
Both files already include `GUI_Utils.hpp` (GUI_App.cpp directly; MainFrame.cpp via MainFrame.hpp) and are inside `namespace Slic3r::GUI`, so the helpers are callable unqualified.
- [ ] **Step 1: Gate the startup auto-check**
In `src/slic3r/GUI/GUI_App.cpp`, change (inside the `CallAfter` at ~line 934):
```cpp
this->check_new_version_sf();
```
to:
```cpp
// Store builds update through the Microsoft Store, never self-update.
if (!is_running_in_msix())
this->check_new_version_sf();
```
- [ ] **Step 2: Redirect the manual menu check**
In `src/slic3r/GUI/MainFrame.cpp`, change (~lines 2578-2583):
```cpp
append_menu_item(helpMenu, wxID_ANY, _L("Check for Updates"), _L("Check for Updates"),
[](wxCommandEvent&) {
wxGetApp().check_new_version_sf(true, 1);
}, "", nullptr, []() {
return true;
});
```
to:
```cpp
append_menu_item(helpMenu, wxID_ANY, _L("Check for Updates"), _L("Check for Updates"),
[](wxCommandEvent&) {
if (is_running_in_msix())
open_ms_store_product_page();
else
wxGetApp().check_new_version_sf(true, 1);
}, "", nullptr, []() {
return true;
});
```
- [ ] **Step 3: Build**
Run the Windows build command from the plan header (target `OrcaSlicer`). Expected: exit code 0.
- [ ] **Step 4: Commit**
```bash
git add src/slic3r/GUI/GUI_App.cpp src/slic3r/GUI/MainFrame.cpp
git commit -m "feat: suppress self-update in MSIX Store build"
```
---
### Task 7: gate runtime file associations in packaged context
**Files:**
- Modify: `src/slic3r/GUI/GUI_App.cpp` (`associate_files` ~9112, `disassociate_files` ~9137, `associate_url` ~9188, `disassociate_url` ~9213 — line numbers shifted ~+2 by Task 6)
- Modify: `src/slic3r/GUI/Preferences.cpp` (Associate tab, ~lines 1848-1883)
- [ ] **Step 1: Early-return in the four association functions**
In `src/slic3r/GUI/GUI_App.cpp`, add an early return as the first statement inside the `#ifdef WIN32` block of each of the four functions. In `associate_files`:
```cpp
void GUI_App::associate_files(std::wstring extend)
{
#ifdef WIN32
// MSIX: shell integration is declared in the package manifest; registry
// writes from a packaged process are virtualized and invisible to the shell.
if (is_running_in_msix())
return;
wchar_t app_path[MAX_PATH];
```
In `disassociate_files`, `associate_url`, and `disassociate_url`, add the same guard without repeating the comment — first statement inside `#ifdef WIN32`:
```cpp
if (is_running_in_msix())
return;
```
(`associate_url` keeps its Linux `#elif` branch untouched — the guard goes only in the WIN32 branch.)
- [ ] **Step 2: Hide the Associate tab in Preferences**
In `src/slic3r/GUI/Preferences.cpp`, wrap the body of the Associate-tab block (lines 1848-1883). Change:
```cpp
#ifdef _WIN32
m_pref_tabs->AppendItem(_L("Associate"));
```
to:
```cpp
#ifdef _WIN32
// MSIX: associations are declared in the package manifest and defaults are
// managed by Windows Settings; the runtime registry toggles below cannot work.
if (!is_running_in_msix()) {
m_pref_tabs->AppendItem(_L("Associate"));
```
and change the end of that block:
```cpp
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
#endif // _WIN32
//////////////////////////
//// DEVELOPER TAB
```
to:
```cpp
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
}
#endif // _WIN32
//////////////////////////
//// DEVELOPER TAB
```
(Keep the inner lines at their current indentation — a re-indent would bloat the diff; the `{`/`}` pair at statement level matches the file's pragmatic style. Skipping the whole block keeps `m_pref_tabs` items and `f_sizers` entries paired, which is what the tab-switching logic relies on. This also removes the only `check_url_association` consumers, so no other prompt suppression is needed.)
- [ ] **Step 3: Build**
Run the Windows build command from the plan header (target `OrcaSlicer`). Expected: exit code 0.
- [ ] **Step 4: Sanity-check classic behavior is unchanged**
Launch the freshly built classic exe: `build/src/RelWithDebInfo/orca-slicer.exe` (path per local layout). Open Preferences — the Associate tab must still be present with all checkboxes (we are not packaged, so `is_running_in_msix()` is false). Close the app.
- [ ] **Step 5: Commit**
```bash
git add src/slic3r/GUI/GUI_App.cpp src/slic3r/GUI/Preferences.cpp
git commit -m "feat: suppress runtime file associations in MSIX Store build"
```
---
### Task 8: end-to-end verification + PR
No new files. This is the verification gate the PR description documents (repo guideline: behavior changes need documented verification).
- [ ] **Step 1: Push branch and open a draft PR**
```bash
git push -u origin msix-store-build
gh pr create --draft --title "Add Microsoft Store MSIX package build" --body "<composed per below>"
```
(Compose the body first — pass it via `--body` with a here-string, not `--body-file -`, which blocks on stdin in non-interactive shells.) PR body must include: motivation (SAC blocks unsigned NSIS installer; Store re-signs), summary of the four-token identity scheme + the three repo variables to set after Partner Center name reservation (`ORCA_MSIX_IDENTITY_NAME`, `ORCA_MSIX_PUBLISHER`, `ORCA_MSIX_PUBLISHER_DISPLAY_NAME`), the spec path, and the manual-verification checklist from Steps 3-5 below with results.
- [ ] **Step 2: Verify CI**
Wait for the PR's Windows job. Expected: new "Build MSIX Store package Win" step succeeds with placeholder identity; artifact `OrcaSlicer_Windows_MSIX_PR<N>` contains one `.msix`; NSIS installer and portable zip artifacts still produced unchanged.
- [ ] **Step 3: Local loose-layout install test (Developer Mode required)**
```powershell
./scripts/msix/build_msix.ps1 -StageOnly
Add-AppxPackage -Register "$env:TEMP\orca-msix-staging\AppxManifest.xml"
```
Then verify, with results recorded in the PR:
- App launches from Start menu ("OrcaSlicer (dev)" entry).
- Preferences has NO Associate tab; Help > Check for Updates opens the Store app (placeholder PFN won't resolve to a listing — that's expected pre-reservation; the point is it does NOT run the classic downloader).
- Right-click a `.3mf` file > Open with — packaged OrcaSlicer is listed and opens the file. Visit `orcaslicer://` from a browser — packaged app activates.
- Single-instance hand-off: with the packaged app running, open a second `.3mf` the same way — it loads in the existing instance instead of spawning a new one.
- Config interop: profiles created by the classic install are visible in the packaged app and vice versa (`%APPDATA%\OrcaSlicer` is shared; requires Win11 for the fine-grained exclusion on a loose layout).
- Bambu network plugin: install/download plugin from inside the packaged app succeeds and loads.
- Uninstall the packaged app (right-click in Start) — `%APPDATA%\OrcaSlicer` still exists.
Remove the test registration when done: `Get-AppxPackage *OrcaSlicer.Dev* | Remove-AppxPackage`
- [ ] **Step 4: WACK (optional but recommended)**
On a machine with the Windows SDK: pack a real `.msix` (Task 3 script), then run the Windows App Certification Kit GUI against it. Record pass/fail + any triaged warnings in the PR. (Restricted-capability warnings about `unvirtualizedResources` are expected and justified at submission time.)
- [ ] **Step 5: Mark PR ready for review**
```bash
gh pr ready
```
---
## Post-merge (NOT part of this plan — tracked in MSIX_STORE_HANDOFF.md)
Once the Partner Center company account is approved: reserve the app name, set the three repo variables from Product identity, download the next CI `.msix` artifact, and do the first manual Store submission (listing, IARC, `runFullTrust` + `unvirtualizedResources` justifications). True SAC verification happens post-certification via a private-audience listing.

View File

@@ -0,0 +1,183 @@
# MSIX Microsoft Store Build — Design
Date: 2026-06-11. Status: approved (design review with maintainer).
Planning/requirements background: `MSIX_STORE_HANDOFF.md` (repo root,
validated against Microsoft Learn docs and Store Policies v7.19).
## Goal
Produce an unsigned `.msix` of OrcaSlicer in CI, built from the same
Windows install tree as the NSIS installer, suitable for manual upload to
Partner Center. The Store re-signs with Microsoft's certificate, which
makes the Store build install cleanly on Smart App Control (SAC) machines
that block the unsigned NSIS installer.
Everything is additive: existing build outputs (NSIS exe, portable zip)
are untouched, and every runtime behavior change is gated behind a
packaged-context check so classic builds behave exactly as today.
## Decisions (already made)
- Packaging path: hand-written `AppxManifest.xml` + `makeappx pack` over
the existing install tree. No CPack MSIX generator exists.
- Minimum Windows: 10 1903 (`MinVersion 10.0.18362.0`), declaring BOTH
virtualization elements (Win11 fine-grained exclusion + Win10 coarse
disable). Each OS honors the one it supports.
- Network plugin: keep the runtime downloader, do NOT bundle Bambu's
closed-source DLLs in the package.
- Config stays at the real `%APPDATA%\OrcaSlicer` via manifest
virtualization exclusions + `unvirtualizedResources` restricted
capability. No `data_dir` code changes.
- Submission is manual via Partner Center (first submission must be);
CI only produces the artifact.
## Components
### 1. `scripts/msix/` — packaging files (new)
Precedent: platform packaging already lives under `scripts/` (flatpak).
**`AppxManifest.xml`** — template with four substitution tokens:
`@MSIX_VERSION@`, `@MSIX_IDENTITY_NAME@`, `@MSIX_PUBLISHER@`,
`@MSIX_PUBLISHER_DISPLAY_NAME@` (all three identity strings must match
the Partner Center-assigned values exactly).
Key contents (exact schema/namespace details verified against the MSIX
schema reference during implementation; `makeappx`/WACK validate):
- `TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0"`.
- Capabilities: `runFullTrust` + `rescap:unvirtualizedResources`.
- Under `Properties`, both virtualization elements:
- `virtualization:FileSystemWriteVirtualization` with
`ExcludedDirectory = $(KnownFolder:RoamingAppData)\OrcaSlicer`
(Windows 11 fine-grained), and
- `desktop6:FileSystemWriteVirtualization = disabled`
(Windows 10 1903+ coarse fallback).
- `Application Executable="orca-slicer.exe"
EntryPoint="Windows.FullTrustApplication"` (launcher output name set
at `src/CMakeLists.txt:189`).
- Extensions:
- `windows.fileTypeAssociation` for `.3mf`, `.stl`, `.step`/`.stp`,
`.gcode`, `.drc`. Note: `.gcode` and `.drc` are opt-in in the
classic build; manifest declarations are static, so they are
always declared. Declaring an FTA only adds OrcaSlicer to "Open
with" — the user controls defaults via Windows Settings, so this is
not a behavior regression.
- `windows.protocol` for `orcaslicer://`. The classic build also
offers opt-in handlers for `prusaslicer://`, `bambustudio://` and
`cura://` (Preferences "Associate" tab); those are NOT declared in
the manifest for now (possible follow-up) and their toggles are
hidden in packaged context.
- `rescap3:MigrationProgIds` declaring `Orca.Slicer.1`.
KNOWN LIMITATION: the classic build writes its ProgID with a
leading space (`L" Orca.Slicer.1"`, `GUI_App.cpp:9119`), which
MigrationProgIds cannot express. Migration hand-off from existing
NSIS installs may therefore be partial; Windows shows both apps in
"Open with" and the user picks once. The legacy ProgID is NOT fixed
in this change (separate change with its own migration risk).
**`assets/`** — committed PNGs generated once from
`resources/images/OrcaSlicer.svg`: Square44x44Logo, Square150x150Logo,
StoreLogo (50x50), plus standard scale variants. Committed rather than
generated in CI so the pack step has no extra tool dependencies.
**`build_msix.ps1`** — single script, runnable in CI and locally (needs
Windows SDK for `makeappx`):
1. Parse `version.inc` `SoftFever_VERSION` (`2.4.0-dev` → `2.4.0.0`;
MAJOR.MINOR.PATCH from the leading semver triplet, revision fixed
at 0 as the Store requires).
2. Stage: copy the install tree (`build/OrcaSlicer`) + token-substituted
manifest + `assets/` into a temp layout.
3. `makeappx pack` → `OrcaSlicer_Windows_MSIX_<version>.msix`.
Parameters: `-InstallDir`, `-OutputPath`, `-IdentityName`, `-Publisher`,
`-PublisherDisplayName` (identity defaults are obvious placeholders). NO signing — the Store
strips and re-signs; local testing uses Developer Mode loose-layout
registration instead.
### 2. CI — `.github/workflows/build_orca.yml`
One new step in the Windows job, after the install tree exists (adjacent
to the portable-zip step): run `build_msix.ps1`, upload the `.msix` as a
workflow artifact. Identity comes from repo variables
(`vars.ORCA_MSIX_IDENTITY_NAME`, `vars.ORCA_MSIX_PUBLISHER`,
`vars.ORCA_MSIX_PUBLISHER_DISPLAY_NAME`); when unset,
placeholder defaults still produce a valid artifact (not Store-uploadable,
which is fine pre-approval). The step must not fail the job when repo
variables are absent (forks).
### 3. Runtime changes (Windows-only, packaged-context-gated)
**New helper** — `bool is_running_in_msix()` in
`src/slic3r/GUI/GUI_Utils.{hpp,cpp}`: cached null-buffer
`GetCurrentPackageFullName` probe (`ERROR_INSUFFICIENT_BUFFER` ⇒
packaged, `APPMODEL_ERROR_NO_PACKAGE` ⇒ not); constant `false` on
non-Windows.
**Updater suppression (R4)** — Store apps must not self-update:
- Skip the startup auto-check (`check_new_version_sf()` call at
`GUI_App.cpp:934`) when packaged.
- The manual "Check for updates" menu action (`MainFrame.cpp:2580`)
opens the Store listing via `ms-windows-store://pdp/?PFN=<family>`,
where the package family name comes from
`GetCurrentPackageFamilyName` at runtime — no build-time ProductId
define or extra repo variable needed, and it works identically in
pre- and post-reservation builds. It never falls back to the classic
download flow.
**Association suppression (R3)** — the manifest owns shell integration;
runtime registry writes are virtualized and invisible:
- Early-return in `associate_files`, `disassociate_files`,
`associate_url`, `disassociate_url` when packaged.
- Hide the whole "Associate" tab in Preferences
(`Preferences.cpp:1848-1883`) in packaged context — the
file-association checkboxes and the `prusaslicer://` /
`bambustudio://` / `cura://` URL-handler rows all rely on runtime
registry writes that are virtualized. The `check_url_association`
consumers live exclusively in that tab, so no other prompt suppression
is needed.
## What does NOT change
- `data_dir` / config location code — `%APPDATA%\OrcaSlicer` everywhere.
- NSIS/CPack packaging, portable zip, all non-Windows builds.
- Bambu network plugin download flow (works against the real AppData
path; SAC behavior verified per the test plan).
- Classic-build association/updater behavior (gates are packaged-only).
## Verification
No meaningful unit-test surface (Windows shell + packaging behavior);
the PR documents manual verification per repo review guidelines:
1. Local loose-layout install (Developer Mode,
`Add-AppxPackage -Register AppxManifest.xml` over the staged layout):
app launches; `.3mf` open-with and `orcaslicer://` activation work;
single-instance hand-off works when launched via the package alias.
2. Config interop: profiles created by a classic install are visible in
the packaged app and vice versa (Win11 machine); profiles survive
packaged-app uninstall.
3. Updater: no startup check; menu action opens Store listing.
4. Network plugin: download + load succeeds in packaged context.
5. WACK run against the CI artifact passes (or failures triaged).
6. CI: Windows job produces the `.msix` artifact with correct version;
NSIS and portable artifacts byte-identical in content to before
(spot-check).
7. Post-certification only: SAC end-to-end test via private-audience
listing (cannot be done locally; Store signature is the variable
under test).
## Sequencing / rollout
The PR lands before Partner Center account approval — placeholder
identity builds a valid artifact. Once the account clears and the app
name is reserved, set the three repo variables (identity name, publisher
`CN=<GUID>`, publisher display name); the next CI run produces the
uploadable package. First Store submission is manual (listing, IARC,
`runFullTrust` + `unvirtualizedResources` justifications) per the
handoff doc's "Submission process" section.
Main external risk (tracked in handoff doc R2.5): Store approval of
`unvirtualizedResources` for a non-game app is not guaranteed; fallback
is documented there and does not change this design's code shape.

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:rescap3="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/3"
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10"
IgnorableNamespaces="uap rescap rescap3 desktop6 virtualization">
<Identity Name="@MSIX_IDENTITY_NAME@"
Publisher="@MSIX_PUBLISHER@"
Version="@MSIX_VERSION@"
ProcessorArchitecture="x64" />
<Properties>
<DisplayName>OrcaSlicer</DisplayName>
<PublisherDisplayName>@MSIX_PUBLISHER_DISPLAY_NAME@</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
<!-- Keep config in the real %APPDATA%\OrcaSlicer so it survives uninstall and
is shared with the classic (NSIS/portable) install.
Win10 1903+: coarse switch disables AppData write virtualization entirely.
Win11+: fine-grained exclusion below takes precedence over the coarse switch. -->
<desktop6:FileSystemWriteVirtualization>disabled</desktop6:FileSystemWriteVirtualization>
<virtualization:FileSystemWriteVirtualization>
<virtualization:ExcludedDirectories>
<virtualization:ExcludedDirectory>$(KnownFolder:RoamingAppData)\OrcaSlicer</virtualization:ExcludedDirectory>
</virtualization:ExcludedDirectories>
</virtualization:FileSystemWriteVirtualization>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.26100.0" />
</Dependencies>
<Resources>
<Resource Language="en-us" />
</Resources>
<Applications>
<Application Id="OrcaSlicer" Executable="orca-slicer.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="OrcaSlicer"
Description="Open-source slicer for FDM 3D printers"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png" />
<Extensions>
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="orcaslicer-models">
<uap:SupportedFileTypes>
<uap:FileType>.3mf</uap:FileType>
<uap:FileType>.stl</uap:FileType>
<uap:FileType>.step</uap:FileType>
<uap:FileType>.stp</uap:FileType>
<uap:FileType>.gcode</uap:FileType>
<uap:FileType>.drc</uap:FileType>
</uap:SupportedFileTypes>
<rescap3:MigrationProgIds>
<rescap3:MigrationProgId>Orca.Slicer.1</rescap3:MigrationProgId>
</rescap3:MigrationProgIds>
</uap:FileTypeAssociation>
</uap:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="orcaslicer" />
</uap:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources" />
</Capabilities>
</Package>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,67 @@
<#
Builds the unsigned MSIX Store package from an existing install tree.
The package is intentionally NOT signed: the Microsoft Store strips and
re-signs uploads with Microsoft's certificate. For local installs use
Developer Mode loose-layout registration instead:
./scripts/msix/build_msix.ps1 -StageOnly
Add-AppxPackage -Register <staging>\AppxManifest.xml
Requires the Windows SDK (makeappx.exe) unless -StageOnly is used.
#>
param(
[string]$InstallDir = "build/OrcaSlicer",
[string]$OutputPath = "build/OrcaSlicer_Windows_MSIX.msix",
[string]$StagingDir = "",
[switch]$StageOnly,
[string]$IdentityName = "OrcaSlicer.Dev",
[string]$Publisher = "CN=00000000-0000-0000-0000-000000000000",
[string]$PublisherDisplayName = "OrcaSlicer (dev)"
)
$ErrorActionPreference = 'Stop'
$repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
# MSIX version = MAJOR.MINOR.PATCH.0 from the SoftFever_VERSION semver triplet
# (Store requires the revision field to be 0).
$versionContent = Get-Content (Join-Path $repoRoot 'version.inc') -Raw
if ($versionContent -notmatch 'set\(SoftFever_VERSION "(\d+)\.(\d+)\.(\d+)') {
throw "Could not parse SoftFever_VERSION from version.inc"
}
$msixVersion = "$($Matches[1]).$($Matches[2]).$($Matches[3]).0"
Write-Output "MSIX version: $msixVersion"
if (-not (Test-Path (Join-Path $InstallDir 'orca-slicer.exe'))) {
throw "orca-slicer.exe not found in '$InstallDir' - build the install tree first"
}
if ([string]::IsNullOrEmpty($StagingDir)) {
$StagingDir = Join-Path ([System.IO.Path]::GetTempPath()) 'orca-msix-staging'
}
if (Test-Path $StagingDir) { Remove-Item $StagingDir -Recurse -Force }
New-Item -ItemType Directory -Force $StagingDir | Out-Null
Copy-Item -Path (Join-Path $InstallDir '*') -Destination $StagingDir -Recurse
Copy-Item -Path (Join-Path $PSScriptRoot 'assets') -Destination (Join-Path $StagingDir 'Assets') -Recurse
$manifest = Get-Content (Join-Path $PSScriptRoot 'AppxManifest.xml') -Raw
$manifest = $manifest.Replace('@MSIX_VERSION@', $msixVersion)
$manifest = $manifest.Replace('@MSIX_IDENTITY_NAME@', $IdentityName)
$manifest = $manifest.Replace('@MSIX_PUBLISHER@', $Publisher)
$manifest = $manifest.Replace('@MSIX_PUBLISHER_DISPLAY_NAME@', $PublisherDisplayName)
Set-Content -Path (Join-Path $StagingDir 'AppxManifest.xml') -Value $manifest -Encoding utf8
if ($StageOnly) {
Write-Output "Staged loose layout at: $StagingDir"
return
}
$makeappx = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.*\x64\makeappx.exe" -ErrorAction SilentlyContinue |
Sort-Object { [version]$_.Directory.Parent.Name } -Descending |
Select-Object -First 1 -ExpandProperty FullName
if (-not $makeappx) {
throw "makeappx.exe not found under '${env:ProgramFiles(x86)}\Windows Kits\10\bin' - install the Windows SDK"
}
Write-Output "Using makeappx: $makeappx"
& $makeappx pack /d $StagingDir /p $OutputPath /o
if ($LASTEXITCODE -ne 0) { throw "makeappx pack failed with exit code $LASTEXITCODE" }
Write-Output "Packed: $OutputPath"

View File

@@ -0,0 +1,32 @@
# Generates the MSIX package logo assets from the 192px master logo.
# Run once on Windows (re-run if the logo changes), then commit the PNGs in assets/.
# Scale-200 variants would need a >192px master; regenerate from OrcaSlicer.svg if ever needed.
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
$source = Join-Path $repoRoot 'resources\images\OrcaSlicer_192px.png'
$outDir = Join-Path $PSScriptRoot 'assets'
New-Item -ItemType Directory -Force $outDir | Out-Null
$sizes = [ordered]@{
'Square150x150Logo.png' = 150
'Square44x44Logo.png' = 44
'Square44x44Logo.targetsize-44_altform-unplated.png' = 44
'StoreLogo.png' = 50
}
$src = [System.Drawing.Image]::FromFile($source)
foreach ($name in $sizes.Keys) {
$px = $sizes[$name]
$bmp = New-Object System.Drawing.Bitmap($px, $px)
$gfx = [System.Drawing.Graphics]::FromImage($bmp)
$gfx.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$gfx.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
$gfx.DrawImage($src, 0, 0, $px, $px)
$gfx.Dispose()
$bmp.Save((Join-Path $outDir $name), [System.Drawing.Imaging.ImageFormat]::Png)
$bmp.Dispose()
Write-Output "Wrote $name (${px}x${px})"
}
$src.Dispose()

View File

@@ -807,8 +807,6 @@ wxAuiToolBarItem* BBLTopbar::FindToolByCurrentPosition()
}
#ifdef __WXMSW__
#include <windowsx.h>
WXLRESULT CenteredTitle::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
switch (nMsg) {
@@ -825,8 +823,6 @@ WXLRESULT BBLTopbar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam
{
switch (nMsg) {
case WM_NCHITTEST: {
m_last_mouse_position = ScreenToClient({GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)});
wxAuiToolBarItem* item = this->FindToolByCurrentPosition();
if (item != NULL && item->GetWindow() != m_title_ctrl) {
break;

View File

@@ -931,7 +931,9 @@ void GUI_App::post_init()
this->preset_updater->sync(http_url, language, network_ver, sys_preset ? preset_bundle : nullptr);
}
this->check_new_version_sf();
// Store builds update through the Microsoft Store, never self-update.
if (!is_running_in_msix())
this->check_new_version_sf();
const auto cloud_provider = get_printer_cloud_provider();
if (is_user_login(cloud_provider) && !app_config->get_stealth_mode()) {
// this->check_privacy_version(0);
@@ -9112,6 +9114,10 @@ static bool del_win_registry(HKEY hkeyHive, const wchar_t *pszVar, const wchar_t
void GUI_App::associate_files(std::wstring extend)
{
#ifdef WIN32
// MSIX: shell integration is declared in the package manifest; registry
// writes from a packaged process are virtualized and invisible to the shell.
if (is_running_in_msix())
return;
wchar_t app_path[MAX_PATH];
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
@@ -9137,6 +9143,8 @@ void GUI_App::associate_files(std::wstring extend)
void GUI_App::disassociate_files(std::wstring extend)
{
#ifdef WIN32
if (is_running_in_msix())
return;
wchar_t app_path[MAX_PATH];
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
@@ -9188,6 +9196,8 @@ bool GUI_App::check_url_association(std::wstring url_prefix, std::wstring& reg_b
void GUI_App::associate_url(std::wstring url_prefix)
{
#ifdef WIN32
if (is_running_in_msix())
return;
boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location()));
wxString wbinary = from_path(binary_path);
BOOST_LOG_TRIVIAL(info) << "Downloader registration: Path of binary: " << wbinary.ToUTF8().data();
@@ -9213,6 +9223,8 @@ void GUI_App::associate_url(std::wstring url_prefix)
void GUI_App::disassociate_url(std::wstring url_prefix)
{
#ifdef WIN32
if (is_running_in_msix())
return;
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
if (!key_full.Exists()) {
return;

View File

@@ -24,6 +24,7 @@
#include <wx/font.h>
#include <wx/fontutil.h>
#include <wx/display.h>
#include <wx/utils.h>
#include "libslic3r/Config.hpp"
@@ -171,6 +172,42 @@ template<class F> typename F::FN winapi_get_function(const wchar_t *dll, const c
}
#endif
bool is_running_in_msix()
{
#ifdef _WIN32
// The package identity APIs are Win8+ - resolved dynamically so the exe still loads on Win7
// (same treatment as the DPI APIs below). Null-buffer probe: returns ERROR_INSUFFICIENT_BUFFER
// when packaged, APPMODEL_ERROR_NO_PACKAGE when running unpackaged.
struct GetCurrentPackageFullName_t { typedef LONG (WINAPI *FN)(UINT32 *length, PWSTR full_name); };
static const bool packaged = []() {
auto fn = winapi_get_function<GetCurrentPackageFullName_t>(L"Kernel32.dll", "GetCurrentPackageFullName");
UINT32 length = 0;
return fn != nullptr && fn(&length, nullptr) != APPMODEL_ERROR_NO_PACKAGE;
}();
return packaged;
#else
return false;
#endif
}
void open_ms_store_product_page()
{
#ifdef _WIN32
struct GetCurrentPackageFamilyName_t { typedef LONG (WINAPI *FN)(UINT32 *length, PWSTR family_name); };
static auto fn = winapi_get_function<GetCurrentPackageFamilyName_t>(L"Kernel32.dll", "GetCurrentPackageFamilyName");
if (fn == nullptr)
return;
UINT32 length = 0;
if (fn(&length, nullptr) != ERROR_INSUFFICIENT_BUFFER)
return;
std::wstring family_name(length, L'\0');
if (fn(&length, family_name.data()) != ERROR_SUCCESS)
return;
family_name.resize(length > 0 ? length - 1 : 0); // drop the terminating null
wxLaunchDefaultBrowser(wxString(L"ms-windows-store://pdp/?PFN=") + family_name.c_str());
#endif
}
// If called with nullptr, a DPI for the primary monitor is returned.
int get_dpi_for_window(const wxWindow *window)
{

View File

@@ -67,6 +67,10 @@ wxDECLARE_EVENT(EVT_VOLUME_DETACHED, VolumeDetachedEvent);
wxTopLevelWindow* find_toplevel_parent(wxWindow *window);
wxString format_nozzle_diameter(float diameter);
// True when running inside an MSIX package (Microsoft Store build); always false on non-Windows.
bool is_running_in_msix();
// Opens the Microsoft Store product page for the current package. No-op when not packaged.
void open_ms_store_product_page();
void on_window_geometry(wxTopLevelWindow *tlw, std::function<void()> callback);

View File

@@ -2577,7 +2577,10 @@ static wxMenu* generate_help_menu()
// Check New Version
append_menu_item(helpMenu, wxID_ANY, _L("Check for Updates"), _L("Check for Updates"),
[](wxCommandEvent&) {
wxGetApp().check_new_version_sf(true, 1);
if (is_running_in_msix())
open_ms_store_product_page();
else
wxGetApp().check_new_version_sf(true, 1);
}, "", nullptr, []() {
return true;
});

View File

@@ -1846,6 +1846,9 @@ void PreferencesDialog::create_items()
//// ASSOCIATE TAB
/////////////////////////////////////
#ifdef _WIN32
// MSIX: associations are declared in the package manifest and defaults are
// managed by Windows Settings; the runtime registry toggles below cannot work.
if (!is_running_in_msix()) {
m_pref_tabs->AppendItem(_L("Associate"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
g_sizer = f_sizers.back();
@@ -1880,6 +1883,7 @@ void PreferencesDialog::create_items()
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
}
#endif // _WIN32
//////////////////////////