mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-06-11 06:23:08 +00:00
Compare commits
2 Commits
msix-store
...
nightly-bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93a82af6b4 | ||
|
|
06eefe7c1e |
2
.github/workflows/build_all.yml
vendored
2
.github/workflows/build_all.yml
vendored
@@ -14,7 +14,6 @@ on:
|
||||
- 'resources/**'
|
||||
- ".github/workflows/build_*.yml"
|
||||
- 'scripts/flatpak/**'
|
||||
- 'scripts/msix/**'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -31,7 +30,6 @@ on:
|
||||
- 'build_release_vs2022.bat'
|
||||
- 'build_release_macos.sh'
|
||||
- 'scripts/flatpak/**'
|
||||
- 'scripts/msix/**'
|
||||
|
||||
|
||||
schedule:
|
||||
|
||||
19
.github/workflows/build_orca.yml
vendored
19
.github/workflows/build_orca.yml
vendored
@@ -371,25 +371,6 @@ 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
|
||||
|
||||
3
.github/workflows/publish_release.yml
vendored
3
.github/workflows/publish_release.yml
vendored
@@ -73,9 +73,8 @@ 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_V*' \
|
||||
-p 'OrcaSlicer_Windows_*' \
|
||||
-p 'OrcaSlicer_Mac_universal_*' \
|
||||
-p 'OrcaSlicer_Linux_ubuntu_*' \
|
||||
-p 'OrcaSlicer-Linux-flatpak_*' \
|
||||
|
||||
@@ -1,645 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,190 +0,0 @@
|
||||
# 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 redirection (R4)** — Store apps must not self-update, but
|
||||
OrcaSlicer's version check is notification-only (it never
|
||||
auto-downloads), so the check itself stays enabled when packaged:
|
||||
|
||||
- The startup auto-check (`check_new_version_sf()`) and the manual
|
||||
"Check for updates" menu action run unchanged.
|
||||
- The new-version dialog (`UpdateVersionDialog`) changes when packaged:
|
||||
the Download button is hidden, the info text tells the user to update
|
||||
OrcaSlicer from the Microsoft Store, and the "Check on Github"
|
||||
hyperlink becomes "Open Microsoft Store", which opens the Store
|
||||
listing via `ms-windows-store://pdp/?PFN=<family>`. 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. The packaged
|
||||
build never opens the GitHub release page.
|
||||
|
||||
**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: version check runs as in the classic build (startup +
|
||||
menu); the new-version dialog hides the Download button, asks the
|
||||
user to update from the Microsoft Store, and its "Open Microsoft
|
||||
Store" link opens the 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.
|
||||
Binary file not shown.
@@ -1,75 +0,0 @@
|
||||
<?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.
|
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,67 +0,0 @@
|
||||
<#
|
||||
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"
|
||||
@@ -1,32 +0,0 @@
|
||||
# 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()
|
||||
@@ -807,6 +807,8 @@ wxAuiToolBarItem* BBLTopbar::FindToolByCurrentPosition()
|
||||
}
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#include <windowsx.h>
|
||||
|
||||
WXLRESULT CenteredTitle::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
|
||||
{
|
||||
switch (nMsg) {
|
||||
@@ -823,6 +825,8 @@ 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;
|
||||
|
||||
@@ -2859,11 +2859,7 @@ bool GUI_App::on_init_inner()
|
||||
switch (dialog.ShowModal())
|
||||
{
|
||||
case wxID_YES:
|
||||
// Store builds get updates from the Microsoft Store, not the GitHub release page.
|
||||
if (is_running_in_msix())
|
||||
open_ms_store_product_page();
|
||||
else
|
||||
wxLaunchDefaultBrowser(version_info.url);
|
||||
wxLaunchDefaultBrowser(version_info.url);
|
||||
break;
|
||||
case wxID_NO:
|
||||
break;
|
||||
@@ -9116,10 +9112,6 @@ 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));
|
||||
|
||||
@@ -9145,8 +9137,6 @@ 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));
|
||||
|
||||
@@ -9198,8 +9188,6 @@ 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();
|
||||
@@ -9225,8 +9213,6 @@ 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;
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <wx/font.h>
|
||||
#include <wx/fontutil.h>
|
||||
#include <wx/display.h>
|
||||
#include <wx/utils.h>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
|
||||
@@ -172,42 +171,6 @@ 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)
|
||||
{
|
||||
|
||||
@@ -67,10 +67,6 @@ 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);
|
||||
|
||||
|
||||
@@ -1846,9 +1846,6 @@ 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();
|
||||
@@ -1883,7 +1880,6 @@ void PreferencesDialog::create_items()
|
||||
|
||||
g_sizer->AddSpacer(FromDIP(10));
|
||||
sizer_page->Add(g_sizer, 0, wxEXPAND);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
//////////////////////////
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "GUI_Preview.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "format.hpp"
|
||||
@@ -253,9 +252,7 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent)
|
||||
m_text_up_info = new Label(this, Label::Head_14, wxEmptyString, LB_AUTO_WRAP);
|
||||
m_text_up_info->SetForegroundColour(wxColour(0x26, 0x2E, 0x30));
|
||||
|
||||
// Store builds get updates from the Microsoft Store: wxID_YES opens the Store
|
||||
// product page there (see the EVT_SLIC3R_VERSION_ONLINE handler) instead of GitHub.
|
||||
auto github_link = new HyperLink(this, is_running_in_msix() ? _L("Open Microsoft Store") : _L("Check on Github"), "", LB_AUTO_WRAP);
|
||||
auto github_link = new HyperLink(this, _L("Check on Github"), "", LB_AUTO_WRAP);
|
||||
github_link->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) {
|
||||
EndModal(wxID_YES);
|
||||
});
|
||||
@@ -312,9 +309,6 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent)
|
||||
EndModal(wxID_YES);
|
||||
});
|
||||
|
||||
if (is_running_in_msix())
|
||||
m_button_download->Hide();
|
||||
|
||||
m_button_skip_version = new Button(this, _L("Skip this Version"));
|
||||
m_button_skip_version->SetStyle(ButtonStyle::Regular, ButtonType::Choice);
|
||||
|
||||
@@ -485,10 +479,7 @@ void UpdateVersionDialog::update_version_info(wxString release_note, wxString ve
|
||||
// else {
|
||||
//m_simplebook_release_note->SetMaxSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
m_simplebook_release_note->SetSelection(1);
|
||||
if (is_running_in_msix())
|
||||
m_text_up_info->SetLabel(wxString::Format(_L("New version available: %s. Please update OrcaSlicer from the Microsoft Store."), version));
|
||||
else
|
||||
m_text_up_info->SetLabel(wxString::Format(_L("Click to download new version in default browser: %s"), version));
|
||||
m_text_up_info->SetLabel(wxString::Format(_L("Click to download new version in default browser: %s"), version));
|
||||
auto data_buf_in = release_note.utf8_str();
|
||||
auto bg_color = StateColor::darkModeColorFor(wxColour("#FFFFFF")).GetAsString();
|
||||
auto fg_color = StateColor::darkModeColorFor(wxColour("#262E30")).GetAsString();
|
||||
|
||||
Reference in New Issue
Block a user