mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-02 15:52:14 +00:00
feat: windows impl
This commit is contained in:
@@ -11,7 +11,21 @@
|
|||||||
|
|
||||||
#include <wx/event.h>
|
#include <wx/event.h>
|
||||||
|
|
||||||
|
#ifdef __WIN32__
|
||||||
|
#include <WebView2.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
#ifdef __VISUALC__
|
||||||
|
#include <wrl/event.h>
|
||||||
|
using Microsoft::WRL::Callback;
|
||||||
|
#else
|
||||||
|
#include <wx/msw/private/comptr.h>
|
||||||
|
#include <wx/msw/wrl/event.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
@@ -280,8 +294,261 @@ void enable_plugin_download_redirect(wxWebView* view, wxString target_dir, Plugi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#elif defined(__WIN32__)
|
#elif defined(__WIN32__)
|
||||||
void enable_plugin_download_redirect(wxWebView*, wxString, PluginDownloadCallback)
|
|
||||||
{ throw std::runtime_error("Plugin webview download redirection is not implemented on Windows."); }
|
std::wstring sanitize_plugin_download_filename(const wchar_t* suggested)
|
||||||
|
{
|
||||||
|
std::wstring name;
|
||||||
|
try {
|
||||||
|
name = boost::filesystem::path(suggested ? suggested : L"").filename().wstring();
|
||||||
|
} catch (...) {
|
||||||
|
name.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (wchar_t& c : name) {
|
||||||
|
if (c < 0x20 || c == L'<' || c == L'>' || c == L':' || c == L'"' || c == L'/' || c == L'\\' ||
|
||||||
|
c == L'|' || c == L'?' || c == L'*')
|
||||||
|
c = L'_';
|
||||||
|
}
|
||||||
|
while (!name.empty() && (name.back() == L' ' || name.back() == L'.'))
|
||||||
|
name.pop_back();
|
||||||
|
|
||||||
|
if (name.empty() || name == L"." || name == L"..")
|
||||||
|
name = L"download";
|
||||||
|
|
||||||
|
// Windows reserves these device names even when an extension is present.
|
||||||
|
const std::wstring stem = boost::filesystem::path(name).stem().wstring();
|
||||||
|
const std::wstring upper_stem = [&stem] {
|
||||||
|
std::wstring value = stem;
|
||||||
|
for (wchar_t& c : value) {
|
||||||
|
if (c >= L'a' && c <= L'z')
|
||||||
|
c -= L'a' - L'A';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}();
|
||||||
|
if (upper_stem == L"CON" || upper_stem == L"PRN" || upper_stem == L"AUX" || upper_stem == L"NUL" ||
|
||||||
|
(upper_stem.size() == 4 && (upper_stem.substr(0, 3) == L"COM" || upper_stem.substr(0, 3) == L"LPT") &&
|
||||||
|
upper_stem[3] >= L'1' && upper_stem[3] <= L'9'))
|
||||||
|
name.insert(0, L"_");
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::filesystem::path unique_plugin_download_path(const boost::filesystem::path& dir, const std::wstring& filename)
|
||||||
|
{
|
||||||
|
const boost::filesystem::path base(filename);
|
||||||
|
const std::wstring stem = base.stem().wstring();
|
||||||
|
const std::wstring ext = base.extension().wstring();
|
||||||
|
boost::filesystem::path candidate = dir / filename;
|
||||||
|
for (int n = 1; boost::filesystem::exists(candidate); ++n)
|
||||||
|
candidate = dir / (stem + L" (" + std::to_wstring(n) + L")" + ext);
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PluginDownloadRedirectConfig
|
||||||
|
{
|
||||||
|
wxString target_dir;
|
||||||
|
PluginDownloadCallback callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PluginDownloadSession
|
||||||
|
{
|
||||||
|
wxString target_dir;
|
||||||
|
PluginDownloadCallback callback;
|
||||||
|
bool notified{false};
|
||||||
|
wxString resolved_filename;
|
||||||
|
wxString resolved_path;
|
||||||
|
wxString mime_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
void notify_plugin_download(PluginDownloadSession& session, nlohmann::json info)
|
||||||
|
{
|
||||||
|
if (session.notified)
|
||||||
|
return;
|
||||||
|
session.notified = true;
|
||||||
|
if (session.callback) {
|
||||||
|
try {
|
||||||
|
session.callback(info);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "PluginWebDialog: download callback failed: " << e.what();
|
||||||
|
} catch (...) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "PluginWebDialog: download callback failed.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fail_plugin_download(PluginDownloadSession& session, const std::string& error)
|
||||||
|
{
|
||||||
|
notify_plugin_download(session,
|
||||||
|
{{"filename", std::string(session.resolved_filename.utf8_string())},
|
||||||
|
{"path", ""},
|
||||||
|
{"mimeType", std::string(session.mime_type.utf8_string())},
|
||||||
|
{"size", 0},
|
||||||
|
{"success", false},
|
||||||
|
{"error", error}});
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT on_plugin_download_state_changed(const std::shared_ptr<PluginDownloadSession>& session,
|
||||||
|
ICoreWebView2DownloadOperation* operation)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
COREWEBVIEW2_DOWNLOAD_STATE state = COREWEBVIEW2_DOWNLOAD_STATE_IN_PROGRESS;
|
||||||
|
if (!operation || FAILED(operation->get_State(&state)))
|
||||||
|
return S_OK;
|
||||||
|
if (state == COREWEBVIEW2_DOWNLOAD_STATE_IN_PROGRESS)
|
||||||
|
return S_OK;
|
||||||
|
|
||||||
|
INT64 bytes_received = 0;
|
||||||
|
operation->get_BytesReceived(&bytes_received);
|
||||||
|
LPWSTR result_path = nullptr;
|
||||||
|
operation->get_ResultFilePath(&result_path);
|
||||||
|
if (result_path) {
|
||||||
|
session->resolved_path = wxString(result_path);
|
||||||
|
CoTaskMemFree(result_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == COREWEBVIEW2_DOWNLOAD_STATE_COMPLETED) {
|
||||||
|
notify_plugin_download(*session,
|
||||||
|
{{"filename", std::string(session->resolved_filename.utf8_string())},
|
||||||
|
{"path", std::string(session->resolved_path.utf8_string())},
|
||||||
|
{"mimeType", std::string(session->mime_type.utf8_string())},
|
||||||
|
{"size", bytes_received},
|
||||||
|
{"success", true},
|
||||||
|
{"error", ""}});
|
||||||
|
} else {
|
||||||
|
COREWEBVIEW2_DOWNLOAD_INTERRUPT_REASON reason =
|
||||||
|
COREWEBVIEW2_DOWNLOAD_INTERRUPT_REASON_NONE;
|
||||||
|
operation->get_InterruptReason(&reason);
|
||||||
|
fail_plugin_download(*session, "Download interrupted (" + std::to_string(static_cast<int>(reason)) + ").");
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
fail_plugin_download(*session, e.what());
|
||||||
|
} catch (...) {
|
||||||
|
fail_plugin_download(*session, "Download failed.");
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel_plugin_download(ICoreWebView2DownloadStartingEventArgs* args, ICoreWebView2DownloadOperation* operation)
|
||||||
|
{
|
||||||
|
if (args)
|
||||||
|
args->put_Cancel(TRUE);
|
||||||
|
if (args)
|
||||||
|
args->put_Handled(TRUE);
|
||||||
|
if (operation)
|
||||||
|
operation->Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT on_plugin_download_starting(ICoreWebView2DownloadStartingEventArgs* args,
|
||||||
|
const std::shared_ptr<PluginDownloadRedirectConfig>& config)
|
||||||
|
{
|
||||||
|
ICoreWebView2DownloadOperation* operation = nullptr;
|
||||||
|
auto session = std::make_shared<PluginDownloadSession>();
|
||||||
|
session->target_dir = config->target_dir;
|
||||||
|
session->callback = config->callback;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!args || FAILED(args->get_DownloadOperation(&operation)))
|
||||||
|
throw std::runtime_error("Could not initialize the download operation.");
|
||||||
|
|
||||||
|
LPWSTR suggested_path = nullptr;
|
||||||
|
args->get_ResultFilePath(&suggested_path);
|
||||||
|
const std::wstring filename = sanitize_plugin_download_filename(suggested_path);
|
||||||
|
if (suggested_path)
|
||||||
|
CoTaskMemFree(suggested_path);
|
||||||
|
session->resolved_filename = wxString(filename);
|
||||||
|
|
||||||
|
LPWSTR mime_type = nullptr;
|
||||||
|
if (SUCCEEDED(operation->get_MimeType(&mime_type)) && mime_type) {
|
||||||
|
session->mime_type = wxString(mime_type);
|
||||||
|
CoTaskMemFree(mime_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session->target_dir.empty())
|
||||||
|
throw std::runtime_error("Plugin storage directory is unavailable.");
|
||||||
|
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
const fs::path dir(std::wstring(session->target_dir.wc_str()));
|
||||||
|
fs::create_directories(dir);
|
||||||
|
if (!fs::is_directory(dir))
|
||||||
|
throw std::runtime_error("Could not create the plugin storage directory.");
|
||||||
|
|
||||||
|
const fs::path destination = unique_plugin_download_path(dir, filename);
|
||||||
|
session->resolved_path = wxString(destination.wstring());
|
||||||
|
if (FAILED(args->put_ResultFilePath(destination.wstring().c_str())))
|
||||||
|
throw std::runtime_error("Could not set the plugin download destination.");
|
||||||
|
|
||||||
|
auto state_handler = Callback<ICoreWebView2StateChangedEventHandler>(
|
||||||
|
[session](ICoreWebView2DownloadOperation* download, IUnknown*) {
|
||||||
|
return on_plugin_download_state_changed(session, download);
|
||||||
|
});
|
||||||
|
EventRegistrationToken token{};
|
||||||
|
const HRESULT state_result = operation->add_StateChanged(state_handler.Get(), &token);
|
||||||
|
if (FAILED(state_result))
|
||||||
|
throw std::runtime_error("Could not monitor the plugin download.");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
cancel_plugin_download(args, operation);
|
||||||
|
fail_plugin_download(*session, e.what());
|
||||||
|
} catch (...) {
|
||||||
|
cancel_plugin_download(args, operation);
|
||||||
|
fail_plugin_download(*session, "Could not redirect the plugin download.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation)
|
||||||
|
operation->Release();
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void attach_plugin_download_redirect(wxWebView* view, const std::shared_ptr<PluginDownloadRedirectConfig>& config)
|
||||||
|
{
|
||||||
|
auto* web_view = static_cast<ICoreWebView2*>(view->GetNativeBackend());
|
||||||
|
if (!web_view)
|
||||||
|
throw std::runtime_error("WebView2 is not ready for download redirection.");
|
||||||
|
|
||||||
|
ICoreWebView2_4* web_view_4 = nullptr;
|
||||||
|
const HRESULT query_result = web_view->QueryInterface(IID_ICoreWebView2_4,
|
||||||
|
reinterpret_cast<void**>(&web_view_4));
|
||||||
|
if (FAILED(query_result) || !web_view_4)
|
||||||
|
throw std::runtime_error("WebView2 download interception is unavailable.");
|
||||||
|
|
||||||
|
auto handler = Callback<ICoreWebView2DownloadStartingEventHandler>(
|
||||||
|
[config](ICoreWebView2*, ICoreWebView2DownloadStartingEventArgs* args) {
|
||||||
|
try {
|
||||||
|
return on_plugin_download_starting(args, config);
|
||||||
|
} catch (...) {
|
||||||
|
// COM event callbacks must not allow exceptions to escape into WebView2.
|
||||||
|
if (args)
|
||||||
|
args->put_Cancel(TRUE);
|
||||||
|
return E_FAIL;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
EventRegistrationToken token{};
|
||||||
|
const HRESULT add_result = web_view_4->add_DownloadStarting(handler.Get(), &token);
|
||||||
|
web_view_4->Release();
|
||||||
|
if (FAILED(add_result))
|
||||||
|
throw std::runtime_error("Could not register WebView2 download interception.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void enable_plugin_download_redirect(wxWebView* view, wxString target_dir, PluginDownloadCallback callback)
|
||||||
|
{
|
||||||
|
if (!view)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto config = std::make_shared<PluginDownloadRedirectConfig>(PluginDownloadRedirectConfig{std::move(target_dir), std::move(callback)});
|
||||||
|
if (view->GetNativeBackend()) {
|
||||||
|
attach_plugin_download_redirect(view, config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebView2 creates its native controller asynchronously. The created event is
|
||||||
|
// queued by wxWidgets after GetNativeBackend() becomes usable.
|
||||||
|
view->Bind(wxEVT_WEBVIEW_CREATED, [view, config](wxWebViewEvent&) {
|
||||||
|
try {
|
||||||
|
attach_plugin_download_redirect(view, config);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "PluginWebDialog: " << e.what();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
#elif defined(__WXMAC__) || defined(__WXOSX__)
|
#elif defined(__WXMAC__) || defined(__WXOSX__)
|
||||||
void enable_plugin_download_redirect(wxWebView*, wxString, PluginDownloadCallback)
|
void enable_plugin_download_redirect(wxWebView*, wxString, PluginDownloadCallback)
|
||||||
{ throw std::runtime_error("Plugin webview download redirection is not implemented on macOS."); }
|
{ throw std::runtime_error("Plugin webview download redirection is not implemented on macOS."); }
|
||||||
|
|||||||
Reference in New Issue
Block a user