mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-03 08:12:07 +00:00
feat(WIP): macos impl
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/Utils/MacDarkMode.hpp"
|
||||
|
||||
#include <libslic3r/Utils.hpp>
|
||||
#include "slic3r/plugin/PluginManager.hpp"
|
||||
@@ -10,6 +11,7 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/filename.h>
|
||||
|
||||
#ifdef __WIN32__
|
||||
#include <WebView2.h>
|
||||
@@ -41,6 +43,83 @@ using Microsoft::WRL::Callback;
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
wxString sanitize_plugin_download_filename(const wxString& suggested)
|
||||
{
|
||||
wxString name = wxFileName(suggested).GetFullName();
|
||||
for (size_t i = 0; i < name.length(); ++i) {
|
||||
if (name[i] < 0x20 || name[i] == '/' || name[i] == '\\' || name[i] == ':' || name[i] == '<' ||
|
||||
name[i] == '>' || name[i] == '"' || name[i] == '|' || name[i] == '?' || name[i] == '*')
|
||||
name[i] = '_';
|
||||
}
|
||||
while (!name.empty() && (name.Last() == ' ' || name.Last() == '.'))
|
||||
name.RemoveLast();
|
||||
|
||||
#ifdef __WIN32__
|
||||
const wxString stem = wxFileName(name).GetName().Upper();
|
||||
if (stem == "CON" || stem == "PRN" || stem == "AUX" || stem == "NUL" ||
|
||||
(stem.length() == 4 && (stem.StartsWith("COM") || stem.StartsWith("LPT")) && stem[3] >= '1' && stem[3] <= '9'))
|
||||
name.Prepend('_');
|
||||
#endif
|
||||
|
||||
return name.empty() ? wxString("download") : name;
|
||||
}
|
||||
|
||||
boost::filesystem::path unique_plugin_download_path(const boost::filesystem::path& dir,
|
||||
const boost::filesystem::path& filename)
|
||||
{
|
||||
boost::filesystem::path candidate = dir / filename;
|
||||
#ifdef __WIN32__
|
||||
const std::wstring stem = filename.stem().wstring();
|
||||
const std::wstring ext = filename.extension().wstring();
|
||||
for (int n = 1; boost::filesystem::exists(candidate); ++n)
|
||||
candidate = dir / (stem + L" (" + std::to_wstring(n) + L")" + ext);
|
||||
#else
|
||||
const std::string stem = filename.stem().string();
|
||||
const std::string ext = filename.extension().string();
|
||||
for (int n = 1; boost::filesystem::exists(candidate); ++n)
|
||||
candidate = dir / (stem + " (" + std::to_string(n) + ")" + ext);
|
||||
#endif
|
||||
return candidate;
|
||||
}
|
||||
|
||||
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 finish_plugin_download(PluginDownloadSession& session, long long size)
|
||||
{
|
||||
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", size},
|
||||
{"success", true},
|
||||
{"error", ""}});
|
||||
}
|
||||
|
||||
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}});
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Low-specificity element defaults (no !important) for UNSTYLED plugin HTML, so a bare
|
||||
@@ -116,71 +195,19 @@ wxString web_base_url()
|
||||
return wxString("file://") + from_u8(dir) + "/";
|
||||
}
|
||||
|
||||
using PluginDownloadCallback = std::function<void(const nlohmann::json&)>;
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
std::string sanitize_plugin_download_filename(const gchar* suggested)
|
||||
{
|
||||
std::string name = suggested ? suggested : "";
|
||||
name = boost::filesystem::path(name).filename().string();
|
||||
if (name.empty() || name == "." || name == "..")
|
||||
name = "download";
|
||||
return name;
|
||||
}
|
||||
|
||||
boost::filesystem::path unique_plugin_download_path(const boost::filesystem::path& dir, const std::string& filename)
|
||||
{
|
||||
const boost::filesystem::path base = filename;
|
||||
const std::string stem = base.stem().string();
|
||||
const std::string ext = base.extension().string();
|
||||
boost::filesystem::path candidate = dir / filename;
|
||||
for (int n = 1; boost::filesystem::exists(candidate); ++n)
|
||||
candidate = dir / (stem + " (" + std::to_string(n) + ")" + ext);
|
||||
return candidate;
|
||||
}
|
||||
|
||||
struct PluginDownloadRedirectConfig
|
||||
{
|
||||
wxString target_dir;
|
||||
PluginDownloadCallback callback;
|
||||
};
|
||||
|
||||
constexpr const char* PLUGIN_DOWNLOAD_REDIRECT_DATA_KEY = "orca-plugin-download-redirect-config";
|
||||
constexpr const char* PLUGIN_DOWNLOAD_STARTED_KEY = "orca-plugin-download-started-connected";
|
||||
constexpr const char* PLUGIN_DOWNLOAD_POLICY_KEY = "orca-plugin-download-policy-connected";
|
||||
|
||||
struct PluginDownloadSession
|
||||
{
|
||||
wxString target_dir;
|
||||
PluginDownloadCallback callback;
|
||||
bool notified{false};
|
||||
wxString resolved_filename;
|
||||
wxString resolved_path;
|
||||
};
|
||||
|
||||
void notify_plugin_download(PluginDownloadSession* session, nlohmann::json info)
|
||||
{
|
||||
if (session->notified)
|
||||
return;
|
||||
session->notified = true;
|
||||
if (session->callback)
|
||||
session->callback(info);
|
||||
}
|
||||
|
||||
void fail_plugin_download(PluginDownloadSession* session, WebKitDownload* download, const std::string& filename, const std::string& error)
|
||||
{
|
||||
notify_plugin_download(session,
|
||||
{{"filename", filename}, {"path", ""}, {"mimeType", ""}, {"size", 0}, {"success", false}, {"error", error}});
|
||||
webkit_download_cancel(download);
|
||||
}
|
||||
|
||||
gboolean on_plugin_download_decide_destination(WebKitDownload* download, const gchar* suggested_filename, gpointer user_data)
|
||||
{
|
||||
auto* session = static_cast<PluginDownloadSession*>(user_data);
|
||||
const std::string safe_name = sanitize_plugin_download_filename(suggested_filename);
|
||||
const wxString safe_name = sanitize_plugin_download_filename(wxString::FromUTF8(suggested_filename ? suggested_filename : ""));
|
||||
if (session->target_dir.empty()) {
|
||||
fail_plugin_download(session, download, safe_name, "Plugin storage directory is unavailable.");
|
||||
fail_plugin_download(*session, "Plugin storage directory is unavailable.");
|
||||
webkit_download_cancel(download);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -189,23 +216,25 @@ gboolean on_plugin_download_decide_destination(WebKitDownload* download, const g
|
||||
boost::system::error_code ec;
|
||||
fs::create_directories(dir, ec);
|
||||
if (ec || !fs::is_directory(dir, ec)) {
|
||||
fail_plugin_download(session, download, safe_name, "Could not create the plugin storage directory.");
|
||||
fail_plugin_download(*session, "Could not create the plugin storage directory.");
|
||||
webkit_download_cancel(download);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
const fs::path dest = unique_plugin_download_path(dir, safe_name);
|
||||
const fs::path dest = unique_plugin_download_path(dir, fs::path(std::string(safe_name.utf8_string())));
|
||||
GError* uri_error = nullptr;
|
||||
gchar* uri = g_filename_to_uri(dest.string().c_str(), nullptr, &uri_error);
|
||||
if (!uri) {
|
||||
const std::string error = (uri_error && uri_error->message) ? uri_error->message :
|
||||
"Could not build a destination path for the download.";
|
||||
fail_plugin_download(session, download, safe_name, error);
|
||||
fail_plugin_download(*session, error);
|
||||
webkit_download_cancel(download);
|
||||
if (uri_error)
|
||||
g_error_free(uri_error);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
session->resolved_filename = wxString::FromUTF8(dest.filename().string());
|
||||
session->resolved_filename = safe_name;
|
||||
session->resolved_path = wxString::FromUTF8(dest.string());
|
||||
webkit_download_set_destination(download, uri);
|
||||
g_free(uri);
|
||||
@@ -217,24 +246,15 @@ void on_plugin_download_finished(WebKitDownload* download, gpointer user_data)
|
||||
auto* session = static_cast<PluginDownloadSession*>(user_data);
|
||||
WebKitURIResponse* response = webkit_download_get_response(download);
|
||||
const gchar* mime = response ? webkit_uri_response_get_mime_type(response) : nullptr;
|
||||
notify_plugin_download(session, {{"filename", std::string(session->resolved_filename.utf8_string())},
|
||||
{"path", std::string(session->resolved_path.utf8_string())},
|
||||
{"mimeType", mime ? mime : ""},
|
||||
{"size", webkit_download_get_received_data_length(download)},
|
||||
{"success", true},
|
||||
{"error", ""}});
|
||||
session->mime_type = wxString::FromUTF8(mime ? mime : "");
|
||||
finish_plugin_download(*session, webkit_download_get_received_data_length(download));
|
||||
delete session;
|
||||
}
|
||||
|
||||
void on_plugin_download_failed(WebKitDownload*, GError* error, gpointer user_data)
|
||||
{
|
||||
auto* session = static_cast<PluginDownloadSession*>(user_data);
|
||||
notify_plugin_download(session, {{"filename", std::string(session->resolved_filename.utf8_string())},
|
||||
{"path", std::string(session->resolved_path.utf8_string())},
|
||||
{"mimeType", ""},
|
||||
{"size", 0},
|
||||
{"success", false},
|
||||
{"error", (error && error->message) ? error->message : "Download failed."}});
|
||||
fail_plugin_download(*session, (error && error->message) ? error->message : "Download failed.");
|
||||
delete session;
|
||||
}
|
||||
|
||||
@@ -295,98 +315,6 @@ void enable_plugin_download_redirect(wxWebView* view, wxString target_dir, Plugi
|
||||
}
|
||||
#elif defined(__WIN32__)
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -407,13 +335,7 @@ HRESULT on_plugin_download_state_changed(const std::shared_ptr<PluginDownloadSes
|
||||
}
|
||||
|
||||
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", ""}});
|
||||
finish_plugin_download(*session, bytes_received);
|
||||
} else {
|
||||
COREWEBVIEW2_DOWNLOAD_INTERRUPT_REASON reason =
|
||||
COREWEBVIEW2_DOWNLOAD_INTERRUPT_REASON_NONE;
|
||||
@@ -452,10 +374,10 @@ HRESULT on_plugin_download_starting(ICoreWebView2DownloadStartingEventArgs* args
|
||||
|
||||
LPWSTR suggested_path = nullptr;
|
||||
args->get_ResultFilePath(&suggested_path);
|
||||
const std::wstring filename = sanitize_plugin_download_filename(suggested_path);
|
||||
const wxString filename = sanitize_plugin_download_filename(suggested_path ? wxString(suggested_path) : wxString());
|
||||
if (suggested_path)
|
||||
CoTaskMemFree(suggested_path);
|
||||
session->resolved_filename = wxString(filename);
|
||||
session->resolved_filename = filename;
|
||||
|
||||
LPWSTR mime_type = nullptr;
|
||||
if (SUCCEEDED(operation->get_MimeType(&mime_type)) && mime_type) {
|
||||
@@ -472,7 +394,7 @@ HRESULT on_plugin_download_starting(ICoreWebView2DownloadStartingEventArgs* args
|
||||
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);
|
||||
const fs::path destination = unique_plugin_download_path(dir, fs::path(std::wstring(filename.wc_str())));
|
||||
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.");
|
||||
@@ -550,8 +472,17 @@ void enable_plugin_download_redirect(wxWebView* view, wxString target_dir, Plugi
|
||||
});
|
||||
}
|
||||
#elif defined(__WXMAC__) || defined(__WXOSX__)
|
||||
void enable_plugin_download_redirect(wxWebView*, wxString, PluginDownloadCallback)
|
||||
{ throw std::runtime_error("Plugin webview download redirection is not implemented on macOS."); }
|
||||
|
||||
void enable_plugin_download_redirect(wxWebView* view, wxString target_dir, PluginDownloadCallback callback)
|
||||
{
|
||||
if (!view || !view->GetNativeBackend())
|
||||
return;
|
||||
|
||||
WKWebView_setDownloadRedirect(
|
||||
view->GetNativeBackend(),
|
||||
std::move(target_dir),
|
||||
std::move(callback));
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "Widgets/WebViewHostDialog.hpp"
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -12,6 +14,32 @@
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
using PluginDownloadCallback = std::function<void(const nlohmann::json&)>;
|
||||
|
||||
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;
|
||||
long long size{0};
|
||||
};
|
||||
|
||||
wxString sanitize_plugin_download_filename(const wxString& suggested);
|
||||
boost::filesystem::path unique_plugin_download_path(const boost::filesystem::path& dir,
|
||||
const boost::filesystem::path& filename);
|
||||
void notify_plugin_download(PluginDownloadSession& session, nlohmann::json info);
|
||||
void finish_plugin_download(PluginDownloadSession& session, long long size);
|
||||
void fail_plugin_download(PluginDownloadSession& session, const std::string& error);
|
||||
|
||||
// A host-owned webview window that renders plugin-supplied raw HTML or loads a
|
||||
// plugin-supplied URL and bridges messages to/from the page through a small
|
||||
// injected `window.orca` API.
|
||||
@@ -27,7 +55,7 @@ class PluginWebDialog : public Slic3r::GUI::WebViewHostDialog
|
||||
public:
|
||||
using MessageHandler = std::function<void(const nlohmann::json& data)>;
|
||||
using SubmitHandler = std::function<void(const nlohmann::json& data)>;
|
||||
using DownloadHandler = std::function<void(const nlohmann::json& data)>;
|
||||
using DownloadHandler = PluginDownloadCallback;
|
||||
using CloseHandler = std::function<void()>;
|
||||
|
||||
// on_submit fires once for window.orca.submit(). on_download_complete fires once
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
#include "slic3r/GUI/PluginWebDialog.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
@@ -12,6 +14,7 @@ extern double mac_max_scaling_factor();
|
||||
extern void set_miniaturizable(void * window);
|
||||
void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &));
|
||||
void WKWebView_setTransparentBackground(void * web);
|
||||
void WKWebView_setDownloadRedirect(void* web, wxString target_dir, PluginDownloadCallback callback);
|
||||
void set_tag_when_enter_full_screen(bool isfullscreen);
|
||||
void set_title_colour_after_set_title(void * window);
|
||||
void initGestures(void * view, wxEvtHandler * handler);
|
||||
|
||||
@@ -12,9 +12,233 @@
|
||||
|
||||
#include <objc/runtime.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
@interface MacDarkMode : NSObject {}
|
||||
@end
|
||||
|
||||
using Slic3r::GUI::PluginDownloadCallback;
|
||||
using Slic3r::GUI::PluginDownloadRedirectConfig;
|
||||
using Slic3r::GUI::PluginDownloadSession;
|
||||
using Slic3r::GUI::fail_plugin_download;
|
||||
using Slic3r::GUI::finish_plugin_download;
|
||||
using Slic3r::GUI::sanitize_plugin_download_filename;
|
||||
using Slic3r::GUI::unique_plugin_download_path;
|
||||
|
||||
@interface OrcaWKDownloadContext : NSObject
|
||||
{
|
||||
@public
|
||||
std::shared_ptr<PluginDownloadRedirectConfig> m_config;
|
||||
NSMutableArray* m_download_delegates;
|
||||
}
|
||||
- (id)initWithConfig:(std::shared_ptr<PluginDownloadRedirectConfig>)config;
|
||||
- (void)removeDownloadDelegate:(id)delegate;
|
||||
@end
|
||||
|
||||
@interface OrcaWKDownloadDelegate : NSObject <WKDownloadDelegate>
|
||||
{
|
||||
OrcaWKDownloadContext* m_context;
|
||||
std::shared_ptr<PluginDownloadSession> m_session;
|
||||
}
|
||||
- (id)initWithContext:(OrcaWKDownloadContext*)context;
|
||||
@end
|
||||
|
||||
static char g_orca_wk_download_context_key;
|
||||
|
||||
@implementation OrcaWKDownloadContext
|
||||
|
||||
- (id)initWithConfig:(std::shared_ptr<PluginDownloadRedirectConfig>)config
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
m_config = std::move(config);
|
||||
m_download_delegates = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)removeDownloadDelegate:(id)delegate
|
||||
{
|
||||
[m_download_delegates removeObjectIdenticalTo:delegate];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[m_download_delegates release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation OrcaWKDownloadDelegate
|
||||
|
||||
- (id)initWithContext:(OrcaWKDownloadContext*)context
|
||||
{
|
||||
if ((self = [super init]))
|
||||
m_context = context;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)download:(WKDownload*)download
|
||||
decideDestinationUsingResponse:(NSURLResponse*)response
|
||||
suggestedFilename:(NSString*)suggestedFilename
|
||||
completionHandler:(void (^)(NSURL* destinationURL))completionHandler
|
||||
{
|
||||
(void)download;
|
||||
m_session = std::make_shared<PluginDownloadSession>();
|
||||
m_session->resolved_filename = sanitize_plugin_download_filename(suggestedFilename ? wxCFStringRef::AsString(suggestedFilename) : wxString());
|
||||
m_session->mime_type = response.MIMEType ? wxCFStringRef::AsString(response.MIMEType) : wxString();
|
||||
m_session->size = response.expectedContentLength > 0 ? response.expectedContentLength : 0;
|
||||
m_session->callback = m_context->m_config->callback;
|
||||
|
||||
try {
|
||||
if (m_context->m_config->target_dir.empty())
|
||||
throw std::runtime_error("Plugin storage directory is unavailable.");
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
const fs::path dir(std::string(m_context->m_config->target_dir.utf8_string()));
|
||||
boost::system::error_code ec;
|
||||
fs::create_directories(dir, ec);
|
||||
if (ec || !fs::is_directory(dir, ec))
|
||||
throw std::runtime_error("Could not create the plugin storage directory.");
|
||||
|
||||
const fs::path destination = unique_plugin_download_path(
|
||||
dir, fs::path(std::string(m_session->resolved_filename.utf8_string())));
|
||||
m_session->resolved_path = wxString::FromUTF8(destination.string());
|
||||
const wxString destination_url = wxString::FromUTF8(destination.string());
|
||||
if (completionHandler)
|
||||
completionHandler([NSURL fileURLWithPath:wxCFStringRef(destination_url).AsNSString()]);
|
||||
} catch (const std::exception& e) {
|
||||
fail_plugin_download(*m_session, e.what());
|
||||
if (completionHandler)
|
||||
completionHandler(nil);
|
||||
} catch (...) {
|
||||
fail_plugin_download(*m_session, "Could not redirect the plugin download.");
|
||||
if (completionHandler)
|
||||
completionHandler(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)downloadDidFinish:(WKDownload*)download
|
||||
{
|
||||
(void)download;
|
||||
if (m_session)
|
||||
finish_plugin_download(*m_session, m_session->size);
|
||||
[m_context removeDownloadDelegate:self];
|
||||
}
|
||||
|
||||
- (void)download:(WKDownload*)download didFailWithError:(NSError*)error resumeData:(NSData*)resumeData
|
||||
{
|
||||
(void)download;
|
||||
(void)resumeData;
|
||||
if (m_session) {
|
||||
const wxString message = error ? wxCFStringRef::AsString(error.localizedDescription) : wxString("Download failed.");
|
||||
fail_plugin_download(*m_session, std::string(message.utf8_string()));
|
||||
}
|
||||
[m_context removeDownloadDelegate:self];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static OrcaWKDownloadContext* wk_download_context(WKWebView* webView)
|
||||
{
|
||||
return (OrcaWKDownloadContext*)objc_getAssociatedObject(webView, &g_orca_wk_download_context_key);
|
||||
}
|
||||
|
||||
static void wk_decide_navigation_response(WKWebView* webView,
|
||||
WKNavigationResponse* response,
|
||||
void (^decisionHandler)(WKNavigationResponsePolicy))
|
||||
{
|
||||
OrcaWKDownloadContext* context = wk_download_context(webView);
|
||||
if (!context) {
|
||||
decisionHandler(WKNavigationResponsePolicyAllow);
|
||||
return;
|
||||
}
|
||||
|
||||
bool should_download = !response.canShowMIMEType;
|
||||
if ([response.response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
NSString* disposition = [(NSHTTPURLResponse*)response.response valueForHTTPHeaderField:@"Content-Disposition"];
|
||||
should_download = should_download ||
|
||||
[disposition rangeOfString:@"attachment" options:NSCaseInsensitiveSearch].location != NSNotFound;
|
||||
}
|
||||
decisionHandler(should_download ? WKNavigationResponsePolicyDownload : WKNavigationResponsePolicyAllow);
|
||||
}
|
||||
|
||||
static void wk_did_become_download(WKWebView* webView, WKNavigationResponse*, WKDownload* download)
|
||||
{
|
||||
OrcaWKDownloadContext* context = wk_download_context(webView);
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
OrcaWKDownloadDelegate* delegate = [[OrcaWKDownloadDelegate alloc] initWithContext:context];
|
||||
[context->m_download_delegates addObject:delegate];
|
||||
[download setDelegate:delegate];
|
||||
[delegate release];
|
||||
}
|
||||
|
||||
static void wk_decide_navigation_response_imp(id,
|
||||
SEL,
|
||||
WKWebView* webView,
|
||||
WKNavigationResponse* response,
|
||||
void (^decisionHandler)(WKNavigationResponsePolicy))
|
||||
{
|
||||
wk_decide_navigation_response(webView, response, decisionHandler);
|
||||
}
|
||||
|
||||
static void wk_did_become_download_imp(id,
|
||||
SEL,
|
||||
WKWebView* webView,
|
||||
WKNavigationResponse* response,
|
||||
WKDownload* download)
|
||||
{
|
||||
wk_did_become_download(webView, response, download);
|
||||
}
|
||||
|
||||
static void wk_did_become_action_download_imp(id,
|
||||
SEL,
|
||||
WKWebView* webView,
|
||||
WKNavigationAction* action,
|
||||
WKDownload* download)
|
||||
{
|
||||
(void)action;
|
||||
OrcaWKDownloadContext* context = wk_download_context(webView);
|
||||
if (!context)
|
||||
return;
|
||||
OrcaWKDownloadDelegate* delegate = [[OrcaWKDownloadDelegate alloc] initWithContext:context];
|
||||
[context->m_download_delegates addObject:delegate];
|
||||
[download setDelegate:delegate];
|
||||
[delegate release];
|
||||
}
|
||||
|
||||
static void install_wk_download_delegate_methods(WKWebView* webView)
|
||||
{
|
||||
id navigationDelegate = webView.navigationDelegate;
|
||||
if (!navigationDelegate)
|
||||
return;
|
||||
|
||||
Class delegateClass = object_getClass(navigationDelegate);
|
||||
class_addMethod(delegateClass,
|
||||
@selector(webView:decidePolicyForNavigationResponse:decisionHandler:),
|
||||
(IMP)wk_decide_navigation_response_imp,
|
||||
"v@:@@@");
|
||||
class_addMethod(delegateClass,
|
||||
@selector(webView:navigationResponse:didBecomeDownload:),
|
||||
(IMP)wk_did_become_download_imp,
|
||||
"v@:@@@");
|
||||
class_addMethod(delegateClass,
|
||||
@selector(webView:navigationAction:didBecomeDownload:),
|
||||
(IMP)wk_did_become_action_download_imp,
|
||||
"v@:@@@");
|
||||
}
|
||||
|
||||
@implementation MacDarkMode
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -83,6 +307,19 @@ void set_title_colour_after_set_title(void * window)
|
||||
}
|
||||
}
|
||||
|
||||
void WKWebView_setDownloadRedirect(void* web, wxString target_dir, PluginDownloadCallback callback)
|
||||
{
|
||||
WKWebView* webView = (WKWebView*)web;
|
||||
if (!webView)
|
||||
return;
|
||||
|
||||
auto config = std::make_shared<PluginDownloadRedirectConfig>(PluginDownloadRedirectConfig{std::move(target_dir), std::move(callback)});
|
||||
OrcaWKDownloadContext* context = [[OrcaWKDownloadContext alloc] initWithConfig:std::move(config)];
|
||||
objc_setAssociatedObject(webView, &g_orca_wk_download_context_key, context, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
install_wk_download_delegate_methods(webView);
|
||||
[context release];
|
||||
}
|
||||
|
||||
void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &))
|
||||
{
|
||||
[(WKWebView*)web evaluateJavaScript:wxCFStringRef(script).AsNSString() completionHandler: ^(id result, NSError *error) {
|
||||
|
||||
Reference in New Issue
Block a user