summaryrefslogtreecommitdiffstats
path: root/src/extension/implementation/script.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/extension/implementation/script.cpp')
-rw-r--r--src/extension/implementation/script.cpp911
1 files changed, 911 insertions, 0 deletions
diff --git a/src/extension/implementation/script.cpp b/src/extension/implementation/script.cpp
new file mode 100644
index 0000000..40357f7
--- /dev/null
+++ b/src/extension/implementation/script.cpp
@@ -0,0 +1,911 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * Code for handling extensions (i.e. scripts).
+ *
+ * Authors:
+ * Bryce Harrington <bryce@osdl.org>
+ * Ted Gould <ted@gould.cx>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2002-2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "script.h"
+
+#include <glib/gstdio.h>
+#include <glibmm.h>
+#include <glibmm/convert.h>
+#include <glibmm/miscutils.h>
+#include <gtkmm/main.h>
+#include <gtkmm/messagedialog.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/textview.h>
+
+#include "desktop.h"
+#include "extension/db.h"
+#include "extension/effect.h"
+#include "extension/execution-env.h"
+#include "extension/init.h"
+#include "extension/input.h"
+#include "extension/output.h"
+#include "extension/system.h"
+#include "extension/template.h"
+#include "inkscape-window.h"
+#include "inkscape.h"
+#include "io/resource.h"
+#include "io/file.h"
+#include "layer-manager.h"
+#include "object/sp-namedview.h"
+#include "object/sp-page.h"
+#include "object/sp-path.h"
+#include "object/sp-root.h"
+#include "path-prefix.h"
+#include "preferences.h"
+#include "selection.h"
+#include "io/dir-util.h"
+#include "ui/desktop/menubar.h"
+#include "ui/dialog-events.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/path-manipulator.h"
+#include "ui/tools/node-tool.h"
+#include "ui/util.h"
+#include "ui/view/view.h"
+#include "widgets/desktop-widget.h"
+#include "xml/attribute-record.h"
+#include "xml/rebase-hrefs.h"
+
+/* Namespaces */
+namespace Inkscape {
+namespace Extension {
+namespace Implementation {
+
+/** \brief Make GTK+ events continue to come through a little bit
+
+ This just keeps coming the events through so that we'll make the GUI
+ update and look pretty.
+*/
+void Script::pump_events () {
+ while ( Gtk::Main::events_pending() ) {
+ Gtk::Main::iteration();
+ }
+ return;
+}
+
+
+/** \brief A table of what interpreters to call for a given language
+
+ This table is used to keep track of all the programs to execute a
+ given script. It also tracks the preference to use to overwrite
+ the given interpreter to a custom one per user.
+*/
+const std::map<std::string, Script::interpreter_t> Script::interpreterTab = {
+ // clang-format off
+#ifdef _WIN32
+ { "perl", {"perl-interpreter", {"wperl" }}},
+ { "python", {"python-interpreter", {"pythonw" }}},
+#elif defined __APPLE__
+ { "perl", {"perl-interpreter", {"perl" }}},
+ { "python", {"python-interpreter", {"python3" }}},
+#else
+ { "perl", {"perl-interpreter", {"perl" }}},
+ { "python", {"python-interpreter", {"python3", "python" }}},
+#endif
+ { "python2", {"python2-interpreter", {"python2", "python" }}},
+ { "ruby", {"ruby-interpreter", {"ruby" }}},
+ { "shell", {"shell-interpreter", {"sh" }}},
+ // clang-format on
+};
+
+
+
+/** \brief Look up an interpreter name, and translate to something that
+ is executable
+ \param interpNameArg The name of the interpreter that we're looking
+ for, should be an entry in interpreterTab
+*/
+std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
+{
+ // 0. Do we have a supported interpreter type?
+ auto interp = interpreterTab.find(interpNameArg);
+ if (interp == interpreterTab.end()) {
+ g_critical("Script::resolveInterpreterExecutable(): unknown script interpreter '%s'", interpNameArg.c_str());
+ return "";
+ }
+
+ std::list<Glib::ustring> searchList;
+ std::copy(interp->second.defaultvals.begin(), interp->second.defaultvals.end(), std::back_inserter(searchList));
+
+ // 1. Check preferences for an override.
+ auto prefs = Inkscape::Preferences::get();
+ auto prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->second.prefstring));
+
+ if (!prefInterp.empty()) {
+ searchList.push_front(prefInterp);
+ }
+
+ // 2. Search for things in the path if they're there or an absolute
+ for (const auto& binname : searchList) {
+ auto interpreter_path = Glib::filename_from_utf8(binname);
+
+ if (!Glib::path_is_absolute(interpreter_path)) {
+ auto found_path = Glib::find_program_in_path(interpreter_path);
+ if (!found_path.empty()) {
+ return found_path;
+ }
+ } else {
+ return interpreter_path;
+ }
+ }
+
+ // 3. Error
+ g_critical("Script::resolveInterpreterExecutable(): failed to locate script interpreter '%s'", interpNameArg.c_str());
+ return "";
+}
+
+/** \brief This function creates a script object and sets up the
+ variables.
+ \return A script object
+
+ This function just sets the command to NULL. It should get built
+ officially in the load function. This allows for less allocation
+ of memory in the unloaded state.
+*/
+Script::Script()
+ : Implementation()
+ , _canceled(false)
+ , parent_window(nullptr)
+{
+}
+
+/**
+ * \brief Destructor
+ */
+Script::~Script()
+= default;
+
+
+/**
+ \return none
+ \brief This function 'loads' an extension, basically it determines
+ the full command for the extension and stores that.
+ \param module The extension to be loaded.
+
+ The most difficult part about this function is finding the actual
+ command through all of the Reprs. Basically it is hidden down a
+ couple of layers, and so the code has to move down too. When
+ the command is actually found, it has its relative directory
+ solved.
+
+ At that point all of the loops are exited, and there is an
+ if statement to make sure they didn't exit because of not finding
+ the command. If that's the case, the extension doesn't get loaded
+ and should error out at a higher level.
+*/
+
+bool Script::load(Inkscape::Extension::Extension *module)
+{
+ if (module->loaded()) {
+ return true;
+ }
+
+ helper_extension = "";
+
+ /* This should probably check to find the executable... */
+ Inkscape::XML::Node *child_repr = module->get_repr()->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
+ for (child_repr = child_repr->firstChild(); child_repr != nullptr; child_repr = child_repr->next()) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
+ const gchar *interpretstr = child_repr->attribute("interpreter");
+ if (interpretstr != nullptr) {
+ std::string interpString = resolveInterpreterExecutable(interpretstr);
+ if (interpString.empty()) {
+ continue; // can't have a script extension with empty interpreter
+ }
+ command.push_back(interpString);
+ }
+ // TODO: we already parse commands as dependencies in extension.cpp
+ // can can we optimize this to be less indirect?
+ const char *script_name = child_repr->firstChild()->content();
+ std::string script_location = module->get_dependency_location(script_name);
+ command.push_back(std::move(script_location));
+ } else if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
+ helper_extension = child_repr->firstChild()->content();
+ }
+ }
+
+ break;
+ }
+ child_repr = child_repr->next();
+ }
+
+ // TODO: Currently this causes extensions to fail silently, see comment in Extension::set_state()
+ g_return_val_if_fail(command.size() > 0, false);
+
+ return true;
+}
+
+
+/**
+ \return None.
+ \brief Unload this puppy!
+ \param module Extension to be unloaded.
+
+ This function just sets the module to unloaded. It free's the
+ command if it has been allocated.
+*/
+void Script::unload(Inkscape::Extension::Extension */*module*/)
+{
+ command.clear();
+ helper_extension = "";
+}
+
+
+
+
+/**
+ \return Whether the check passed or not
+ \brief Check every dependency that was given to make sure we should keep this extension
+ \param module The Extension in question
+
+*/
+bool Script::check(Inkscape::Extension::Extension *module)
+{
+ int script_count = 0;
+ Inkscape::XML::Node *child_repr = module->get_repr()->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
+ script_count++;
+
+ // check if all helper_extensions attached to this script were registered
+ child_repr = child_repr->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
+ gchar const *helper = child_repr->firstChild()->content();
+ if (Inkscape::Extension::db.get(helper) == nullptr) {
+ return false;
+ }
+ }
+
+ child_repr = child_repr->next();
+ }
+
+ break;
+ }
+ child_repr = child_repr->next();
+ }
+
+ if (script_count == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Create a new document based on the given template.
+ */
+SPDocument *Script::new_from_template(Inkscape::Extension::Template *module)
+{
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment();
+
+ if (auto in_file = module->get_template_filename()) {
+ file_listener fileout;
+ execute(command, params, in_file->get_path(), fileout);
+ auto svg = fileout.string();
+ auto rdoc = sp_repr_read_mem(svg.c_str(), svg.length(), SP_SVG_NS_URI);
+ if (rdoc) {
+ auto name = g_strdup_printf(_("New document %d"), SPDocument::get_new_doc_number());
+ return SPDocument::createDoc(rdoc, nullptr, nullptr, name, false, nullptr);
+ }
+ }
+
+ return nullptr;
+}
+
+/**
+ * Take an existing document and selected page and resize or add items as needed.
+ */
+void Script::resize_to_template(Inkscape::Extension::Template *tmod, SPDocument *doc, SPPage *page)
+{
+ std::list<std::string> params;
+ {
+ std::string param = "--page=";
+ if (page) {
+ param += page->getId();
+ } else {
+ // This means 'resize the svg document'
+ param += doc->getRoot()->getId();
+ }
+ params.push_back(param);
+ }
+ _change_extension(tmod, doc, params, true);
+}
+
+/**
+ \return A new document that has been opened
+ \brief This function uses a filename that is put in, and calls
+ the extension's command to create an SVG file which is
+ returned.
+ \param module Extension to use.
+ \param filename File to open.
+
+ First things first, this function needs a temporary file name. To
+ create one of those the function Glib::file_open_tmp is used with
+ the header of ink_ext_.
+
+ The extension is then executed using the 'execute' function
+ with the filename assigned and then the temporary filename.
+ After execution the SVG should be in the temporary file.
+
+ Finally, the temporary file is opened using the SVG input module and
+ a document is returned. That document has its filename set to
+ the incoming filename (so that it's not the temporary filename).
+ That document is then returned from this function.
+*/
+SPDocument *Script::open(Inkscape::Extension::Input *module,
+ const gchar *filenameArg)
+{
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment();
+
+ std::string tempfilename_out;
+ int tempfd_out = 0;
+ try {
+ tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
+ } catch (...) {
+ /// \todo Popup dialog here
+ return nullptr;
+ }
+
+ std::string lfilename = Glib::filename_from_utf8(filenameArg);
+
+ file_listener fileout;
+ int data_read = execute(command, params, lfilename, fileout);
+ fileout.toFile(tempfilename_out);
+
+ SPDocument * mydoc = nullptr;
+ if (data_read > 10) {
+ if (helper_extension.size()==0) {
+ mydoc = Inkscape::Extension::open(
+ Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
+ tempfilename_out.c_str());
+ } else {
+ mydoc = Inkscape::Extension::open(
+ Inkscape::Extension::db.get(helper_extension.c_str()),
+ tempfilename_out.c_str());
+ }
+ } // data_read
+
+ if (mydoc != nullptr) {
+ mydoc->setDocumentBase(nullptr);
+ mydoc->changeFilenameAndHrefs(filenameArg);
+ }
+
+ // make sure we don't leak file descriptors from Glib::file_open_tmp
+ close(tempfd_out);
+
+ unlink(tempfilename_out.c_str());
+
+ return mydoc;
+} // open
+
+
+
+/**
+ \return none
+ \brief This function uses an extension to save a document. It first
+ creates an SVG file of the document, and then runs it through
+ the script.
+ \param module Extension to be used
+ \param doc Document to be saved
+ \param filename The name to save the final file as
+ \return false in case of any failure writing the file, otherwise true
+
+ Well, at some point people need to save - it is really what makes
+ the entire application useful. And, it is possible that someone
+ would want to use an extension for this, so we need a function to
+ do that, eh?
+
+ First things first, the document is saved to a temporary file that
+ is an SVG file. To get the temporary filename Glib::file_open_tmp is used with
+ ink_ext_ as a prefix. Don't worry, this file gets deleted at the
+ end of the function.
+
+ After we have the SVG file, then Script::execute is called with
+ the temporary file name and the final output filename. This should
+ put the output of the script into the final output file. We then
+ delete the temporary file.
+*/
+void Script::save(Inkscape::Extension::Output *module,
+ SPDocument *doc,
+ const gchar *filenameArg)
+{
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment(doc);
+
+ std::string tempfilename_in;
+ int tempfd_in = 0;
+ try {
+ tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
+ } catch (...) {
+ /// \todo Popup dialog here
+ throw Inkscape::Extension::Output::save_failed();
+ }
+
+ if (helper_extension.size() == 0) {
+ Inkscape::Extension::save(
+ Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
+ doc, tempfilename_in.c_str(), false, false,
+ Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
+ } else {
+ Inkscape::Extension::save(
+ Inkscape::Extension::db.get(helper_extension.c_str()),
+ doc, tempfilename_in.c_str(), false, false,
+ Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
+ }
+
+
+ file_listener fileout;
+ int data_read = execute(command, params, tempfilename_in, fileout);
+
+ bool success = false;
+
+ if (data_read > 0) {
+ std::string lfilename = Glib::filename_from_utf8(filenameArg);
+ success = fileout.toFile(lfilename);
+ }
+
+ // make sure we don't leak file descriptors from Glib::file_open_tmp
+ close(tempfd_in);
+ // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
+ unlink(tempfilename_in.c_str());
+
+ if (success == false) {
+ throw Inkscape::Extension::Output::save_failed();
+ }
+
+ return;
+}
+
+
+void Script::export_raster(Inkscape::Extension::Output *module,
+ const SPDocument *doc,
+ const std::string &png_file,
+ const gchar *filenameArg)
+{
+ if(!module->is_raster()) {
+ g_error("Can not export raster to non-raster extension.");
+ return;
+ }
+
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment(doc);
+
+ file_listener fileout;
+ int data_read = execute(command, params, png_file, fileout);
+
+ bool success = false;
+ if (data_read > 0) {
+ std::string lfilename = Glib::filename_from_utf8(filenameArg);
+ success = fileout.toFile(lfilename);
+ }
+ if (success == false) {
+ throw Inkscape::Extension::Output::save_failed();
+ }
+ return;
+}
+
+/**
+ \return none
+ \brief This function uses an extension as an effect on a document.
+ \param module Extension to effect with.
+ \param doc Document to run through the effect.
+
+ This function is a little bit trickier than the previous two. It
+ needs two temporary files to get its work done. Both of these
+ files have random names created for them using the Glib::file_open_temp function
+ with the ink_ext_ prefix in the temporary directory. Like the other
+ functions, the temporary files are deleted at the end.
+
+ To save/load the two temporary documents (both are SVG) the internal
+ modules for SVG load and save are used. They are both used through
+ the module system function by passing their keys into the functions.
+
+ The command itself is built a little bit differently than in other
+ functions because the effect support selections. So on the command
+ line a list of all the ids that are selected is included. Currently,
+ this only works for a single selected object, but there will be more.
+ The command string is filled with the data, and then after the execution
+ it is freed.
+
+ The execute function is used at the core of this function
+ to execute the Script on the two SVG documents (actually only one
+ exists at the time, the other is created by that script). At that
+ point both should be full, and the second one is loaded.
+*/
+void Script::effect(Inkscape::Extension::Effect *module,
+ Inkscape::UI::View::View *doc,
+ ImplementationDocumentCache * docCache)
+{
+ if (doc == nullptr)
+ {
+ g_warning("Script::effect: View not defined");
+ return;
+ }
+
+ SPDesktop *desktop = reinterpret_cast<SPDesktop *>(doc);
+ sp_namedview_document_from_window(desktop);
+
+ if (module->no_doc) {
+ // this is a no-doc extension, e.g. a Help menu command;
+ // just run the command without any files, ignoring errors
+
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment(desktop->getDocument());
+
+ Glib::ustring empty;
+ file_listener outfile;
+ execute(command, {}, empty, outfile);
+
+ // Hack to allow for extension manager to reload extensions
+ // TODO: Find a better way to do this, e.g. implement an action and have extensions (or users)
+ // call that instead when there's a change that requires extensions to reload
+ if (!g_strcmp0(module->get_id(), "org.inkscape.extension.manager")) {
+ Inkscape::Extension::refresh_user_extensions();
+ build_menu(); // Rebuild main menubar.
+ }
+
+ return;
+ }
+
+ std::list<std::string> params;
+ if (desktop) {
+ Inkscape::Selection * selection = desktop->getSelection();
+ if (selection) {
+ params = selection->params;
+ selection->clear();
+ }
+ }
+ _change_extension(module, desktop->getDocument(), params, module->ignore_stderr);
+}
+
+//uncomment if issues on ref extensions links
+/* void sp_change_hrefs(Inkscape::XML::Node *repr, gchar const *const oldfilename, gchar const *const filename)
+{
+ gchar *new_document_base = nullptr;
+ gchar *new_document_filename = nullptr;
+ gchar *old_document_base = nullptr;
+ gchar *old_document_filename = nullptr;
+ if (filename) {
+
+#ifndef _WIN32
+ new_document_filename = prepend_current_dir_if_relative(filename);
+ old_document_filename = prepend_current_dir_if_relative(oldfilename);
+#else
+ // FIXME: it may be that prepend_current_dir_if_relative works OK on windows too, test!
+ new_document_filename = g_strdup(filename);
+ old_document_filename = g_strdup(oldfilename);
+#endif
+
+ new_document_base = g_path_get_dirname(new_document_filename);
+ old_document_base = g_path_get_dirname(old_document_filename);
+ } else {
+ new_document_base = nullptr;
+ old_document_base = nullptr;
+ }
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool use_sodipodi_absref = prefs->getBool("/options/svgoutput/usesodipodiabsref", false);
+ Inkscape::XML::rebase_hrefs(repr, old_document_base, new_document_base, use_sodipodi_absref);
+ g_free(new_document_base);
+ g_free(old_document_base);
+ g_free(new_document_filename);
+ g_free(old_document_filename);
+} */
+
+/**
+ * Internally, any modification of an existing document, used by effect and resize_page extensions.
+ */
+void Script::_change_extension(Inkscape::Extension::Extension *module, SPDocument *doc, std::list<std::string> &params, bool ignore_stderr)
+{
+ module->paramListString(params);
+ module->set_environment(doc);
+
+ if (auto env = module->get_execution_env()) {
+ parent_window = env->get_working_dialog();
+ }
+
+ auto tempfile_out = Inkscape::IO::TempFilename("ink_ext_XXXXXX.svg");
+ auto tempfile_in = Inkscape::IO::TempFilename("ink_ext_XXXXXX.svg");
+
+ // Save current document to a temporary file we can send to the extension
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/options/svgoutput/disable_optimizations", true);
+ Inkscape::Extension::save(
+ Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
+ doc, tempfile_in.get_filename().c_str(), false, false,
+ Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
+ prefs->setBool("/options/svgoutput/disable_optimizations", false);
+
+ file_listener fileout;
+ int data_read = execute(command, params, tempfile_in.get_filename(), fileout, ignore_stderr);
+ if (data_read == 0) {
+ return;
+ }
+ fileout.toFile(tempfile_out.get_filename());
+
+ pump_events();
+ Inkscape::XML::Document *new_xmldoc = nullptr;
+ if (data_read > 10) {
+ new_xmldoc = sp_repr_read_file(tempfile_out.get_filename().c_str(), SP_SVG_NS_URI);
+ } // data_read
+
+ pump_events();
+
+ if (new_xmldoc) {
+ //uncomment if issues on ref extensions links (with previous function)
+ //sp_change_hrefs(new_xmldoc, tempfile_out.get_filename().c_str(), doc->getDocumentFilename());
+ doc->rebase(new_xmldoc);
+ } else {
+ Inkscape::UI::gui_warning(_("The output from the extension could not be parsed."), parent_window);
+ }
+
+ return;
+}
+
+/** \brief This function checks the stderr file, and if it has data,
+ shows it in a warning dialog to the user
+ \param filename Filename of the stderr file
+*/
+void Script::showPopupError (const Glib::ustring &data,
+ Gtk::MessageType type,
+ const Glib::ustring &message)
+{
+ Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
+ warning.set_resizable(true);
+ GtkWidget *dlg = GTK_WIDGET(warning.gobj());
+ if (parent_window) {
+ warning.set_transient_for(*parent_window);
+ } else {
+ sp_transientize(dlg);
+ }
+
+ auto vbox = warning.get_content_area();
+
+ /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
+ Gtk::TextView * textview = new Gtk::TextView();
+ textview->set_editable(false);
+ textview->set_wrap_mode(Gtk::WRAP_WORD);
+ textview->show();
+
+ textview->get_buffer()->set_text(data.c_str());
+
+ Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
+ scrollwindow->add(*textview);
+ scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
+ scrollwindow->show();
+ scrollwindow->set_size_request(0, 60);
+
+ vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
+
+ warning.run();
+
+ delete textview;
+ delete scrollwindow;
+
+ return;
+}
+
+bool Script::cancelProcessing () {
+ _canceled = true;
+ if (_main_loop) {
+ _main_loop->quit();
+ }
+ Glib::spawn_close_pid(_pid);
+
+ return true;
+}
+
+
+/** \brief This is the core of the extension file as it actually does
+ the execution of the extension.
+ \param in_command The command to be executed
+ \param filein Filename coming in
+ \param fileout Filename of the out file
+ \return Number of bytes that were read into the output file.
+
+ The first thing that this function does is build the command to be
+ executed. This consists of the first string (in_command) and then
+ the filename for input (filein). This file is put on the command
+ line.
+
+ The next thing that this function does is open a pipe to the
+ command and get the file handle in the ppipe variable. It then
+ opens the output file with the output file handle. Both of these
+ operations are checked extensively for errors.
+
+ After both are opened, then the data is copied from the output
+ of the pipe into the file out using \a fread and \a fwrite. These two
+ functions are used because of their primitive nature - they make
+ no assumptions about the data. A buffer is used in the transfer,
+ but the output of \a fread is stored so the exact number of bytes
+ is handled gracefully.
+
+ At the very end (after the data has been copied) both of the files
+ are closed, and we return to what we were doing.
+*/
+int Script::execute (const std::list<std::string> &in_command,
+ const std::list<std::string> &in_params,
+ const Glib::ustring &filein,
+ file_listener &fileout,
+ bool ignore_stderr)
+{
+ g_return_val_if_fail(!in_command.empty(), 0);
+
+ std::vector<std::string> argv;
+
+ bool interpreted = (in_command.size() == 2);
+ std::string program = in_command.front();
+ std::string script = interpreted ? in_command.back() : "";
+ std::string working_directory = "";
+
+ // We should always have an absolute path here:
+ // - For interpreted scripts, see Script::resolveInterpreterExecutable()
+ // - For "normal" scripts this should be done as part of the dependency checking, see Dependency::check()
+ if (!Glib::path_is_absolute(program)) {
+ g_critical("Script::execute(): Got unexpected relative path '%s'. Please report a bug.", program.c_str());
+ return 0;
+ }
+ argv.push_back(program);
+
+ if (interpreted) {
+ // On Windows, Python garbles Unicode command line parameters
+ // in an useless way. This means extensions fail when Inkscape
+ // is run from an Unicode directory.
+ // As a workaround, we set the working directory to the one
+ // containing the script.
+ working_directory = Glib::path_get_dirname(script);
+ script = Glib::path_get_basename(script);
+ argv.push_back(script);
+ }
+
+ // assemble the rest of argv
+ std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
+ if (!filein.empty()) {
+ auto filein_native = Glib::filename_from_utf8(filein);
+ if (!Glib::path_is_absolute(filein_native))
+ filein_native = Glib::build_filename(Glib::get_current_dir(), filein_native);
+ argv.push_back(filein_native);
+ }
+
+ //for(int i=0;i<argv.size(); ++i){printf("%s ",argv[i].c_str());}printf("\n");
+
+ int stdout_pipe, stderr_pipe;
+
+ try {
+ Glib::spawn_async_with_pipes(working_directory, // working directory
+ argv, // arg v
+ static_cast<Glib::SpawnFlags>(0), // no flags
+ sigc::slot<void ()>(),
+ &_pid, // Pid
+ nullptr, // STDIN
+ &stdout_pipe, // STDOUT
+ &stderr_pipe); // STDERR
+ } catch (Glib::Error &e) {
+ g_critical("Script::execute(): failed to execute program '%s'.\n\tReason: %s", program.c_str(), e.what().data());
+ return 0;
+ }
+
+ // Create a new MainContext for the loop so that the original context sources are not run here,
+ // this enforces that only the file_listeners should be read in this new MainLoop
+ Glib::RefPtr<Glib::MainContext> _main_context = Glib::MainContext::create();
+ _main_loop = Glib::MainLoop::create(_main_context, false);
+
+ file_listener fileerr;
+ fileout.init(stdout_pipe, _main_loop);
+ fileerr.init(stderr_pipe, _main_loop);
+
+ _canceled = false;
+ _main_loop->run();
+
+ // Ensure all the data is out of the pipe
+ while (!fileout.isDead()) {
+ fileout.read(Glib::IO_IN);
+ }
+ while (!fileerr.isDead()) {
+ fileerr.read(Glib::IO_IN);
+ }
+
+ _main_loop.reset();
+
+ if (_canceled) {
+ // std::cout << "Script Canceled" << std::endl;
+ return 0;
+ }
+
+ Glib::ustring stderr_data = fileerr.string();
+ if (!stderr_data.empty() && !ignore_stderr) {
+ if (INKSCAPE.use_gui()) {
+ showPopupError(stderr_data, Gtk::MESSAGE_INFO,
+ _("Inkscape has received additional data from the script executed. "
+ "The script did not return an error, but this may indicate the results will not be as expected."));
+ } else {
+ std::cerr << "Script Error\n----\n" << stderr_data.c_str() << "\n----\n";
+ }
+ }
+
+ Glib::ustring stdout_data = fileout.string();
+ return stdout_data.length();
+}
+
+
+void Script::file_listener::init(int fd, Glib::RefPtr<Glib::MainLoop> main) {
+ _channel = Glib::IOChannel::create_from_fd(fd);
+ _channel->set_close_on_unref(true);
+ _channel->set_encoding();
+ _conn = main->get_context()->signal_io().connect(sigc::mem_fun(*this, &file_listener::read), _channel, Glib::IO_IN | Glib::IO_HUP | Glib::IO_ERR);
+ _main_loop = main;
+
+ return;
+}
+
+bool Script::file_listener::read(Glib::IOCondition condition) {
+ if (condition != Glib::IO_IN) {
+ _main_loop->quit();
+ return false;
+ }
+
+ Glib::IOStatus status;
+ Glib::ustring out;
+ status = _channel->read_line(out);
+ _string += out;
+
+ if (status != Glib::IO_STATUS_NORMAL) {
+ _main_loop->quit();
+ _dead = true;
+ return false;
+ }
+
+ return true;
+}
+
+bool Script::file_listener::toFile(const Glib::ustring &name) {
+ return toFile(Glib::filename_from_utf8(name));
+}
+
+bool Script::file_listener::toFile(const std::string &name) {
+ try {
+ Glib::RefPtr<Glib::IOChannel> stdout_file = Glib::IOChannel::create_from_file(name, "w");
+ stdout_file->set_encoding();
+ stdout_file->write(_string);
+ } catch (Glib::FileError &e) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace Implementation
+} // namespace Extension
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :