Merge branch 'feat/plugin-feature' into feature/speed-dial

# Conflicts:
#	src/slic3r/GUI/PluginsDialog.hpp
This commit is contained in:
Andrew
2026-07-17 13:59:56 +08:00
151 changed files with 5116 additions and 1422 deletions

View File

@@ -116,8 +116,8 @@ struct PluginDialogItem
PluginAvailableActions available_actions;
};
constexpr bool kFetchCloudCatalog = true;
constexpr bool kUseCurrentCloudCatalog = false;
constexpr bool kFetchCloudMeta = true;
constexpr bool kUseCurrentCloudMeta = false;
// The type of a package's first loaded capability. A package that is not loaded has no type.
PluginCapabilityType primary_capability_type_of(PluginManager& manager, const std::string& plugin_key)
@@ -126,7 +126,7 @@ PluginCapabilityType primary_capability_type_of(PluginManager& manager, const st
return capabilities.empty() ? PluginCapabilityType::Unknown : capabilities.front()->type();
}
std::vector<PluginDescriptor> current_cloud_catalog_snapshot()
std::vector<PluginDescriptor> current_cloud_metadata_snapshot()
{
std::vector<PluginDescriptor> cloud_entries;
for (const PluginDescriptor& entry : PluginManager::instance().get_plugin_descriptors(/*include_invalid=*/true))
@@ -147,21 +147,18 @@ PluginDescriptor as_cloud_only_descriptor(PluginDescriptor descriptor)
return descriptor;
}
// rescan_plugins() clears the whole catalog and rediscovers only local packages. When
// callers do not need fresh cloud data, reuse the current cloud rows after the local
// rescan so cloud-only rows and cloud-derived UI state do not disappear.
void refresh_plugin_catalog_blocking(bool fetch_cloud)
void refresh_plugin_metadata_blocking(bool fetch_cloud)
{
PluginManager& manager = PluginManager::instance();
std::vector<std::string> not_found, unauthorized;
const std::vector<PluginDescriptor> current_cloud_catalog = fetch_cloud ? std::vector<PluginDescriptor>{} :
current_cloud_catalog_snapshot();
const std::vector<PluginDescriptor> current_cloud_metadata = fetch_cloud ? std::vector<PluginDescriptor>{} :
current_cloud_metadata_snapshot();
manager.rescan_plugins();
if (!fetch_cloud) {
manager.update_cloud_catalog(current_cloud_catalog);
manager.update_cloud_metadata(current_cloud_metadata);
return;
}
@@ -447,7 +444,7 @@ void PluginsDialog::set_open_terminal_dlg_fn()
void PluginsDialog::update_plugin_dialog_ui()
{
// Called after the shared catalog is already updated, for example from the
// Called after the any metadata is already updated, for example from the
// cloud-plugin state callback. Do not fetch here or the callback can re-enter.
send_plugins();
resolve_pending_activation();
@@ -479,12 +476,21 @@ void PluginsDialog::on_script_message(const nlohmann::json& payload)
if (handle_common_script_command(payload))
return;
// Defer command handling out of the webview script-message callback: GTK and macOS
// deliver it synchronously inside the native webview callback (see ui_create_window
// in PluginHostUi.cpp), and window work on that stack is the crash class fixed in
// b779a7bfed/f2ccbfc8b5. Deferring at this single entry point keeps every command
// handler, current and future, off that stack by construction.
wxGetApp().CallAfter([this, alive = m_alive, payload]() {
if (alive->load(std::memory_order_acquire))
handle_web_command(payload);
});
}
void PluginsDialog::handle_web_command(const nlohmann::json& payload)
{
const std::string command = payload.value("command", "");
if (command == "request_plugins") {
// The web page finished loading and is asking for the current catalog. Plugin
// discovery already runs at startup and on login, so the shared catalog is up to
// date by the time the dialog opens. Just render it here - no blocking fetch on
// open. The Refresh button (refresh_plugins) is what triggers a fresh discovery.
send_plugins();
} else if (command == "refresh_plugins") {
refresh_plugins();
@@ -497,8 +503,8 @@ void PluginsDialog::on_script_message(const nlohmann::json& payload)
install_plugin_from_file();
} else if (command == "plugin_menu_action") {
handle_plugin_menu_action(payload.value("plugin_key", ""), payload.value("action", ""));
} else if (command == "run_script_plugin") {
run_script_plugin(payload.value("plugin_key", ""), payload.value("capability_name", ""));
} else if (command == "run_script_plugin_capability") {
run_script_plugin_capability(payload.value("plugin_key", ""), payload.value("capability_name", ""));
} else if (command == "open_terminal") {
m_open_terminal_dlg_fn();
} else if (command == "update_plugin") {
@@ -569,16 +575,16 @@ std::shared_ptr<PluginCapabilityInterface> PluginsDialog::get_capability(const s
return PluginManager::instance().get_plugin_capability(plugin_key, capability_name, type, /*only_enabled=*/false);
}
void PluginsDialog::refresh_plugin_catalog_async(const wxString& title, const wxString& message, bool fetch_cloud)
void PluginsDialog::refresh_plugin_metadata_async(const wxString& title, const wxString& message, bool fetch_cloud)
{
run_with_dialog([fetch_cloud]() { refresh_plugin_catalog_blocking(fetch_cloud); }, [this]() { send_plugins(); }, title, message);
run_with_dialog([fetch_cloud]() { refresh_plugin_metadata_blocking(fetch_cloud); }, [this]() { send_plugins(); }, title, message);
}
void PluginsDialog::refresh_plugins()
{
BOOST_LOG_TRIVIAL(info) << "Refreshing plugins from Plugins dialog";
refresh_plugin_catalog_async(_L("Refreshing"), _L("Refreshing plugins data"), kFetchCloudCatalog);
refresh_plugin_metadata_async(_L("Refreshing"), _L("Refreshing plugins data"), kFetchCloudMeta);
}
void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled)
@@ -590,9 +596,6 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled)
PluginDescriptor row_data;
if (!get_descriptor(plugin_key, row_data)) {
// The row no longer maps to a catalog entry (the catalog changed under the UI).
// Toggling is a local action, so just re-render from the current catalog; a cloud
// fetch (or clearing rescan) adds nothing here.
send_plugins();
return;
}
@@ -683,9 +686,6 @@ void PluginsDialog::toggle_plugin_capability(const std::string& plugin_key, Plug
PluginDescriptor row_data;
if (!get_descriptor(plugin_key, row_data)) {
// The row no longer maps to a catalog entry (the catalog changed under the UI).
// Toggling is a local action, so just re-render from the current catalog; a cloud
// fetch (or clearing rescan) adds nothing here.
send_plugins();
return;
}
@@ -738,12 +738,24 @@ void PluginsDialog::handle_plugin_menu_action(const std::string& plugin_key, con
}
}
void PluginsDialog::restore_z_order()
{
// Deferred so it runs on a clean stack after the modal has fully torn down; the
// alive guard covers the dialog being destroyed while the CallAfter is queued.
wxGetApp().CallAfter([this, alive = m_alive]() {
if (alive->load(std::memory_order_acquire) && IsShown())
Raise();
});
}
void PluginsDialog::install_plugin_from_file()
{
wxFileDialog dialog(this, _L("Select plugin package"), wxEmptyString, wxEmptyString, _L("Plugin files (*.py;*.whl)|*.py;*.whl"),
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK)
const int rc = dialog.ShowModal();
restore_z_order();
if (rc != wxID_OK)
return;
if (!install_plugin_package(dialog.GetPath().ToUTF8().data())) {
@@ -796,7 +808,9 @@ bool PluginsDialog::install_plugin_package(const std::string& package_path)
plugin_name),
kOverwritePluginTitle, wxOK | wxCANCEL | wxCANCEL_DEFAULT | wxICON_WARNING);
dialog.SetOKCancelLabels(_L("Overwrite"), _L("Cancel"));
if (dialog.ShowModal() != wxID_OK) {
const int overwrite_rc = dialog.ShowModal();
restore_z_order();
if (overwrite_rc != wxID_OK) {
BOOST_LOG_TRIVIAL(info) << "Plugin package installation cancelled before overwrite. package=" << package_path
<< " plugin=" << plugin_descriptor.name;
return false;
@@ -824,7 +838,7 @@ bool PluginsDialog::install_plugin_package(const std::string& package_path)
BOOST_LOG_TRIVIAL(info) << "Plugin package installed successfully from " << package_path;
const wxString installed_name = from_u8(plugin_descriptor.name.empty() ? package_file.filename().string() : plugin_descriptor.name);
show_status(wxString::Format(_L("Installed \"%s\"."), installed_name), "success");
refresh_plugin_catalog_async(_L("Refreshing"), _L("Refreshing plugins data"), kUseCurrentCloudCatalog);
refresh_plugin_metadata_async(_L("Refreshing"), _L("Refreshing plugins data"), kUseCurrentCloudMeta);
return true;
}
@@ -878,7 +892,7 @@ wxString PluginsDialog::plugin_display_name(const std::string& plugin_key) const
return from_u8(plugin_key);
}
void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std::string& capability_name)
void PluginsDialog::run_script_plugin_capability(const std::string& plugin_key, const std::string& capability_name)
{
PluginManager& manager = PluginManager::instance();
@@ -1012,16 +1026,17 @@ void PluginsDialog::delete_local_plugin(const PluginDescriptor& plugin)
const wxString plugin_name = from_u8(plugin.name);
const int rc = wxMessageBox(wxString::Format(_L("Delete plugin \"%s\"?\n\nThis permanently removes the plugin folder."), plugin_name),
kDeletePluginTitle, wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
restore_z_order();
if (rc != wxYES)
return;
auto state = std::make_shared<PluginOperationState>();
run_with_dialog(
[plugin_key = plugin.plugin_key, refresh_catalog = plugin.is_cloud_plugin(), state]() {
[plugin_key = plugin.plugin_key, should_refresh = plugin.is_cloud_plugin(), state]() {
std::string error;
const bool succeeded = PluginManager::instance().delete_plugin(plugin_key, error);
if (succeeded && refresh_catalog)
refresh_plugin_catalog_blocking(kFetchCloudCatalog);
if (succeeded && should_refresh)
refresh_plugin_metadata_blocking(kFetchCloudMeta);
store_plugin_operation_result(state, succeeded, std::move(error));
},
[this, state, plugin_name]() {
@@ -1044,6 +1059,7 @@ void PluginsDialog::unsubscribe_cloud_plugin(const PluginDescriptor& plugin)
wxString::Format(_L("Unsubscribe plugin \"%s\"?\n\nThis will stop tracking the plugin and delete any local plugin files."),
plugin_name),
kUnsubscribeTitle, wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
restore_z_order();
if (rc != wxYES)
return;
@@ -1126,7 +1142,7 @@ void PluginsDialog::reinstall_cloud_plugin(const PluginDescriptor& plugin)
return;
}
manager.update_cloud_catalog(std::vector<PluginDescriptor>{as_cloud_only_descriptor(plugin)});
manager.update_cloud_metadata(std::vector<PluginDescriptor>{as_cloud_only_descriptor(plugin)});
}
if (!install_cloud_plugin(plugin_key, plugin.version, from_u8(plugin.name))) {
@@ -1175,6 +1191,7 @@ void PluginsDialog::delete_mine_local_and_cloud_plugin(const std::string& plugin
"deletes the plugin from the cloud. This action cannot be undone."),
plugin_name),
kDeletePluginTitle, wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
restore_z_order();
if (rc != wxYES)
return;
@@ -1184,10 +1201,6 @@ void PluginsDialog::delete_mine_local_and_cloud_plugin(const std::string& plugin
return;
}
// delete_mine_local_and_cloud_plugin already updated the in-memory catalog
// (finalize_cloud_plugin_removal removes the row and, when a local package existed,
// re-syncs the cloud list itself), so a UI refresh is sufficient here - an extra
// clearing rescan + cloud fetch would be redundant.
send_plugins();
show_status(wxString::Format(_L("Deleted \"%s\"."), plugin_name), "success");
}