Code

Extensions. Fix for Bug #668895 (Extensions with <check> tags fail to load).
[inkscape.git] / src / extension / implementation / script.cpp
index e4a7c57475cebb5f5d51ec43eff8bd9916cf09b3..428ee626fd361f79342dc7624d67f192e62da165 100644 (file)
@@ -5,29 +5,14 @@
  * Authors:
  *   Bryce Harrington <bryce@osdl.org>
  *   Ted Gould <ted@gould.cx>
+ *   Jon A. Cruz <jon@joncruz.org>
+ *   Abhishek Sharma
  *
  * Copyright (C) 2002-2005,2007 Authors
  *
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 
-/*
-TODO:
-FIXME:
-  After Inkscape makes a formal requirement for a GTK version above 2.11.4, please
-  replace all the instances of ink_ext_XXXXXX in this file that represent
-  svg files with ink_ext_XXXXXX.svg . Doing so will prevent errors in extensions
-  that call inkscape to manipulate the file.
-  
-  "** (inkscape:5848): WARNING **: Format autodetect failed. The file is being opened as SVG."
-  
-  references:
-  http://www.gtk.org/api/2.6/glib/glib-File-Utilities.html#g-mkstemp
-  http://ftp.gnome.org/pub/gnome/sources/glib/2.11/glib-2.11.4.changes
-  http://developer.gnome.org/doc/API/2.0/glib/glib-File-Utilities.html#g-mkstemp
-  
-  --Aaron Spike
-*/
 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
 
 #ifdef HAVE_CONFIG_H
@@ -37,14 +22,17 @@ FIXME:
 #include <unistd.h>
 
 #include <errno.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"
@@ -52,6 +40,9 @@ FIXME:
 #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"
 
