Code

Extensions. Fix for Bug #668895 (Extensions with <check> tags fail to load).
[inkscape.git] / src / extension / implementation / script.cpp
index e28015f6f57ed73c08af85cc2775a7b7a6c7c980..428ee626fd361f79342dc7624d67f192e62da165 100644 (file)
@@ -5,8 +5,10 @@
  * Authors:
  *   Bryce Harrington <bryce@osdl.org>
  *   Ted Gould <ted@gould.cx>
+ *   Jon A. Cruz <jon@joncruz.org>
+ *   Abhishek Sharma
  *
- * Copyright (C) 2002-2005 Authors
+ * Copyright (C) 2002-2005,2007 Authors
  *
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 #include <unistd.h>
 
 #include <errno.h>
-#include <gtkmm/textview.h>
-#include <gtkmm/scrolledwindow.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gtkmm.h>
 
 #include "ui/view/view.h"
 #include "desktop-handles.h"
+#include "desktop.h"
 #include "selection.h"
 #include "sp-namedview.h"
 #include "io/sys.h"
-#include "prefs-utils.h"
+#include "preferences.h"
 #include "../system.h"
 #include "extension/effect.h"
+#include "extension/output.h"
+#include "extension/input.h"
 #include "extension/db.h"
 #include "script.h"
+#include "dialogs/dialog-events.h"
+#include "inkscape.h"
+#include "xml/node.h"
+#include "xml/attribute-record.h"
 
 #include "util/glib-list-iterators.h"
 
+
+
 #ifdef WIN32
 #include <windows.h>
+#include <sys/stat.h>
+#include "registrytool.h"
 #endif
 
+
+
 /** This is the command buffer that gets allocated from the stack */
 #define BUFSIZE (255)
 
