19#include <boost/range/adaptor/reversed.hpp>
20#include <glib/gstdio.h>
21#include <glibmm/convert.h>
22#include <glibmm/fileutils.h>
23#include <glibmm/main.h>
24#include <glibmm/miscutils.h>
25#include <gtkmm/enums.h>
26#include <gtkmm/messagedialog.h>
27#include <gtkmm/scrolledwindow.h>
28#include <gtkmm/textview.h>
29#include <gtkmm/window.h>
71#define KILL_PROCESS(pid) TerminateProcess(pid, 0)
73#define KILL_PROCESS(pid) kill(pid, SIGTERM)
84 auto main_context = Glib::MainContext::get_default();
85 while (main_context->iteration(
false)) {
99 {
"perl", {
"perl-interpreter", {
"wperl" }}},
100 {
"python", {
"python-interpreter", {
"pythonw" }}},
101#elif defined __APPLE__
102 {
"perl", {
"perl-interpreter", {
"perl" }}},
103 {
"python", {
"python-interpreter", {
"python3" }}},
105 {
"perl", {
"perl-interpreter", {
"perl" }}},
106 {
"python", {
"python-interpreter", {
"python3",
"python" }}},
108 {
"python2", {
"python2-interpreter", {
"python2",
"python" }}},
109 {
"ruby", {
"ruby-interpreter", {
"ruby" }}},
110 {
"shell", {
"shell-interpreter", {
"sh" }}},
126 g_critical(
"Script::resolveInterpreterExecutable(): unknown script interpreter '%s'", interpNameArg.c_str());
130 std::list<Glib::ustring> searchList;
131 std::copy(interp->second.defaultvals.begin(), interp->second.defaultvals.end(), std::back_inserter(searchList));
135 auto prefInterp = prefs->getString(
"/extensions/" + Glib::ustring(interp->second.prefstring));
137 if (!prefInterp.empty()) {
138 searchList.push_front(prefInterp);
142 for (
const auto& binname : searchList) {
143 auto interpreter_path = Glib::filename_from_utf8(binname);
145 if (!Glib::path_is_absolute(interpreter_path)) {
146 auto found_path = Glib::find_program_in_path(interpreter_path);
147 if (!found_path.empty()) {
151 return interpreter_path;
156 g_critical(
"Script::resolveInterpreterExecutable(): failed to locate script interpreter '%s'", interpNameArg.c_str());
171 , parent_window(nullptr)
210 while (child_repr !=
nullptr) {
211 if (!strcmp(child_repr->
name(), INKSCAPE_EXTENSION_NS
"script")) {
212 for (child_repr = child_repr->
firstChild(); child_repr !=
nullptr; child_repr = child_repr->
next()) {
213 if (!strcmp(child_repr->
name(), INKSCAPE_EXTENSION_NS
"command")) {
214 const gchar *interpretstr = child_repr->
attribute(
"interpreter");
215 if (interpretstr !=
nullptr) {
217 if (interpString.empty()) {
220 command.push_back(interpString);
225 std::string script_location =
module->get_dependency_location(script_name);
226 command.push_back(std::move(script_location));
227 }
else if (!strcmp(child_repr->
name(), INKSCAPE_EXTENSION_NS
"helper_extension")) {
234 child_repr = child_repr->
next();
238 g_return_val_if_fail(
command.size() > 0,
false);
269 int script_count = 0;
271 while (child_repr !=
nullptr) {
272 if (!strcmp(child_repr->
name(), INKSCAPE_EXTENSION_NS
"script")) {
277 while (child_repr !=
nullptr) {
278 if (!strcmp(child_repr->
name(), INKSCAPE_EXTENSION_NS
"helper_extension")) {
285 child_repr = child_repr->
next();
290 child_repr = child_repr->
next();
293 if (script_count == 0) {
305 std::list<std::string> params;
306 module->paramListString(params);
307 module->set_environment();
312 auto svg = fileout.
string();
328 std::list<std::string> params;
330 std::string param =
"--page=";
332 param +=
page->getId();
337 params.push_back(param);
365 std::list<std::string> params;
366 module->paramListString(params);
367 module->set_environment();
369 std::string tempfilename_out;
372 tempfd_out = Glib::file_open_tmp(tempfilename_out,
"ink_ext_XXXXXX.svg");
378 std::string lfilename = Glib::filename_from_utf8(filenameArg);
382 fileout.
toFile(tempfilename_out);
384 std::unique_ptr<SPDocument> mydoc;
385 if (data_read > 10) {
389 tempfilename_out.c_str());
393 tempfilename_out.c_str());
398 mydoc->setDocumentBase(
nullptr);
399 mydoc->changeFilenameAndHrefs(filenameArg);
405 unlink(tempfilename_out.c_str());
439 const gchar *filenameArg)
441 std::list<std::string> params;
442 module->paramListString(params);
443 module->set_environment(doc);
445 std::string tempfilename_in;
448 tempfd_in = Glib::file_open_tmp(tempfilename_in,
"ink_ext_XXXXXX.svg");
457 doc, tempfilename_in.c_str(),
false,
false,
462 doc, tempfilename_in.c_str(),
false,
false,
468 int data_read =
execute(
command, params, tempfilename_in, fileout);
470 bool success =
false;
473 std::string lfilename = Glib::filename_from_utf8(filenameArg);
474 success = fileout.
toFile(lfilename);
480 unlink(tempfilename_in.c_str());
482 if (success ==
false) {
490 const std::string &png_file,
491 const gchar *filenameArg)
494 g_error(
"Can not export raster to non-raster extension.");
498 std::list<std::string> params;
499 module->paramListString(params);
500 module->set_environment(doc);
505 bool success =
false;
507 std::string lfilename = Glib::filename_from_utf8(filenameArg);
508 success = fileout.
toFile(lfilename);
510 if (success ==
false) {
550 g_warning(
"Script::effect: Desktop not defined");
560 std::list<std::string> params;
561 module->paramListString(params);
562 module->set_environment(desktop->getDocument());
571 if (!g_strcmp0(module->
get_id(),
"org.inkscape.extension.manager")) {
572 Inkscape::Extension::refresh_user_extensions();
579 std::list<std::string> params;
583 auto state = selection->getState();
586 for (
auto const &
id : state.selected_ids) {
587 std::string selected_id =
"--id=";
589 params.push_back(std::move(selected_id));
593 for (
auto const &
node : state.selected_nodes) {
594 params.push_back(Glib::ustring::compose(
"--selected-nodes=%1:%2:%3",
node.path_id,
node.subpath_index,
607 std::list<std::string> params;
615 std::list<std::string> ¶ms,
bool ignore_stderr,
bool pipe_diffs)
617 module->paramListString(params);
618 module->set_environment(doc);
629 prefs->
setBool(
"/options/svgoutput/disable_optimizations",
true);
632 doc, tempfile_in.get_filename().c_str(),
false,
false,
634 prefs->
setBool(
"/options/svgoutput/disable_optimizations",
false);
637 int data_read =
execute(
command, params, tempfile_in.get_filename(), fileout, ignore_stderr, pipe_diffs);
638 if (data_read == 0) {
641 fileout.
toFile(tempfile_out.get_filename());
645 if (data_read > 10) {
646 new_xmldoc =
sp_repr_read_file(tempfile_out.get_filename().c_str(), SP_SVG_NS_URI);
665 Gtk::MessageType type,
666 const Glib::ustring &message)
668 Gtk::MessageDialog warning(message,
false, type, Gtk::ButtonsType::OK,
true);
669 warning.set_resizable(
true);
676 auto const textview = Gtk::make_managed<Gtk::TextView>();
677 textview->set_editable(
false);
678 textview->set_wrap_mode(Gtk::WrapMode::WORD);
679 textview->get_buffer()->set_text(
data);
681 auto const scrollwindow = Gtk::make_managed<Gtk::ScrolledWindow>();
682 scrollwindow->set_child(*textview);
684 scrollwindow->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
685 scrollwindow->set_has_frame(
true);
686 scrollwindow->set_size_request(0, 60);
688 auto const vbox = warning.get_content_area();
699 Glib::spawn_close_pid(
_pid);
731int Script::execute(std::list<std::string>
const &in_command, std::list<std::string>
const &in_params,
732 Glib::ustring
const &filein,
file_listener &fileout,
bool ignore_stderr,
bool pipe_diffs)
734 g_return_val_if_fail(!in_command.empty(), 0);
736 std::vector<std::string> argv;
738 bool interpreted = (in_command.size() == 2);
739 std::string program = in_command.front();
740 std::string script = interpreted ? in_command.back() :
"";
741 std::string working_directory =
"";
743 auto const desktop = SP_ACTIVE_DESKTOP;
752 if (!Glib::path_is_absolute(program)) {
753 g_critical(
"Script::execute(): Got unexpected relative path '%s'. Please report a bug.", program.c_str());
756 argv.push_back(program);
764 working_directory = Glib::path_get_dirname(script);
765 script = Glib::path_get_basename(script);
766 argv.push_back(script);
770 std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
771 if (!filein.empty()) {
772 auto filein_native = Glib::filename_from_utf8(filein);
773 if (!Glib::path_is_absolute(filein_native))
774 filein_native = Glib::build_filename(Glib::get_current_dir(), filein_native);
775 argv.push_back(filein_native);
780 int stdout_pipe, stderr_pipe, stdin_pipe;
783 auto spawn_flags = Glib::SpawnFlags::DEFAULT;
784 if (Glib::getenv(
"SNAP") !=
"") {
788 spawn_flags = Glib::SpawnFlags::LEAVE_DESCRIPTORS_OPEN;
790 Glib::spawn_async_with_pipes(working_directory,
793 sigc::slot<
void()>(),
798 }
catch (Glib::Error
const &e) {
799 g_critical(
"Script::execute(): failed to execute program '%s'.\n\tReason: %s", program.c_str(), e.what());
804 auto const local_pid =
_pid;
809 auto const main_context = !pipe_diffs
810 ? Glib::MainContext::create()
811 : Glib::MainContext::get_default();
813 _main_loop = Glib::MainLoop::create(main_context,
false);
819 std::optional<PreviewObserver> watch;
820 bool lost_document =
false;
821 std::vector<sigc::scoped_connection> conns;
824 auto stdin_channel = Glib::IOChannel::create_from_fd(stdin_pipe);
825 stdin_channel->set_close_on_unref(
false);
826 stdin_channel->set_encoding();
829 stdin_channel->set_flags(
static_cast<Glib::IOFlags
>(G_IO_FLAG_NONBLOCK));
831 stdin_channel->set_buffered(
false);
833 watch.emplace(std::move(stdin_channel));
834 (*watch).connect(
desktop, document);
835 auto on_lose_document = [&] {
836 KILL_PROCESS(local_pid);
837 (*watch).disconnect(document);
838 lost_document =
true;
843 conns.emplace_back(document->connectDestroy(on_lose_document));
849 if (pipe_diffs && !lost_document) {
850 (*watch).disconnect(document);
854 while (!fileout.
isDead()) {
855 fileout.
read(Glib::IOCondition::IO_IN);
857 while (!fileerr.
isDead()) {
858 fileerr.
read(Glib::IOCondition::IO_IN);
863 if (pipe_diffs && lost_document) {
872 Glib::ustring stderr_data = fileerr.
string();
873 if (!stderr_data.empty() && !ignore_stderr) {
874 if (INKSCAPE.use_gui()) {
876 _(
"Inkscape has received additional data from the script executed. "
877 "The script did not return an error, but this may indicate the results will not be as expected."));
879 std::cerr <<
"Script Error\n----\n" << stderr_data.c_str() <<
"\n----\n";
883 Glib::ustring stdout_data = fileout.
string();
884 return stdout_data.length();
890 _channel = Glib::IOChannel::create_from_fd(fd);
893 _conn =
main->get_context()->signal_io().connect(sigc::mem_fun(*
this, &
file_listener::read),
_channel, Glib::IOCondition::IO_IN | Glib::IOCondition::IO_HUP | Glib::IOCondition::IO_ERR);
898 if (condition != Glib::IOCondition::IO_IN) {
903 Glib::IOStatus status;
905 status = _channel->read_line(out);
908 if (status != Glib::IOStatus::NORMAL) {
922 return toFile(Glib::filename_from_utf8(
name));
931 Glib::RefPtr<Glib::IOChannel> stdout_file = Glib::IOChannel::create_from_file(
name,
"w");
932 stdout_file->set_encoding();
933 stdout_file->write(_string);
934 }
catch (Glib::FileError &e) {
941 : _channel{
std::move(channel)}
954 _reconstruction_start_connection =
956 _reconstruction_finish_connection =
963 _select_changed.disconnect();
964 _reconstruction_start_connection.disconnect();
965 _reconstruction_finish_connection.disconnect();
976 eventPopulator(doc, event_node);
979 _channel->write(xml_output +
"\n");
987 if (_pause_select_events) {
992 for (
auto objsel : selection->
objects()) {
1003 std::vector<XML::Event *> events;
1006 for (
auto e = ee->
event; e; e = e->next) {
1007 events.push_back(e);
1011 for (
auto e : events | boost::adaptors::reversed) {
1016 event_node->setAttribute(
"after", eadd->ref->attribute(
"id"));
1019 Inkscape::XML::Node *new_child = eadd->child->duplicate(doc);
1021 event_node->appendChild(new_child);
1022 Inkscape::GC::release(new_child);
1025 event_node->setAttribute(
"parent", eadd->repr->attribute(
"id"));
1029 if (edel->repr && edel->repr->attribute(
"id")) {
1030 event_node->setAttribute(
"parent", edel->repr->attribute(
"id"));
1033 event_node->setAttribute(
"after", edel->ref->attribute(
"id"));
1036 event_node->setAttribute(
"child", edel->child->attribute(
"id"));
1040 if (echga->repr && e->repr->attribute(
"id")) {
1041 event_node->setAttribute(
"element-id", echga->repr->attribute(
"id"));
1043 event_node->
setAttribute(
"attribute-name", g_quark_to_string(echga->key));
1044 event_node->
setAttribute(
"old-value", &*(echga->oldval));
1045 event_node->
setAttribute(
"new-value", &*(echga->newval));
1048 if (e->repr && e->repr->attribute(
"id")) {
1049 event_node->
setAttribute(
"element-id", e->repr->attribute(
"id"));
1051 event_node->
setAttribute(
"old-content", &*(echgc->oldval));
1052 event_node->
setAttribute(
"new-content", &*(echgc->newval));
1055 if (echgo->repr && echgo->repr->attribute(
"id")) {
1056 event_node->
setAttribute(
"element-id", e->repr->attribute(
"id"));
1058 event_node->
setAttribute(
"child", echgo->child->attribute(
"id"));
1059 if (echgo->oldref) {
1060 event_node->
setAttribute(
"old-ref", echgo->oldref->attribute(
"id"));
1062 if (echgo->newref) {
1063 event_node->
setAttribute(
"new-ref", echgo->newref->attribute(
"id"));
1066 event_node->
setAttribute(
"type",
"element_name_change");
1067 if (echgn->repr && echgn->repr->attribute(
"id")) {
1068 event_node->
setAttribute(
"element-id", e->repr->attribute(
"id"));
1070 event_node->
setAttribute(
"old-name", g_quark_to_string(echgn->old_name));
1071 event_node->
setAttribute(
"new-name", g_quark_to_string(echgn->new_name));
1081 notifyUndoCommitEvent(e);
1086 notifyUndoCommitEvent(e);
TODO: insert short description here.
Effects are extensions that take a document and do something to it in place.
bool ignore_stderr
If stderr log should be shown, when process return code is 0.
bool pipe_diffs
If changesets should be piped in via stdin.
Gtk::Dialog * get_working_dialog()
Return reference to working dialog (if any)
The object that is the basis for the Extension system.
char const * get_id() const
Get the ID of this extension - not a copy don't delete!
bool loaded()
A quick function to test the state of the extension.
A cache for the document and this implementation.
Base class for all implementations of modules.
void disconnect(SPDocument *document)
void notifyUndoCommitEvent(Event *log) override
Triggered when a set of transactions is committed to the undo log.
void selectionChanged(Inkscape::Selection *selection)
void createAndSendEvent(std::function< void(Inkscape::XML::Document *doc, Inkscape::XML::Node *)> const &eventPopulator)
void notifyClearRedoEvent() override
Triggered when the redo log is cleared.
void notifyUndoExpired(Event *log) override
Triggered when undo items are removed from the back of the log (expired)
void notifyRedoEvent(Event *log) override
Triggered when the user issues a redo command.
void connect(SPDesktop const *desktop, SPDocument *document)
PreviewObserver(Glib::RefPtr< Glib::IOChannel > channel)
void notifyClearUndoEvent() override
Triggered when the undo log is cleared.
void notifyUndoEvent(Event *log) override
Triggered when the user issues an undo command.
Glib::RefPtr< Glib::MainLoop > _main_loop
Glib::RefPtr< Glib::IOChannel > _channel
bool toFile(const Glib::ustring &name)
sigc::scoped_connection _conn
bool read(Glib::IOCondition condition)
void init(int fd, Glib::RefPtr< Glib::MainLoop > main)
std::unique_ptr< SPDocument > new_from_template(Inkscape::Extension::Template *module) override
Create a new document based on the given template.
Script()
This function creates a script object and sets up the variables.
bool check(Inkscape::Extension::Extension *module) override
Check every dependency that was given to make sure we should keep this extension.
void resize_to_template(Inkscape::Extension::Template *tmod, SPDocument *doc, SPPage *page) override
Take an existing document and selected page and resize or add items as needed.
void pump_events()
Make GTK+ events continue to come through a little bit.
Glib::ustring helper_extension
This is the extension that will be used as the helper to read in or write out the data.
void save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) override
This function uses an extension to save a document. It first creates an SVG file of the document,...
std::string resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
Look up an interpreter name, and translate to something that is executable.
void export_raster(Inkscape::Extension::Output *module, const SPDocument *doc, std::string const &png_file, gchar const *filename) override
Convert from PNG to raster format.
Glib::RefPtr< Glib::MainLoop > _main_loop
void unload(Inkscape::Extension::Extension *module) override
Unload this puppy!
int execute(std::list< std::string > const &in_command, std::list< std::string > const &in_params, Glib::ustring const &filein, file_listener &fileout, bool ignore_stderr=false, bool pipe_diffs=false)
This is the core of the extension file as it actually does the execution of the extension.
std::list< std::string > command
The command that has been derived from the configuration file with appropriate directories.
bool cancelProcessing() override
std::unique_ptr< SPDocument > open(Inkscape::Extension::Input *module, char const *filename, bool is_importing) override
This function uses a filename that is put in, and calls the extension's command to create an SVG file...
Gtk::Window * parent_window
The window which should be considered as "parent window" of the script execution, e....
void _change_extension(Inkscape::Extension::Extension *mod, ExecutionEnv *executionEnv, SPDocument *doc, std::list< std::string > ¶ms, bool ignore_stderr, bool pipe_diffs=false)
Internally, any modification of an existing document, used by effect and resize_page extensions.
void effect(Inkscape::Extension::Effect *module, ExecutionEnv *executionEnv, SPDesktop *desktop, ImplementationDocumentCache *docCache) override
This function uses an extension as an effect on a document.
void showPopupError(Glib::ustring const &filename, Gtk::MessageType type, Glib::ustring const &message)
This function checks the stderr file, and if it has data, shows it in a warning dialog to the user.
~Script() override
Destructor.
static const std::map< std::string, interpreter_t > interpreterTab
A table of what interpreters to call for a given language.
bool load(Inkscape::Extension::Extension *module) override
This function 'loads' an extension, basically it determines the full command for the extension and st...
Generic failure for an undescribed reason.
Glib::RefPtr< Gio::File > get_template_filename() const
Get the template filename, or return the default template.
SPObjectRange objects()
Returns the list of selected objects.
Preference storage class.
static Preferences * get()
Access the singleton Preferences object.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
The set of selected SPObjects for a given document and layer model.
sigc::connection connectChanged(sigc::slot< void(Selection *)> slot)
Connects a slot to be notified of selection changes.
Object representing child addition.
Object representing attribute change.
Object representing content change.
Object representing element name change.
Object representing child order change.
Object representing child removal.
Interface for refcounted XML nodes.
virtual Node * next()=0
Get the next sibling of this node.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
virtual char const * name() const =0
Get the name of the element node.
void addChildAtPos(Node *child, unsigned pos)
Insert another node as a child of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
virtual Node * firstChild()=0
Get the first child of this node.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
virtual char const * content() const =0
Get the content of a text or comment node.
virtual Document * document()=0
Get the node's associated document.
To do: update description of desktop.
sigc::connection connectDocumentReplaced(F &&slot)
SPDocument * getDocument() const
Inkscape::Selection * getSelection() const
sigc::connection connectDestroy(F &&slot)
Typed SVG document implementation.
static std::unique_ptr< SPDocument > createDoc(Inkscape::XML::Document *rdoc, char const *filename, char const *base, char const *name, bool keepalive, SPDocument *parent=nullptr)
static int get_new_doc_number()
SPRoot * getRoot()
Returns our SPRoot.
void addUndoObserver(Inkscape::UndoStackObserver &observer)
Add the observer to the document's undo listener The caller is in charge of freeing any memory alloca...
void removeUndoObserver(Inkscape::UndoStackObserver &observer)
sigc::connection connectReconstructionFinish(ReconstructionFinish::slot_type slot)
void rebase(Inkscape::XML::Document *new_xmldoc, bool keep_namedview=true)
sigc::connection connectReconstructionStart(ReconstructionStart::slot_type slot, bool first=false)
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
char const * getId() const
Returns the objects current ID string.
Control point selection - stores a set of control points and applies transformations to them.
Editable view implementation.
void sp_transientize(Gtk::Window &window)
Make the argument dialog transient to the currently active document window.
Event handler for dialog windows.
TODO: insert short description here.
Inkscape::XML::Node * node
Inkscape - An SVG editor.
Multi path manipulator - a tool component that edits multiple paths at once.
DB db
This is the actual database object.
std::unique_ptr< SPDocument > open(Extension *key, char const *filename, bool is_importing)
This is a generic function to use the open function of a module (including Autodetect)
void save(Extension *key, SPDocument *doc, gchar const *filename, bool check_overwrite, bool official, Inkscape::Extension::FileSaveMethod save_method)
This is a generic function to use the save function of a module (including Autodetect)
@ FILE_SAVE_METHOD_TEMPORARY
static R & release(R &r)
Decrements the reference count of a anchored object.
void gui_warning(const std::string &msg, Gtk::Window *parent_window)
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
int dialog_run(Gtk::Dialog &dialog)
This is a GTK4 porting aid meant to replace the removal of the Gtk::Dialog synchronous API.
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
Path manipulator - a component that edits a single path on-canvas.
TODO: insert short description here.
Singleton class to access the preferences file in a convenient way.
TODO: insert short description here.
Document * sp_repr_read_file(const gchar *filename, const gchar *default_ns, bool xinclude)
Reads XML from a file, and returns the Document.
Document * sp_repr_read_mem(const gchar *buffer, gint length, const gchar *default_ns)
Reads and parses XML from a buffer, returning it as an Document.
Glib::ustring sp_repr_write_buf(Node *repr, int indent_level, bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent, char const *old_href_base, char const *new_href_base)
C facade to Inkscape::XML::Node.
Inkscape::IO::Resource - simple resource API.
Inkscape::XML::SimpleDocument - generic XML document implementation.
void sp_namedview_document_from_window(SPDesktop *desktop)
SPRoot: SVG <svg> implementation.
Document was closed during execution of async extension.
Interface for XML documents.
virtual Node * createElement(char const *name)=0
Event object representing a change of the XML document.