From 15f330641c6b8aa6d9225319de424d9039dcfd2a Mon Sep 17 00:00:00 2001 From: SoftFever Date: Thu, 11 Jun 2026 23:56:16 +0800 Subject: [PATCH] Add Microsoft Store MSIX package build (#14142) * docs: add MSIX Store build design spec * docs: update MSIX spec (PFN deep link, .drc, Associate tab) and add implementation plan * ci: add MSIX logo asset generator and generated assets * ci: fix MSIX asset rendering edge bleed (PixelOffsetMode) and make output order deterministic * ci: add MSIX AppxManifest template * ci: add MSIX packaging script * ci: make build_msix.ps1 stage-only exit dot-source safe * ci: build MSIX Store package in Windows job * ci: run MSIX pack after existing Windows uploads and keep it out of release downloads * feat: add MSIX packaged-context detection helpers * fix: resolve MSIX package APIs dynamically to keep Win7 loadable * feat: suppress self-update in MSIX Store build * feat: suppress runtime file associations in MSIX Store build * feat: keep version check in MSIX build, point update dialog at the Store The update check is notification-only (OrcaSlicer never auto-downloads), so the Store build keeps checking for new versions instead of skipping the check. What changes when packaged is the new-version dialog: the Download button is hidden, the info text asks the user to update from the Microsoft Store, and the hyperlink / wxID_YES action opens the Store product page instead of the GitHub release page. * docs: align spec verification plan with Store-redirect updater behavior * feat: default MSIX identity to the reserved Partner Center values * feat: render MSIX logos full-bleed from the gradient-circle SVG * feat: point update dialog Download button at the Store in MSIX builds * feat: link Associate tab to Windows Default Apps settings in MSIX builds * docs: align spec with review-driven logo, dialog and Associate-tab changes * clearn up --- .github/workflows/build_all.yml | 2 + .github/workflows/build_orca.yml | 19 +++++ .github/workflows/publish_release.yml | 3 +- scripts/msix/AppxManifest.xml | 75 ++++++++++++++++++ scripts/msix/assets/Square150x150Logo.png | Bin 0 -> 20935 bytes scripts/msix/assets/Square44x44Logo.png | Bin 0 -> 3538 bytes ...x44Logo.targetsize-44_altform-unplated.png | Bin 0 -> 3538 bytes scripts/msix/assets/StoreLogo.png | Bin 0 -> 4339 bytes scripts/msix/build_msix.ps1 | 67 ++++++++++++++++ scripts/msix/generate_assets.ps1 | 56 +++++++++++++ src/slic3r/GUI/GUI_App.cpp | 16 +++- src/slic3r/GUI/GUI_Utils.cpp | 37 +++++++++ src/slic3r/GUI/GUI_Utils.hpp | 4 + src/slic3r/GUI/Preferences.cpp | 21 +++++ src/slic3r/GUI/ReleaseNote.cpp | 12 ++- 15 files changed, 307 insertions(+), 5 deletions(-) create mode 100644 scripts/msix/AppxManifest.xml create mode 100644 scripts/msix/assets/Square150x150Logo.png create mode 100644 scripts/msix/assets/Square44x44Logo.png create mode 100644 scripts/msix/assets/Square44x44Logo.targetsize-44_altform-unplated.png create mode 100644 scripts/msix/assets/StoreLogo.png create mode 100644 scripts/msix/build_msix.ps1 create mode 100644 scripts/msix/generate_assets.ps1 diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 79ffafa0f1..0788832d5c 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -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: diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index 7ccb52b0e6..1e8588a373 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -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.OrcaSlicer' }}" ` + -Publisher "${{ vars.ORCA_MSIX_PUBLISHER || 'CN=38F7EA55-C73B-4072-B3B2-C8E0EA15BB82' }}" ` + -PublisherDisplayName "${{ vars.ORCA_MSIX_PUBLISHER_DISPLAY_NAME || 'OrcaSlicer' }}" + + - 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 diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 7c2999e872..98514d99db 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -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_*' \ diff --git a/scripts/msix/AppxManifest.xml b/scripts/msix/AppxManifest.xml new file mode 100644 index 0000000000..56477bf80a --- /dev/null +++ b/scripts/msix/AppxManifest.xml @@ -0,0 +1,75 @@ + + + + + + + OrcaSlicer + @MSIX_PUBLISHER_DISPLAY_NAME@ + Assets\StoreLogo.png + + disabled + + + $(KnownFolder:RoamingAppData)\OrcaSlicer + + + + + + + + + + + + + + + + + + + + .3mf + .stl + .step + .stp + .gcode + .drc + + + Orca.Slicer.1 + + + + + + + + + + + + + + + diff --git a/scripts/msix/assets/Square150x150Logo.png b/scripts/msix/assets/Square150x150Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..546b5c57f9badb7497e2ce3d70190ebaeb7678be GIT binary patch literal 20935 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�rrMI3-znsrz{NiQsI0_V<+Lc&5624TgN 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 literal 0 HcmV?d00001 diff --git a/scripts/msix/build_msix.ps1 b/scripts/msix/build_msix.ps1 new file mode 100644 index 0000000000..55e9fc156f --- /dev/null +++ b/scripts/msix/build_msix.ps1 @@ -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 \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.OrcaSlicer", + [string]$Publisher = "CN=38F7EA55-C73B-4072-B3B2-C8E0EA15BB82", + [string]$PublisherDisplayName = "OrcaSlicer" +) +$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" diff --git a/scripts/msix/generate_assets.ps1 b/scripts/msix/generate_assets.ps1 new file mode 100644 index 0000000000..52071cb7be --- /dev/null +++ b/scripts/msix/generate_assets.ps1 @@ -0,0 +1,56 @@ +# Generates the MSIX package logo assets from the master vector logo +# (resources\images\OrcaSlicer_gradient_circle.svg). Each PNG is rendered from +# the SVG at its exact target size (true per-size vector rasterization, not +# downscaled from one bitmap), preserving alpha transparency in the corners +# outside the circle (the manifest uses BackgroundColor="transparent"). +# +# Run once locally on Windows (re-run only if the logo changes), then commit +# the PNGs in assets/. CI never runs this script. +# +# Prerequisite: Python 3 with the resvg-py package (pip install resvg-py). +# It bundles the resvg SVG renderer, needed because the master SVG uses +# gradients with alpha-fade stops that System.Drawing cannot rasterize. +param( + [string]$Python = 'python' +) +$ErrorActionPreference = 'Stop' + +$repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent +$source = Join-Path $repoRoot 'resources\images\OrcaSlicer_gradient_circle.svg' +$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 +} + +$py = @' +import sys +from pathlib import Path + +import resvg_py + +svg, out_dir = sys.argv[1], Path(sys.argv[2]) +for spec in sys.argv[3:]: + name, px = spec.rsplit('=', 1) + px = int(px) + data = resvg_py.svg_to_bytes(svg_path=svg, width=px, height=px) + (out_dir / name).write_bytes(bytes(data)) + print(f'Wrote {name} ({px}x{px})') +'@ + +$renderScript = Join-Path $env:TEMP 'orca_msix_render.py' +Set-Content -Path $renderScript -Value $py -Encoding utf8 +try { + $specs = foreach ($name in $sizes.Keys) { "$name=$($sizes[$name])" } + & $Python $renderScript $source $outDir @specs + if ($LASTEXITCODE -ne 0) { + throw 'resvg render failed. Is resvg-py installed? (pip install resvg-py)' + } +} +finally { + Remove-Item $renderScript -ErrorAction SilentlyContinue +} diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 53c987fabb..4b35274867 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2859,7 +2859,11 @@ bool GUI_App::on_init_inner() switch (dialog.ShowModal()) { case wxID_YES: - wxLaunchDefaultBrowser(version_info.url); + // 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); break; case wxID_NO: break; @@ -9112,6 +9116,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 +9145,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 +9198,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 +9225,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; diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index a16d9916a6..697eefea0b 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "libslic3r/Config.hpp" @@ -171,6 +172,42 @@ template 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(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(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) { diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index e50c9254a9..d6767d3310 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -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 callback); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 1e6b1f6558..ef3d8fcee5 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1846,6 +1846,26 @@ 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. + // Show a minimal page that sends the user to Windows' Default Apps settings instead. + 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(); + g_sizer->AddGrowableCol(0, 1); + + g_sizer->Add(create_item_title(_L("Associate files to OrcaSlicer")), 1, wxEXPAND); + + auto item_open_default_apps = create_item_button( + _L("File associations for the Microsoft Store version are managed by Windows Settings."), + _L("Open Windows Default Apps Settings"), "", "", + []() { wxLaunchDefaultBrowser("ms-settings:defaultapps"); }); + g_sizer->Add(item_open_default_apps); + + g_sizer->AddSpacer(FromDIP(10)); + sizer_page->Add(g_sizer, 0, wxEXPAND); + } else { m_pref_tabs->AppendItem(_L("Associate")); f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0)); g_sizer = f_sizers.back(); @@ -1880,6 +1900,7 @@ void PreferencesDialog::create_items() g_sizer->AddSpacer(FromDIP(10)); sizer_page->Add(g_sizer, 0, wxEXPAND); + } #endif // _WIN32 ////////////////////////// diff --git a/src/slic3r/GUI/ReleaseNote.cpp b/src/slic3r/GUI/ReleaseNote.cpp index 3dd87e8626..84546a941d 100644 --- a/src/slic3r/GUI/ReleaseNote.cpp +++ b/src/slic3r/GUI/ReleaseNote.cpp @@ -5,6 +5,7 @@ #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" @@ -252,7 +253,9 @@ 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)); - auto github_link = new HyperLink(this, _L("Check on Github"), "", LB_AUTO_WRAP); + // 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("Check on Microsoft Store") : _L("Check on Github"), "", LB_AUTO_WRAP); github_link->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { EndModal(wxID_YES); }); @@ -302,7 +305,7 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent) auto sizer_button = new wxBoxSizer(wxHORIZONTAL); - m_button_download = new Button(this, _L("Download")); + m_button_download = new Button(this, is_running_in_msix() ? _L("Open Microsoft Store") : _L("Download")); m_button_download->SetStyle(ButtonStyle::Confirm, ButtonType::Choice); m_button_download->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { @@ -479,7 +482,10 @@ 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); - m_text_up_info->SetLabel(wxString::Format(_L("Click to download new version in default browser: %s"), version)); + 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)); auto data_buf_in = release_note.utf8_str(); auto bg_color = StateColor::darkModeColorFor(wxColour("#FFFFFF")).GetAsString(); auto fg_color = StateColor::darkModeColorFor(wxColour("#262E30")).GetAsString();