Files
OrcaSlicer/src/slic3r/GUI/Widgets/WebView.cpp
SoftFever 451d2f946f feat: native Windows ARM64 build support (Snapdragon X Elite) (supersedes #14059) (#14381)
* feat: native Windows ARM64 build support

Builds on the merged DEPS_ARCH=arm64 plumbing (#13424) by adding the
dependency and source fixes needed for a green native ARM64 build on the
windows-11-arm runner. Validated end-to-end on Snapdragon X Elite hardware
(via a downstream fork using the same fixes); see OrcaSlicer/OrcaSlicer#8271
for the full writeup.

Dependencies:
- OpenEXR 2.5.5: ImfSimd.h hard-codes IMF_HAVE_SSE2 for any MSVC, pulling in
  <emmintrin.h> (x86-only) -> C1189. Patch the header to require an x86 target
  and force SSE cache vars off on ARM64.
- Boost.Context: use the winfib implementation on ARM64 (Windows Fiber API)
  to avoid the armasm64 / CMake ASM_ARMASM linker-module bug, while keeping
  the Boost::context target Boost.Asio needs.
- OpenCV: disable WITH_IPP on ARM64 (Intel IPP/IPP-ICV is x86/x64 only;
  otherwise ~200 unresolved ippicv* externals at link).
- OpenSSL: use VC-WIN64-ARM on ARM64.
- FindGLEW: add an ARM64 arch branch.

Sources:
- clipper Int128.hpp: _mul128 is an x64-only intrinsic guarded by _WIN64
  (true on ARM64); guard on _M_X64 and use the portable path.
- imgui imgui_widgets.cpp: fix va_start(vaList, &text) -> va_start(vaList, text)
  (the &-form compiled on x64 but is invalid on ARM64).
- crash reporter: StackWalker.cpp gains an _M_ARM64 branch; BaseException.cpp
  uses Cpsr instead of the x86-only EFlags on ARM64.

CI:
- New build_windows_arm64.yml on windows-11-arm: pins CMake 3.31.x, stages
  ARM64 GMP/MPFR from MSYS2 clangarm64 (with llvm-dlltool import libs),
  caches deps with a fixed-depth hashFiles key, builds and uploads the binary.

OCCT/STEP, SVG-to-3D and text emboss all build and work on ARM64 (no stubs
needed). Full feature parity with x64.

* fix(ci): use forward-slash DESTDIR to avoid CMake '\a' escape error

deps configure failed at GMP/GMP.cmake: "Invalid character escape '\a'"
because DESTDIR carried Windows backslashes (C:\a\...) and is re-parsed
when re-set with the /usr/local suffix. Pass DESTDIR (and the slicer's
DEPS prefix) with forward slashes via %CD:\=/%.

* fix(ci): don't export DESTDIR env var (CMake staged-install doubles paths)

Setting a DESTDIR *environment* variable made CMake treat it as the staged
install prefix and prepend it to every dependency's install path, so e.g.
FreeType installed to <DESTDIR>/a/.../OrcaSlicer_dep/usr/local and OCCT
then couldn't find its headers. Compute the forward-slash path into a
differently-named var (ORCA_DESTDIR) and pass it only via -DDESTDIR.

* ci(windows-arm64): fold ARM64 build into the standard Windows matrix

Replace the standalone build_windows_arm64.yml with a matrix entry on the
existing build_windows job, so x64 and ARM64 share one reusable workflow
chain (build_all -> build_check_cache -> build_deps -> build_orca), per
review feedback on #14059.

- build_all.yml: build_windows now matrices over {x64: windows-latest,
  arm64: windows-11-arm} and threads `arch` through. Self-hosted runner
  stays x64-only.
- build_check_cache.yml: cache key and dep-prefix path are now
  architecture-specific on Windows (deps/build-arm64/OrcaSlicer_dep).
- build_release_vs.bat: accept an `arm64` argument (mirrors
  build_release_vs2022.bat) -> uses `-A ARM64` and the build-arm64 tree.
  The top-level CMake auto-derives CMAKE_PREFIX_PATH from the build dir,
  so no explicit prefix is needed.
- build_deps.yml / build_orca.yml: gate the ARM64-only prep behind
  `inputs.arch == 'arm64'` -- pin CMake 3.31.x, and stage MSYS2
  clangarm64 GMP/MPFR import libs. NSIS installer/PDB/profile_validator
  remain x64-only; ARM64 ships the portable zip. Artifact names get an
  arch suffix to avoid collisions between the two Windows jobs.

https://claude.ai/code/session_0164c7ZhCLsYBmCiVN9pWDjK

* ci(temp): generate GMP/MPFR win-arm64 blobs to commit to repo

* feat(deps): add prebuilt GMP/MPFR win-arm64 blobs

The repo ships prebuilt GMP/MPFR import libs + DLLs for win-x64 and
win-x86; the Windows ARM64 build path copies from win-${DEPS_ARCH}
(CMakeLists.txt) but the win-arm64 blobs were missing, so the slicer
configure failed at "file COPY cannot find .../win-arm64/libgmp-10.dll".

Add win-arm64 libgmp-10.{dll,lib} and libmpfr-4.{dll,lib}, generated from
the MSYS2 clangarm64 gmp/mpfr packages with MSVC-compatible import libs via
llvm-dlltool. Headers are shared across arches and unchanged.

* simplify OpenEXR.cmake

* set default arch

* support msix

* ship installer

* try to fix webview2runtime issue

---------

Co-authored-by: Adam Behrman <adam.behrman@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Adam Behrman <abehrman@users.noreply.github.com>
2026-06-26 02:26:23 +08:00

413 lines
17 KiB
C++

#include "WebView.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/Utils/MacDarkMode.hpp"
#include <boost/log/trivial.hpp>
#include <wx/webviewarchivehandler.h>
#include <wx/webviewfshandler.h>
#if wxUSE_WEBVIEW_EDGE
#include <wx/msw/webview_edge.h>
#elif defined(__WXMAC__)
#include <wx/osx/webview_webkit.h>
#endif
#include <wx/uri.h>
#if defined(__WIN32__) || defined(__WXMAC__)
#include "wx/private/jsscriptwrapper.h"
#endif
#ifdef __WIN32__
#include <WebView2.h>
#include <Shellapi.h>
#include <slic3r/Utils/Http.hpp>
#elif defined __linux__
#include <gtk/gtk.h>
#define WEBKIT_API
struct WebKitWebView;
struct WebKitJavascriptResult;
extern "C" {
WEBKIT_API void
webkit_web_view_run_javascript (WebKitWebView *web_view,
const gchar *script,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
WEBKIT_API WebKitJavascriptResult *
webkit_web_view_run_javascript_finish (WebKitWebView *web_view,
GAsyncResult *result,
GError **error);
WEBKIT_API void
webkit_javascript_result_unref (WebKitJavascriptResult *js_result);
}
#endif
#ifdef __WIN32__
// Run Download and Install in another thread so we don't block the UI thread
DWORD DownloadAndInstallWV2RT() {
int returnCode = 2; // Download failed
// Use fwlink to download WebView2 Bootstrapper at runtime and invoke installation
// Broken/Invalid Https Certificate will fail to download
// Use of the download link below is governed by the below terms. You may acquire the link
// for your use at https://developer.microsoft.com/microsoft-edge/webview2/. Microsoft owns
// all legal right, title, and interest in and to the WebView2 Runtime Bootstrapper
// ("Software") and related documentation, including any intellectual property in the
// Software. You must acquire all code, including any code obtained from a Microsoft URL,
// under a separate license directly from Microsoft, including a Microsoft download site
// (e.g., https://developer.microsoft.com/microsoft-edge/webview2/).
// HRESULT hr = URLDownloadToFileW(NULL, L"https://go.microsoft.com/fwlink/p/?LinkId=2124703",
// L".\\plugin\\MicrosoftEdgeWebview2Setup.exe", 0, 0);
fs::path target_file_path = (fs::temp_directory_path() / "MicrosoftEdgeWebview2Setup.exe");
bool downloaded = false;
Slic3r::Http::get("https://go.microsoft.com/fwlink/p/?LinkId=2124703")
.on_error([](std::string body, std::string error, unsigned http_status) {
})
.on_complete([&downloaded, target_file_path](std::string body, unsigned http_status) {
fs::fstream file(target_file_path, std::ios::out | std::ios::binary | std::ios::trunc);
file.write(body.c_str(), body.size());
file.flush();
file.close();
downloaded = true;
})
.perform_sync();
// Sleep for 1 second to wait for the buffer writen into disk
std::this_thread::sleep_for(1000ms);
if (downloaded) {
// Either Package the WebView2 Bootstrapper with your app or download it using fwlink
// Then invoke install at Runtime.
// Keep the path string alive for the duration of the ShellExecuteExW call;
// assigning .c_str() of a temporary directly would leave lpFile dangling.
const std::wstring installer_path = target_file_path.generic_wstring();
SHELLEXECUTEINFOW shExInfo = {0};
shExInfo.cbSize = sizeof(shExInfo);
shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
shExInfo.hwnd = 0;
shExInfo.lpVerb = L"runas";
shExInfo.lpFile = installer_path.c_str();
shExInfo.lpParameters = L" /silent /install";
shExInfo.lpDirectory = 0;
shExInfo.nShow = 0;
shExInfo.hInstApp = 0;
if (ShellExecuteExW(&shExInfo)) {
WaitForSingleObject(shExInfo.hProcess, INFINITE);
returnCode = 0; // Install successfull
} else {
returnCode = 1; // Install failed
}
}
return returnCode;
}
class WebViewEdge : public wxWebViewEdge
{
public:
bool SetUserAgent(const wxString &userAgent)
{
bool dark = userAgent.Contains("dark");
SetColorScheme(dark ? COREWEBVIEW2_PREFERRED_COLOR_SCHEME_DARK : COREWEBVIEW2_PREFERRED_COLOR_SCHEME_LIGHT);
ICoreWebView2 *webView2 = (ICoreWebView2 *) GetNativeBackend();
if (webView2) {
ICoreWebView2Settings *settings;
HRESULT hr = webView2->get_Settings(&settings);
if (hr == S_OK) {
ICoreWebView2Settings2 *settings2;
hr = settings->QueryInterface(&settings2);
if (hr == S_OK) {
settings2->put_UserAgent(userAgent.wc_str());
settings2->Release();
return true;
}
}
settings->Release();
return false;
}
pendingUserAgent = userAgent;
return true;
}
bool SetColorScheme(COREWEBVIEW2_PREFERRED_COLOR_SCHEME colorScheme)
{
ICoreWebView2 *webView2 = (ICoreWebView2 *) GetNativeBackend();
if (webView2) {
ICoreWebView2_13 * webView2_13;
HRESULT hr = webView2->QueryInterface(&webView2_13);
if (hr == S_OK) {
ICoreWebView2Profile *profile;
hr = webView2_13->get_Profile(&profile);
if (hr == S_OK) {
profile->put_PreferredColorScheme(colorScheme);
profile->Release();
return true;
}
webView2_13->Release();
}
return false;
}
pendingColorScheme = colorScheme;
return true;
}
void DoGetClientSize(int *x, int *y) const override
{
if (!pendingUserAgent.empty()) {
auto thiz = const_cast<WebViewEdge *>(this);
auto userAgent = std::move(thiz->pendingUserAgent);
thiz->pendingUserAgent.clear();
thiz->SetUserAgent(userAgent);
}
if (pendingColorScheme) {
auto thiz = const_cast<WebViewEdge *>(this);
auto colorScheme = pendingColorScheme;
thiz->pendingColorScheme = COREWEBVIEW2_PREFERRED_COLOR_SCHEME_AUTO;
thiz->SetColorScheme(colorScheme);
}
wxWebViewEdge::DoGetClientSize(x, y);
};
private:
wxString pendingUserAgent;
COREWEBVIEW2_PREFERRED_COLOR_SCHEME pendingColorScheme = COREWEBVIEW2_PREFERRED_COLOR_SCHEME_AUTO;
};
#elif defined __WXOSX__
class WebViewWebKit : public wxWebViewWebKit
{
public:
WebViewWebKit()
: wxWebViewWebKit(wxWebView::NewConfiguration(wxWebViewBackendWebKit))
{
}
~WebViewWebKit() override
{
RemoveScriptMessageHandler("wx");
}
};
#endif
class FakeWebView : public wxWebView
{
virtual bool Create(wxWindow* parent, wxWindowID id, const wxString& url, const wxPoint& pos, const wxSize& size, long style, const wxString& name) override { return false; }
virtual wxString GetCurrentTitle() const override { return wxString(); }
virtual wxString GetCurrentURL() const override { return wxString(); }
virtual bool IsBusy() const override { return false; }
virtual bool IsEditable() const override { return false; }
virtual void LoadURL(const wxString& url) override { }
virtual void Print() override { }
virtual void RegisterHandler(wxSharedPtr<wxWebViewHandler> handler) override { }
virtual void Reload(wxWebViewReloadFlags flags = wxWEBVIEW_RELOAD_DEFAULT) override { }
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const override { return false; }
virtual void SetEditable(bool enable = true) override { }
virtual void Stop() override { }
virtual bool CanGoBack() const override { return false; }
virtual bool CanGoForward() const override { return false; }
virtual void GoBack() override { }
virtual void GoForward() override { }
virtual void ClearHistory() override { }
virtual void EnableHistory(bool enable = true) override { }
virtual wxVector<wxSharedPtr<wxWebViewHistoryItem>> GetBackwardHistory() override { return {}; }
virtual wxVector<wxSharedPtr<wxWebViewHistoryItem>> GetForwardHistory() override { return {}; }
virtual void LoadHistoryItem(wxSharedPtr<wxWebViewHistoryItem> item) override { }
virtual bool CanSetZoomType(wxWebViewZoomType type) const override { return false; }
virtual float GetZoomFactor() const override { return 0.0f; }
virtual wxWebViewZoomType GetZoomType() const override { return wxWebViewZoomType(); }
virtual void SetZoomFactor(float zoom) override { }
virtual void SetZoomType(wxWebViewZoomType zoomType) override { }
virtual bool CanUndo() const override { return false; }
virtual bool CanRedo() const override { return false; }
virtual void Undo() override { }
virtual void Redo() override { }
virtual void* GetNativeBackend() const override { return nullptr; }
virtual void DoSetPage(const wxString& html, const wxString& baseUrl) override { }
};
wxDEFINE_EVENT(EVT_WEBVIEW_RECREATED, wxCommandEvent);
static std::vector<wxWebView*> g_webviews;
static std::vector<wxWebView*> g_delay_webviews;
class WebViewRef : public wxObjectRefData
{
public:
WebViewRef(wxWebView *webView) : m_webView(webView) {}
~WebViewRef() {
auto iter = std::find(g_webviews.begin(), g_webviews.end(), m_webView);
assert(iter != g_webviews.end());
if (iter != g_webviews.end())
g_webviews.erase(iter);
}
wxWebView *m_webView;
};
wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
{
#if wxUSE_WEBVIEW_EDGE
// Check if a fixed version of edge is present in
// $executable_path/edge_fixed and use it
wxFileName edgeFixedDir(wxStandardPaths::Get().GetExecutablePath());
edgeFixedDir.SetFullName("");
edgeFixedDir.AppendDir("edge_fixed");
if (edgeFixedDir.DirExists()) {
wxWebViewEdge::MSWSetBrowserExecutableDir(edgeFixedDir.GetFullPath());
wxLogMessage("Using fixed edge version");
}
#endif
auto url2 = url;
#ifdef __WIN32__
url2.Replace("\\", "/");
#endif
if (!url2.empty()) { url2 = wxURI(url2).BuildURI(); }
BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << ": " << url2.ToUTF8();
#ifdef __WIN32__
wxWebView* webView = new WebViewEdge;
#elif defined(__WXOSX__)
wxWebView* webView = new WebViewWebKit;
#else
auto webView = wxWebView::New();
#endif
if (webView) {
webView->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
wxString language_code = Slic3r::GUI::wxGetApp().current_language_code().BeforeFirst('_');
language_code = language_code.ToStdString();
#ifdef __WIN32__
webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52 BBL-Slicer/v%s (%s) BBL-Language/%s",
Slic3r::GUI::wxGetApp().get_bbl_client_version(), Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str()));
webView->Create(parent, wxID_ANY, url2, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
// We register the wxfs:// protocol for testing purposes
webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("bbl")));
// And the memory: file system
webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
#else
// With WKWebView handlers need to be registered before creation.
// On Linux (WebKit2GTK), URI schemes are registered globally and can only
// be registered once, so guard against multiple registrations.
static bool s_schemes_registered = false;
if (!s_schemes_registered) {
webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
s_schemes_registered = true;
}
webView->Create(parent, wxID_ANY, url2, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) BBL-Slicer/v%s (%s) BBL-Language/%s",
Slic3r::GUI::wxGetApp().get_bbl_client_version(), Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str()));
#endif
#ifdef __WXMAC__
WKWebView * wkWebView = (WKWebView *) webView->GetNativeBackend();
Slic3r::GUI::WKWebView_setTransparentBackground(wkWebView);
#endif
auto addScriptMessageHandler = [] (wxWebView *webView) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": begin to add script message handler for wx.";
Slic3r::GUI::wxGetApp().set_adding_script_handler(true);
if (!webView->AddScriptMessageHandler("wx"))
wxLogError("Could not add script message handler");
Slic3r::GUI::wxGetApp().set_adding_script_handler(false);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": finished add script message handler for wx.";
};
#ifndef __WIN32__
webView->CallAfter([webView, addScriptMessageHandler] {
#endif
if (Slic3r::GUI::wxGetApp().is_adding_script_handler()) {
g_delay_webviews.push_back(webView);
} else {
addScriptMessageHandler(webView);
while (!g_delay_webviews.empty()) {
auto views = std::move(g_delay_webviews);
for (auto wv : views)
addScriptMessageHandler(wv);
}
}
#ifndef __WIN32__
});
#endif
webView->EnableContextMenu(true);
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": failed. Use fake web view.";
webView = new FakeWebView;
}
webView->SetRefData(new WebViewRef(webView));
g_webviews.push_back(webView);
return webView;
}
#if wxUSE_WEBVIEW_EDGE
bool WebView::CheckWebViewRuntime()
{
wxWebViewFactoryEdge factory;
auto wxVersion = factory.GetVersionInfo(wxVersionContext::RunTime);
bool present = wxVersion.GetMajor() != 0;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": WebView2 runtime "
<< (present ? "found, version " : "not found (")
<< wxVersion.ToString().ToUTF8().data() << (present ? "" : ")");
return present;
}
bool WebView::DownloadAndInstallWebViewRuntime()
{
return DownloadAndInstallWV2RT() == 0;
}
#endif
void WebView::LoadUrl(wxWebView * webView, wxString const &url)
{
auto url2 = url;
#ifdef __WIN32__
url2.Replace("\\", "/");
#endif
if (!url2.empty()) { url2 = wxURI(url2).BuildURI(); }
BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << url2.ToUTF8();
webView->LoadURL(url2);
}
bool WebView::RunScript(wxWebView *webView, wxString const &javascript)
{
if (Slic3r::GUI::wxGetApp().app_config->get("internal_developer_mode") == "true"
&& javascript.find("studio_userlogin") == wxString::npos)
wxLogMessage("Running JavaScript:\n%s\n", javascript);
try {
#ifdef __WIN32__
ICoreWebView2 * webView2 = (ICoreWebView2 *) webView->GetNativeBackend();
if (webView2 == nullptr)
return false;
return webView2->ExecuteScript(javascript, NULL) == 0;
#elif defined __WXMAC__
WKWebView * wkWebView = (WKWebView *) webView->GetNativeBackend();
Slic3r::GUI::WKWebView_evaluateJavaScript(wkWebView, javascript, nullptr);
return true;
#else
WebKitWebView *wkWebView = (WebKitWebView *) webView->GetNativeBackend();
webkit_web_view_run_javascript(
wkWebView, javascript.utf8_str(), NULL,
[](GObject *wkWebView, GAsyncResult *res, void *) {
GError * error = NULL;
auto result = webkit_web_view_run_javascript_finish((WebKitWebView*)wkWebView, res, &error);
if (!result)
g_error_free (error);
else
webkit_javascript_result_unref (result);
}, NULL);
return true;
#endif
} catch (std::exception &) {
return false;
}
}
void WebView::RecreateAll()
{
auto dark = Slic3r::GUI::wxGetApp().dark_mode();
wxString language_code = Slic3r::GUI::wxGetApp().current_language_code().BeforeFirst('_');
language_code = language_code.ToStdString();
for (auto webView : g_webviews) {
webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) BBL-Slicer/v%s (%s) BBL-Language/%s",
Slic3r::GUI::wxGetApp().get_bbl_client_version(), dark ? "dark" : "light", language_code.mb_str()));
webView->Reload();
}
}