@@ -75,14 +66,17 @@ namespace Inkscape {
 namespace Extension {
 namespace Implementation {
 
+/** \brief  Make GTK+ events continue to come through a little bit
 
-
-//Interpreter lookup table
-struct interpreter_t {
-        gchar * identity;
-        gchar * prefstring;
-        gchar * defaultval;
-};
+    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
@@ -91,9 +85,13 @@ struct interpreter_t {
     given script.  It also tracks the preference to use to overwrite
     the given interpreter to a custom one per user.
 */
-static interpreter_t interpreterTab[] = {
+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    }
@@ -101,139 +99,47 @@ static interpreter_t interpreterTab[] = {
 
 
 
-/**
- * Look up an interpreter name, and translate to something that
- * is executable
- */
-static Glib::ustring
-resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
+/** \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)
 {
-
-    Glib::ustring interpName = interpNameArg;
-
-    interpreter_t *interp;
+    interpreter_t const *interp = 0;
     bool foundInterp = false;
     for (interp =  interpreterTab ; interp->identity ; interp++ ){
-        if (interpName == interp->identity) {
+        if (interpNameArg == interp->identity) {
             foundInterp = true;
             break;
         }
     }
 
     // Do we have a supported interpreter type?
-    if (!foundInterp)
+    if (!foundInterp) {
         return "";
-    interpName = interp->defaultval;
-
-    // 1.  Check preferences
-    gchar *prefInterp = (gchar *)prefs_get_string_attribute(
-                                "extensions", interp->prefstring);
-
-    if (prefInterp) {
-        interpName = prefInterp;
-        return interpName;
     }
+    std::string interpreter_path = Glib::filename_from_utf8(interp->defaultval);
 
-#ifdef _WIN32
-
-    // 2.  Windows.  Try looking relative to inkscape.exe
-    RegistryTool rt;
-    Glib::ustring fullPath;
-    Glib::ustring path;
-    Glib::ustring exeName;
-    if (rt.getExeInfo(fullPath, path, exeName)) {
-        Glib::ustring interpPath = path;
-        interpPath.append("\\");
-        interpPath.append(interpName);
-        interpPath.append("\\");
-        interpPath.append(interpName);
-        interpPath.append(".exe");
-        struct stat finfo;
-        if (stat(interpPath .c_str(), &finfo) ==0) {
-            g_message("Found local interpreter, '%s',  Size: %d",
-                      interpPath .c_str(),
-                      (int)finfo.st_size);
-            return interpPath;
-        }                       
-    }
+    // 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));
 
-    // 3. Try searching the path
-    char szExePath[MAX_PATH];
-    char szCurrentDir[MAX_PATH];
-    GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
-    unsigned int ret = (unsigned int)FindExecutable(
-                  interpName.c_str(), szCurrentDir, szExePath);
-    if (ret > 32) {
-        interpName = szExePath;
-        return interpName;
+    if (!prefInterp.empty()) {
+        interpreter_path = Glib::filename_from_utf8(prefInterp);
     }
 
-#endif // win32
-
-
-    return interpName;
+    // 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;
 }
 
-
-class file_listener {
-    Glib::ustring _string;
-    sigc::connection _conn;
-    Glib::RefPtr<Glib::IOChannel> _channel;
-    Glib::RefPtr<Glib::MainLoop> _main_loop;
-    
-public:
-    file_listener () { };
-    ~file_listener () {
-        _conn.disconnect();
-    };
-
-    void init (int fd, Glib::RefPtr<Glib::MainLoop> main) {
-        _channel = Glib::IOChannel::create_from_fd(fd);
-        _channel->set_encoding();
-        _conn = Glib::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 read (Glib::IOCondition condition) {
-        if (condition != Glib::IO_IN) {
-            _main_loop->quit();
-            return false;
-        }
-
-        Glib::IOStatus status;
-        Glib::ustring out;
-        status = _channel->read_to_end(out);
-
-        if (status != Glib::IO_STATUS_NORMAL) {
-            _main_loop->quit();
-            return false;
-        }
-
-        _string += out;
-        return true;
-    };
-
-    // Note, doing a copy here, on purpose
-    Glib::ustring string (void) { return _string; };
-
-    void toFile (const Glib::ustring &name) {
-        Glib::RefPtr<Glib::IOChannel> stdout_file = Glib::IOChannel::create_from_file(name, "w");
-        stdout_file->write(_string);
-        return;
-    };
-};
-
-int execute (const std::list<std::string> &in_command,
-             const std::list<std::string> &in_params,
-             const Glib::ustring &filein,
-             file_listener &fileout);
-void checkStderr (const Glib::ustring &data,
-                        Gtk::MessageType type,
-                  const Glib::ustring &message);
-
-
 /** \brief     This function creates a script object and sets up the
                variables.
     \return    A script object
@@ -247,7 +153,6 @@ Script::Script() :
 {
 }
 
-
 /**
  *   brief     Destructor
  */
@@ -272,38 +177,33 @@ Script::~Script()
     string.  This means that the caller of this function can always
     free what they are given (and should do it too!).
 */
-Glib::ustring
+std::string
 Script::solve_reldir(Inkscape::XML::Node *reprin) {
 
     gchar const *s = reprin->attribute("reldir");
 
-    if (!s) {
+    // 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;
     }
 
     Glib::ustring reldir = s;
 
-    if (reldir == "extensions") {
-
-        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);
+    for (unsigned int i=0;
+        i < Inkscape::Extension::Extension::search_path.size();
+        i++) {
 
-            if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
-                return filename;
+        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 {
-        Glib::ustring str = sp_repr_children(reprin)->content();
-        return str;
     }
 
     return "";
@@ -327,38 +227,35 @@ 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(const Glib::ustring &command)
+bool Script::check_existence(const std::string &command)
 {
 
     // Check the simple case first
-    if (command.size() == 0) {
+    if (command.empty()) {
         return false;
     }
 
-    //Don't search when it contains a slash. */
-    if (command.find(G_DIR_SEPARATOR) != command.npos) {
-        if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
+    //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
+        } else {
             return false;
+        }
     }
 
-
-    Glib::ustring path; 
-    gchar *s = (gchar *) g_getenv("PATH");
-    if (s)
-        path = s;
-    else
+    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_SEARCHPATH_SEPARATOR_S;
+    }
 
     std::string::size_type pos  = 0;
     std::string::size_type pos2 = 0;
     while ( pos < path.size() ) {
 
-        Glib::ustring localPath;
+        std::string localPath;
 
         pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
         if (pos2 == path.npos) {
@@ -368,13 +265,13 @@ Script::check_existance(const Glib::ustring &command)
             localPath = path.substr(pos, pos2-pos);
             pos = pos2+1;
         }
-        
+
         //printf("### %s\n", localPath.c_str());
-        Glib::ustring candidatePath = 
+        std::string candidatePath =
                       Glib::build_filename(localPath, command);
 
-        if (Inkscape::IO::file_test(candidatePath .c_str(),
-                      G_FILE_TEST_EXISTS)) {
+        if (Glib::file_test(candidatePath,
+                      Glib::FILE_TEST_EXISTS)) {
             return true;
         }
 
@@ -405,31 +302,29 @@ Script::check_existance(const Glib::ustring &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;
+    if (module->loaded()) {
+        return true;
+    }
 
     helper_extension = "";
 
     /* This should probably check to find the executable... */
     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")) {
             child_repr = sp_repr_children(child_repr);
             while (child_repr != NULL) {
-                if (!strcmp(child_repr->name(), "command")) {
+                if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
                     const gchar *interpretstr = child_repr->attribute("interpreter");
                     if (interpretstr != NULL) {
-                        Glib::ustring interpString =
-                            resolveInterpreterExecutable(interpretstr);
-                        command.insert(command.end(), interpretstr);
+                        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")) {
+                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);
@@ -440,7 +335,7 @@ Script::load(Inkscape::Extension::Extension *module)
         child_repr = sp_repr_next(child_repr);
     }
 
-    //g_return_val_if_fail(command.length() > 0, FALSE);
+    //g_return_val_if_fail(command.length() > 0, false);
 
     return true;
 }
@@ -454,8 +349,7 @@ 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*/)
 {
     command.clear();
     helper_extension = "";
@@ -473,25 +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")) {
-                    Glib::ustring command_text = solve_reldir(child_repr);
-                    if (command_text.size() > 0) {
+                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 = check_existance(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;
                     }
                 }
 
@@ -503,39 +399,68 @@ Script::check(Inkscape::Extension::Extension *module)
         child_repr = sp_repr_next(child_repr);
     }
 
+    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 ( );
+};
 
+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;
+    }
 
-/**
-    \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
+    SPDesktop *desktop = (SPDesktop *) view;
+    sp_namedview_document_from_window(desktop);
 
-    This function should really do something, right now it doesn't.
-*/
-Gtk::Widget *
-Script::prefs_input(Inkscape::Extension::Input *module,
-                    const gchar *filename)
+    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(NULL, 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(NULL, NULL); 
+    return module->autogui(NULL, NULL);
 }
 
 
@@ -543,32 +468,15 @@ Script::prefs_output(Inkscape::Extension::Output *module)
 /**
     \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,
-                     sigc::signal<void> * changeSignal)
+Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
 {
-    SPDocument * current_document = view->doc();
-
-    using Inkscape::Util::GSListConstIterator;
-    GSListConstIterator<SPItem *> selected =
-           sp_desktop_selection((SPDesktop *)view)->itemList();
-    Inkscape::XML::Node * first_select = NULL;
-    if (selected != NULL) {
-        const SPItem * item = *selected;
-        first_select = SP_OBJECT_REPR(item);
-    }
-
-    return module->autogui(current_document, first_select, changeSignal);
+    return module->autogui(NULL, NULL);
 }
 
-
-
-
 /**
     \return  A new document that has been opened
     \brief   This function uses a filename that is put in, and calls
@@ -590,17 +498,16 @@ Script::prefs_effect(Inkscape::Extension::Effect *module,
     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,
+SPDocument *Script::open(Inkscape::Extension::Input *module,
              const gchar *filenameArg)
 {
     std::list<std::string> params;
+    module->paramListString(params);
 
-    // FIXME: process the GError instead of passing NULL
     std::string tempfilename_out;
     int tempfd_out = 0;
     try {
-        tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
+        tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
     } catch (...) {
         /// \todo Popup dialog here
         return NULL;
@@ -626,7 +533,8 @@ Script::open(Inkscape::Extension::Input *module,
     } // data_read
 
     if (mydoc != NULL) {
-        sp_document_set_uri(mydoc, filenameArg);
+        mydoc->setBase(0);
+        mydoc->changeUriAndHrefs(filenameArg);
     }
 
     // make sure we don't leak file descriptors from g_file_open_tmp
@@ -647,6 +555,7 @@ Script::open(Inkscape::Extension::Input *module,
     \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
@@ -663,65 +572,55 @@ Script::open(Inkscape::Extension::Input *module,
     put the output of the script into the final output file.  We then
     delete the temporary file.
 */
-void
-Script::save(Inkscape::Extension::Output *module,
+void Script::save(Inkscape::Extension::Output *module,
              SPDocument *doc,
              const gchar *filenameArg)
 {
-#if 0
-    Glib::ustring filename = filenameArg;
-
-    gchar *tmpname;
-    // FIXME: process the GError instead of passing NULL
-    gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
-    if (tempfd == -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);
-        }
-    }
+    std::list<std::string> params;
+    module->paramListString(params);
 
-    Glib::ustring tempfilename_in = tmpname;
-    g_free(tmpname);
+    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);
+                   doc, tempfilename_in.c_str(), false, 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, FALSE);
+                   doc, tempfilename_in.c_str(), false, false, false,
+                   Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
     }
 
-    gsize bytesRead = 0;
-    gsize bytesWritten = 0;
-    GError *error = NULL;
-    Glib::ustring local_filename =
-            g_filename_from_utf8( filename.c_str(), -1,
-                                 &bytesRead,  &bytesWritten, &error);
 
-    Glib::ustring local_command = command;
-    Glib::ustring paramString   = *module->paramString();
-    local_command.append(paramString);
-
-    execute(local_command, tempfilename_in, local_filename);
+    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 g_file_open_tmp
-    close(tempfd);
+    close(tempfd_in);
     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
     unlink(tempfilename_in.c_str());
-#endif
+
+    if (success == false) {
+        throw Inkscape::Extension::Output::save_failed();
+    }
+
+    return;
 }
 
 
@@ -754,15 +653,28 @@ Script::save(Inkscape::Extension::Output *module,
     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)
 {
+    if (docCache == NULL) {
+        docCache = newDocCache(module, doc);
+    }
+    ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
+    if (dc == NULL) {
+        printf("TOO BAD TO LIVE!!!");
+        exit(1);
+    }
+
+    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; 
+    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
-        module->paramListString(params);
 
         Glib::ustring empty;
         file_listener outfile;
@@ -771,73 +683,33 @@ Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *do
         return;
     }
 
-    gchar *tmpname;
-    // FIXME: process the GError instead of passing NULL
-    gint tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
-    if (tempfd_in == -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);
-        }
-    }
-
-    Glib::ustring tempfilename_in = tmpname;
-    g_free(tmpname);
-
-
-    // FIXME: process the GError instead of passing NULL
-    gint tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
-    if (tempfd_out == -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);
-        }
+    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;
     }
 
