Merge upstream main: vase settings from plate settings, infill density with modifiers, STEP chamfers, plugin thread permission

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Clifford GarwoodandClaude Opus 5.5 committed 2026-10-08 23:31:15 -04:00
commit d4edf59301
26 files changed
+513 -161

No files matched your search

@@ -303,10 +303,11 @@ else
fi
has_host_runtime_library() {
local lib_name path
local lib_name="\$1"
local path
if command -v ldconfig >/dev/null 2>&1; then
if ldconfig -p 2>/dev/null | grep -Fq " \$lib_name"; then
if ldconfig -p 2>/dev/null | grep -Fq "\$lib_name ("; then
return 0
fi
fi
+4 -4
View File
@@ -1320,7 +1320,7 @@ void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill>
#endif
// friend to Layer
void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator)
void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator)
{
for (LayerRegion *layerm : m_regions)
layerm->fills.clear();
@@ -1353,7 +1353,7 @@ void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle;
const FillAdaptive::Octrees *octrees = surface_fill.params.pattern == ipSupportCubic ? support_fill_octrees : adaptive_fill_octrees;
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
if (surface_fill.params.pattern == ipConcentricInternal) {
@@ -1516,7 +1516,7 @@ void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const
* - For lightning/adaptive patterns, the respective generators are wired so their
* polylines match the final infill layout.
*/
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator) const
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator) const
{
LockRegionParam skin_inner_param;
std::vector<SurfaceFill> surface_fills = group_fills(*this, skin_inner_param);
@@ -1574,7 +1574,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdapti
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle;
const FillAdaptive::Octrees *octrees = surface_fill.params.pattern == ipSupportCubic ? support_fill_octrees : adaptive_fill_octrees;
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
+14 -77
View File
@@ -301,88 +301,25 @@ void OctreeDeleter::operator()(Octree *p) {
delete p;
}
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object)
std::vector<double> adaptive_fill_line_spacing(const PrintObject &print_object)
{
// Output, spacing for icAdaptiveCubic and icSupportCubic
double adaptive_line_spacing = 0.;
double support_line_spacing = 0.;
enum class Tristate {
Yes,
No,
Maybe
};
struct RegionFillData {
Tristate has_adaptive_infill;
Tristate has_support_infill;
double density;
double extrusion_width;
};
std::vector<RegionFillData> region_fill_data;
region_fill_data.reserve(print_object.num_printing_regions());
bool build_octree = false;
std::vector<double> line_spacing(print_object.num_printing_regions(), 0.);
const std::vector<double> &nozzle_diameters = print_object.print()->config().nozzle_diameter.values;
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) {
const PrintRegionConfig &config = print_object.printing_region(region_id).config();
bool nonempty = config.sparse_infill_density > 0;
bool has_adaptive_infill = nonempty && config.sparse_infill_pattern == ipAdaptiveCubic;
bool has_support_infill = nonempty && config.sparse_infill_pattern == ipSupportCubic;
double sparse_infill_line_width = config.sparse_infill_line_width.get_abs_value(max_nozzle_diameter);
region_fill_data.push_back(RegionFillData({
has_adaptive_infill ? Tristate::Maybe : Tristate::No,
has_support_infill ? Tristate::Maybe : Tristate::No,
config.sparse_infill_density,
sparse_infill_line_width != 0. ? sparse_infill_line_width : default_infill_extrusion_width
}));
build_octree |= has_adaptive_infill || has_support_infill;
for (size_t region_id = 0; region_id < line_spacing.size(); ++ region_id) {
const PrintRegionConfig &config = print_object.printing_region(region_id).config();
if (config.sparse_infill_density <= 0 || ! is_octree_infill_pattern(config.sparse_infill_pattern) ||
std::none_of(print_object.layers().begin(), print_object.layers().end(), [region_id](const Layer *layer) {
return region_id < layer->regions().size() && ! layer->regions()[region_id]->fill_surfaces.empty();
}))
continue;
double extrusion_width = config.sparse_infill_line_width.get_abs_value(max_nozzle_diameter);
if (extrusion_width == 0.)
extrusion_width = default_infill_extrusion_width;
line_spacing[region_id] = extrusion_width / ((config.sparse_infill_density / 100.0f) * 0.333333333f) * config.fill_multiline.value;
}
if (build_octree) {
// Compute the average of above parameters over all layers
for (const Layer *layer : print_object.layers())
for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) {
RegionFillData &rd = region_fill_data[region_id];
if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
rd.has_adaptive_infill = Tristate::Yes;
if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
rd.has_support_infill = Tristate::Yes;
}
double adaptive_fill_density = 0.;
double adaptive_infill_extrusion_width = 0.;
int adaptive_cnt = 0;
double support_fill_density = 0.;
double support_infill_extrusion_width = 0.;
int support_cnt = 0;
for (const RegionFillData &rd : region_fill_data) {
if (rd.has_adaptive_infill == Tristate::Yes) {
adaptive_fill_density += rd.density;
adaptive_infill_extrusion_width += rd.extrusion_width;
++ adaptive_cnt;
} else if (rd.has_support_infill == Tristate::Yes) {
support_fill_density += rd.density;
support_infill_extrusion_width += rd.extrusion_width;
++ support_cnt;
}
}
auto to_line_spacing = [](int cnt, double density, double extrusion_width) {
if (cnt) {
density /= double(cnt);
extrusion_width /= double(cnt);
return extrusion_width / ((density / 100.0f) * 0.333333333f);
} else
return 0.;
};
const int n_multiline = print_object.printing_region(0).config().fill_multiline.value;
adaptive_line_spacing = to_line_spacing(adaptive_cnt, adaptive_fill_density, adaptive_infill_extrusion_width) * n_multiline;
support_line_spacing = to_line_spacing(support_cnt, support_fill_density, support_infill_extrusion_width) * n_multiline;
}
return std::make_pair(adaptive_line_spacing, support_line_spacing);
return line_spacing;
}
// Context used by generate_infill_lines() when recursively traversing an octree in a DDA fashion
+16 -5
View File
@@ -59,11 +59,22 @@ struct Octrees
}
};
// Calculate line spacing for
// 1) adaptive cubic infill
// 2) adaptive internal support cubic infill
// Returns zero for a particular infill type if no such infill is to be generated.
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object);
// Orca: The octrees of each line spacing the regions of an object fill with.
struct RegionOctrees
{
std::vector<Octrees> sets;
// Index into sets for each region, -1 for a region without adaptive or support cubic infill.
std::vector<int> region_set;
const Octrees *region(size_t region_id) const
{
return region_id < region_set.size() && region_set[region_id] >= 0 ? &sets[region_set[region_id]] : nullptr;
}
};
// Line spacing of the adaptive or support cubic infill of each region of the object,
// zero for a region that generates no such infill.
std::vector<double> adaptive_fill_line_spacing(const PrintObject &print_object);
// Rotation of the octree to stand on one of its corners.
Eigen::Quaterniond transform_to_world();
+4 -5
View File
@@ -33,7 +33,7 @@ class PrintObject;
class Print;
namespace FillAdaptive {
struct Octrees;
struct RegionOctrees;
};
namespace FillLightning {
@@ -207,10 +207,9 @@ public:
static bool is_perimeter_compatible(const Print& print, const PrintRegion& a, const PrintRegion& b);
void make_perimeters();
// Phony version of make_fills() without parameters for Perl integration only.
void make_fills() { this->make_fills(nullptr, nullptr); }
void make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator = nullptr);
Polylines generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::Octrees *adaptive_fill_octrees,
const FillAdaptive::Octrees *support_fill_octrees,
void make_fills() { this->make_fills(nullptr); }
void make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator = nullptr);
Polylines generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::RegionOctrees *fill_octrees,
FillLightning::Generator* lightning_generator) const;
void make_ironing();
// Returns the filament id (1-based) the region is ironed with, or -1 when the
+22 -1
View File
@@ -1045,7 +1045,28 @@ void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& b
// But we can force it to work by spliting the src mesh into disconnected components,
// and do booleans seperately, then merge all the results.
indexed_triangle_set all_its;
if (boolean_opts == "UNION" || boolean_opts == "A_NOT_B") {
if (boolean_opts == "A_NOT_B") {
// Each cut can leave the source with several disconnected components, which mcut rejects
// in the next dispatch, so re-split after every cut part (e.g. each letter of a text).
std::vector<indexed_triangle_set> parts = std::move(src_parts);
for (size_t j = 0; j < cut_parts.size(); j++) {
auto cut_part = triangle_mesh_to_mcut(cut_parts[j]);
std::vector<indexed_triangle_set> next_parts;
for (indexed_triangle_set &part : parts) {
auto src_part = triangle_mesh_to_mcut(part);
if (do_boolean_single(*src_part, *cut_part, boolean_opts)) {
TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part);
std::vector<indexed_triangle_set> pieces = its_split(tri_part.its);
std::move(pieces.begin(), pieces.end(), std::back_inserter(next_parts));
} else
next_parts.emplace_back(std::move(part));
}
parts = std::move(next_parts);
}
for (const indexed_triangle_set &part : parts)
its_merge(all_its, part);
}
else if (boolean_opts == "UNION") {
for (size_t i = 0; i < src_parts.size(); i++) {
auto src_part = triangle_mesh_to_mcut(src_parts[i]);
for (size_t j = 0; j < cut_parts.size(); j++) {
+2 -2
View File
@@ -583,7 +583,7 @@ private:
void discover_horizontal_shells();
void combine_infill();
void _generate_support_material();
std::pair<FillAdaptive::Octrees, FillAdaptive::Octrees> prepare_adaptive_infill_data(
FillAdaptive::RegionOctrees prepare_adaptive_infill_data(
const std::vector<std::pair<const Surface*, const Layer*>>& surfaces_w_layer) const;
FillLightning::GeneratorPtr prepare_lightning_infill_data();
@@ -616,7 +616,7 @@ private:
// so that next call to make_perimeters() performs a union() before computing loops
bool m_typed_slices = false;
std::pair<FillAdaptive::Octrees, FillAdaptive::Octrees> m_adaptive_fill_octrees;
FillAdaptive::RegionOctrees m_adaptive_fill_octrees;
std::vector<BoundingBox> m_separated_body_bboxes;
FillLightning::GeneratorPtr m_lightning_generator;
+1 -1
View File
@@ -13172,7 +13172,7 @@ CustomGcodeSpecificConfigDef::CustomGcodeSpecificConfigDef()
// Common Defs
def = this->add("layer_num", coInt);
def->label = L("Layer number");
def->tooltip = L("Index of the current layer. One-based (i.e. first layer is number 1).");
def->tooltip = L("Index of the current layer. Zero-based (i.e. first layer is number 0), except in extrusion role change G-code, where it is one-based.");
def = this->add("layer_z", coFloat);
def->label = L("Layer Z");
+53 -35
View File
@@ -848,7 +848,7 @@ void PrintObject::infill()
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
m_print->throw_if_canceled();
m_layers[layer_idx]->make_fills(&m_adaptive_fill_octrees.first, &m_adaptive_fill_octrees.second, this->m_lightning_generator.get());
m_layers[layer_idx]->make_fills(&m_adaptive_fill_octrees, this->m_lightning_generator.get());
}
}
);
@@ -1168,14 +1168,27 @@ static std::vector<indexed_triangle_set> split_mesh_by_body(const PrintObject &o
return bodies;
}
std::pair<FillAdaptive::Octrees, FillAdaptive::Octrees> PrintObject::prepare_adaptive_infill_data(
FillAdaptive::RegionOctrees PrintObject::prepare_adaptive_infill_data(
const std::vector<std::pair<const Surface *, const Layer *>> &surfaces_w_layer) const
{
using namespace FillAdaptive;
auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this);
if ((adaptive_line_spacing == 0. && support_line_spacing == 0.) || this->layers().empty())
// Orca: Each region fills with the octrees of its own line spacing, shared by the regions of equal spacing.
const std::vector<double> line_spacing = adaptive_fill_line_spacing(*this);
std::vector<std::pair<double, bool>> spacings; // Line spacing, support cubic.
RegionOctrees octrees;
octrees.region_set.assign(line_spacing.size(), -1);
for (size_t region_id = 0; region_id < line_spacing.size(); ++ region_id)
if (line_spacing[region_id] > 0.) {
const std::pair<double, bool> spacing(line_spacing[region_id], this->printing_region(region_id).config().sparse_infill_pattern == ipSupportCubic);
const auto it = std::find(spacings.begin(), spacings.end(), spacing);
octrees.region_set[region_id] = int(it - spacings.begin());
if (it == spacings.end())
spacings.push_back(spacing);
}
if (spacings.empty() || this->layers().empty())
return {};
octrees.sets.resize(spacings.size());
indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set();
// Rotate mesh and build octree on it with axis-aligned (standart base) cubes.
@@ -1198,44 +1211,50 @@ std::pair<FillAdaptive::Octrees, FillAdaptive::Octrees> PrintObject::prepare_ada
}
});
// Orca: Each body gets the octree it has when sliced on its own, from its own triangles.
std::pair<Octrees, Octrees> octrees;
const size_t num_bodies = m_separated_body_bboxes.size();
bool need_object = num_bodies <= 1;
// Orca: Each body gets the octree it has when sliced on its own, from its own triangles, for each line spacing
// its regions fill with. Body num_bodies stands for the whole object, which serves an object of a single body
// and the surfaces of bodies that have no octree of their own.
const size_t num_bodies = m_separated_body_bboxes.size();
std::vector<std::pair<size_t, size_t>> to_build; // Set, body.
std::vector<indexed_triangle_set> body_meshes;
std::vector<std::vector<Vec3d>> body_overhangs(num_bodies);
if (num_bodies > 1) {
const std::vector<indexed_triangle_set> body_meshes = split_mesh_by_body(*this, mesh, num_bodies);
need_object = std::any_of(body_meshes.begin(), body_meshes.end(), [](const indexed_triangle_set &its) { return its.indices.empty(); });
std::vector<std::vector<Vec3d>> body_overhangs(num_bodies);
body_meshes = split_mesh_by_body(*this, mesh, num_bodies);
for (size_t i = 0; i < surfaces_w_layer.size(); ++ i)
if (const int body = separated_body_at(*surfaces_w_layer[i].second, surfaces_w_layer[i].first->expolygon.contour.points.front()); body >= 0)
append(body_overhangs[body], overhangs[i]);
if (adaptive_line_spacing)
octrees.first.bodies.resize(num_bodies);
if (support_line_spacing)
octrees.second.bodies.resize(num_bodies);
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_bodies), [&, adaptive_spacing = adaptive_line_spacing, support_spacing = support_line_spacing](
const tbb::blocked_range<size_t> &range) {
for (size_t body = range.begin(); body < range.end(); ++ body) {
m_print->throw_if_canceled();
if (body_meshes[body].indices.empty())
continue;
if (adaptive_spacing)
octrees.first.bodies[body] = build_octree(body_meshes[body], body_overhangs[body], adaptive_spacing, false);
if (support_spacing)
octrees.second.bodies[body] = build_octree(body_meshes[body], body_overhangs[body], support_spacing, true);
}
});
}
std::vector<std::vector<char>> fills(spacings.size(), std::vector<char>(num_bodies + 1, false));
for (const Layer *layer : m_layers)
for (size_t region_id = 0; region_id < layer->regions().size() && region_id < octrees.region_set.size(); ++ region_id)
if (const int set = octrees.region_set[region_id]; set >= 0)
for (const Surface &surface : layer->regions()[region_id]->fill_surfaces) {
const int body = separated_body_at(*layer, surface.expolygon.contour.points.front());
fills[set][body >= 0 && ! body_meshes[body].indices.empty() ? size_t(body) : num_bodies] = true;
}
for (size_t set = 0; set < spacings.size(); ++ set) {
octrees.sets[set].bodies.resize(num_bodies);
for (size_t body = 0; body <= num_bodies; ++ body)
if (fills[set][body])
to_build.emplace_back(set, body);
}
} else
for (size_t set = 0; set < spacings.size(); ++ set)
to_build.emplace_back(set, num_bodies);
// and gather them.
for (size_t i = 1; i < overhangs.size(); ++ i)
append(overhangs.front(), std::move(overhangs[i]));
// Orca: The object's octree only serves bodies that have none of their own.
if (need_object && adaptive_line_spacing)
octrees.first.object = build_octree(mesh, overhangs.front(), adaptive_line_spacing, false);
if (need_object && support_line_spacing)
octrees.second.object = build_octree(mesh, overhangs.front(), support_line_spacing, true);
tbb::parallel_for(tbb::blocked_range<size_t>(0, to_build.size()), [&](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++ i) {
m_print->throw_if_canceled();
const auto [set, body] = to_build[i];
const bool object = body == num_bodies;
(object ? octrees.sets[set].object : octrees.sets[set].bodies[body]) =
build_octree(object ? mesh : body_meshes[body], object ? overhangs.front() : body_overhangs[body], spacings[set].first,
spacings[set].second);
}
});
return octrees;
}
@@ -3077,8 +3096,7 @@ void PrintObject::bridge_over_infill()
for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) {
size_t lidx = layers_to_generate_infill[job_idx];
infill_lines.at(
lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(&po->m_adaptive_fill_octrees.first,
&po->m_adaptive_fill_octrees.second,
lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(&po->m_adaptive_fill_octrees,
po->m_lightning_generator.get());
}
});
+3 -3
View File
@@ -4123,7 +4123,7 @@ void PartPlate::set_vase_mode_related_object_config(int obj_id) {
new_conf.set_key_value("detect_thin_wall", new ConfigOptionBool(false));
new_conf.set_key_value("timelapse_type", new ConfigOptionEnum<TimelapseType>(tlTraditional));
new_conf.set_key_value("overhang_reverse", new ConfigOptionBool(false));
auto applying_keys = global_config->diff(new_conf);
const auto applying_keys = global_config->diff(new_conf);
for (ModelObject* object : obj_ptrs) {
ModelConfigObject& config = object->config;
@@ -4132,8 +4132,8 @@ void PartPlate::set_vase_mode_related_object_config(int obj_id) {
config.set_key_value(opt_key, new_conf.option(opt_key)->clone());
}
applying_keys = config.get().diff(new_conf);
for (auto opt_key : applying_keys) {
const auto object_keys = config.get().diff(new_conf);
for (auto opt_key : object_keys) {
config.set_key_value(opt_key, new_conf.option(opt_key)->clone());
}
}
+3 -4
View File
@@ -947,13 +947,12 @@ std::string Http::tls_global_init()
std::string Http::tls_system_cert_store()
{
std::string ret;
#ifdef OPENSSL_CERT_OVERRIDE
ret = ::getenv(X509_get_default_cert_file_env());
if (const char *cert_file = ::getenv(X509_get_default_cert_file_env()))
return cert_file;
#endif
return ret;
return {};
}
std::string Http::url_encode(const std::string &str)
+73 -9
View File
@@ -116,6 +116,8 @@ static const std::unordered_map<std::string, AuditEventCategory> audit_event_cat
{"_winapi.CreateProcess", AuditEventCategory::ProcessCreate},
{"_posixsubprocess.fork_exec", AuditEventCategory::ProcessCreate},
// threading
{"_thread.start_new_thread", AuditEventCategory::Threading},
// processreplace: exec* replaces the current process image rather than spawning a child
{"os.exec", AuditEventCategory::ProcessReplace},
};
@@ -739,6 +741,12 @@ std::vector<std::string> audit_targets(const std::string& event_name, AuditEvent
}
return targets;
}
case AuditEventCategory::Threading:
// Thread creation exposes no user-supplied target. Use a fixed sentinel so the grant
// persists per plugin: the permission list matches targets by exact string, and the
// started function's repr embeds an address that changes every run.
targets.emplace_back("thread");
return targets;
default:
break;
}
@@ -765,6 +773,7 @@ std::vector<std::string>* permission_list_for(AuditEventCategory category, Plugi
case AuditEventCategory::Http: return &permissions.network_http;
case AuditEventCategory::Socket: return &permissions.network_socket;
case AuditEventCategory::ProcessCreate: return &permissions.process;
case AuditEventCategory::Threading: return &permissions.threading;
case AuditEventCategory::ProcessReplace: return &permissions.process;
default: return nullptr;
}
@@ -837,6 +846,8 @@ wxString audit_message(AuditEventCategory category, const wxString& plugin_name,
return wxString::Format(_L("Plugin \"%s\" is requesting to open a network connection to:\n%s"), plugin_name, target_list);
case AuditEventCategory::ProcessCreate:
return wxString::Format(_L("Plugin \"%s\" is requesting to run the following command(s):\n%s"), plugin_name, target_list);
case AuditEventCategory::Threading:
return wxString::Format(_L("Plugin \"%s\" is requesting permission to create a thread."), plugin_name);
case AuditEventCategory::ProcessReplace:
return wxString::Format(_L("Plugin \"%s\" is requesting to replace the running application with:\n%s"), plugin_name, target_list);
default:
@@ -844,6 +855,67 @@ wxString audit_message(AuditEventCategory category, const wxString& plugin_name,
}
}
// Builds and shows the modal permission prompt. Must run on the GUI thread.
bool prompt_for_targets(AuditEventCategory category, const std::string& plugin_name, const std::string& event_name,
const std::vector<std::string>& unresolved)
{
wxString target_list;
for (const auto& target : unresolved)
target_list += wxString::FromUTF8(target.c_str()) + "\n";
wxMessageDialog dialog(nullptr,
audit_message(category, wxString::FromUTF8(plugin_name.c_str()),
wxString::FromUTF8(event_name.c_str()), target_list),
_L("Plugin permission request"), wxYES_NO | wxICON_WARNING);
return dialog.ShowModal() == wxID_YES;
}
// Records a grant in the plugin's sidecar so it is not asked again. Reads the install state
// freshly because the async prompt outlives the caller's stack copy of it.
void persist_grant(const std::string& plugin_key, AuditEventCategory category, const std::vector<std::string>& targets)
{
PluginInstallState state;
if (!PluginManager::instance().get_install_state(plugin_key, state))
return;
std::vector<std::string>* permission_list = permission_list_for(category, state.permissions);
if (!permission_list)
return;
for (const auto& target : targets)
persist_permission(plugin_key, state, *permission_list, target);
}
// Requests permission for an audited event, returning true when it is already granted or the user
// approves an inline prompt.
//
// An audited event can fire on a thread the UI thread may itself be blocked waiting on: the
// SlicingPipeline hook runs on the slicing worker thread (see PluginHooks.cpp), and
// BackgroundSlicingProcess::stop()/stop_internal() park the UI thread until that worker stops.
// Blocking the worker on a marshaled modal -- which is safe for the plugin-load worker that
// request_filesystem_read_permissions runs on -- would therefore deadlock the application (the
// invariant PluginHostUi.cpp documents for slicing-hook UI calls). Off the main thread the prompt
// is therefore posted asynchronously and the current event denied (fail closed, like an unanswered
// prompt); the grant is persisted once the user accepts, so a later attempt succeeds without
// re-prompting.
bool request_permission(AuditEventCategory category, const std::string& plugin_key, const std::string& plugin_name,
const std::string& event_name, const std::vector<std::string>& unresolved)
{
if (wxTheApp == nullptr || GUI::wxGetApp().is_closing())
return false;
if (wxIsMainThread())
return prompt_for_targets(category, plugin_name, event_name, unresolved);
GUI::wxGetApp().CallAfter([category, plugin_key, plugin_name, event_name, unresolved]() {
if (wxTheApp == nullptr || GUI::wxGetApp().is_closing())
return;
if (prompt_for_targets(category, plugin_name, event_name, unresolved))
persist_grant(plugin_key, category, unresolved);
});
return false;
}
int decide_audited_event(PluginAuditManager& mgr,
PluginInstallState& state,
const std::string& plugin_key,
@@ -864,15 +936,7 @@ int decide_audited_event(PluginAuditManager& mgr,
return 0;
}
wxString target_list;
for (const auto& target : unresolved)
target_list += wxString::FromUTF8(target.c_str()) + "\n";
wxMessageDialog dialog(nullptr,
audit_message(category, wxString::FromUTF8(plugin_name.c_str()),
wxString::FromUTF8(event_name.c_str()), target_list),
_L("Plugin permission request"), wxYES_NO | wxICON_WARNING);
if (dialog.ShowModal() != wxID_YES)
if (!request_permission(category, plugin_key, plugin_name, event_name, unresolved))
return report_denied(mgr, event_name, {false, "audit permission required"});
if (permission_list)
+2
View File
@@ -807,6 +807,7 @@ bool read_install_state(const boost::filesystem::path& plugin_dir, PluginInstall
read_string_list("network_http", parsed.permissions.network_http);
read_string_list("network_socket", parsed.permissions.network_socket);
read_string_list("process", parsed.permissions.process);
read_string_list("threading", parsed.permissions.threading);
}
if (state.contains("enabled") && state["enabled"].is_boolean())
@@ -850,6 +851,7 @@ bool write_install_state(const boost::filesystem::path& plugin_dir, const Plugin
{"network_http", state.permissions.network_http},
{"network_socket", state.permissions.network_socket},
{"process", state.permissions.process},
{"threading", state.permissions.threading},
};
nlohmann::json capabilities = nlohmann::json::array();
+1
View File
@@ -92,6 +92,7 @@ struct PluginPermissions
std::vector<std::string> network_http;
std::vector<std::string> network_socket;
std::vector<std::string> process;
std::vector<std::string> threading;
};
struct PluginInstallState {