+
+
 /* Namespaces */
 namespace Inkscape {
 namespace Extension {
 namespace Implementation {
 
-/* Real functions */
-/**
-    \return    A script object
-    \brief     This function creates a script object and sets up the
+/** \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 (void) {
+    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.
+*/
+Script::interpreter_t const Script::interpreterTab[] = {
+        {"perl",   "perl-interpreter",   "perl"   },
+#ifdef WIN32
+        {"python", "python-interpreter", "pythonw" },
+#else
+        {"python", "python-interpreter", "python" },
+#endif
+        {"ruby",   "ruby-interpreter",   "ruby"   },
+        {"shell",  "shell-interpreter",  "sh"     },
+        { NULL,    NULL,                  NULL    }
+};
+
+
+
+/** \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)
+{
+    interpreter_t const *interp = 0;
+    bool foundInterp = false;
+    for (interp =  interpreterTab ; interp->identity ; interp++ ){
+        if (interpNameArg == interp->identity) {
+            foundInterp = true;
+            break;
+        }
+    }
+
+    // Do we have a supported interpreter type?
+    if (!foundInterp) {
+        return "";
+    }
+    std::string interpreter_path = Glib::filename_from_utf8(interp->defaultval);
+
+    // 1.  Check preferences for an override.
+    // Note: this must be an absolute path.
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    Glib::ustring prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->prefstring));
+
+    if (!prefInterp.empty()) {
+        interpreter_path = Glib::filename_from_utf8(prefInterp);
+    }
+
+    // 2. Search the path.
+    // Do this on all systems, for consistency.
+    // PATH is set up to contain the Python and Perl binary directories
+    // on Windows, so no extra code is necessary.
+    if (!Glib::path_is_absolute(interpreter_path)) {
+        interpreter_path = Glib::find_program_in_path(interpreter_path);
+    }
+    return interpreter_path;
+}
+
+/** \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(),
-    command(NULL),
-    helper_extension(NULL)
+    Implementation()
+{
+}
+
+/**
+ *   brief     Destructor
+ */
+Script::~Script()
 {
 }
 
+
+
 /**
     \return    A string with the complete string with the relative directory expanded
     \brief     This function takes in a Repr that contains a reldir entry
@@ -80,29 +177,40 @@ Script::Script() :
     string.  This means that the caller of this function can always
     free what they are given (and should do it too!).
 */
-gchar *
+std::string
 Script::solve_reldir(Inkscape::XML::Node *reprin) {
-    gchar const *reldir = reprin->attribute("reldir");
 
-    if (reldir == NULL) {
-        return g_strdup(sp_repr_children(reprin)->content());
+    gchar const *s = reprin->attribute("reldir");
+
+    // right now the only recognized relative directory is "extensions"
+    if (!s || Glib::ustring(s) != "extensions") {
+        Glib::ustring str = sp_repr_children(reprin)->content();
+        return str;
     }
 
-    if (!strcmp(reldir, "extensions")) {
-        for(unsigned int i=0; i<Inkscape::Extension::Extension::search_path.size(); i++) {
-            gchar * filename = g_build_filename(Inkscape::Extension::Extension::search_path[i], sp_repr_children(reprin)->content(), NULL);
-            if ( Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS) ) {
-                return filename;
-            }
-            g_free(filename);
+    Glib::ustring reldir = s;
+
+    for (unsigned int i=0;
+        i < Inkscape::Extension::Extension::search_path.size();
+        i++) {
+
+        gchar * fname = g_build_filename(
+           Inkscape::Extension::Extension::search_path[i],
+           sp_repr_children(reprin)->content(),
+           NULL);
+        Glib::ustring filename = fname;
+        g_free(fname);
+
+        if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) ) {
+            return Glib::filename_from_utf8(filename);
         }
-    } else {
-        return g_strdup(sp_repr_children(reprin)->content());
     }
 
-    return NULL;
+    return "";
 }
 
+
+
 /**
     \return   Whether the command given exists, including in the path
     \brief    This function is used to find out if something exists for
@@ -119,66 +227,63 @@ Script::solve_reldir(Inkscape::XML::Node *reprin) {
     then a TRUE is returned.  If we get all the way through the path
     then a FALSE is returned, the command could not be found.
 */
-bool
-Script::check_existance(gchar const *command)
+bool Script::check_existence(const std::string &command)
 {
-    if (*command == '\0') {
-        /* We check the simple case first. */
-        return FALSE;
-    }
 
-    if (g_utf8_strchr(command, -1, G_DIR_SEPARATOR) != NULL) {
-        /* Don't search when it contains a slash. */
-        if (Inkscape::IO::file_test(command, G_FILE_TEST_EXISTS))
-            return TRUE;
-        else
-            return FALSE;
+    // Check the simple case first
+    if (command.empty()) {
+        return false;
     }
 
+    //Don't search when it is an absolute path. */
+    if (Glib::path_is_absolute(command)) {
+        if (Glib::file_test(command, Glib::FILE_TEST_EXISTS)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
 
-    gchar *path = g_strdup(g_getenv("PATH"));
-    if (path == NULL) {
-        /* There is no `PATH' in the environment.
+    std::string path = Glib::getenv("PATH");
+    if (path.empty()) {
+       /* There is no `PATH' in the environment.
            The default search path is the current directory */
-        path = g_strdup(G_SEARCHPATH_SEPARATOR_S);
+        path = G_SEARCHPATH_SEPARATOR_S;
     }
-    gchar *orig_path = path;
 
-    for (; path != NULL;) {
-        gchar *const local_path = path;
-        path = g_utf8_strchr(path, -1, G_SEARCHPATH_SEPARATOR);
-        if (path == NULL) {
-            break;
-        }
-        /* Not sure whether this is UTF8 happy, but it would seem
-           like it considering that I'm searching (and finding)
-           the ':' character */
-        if (path != local_path && path != NULL) {
-            path[0] = '\0';
-            path++;
-        } else {
-            path = NULL;
-        }
+    std::string::size_type pos  = 0;
+    std::string::size_type pos2 = 0;
+    while ( pos < path.size() ) {
+
+        std::string localPath;
 
-        gchar *final_name;
-        if (local_path == '\0') {
-            final_name = g_strdup(command);
+        pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
+        if (pos2 == path.npos) {
+            localPath = path.substr(pos);
+            pos = path.size();
         } else {
-            final_name = g_build_filename(local_path, command, NULL);
+            localPath = path.substr(pos, pos2-pos);
+            pos = pos2+1;
         }
 
-        if (Inkscape::IO::file_test(final_name, G_FILE_TEST_EXISTS)) {
-            g_free(final_name);
-            g_free(orig_path);
-            return TRUE;
+        //printf("### %s\n", localPath.c_str());
+        std::string candidatePath =
+                      Glib::build_filename(localPath, command);
+
+        if (Glib::file_test(candidatePath,
+                      Glib::FILE_TEST_EXISTS)) {
+            return true;
         }
 
-        g_free(final_name);
     }
 
-    return FALSE;
+    return false;
 }
 
+
+
+
+
 /**
     \return   none
     \brief    This function 'loads' an extention, basically it determines
@@ -197,64 +302,30 @@ Script::check_existance(gchar const *command)
     and should error out at a higher level.
 */
 
-bool
-Script::load(Inkscape::Extension::Extension *module)
+bool Script::load(Inkscape::Extension::Extension *module)
 {
     if (module->loaded()) {
-        return TRUE;
+        return true;
     }
 
-    helper_extension = NULL;
+    helper_extension = "";
 
     /* This should probably check to find the executable... */
     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
-    gchar *command_text = NULL;
     while (child_repr != NULL) {
-        if (!strcmp(child_repr->name(), "script")) {
+        if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
             child_repr = sp_repr_children(child_repr);
             while (child_repr != NULL) {
-                if (!strcmp(child_repr->name(), "command")) {
-                    command_text = solve_reldir(child_repr);
-
-                    const gchar * interpretstr = child_repr->attribute("interpreter");
+                if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
+                    const gchar *interpretstr = child_repr->attribute("interpreter");
                     if (interpretstr != NULL) {
-                        struct interpreter_t {
-                            gchar * identity;
-                            gchar * prefstring;
-                            gchar * defaultval;
-                        };
-                        const interpreter_t interpreterlst[] = {
-                            {"perl", "perl-interpreter", "perl"},
-                            {"python", "python-interpreter", "python"},
-                            {"ruby", "ruby-interpreter", "ruby"},
-                            {"shell", "shell-interpreter", "sh"}
-                        }; /* Change count below if you change structure */
-                        for (unsigned int i = 0; i < 4; i++) {
-                            if (!strcmp(interpretstr, interpreterlst[i].identity)) {
-                                const gchar * insertText = interpreterlst[i].defaultval;
-                                if (prefs_get_string_attribute("extensions", interpreterlst[i].prefstring) != NULL)
-                                    insertText = prefs_get_string_attribute("extensions", interpreterlst[i].prefstring);
-#ifdef _WIN32
-                                else {
-                                    char szExePath[MAX_PATH];
-                                    char szCurrentDir[MAX_PATH];
-                                    GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
-                                    if (reinterpret_cast<unsigned>(FindExecutable(command_text, szCurrentDir, szExePath)) > 32)
-                                        insertText = szExePath;
-                                }
-#endif
-
-                                gchar * temp = command_text;
-                                command_text = g_strconcat(insertText, " ", temp, NULL);
-                                g_free(temp);
-
-                                break;
-                            }
-                        }
+                        std::string interpString = resolveInterpreterExecutable(interpretstr);
+                        command.insert(command.end(), interpString);
                     }
+                    command.insert(command.end(), solve_reldir(child_repr));
                 }
-                if (!strcmp(child_repr->name(), "helper_extension")) {
-                    helper_extension = g_strdup(sp_repr_children(child_repr)->content());
+                if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
+                    helper_extension = sp_repr_children(child_repr)->content();
                 }
                 child_repr = sp_repr_next(child_repr);
             }
@@ -264,15 +335,12 @@ Script::load(Inkscape::Extension::Extension *module)
         child_repr = sp_repr_next(child_repr);
     }
 
-    g_return_val_if_fail(command_text != NULL, FALSE);
-
-    if (command != NULL)
-        g_free(command);
-    command = command_text;
+    //g_return_val_if_fail(command.length() > 0, false);
 
-    return TRUE;
+    return true;
 }
 
+
 /**
     \return   None.
     \brief    Unload this puppy!
@@ -281,21 +349,15 @@ Script::load(Inkscape::Extension::Extension *module)
     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)
+void Script::unload(Inkscape::Extension::Extension */*module*/)
 {
-    if (command != NULL) {
-        g_free(command);
-        command = NULL;
-    }
-    if (helper_extension != NULL) {
-        g_free(helper_extension);
-        helper_extension = NULL;
-    }
-
-    return;
+    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
@@ -305,28 +367,27 @@ Script::unload(Inkscape::Extension::Extension *module)
 bool
 Script::check(Inkscape::Extension::Extension *module)
 {
+    int script_count = 0;
     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
     while (child_repr != NULL) {
-        if (!strcmp(child_repr->name(), "script")) {
+        if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
+            script_count++;
             child_repr = sp_repr_children(child_repr);
             while (child_repr != NULL) {
-                if (!strcmp(child_repr->name(), "check")) {
-                    gchar *command_text = solve_reldir(child_repr);
-                    if (command_text != NULL) {
+                if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
+                    std::string command_text = solve_reldir(child_repr);
+                    if (!command_text.empty()) {
                         /* I've got the command */
-                        bool existance;
-
-                        existance = check_existance(command_text);
-                        g_free(command_text);
+                        bool existance = check_existence(command_text);
                         if (!existance)
-                            return FALSE;
+                            return false;
                     }
                 }
 
-                if (!strcmp(child_repr->name(), "helper_extension")) {
+                if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
                     gchar const *helper = sp_repr_children(child_repr)->content();
                     if (Inkscape::Extension::db.get(helper) == NULL) {
-                        return FALSE;
+                        return false;
                     }
                 }
 
@@ -338,47 +399,80 @@ Script::check(Inkscape::Extension::Extension *module)
         child_repr = sp_repr_next(child_repr);
     }
 
-    return TRUE;
+    if (script_count == 0) {
+        return false;
+    }
+
+    return true;
 }
 
-/**
-    \return   A dialog for preferences
-    \brief    A stub funtion right now
-    \param    module    Module who's preferences need getting
-    \param    filename  Hey, the file you're getting might be important
+class ScriptDocCache : public ImplementationDocumentCache {
+    friend class Script;
+protected:
+    std::string _filename;
+    int _tempfd;
+public:
+    ScriptDocCache (Inkscape::UI::View::View * view);
+    ~ScriptDocCache ( );
+};
 
-    This function should really do something, right now it doesn't.
-*/
-Gtk::Widget *
-Script::prefs_input(Inkscape::Extension::Input *module, gchar const *filename)
+ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
+    ImplementationDocumentCache(view),
+    _filename(""),
+    _tempfd(0)
+{
+    try {
+        _tempfd = Inkscape::IO::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::Extension::save(
+              Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
+              view->doc(), _filename.c_str(), false, false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
+
+    return;
+}
+
+ScriptDocCache::~ScriptDocCache ( )
 {
-    /*return module->autogui(); */
-    return NULL;
+    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 funtion right now
-    \param    module    Module whose preferences need getting
+    \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_output(Inkscape::Extension::Output *module)
+Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module,
+                    const gchar */*filename*/)
 {
-    /*return module->autogui();*/
-    return NULL;
+    return module->autogui(NULL, NULL);
 }
 
+
+
 /**
     \return   A dialog for preferences
     \brief    A stub funtion right now
-    \param    module    Module who's preferences need getting
+    \param    module    Module whose preferences need getting
 
     This function should really do something, right now it doesn't.
 */
-Gtk::Widget *
-Script::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *view)
+Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
 {
     return module->autogui(NULL, NULL);
 }
@@ -404,59 +498,54 @@ Script::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::Vi
     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, gchar const *filename)
+SPDocument *Script::open(Inkscape::Extension::Input *module,
+             const gchar *filenameArg)
 {
-    int data_read = 0;
-    gint tempfd;
-    gchar *tempfilename_out;
-
-    // FIXME: process the GError instead of passing NULL
-    if ((tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) {
-        /* Error, couldn't create temporary filename */
-        if (errno == EINVAL) {
-            /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
-            perror("Extension::Script:  template for filenames is misconfigured.\n");
-            exit(-1);
-        } else if (errno == EEXIST) {
-            /* Now the  contents of template are undefined. */
-            perror("Extension::Script:  Could not create a unique temporary filename\n");
-            return NULL;
-        } else {
-            perror("Extension::Script:  Unknown error creating temporary filename\n");
-            exit(-1);
-        }
+    std::list<std::string> params;
+    module->paramListString(params);
+
+    std::string tempfilename_out;
+    int tempfd_out = 0;
+    try {
+        tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
+    } catch (...) {
+        /// \todo Popup dialog here
+        return NULL;
     }
 
-    gsize bytesRead = 0;
-    gsize bytesWritten = 0;
-    GError *error = NULL;
-    gchar *local_filename = g_filename_from_utf8( filename,
-                                                  -1,  &bytesRead,  &bytesWritten, &error);
+    std::string lfilename = Glib::filename_from_utf8(filenameArg);
 
-    data_read = execute(command, local_filename, tempfilename_out);
-    g_free(local_filename);
+    file_listener fileout;
+    int data_read = execute(command, params, lfilename, fileout);
+    fileout.toFile(tempfilename_out);
 
-    SPDocument *mydoc = NULL;
+    SPDocument * mydoc = NULL;
     if (data_read > 10) {
-        if (helper_extension == NULL) {
-            mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out);
+        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), tempfilename_out);
+            mydoc = Inkscape::Extension::open(
+                  Inkscape::Extension::db.get(helper_extension.c_str()),
+                  tempfilename_out.c_str());
         }
-    }
+    } // data_read
 
-    if (mydoc != NULL)
-        sp_document_set_uri(mydoc, (const gchar *)filename);
+    if (mydoc != NULL) {
+        mydoc->setBase(0);
+        mydoc->changeUriAndHrefs(filenameArg);
+    }
 
     // make sure we don't leak file descriptors from g_file_open_tmp
-    close(tempfd);
-    // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
-    unlink(tempfilename_out);
-    g_free(tempfilename_out);
+    close(tempfd_out);
+
+    unlink(tempfilename_out.c_str());
 
     return mydoc;
-}
+} // open
+
+
 
 /**
     \return   none
@@ -466,6 +555,7 @@ Script::open(Inkscape::Extension::Input *module, gchar const *filename)
     \param    module    Extention 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
@@ -482,51 +572,59 @@ Script::open(Inkscape::Extension::Input *module, gchar const *filename)
     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, gchar const *filename)
+void Script::save(Inkscape::Extension::Output *module,
+             SPDocument *doc,
+             const gchar *filenameArg)
 {
-    gint tempfd;
-    gchar *tempfilename_in;
-    // FIXME: process the GError instead of passing NULL
-    if ((tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) {
-        /* Error, couldn't create temporary filename */
-        if (errno == EINVAL) {
-            /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
-            perror("Extension::Script:  template for filenames is misconfigured.\n");
-            exit(-1);
-        } else if (errno == EEXIST) {
-            /* Now the  contents of template are undefined. */
-            perror("Extension::Script:  Could not create a unique temporary filename\n");
-            return;
-        } else {
-            perror("Extension::Script:  Unknown error creating temporary filename\n");
-            exit(-1);
-        }
-    }
-
-    if (helper_extension == NULL) {
-        Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), doc, tempfilename_in, FALSE, FALSE, FALSE);
+    std::list<std::string> params;
+    module->paramListString(params);
+
+    std::string tempfilename_in;
+    int tempfd_in = 0;
+    try {
+        tempfd_in = Inkscape::IO::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, false,
+                   Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
     } else {
-        Inkscape::Extension::save(Inkscape::Extension::db.get(helper_extension), doc, tempfilename_in, FALSE, FALSE, FALSE);
+        Inkscape::Extension::save(
+                   Inkscape::Extension::db.get(helper_extension.c_str()),
+                   doc, tempfilename_in.c_str(), false, false, false,
+                   Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
     }
 
-    gsize bytesRead = 0;
-    gsize bytesWritten = 0;
-    GError *error = NULL;
-    gchar *local_filename = g_filename_from_utf8( filename,
-                                                  -1,  &bytesRead,  &bytesWritten, &error);
 
-    execute(command, tempfilename_in, local_filename);
+    file_listener fileout;
+    int data_read = execute(command, params, tempfilename_in, fileout);
+    
+    bool success = false;
 
-    g_free(local_filename);
+    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 g_file_open_tmp
-    close(tempfd);
+    close(tempfd_in);
     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
-    unlink(tempfilename_in);
-    g_free(tempfilename_in);
+    unlink(tempfilename_in.c_str());
+
+    if (success == false) {
+        throw Inkscape::Extension::Output::save_failed();
+    }
+
+    return;
 }
 
+
+
 /**
     \return    none
     \brief     This function uses an extention as a effect on a document.
@@ -536,7 +634,7 @@ Script::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *
     This function is a little bit trickier than the previous two.  It
     needs two temporary files to get it's work done.  Both of these
     files have random names created for them using the g_file_open_temp function
-    with the sp_ext_ prefix in the temporary directory.  Like the other
+    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
@@ -555,98 +653,91 @@ Script::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *
     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)
+void Script::effect(Inkscape::Extension::Effect *module,
+               Inkscape::UI::View::View *doc,
+               ImplementationDocumentCache * docCache)
 {
-    int data_read = 0;
-    SPDocument * mydoc = NULL;
-    gint tempfd_in;
-    gchar *tempfilename_in;
-
-    // FIXME: process the GError instead of passing NULL
-    if ((tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) {
-        /* Error, couldn't create temporary filename */
-        if (errno == EINVAL) {
-            /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
-            perror("Extension::Script:  template for filenames is misconfigured.\n");
-            exit(-1);
-        } else if (errno == EEXIST) {
-            /* Now the  contents of template are undefined. */
-            perror("Extension::Script:  Could not create a unique temporary filename\n");
-            return;
-        } else {
-            perror("Extension::Script:  Unknown error creating temporary filename\n");
-            exit(-1);
-        }
+    if (docCache == NULL) {
+        docCache = newDocCache(module, doc);
     }
-
-    gint tempfd_out;
-    gchar *tempfilename_out;
-    // FIXME: process the GError instead of passing NULL
-    if ((tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) {
-        /* Error, couldn't create temporary filename */
-        if (errno == EINVAL) {
-            /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
-            perror("Extension::Script:  template for filenames is misconfigured.\n");
-            exit(-1);
-        } else if (errno == EEXIST) {
-            /* Now the  contents of template are undefined. */
-            perror("Extension::Script:  Could not create a unique temporary filename\n");
-            return;
-        } else {
-            perror("Extension::Script:  Unknown error creating temporary filename\n");
-            exit(-1);
-        }
+    ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
+    if (dc == NULL) {
+        printf("TOO BAD TO LIVE!!!");
+        exit(1);
     }
 
-    Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
-                              doc->doc(), tempfilename_in, FALSE, FALSE, FALSE);
+    SPDesktop *desktop = (SPDesktop *)doc;
+    sp_namedview_document_from_window(desktop);
+
+    std::list<std::string> params;
+    module->paramListString(params);
+
+    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);
+
+        return;
+    }
 
-    Glib::ustring local_command(command);
+    std::string tempfilename_out;
+    int tempfd_out = 0;
+    try {
+        tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
+    } catch (...) {
+        /// \todo Popup dialog here
+        return;
+    }
 
-    /* fixme: Should be some sort of checking here.  Don't know how to do this with structs instead
-     * of classes. */
-    SPDesktop *desktop = (SPDesktop *) doc;
     if (desktop != NULL) {
-        using Inkscape::Util::GSListConstIterator;
-        GSListConstIterator<SPItem *> selected = sp_desktop_selection(desktop)->itemList();
+        Inkscape::Util::GSListConstIterator<SPItem *> selected =
+             sp_desktop_selection(desktop)->itemList();
         while ( selected != NULL ) {
-            local_command += " --id=";
-            local_command += SP_OBJECT_ID(*selected);
+            Glib::ustring selected_id;
+            selected_id += "--id=";
+            selected_id += (*selected)->getId();
+            params.insert(params.begin(), selected_id);
             ++selected;
         }
     }
 
-    Glib::ustring * paramString = module->paramString();
-    local_command += *paramString;
-    delete paramString;
+    file_listener fileout;
+    int data_read = execute(command, params, dc->_filename, fileout);
+    fileout.toFile(tempfilename_out);
 
-    // std::cout << local_command << std::endl;
+    pump_events();
 
-    data_read = execute(local_command.c_str(), tempfilename_in, tempfilename_out);
+    SPDocument * mydoc = NULL;
+    if (data_read > 10) {
+        mydoc = Inkscape::Extension::open(
+              Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
+              tempfilename_out.c_str());
+    } // data_read
 
-    if (data_read > 10)
-        mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out);
+    pump_events();
 
     // make sure we don't leak file descriptors from g_file_open_tmp
-    close(tempfd_in);
     close(tempfd_out);
-    // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
-    unlink(tempfilename_in);
-    g_free(tempfilename_in);
-    unlink(tempfilename_out);
-    g_free(tempfilename_out);
+
+    g_unlink(tempfilename_out.c_str());
 
     /* Do something with mydoc.... */
-    if (mydoc != NULL) {
+    if (mydoc) {
         doc->doc()->emitReconstructionStart();
         copy_doc(doc->doc()->rroot, mydoc->rroot);
         doc->doc()->emitReconstructionFinish();
         mydoc->release();
+        sp_namedview_update_layers_from_document(desktop);
     }
+
+    return;
 }
 
 
+
 /**
     \brief  A function to take all the svg elements from one document
             and put them in another.
@@ -662,218 +753,83 @@ Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *do
     elements and putting them into the old document.  The copy
     is then complete.
 */
-void
-Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
+void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
 {
     std::vector<Inkscape::XML::Node *> delete_list;
+    Inkscape::XML::Node * oldroot_namedview = NULL;
+
     for (Inkscape::XML::Node * child = oldroot->firstChild();
             child != NULL;
             child = child->next()) {
-        if (!strcmp("sodipodi:namedview", child->name()))
-            continue;
-        if (!strcmp("svg:defs", child->name()))
-            continue;
-        delete_list.push_back(child);
+        if (!strcmp("sodipodi:namedview", child->name())) {
+            oldroot_namedview = child;
+            for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
+                    oldroot_namedview_child != NULL;
+                    oldroot_namedview_child = oldroot_namedview_child->next()) {
+                delete_list.push_back(oldroot_namedview_child);
+            }
+        } else {
+            delete_list.push_back(child);
+        }
     }
-    for (unsigned int i = 0; i < delete_list.size(); i++)
+    for (unsigned int i = 0; i < delete_list.size(); i++) {
         sp_repr_unparent(delete_list[i]);
+    }
 
     for (Inkscape::XML::Node * child = newroot->firstChild();
             child != NULL;
             child = child->next()) {
-        if (!strcmp("sodipodi:namedview", child->name()))
-            continue;
-        if (!strcmp("svg:defs", child->name()))
-            continue;
-        oldroot->appendChild(child->duplicate());
-    }
-
-    /** \todo  Restore correct layer */
-    /** \todo  Restore correct selection */
-}
-
-/* Helper class used by Script::execute */
-class pipe_t {
-public:
-    /* These functions set errno if they return false.
-       I'm not sure whether that's a good idea or not, but it should be reasonably
-       straightforward to change it if needed. */
-    bool open(char *command, char const *errorFile, int mode);
-    bool close();
-
-    /* These return the number of bytes read/written. */
-    size_t read(void *buffer, size_t size);
-    size_t write(void const *buffer, size_t size);
-
-    enum {
-        mode_read  = 1 << 0,
-        mode_write = 1 << 1,
-    };
-
-private:
-#ifdef WIN32
-    /* This is used to translate win32 errors into errno errors.
-       It only recognizes a few win32 errors for the moment though. */
-    static int translate_error(DWORD err);
-
-    HANDLE hpipe;
-#else
-    FILE *ppipe;
-#endif
-};
-
-/**
-    \return   none
-    \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 is 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 fread and 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 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 gchar * in_command, const gchar * filein, const gchar * fileout)
-{
-    g_return_val_if_fail(in_command != NULL, 0);
-    // printf("Executing: %s\n", in_command);
-
-    gchar * errorFile;
-    gint errorFileNum;
-    errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &errorFile, NULL);
-    if (errorFileNum != 0) {
-        close(errorFileNum);
-    } else {
-        g_free(errorFile);
-        errorFile = NULL;
-    }
-
-    char *command = g_strdup_printf("%s \"%s\"", in_command, filein);
-    // std::cout << "Command to run: " << command << std::endl;
-
-    pipe_t pipe;
-    bool open_success = pipe.open(command, errorFile, pipe_t::mode_read);
-    g_free(command);
-
-    /* Run script */
-    if (!open_success) {
-        /* Error - could not open pipe - check errno */
-        if (errno == EINVAL) {
-            perror("Extension::Script:  Invalid mode argument in popen\n");
-        } else if (errno == ECHILD) {
-            perror("Extension::Script:  Cannot obtain child extension status in popen\n");
+        if (!strcmp("sodipodi:namedview", child->name())) {
+            if (oldroot_namedview != NULL) {
+                for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
+                        newroot_namedview_child != NULL;
+                        newroot_namedview_child = newroot_namedview_child->next()) {
+                    oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
+                }
+            }
         } else {
-            perror("Extension::Script:  Unknown error for popen\n");
+            oldroot->appendChild(child->duplicate(oldroot->document()));
         }
-        return 0;
     }
 
-    Inkscape::IO::dump_fopen_call(fileout, "J");
-    FILE *pfile = Inkscape::IO::fopen_utf8name(fileout, "w");
+    {
+        using Inkscape::Util::List;
+        using Inkscape::XML::AttributeRecord;
+        std::vector<gchar const *> attribs;
 
-    if (pfile == NULL) {
-        /* Error - could not open file */
-        if (errno == EINVAL) {
-            perror("Extension::Script:  The mode provided to fopen was invalid\n");
-        } else {
-            perror("Extension::Script:  Unknown error attempting to open temporary file\n");
+        // Make a list of all attributes of the old root node.
+        for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
+            attribs.push_back(g_quark_to_string(iter->key));
         }
-        return 0;
-    }
-
-    /* Copy pipe output to a temporary file */
-    int amount_read = 0;
-    char buf[BUFSIZE];
-    int num_read;
-    while ((num_read = pipe.read(buf, BUFSIZE)) != 0) {
-        amount_read += num_read;
-        fwrite(buf, 1, num_read, pfile);
-    }
 
-    /* Close file */
-    if (fclose(pfile) == EOF) {
-        if (errno == EBADF) {
-            perror("Extension::Script:  The filedescriptor for the temporary file is invalid\n");
-            return 0;
-        } else {
-            perror("Extension::Script:  Unknown error closing temporary file\n");
+        // Delete the attributes of the old root nodes.
+        for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++) {
+            oldroot->setAttribute(*it, NULL);
         }
-    }
 
-    /* Close pipe */
-    if (!pipe.close()) {
-        if (errno == EINVAL) {
-            perror("Extension::Script:  Invalid mode set for pclose\n");
-        } else if (errno == ECHILD) {
-            perror("Extension::Script:  Could not obtain child status for pclose\n");
-        } else {
-            if (errorFile != NULL) {
-                checkStderr(errorFile, Gtk::MESSAGE_ERROR,
-                    _("Inkscape has received an error from the script that it called.  "
-                      "The text returned with the error is included below.  "
-                      "Inkscape will continue working, but the action you requested has been cancelled."));
-            } else {
-                perror("Extension::Script:  Unknown error for pclose\n");
-            }
-        }
-        /* Could be a lie, but if there is an error, we don't want
-         * to count on what was read being good */
-        amount_read = 0;
-    } else {
-        if (errorFile != NULL) {
-            checkStderr(errorFile, 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."));
+        // Set the new attributes.
+        for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) {
+            gchar const *name = g_quark_to_string(iter->key);
+            oldroot->setAttribute(name, newroot->attribute(name));
         }
     }
 
-    if (errorFile != NULL) {
-        unlink(errorFile);
-        g_free(errorFile);
-    }
-
-    return amount_read;
+    /** \todo  Restore correct layer */
+    /** \todo  Restore correct selection */
 }
 
 /**  \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 (gchar * filename, Gtk::MessageType type, gchar * message)
+void Script::checkStderr (const Glib::ustring &data,
+                           Gtk::MessageType type,
+                     const Glib::ustring &message)
 {
-    // magic win32 crlf->lf conversion means the file length is not the same as
-    // the text length, but luckily gtk will accept crlf in textviews so we can
-    // just use binary mode
-    std::ifstream stderrf (filename, std::ios_base::in | std::ios_base::binary);
-    if (!stderrf.is_open()) return;
-
-    stderrf.seekg(0, std::ios::end);
-    int length = stderrf.tellg();
-    if (0 == length) return;
-    stderrf.seekg(0, std::ios::beg);
-
     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
     warning.set_resizable(true);
+    GtkWidget *dlg = GTK_WIDGET(warning.gobj());
+    sp_transientize(dlg);
 
     Gtk::VBox * vbox = warning.get_vbox();
 
@@ -883,11 +839,13 @@ Script::checkStderr (gchar * filename, Gtk::MessageType type, gchar * message)
     textview->set_wrap_mode(Gtk::WRAP_WORD);
     textview->show();
 
-    char * buffer = new char [length];
-    stderrf.read(buffer, length);
-    textview->get_buffer()->set_text(buffer, buffer + length);
-    delete buffer;
-    stderrf.close();
+    // Remove the last character
+    char *errormsg = (char*) data.c_str();
+    while (*errormsg != '\0') errormsg++;
+    errormsg -= 1;
+    *errormsg = '\0';
+
+    textview->get_buffer()->set_text(_(data.c_str()));
 
     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
     scrollwindow->add(*textview);
@@ -902,144 +860,151 @@ Script::checkStderr (gchar * filename, Gtk::MessageType type, gchar * message)
     return;
 }
 
-#ifdef WIN32
-
-bool pipe_t::open(char *command, char const *errorFile, int mode_p) {
-    HANDLE pipe_write;
-
-    // Create pipe
-    {
-        SECURITY_ATTRIBUTES secattrs;
-        ZeroMemory(&secattrs, sizeof(secattrs));
-        secattrs.nLength = sizeof(secattrs);
-        secattrs.lpSecurityDescriptor = 0;
-        secattrs.bInheritHandle = TRUE;
-        HANDLE t_pipe_read = 0;
-        if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) {
-            errno = translate_error(GetLastError());
-            return false;
-        }
-        // This duplicate handle makes the read pipe uninheritable
-        if ( !DuplicateHandle(GetCurrentProcess(), t_pipe_read, GetCurrentProcess(), &hpipe, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) ) {
-            int en = translate_error(GetLastError());
-            CloseHandle(t_pipe_read);
-            CloseHandle(pipe_write);
-            errno = en;
-            return false;
-        }
-    }
-    // Open stderr file
-    HANDLE hStdErrFile = CreateFile(errorFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
-    HANDLE hInheritableStdErr;
-    DuplicateHandle(GetCurrentProcess(), hStdErrFile, GetCurrentProcess(), &hInheritableStdErr, 0, TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
-
-    // Create process
-    {
-        PROCESS_INFORMATION procinfo;
-        STARTUPINFO startupinfo;
-        ZeroMemory(&procinfo, sizeof(procinfo));
-        ZeroMemory(&startupinfo, sizeof(startupinfo));
-        startupinfo.cb = sizeof(startupinfo);
-        //startupinfo.lpReserved = 0;
-        //startupinfo.lpDesktop = 0;
-        //startupinfo.lpTitle = 0;
-        startupinfo.dwFlags = STARTF_USESTDHANDLES;
-        startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
-        startupinfo.hStdOutput = pipe_write;
-        startupinfo.hStdError = hInheritableStdErr;
-
-        if ( !CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &startupinfo, &procinfo) ) {
-            errno = translate_error(GetLastError());
-            return false;
-        }
-        CloseHandle(procinfo.hThread);
-        CloseHandle(procinfo.hProcess);
-    }
-
-    // Close our copy of the write handle
-    CloseHandle(hInheritableStdErr);
-    CloseHandle(pipe_write);
+bool Script::cancelProcessing (void) {
+    _canceled = true;
+    _main_loop->quit();
+    Glib::spawn_close_pid(_pid);
 
     return true;
 }
 
-bool pipe_t::close() {
-    BOOL retval = CloseHandle(hpipe);
-    if ( !retval ) {
-        errno = translate_error(GetLastError());
-    }
-    return retval != FALSE;
-}
 
-size_t pipe_t::read(void *buffer, size_t size) {
-    DWORD bytes_read = 0;
-    ReadFile(hpipe, buffer, size, &bytes_read, 0);
-    return bytes_read;
-}
+/** \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.
 
-size_t pipe_t::write(void const *buffer, size_t size) {
-    DWORD bytes_written = 0;
-    WriteFile(hpipe, buffer, size, &bytes_written, 0);
-    return bytes_written;
-}
+    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 is 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 fread and 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 fread is stored so the exact number of bytes
+    is handled gracefully.
 
-int pipe_t::translate_error(DWORD err) {
-    switch (err) {
-        case ERROR_FILE_NOT_FOUND:
-            return ENOENT;
-        case ERROR_INVALID_HANDLE:
-        case ERROR_INVALID_PARAMETER:
-            return EINVAL;
-        default:
-            return 0;
+    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);
+    // printf("Executing\n");
+
+    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 = "";
+
+    // Use Glib::find_program_in_path instead of the equivalent
+    // Glib::spawn_* functionality, because _wspawnp is broken on Windows:
+    // it doesn't work when PATH contains Unicode directories
+    if (!Glib::path_is_absolute(program)) {
+        program = Glib::find_program_in_path(program);
+    }
+    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);
+        #ifdef G_OS_WIN32
+        // ANNOYING: glibmm does not wrap g_win32_locale_filename_from_utf8
+        gchar *workdir_s = g_win32_locale_filename_from_utf8(working_directory.data());
+        working_directory = workdir_s;
+        g_free(workdir_s);
+        #endif
+
+        argv.push_back(script);
+    }
+
+    // assemble the rest of argv
+    std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
+    if (!filein.empty()) {
+        argv.push_back(filein);
+    }
+
+    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
+                                     NULL,           // STDIN
+                                     &stdout_pipe,   // STDOUT
+                                     &stderr_pipe);  // STDERR
+    } catch (Glib::Error e) {
+        printf("Can't Spawn!!! spawn returns: %s\n", e.what().data());
+        return 0;
     }
-}
 
-#else // Win32
+    _main_loop = Glib::MainLoop::create(false);
 
-bool pipe_t::open(char *command, char const *errorFile, int mode_p) {
-    char popen_mode[4] = {0,0,0,0};
-    char *popen_mode_cur = popen_mode;
+    file_listener fileerr;
+    fileout.init(stdout_pipe, _main_loop);
+    fileerr.init(stderr_pipe, _main_loop);
 
-    if ( (mode_p & mode_read) != 0 ) {
-        *popen_mode_cur++ = 'r';
-    }
+    _canceled = false;
+    _main_loop->run();
 
-    if ( (mode_p & mode_write) != 0 ) {
-        *popen_mode_cur++ = 'w';
+    // 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);
     }
 
-    /* Get the commandline to be run */
-    if (errorFile != NULL) {
-        char * temp;
-        temp = g_strdup_printf("%s 2> %s", command, errorFile);
-        ppipe = popen(temp, popen_mode);
-        g_free(temp);
-    } else
-        ppipe = popen(command, popen_mode);
-
-    return ppipe != NULL;
-}
+    if (_canceled) {
+        // std::cout << "Script Canceled" << std::endl;
+        return 0;
+    }
 
-bool pipe_t::close() {
-    return fclose(ppipe) == 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."));
+    }
 
-size_t pipe_t::read(void *buffer, size_t size) {
-    return fread(buffer, 1, size, ppipe);
-}
+    Glib::ustring stdout_data = fileout.string();
+    if (stdout_data.length() == 0) {
+        return 0;
+    }
 
-size_t pipe_t::write(void const *buffer, size_t size) {
-    return fwrite(buffer, 1, size, ppipe);
+    // std::cout << "Finishing Execution." << std::endl;
+    return stdout_data.length();
 }
 
-#endif // (Non-)Win32
 
 
-}  /* Inkscape  */
-}  /* module  */
-}  /* Implementation  */
 
+}  // namespace Implementation
+}  // namespace Extension
+}  // namespace Inkscape
 
 /*
   Local Variables:
@@ -1050,4 +1015,4 @@ size_t pipe_t::write(void const *buffer, size_t size) {
   fill-column:99
   End:
 */
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :