summaryrefslogtreecommitdiffstats
path: root/src/extension/implementation/script.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /src/extension/implementation/script.cpp
parentInitial commit. (diff)
downloadinkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz
inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/extension/implementation/script.cpp')
-rw-r--r--src/extension/implementation/script.cpp1022
1 files changed, 1022 insertions, 0 deletions
diff --git a/src/extension/implementation/script.cpp b/src/extension/implementation/script.cpp
new file mode 100644
index 0000000..0d0a07e
--- /dev/null
+++ b/src/extension/implementation/script.cpp
@@ -0,0 +1,1022 @@
+// 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 <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 "inkscape.h"
+#include "inkscape-window.h"
+#include "path-prefix.h"
+#include "preferences.h"
+#include "script.h"
+#include "selection.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 "io/resource.h"
+#include "layer-manager.h"
+#include "object/sp-namedview.h"
+#include "object/sp-path.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/view/view.h"
+#include "widgets/desktop-widget.h"
+#include "xml/attribute-record.h"
+#include "xml/node.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;
+}
+
+class ScriptDocCache : public ImplementationDocumentCache {
+ friend class Script;
+protected:
+ std::string _filename;
+ int _tempfd;
+public:
+ ScriptDocCache (Inkscape::UI::View::View * view);
+ ~ScriptDocCache ( ) override;
+};
+
+ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
+ ImplementationDocumentCache(view),
+ _filename(""),
+ _tempfd(0)
+{
+ try {
+ _tempfd = Glib::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
+ } catch (...) {
+ /// \todo Popup dialog here
+ return;
+ }
+
+ SPDesktop *desktop = (SPDesktop *) view;
+ sp_namedview_document_from_window(desktop);
+
+ 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),
+ view->doc(), _filename.c_str(), false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
+ prefs->setBool("/options/svgoutput/disable_optimizations", false);
+ return;
+}
+
+ScriptDocCache::~ScriptDocCache ( )
+{
+ close(_tempfd);
+ unlink(_filename.c_str());
+}
+
+ImplementationDocumentCache *Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
+ return new ScriptDocCache(view);
+}
+
+
+/**
+ \return A dialog for preferences
+ \brief A stub function right now
+ \param module Module who's preferences need getting
+ \param filename Hey, the file you're getting might be important
+
+ This function should really do something, right now it doesn't.
+*/
+Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module,
+ const gchar */*filename*/)
+{
+ return module->autogui(nullptr, nullptr);
+}
+
+
+
+/**
+ \return A dialog for preferences
+ \brief A stub function right now
+ \param module Module whose preferences need getting
+
+ This function should really do something, right now it doesn't.
+*/
+Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
+{
+ return module->autogui(nullptr, nullptr);
+}
+
+/**
+ \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 (docCache == nullptr) {
+ docCache = newDocCache(module, doc);
+ }
+ ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
+ if (dc == nullptr) {
+ printf("TOO BAD TO LIVE!!!");
+ exit(1);
+ }
+ if (doc == nullptr)
+ {
+ g_warning("Script::effect: View not defined");
+ return;
+ }
+
+ SPDesktop *desktop = reinterpret_cast<SPDesktop *>(doc);
+ sp_namedview_document_from_window(desktop);
+
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment(desktop->getDocument());
+
+ parent_window = module->get_execution_env()->get_working_dialog();
+
+ 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
+
+ Glib::ustring empty;
+ file_listener outfile;
+ execute(command, params, 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::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;
+ }
+
+ if (desktop) {
+ Inkscape::Selection * selection = desktop->getSelection();
+ if (selection) {
+ params = selection->params;
+ module->paramListString(params);
+ selection->clear();
+ }
+ }
+
+ file_listener fileout;
+ int data_read = execute(command, params, dc->_filename, fileout);
+ fileout.toFile(tempfilename_out);
+
+ pump_events();
+
+ SPDocument * mydoc = nullptr;
+ if (data_read > 10) {
+ try {
+ mydoc = Inkscape::Extension::open(
+ Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
+ tempfilename_out.c_str());
+ } catch (const Inkscape::Extension::Input::open_failed &e) {
+ g_warning("Extension returned output that could not be parsed: %s", e.what());
+ Gtk::MessageDialog warning(
+ _("The output from the extension could not be parsed."),
+ false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK, true);
+ warning.set_transient_for( parent_window ? *parent_window : *(INKSCAPE.active_desktop()->getToplevel()) );
+ warning.run();
+ }
+ } // data_read
+
+ pump_events();
+
+ // make sure we don't leak file descriptors from Glib::file_open_tmp
+ close(tempfd_out);
+
+ g_unlink(tempfilename_out.c_str());
+
+ if (mydoc) {
+ SPDocument* vd=doc->doc();
+ if (vd != nullptr)
+ {
+ mydoc->changeFilenameAndHrefs(vd->getDocumentFilename());
+
+ vd->emitReconstructionStart();
+ copy_doc(vd->getReprRoot(), mydoc->getReprRoot());
+ vd->emitReconstructionFinish();
+
+ // Getting the named view from the document generated by the extension
+ SPNamedView *nv = mydoc->getNamedView();
+
+ //Check if it has a default layer set up
+ SPObject *layer = nullptr;
+ if ( nv != nullptr)
+ {
+ SPDocument *document = desktop->doc();
+ if (document != nullptr) {
+ //If so, get that layer
+ if( nv->default_layer_id != 0 ) {
+ layer = document->getObjectById(g_quark_to_string(nv->default_layer_id));
+ }
+ desktop->showGrids(nv->grids_visible);
+ }
+ }
+
+ sp_namedview_update_layers_from_document(desktop);
+ //If that layer exists,
+ if (layer) {
+ //set the current layer
+ desktop->layerManager().setCurrentLayer(layer);
+ }
+ }
+ mydoc->release();
+ }
+
+ return;
+}
+
+
+
+/**
+ \brief A function to replace all the elements in an old document
+ by those from a new document.
+ document and repinserts them into an emptied old document.
+ \param oldroot The root node of the old (destination) document.
+ \param newroot The root node of the new (source) document.
+
+ This function first deletes all the root attributes in the old document followed
+ by copying all the root attributes from the new document to the old document.
+
+ It then deletes all the elements in the old document by
+ making two passes, the first to create a list of the old elements and
+ the second to actually delete them. This two pass approach removes issues
+ with the list being changed while parsing through it... lots of nasty bugs.
+
+ Then, it copies all the element in the new document into the old document.
+
+ Finally, it copies the attributes in namedview.
+*/
+void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
+{
+ if ((oldroot == nullptr) ||(newroot == nullptr))
+ {
+ g_warning("Error on copy_doc: NULL pointer input.");
+ return;
+ }
+
+ // For copying attributes in root and in namedview
+ std::vector<gchar const *> attribs;
+
+ // Must explicitly copy root attributes. This must be done first since
+ // copying grid lines calls "SPGuide::set()" which needs to know the
+ // width, height, and viewBox of the root element.
+
+ // Make a list of all attributes of the old root node.
+ for (const auto & iter : oldroot->attributeList()) {
+ attribs.push_back(g_quark_to_string(iter.key));
+ }
+
+ // Delete the attributes of the old root node.
+ for (auto attrib : attribs) {
+ oldroot->removeAttribute(attrib);
+ }
+
+ // Set the new attributes.
+ for (const auto & iter : newroot->attributeList()) {
+ gchar const *name = g_quark_to_string(iter.key);
+ oldroot->setAttribute(name, newroot->attribute(name));
+ }
+
+ // Question: Why is the "sodipodi:namedview" special? Treating it as a normal
+ // element results in crashes.
+ // Seems to be a bug:
+ // http://inkscape.13.x6.nabble.com/Effect-that-modifies-the-document-properties-tt2822126.html
+
+ std::vector<Inkscape::XML::Node *> delete_list;
+
+ // Make list
+ for (Inkscape::XML::Node * child = oldroot->firstChild();
+ child != nullptr;
+ child = child->next()) {
+ if (!strcmp("sodipodi:namedview", child->name())) {
+ for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
+ oldroot_namedview_child != nullptr;
+ oldroot_namedview_child = oldroot_namedview_child->next()) {
+ delete_list.push_back(oldroot_namedview_child);
+ }
+ break;
+ }
+ }
+
+ // Unparent (delete)
+ for (auto & i : delete_list) {
+ sp_repr_unparent(i);
+ }
+ attribs.clear();
+ oldroot->mergeFrom(newroot, "id", true, true);
+}
+
+/** \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::checkStderr (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)
+{
+ 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.length() != 0 &&
+ INKSCAPE.use_gui()
+ ) {
+ checkStderr(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."));
+ }
+
+ Glib::ustring stdout_data = fileout.string();
+ if (stdout_data.length() == 0) {
+ return 0;
+ }
+
+ // std::cout << "Finishing Execution." << std::endl;
+ 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 :