#include "PrinterWebViewHandler.hpp" #include "I18N.hpp" #include "PrinterWebView.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Widgets/WebView.hpp" #include "slic3r/Utils/PrintHost.hpp" #include "libslic3r/Preset.hpp" #include #include #include #include #include #include using json = nlohmann::json; namespace Slic3r { namespace GUI { PrinterWebViewHandler::PrinterWebViewHandler(PrinterWebView& owner) : m_owner(owner) { } PrinterWebViewHandler::~PrinterWebViewHandler() = default; void PrinterWebViewHandler::on_loaded(wxWebViewEvent &evt) { } void PrinterWebViewHandler::on_script_message(wxWebViewEvent &evt) { } PrinterWebView& PrinterWebViewHandler::owner() const { return m_owner; } wxWebView* PrinterWebViewHandler::browser() const { return m_owner.m_browser; } namespace { DynamicPrintConfig* get_active_printer_config() { if (wxGetApp().preset_bundle == nullptr) return nullptr; return &wxGetApp().preset_bundle->printers.get_edited_preset().config; } std::string json_string(const json& node, const char* key) { auto it = node.find(key); return (it != node.end() && it->is_string()) ? it->get() : std::string(); } std::string dump_json(const json& node) { return node.dump(-1, ' ', false, json::error_handler_t::replace); } boost::filesystem::path path_from_utf8(const std::string& utf8_path) { #ifdef _WIN32 const wxString wide_path = wxString::FromUTF8(utf8_path.c_str()); return boost::filesystem::path(wide_path.ToStdWstring()); #else return boost::filesystem::path(utf8_path); #endif } std::string filename_to_utf8(const boost::filesystem::path& path) { #ifdef _WIN32 const wxString wx_filename(path.filename().c_str()); const wxScopedCharBuffer utf8 = wx_filename.ToUTF8(); return utf8.data() != nullptr ? std::string(utf8.data()) : std::string(); #else return path.filename().string(); #endif } class ElegooPrinterWebViewHandler final : public PrinterWebViewHandler { public: explicit ElegooPrinterWebViewHandler(PrinterWebView& owner) : PrinterWebViewHandler(owner) { } ~ElegooPrinterWebViewHandler() override { stop_upload = true; if (upload_thread.joinable()) upload_thread.join(); } void on_script_message(wxWebViewEvent &evt) override { const wxString message = evt.GetString(); if (message.empty()) return; json root = json::parse(message.ToUTF8().data(), nullptr, false); if (root.is_discarded() || !root.is_object()) return; std::string request_id = json_string(root, "id"); std::string method = json_string(root, "method"); json params = root.contains("params") && root["params"].is_object() ? root["params"] : json::object(); if (method.empty()) { method = json_string(root, "command"); if (params.empty() && root.contains("data") && root["data"].is_object()) params = root["data"]; } if (method == "open" || method == "common_openurl") { const std::string url = json_string(params, "url").empty() ? json_string(root, "url") : json_string(params, "url"); if (!url.empty()) wxLaunchDefaultBrowser(url); if (!request_id.empty()) send_ipc_message("response", request_id, method, 0, "success"); return; } if (method == "upload_file") { handle_upload_request(request_id, method, dump_json(params)); return; } if (method == "open_file_dialog") { handle_open_file_dialog_request(request_id, method, dump_json(params)); return; } if (method == "get_sn") { handle_get_sn_request(request_id, method); return; } } private: void send_ipc_message(const char* type, const std::string& request_id, const std::string& method, int code, const std::string& message, const std::string& data_json = "{}") { if (browser() == nullptr) return; json body = json::object(); body["type"] = type; if (!request_id.empty()) body["id"] = request_id; if (!method.empty()) body["method"] = method; json data = json::parse(data_json, nullptr, false); if (data.is_discarded()) data = json::object(); body["data"] = std::move(data); if (std::string(type) == "response") { body["code"] = code; body["message"] = message; } const wxString payload = wxString::FromUTF8(dump_json(body)); const wxString script = "if (typeof HandleStudio === 'function') { HandleStudio(" + payload + "); } else { window.postMessage(" + payload + ", '*'); }"; wxGetApp().CallAfter([this, script]() { if (browser() != nullptr) WebView::RunScript(browser(), script); }); } void handle_upload_request(const std::string& request_id, const std::string& method, const std::string& params_json) { if (upload_in_progress.exchange(true)) { send_ipc_message("response", request_id, method, 1, "Upload already in progress"); return; } if (upload_thread.joinable()) upload_thread.join(); json params = json::parse(params_json, nullptr, false); if (params.is_discarded()) params = json::object(); std::string file_path = json_string(params, "filePath"); std::string file_name = json_string(params, "fileName"); if (file_path.empty()) { upload_in_progress = false; send_ipc_message("response", request_id, method, 1, "Missing filePath"); return; } // HTML IPC passes UTF-8 strings; decode explicitly to avoid Windows codepage issues. boost::filesystem::path source_path = path_from_utf8(file_path); if (file_name.empty()) file_name = filename_to_utf8(source_path); DynamicPrintConfig* config = get_active_printer_config(); std::unique_ptr print_host(config == nullptr ? nullptr : PrintHost::get_print_host(config)); if (print_host == nullptr) { upload_in_progress = false; send_ipc_message("response", request_id, method, 1, "Could not get a valid Printer Host reference"); return; } stop_upload = false; upload_thread = std::thread([this, request_id, method, file_path, file_name, source_path, print_host = std::move(print_host)]() mutable { std::string error_message; PrintHostUpload upload_data; upload_data.use_3mf = false; upload_data.post_action = PrintHostPostUploadAction::None; upload_data.source_path = source_path; upload_data.upload_path = path_from_utf8(file_name); const bool success = print_host->upload( std::move(upload_data), [this, request_id](Http::Progress progress, bool& cancel) { cancel = stop_upload.load(); json data = { {"uploadedBytes", static_cast(progress.ulnow)}, {"totalBytes", static_cast(progress.ultotal)} }; send_ipc_message("event", request_id, "upload_progress", 0, "", dump_json(data)); }, [&error_message](wxString error) { error_message = error.ToUTF8().data(); }, [this, request_id](wxString tag, wxString status) { json data = { {"tag", tag.ToUTF8().data()}, {"status", status.ToUTF8().data()} }; send_ipc_message("event", request_id, "upload_info", 0, "", dump_json(data)); }); upload_in_progress = false; if (success) { json data = { {"success", true}, {"filePath", file_path}, {"fileName", file_name} }; send_ipc_message("response", request_id, method, 0, "success", dump_json(data)); } else { if (error_message.empty()) error_message = "Upload failed"; send_ipc_message("response", request_id, method, 1, error_message); } }); } void handle_open_file_dialog_request(const std::string& request_id, const std::string& method, const std::string& params_json) { json params = json::parse(params_json, nullptr, false); if (params.is_discarded()) params = json::object(); const std::string filter = json_string(params, "filter").empty() ? "All files (*.*)|*.*" : json_string(params, "filter"); wxWindow* parent = owner().GetParent(); if (parent == nullptr) parent = wxGetApp().GetTopWindow(); wxFileDialog open_file_dialog(parent, _L("Open File"), "", "", wxString::FromUTF8(filter), wxFD_OPEN | wxFD_FILE_MUST_EXIST); json data = json::object(); data["files"] = json::array(); if (open_file_dialog.ShowModal() != wxID_CANCEL) data["files"].push_back(open_file_dialog.GetPath().ToUTF8().data()); send_ipc_message("response", request_id, method, 0, "success", dump_json(data)); } void handle_get_sn_request(const std::string& request_id, const std::string& method) { // Panel always calls get_sn with a 10s IPC timeout. Answer immediately from // dev_sn / cache — do not spawn a thread or perform HTTP (panel uses URL sn on miss). std::string sn; if (DynamicPrintConfig* config = get_active_printer_config()) { const std::unique_ptr host(PrintHost::get_print_host(config)); if (host) sn = host->get_sn(); } json data = { { "sn", sn } }; send_ipc_message("response", request_id, method, 0, "success", dump_json(data)); } std::atomic upload_in_progress { false }; std::atomic stop_upload { false }; std::thread upload_thread; }; } // namespace std::unique_ptr create_printer_webview_handler(PrinterWebView& owner) { auto cfg = get_active_printer_config(); if(cfg == nullptr) return nullptr; const auto host_type = cfg->option>("host_type")->value; switch (host_type) { case PrintHostType::htElegooLink: return std::make_unique(owner); default: return nullptr; } } } // GUI } // Slic3r