Code

Allow Inkscape to run from Unicode directories on Windows
[inkscape.git] / src / extension / implementation / script.cpp
1 /** \file
2  * Code for handling extensions (i.e.\ scripts).
3  */
4 /*
5  * Authors:
6  *   Bryce Harrington <bryce@osdl.org>
7  *   Ted Gould <ted@gould.cx>
8  *   Jon A. Cruz <jon@joncruz.org>
9  *
10  * Copyright (C) 2002-2005,2007 Authors
11  *
12  * Released under GNU GPL, read the file 'COPYING' for more information
13  */
15 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
21 #include <unistd.h>
23 #include <errno.h>
24 #include <glib.h>
25 #include <glib/gstdio.h>
26 #include <gtkmm.h>
28 #include "ui/view/view.h"
29 #include "desktop-handles.h"
30 #include "desktop.h"
31 #include "selection.h"
32 #include "sp-namedview.h"
33 #include "io/sys.h"
34 #include "preferences.h"
35 #include "../system.h"
36 #include "extension/effect.h"
37 #include "extension/output.h"
38 #include "extension/input.h"
39 #include "extension/db.h"
40 #include "script.h"
41 #include "dialogs/dialog-events.h"
42 #include "application/application.h"
43 #include "xml/node.h"
44 #include "xml/attribute-record.h"
46 #include "util/glib-list-iterators.h"
50 #ifdef WIN32
51 #include <windows.h>
52 #include <sys/stat.h>
53 #include "registrytool.h"
54 #endif
58 /** This is the command buffer that gets allocated from the stack */
59 #define BUFSIZE (255)
63 /* Namespaces */
64 namespace Inkscape {
65 namespace Extension {
66 namespace Implementation {
68 /** \brief  Make GTK+ events continue to come through a little bit
70     This just keeps coming the events through so that we'll make the GUI
71     update and look pretty.
72 */
73 void Script::pump_events (void) {
74     while ( Gtk::Main::events_pending() ) {
75         Gtk::Main::iteration();
76     }
77     return;
78 }
81 /** \brief  A table of what interpreters to call for a given language
83     This table is used to keep track of all the programs to execute a
84     given script.  It also tracks the preference to use to overwrite
85     the given interpreter to a custom one per user.
86 */
87 Script::interpreter_t const Script::interpreterTab[] = {
88         {"perl",   "perl-interpreter",   "perl"   },
89 #ifdef WIN32
90         {"python", "python-interpreter", "pythonw" },
91 #else
92         {"python", "python-interpreter", "python" },
93 #endif
94         {"ruby",   "ruby-interpreter",   "ruby"   },
95         {"shell",  "shell-interpreter",  "sh"     },
96         { NULL,    NULL,                  NULL    }
97 };
101 /** \brief Look up an interpreter name, and translate to something that
102     is executable
103     \param interpNameArg  The name of the interpreter that we're looking
104     for, should be an entry in interpreterTab
105 */
106 std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
108     interpreter_t const *interp = 0;
109     bool foundInterp = false;
110     for (interp =  interpreterTab ; interp->identity ; interp++ ){
111         if (interpNameArg == interp->identity) {
112             foundInterp = true;
113             break;
114         }
115     }
117     // Do we have a supported interpreter type?
118     if (!foundInterp) {
119         return "";
120     }
121     std::string interpreter_path = Glib::filename_from_utf8(interp->defaultval);
123     // 1.  Check preferences for an override.
124     // Note: this must be an absolute path.
125     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
126     Glib::ustring prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->prefstring));
128     if (!prefInterp.empty()) {
129         interpreter_path = Glib::filename_from_utf8(prefInterp);
130     }
132     // 2. Search the path.
133     // Do this on all systems, for consistency.
134     // PATH is set up to contain the Python and Perl binary directories
135     // on Windows, so no extra code is necessary.
136     if (!Glib::path_is_absolute(interpreter_path)) {
137         interpreter_path = Glib::find_program_in_path(interpreter_path);
138     }
139     printf("Interpreter name: %s\n", interpreter_path.data());
140     return interpreter_path;
143 /** \brief     This function creates a script object and sets up the
144                variables.
145     \return    A script object
147    This function just sets the command to NULL.  It should get built
148    officially in the load function.  This allows for less allocation
149    of memory in the unloaded state.
150 */
151 Script::Script() :
152     Implementation()
156 /**
157  *   brief     Destructor
158  */
159 Script::~Script()
165 /**
166     \return    A string with the complete string with the relative directory expanded
167     \brief     This function takes in a Repr that contains a reldir entry
168                and returns that data with the relative directory expanded.
169                Mostly it is here so that relative directories all get used
170                the same way.
171     \param     reprin   The Inkscape::XML::Node with the reldir in it.
173     Basically this function looks at an attribute of the Repr, and makes
174     a decision based on that.  Currently, it is only working with the
175     'extensions' relative directory, but there will be more of them.
176     One thing to notice is that this function always returns an allocated
177     string.  This means that the caller of this function can always
178     free what they are given (and should do it too!).
179 */
180 std::string
181 Script::solve_reldir(Inkscape::XML::Node *reprin) {
183     gchar const *s = reprin->attribute("reldir");
185     // right now the only recognized relative directory is "extensions"
186     if (!s || Glib::ustring(s) != "extensions") {
187         Glib::ustring str = sp_repr_children(reprin)->content();
188         return str;
189     }
191     Glib::ustring reldir = s;
193     for (unsigned int i=0;
194         i < Inkscape::Extension::Extension::search_path.size();
195         i++) {
197         gchar * fname = g_build_filename(
198            Inkscape::Extension::Extension::search_path[i],
199            sp_repr_children(reprin)->content(),
200            NULL);
201         Glib::ustring filename = fname;
202         g_free(fname);
204         if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) ) {
205             return Glib::filename_from_utf8(filename);
206         }
207     }
209     return "";
214 /**
215     \return   Whether the command given exists, including in the path
216     \brief    This function is used to find out if something exists for
217               the check command.  It can look in the path if required.
218     \param    command   The command or file that should be looked for
220     The first thing that this function does is check to see if the
221     incoming file name has a directory delimiter in it.  This would
222     mean that it wants to control the directories, and should be
223     used directly.
225     If not, the path is used.  Each entry in the path is stepped through,
226     attached to the string, and then tested.  If the file is found
227     then a TRUE is returned.  If we get all the way through the path
228     then a FALSE is returned, the command could not be found.
229 */
230 bool Script::check_existence(const std::string &command)
233     // Check the simple case first
234     if (command.empty()) {
235         return false;
236     }
238     //Don't search when it is an absolute path. */
239     if (!Glib::path_is_absolute(command)) {
240         if (Glib::file_test(command, Glib::FILE_TEST_EXISTS)) {
241             return true;
242         } else {
243             return false;
244         }
245     }
247     std::string path = Glib::getenv("PATH");
248     if (path.empty()) {
249        /* There is no `PATH' in the environment.
250            The default search path is the current directory */
251         path = G_SEARCHPATH_SEPARATOR_S;
252     }
254     std::string::size_type pos  = 0;
255     std::string::size_type pos2 = 0;
256     while ( pos < path.size() ) {
258         std::string localPath;
260         pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
261         if (pos2 == path.npos) {
262             localPath = path.substr(pos);
263             pos = path.size();
264         } else {
265             localPath = path.substr(pos, pos2-pos);
266             pos = pos2+1;
267         }
269         //printf("### %s\n", localPath.c_str());
270         std::string candidatePath =
271                       Glib::build_filename(localPath, command);
273         if (Glib::file_test(candidatePath,
274                       Glib::FILE_TEST_EXISTS)) {
275             return true;
276         }
278     }
280     return false;
287 /**
288     \return   none
289     \brief    This function 'loads' an extention, basically it determines
290               the full command for the extention and stores that.
291     \param    module  The extention to be loaded.
293     The most difficult part about this function is finding the actual
294     command through all of the Reprs.  Basically it is hidden down a
295     couple of layers, and so the code has to move down too.  When
296     the command is actually found, it has its relative directory
297     solved.
299     At that point all of the loops are exited, and there is an
300     if statement to make sure they didn't exit because of not finding
301     the command.  If that's the case, the extention doesn't get loaded
302     and should error out at a higher level.
303 */
305 bool Script::load(Inkscape::Extension::Extension *module)
307     if (module->loaded()) {
308         return true;
309     }
311     helper_extension = "";
313     /* This should probably check to find the executable... */
314     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
315     while (child_repr != NULL) {
316         if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
317             child_repr = sp_repr_children(child_repr);
318             while (child_repr != NULL) {
319                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
320                     const gchar *interpretstr = child_repr->attribute("interpreter");
321                     if (interpretstr != NULL) {
322                         std::string interpString = resolveInterpreterExecutable(interpretstr);
323                         command.insert(command.end(), interpString);
324                     }
325                     command.insert(command.end(), solve_reldir(child_repr));
326                 }
327                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
328                     helper_extension = sp_repr_children(child_repr)->content();
329                 }
330                 child_repr = sp_repr_next(child_repr);
331             }
333             break;
334         }
335         child_repr = sp_repr_next(child_repr);
336     }
338     //g_return_val_if_fail(command.length() > 0, false);
340     return true;
344 /**
345     \return   None.
346     \brief    Unload this puppy!
347     \param    module  Extension to be unloaded.
349     This function just sets the module to unloaded.  It free's the
350     command if it has been allocated.
351 */
352 void Script::unload(Inkscape::Extension::Extension */*module*/)
354     command.clear();
355     helper_extension = "";
361 /**
362     \return   Whether the check passed or not
363     \brief    Check every dependency that was given to make sure we should keep this extension
364     \param    module  The Extension in question
366 */
367 bool
368 Script::check(Inkscape::Extension::Extension *module)
370     int script_count = 0;
371     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
372     while (child_repr != NULL) {
373         if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
374             script_count++;
375             child_repr = sp_repr_children(child_repr);
376             while (child_repr != NULL) {
377                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
378                     std::string command_text = solve_reldir(child_repr);
379                     if (!command_text.empty()) {
380                         /* I've got the command */
381                         bool existance = check_existence(command_text);
382                         if (!existance)
383                             return false;
384                     }
385                 }
387                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
388                     gchar const *helper = sp_repr_children(child_repr)->content();
389                     if (Inkscape::Extension::db.get(helper) == NULL) {
390                         return false;
391                     }
392                 }
394                 child_repr = sp_repr_next(child_repr);
395             }
397             break;
398         }
399         child_repr = sp_repr_next(child_repr);
400     }
402     if (script_count == 0) {
403         return false;
404     }
406     return true;
409 class ScriptDocCache : public ImplementationDocumentCache {
410     friend class Script;
411 protected:
412     std::string _filename;
413     int _tempfd;
414 public:
415     ScriptDocCache (Inkscape::UI::View::View * view);
416     ~ScriptDocCache ( );
417 };
419 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
420     ImplementationDocumentCache(view),
421     _filename(""),
422     _tempfd(0)
424     try {
425         _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
426     } catch (...) {
427         /// \todo Popup dialog here
428         return;
429     }
431     SPDesktop *desktop = (SPDesktop *) view;
432     sp_namedview_document_from_window(desktop);
434     Inkscape::Extension::save(
435               Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
436               view->doc(), _filename.c_str(), false, false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
438     return;
441 ScriptDocCache::~ScriptDocCache ( )
443     close(_tempfd);
444     unlink(_filename.c_str());
447 ImplementationDocumentCache *Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
448     return new ScriptDocCache(view);
452 /**
453     \return   A dialog for preferences
454     \brief    A stub funtion right now
455     \param    module    Module who's preferences need getting
456     \param    filename  Hey, the file you're getting might be important
458     This function should really do something, right now it doesn't.
459 */
460 Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module,
461                     const gchar */*filename*/)
463     return module->autogui(NULL, NULL);
468 /**
469     \return   A dialog for preferences
470     \brief    A stub funtion right now
471     \param    module    Module whose preferences need getting
473     This function should really do something, right now it doesn't.
474 */
475 Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
477     return module->autogui(NULL, NULL);
480 /**
481     \return  A new document that has been opened
482     \brief   This function uses a filename that is put in, and calls
483              the extension's command to create an SVG file which is
484              returned.
485     \param   module   Extension to use.
486     \param   filename File to open.
488     First things first, this function needs a temporary file name.  To
489     create on of those the function g_file_open_tmp is used with
490     the header of ink_ext_.
492     The extension is then executed using the 'execute' function
493     with the filname coming in, and the temporary filename.  After
494     That executing, the SVG should be in the temporary file.
496     Finally, the temporary file is opened using the SVG input module and
497     a document is returned.  That document has its filename set to
498     the incoming filename (so that it's not the temporary filename).
499     That document is then returned from this function.
500 */
501 SPDocument *Script::open(Inkscape::Extension::Input *module,
502              const gchar *filenameArg)
504     std::list<std::string> params;
505     module->paramListString(params);
507     std::string tempfilename_out;
508     int tempfd_out = 0;
509     try {
510         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
511     } catch (...) {
512         /// \todo Popup dialog here
513         return NULL;
514     }
516     std::string lfilename = Glib::filename_from_utf8(filenameArg);
518     file_listener fileout;
519     int data_read = execute(command, params, lfilename, fileout);
520     fileout.toFile(tempfilename_out);
522     SPDocument * mydoc = NULL;
523     if (data_read > 10) {
524         if (helper_extension.size()==0) {
525             mydoc = Inkscape::Extension::open(
526                   Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
527                   tempfilename_out.c_str());
528         } else {
529             mydoc = Inkscape::Extension::open(
530                   Inkscape::Extension::db.get(helper_extension.c_str()),
531                   tempfilename_out.c_str());
532         }
533     } // data_read
535     if (mydoc != NULL) {
536         g_free(mydoc->base);
537         mydoc->base = NULL;
538         sp_document_change_uri_and_hrefs(mydoc, filenameArg);
539     }
541     // make sure we don't leak file descriptors from g_file_open_tmp
542     close(tempfd_out);
544     unlink(tempfilename_out.c_str());
546     return mydoc;
547 } // open
551 /**
552     \return   none
553     \brief    This function uses an extention to save a document.  It first
554               creates an SVG file of the document, and then runs it through
555               the script.
556     \param    module    Extention to be used
557     \param    doc       Document to be saved
558     \param    filename  The name to save the final file as
559     \return   false in case of any failure writing the file, otherwise true
561     Well, at some point people need to save - it is really what makes
562     the entire application useful.  And, it is possible that someone
563     would want to use an extetion for this, so we need a function to
564     do that eh?
566     First things first, the document is saved to a temporary file that
567     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
568     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
569     end of the function.
571     After we have the SVG file, then extention_execute is called with
572     the temporary file name and the final output filename.  This should
573     put the output of the script into the final output file.  We then
574     delete the temporary file.
575 */
576 void Script::save(Inkscape::Extension::Output *module,
577              SPDocument *doc,
578              const gchar *filenameArg)
580     std::list<std::string> params;
581     module->paramListString(params);
583     std::string tempfilename_in;
584     int tempfd_in = 0;
585     try {
586         tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
587     } catch (...) {
588         /// \todo Popup dialog here
589         throw Inkscape::Extension::Output::save_failed();
590     }
592     if (helper_extension.size() == 0) {
593         Inkscape::Extension::save(
594                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
595                    doc, tempfilename_in.c_str(), false, false, false,
596                    Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
597     } else {
598         Inkscape::Extension::save(
599                    Inkscape::Extension::db.get(helper_extension.c_str()),
600                    doc, tempfilename_in.c_str(), false, false, false,
601                    Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
602     }
605     file_listener fileout;
606     execute(command, params, tempfilename_in, fileout);
608     std::string lfilename = Glib::filename_from_utf8(filenameArg);
609     bool success = fileout.toFile(lfilename);
611     // make sure we don't leak file descriptors from g_file_open_tmp
612     close(tempfd_in);
613     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
614     unlink(tempfilename_in.c_str());
616     if (success == false) {
617         throw Inkscape::Extension::Output::save_failed();
618     }
620     return;
625 /**
626     \return    none
627     \brief     This function uses an extention as a effect on a document.
628     \param     module   Extention to effect with.
629     \param     doc      Document to run through the effect.
631     This function is a little bit trickier than the previous two.  It
632     needs two temporary files to get it's work done.  Both of these
633     files have random names created for them using the g_file_open_temp function
634     with the ink_ext_ prefix in the temporary directory.  Like the other
635     functions, the temporary files are deleted at the end.
637     To save/load the two temporary documents (both are SVG) the internal
638     modules for SVG load and save are used.  They are both used through
639     the module system function by passing their keys into the functions.
641     The command itself is built a little bit differently than in other
642     functions because the effect support selections.  So on the command
643     line a list of all the ids that are selected is included.  Currently,
644     this only works for a single selected object, but there will be more.
645     The command string is filled with the data, and then after the execution
646     it is freed.
648     The execute function is used at the core of this function
649     to execute the Script on the two SVG documents (actually only one
650     exists at the time, the other is created by that script).  At that
651     point both should be full, and the second one is loaded.
652 */
653 void Script::effect(Inkscape::Extension::Effect *module,
654                Inkscape::UI::View::View *doc,
655                ImplementationDocumentCache * docCache)
657     if (docCache == NULL) {
658         docCache = newDocCache(module, doc);
659     }
660     ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
661     if (dc == NULL) {
662         printf("TOO BAD TO LIVE!!!");
663         exit(1);
664     }
666     SPDesktop *desktop = (SPDesktop *)doc;
667     sp_namedview_document_from_window(desktop);
669     std::list<std::string> params;
670     module->paramListString(params);
672     if (module->no_doc) {
673         // this is a no-doc extension, e.g. a Help menu command;
674         // just run the command without any files, ignoring errors
676         Glib::ustring empty;
677         file_listener outfile;
678         execute(command, params, empty, outfile);
680         return;
681     }
683     std::string tempfilename_out;
684     int tempfd_out = 0;
685     try {
686         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
687     } catch (...) {
688         /// \todo Popup dialog here
689         return;
690     }
692     if (desktop != NULL) {
693         Inkscape::Util::GSListConstIterator<SPItem *> selected =
694              sp_desktop_selection(desktop)->itemList();
695         while ( selected != NULL ) {
696             Glib::ustring selected_id;
697             selected_id += "--id=";
698             selected_id += (*selected)->getId();
699             params.insert(params.begin(), selected_id);
700             ++selected;
701         }
702     }
704     file_listener fileout;
705     int data_read = execute(command, params, dc->_filename, fileout);
706     fileout.toFile(tempfilename_out);
708     pump_events();
710     SPDocument * mydoc = NULL;
711     if (data_read > 10) {
712         mydoc = Inkscape::Extension::open(
713               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
714               tempfilename_out.c_str());
715     } // data_read
717     pump_events();
719     // make sure we don't leak file descriptors from g_file_open_tmp
720     close(tempfd_out);
722     g_unlink(tempfilename_out.c_str());
724     /* Do something with mydoc.... */
725     if (mydoc) {
726         doc->doc()->emitReconstructionStart();
727         copy_doc(doc->doc()->rroot, mydoc->rroot);
728         doc->doc()->emitReconstructionFinish();
729         mydoc->release();
730         sp_namedview_update_layers_from_document(desktop);
731     }
733     return;
738 /**
739     \brief  A function to take all the svg elements from one document
740             and put them in another.
741     \param  oldroot  The root node of the document to be replaced
742     \param  newroot  The root node of the document to replace it with
744     This function first deletes all of the data in the old document.  It
745     does this by creating a list of what needs to be deleted, and then
746     goes through the list.  This two pass approach removes issues with
747     the list being change while parsing through it.  Lots of nasty bugs.
749     Then, it goes through the new document, duplicating all of the
750     elements and putting them into the old document.  The copy
751     is then complete.
752 */
753 void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
755     std::vector<Inkscape::XML::Node *> delete_list;
756     Inkscape::XML::Node * oldroot_namedview = NULL;
758     for (Inkscape::XML::Node * child = oldroot->firstChild();
759             child != NULL;
760             child = child->next()) {
761         if (!strcmp("sodipodi:namedview", child->name())) {
762             oldroot_namedview = child;
763             for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
764                     oldroot_namedview_child != NULL;
765                     oldroot_namedview_child = oldroot_namedview_child->next()) {
766                 delete_list.push_back(oldroot_namedview_child);
767             }
768         } else {
769             delete_list.push_back(child);
770         }
771     }
772     for (unsigned int i = 0; i < delete_list.size(); i++) {
773         sp_repr_unparent(delete_list[i]);
774     }
776     for (Inkscape::XML::Node * child = newroot->firstChild();
777             child != NULL;
778             child = child->next()) {
779         if (!strcmp("sodipodi:namedview", child->name())) {
780             if (oldroot_namedview != NULL) {
781                 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
782                         newroot_namedview_child != NULL;
783                         newroot_namedview_child = newroot_namedview_child->next()) {
784                     oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
785                 }
786             }
787         } else {
788             oldroot->appendChild(child->duplicate(oldroot->document()));
789         }
790     }
792     {
793         using Inkscape::Util::List;
794         using Inkscape::XML::AttributeRecord;
795         std::vector<gchar const *> attribs;
797         // Make a list of all attributes of the old root node.
798         for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
799             attribs.push_back(g_quark_to_string(iter->key));
800         }
802         // Delete the attributes of the old root nodes.
803         for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++) {
804             oldroot->setAttribute(*it, NULL);
805         }
807         // Set the new attributes.
808         for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) {
809             gchar const *name = g_quark_to_string(iter->key);
810             oldroot->setAttribute(name, newroot->attribute(name));
811         }
812     }
814     /** \todo  Restore correct layer */
815     /** \todo  Restore correct selection */
818 /**  \brief  This function checks the stderr file, and if it has data,
819              shows it in a warning dialog to the user
820      \param  filename  Filename of the stderr file
821 */
822 void Script::checkStderr (const Glib::ustring &data,
823                            Gtk::MessageType type,
824                      const Glib::ustring &message)
826     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
827     warning.set_resizable(true);
828     GtkWidget *dlg = GTK_WIDGET(warning.gobj());
829     sp_transientize(dlg);
831     Gtk::VBox * vbox = warning.get_vbox();
833     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
834     Gtk::TextView * textview = new Gtk::TextView();
835     textview->set_editable(false);
836     textview->set_wrap_mode(Gtk::WRAP_WORD);
837     textview->show();
839     textview->get_buffer()->set_text(data.c_str());
841     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
842     scrollwindow->add(*textview);
843     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
844     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
845     scrollwindow->show();
847     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
849     warning.run();
851     return;
854 bool Script::cancelProcessing (void) {
855     _canceled = true;
856     _main_loop->quit();
857     Glib::spawn_close_pid(_pid);
859     return true;
863 /** \brief    This is the core of the extension file as it actually does
864               the execution of the extension.
865     \param    in_command  The command to be executed
866     \param    filein      Filename coming in
867     \param    fileout     Filename of the out file
868     \return   Number of bytes that were read into the output file.
870     The first thing that this function does is build the command to be
871     executed.  This consists of the first string (in_command) and then
872     the filename for input (filein).  This file is put on the command
873     line.
875     The next thing is that this function does is open a pipe to the
876     command and get the file handle in the ppipe variable.  It then
877     opens the output file with the output file handle.  Both of these
878     operations are checked extensively for errors.
880     After both are opened, then the data is copied from the output
881     of the pipe into the file out using fread and fwrite.  These two
882     functions are used because of their primitive nature they make
883     no assumptions about the data.  A buffer is used in the transfer,
884     but the output of fread is stored so the exact number of bytes
885     is handled gracefully.
887     At the very end (after the data has been copied) both of the files
888     are closed, and we return to what we were doing.
889 */
890 int Script::execute (const std::list<std::string> &in_command,
891                  const std::list<std::string> &in_params,
892                  const Glib::ustring &filein,
893                  file_listener &fileout)
895     g_return_val_if_fail(!in_command.empty(), 0);
896     // printf("Executing\n");
898     std::vector<std::string> argv;
900     bool interpreted = (in_command.size() == 2);
901     std::string program = in_command.front();
902     std::string script = interpreted ? in_command.back() : "";
903     std::string working_directory = "";
905     // Use Glib::find_program_in_path instead of the equivalent
906     // Glib::spawn_* functionality, because _wspawnp is broken on Windows:
907     // it doesn't work when PATH contains Unicode directories
908     if (!Glib::path_is_absolute(program)) {
909         program = Glib::find_program_in_path(program);
910     }
911     argv.push_back(program);
913     if (interpreted) {
914         // On Windows, Python garbles Unicode command line parameters
915         // in an useless way. This means extensions fail when Inkscape
916         // is run from an Unicode directory.
917         // As a workaround, we set the working directory to the one
918         // containing the script.
919         working_directory = Glib::path_get_dirname(script);
920         script = Glib::path_get_basename(script);
921         #ifdef G_OS_WIN32
922         // ANNOYING: glibmm does not wrap g_win32_locale_filename_from_utf8
923         gchar *workdir_s = g_win32_locale_filename_from_utf8(working_directory.data());
924         working_directory = workdir_s;
925         g_free(workdir_s);
926         #endif
928         argv.push_back(script);
929     }
931     // assemble the rest of argv
932     std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
933     if (!filein.empty()) {
934         argv.push_back(filein);
935     }
937     int stdout_pipe, stderr_pipe;
939     try {
940         Glib::spawn_async_with_pipes(working_directory, // working directory
941                                      argv,  // arg v
942                                      static_cast<Glib::SpawnFlags>(0), // no flags
943                                      sigc::slot<void>(),
944                                      &_pid,          // Pid
945                                      NULL,           // STDIN
946                                      &stdout_pipe,   // STDOUT
947                                      &stderr_pipe);  // STDERR
948     } catch (Glib::Error e) {
949         printf("Can't Spawn!!! spawn returns: %s\n", e.what().data());
950         return 0;
951     }
953     _main_loop = Glib::MainLoop::create(false);
955     file_listener fileerr;
956     fileout.init(stdout_pipe, _main_loop);
957     fileerr.init(stderr_pipe, _main_loop);
959     _canceled = false;
960     _main_loop->run();
962     // Ensure all the data is out of the pipe
963     while (!fileout.isDead()) {
964         fileout.read(Glib::IO_IN);
965     }
966     while (!fileerr.isDead()) {
967         fileerr.read(Glib::IO_IN);
968     }
970     if (_canceled) {
971         // std::cout << "Script Canceled" << std::endl;
972         return 0;
973     }
975     Glib::ustring stderr_data = fileerr.string();
976     if (stderr_data.length() != 0 &&
977         Inkscape::NSApplication::Application::getUseGui()
978        ) {
979         checkStderr(stderr_data, Gtk::MESSAGE_INFO,
980                                  _("Inkscape has received additional data from the script executed.  "
981                                    "The script did not return an error, but this may indicate the results will not be as expected."));
982     }
984     Glib::ustring stdout_data = fileout.string();
985     if (stdout_data.length() == 0) {
986         return 0;
987     }
989     // std::cout << "Finishing Execution." << std::endl;
990     return stdout_data.length();
996 }  // namespace Implementation
997 }  // namespace Extension
998 }  // namespace Inkscape
1000 /*
1001   Local Variables:
1002   mode:c++
1003   c-file-style:"stroustrup"
1004   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1005   indent-tabs-mode:nil
1006   fill-column:99
1007   End:
1008 */
1009 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :