#ifndef slic3r_PluginsDialog_hpp_ #define slic3r_PluginsDialog_hpp_ #include "Widgets/WebViewHostDialog.hpp" #include "PluginSource.hpp" #include "PluginStatus.hpp" #include "PluginSort.hpp" #include "slic3r/plugin/PluginDescriptor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class wxTimer; namespace Slic3r { class PluginCapabilityInterface; enum class PluginCapabilityType; namespace GUI { class PluginsDialog : public Slic3r::GUI::WebViewHostDialog { public: PluginsDialog(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT(""), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxMAXIMIZE_BOX | wxRESIZE_BORDER); ~PluginsDialog(); void set_open_terminal_dlg_fn(); void update_plugin_dialog_ui(); private: void open_plugin_on_cloud(const std::string& sharing_token); void open_plugin_hub(); void on_script_message(const nlohmann::json& payload) override; void send_plugins(); void set_plugin_sort(const std::string& sort_key, const std::string& sort_order); nlohmann::json build_plugins_payload() const; bool get_descriptor(const std::string& plugin_key, Slic3r::PluginDescriptor& descriptor) const; std::shared_ptr get_capability(const std::string& plugin_key, PluginCapabilityType type, const std::string& capability_name) const; void refresh_plugin_catalog_async(const wxString& title, const wxString& message, bool fetch_cloud); void refresh_plugins(); void toggle_plugin(const std::string& plugin_key, bool enabled); void toggle_plugin_capability(const std::string& plugin_key, PluginCapabilityType type, const std::string& capability_name, bool enabled); void handle_plugin_menu_action(const std::string& plugin_key, const std::string& action); void install_plugin_from_file(); bool install_plugin_package(const std::string& package_path); bool install_cloud_plugin(const std::string& uuid, const std::string& version, const wxString& name); void run_script_plugin(const std::string& plugin_key, const std::string& capability_name); // Config tab. Both are scoped to (plugin_key, type, capability_name): a request naming a // capability that is gone or not configurable is refused rather than served from, or written // to, some other entry. void send_capability_config(const std::string& plugin_key, PluginCapabilityType type, const std::string& capability_name); void save_capability_config(const std::string& plugin_key, PluginCapabilityType type, const std::string& capability_name, const nlohmann::json& config); void restore_capability_config(const std::string& plugin_key, PluginCapabilityType type, const std::string& capability_name); // Pushes a one-line result into the web footer status bar (level: "success" | "warn" | "error" | "info"), // used for every plugin/capability operation instead of a modal box so the dialog stays non-disruptive. void show_status(const wxString& message, const char* level); // Best-effort human-readable name for a plugin_key (falls back to the key itself). wxString plugin_display_name(const std::string& plugin_key) const; // Turns the pending "Activating..." status into "Activated"/"Failed to activate" once an // asynchronous plugin load reported via update_plugin_dialog_ui() finishes. No-op otherwise. void resolve_pending_activation(); void update_plugin(const std::string& plugin_key); void open_plugin_folder(const Slic3r::PluginDescriptor& plugin); void delete_local_plugin(const Slic3r::PluginDescriptor& plugin); void unsubscribe_cloud_plugin(const Slic3r::PluginDescriptor& plugin); void reinstall_local_plugin(const std::string& plugin_key); void reinstall_cloud_plugin(const Slic3r::PluginDescriptor& plugin); void delete_mine_local_and_cloud_plugin(const std::string& plugin_key); // In the future, we can allow users to choose which plugin version they want to install. template void run_with_dialog(Run&& run, OnFinish&& on_finish, const wxString& title, const wxString& message, int maximum = 100, int style = wxPD_APP_MODAL | wxPD_AUTO_HIDE, bool finish_after_dialog_destroyed = false) { const auto alive = m_alive; wxProgressDialog* progress = new wxProgressDialog(title, message, maximum, this, style); wxTimer* timer = new wxTimer(); timer->Bind(wxEVT_TIMER, [alive, progress, message](wxTimerEvent&) { if (alive->load(std::memory_order_acquire) && progress) progress->Pulse(message); }); timer->Start(100); std::thread([alive, progress, timer, run = std::forward(run), on_finish = std::forward(on_finish), finish_after_dialog_destroyed]() mutable { try { run(); } catch (const std::exception& ex) { BOOST_LOG_TRIVIAL(error) << "Plugin dialog worker failed: " << ex.what(); } catch (...) { BOOST_LOG_TRIVIAL(error) << "Plugin dialog worker failed with an unknown exception"; } if (wxTheApp == nullptr) return; wxTheApp->CallAfter([alive, progress, timer, on_finish = std::move(on_finish), finish_after_dialog_destroyed]() mutable { timer->Stop(); delete timer; if (alive->load(std::memory_order_acquire)) { progress->Destroy(); on_finish(); } else if (finish_after_dialog_destroyed) { on_finish(); } }); }).detach(); } template std::invoke_result_t&> run_with_dialog_wait(Run&& run, const wxString& title, const wxString& message, int maximum = 100, int style = wxPD_APP_MODAL | wxPD_AUTO_HIDE) { using Result = std::invoke_result_t&>; bool finished = false; wxEventLoop loop; auto on_finish = [&finished, &loop]() { finished = true; if (loop.IsRunning()) loop.Exit(); }; if constexpr (std::is_void_v) { struct WaitState { std::mutex mutex; std::exception_ptr exception; }; auto state = std::make_shared(); run_with_dialog( [run = std::forward(run), state]() mutable { try { run(); } catch (...) { std::lock_guard lock(state->mutex); state->exception = std::current_exception(); } }, on_finish, title, message, maximum, style, true); if (!finished) loop.Run(); std::exception_ptr exception; { std::lock_guard lock(state->mutex); exception = state->exception; } if (exception) std::rethrow_exception(exception); } else { using StoredResult = std::decay_t; struct WaitState { std::mutex mutex; std::optional result; std::exception_ptr exception; }; auto state = std::make_shared(); run_with_dialog( [run = std::forward(run), state]() mutable { try { StoredResult result = run(); std::lock_guard lock(state->mutex); state->result.emplace(std::move(result)); } catch (...) { std::lock_guard lock(state->mutex); state->exception = std::current_exception(); } }, on_finish, title, message, maximum, style, true); if (!finished) loop.Run(); std::optional result; std::exception_ptr exception; { std::lock_guard lock(state->mutex); if (state->result) result.emplace(std::move(*state->result)); exception = state->exception; } if (exception) std::rethrow_exception(exception); return std::move(*result); } } std::function m_open_terminal_dlg_fn; std::shared_ptr> m_alive = std::make_shared>(true); PluginSortKey m_plugin_sort_key = PluginSortKey::None; PluginSortOrder m_plugin_sort_order = PluginSortOrder::Asc; // Serializes run_script_plugin. With main-thread execution a plugin's orca.host.ui modal // (message/show_dialog) or the result message box pumps a nested event loop, which could // re-dispatch the web "run_script_plugin" command and start a second, overlapping run. bool m_script_running = false; // Plugin whose asynchronous activation is in flight, awaited by resolve_pending_activation(). // Empty when no activation is pending. std::string m_activating_plugin_key; }; } // namespace GUI } // namespace Slic3r #endif