-    Glib::ustring tempfilename_out= tmpname;
-    g_free(tmpname);
-
-    SPDesktop *desktop = (SPDesktop *) doc;
-    sp_namedview_document_from_window(desktop);
-
-    Inkscape::Extension::save(
-              Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
-              doc->doc(), tempfilename_in.c_str(), FALSE, FALSE, FALSE);
-
     if (desktop != NULL) {
         Inkscape::Util::GSListConstIterator<SPItem *> selected =
              sp_desktop_selection(desktop)->itemList();
         while ( selected != NULL ) {
             Glib::ustring selected_id;
             selected_id += "--id=";
-            selected_id += SP_OBJECT_ID(*selected);
+            selected_id += (*selected)->getId();
             params.insert(params.begin(), selected_id);
             ++selected;
         }
     }
 
     file_listener fileout;
-    int data_read = execute(command, params, tempfilename_in, fileout);
+    int data_read = execute(command, params, dc->_filename, fileout);
     fileout.toFile(tempfilename_out);
 
+    pump_events();
+
     SPDocument * mydoc = NULL;
     if (data_read > 10) {
         mydoc = Inkscape::Extension::open(
@@ -845,13 +717,12 @@ Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *do
               tempfilename_out.c_str());
     } // data_read
 
+    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.c_str());
-    unlink(tempfilename_out.c_str());
+    g_unlink(tempfilename_out.c_str());
 
     /* Do something with mydoc.... */
     if (mydoc) {
@@ -882,26 +753,65 @@ 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;
-        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;
-        oldroot->appendChild(child->duplicate(newroot->document()));
+        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 {
+            oldroot->appendChild(child->duplicate(oldroot->document()));
+        }
+    }
+
+    {
+        using Inkscape::Util::List;
+        using Inkscape::XML::AttributeRecord;
+        std::vector<gchar const *> attribs;
+
+        // 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));
+        }
+
+        // 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);
+        }
+
+        // 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));
+        }
     }
 
     /** \todo  Restore correct layer */
