diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 98794297e49..6ea43705c8f 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -539,6 +539,8 @@ set(SLIC3R_GUI_SOURCES GUI/SMPhysicalPrinterDialog.cpp GUI/SSWCP.cpp GUI/SSWCP.hpp + GUI/WebSocketDebugServer.cpp + GUI/WebSocketDebugServer.hpp GUI/DownloadManager.cpp GUI/DownloadManager.hpp GUI/GenericDownloadDialog.cpp diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index e9f4cceddfe..4f6dc46c54c 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1123,7 +1123,7 @@ void GUI_App::post_init() } } - // Start preset sync after project opened, otherwise we could have preset change during project opening which could cause crash + // Start preset sync after project opened, otherwise we could have preset change during project opening which could cause crash if (app_config->get("sync_user_preset") == "true") { // BBS loading user preset // Always async, not such startup step @@ -1143,7 +1143,7 @@ void GUI_App::post_init() // Neither wxShowEvent nor wxWindowCreateEvent work reliably. if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater. CallAfter([this] { - try { + bool cw_showed = this->config_wizard_startup(); SSWCP_MqttAgent_Instance::m_dialog = new WebPresetDialog(this); @@ -1155,13 +1155,7 @@ void GUI_App::post_init() this->preset_updater->sync(http_url, language, network_ver, sys_preset ? preset_bundle : nullptr); this->preset_updater->sync_web_async(true); this->check_new_version_sf(false, false); - } catch (const std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "CallAfter config wizard exception: " << e.what(); - flush_logs(); - } catch (...) { - BOOST_LOG_TRIVIAL(error) << "CallAfter config wizard unknown exception"; - flush_logs(); - } + }); } @@ -1988,7 +1982,7 @@ void GUI_App::init_networking_callbacks() else { obj->parse_json(msg, true); } - + if (!this->is_enable_multi_machine()) { if ((sel == obj || sel == nullptr) && obj->is_ams_need_update) { @@ -2801,7 +2795,7 @@ bool GUI_App::on_init_inner() skip_this_version = false; } } - if (!skip_this_version || evt.GetInt() != 0) { + if (!skip_this_version || evt.GetInt() != 0) { wxString extmsg = wxString::FromUTF8(version_info.description); if(!m_updateDialog) return; @@ -2811,8 +2805,8 @@ bool GUI_App::on_init_inner() } m_updateDialog->Raise(); m_updateDialog->Show(); - m_updateDialog->setUrl(version_info.url); - + m_updateDialog->setUrl(version_info.url); + } } }); @@ -2828,7 +2822,7 @@ bool GUI_App::on_init_inner() false, wxCENTER | wxICON_INFORMATION); dialog.SetExtendedMessage(description_text); - + int result = dialog.ShowModal(); switch (result) { @@ -2840,7 +2834,7 @@ bool GUI_App::on_init_inner() wxGetApp().mainframe->Close(true); break; case wxID_CANCEL: - wxGetApp().mainframe->Close(true); + wxGetApp().mainframe->Close(true); break; default: wxGetApp().mainframe->Close(true); @@ -3081,6 +3075,16 @@ bool GUI_App::on_init_inner() do_notify_flutter_web_copy_failure(); + // WebSocket debug server: only when Preferences → "Web Debug Mode" (websocket_debug) is on. + // When off, explicitly stop the debug server so port 8766 is not left listening. + const bool websocket_debug_pref = app_config->get_bool("websocket_debug"); + if (websocket_debug_pref) { + BOOST_LOG_TRIVIAL(info) << "Web Debug Mode enabled in preferences, starting WebSocket debug server (port 8766)"; + Slic3r::GUI::SSWCP::enable_debug_mode(true); + } else { + Slic3r::GUI::SSWCP::enable_debug_mode(false); + } + profiler.mark("on_init_inner return"); return true; @@ -3130,7 +3134,7 @@ void GUI_App::machine_find() // wcp订阅 json data = this->app_config->get_devices(); wxGetApp().device_card_notify(data); - + }); } } @@ -3905,7 +3909,7 @@ void GUI_App::recreate_GUI(const wxString &msg_name) std::string printer_model = printer_model_opt->value; is_snapmaker_u1 = boost::icontains(printer_model, "Snapmaker") && boost::icontains(printer_model, "U1"); } - + if (!preset_bundle->is_bbl_vendor()) { if (is_snapmaker_u1) { wxString url = wxString::FromUTF8(LOCALHOST_URL + std::to_string(get_page_http_port()) + "/web/flutter_web/index.html?path=2"); @@ -3919,7 +3923,7 @@ void GUI_App::recreate_GUI(const wxString &msg_name) } wxGetApp().device_card_notify(devices); - + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "recreate_GUI exit"; } @@ -4581,17 +4585,17 @@ std::string GUI_App::handle_web_request(std::string cmd) if (path.has_value()) { wxLaunchDefaultBrowser(path.value()); } - } + } else if (command_str.compare("homepage_makerlab_get") == 0) { //if (mainframe->m_webview) { mainframe->m_webview->SendMakerlabList(); } } - else if (command_str.compare("makerworld_model_open") == 0) + else if (command_str.compare("makerworld_model_open") == 0) { if (root.get_child_optional("model") != boost::none) { pt::ptree data_node = root.get_child("model"); boost::optional path = data_node.get_optional("url"); - if (path.has_value()) - { + if (path.has_value()) + { wxString realurl = from_u8(url_decode(path.value())); wxGetApp().request_model_download(realurl); } @@ -4648,7 +4652,7 @@ void GUI_App::request_open_project(std::string project_id) CallAfter([this, project_id] { mainframe->open_recent_project(-1, wxString::FromUTF8(project_id)); }); } -void GUI_App::sm_request_remove_project(std::string project_id) +void GUI_App::sm_request_remove_project(std::string project_id) { mainframe->sm_remove_recent_project(wxString::FromUTF8(project_id)); } @@ -5040,7 +5044,7 @@ void GUI_App::check_web_version() } void GUI_App::check_preset_version() -{ +{ if (preset_updater != nullptr) preset_updater->sync_config_async(); } @@ -5108,11 +5112,11 @@ void GUI_App::check_new_version_sf(bool show_tips, bool by_user) if (platformType == "win") { fileSize = defaultObj.value("file_size", 0); fileMd5 = defaultObj.value("file_md5", ""); - fileSha256 = defaultObj.value("file_sha256", ""); - version_info.url = defaultObj.value("file_url", ""); + fileSha256 = defaultObj.value("file_sha256", ""); + version_info.url = defaultObj.value("file_url", ""); reservedData = defaultObj.value("reserved_1", ""); - reservedData2 = defaultObj.value("reserved_2", ""); + reservedData2 = defaultObj.value("reserved_2", ""); } else if (platformType == "mac") { @@ -5141,8 +5145,8 @@ void GUI_App::check_new_version_sf(bool show_tips, bool by_user) version_info.url = platformObj.value("file_url", ""); reservedData = platformObj.value("reserved_1", ""); - reservedData2 = platformObj.value("reserved_2", ""); - + reservedData2 = platformObj.value("reserved_2", ""); + } else { @@ -5179,7 +5183,7 @@ void GUI_App::check_new_version_sf(bool show_tips, bool by_user) GUI::wxGetApp().QueueEvent(evt); } catch (const std::exception& ex) { std::string errorMsg = ex.what(); - BOOST_LOG_TRIVIAL(fatal) << "request server soft update data error:" << errorMsg; + BOOST_LOG_TRIVIAL(fatal) << "request server soft update data error:" << errorMsg; } }) .perform(); @@ -5612,7 +5616,7 @@ void GUI_App::stop_sync_user_preset() // m_http_server.stop(); //} -void GUI_App::start_page_http_server() +void GUI_App::start_page_http_server() { if (!m_page_http_server.is_started()) m_page_http_server.start(); @@ -6931,7 +6935,7 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage } auto isAgree = wxGetApp().app_config->get("app", PRIVACY_POLICY_FLAGS); - user_update_privacy_notify(isAgree == "true"); + user_update_privacy_notify(isAgree == "true"); BOOST_LOG_TRIVIAL(warning) << "run_wizard changed the privacy policy with: " << (isAgree); return res; } @@ -7143,7 +7147,7 @@ void GUI_App::user_update_privacy_notify(const bool& res) json data; data[PRIVACY_POLICY_FLAGS] = res; - + for (const auto& instance : m_user_update_privacy_subscribers) { auto ptr = instance.second.lock(); if (ptr) { @@ -7170,7 +7174,7 @@ bool GUI_App::config_wizard_startup() auto isAgree = wxGetApp().app_config->get("app", PRIVACY_POLICY_FLAGS); user_update_privacy_notify(isAgree == "true"); BOOST_LOG_TRIVIAL(warning) << "config_wizard_startup changed the privacy policy with: " << (isAgree); - try { + if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) { BOOST_LOG_TRIVIAL(info) << "run wizard..."; run_wizard(ConfigWizard::RR_DATA_EMPTY); @@ -7179,17 +7183,10 @@ bool GUI_App::config_wizard_startup() return true; } - if (isAgree.empty()) - { - run_wizard(ConfigWizard::RR_DATA_EMPTY); // Compatible with older versions - return true; - } - } catch (const std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "config_wizard_startup exception: " << e.what(); - flush_logs(); - } catch (...) { - BOOST_LOG_TRIVIAL(error) << "config_wizard_startup unknown exception"; - flush_logs(); + if (isAgree.empty()) + { + run_wizard(ConfigWizard::RR_DATA_EMPTY); // Compatible with older versions + return true; } return false; @@ -7474,7 +7471,7 @@ bool GUI_App::sm_disconnect_current_machine(bool need_reload_printerview) // wxGetApp().load_current_presets(); }); - + } return true; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 6b27317c6b7..edaf1b11101 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -542,6 +542,8 @@ class GUI_App : public wxApp bool is_user_login(); wxString get_international_url(const wxString& origin_url); + wxString flutter_web_base_url(const wxString& path); + wxString build_flutter_web_url(const wxString& path); // SM struct SMUserInfo diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 9b7d4b2bd37..780c40e8937 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -21,6 +21,7 @@ #include #include "sentry_wrapper/SentryWrapper.hpp" +#include "SSWCP.hpp" #ifdef __WINDOWS__ #ifdef _MSW_DARK_MODE @@ -337,7 +338,7 @@ wxBoxSizer *PreferencesDialog::create_item_region_combobox(wxString title, wxWin combobox->GetDropDown().Bind(wxEVT_COMBOBOX, [this, combobox, current_region, local_regions](wxCommandEvent &e) { auto region_index = e.GetSelection(); auto region = local_regions[region_index]; - + // snapmaker AppConfig* config = GUI::wxGetApp().app_config; combobox->SetSelection(region_index); @@ -771,9 +772,9 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxWindow *pa if (param == PRIVACY_POLICY_FLAGS) { - app_config->set("app", PRIVACY_POLICY_FLAGS, checkbox->GetValue()); + app_config->set("app", PRIVACY_POLICY_FLAGS, checkbox->GetValue()); BOOST_LOG_TRIVIAL(warning) <<"create_item_checkbox changed the privacy policy with: "<<(checkbox->GetValue()?"true" : "false"); - wxGetApp().user_update_privacy_notify(checkbox->GetValue()); + wxGetApp().user_update_privacy_notify(checkbox->GetValue()); } // if (param == "staff_pick_switch") { // bool pbool = app_config->get("staff_pick_switch") == "true"; @@ -863,7 +864,8 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxWindow *pa //// for debug mode if (param == "developer_mode") { m_developer_mode_ckeckbox = checkbox; } if (param == "internal_developer_mode") { m_internal_developer_mode_ckeckbox = checkbox; } - if (param == "legacy_networking") { + + if (param == "legacy_networking") { m_legacy_networking_ckeckbox = checkbox; bool pbool = app_config->get_bool("installed_networking"); checkbox->Enable(pbool); @@ -1208,7 +1210,7 @@ wxWindow* PreferencesDialog::create_general_page() auto item_region= create_item_region_combobox(_L("Login Region"), page, _L("Login Region"), Regions); // SM Beta: temporarily open the item_stealth_mode and close the network plugin - + /*auto item_stealth_mode = create_item_checkbox(_L("Stealth mode"), page, _L("This stops the transmission of data to Bambu's cloud services. Users who don't use BBL machines or use LAN mode only can safely turn on this function."), 50, "stealth_mode"); /*auto item_stealth_mode = create_item_checkbox(_L("Stealth mode"), page, _L("This stops the transmission of data to Bambu's cloud services. Users who don't use BBL machines or use LAN mode only can safely turn on this function."), 50, "stealth_mode"); auto item_enable_plugin = create_item_checkbox(_L("Enable network plugin"), page, _L("Enable network plugin"), 50, "installed_networking"); @@ -1218,17 +1220,17 @@ wxWindow* PreferencesDialog::create_general_page() app_config->set_bool("installed_networking", false); app_config->save(); - + //auto item_check_stable_version_only = create_item_checkbox(_L("Check for stable updates only"), page, _L("Check for stable updates only"), 50, "check_stable_update_only"); std::vector Units = {_L("Metric") + " (mm, g)", _L("Imperial") + " (in, oz)"}; auto item_currency = create_item_combobox(_L("Units"), page, _L("Units"), "use_inches", Units); - auto item_single_instance = create_item_checkbox(_L("Allow only one Snapmaker Orca instance"), page, + auto item_single_instance = create_item_checkbox(_L("Allow only one Snapmaker Orca instance"), page, #if __APPLE__ _L("On OSX there is always only one instance of app running by default. However it is allowed to run multiple instances " "of same app from the command line. In such case this settings will allow only one instance."), #else - _L("If this is enabled, when starting Snapmaker Orca and another instance of the same Snapmaker Orca is already running, that instance will be reactivated instead."), + _L("If this is enabled, when starting Snapmaker Orca and another instance of the same Snapmaker Orca is already running, that instance will be reactivated instead."), #endif 50, "single_instance"); @@ -1346,7 +1348,7 @@ wxWindow* PreferencesDialog::create_general_page() hyperlink->SetFont(Label::Head_13); item_priv_policy->Add(hyperlink, 0, wxALIGN_CENTER, 0); - + auto title_develop_mode = create_item_title(_L("Develop mode"), page, _L("Develop mode")); auto item_develop_mode = create_item_checkbox(_L("Develop mode"), page, _L("Develop mode"), 50, "developer_mode"); auto item_skip_ams_blacklist_check = create_item_checkbox(_L("Skip AMS blacklist check"), page, _L("Skip AMS blacklist check"), 50, "skip_ams_blacklist_check"); @@ -1380,7 +1382,7 @@ wxWindow* PreferencesDialog::create_general_page() //sizer_page->Add(item_check_stable_version_only, 0, wxTOP, FromDIP(3)); // SM Beta: temporarily open the item_stealth_mode and close the network plugin - + /*sizer_page->Add(item_stealth_mode, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_enable_plugin, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_legacy_network_plugin, 0, wxTOP, FromDIP(3)); @@ -1427,9 +1429,10 @@ wxWindow* PreferencesDialog::create_general_page() sizer_page->Add(title_develop_mode, 0, wxTOP | wxEXPAND, FromDIP(20)); sizer_page->Add(item_develop_mode, 0, wxTOP, FromDIP(3)); sizer_page->Add(item_skip_ams_blacklist_check, 0, wxTOP, FromDIP(3)); - + + sizer_page->Add(title_user_experience, 0, wxTOP, FromDIP(20)); - sizer_page->Add(item_priv_policy, 0, wxTOP, FromDIP(3)); + sizer_page->Add(item_priv_policy, 0, wxTOP, FromDIP(3)); page->SetSizer(sizer_page); page->Layout(); @@ -1514,6 +1517,7 @@ wxWindow* PreferencesDialog::create_debug_page() page->SetBackgroundColour(*wxWHITE); m_internal_developer_mode_def = app_config->get("internal_developer_mode"); + m_websocket_debug_def = app_config->get("websocket_debug"); m_backup_interval_def = app_config->get("backup_interval"); m_iot_environment_def = app_config->get("iot_environment"); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 6fbf5d19ce9..6ac83f8aa2e 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -92,12 +92,14 @@ class PreferencesDialog : public DPIDialog // debug mode ::CheckBox * m_developer_mode_ckeckbox = {nullptr}; ::CheckBox * m_internal_developer_mode_ckeckbox = {nullptr}; + ::CheckBox * m_dark_mode_ckeckbox = {nullptr}; ::TextInput *m_backup_interval_textinput = {nullptr}; ::CheckBox * m_legacy_networking_ckeckbox = {nullptr}; wxString m_developer_mode_def; wxString m_internal_developer_mode_def; + wxString m_websocket_debug_def; wxString m_backup_interval_def; wxString m_iot_environment_def; diff --git a/src/slic3r/GUI/SSWCP.cpp b/src/slic3r/GUI/SSWCP.cpp index 9874436c235..4868afd11fa 100644 --- a/src/slic3r/GUI/SSWCP.cpp +++ b/src/slic3r/GUI/SSWCP.cpp @@ -55,7 +55,7 @@ namespace Slic3r { namespace GUI { // WCP_Logger WCP_Logger::WCP_Logger() { - + } bool WCP_Logger::run() @@ -107,7 +107,7 @@ bool WCP_Logger::set_level(wxString& level) // Add a log message to the queue void WCP_Logger::add_log(const wxString& content, bool is_web = false, wxString time = "", wxString module = "Default", wxString level = "debug") { - + if (!inited) { return; } @@ -149,7 +149,7 @@ void WCP_Logger::worker() catch (std::exception& e) { } - + } else { m_log_mtx.unlock(); std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -181,15 +181,15 @@ WCP_Logger::~WCP_Logger() catch (std::exception& e) { if (resolver) delete resolver; - + if (socket) delete socket; return; } - + if (resolver) delete resolver; - + if (socket) delete socket; } @@ -244,7 +244,7 @@ std::vector load_thumbnails(const std::string& file, size_t image_c } thumb_content = base64_data; res.emplace_back(thumb_content); - + ++thumbnail_id; } @@ -548,12 +548,12 @@ void SSWCP_Instance::sw_UploadEvent() { std::string tagKey = m_param_data.count("tagKey") ? m_param_data["tagKey"].get() : ""; std::string tagValue = m_param_data.count("tagValue") ? m_param_data["tagValue"].get() : ""; - + sentryReportLog(SENTRY_LOG_LEVEL(level), content, funcModule, tagKey, tagValue, traceId); send_to_js(); finish_job(); - + } catch (std::exception& e) { handle_general_fail(); @@ -597,7 +597,7 @@ void SSWCP_Instance::sw_OpenNetworkDialog() { delete this; // Delete the timer itself } }; - new DelayedReleaseTimer(dlg); + new DelayedReleaseTimer(dlg); }); } catch (std::exception& e) { @@ -826,7 +826,7 @@ void SSWCP_Instance::sw_Log() if (!logger.m_log_level_map.count(level)) { // todo log:级别不对,转成debug,打原生log level = "debug"; - } + } if (logger.m_log_level_map[level] >= logger.get_level()) { logger.add_log(content, true, time, module, level); @@ -950,7 +950,7 @@ void SSWCP_Instance::handle_general_fail(int code, const wxString& msg) send_to_js(); finish_job(); } catch (std::exception& e) {} - + } // Mark instance as invalid @@ -966,7 +966,7 @@ bool SSWCP_Instance::is_Instance_illegal() { m_illegal_mtx.lock(); bool res = m_illegal; m_illegal_mtx.unlock(); - + return res; } @@ -982,37 +982,43 @@ void SSWCP_Instance::set_web_view(wxWebView* view) { // Send response to JavaScript void SSWCP_Instance::send_to_js() { - try { - if (is_Instance_illegal()) { - return; - } + if (is_Instance_illegal()) + return; + - json response, payload; - response["header"] = m_header; + json response, payload; + response["header"] = m_header; - payload["code"] = m_status; - payload["message"] = m_msg; - payload["data"] = m_res_data; + payload["code"] = m_status; + payload["message"] = m_msg; + payload["data"] = m_res_data; - response["payload"] = payload; + response["payload"] = payload; - std::string json_str = response.dump(4, ' ', true); - std::string str_res = "window.postMessage(JSON.stringify(" + json_str + "), '*');"; + std::string json_str = response.dump(4, ' ', true); + std::string str_res = "window.postMessage(JSON.stringify(" + json_str + "), '*');"; - WCP_Logger::getInstance().add_log(str_res, false, "", "WCP", "info"); + WCP_Logger::getInstance().add_log(str_res, false, "", "WCP", "info"); - auto weak_self = std::weak_ptr(shared_from_this()); - wxGetApp().CallAfter([weak_self, str_res]() { - try { - auto self = weak_self.lock(); - if (self && self->m_webview && self->m_webview->GetRefData()) { - WebView::RunScript(self->m_webview, str_res); - } - } catch (std::exception& e) { - WCP_Logger::getInstance().add_log(e.what(), false, "", "WCP", "info"); + auto weak_self = std::weak_ptr(shared_from_this()); + wxGetApp().CallAfter([weak_self, str_res, json_str]() + { + { + auto self = weak_self.lock(); + if (!self) + return; + + // Original communication path: send via WebView postMessage + if (self->m_webview && self->m_webview->GetRefData()) { + WebView::RunScript(self->m_webview, str_res); } - }); - } catch (std::exception& e) {} + + // Flutter debug: copy message to Flutter debug interface via WebSocket + // This is independent of the original communication path above + SSWCP::send_message_to_flutter(json_str); + } + }); + } // Clean up instance @@ -1030,7 +1036,7 @@ void SSWCP_Instance::async_test() { json data; data["str"] = body; this->m_res_data = data; - this->send_to_js(); + this->send_to_js(); finish_job(); }) .perform(); @@ -1070,7 +1076,7 @@ void SSWCP_Instance::test_mqtt_request() { void SSWCP_Instance::sw_SwitchTab() { try { - + if (m_param_data.count("target")) { std::string target_tab = m_param_data["target"].get(); if (SSWCP::m_tab_map.count(target_tab)) { @@ -1080,7 +1086,7 @@ void SSWCP_Instance::sw_SwitchTab() { return; } } - + handle_general_fail(); } catch (std::exception& e) { @@ -1107,7 +1113,7 @@ void SSWCP_Instance::sw_SetCache() { catch (std::exception& e) { handle_general_fail(); } - + } void SSWCP_Instance::sw_GetCache() @@ -1178,7 +1184,7 @@ void SSWCP_Instance::sw_SubscribeCacheKey() std::string key = m_param_data["key"].get(); auto& cache_map = wxGetApp().m_cache_subscribers; - + for (auto iter = cache_map.begin(); iter != cache_map.end();) { if (iter->first.first == m_webview && iter->second == key) { // 删除之前的订阅 @@ -1526,7 +1532,7 @@ void SSWCP_Instance::sw_Unsubscribe_Filter() { iter++; } } - } + } else { BOOST_LOG_TRIVIAL(warning) << "no this cmd for:" << cmd; @@ -1731,7 +1737,7 @@ void SSWCP_Instance::update_filament_info(const json& objects, bool send_message wxGetApp().load_current_presets(); } - + if (send_message) { send_to_js(); finish_job(); @@ -1895,17 +1901,17 @@ void SSWCP_MachineFind_Instance::sw_StartMachineFind() if (!GUI_App::m_app_alive.load()) { return; } - + auto self = weak_self.lock(); if(!self || self->is_stop()){ return; } - + // Double check application is still alive after locking if (!GUI_App::m_app_alive.load()) { return; } - + json machine_data; std::string hostname = reply.hostname; @@ -2004,21 +2010,21 @@ void SSWCP_MachineFind_Instance::sw_StartMachineFind() machine_object[reply.ip.to_string()] = machine_data; } self->add_machine_to_list(machine_object); - + }) .on_complete([weak_self]() { // Check if application is still alive before scheduling callback if (!GUI_App::m_app_alive.load()) { return; } - + try { wxGetApp().CallAfter([weak_self]() { // Check again inside the callback if (!GUI_App::m_app_alive.load()) { return; } - + auto self = weak_self.lock(); if (self && !self->is_stop()) { self->onOneEngineEnd(); @@ -2085,7 +2091,7 @@ void SSWCP_MachineFind_Instance::add_machine_to_list(const json& machine_info) info.ip = ip; wxGetApp().app_config->save_device_info(info); - + wxGetApp().CallAfter([]() { auto devices = wxGetApp().app_config->get_devices(); @@ -2101,7 +2107,7 @@ void SSWCP_MachineFind_Instance::add_machine_to_list(const json& machine_info) json data = devices; wxGetApp().device_card_notify(data); }); - + } } @@ -2238,7 +2244,7 @@ void SSWCP_MachineOption_Instance::process() sw_ServerClientManagerSetUserinfo(); } else if (m_cmd == "sw_DefectDetactionConfig"){ sw_DefectDetactionConfig(); - } + } else if (m_cmd == GET_DEVICEDATA_STORAGESPACE) { sw_GetDeviceDataStorageSpace(); } @@ -2423,7 +2429,7 @@ void SSWCP_MachineOption_Instance::sw_SendGCodes() { std::shared_ptr host = nullptr; wxGetApp().get_connect_host(host); std::vector str_codes; - + if (m_param_data["script"].is_array()) { json codes = m_param_data["script"]; @@ -2447,7 +2453,7 @@ void SSWCP_MachineOption_Instance::sw_SendGCodes() { } }); } - + } catch (const std::exception&) { handle_general_fail(); } @@ -2470,7 +2476,7 @@ void SSWCP_MachineOption_Instance::sw_MachinePrintStart() { host->async_start_print_job(filename, [weak_self](const json& response) { auto self = weak_self.lock(); if (self) { - SSWCP_Instance::on_mqtt_msg_arrived(self, response); + SSWCP_Instance::on_mqtt_msg_arrived(self, response); } }); } @@ -2495,7 +2501,7 @@ void SSWCP_MachineOption_Instance::sw_MachinePrintPause() host->async_pause_print_job([weak_self](const json& response) { auto self = weak_self.lock(); if (self) { - SSWCP_Instance::on_mqtt_msg_arrived(self, response); + SSWCP_Instance::on_mqtt_msg_arrived(self, response); } }); } catch (std::exception& e) { @@ -2518,7 +2524,7 @@ void SSWCP_MachineOption_Instance::sw_MachinePrintResume() host->async_resume_print_job([weak_self](const json& response) { auto self = weak_self.lock(); if (self) { - SSWCP_Instance::on_mqtt_msg_arrived(self, response); + SSWCP_Instance::on_mqtt_msg_arrived(self, response); } }); } catch (std::exception& e) { @@ -2541,7 +2547,7 @@ void SSWCP_MachineOption_Instance::sw_MachinePrintCancel() host->async_cancel_print_job([weak_self](const json& response) { auto self = weak_self.lock(); if (self) { - SSWCP_Instance::on_mqtt_msg_arrived(self, response); + SSWCP_Instance::on_mqtt_msg_arrived(self, response); } }); } catch (std::exception& e) { @@ -2561,10 +2567,10 @@ void SSWCP_MachineOption_Instance::sw_GetSystemInfo() } auto weak_self = std::weak_ptr(shared_from_this()); - host->async_get_system_info([weak_self](const json& response) { + host->async_get_system_info([weak_self](const json& response) { auto self = weak_self.lock(); if (self) { - SSWCP_Instance::on_mqtt_msg_arrived(self, response); + SSWCP_Instance::on_mqtt_msg_arrived(self, response); } }); } @@ -2623,7 +2629,7 @@ void SSWCP_MachineOption_Instance::sw_GetMachineObjects() try { std::shared_ptr host = nullptr; wxGetApp().get_connect_host(host); - + if (!host) { handle_general_fail(-1, "Can't find the active machine"); return; @@ -2785,7 +2791,7 @@ void SSWCP_MachineOption_Instance::sw_MachineFilesRoots() try { std::shared_ptr host = nullptr; wxGetApp().get_connect_host(host); - + if (!host) { handle_general_fail(); return; @@ -2820,13 +2826,13 @@ void SSWCP_MachineOption_Instance::sw_MachineFilesMetadata() { host->async_machine_files_metadata(filename, [weak_self](const json& response) { auto self = weak_self.lock(); if (self) { - SSWCP_Instance::on_mqtt_msg_arrived(self, response); + SSWCP_Instance::on_mqtt_msg_arrived(self, response); } }); } else { handle_general_fail(); } - + } catch (std::exception& e) { handle_general_fail(); } @@ -2851,7 +2857,7 @@ void SSWCP_MachineOption_Instance::sw_MachineFilesThumbnails() [weak_self](const json& response) { auto self = weak_self.lock(); if (self) { - SSWCP_Instance::on_mqtt_msg_arrived(self, response); + SSWCP_Instance::on_mqtt_msg_arrived(self, response); } }); } else { @@ -2882,7 +2888,7 @@ void SSWCP_MachineOption_Instance::sw_MachineFilesGetDirectory() host->async_machine_files_directory(path, extend, [weak_self](const json& response) { auto self = weak_self.lock(); if (self) { - SSWCP_Instance::on_mqtt_msg_arrived(self, response); + SSWCP_Instance::on_mqtt_msg_arrived(self, response); } }); } else { @@ -2916,13 +2922,13 @@ void SSWCP_MachineOption_Instance::sw_ServerClientManagerSetUserinfo() handle_general_fail(-1, "can't find the active machine"); return; } - + auto weak_self = std::weak_ptr(shared_from_this()); host->async_server_client_manager_set_userinfo(m_param_data, [weak_self](const json& response) { auto self = weak_self.lock(); if (self) { - SSWCP_Instance::on_mqtt_msg_arrived(self, response); + SSWCP_Instance::on_mqtt_msg_arrived(self, response); } }); @@ -3008,7 +3014,7 @@ void SSWCP_MachineOption_Instance::sw_GetPrintLegal() } local_name.erase(std::remove(local_name.begin(), local_name.end(), '('), local_name.end()); local_name.erase(std::remove(local_name.begin(), local_name.end(), ')'), local_name.end()); - + m_res_data["preset_model"] = local_name; m_res_data["legal"] = (local_name == connected_model); @@ -3076,7 +3082,7 @@ void SSWCP_MachineOption_Instance::sw_UploadFiletoMachine() { response["filename"] = std::string(filename.ToUTF8()); m_res_data = response; send_to_js(); - + MessageDialog msg_window(nullptr, " " + filename + _L(" has already been uploaded") + "\n", _L("UpLoad Successfully"), wxICON_QUESTION | wxOK); @@ -3117,7 +3123,7 @@ void SSWCP_MachineOption_Instance::sw_DownloadMachineFile() { } else { filename = wxString::FromUTF8(m_param_data["filename"].get()); } - + // 获取文件扩展名 wxString extension; @@ -3154,7 +3160,7 @@ void SSWCP_MachineOption_Instance::sw_DownloadMachineFile() { wxString path = saveFileDialog.GetPath(); auto final_url = Http::encode_url_path(download_url.ToStdString(wxConvUTF8)); - + Http http_object = Http::get(final_url); http_object @@ -3268,7 +3274,7 @@ void SSWCP_MachineOption_Instance::sw_GetFileFilamentMapping() res += std::pow(16, i - 1) * (oriclr[colorSize - i] - '0'); } else { res += std::pow(16, i - 1) * (oriclr[colorSize - i] - 'A' + 10); - } + } } return res; @@ -3306,7 +3312,6 @@ void SSWCP_MachineOption_Instance::sw_GetFileFilamentMapping() response["filament_color_rgba"] = str_res; response["filament_color_multi"] = multi_color_res; } - // filament type if (const auto* filament_type_opt = full_config.option("filament_type"); @@ -3340,7 +3345,7 @@ void SSWCP_MachineOption_Instance::sw_GetFileFilamentMapping() response["nozzle_diameters"] = nozzle_diameters; } } - + // filament used if (config.has("filament_density")) { @@ -3387,13 +3392,13 @@ void SSWCP_MachineOption_Instance::sw_GetFileFilamentMapping() if (!filament_extruder_map.empty()) { json object; for (const auto& item : filament_extruder_map) { - object[std::to_string(item.first)] = std::to_string(item.second); + object[std::to_string(item.first)] = std::to_string(item.second); } response["filament_extruder_map"] = object; } //nozzle info - PartPlate* cur_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); + PartPlate* cur_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); if (cur_plate) { auto* nozzle_opt = cur_plate->fff_print()->config().option("nozzle_diameter"); @@ -3449,7 +3454,7 @@ void SSWCP_MachineOption_Instance::sw_GetFileFilamentMapping() std::string str_width = tmp.substr(0, tmp.find("x")); std::string str_height = tmp.substr(tmp.find("x") + 1); thumbnails_size.push_back({atof(str_width.c_str()), atof(str_height.c_str())}); - + } while (thumbnails_describe != ""); @@ -3466,7 +3471,7 @@ void SSWCP_MachineOption_Instance::sw_GetFileFilamentMapping() thumbnails.push_back(thumbnail); } } - + response["thumbnails"] = thumbnails; // file name @@ -3505,7 +3510,7 @@ void SSWCP_MachineOption_Instance::sw_SetFilamentMappingComplete() // wxICON_QUESTION | wxOK | wxCANCEL); // flag = msg_window.ShowModal(); //} - + WebPreprintDialog* dialog = dynamic_cast(wxGetApp().get_web_preprint_dialog()); if (dialog) { if(flag == wxID_OK){ @@ -3514,7 +3519,7 @@ void SSWCP_MachineOption_Instance::sw_SetFilamentMappingComplete() dialog->set_finish(false); } } - + } else { MessageDialog msg_window(nullptr, " " + _L("setting failed") + "\n", _L("Print Job Setting"), wxICON_QUESTION | wxOK); @@ -3550,7 +3555,7 @@ void SSWCP_MachineOption_Instance::sw_CameraStartMonitor() { host->async_camera_start(domain, interval, expect_pw, [weak_self](const json& response) { auto self = weak_self.lock(); if (self) { - SSWCP_Instance::on_mqtt_msg_arrived(self, response); + SSWCP_Instance::on_mqtt_msg_arrived(self, response); } }); } else { @@ -3609,7 +3614,7 @@ void SSWCP_MachineOption_Instance::sw_CameraStopMonitor() { host->async_canmera_stop(domain, [weak_self](const json& response) { auto self = weak_self.lock(); if (self) { - SSWCP_Instance::on_mqtt_msg_arrived(self, response); + SSWCP_Instance::on_mqtt_msg_arrived(self, response); } }); } else { @@ -3943,7 +3948,7 @@ void SSWCP_MachineOption_Instance::sw_UploadCameraTimelapse() handle_general_fail(); } } -void SSWCP_MachineOption_Instance::CmdForwarding() +void SSWCP_MachineOption_Instance::CmdForwarding() { try { std::shared_ptr host = nullptr; @@ -4037,7 +4042,7 @@ void SSWCP_MachineOption_Instance::sw_DeleteCameraTimelapse() } void SSWCP_MachineOption_Instance::sw_DefectDetactionConfig() -{ +{ try { std::shared_ptr host = nullptr; wxGetApp().get_connect_host(host); @@ -4231,7 +4236,7 @@ void SSWCP_MachineConnect_Instance::sw_connect_other_device() { try { auto weak_self = std::weak_ptr(shared_from_this()); wxGetApp().CallAfter([weak_self](){ - + auto config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; config->set("print_host", ""); config->set("printhost_apikey", ""); @@ -4252,8 +4257,8 @@ void SSWCP_MachineConnect_Instance::sw_connect_other_device() { self->handle_general_fail(); } } - - + + }); } catch (std::exception& e) { @@ -4267,11 +4272,11 @@ void SSWCP_MachineConnect_Instance::sw_test_connect() { try { if (m_param_data.count("ip")) { std::string protocol = "moonraker"; - + if (m_param_data.count("protcol")) { protocol = m_param_data["protocol"].get(); } - + std::string ip = m_param_data["ip"].get(); int port = -1; @@ -4284,9 +4289,9 @@ void SSWCP_MachineConnect_Instance::sw_test_connect() { PrintHostType type = PrintHostType::htMoonRaker; // todo : 增加输入与type的映射 - + p_config->option>("host_type")->value = type; - + p_config->set("print_host", ip + (port == -1 ? "" : std::to_string(port))); std::shared_ptr host(PrintHost::get_print_host(&wxGetApp().preset_bundle->printers.get_edited_preset().config)); @@ -4322,7 +4327,7 @@ void SSWCP_MachineConnect_Instance::sw_test_connect() { } void SSWCP_MachineConnect_Instance::sw_connect() { - + } void SSWCP_MachineConnect_Instance::sw_get_connect_machine() { @@ -4372,9 +4377,9 @@ void SSWCP_MachineConnect_Instance::sw_disconnect() { } } } - - + + bool res = wxGetApp().sm_disconnect_current_machine(need_reload); m_first_connected = true; @@ -4385,7 +4390,7 @@ void SSWCP_MachineConnect_Instance::sw_disconnect() { } } - + wxGetApp().CallAfter([]() { @@ -4432,7 +4437,7 @@ void SSWCP_SliceProject_Instance::process() void SSWCP_SliceProject_Instance::sw_NewProject() { - try { + try { if (!m_param_data.count("preset_name") || m_param_data["preset_name"].get() == "") wxGetApp().request_open_project(""); else { @@ -4446,7 +4451,7 @@ void SSWCP_SliceProject_Instance::sw_NewProject() wxICON_QUESTION | wxOK); msg_window.ShowModal(); } - + } catch (std::exception& e) { // 异常处理 } @@ -4457,7 +4462,7 @@ void SSWCP_SliceProject_Instance::sw_NewProject() } catch (std::exception& e) { handle_general_fail(); - } + } } void SSWCP_SliceProject_Instance::sw_OpenProject() @@ -4474,7 +4479,7 @@ void SSWCP_SliceProject_Instance::sw_OpenProject() void SSWCP_SliceProject_Instance::sw_GetRecentProjects() { try { - + json data; wxGetApp().mainframe->get_recent_projects(data, INT_MAX); @@ -4522,12 +4527,12 @@ void SSWCP_SliceProject_Instance::sw_DeleteRecentFiles() wxGetApp().sm_request_remove_project(paths[i].get()); } } - + } else { handle_general_fail(); return; } - + finish_job(); } catch (std::exception& e) { handle_general_fail(); @@ -4554,7 +4559,6 @@ void SSWCP_UserLogin_Instance::process() m_header.clear(); m_header["event_id"] = m_event_id; } - if (m_cmd == "sw_UserLogin") { sw_UserLogin(); } else if (m_cmd == "sw_UserLogout") { @@ -4593,8 +4597,8 @@ void SSWCP_UserLogin_Instance::sw_UserLogin() wxGetApp().CallAfter([show]() { wxGetApp().sm_request_login(show); }); - - + + } catch (std::exception& e) { handle_general_fail(); @@ -4702,7 +4706,7 @@ void SSWCP_UserLogin_Instance::sw_DownloadFileAndOpen() } } -void SSWCP_UserLogin_Instance::sw_DownloadFile() +void SSWCP_UserLogin_Instance::sw_DownloadFile() { try { std::string fileName = m_param_data.count("file_name") ? m_param_data["file_name"].get() : ""; @@ -4719,7 +4723,7 @@ void SSWCP_UserLogin_Instance::sw_DownloadFile() handle_general_fail(-1, "Download Manager not available"); return; } - + //only download file and don't do anything. //wxGetApp().mainframe->downloadOpenProject(fileUrl, fileName, ""); @@ -4752,8 +4756,8 @@ void SSWCP_UserLogin_Instance::sw_DownloadFileEx() { size_t task_id = download_mgr->start_wcp_download(fileUrl, fileName, shared_from_this(), - true); - + true); + json response; response["task_id"] = task_id; response["file_name"] = fileName; @@ -4762,7 +4766,7 @@ void SSWCP_UserLogin_Instance::sw_DownloadFileEx() { m_status = 0; m_msg = "success"; send_to_js(); - + } catch (std::exception& e) { handle_general_fail(-1, e.what()); } @@ -4771,20 +4775,20 @@ void SSWCP_UserLogin_Instance::sw_DownloadFileEx() { void SSWCP_UserLogin_Instance::sw_CancelDownload() { try { size_t task_id = m_param_data.count("task_id") ? m_param_data["task_id"].get() : 0; - + if (task_id == 0) { handle_general_fail(-1, "task_id is required"); return; } - + DownloadManager* download_mgr = wxGetApp().download_manager(); if (!download_mgr) { handle_general_fail(-1, "WCP Download Manager not available"); return; } - + bool success = download_mgr->cancel_download(task_id); - + if (success) { json response; response["task_id"] = task_id; @@ -4796,7 +4800,7 @@ void SSWCP_UserLogin_Instance::sw_CancelDownload() { handle_general_fail(-1, "Failed to cancel download or task not found"); return; } - + send_to_js(); finish_job(); } catch (std::exception& e) { @@ -4823,12 +4827,12 @@ void SSWCP_UserLogin_Instance::sw_FileView() { return; } - //open file in folder + //open file in folder desktop_open_any_folderEx(file_path); self->send_to_js(); self->finish_job(); - + }); } catch (std::exception& e) { handle_general_fail(); @@ -4875,7 +4879,7 @@ void SSWCP_MachineManage_Instance::process() } else if (m_cmd == "sw_SubscribeLocalDevices") { sw_SubscribeLocalDevices(); } else if (m_cmd == "sw_RenameDevice") { - sw_RenameDevice(); + sw_RenameDevice(); } else if (m_cmd == "sw_SwitchModel") { sw_SwitchModel(); } else if (m_cmd == "sw_DeleteDevices") { @@ -4981,7 +4985,7 @@ void SSWCP_MachineManage_Instance::sw_AddDevice() wxGetApp().web_device_dialog->run(); }); send_to_js(); - + finish_job(); } catch (std::exception& e) { handle_general_fail(); @@ -5083,7 +5087,7 @@ void SSWCP_MachineManage_Instance::sw_SwitchModel() dialog.run(); }); finish_job(); - + } else { handle_general_fail(); } @@ -5132,7 +5136,7 @@ bool SSWCP_MqttAgent_Instance::validate_id(const std::string& id) } else { flag = m_mqtt_engine_map[m_webview].first == id; } - + m_engine_map_mtx.unlock(); return flag; @@ -5202,7 +5206,7 @@ void SSWCP_MqttAgent_Instance::mqtt_msg_cb(const std::string& topic, const std:: self->send_to_js(); } } - + } else { return; } @@ -5292,8 +5296,8 @@ void SSWCP_MqttAgent_Instance::sw_create_mqtt_client() handle_general_fail(-1, "create failed"); return; } - - // 绑定静态回调 + + // 绑定静态回调 client->SetMessageCallback(SSWCP_MqttAgent_Instance::mqtt_msg_cb); m_res_data["type"] = type; @@ -5359,7 +5363,7 @@ void SSWCP_MqttAgent_Instance::sw_mqtt_connect() } }); }); - + } catch (std::exception& e) { handle_general_fail(); @@ -5478,19 +5482,19 @@ void SSWCP_MqttAgent_Instance::sw_mqtt_subscribe() wxGetApp().CallAfter([weak_ptr, msg, flag]() { auto self = weak_ptr.lock(); if (self) { - if (flag) { + if (flag) { // 回复后, 设置event_id, 长期保留对象 if (self->m_event_id != "") { self->m_msg = msg; self->send_to_js(); - + json header; self->m_header.clear(); self->m_header["event_id"] = self->m_event_id; } else { self->handle_general_fail(-1, "event_id is null"); } - + } else { self->handle_general_fail(-1, msg); } @@ -5600,7 +5604,7 @@ void SSWCP_MqttAgent_Instance::sw_mqtt_set_engine() return; } - bool reload_device_view = m_param_data.count("need_reload") ? m_param_data["need_reload"].get() : true; + bool reload_device_view = m_param_data.count("need_reload") ? m_param_data["need_reload"].get() : true; int port = m_param_data["port"].get(); @@ -5620,7 +5624,7 @@ void SSWCP_MqttAgent_Instance::sw_mqtt_set_engine() std::shared_ptr host = dynamic_pointer_cast(tmp_host); if (host) { auto engine = get_current_engine(); - + if (engine == nullptr) { handle_general_fail(-1, "invalid engine"); Slic3r::sentryReportLog(Slic3r::SENTRY_LOG_ERROR, std::string("device_set_engine invalid engine"),DEVICE_SET_ENGINE_ERR); @@ -5676,19 +5680,19 @@ void SSWCP_MqttAgent_Instance::sw_mqtt_set_engine() if (m_param_data.count("code")){ connect_params["code"] = m_param_data["code"]; } - + if (m_param_data.count("ca")) { connect_params["ca"] = m_param_data["ca"]; host->m_ca = m_param_data["ca"].get(); } - + if (m_param_data.count("cert")) { connect_params["cert"] = m_param_data["cert"]; host->m_cert = m_param_data["cert"].get(); } - + if (m_param_data.count("key")) { connect_params["key"] = m_param_data["key"]; @@ -5714,7 +5718,7 @@ void SSWCP_MqttAgent_Instance::sw_mqtt_set_engine() connect_params["clientId"] = m_param_data["clientId"]; host->m_client_id = m_param_data["clientId"].get(); } - + std::string link_mode = m_param_data.count("link_mode") ? m_param_data["link_mode"] : "lan"; @@ -5892,7 +5896,7 @@ void SSWCP_MqttAgent_Instance::sw_mqtt_set_engine() } } else { - + info.nozzle_sizes = nozzle_diameters; info.preset_name = machine_type + " (" + nozzle_diameters[0] + " nozzle)"; wxGetApp().app_config->save_device_info(info); @@ -5953,7 +5957,7 @@ void SSWCP_MqttAgent_Instance::sw_mqtt_set_engine() if (machine_ip_type->get_machine_type(ip, machine_type)) { // 已经发现过的机型信息 // test - + if (machine_type == "lava" || machine_type == "Snapmaker test") { machine_type = "Snapmaker U1"; } @@ -6100,7 +6104,7 @@ void SSWCP_MqttAgent_Instance::sw_mqtt_set_engine() wxGetApp().mainframe->load_printer_url(real_url); // 到时全部加载本地交互页面 } - + } auto self = weak_self.lock(); @@ -6147,7 +6151,7 @@ void SSWCP_MqttAgent_Instance::sw_mqtt_set_engine() self->finish_job(); }); - } + } }); } @@ -6204,7 +6208,7 @@ void SSWCP_MqttAgent_Instance::sw_mqtt_publish() return; } std::string payload = m_param_data["payload"].get(); - + std::weak_ptr weak_ptr = shared_from_this(); auto engine = get_current_engine(); @@ -6250,6 +6254,11 @@ std::string SSWCP::m_display_gcode_filename = ""; long long SSWCP::m_active_file_size = 0; std::mutex SSWCP::m_file_size_mutex; +// WebSocket Debug Server static members +std::unique_ptr SSWCP::m_debug_server = nullptr; +std::mutex SSWCP::m_debug_server_mutex; +bool SSWCP::m_debug_mode_enabled = false; + std::unordered_map SSWCP::m_tab_map = { {"Home", MainFrame::TabPosition::tpHome}, {"3DEditor", MainFrame::TabPosition::tp3DEditor}, @@ -6358,7 +6367,7 @@ std::unordered_set SSWCP::m_mqtt_agent_cmd_list = { std::shared_ptr SSWCP::create_sswcp_instance(std::string cmd, const json& header, const json& data, std::string event_id, wxWebView* webview) { std::shared_ptr instance; - + if (m_machine_find_cmd_list.find(cmd) != m_machine_find_cmd_list.end()) { instance = std::make_shared(cmd, header, data, event_id, webview); } else if (m_machine_connect_cmd_list.find(cmd) != m_machine_connect_cmd_list.end()) { @@ -6379,17 +6388,16 @@ std::shared_ptr SSWCP::create_sswcp_instance(std::string cmd, co else { instance = std::make_shared(cmd, header, data, event_id, webview); } - + return instance; } // Handle incoming web messages void SSWCP::handle_web_message(std::string message, wxWebView* webview) { - try { - - if (!webview) { - return; - } + try { + if (!webview) { + return; + } WCP_Logger::getInstance().add_log(message, false, "", "WCP", "info"); json j_message = json::parse(message); @@ -6425,7 +6433,7 @@ void SSWCP::handle_web_message(std::string message, wxWebView* webview) { instance->process(); } //if (!m_func_map.count(cmd)) { - // // todo:返回不支持处理 + // // todo:return and handle it later //} //m_func_map[cmd](sequenceId, data, callback_name, webview); @@ -6435,6 +6443,46 @@ void SSWCP::handle_web_message(std::string message, wxWebView* webview) { } } +// Handle incoming web messages for Flutter debug (no webview required) +void SSWCP::handle_webmsg_for_debug(std::string message) { + { + WCP_Logger::getInstance().add_log(message, false, "", "WCP", "info"); + + json j_message = json::parse(message); + + if (j_message.empty() || !j_message.count("header") || !j_message.count("payload") || !j_message["payload"].count("cmd")) { + return; + } + + json header = j_message["header"]; + json payload = j_message["payload"]; + + std::string cmd = ""; + std::string event_id = ""; + json params; + + if (payload.count("cmd")) { + cmd = payload["cmd"].get(); + } + if (payload.count("params")) { + params = payload["params"]; + } + + if (payload.count("event_id") && !payload["event_id"].is_null()) { + event_id = payload["event_id"].get(); + } + std::shared_ptr instance = create_sswcp_instance(cmd, header, params, event_id, nullptr); + if (instance) { + if (event_id != "") { + m_instance_list.add_infinite(instance.get(), instance); + } else { + m_instance_list.add(instance.get(), instance, DEFAULT_INSTANCE_TIMEOUT); + } + instance->process(); + } + } +} + // Delete instance from list void SSWCP::delete_target(SSWCP_Instance* target) { wxGetApp().CallAfter([target]() { @@ -6447,16 +6495,16 @@ void SSWCP::stop_subscribe_machine() { wxGetApp().CallAfter([]() { std::vector instances_to_stop; - + auto snapshot = m_instance_list.get_snapshot(); // Get all subscription instances to stop - for (const auto& instance : snapshot) { + for (const auto& instance : snapshot) { if (instance.second->getType() == SSWCP_MachineFind_Instance::MACHINE_OPTION && instance.second->m_cmd == "sw_SubscribeMachineState") { instances_to_stop.push_back(instance.first); } } - + // Stop each instance for (auto* instance : instances_to_stop) { auto instance_ptr = m_instance_list.get(instance); @@ -6471,16 +6519,16 @@ void SSWCP::stop_subscribe_machine() void SSWCP::stop_machine_find() { wxGetApp().CallAfter([]() { std::vector instances_to_stop; - + auto snapshot = m_instance_list.get_snapshot(); // Get all discovery instances to stop - for (const auto& instance : snapshot) { + for (const auto& instance : snapshot) { if (instance.second->getType() == SSWCP_MachineFind_Instance::MACHINE_FIND) { instances_to_stop.push_back(instance.first); } } - + // Set stop flag for each instance for (auto* instance : instances_to_stop) { auto instance_ptr = m_instance_list.get(instance); @@ -6496,7 +6544,7 @@ void SSWCP::on_webview_delete(wxWebView* view) { // Mark all instances associated with this webview as invalid std::vector instances_to_invalidate; - + // Get all instances using this webview for (const auto& instance : m_instance_list) { if (instance.second->value->get_web_view() == view) { @@ -6504,7 +6552,7 @@ void SSWCP::on_webview_delete(wxWebView* view) instance.second->value->set_web_view(nullptr); } } - + // Mark each instance as invalid for (auto* instance : instances_to_invalidate) { auto instance_ptr = m_instance_list.get(instance); @@ -6595,7 +6643,7 @@ bool SSWCP::query_machine_info(std::shared_ptr& host, std::string& ou { if (!host) return false; - // 创建同步等待的条件变量和互斥锁 + // Create condition variables and mutexes for synchronized waiting. std::condition_variable cv; std::shared_ptr mutex(new std::mutex); std::weak_ptr cb_mutex = mutex; @@ -6603,7 +6651,7 @@ bool SSWCP::query_machine_info(std::shared_ptr& host, std::string& ou bool timeout = false; json system_info; - // 发送查询请求 + // send to check request host->async_get_system_info( [&, cb_mutex](const json& response) { if (cb_mutex.expired()) { @@ -6618,7 +6666,7 @@ bool SSWCP::query_machine_info(std::shared_ptr& host, std::string& ou } ); - // 等待响应 + // wait response { std::unique_lock lock(*mutex); auto predicate = [&received]() { return received; }; @@ -6626,27 +6674,27 @@ bool SSWCP::query_machine_info(std::shared_ptr& host, std::string& ou } if (!timeout && !system_info.is_null()) { - // 成功获取到信息 + // success to get data if (system_info.count("data")) { system_info = system_info["data"]; } if (system_info.contains("system_info")) { auto& system_data = system_info["system_info"]; - + if(system_data.contains("product_info")){ auto& product_info = system_data["product_info"]; - // 获取机型 + // get the type for machine if(product_info.contains("machine_type")){ out_model = product_info["machine_type"].get(); } - // 获取喷嘴信息 + // get diameter if(product_info.contains("nozzle_diameter")){ try { if (product_info["nozzle_diameter"].is_array()) { for (const auto& nozzle : product_info["nozzle_diameter"]) { - // todo 不一定是string + // todo maybe not string if (nozzle.is_number()) { double temp = nozzle.get(); if (fabs(temp - 0.2) < 1e-6) { @@ -6658,17 +6706,16 @@ bool SSWCP::query_machine_info(std::shared_ptr& host, std::string& ou } else if (fabs(temp - 0.8) < 1e-6) { out_nozzle_diameters.push_back("0.8"); } - + } else { std::string temp = nozzle.get(); if (temp == "0.2" || temp == "0.4" || temp == "0.6" || temp == "0.8") { out_nozzle_diameters.push_back(temp); } } - + } - } else { - // 如果是单个值 + } else { if (product_info["nozzle_diameter"].is_number()) { double temp = product_info["nozzle_diameter"].get(); if (fabs(temp - 0.2) < 1e-6) { @@ -6703,7 +6750,7 @@ bool SSWCP::query_machine_info(std::shared_ptr& host, std::string& ou return true; } } - + return false; } @@ -6713,6 +6760,90 @@ MachineIPType* MachineIPType::getInstance() return &mipt_instance; } +// WebSocket Debug Server implementation +void SSWCP::enable_debug_mode(bool enable, unsigned short port) +{ + std::lock_guard lock(m_debug_server_mutex); + + if (enable && !m_debug_server) { + BOOST_LOG_TRIVIAL(info) << "Enabling WebSocket debug mode on port " << port; + + m_debug_server = std::make_unique(port); + + // Set message callback to handle messages from Flutter Web + m_debug_server->set_message_callback([](const std::string& message) { + BOOST_LOG_TRIVIAL(debug) << "Received message from Flutter Web via WebSocket"; + + // Handle the message using existing logic (no webview in debug/Flutter path) + wxGetApp().CallAfter([message]() { + SSWCP::handle_webmsg_for_debug(message); + }); + }); + + if (m_debug_server->start()) { + m_debug_mode_enabled = true; + BOOST_LOG_TRIVIAL(info) << " WebSocket debug mode enabled successfully"; + BOOST_LOG_TRIVIAL(info) << " Flutter Web can connect to: ws://localhost:" << port; + } else { + BOOST_LOG_TRIVIAL(error) << "Failed to start WebSocket debug server"; + m_debug_server.reset(); + m_debug_mode_enabled = false; + } + } else if (!enable && m_debug_server) { + BOOST_LOG_TRIVIAL(info) << "Disabling WebSocket debug mode"; + m_debug_server->stop(); + m_debug_server.reset(); + m_debug_mode_enabled = false; + } +} + +void SSWCP::disable_debug_mode() +{ + enable_debug_mode(false); +} + +bool SSWCP::is_debug_mode_enabled() +{ + std::lock_guard lock(m_debug_server_mutex); + return m_debug_mode_enabled; +} + +void SSWCP::send_message_to_flutter(const std::string& message) +{ + std::lock_guard lock(m_debug_server_mutex); + + if (m_debug_server && m_debug_mode_enabled) { + m_debug_server->send_message(message); + } else { + BOOST_LOG_TRIVIAL(warning) << "Cannot send message: WebSocket debug mode not enabled"; + } +} + +void SSWCP::send_message_auto(const std::string& message, wxWebView* webview) +{ + // Original production path: send via WebView postMessage (unchanged, not affected by debug logic) + if (webview && webview->GetRefData()) { + BOOST_LOG_TRIVIAL(debug) << "Sending message to Flutter via WebView postMessage"; + std::string js_code = "window.postMessage(JSON.stringify(" + message + "), '*');"; + WebView::RunScript(webview, js_code); + } + + // Debug path: copy the message to Flutter debug interface via WebSocket (independent of original path) + { + std::lock_guard lock(m_debug_server_mutex); + if (m_debug_mode_enabled && m_debug_server && m_debug_server->has_client()) { + BOOST_LOG_TRIVIAL(debug) << "[DEBUG] Sending message to Flutter via WebSocket"; + m_debug_server->send_message(message); + } + } + + // Warning if no channel is available + if ((!webview || !webview->GetRefData()) && + (!m_debug_mode_enabled || !m_debug_server || !m_debug_server->has_client())) { + BOOST_LOG_TRIVIAL(warning) << "Cannot send message: no WebSocket client and no WebView available"; + } +} + }}; // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/SSWCP.hpp b/src/slic3r/GUI/SSWCP.hpp index 1021f8b745e..50b12e8c96b 100644 --- a/src/slic3r/GUI/SSWCP.hpp +++ b/src/slic3r/GUI/SSWCP.hpp @@ -17,6 +17,7 @@ #include "slic3r/Utils/TimeoutMap.hpp" #include "slic3r/Utils/PrintHost.hpp" #include "slic3r/Utils/MQTT.hpp" +#include "WebSocketDebugServer.hpp" using namespace nlohmann; @@ -599,6 +600,9 @@ class SSWCP // Handle incoming web messages static void handle_web_message(std::string message, wxWebView* webview); + // Handle incoming web messages for Flutter debug (no webview required) + static void handle_webmsg_for_debug(std::string message); + // Create new SSWCP instance static std::shared_ptr create_sswcp_instance( std::string cmd, const json& header, const json& data, std::string event_id, wxWebView* webview); @@ -631,10 +635,17 @@ class SSWCP static std::mutex m_file_size_mutex; static long long m_active_file_size; - - + + static std::unordered_map m_tab_map; // for switching tab + // WebSocket Debug Server methods + static void enable_debug_mode(bool enable = true, unsigned short port = 8766); + static void disable_debug_mode(); + static bool is_debug_mode_enabled(); + static void send_message_to_flutter(const std::string& message); + static void send_message_auto(const std::string& message, wxWebView* webview = nullptr); + private: static std::unordered_set m_machine_find_cmd_list; // Machine find commands static std::unordered_set m_machine_option_cmd_list; // Machine option commands @@ -650,6 +661,11 @@ class SSWCP static std::string m_active_gcode_filename; // name of the file which is pretend to be upload and print static std::string m_display_gcode_filename; // name for display + + // WebSocket Debug Server + static std::unique_ptr m_debug_server; + static std::mutex m_debug_server_mutex; + static bool m_debug_mode_enabled; }; class MachineIPType diff --git a/src/slic3r/GUI/WebSocketDebugServer.cpp b/src/slic3r/GUI/WebSocketDebugServer.cpp new file mode 100644 index 00000000000..96f89473ee1 --- /dev/null +++ b/src/slic3r/GUI/WebSocketDebugServer.cpp @@ -0,0 +1,251 @@ +// WebSocket Debug Server implementation +#include "WebSocketDebugServer.hpp" +#include +#include + +namespace Slic3r { namespace GUI { + +WebSocketDebugServer::WebSocketDebugServer(unsigned short port) + : m_port(port) + , m_running(false) + , m_has_client(false) +{ + BOOST_LOG_TRIVIAL(info) << "WebSocketDebugServer created on port " << m_port; +} + +WebSocketDebugServer::~WebSocketDebugServer() +{ + stop(); +} + +bool WebSocketDebugServer::start() +{ + if (m_running.load()) { + BOOST_LOG_TRIVIAL(warning) << "WebSocket Debug Server already running"; + return true; + } + + m_io_context = std::make_unique(); + + tcp::endpoint endpoint(tcp::v4(), m_port); + m_acceptor = std::make_unique(*m_io_context, endpoint); + + m_running.store(true); + + // Start accept thread + m_accept_thread = std::thread(&WebSocketDebugServer::accept_loop, this); + + // Start send worker thread + m_send_thread = std::thread(&WebSocketDebugServer::send_worker, this); + + BOOST_LOG_TRIVIAL(info) << " WebSocket Debug Server started on ws://localhost:" << m_port; + BOOST_LOG_TRIVIAL(info) << " Waiting for Flutter Web client to connect..."; + return true; + +} + +void WebSocketDebugServer::stop() +{ + if (!m_running.load()) + return; + + BOOST_LOG_TRIVIAL(info) << "Stopping WebSocket Debug Server..."; + m_running.store(false); + m_send_cv.notify_all(); + + if (m_acceptor && m_acceptor->is_open()) + { + boost::system::error_code ec; + m_acceptor->close(ec); + + if (ec) + BOOST_LOG_TRIVIAL(warning) << "Error closing acceptor: " << ec.message(); + } + + // Close WebSocket connection + if (m_ws_stream) + { + boost::system::error_code ec; + m_ws_stream->close(websocket::close_code::normal, ec); + + if (ec) + BOOST_LOG_TRIVIAL(warning) << "Error closing WebSocket: " << ec.message(); + } + + // Stop io_context + if (m_io_context) + m_io_context->stop(); + + // Join threads + if (m_accept_thread.joinable()) + m_accept_thread.join(); + + if (m_send_thread.joinable()) + m_send_thread.join(); + + for (auto& t : m_session_threads) + if (t.joinable()) t.join(); + + m_session_threads.clear(); + m_has_client.store(false); + + BOOST_LOG_TRIVIAL(info) << "WebSocket Debug Server stopped"; +} + +void WebSocketDebugServer::accept_loop() +{ + while (m_running.load()) + { + tcp::socket socket(*m_io_context); + + // Accept connection (blocking) + boost::system::error_code ec; + m_acceptor->accept(socket, ec); + + if (ec) + { + + if (m_running.load()) + BOOST_LOG_TRIVIAL(error) << "Accept error: " << ec.message(); + + continue; + } + + BOOST_LOG_TRIVIAL(info) << "Flutter Web client connected from " + << socket.remote_endpoint().address().to_string(); + + // Spawn a thread per session so accept_loop stays unblocked + m_session_threads.emplace_back(&WebSocketDebugServer::session_loop, this, std::move(socket)); + } +} + +void WebSocketDebugServer::session_loop(tcp::socket socket) +{ + // Create WebSocket stream + auto ws = std::make_shared>(std::move(socket)); + + // Set WebSocket options + ws->set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) { + res.set(beast::http::field::server, "OrcaSlicer-Debug-Server"); + } + )); + + // Accept WebSocket handshake + ws->accept(); + + // Swap in the new stream under the lock, then close old streams outside + // the lock so send_worker is never blocked waiting for TCP teardown. + std::vector>> to_close; + { + std::lock_guard lock(m_client_mutex); + to_close = std::move(m_old_streams); + if (m_ws_stream) { + to_close.push_back(m_ws_stream); + } + m_ws_stream = ws; + m_has_client.store(true); + } + + for (auto& old : to_close) { + if (old && old->is_open()) { + boost::system::error_code ec; + old->close(websocket::close_code::going_away, ec); + } + } + + BOOST_LOG_TRIVIAL(info) << " WebSocket handshake completed, client ready"; + + // Message receive loop + while (m_running.load()) { + beast::flat_buffer buffer; + + boost::system::error_code ec; + ws->read(buffer, ec); + + if (ec == websocket::error::closed) { + BOOST_LOG_TRIVIAL(info) << "Client closed connection"; + break; + } + + if (ec) { + BOOST_LOG_TRIVIAL(error) << "Read error: " << ec.message(); + break; + } + + std::string message = beast::buffers_to_string(buffer.data()); + + BOOST_LOG_TRIVIAL(debug) << "Received from Flutter: " << message.substr(0, 200) + << (message.length() > 200 ? "..." : ""); + + // Call message callback + if (m_message_callback) + m_message_callback(message); + } + + // Clean up + { + std::lock_guard lock(m_client_mutex); + m_ws_stream.reset(); + m_has_client.store(false); + } + + BOOST_LOG_TRIVIAL(info) << " Flutter Web client disconnected"; +} + +void WebSocketDebugServer::send_worker() +{ + while (m_running.load()) { + std::string message; + + { + std::unique_lock lock(m_send_mutex); + m_send_cv.wait(lock, [this] { + return !m_send_queue.empty() || !m_running.load(); + }); + + if (!m_running.load()) break; + + message = m_send_queue.front(); + m_send_queue.pop(); + } + + if (!message.empty()) { + std::lock_guard lock(m_client_mutex); + if (m_ws_stream && m_has_client.load()) + { + + boost::system::error_code ec; + m_ws_stream->write(net::buffer(message), ec); + + if (ec) + BOOST_LOG_TRIVIAL(error) << "Send error: " << ec.message(); + else + BOOST_LOG_TRIVIAL(debug) << "Sent to Flutter: " << message.substr(0, 200) + << (message.length() > 200 ? "..." : ""); + + } + } + } +} + +void WebSocketDebugServer::send_message(const std::string& message) +{ + if (!m_running.load()) { + BOOST_LOG_TRIVIAL(warning) << "Cannot send message: server not running"; + return; + } + + { + std::lock_guard lock(m_send_mutex); + m_send_queue.push(message); + } + m_send_cv.notify_one(); +} + +void WebSocketDebugServer::set_message_callback(MessageCallback callback) +{ + m_message_callback = callback; +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/WebSocketDebugServer.hpp b/src/slic3r/GUI/WebSocketDebugServer.hpp new file mode 100644 index 00000000000..78c9135f0a3 --- /dev/null +++ b/src/slic3r/GUI/WebSocketDebugServer.hpp @@ -0,0 +1,76 @@ +// WebSocket Debug Server for Flutter Web debugging +#ifndef WEBSOCKET_DEBUG_SERVER_HPP +#define WEBSOCKET_DEBUG_SERVER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; +namespace websocket = beast::websocket; +namespace net = boost::asio; +using tcp = boost::asio::ip::tcp; + +namespace Slic3r { namespace GUI { + +class WebSocketDebugServer { +public: + using MessageCallback = std::function; + + explicit WebSocketDebugServer(unsigned short port = 8766); + ~WebSocketDebugServer(); + + // Start the WebSocket server + bool start(); + + // Stop the WebSocket server + void stop(); + + // Send message to Flutter Web client + void send_message(const std::string& message); + + // Set callback for receiving messages + void set_message_callback(MessageCallback callback); + + // Check if a client is connected + bool has_client() const { return m_has_client.load(); } + + // Check if server is running + bool is_running() const { return m_running.load(); } + +private: + void accept_loop(); + void session_loop(tcp::socket socket); + void send_worker(); + + unsigned short m_port; + std::atomic m_running; + std::atomic m_has_client; + + std::unique_ptr m_io_context; + std::unique_ptr m_acceptor; + std::thread m_accept_thread; + std::thread m_send_thread; + std::vector m_session_threads; + + std::shared_ptr> m_ws_stream; + std::vector>> m_old_streams; + MessageCallback m_message_callback; + + std::mutex m_send_mutex; + std::condition_variable m_send_cv; + std::queue m_send_queue; + std::mutex m_client_mutex; +}; + +}} // namespace Slic3r::GUI + +#endif // WEBSOCKET_DEBUG_SERVER_HPP