@@ -912,10 +822,9 @@ Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
              shows it in a warning dialog to the user
      \param  filename  Filename of the stderr file
 */
-void
-checkStderr (const Glib::ustring &data,
-                   Gtk::MessageType type,
-             const Glib::ustring &message)
+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);
@@ -930,7 +839,13 @@ checkStderr (const Glib::ustring &data,
     textview->set_wrap_mode(Gtk::WRAP_WORD);
     textview->show();
 
-    textview->get_buffer()->set_text(data.c_str());
+    // 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);
@@ -945,6 +860,15 @@ checkStderr (const Glib::ustring &data,
     return;
 }
 
+bool Script::cancelProcessing (void) {
+    _canceled = true;
+    _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
@@ -972,65 +896,95 @@ checkStderr (const Glib::ustring &data,
     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
-execute (const std::list<std::string> &in_command,
-         const std::list<std::string> &in_params,
-         const Glib::ustring &filein,
-         file_listener &fileout)
+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.size() > 0, 0);
+    g_return_val_if_fail(!in_command.empty(), 0);
     // printf("Executing\n");
 
-    std::vector <std::string> argv;
+    std::vector<std::string> argv;
 
-    for (std::list<std::string>::const_iterator i = in_command.begin();
-            i != in_command.end(); i++) {
-        argv.push_back(*i);
-    }
+    bool interpreted = (in_command.size() == 2);
+    std::string program = in_command.front();
+    std::string script = interpreted ? in_command.back() : "";
+    std::string working_directory = "";
 
-    if (!(filein.empty())) {
-        argv.push_back(filein);
+    // 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);
     }
-
-    for (std::list<std::string>::const_iterator i = in_params.begin();
-            i != in_params.end(); i++) {
-        argv.push_back(*i);
+    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);
     }
 
-/*
-    for (std::vector<std::string>::const_iterator i = argv.begin();
-            i != argv.end(); i++) {
-        std::cout << *i << std::endl;
+    // assemble the rest of argv
+    std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
+    if (!filein.empty()) {
+        argv.push_back(filein);
     }
-*/
 
-    Glib::Pid pid;
     int stdout_pipe, stderr_pipe;
 
     try {
-        Glib::spawn_async_with_pipes(Glib::get_tmp_dir(), // working directory
+        Glib::spawn_async_with_pipes(working_directory, // working directory
                                      argv,  // arg v
-                                     Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
+                                     static_cast<Glib::SpawnFlags>(0), // no flags
                                      sigc::slot<void>(),
-                                     &pid,           // Pid
+                                     &_pid,          // Pid
                                      NULL,           // STDIN
                                      &stdout_pipe,   // STDOUT
                                      &stderr_pipe);  // STDERR
-    } catch (Glib::SpawnError e) {
-        printf("Can't Spawn!!! %d\n", e.code());
+    } catch (Glib::Error e) {
+        printf("Can't Spawn!!! spawn returns: %s\n", e.what().data());
         return 0;
     }
 
-    Glib::RefPtr<Glib::MainLoop> main_loop = Glib::MainLoop::create(false);
+    _main_loop = Glib::MainLoop::create(false);
 
     file_listener fileerr;
-    fileout.init(stdout_pipe, main_loop);
-    fileerr.init(stderr_pipe, main_loop);
+    fileout.init(stdout_pipe, _main_loop);
+    fileerr.init(stderr_pipe, _main_loop);
+
+    _canceled = false;
+    _main_loop->run();
 
-    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);
+    }
+
+    if (_canceled) {
+        // std::cout << "Script Canceled" << std::endl;
+        return 0;
+    }
 
     Glib::ustring stderr_data = fileerr.string();
-    if (stderr_data.length() != 0) {
+    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."));
@@ -1061,4 +1015,4 @@ execute (const std::list<std::string> &in_command,
   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 :