Code

bc143fd14ece526f129b0c68576dc9d107e8081e
[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  *   Abhishek Sharma
10  *
11  * Copyright (C) 2002-2005,2007 Authors
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
22 #include <unistd.h>
24 #include <errno.h>
25 #include <glib.h>
26 #include <glib/gstdio.h>
27 #include <gtkmm.h>
29 #include "ui/view/view.h"
30 #include "desktop-handles.h"
31 #include "desktop.h"
32 #include "selection.h"
33 #include "sp-namedview.h"
34 #include "io/sys.h"
35 #include "preferences.h"
36 #include "../system.h"
37 #include "extension/effect.h"
38 #include "extension/output.h"
39 #include "extension/input.h"
40 #include "extension/db.h"
41 #include "script.h"
42 #include "dialogs/dialog-events.h"
43 #include "inkscape.h"
44 #include "xml/node.h"
45 #include "xml/attribute-record.h"
47 #include "util/glib-list-iterators.h"
51 #ifdef WIN32
52 #include <windows.h>
53 #include <sys/stat.h>
54 #include "registrytool.h"
55 #endif
59 /** This is the command buffer that gets allocated from the stack */
60 #define BUFSIZE (255)
64 /* Namespaces */
65 namespace Inkscape {
66 namespace Extension {
67 namespace Implementation {
69 /** \brief  Make GTK+ events continue to come through a little bit
71     This just keeps coming the events through so that we'll make the GUI
72     update and look pretty.
73 */
74 void Script::pump_events (void) {
75     while ( Gtk::Main::events_pending() ) {
76         Gtk::Main::iteration();
77     }
78     return;
79 }
82 /** \brief  A table of what interpreters to call for a given language
84     This table is used to keep track of all the programs to execute a
85     given script.  It also tracks the preference to use to overwrite
86     the given interpreter to a custom one per user.
87 */
88 Script::interpreter_t const Script::interpreterTab[] = {
89         {"perl",   "perl-interpreter",   "perl"   },
90 #ifdef WIN32
91         {"python", "python-interpreter", "pythonw" },
92 #else
93         {"python", "python-interpreter", "python" },
94 #endif
95         {"ruby",   "ruby-interpreter",   "ruby"   },
96         {"shell",  "shell-interpreter",  "sh"     },
97         { NULL,    NULL,                  NULL    }
98 };
102 /** \brief Look up an interpreter name, and translate to something that
103     is executable
104     \param interpNameArg  The name of the interpreter that we're looking
105     for, should be an entry in interpreterTab
106 */
107 std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
109     interpreter_t const *interp = 0;
110     bool foundInterp = false;
111     for (interp =  interpreterTab ; interp->identity ; interp++ ){
112         if (interpNameArg == interp->identity) {
113             foundInterp = true;
114             break;
115         }
116     }
118     // Do we have a supported interpreter type?
119     if (!foundInterp) {
120         return "";
121     }
122     std::string interpreter_path = Glib::filename_from_utf8(interp->defaultval);
124     // 1.  Check preferences for an override.
125     // Note: this must be an absolute path.
126     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
127     Glib::ustring prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->prefstring));
129     if (!prefInterp.empty()) {
130         interpreter_path = Glib::filename_from_utf8(prefInterp);
131     }
133     // 2. Search the path.
134     // Do this on all systems, for consistency.
135     // PATH is set up to contain the Python and Perl binary directories
136     // on Windows, so no extra code is necessary.
137     if (!Glib::path_is_absolute(interpreter_path)) {
138         interpreter_path = Glib::find_program_in_path(interpreter_path);
139     }
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         mydoc->setBase(0);
537         mydoc->changeUriAndHrefs(filenameArg);
538     }
540     // make sure we don't leak file descriptors from g_file_open_tmp
541     close(tempfd_out);
543     unlink(tempfilename_out.c_str());
545     return mydoc;
546 } // open
550 /**
551     \return   none
552     \brief    This function uses an extention to save a document.  It first
553               creates an SVG file of the document, and then runs it through
554               the script.
555     \param    module    Extention to be used
556     \param    doc       Document to be saved
557     \param    filename  The name to save the final file as
558     \return   false in case of any failure writing the file, otherwise true
560     Well, at some point people need to save - it is really what makes
561     the entire application useful.  And, it is possible that someone
562     would want to use an extetion for this, so we need a function to
563     do that eh?
565     First things first, the document is saved to a temporary file that
566     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
567     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
568     end of the function.
570     After we have the SVG file, then extention_execute is called with
571     the temporary file name and the final output filename.  This should
572     put the output of the script into the final output file.  We then
573     delete the temporary file.
574 */
575 void Script::save(Inkscape::Extension::Output *module,
576              SPDocument *doc,
577              const gchar *filenameArg)
579     std::list<std::string> params;
580     module->paramListString(params);
582     std::string tempfilename_in;
583     int tempfd_in = 0;
584     try {
585         tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
586     } catch (...) {
587         /// \todo Popup dialog here
588         throw Inkscape::Extension::Output::save_failed();
589     }
591     if (helper_extension.size() == 0) {
592         Inkscape::Extension::save(
593                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
594                    doc, tempfilename_in.c_str(), false, false, false,
595                    Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
596     } else {
597         Inkscape::Extension::save(
598                    Inkscape::Extension::db.get(helper_extension.c_str()),
599                    doc, tempfilename_in.c_str(), false, false, false,
600                    Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
601     }
604     file_listener fileout;
605     int data_read = execute(command, params, tempfilename_in, fileout);
606     
607     bool success = false;
609     if (data_read > 0) {
610         std::string lfilename = Glib::filename_from_utf8(filenameArg);
611         success = fileout.toFile(lfilename);
612     }
614     // make sure we don't leak file descriptors from g_file_open_tmp
615     close(tempfd_in);
616     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
617     unlink(tempfilename_in.c_str());
619     if (success == false) {
620         throw Inkscape::Extension::Output::save_failed();
621     }
623     return;
628 /**
629     \return    none
630     \brief     This function uses an extention as a effect on a document.
631     \param     module   Extention to effect with.
632     \param     doc      Document to run through the effect.
634     This function is a little bit trickier than the previous two.  It
635     needs two temporary files to get it's work done.  Both of these
636     files have random names created for them using the g_file_open_temp function
637     with the ink_ext_ prefix in the temporary directory.  Like the other
638     functions, the temporary files are deleted at the end.
640     To save/load the two temporary documents (both are SVG) the internal
641     modules for SVG load and save are used.  They are both used through
642     the module system function by passing their keys into the functions.
644     The command itself is built a little bit differently than in other
645     functions because the effect support selections.  So on the command
646     line a list of all the ids that are selected is included.  Currently,
647     this only works for a single selected object, but there will be more.
648     The command string is filled with the data, and then after the execution
649     it is freed.
651     The execute function is used at the core of this function
652     to execute the Script on the two SVG documents (actually only one
653     exists at the time, the other is created by that script).  At that
654     point both should be full, and the second one is loaded.
655 */
656 void Script::effect(Inkscape::Extension::Effect *module,
657                Inkscape::UI::View::View *doc,
658                ImplementationDocumentCache * docCache)
660     if (docCache == NULL) {
661         docCache = newDocCache(module, doc);
662     }
663     ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
664     if (dc == NULL) {
665         printf("TOO BAD TO LIVE!!!");
666         exit(1);
667     }
669     SPDesktop *desktop = (SPDesktop *)doc;
670     sp_namedview_document_from_window(desktop);
672     std::list<std::string> params;
673     module->paramListString(params);
675     if (module->no_doc) {
676         // this is a no-doc extension, e.g. a Help menu command;
677         // just run the command without any files, ignoring errors
679         Glib::ustring empty;
680         file_listener outfile;
681         execute(command, params, empty, outfile);
683         return;
684     }
686     std::string tempfilename_out;
687     int tempfd_out = 0;
688     try {
689         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
690     } catch (...) {
691         /// \todo Popup dialog here
692         return;
693     }
695     if (desktop != NULL) {
696         Inkscape::Util::GSListConstIterator<SPItem *> selected =
697              sp_desktop_selection(desktop)->itemList();
698         while ( selected != NULL ) {
699             Glib::ustring selected_id;
700             selected_id += "--id=";
701             selected_id += (*selected)->getId();
702             params.insert(params.begin(), selected_id);
703             ++selected;
704         }
705     }
707     file_listener fileout;
708     int data_read = execute(command, params, dc->_filename, fileout);
709     fileout.toFile(tempfilename_out);
711     pump_events();
713     SPDocument * mydoc = NULL;
714     if (data_read > 10) {
715         mydoc = Inkscape::Extension::open(
716               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
717               tempfilename_out.c_str());
718     } // data_read
720     pump_events();
722     // make sure we don't leak file descriptors from g_file_open_tmp
723     close(tempfd_out);
725     g_unlink(tempfilename_out.c_str());
727     /* Do something with mydoc.... */
728     if (mydoc) {
729         doc->doc()->emitReconstructionStart();
730         copy_doc(doc->doc()->rroot, mydoc->rroot);
731         doc->doc()->emitReconstructionFinish();
732         mydoc->release();
733         sp_namedview_update_layers_from_document(desktop);
734     }
736     return;
741 /**
742     \brief  A function to take all the svg elements from one document
743             and put them in another.
744     \param  oldroot  The root node of the document to be replaced
745     \param  newroot  The root node of the document to replace it with
747     This function first deletes all of the data in the old document.  It
748     does this by creating a list of what needs to be deleted, and then
749     goes through the list.  This two pass approach removes issues with
750     the list being change while parsing through it.  Lots of nasty bugs.
752     Then, it goes through the new document, duplicating all of the
753     elements and putting them into the old document.  The copy
754     is then complete.
755 */
756 void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
758     std::vector<Inkscape::XML::Node *> delete_list;
759     Inkscape::XML::Node * oldroot_namedview = NULL;
761     for (Inkscape::XML::Node * child = oldroot->firstChild();
762             child != NULL;
763             child = child->next()) {
764         if (!strcmp("sodipodi:namedview", child->name())) {
765             oldroot_namedview = child;
766             for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
767                     oldroot_namedview_child != NULL;
768                     oldroot_namedview_child = oldroot_namedview_child->next()) {
769                 delete_list.push_back(oldroot_namedview_child);
770             }
771         } else {
772             delete_list.push_back(child);
773         }
774     }
775     for (unsigned int i = 0; i < delete_list.size(); i++) {
776         sp_repr_unparent(delete_list[i]);
777     }
779     for (Inkscape::XML::Node * child = newroot->firstChild();
780             child != NULL;
781             child = child->next()) {
782         if (!strcmp("sodipodi:namedview", child->name())) {
783             if (oldroot_namedview != NULL) {
784                 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
785                         newroot_namedview_child != NULL;
786                         newroot_namedview_child = newroot_namedview_child->next()) {
787                     oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
788                 }
789             }
790         } else {
791             oldroot->appendChild(child->duplicate(oldroot->document()));
792         }
793     }
795     {
796         using Inkscape::Util::List;
797         using Inkscape::XML::AttributeRecord;
798         std::vector<gchar const *> attribs;
800         // Make a list of all attributes of the old root node.
801         for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
802             attribs.push_back(g_quark_to_string(iter->key));
803         }
805         // Delete the attributes of the old root nodes.
806         for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++) {
807             oldroot->setAttribute(*it, NULL);
808         }
810         // Set the new attributes.
811         for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) {
812             gchar const *name = g_quark_to_string(iter->key);
813             oldroot->setAttribute(name, newroot->attribute(name));
814         }
815     }
817     /** \todo  Restore correct layer */
818     /** \todo  Restore correct selection */
821 /**  \brief  This function checks the stderr file, and if it has data,
822              shows it in a warning dialog to the user
823      \param  filename  Filename of the stderr file
824 */
825 void Script::checkStderr (const Glib::ustring &data,
826                            Gtk::MessageType type,
827                      const Glib::ustring &message)
829     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
830     warning.set_resizable(true);
831     GtkWidget *dlg = GTK_WIDGET(warning.gobj());
832     sp_transientize(dlg);
834     Gtk::VBox * vbox = warning.get_vbox();
836     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
837     Gtk::TextView * textview = new Gtk::TextView();
838     textview->set_editable(false);
839     textview->set_wrap_mode(Gtk::WRAP_WORD);
840     textview->show();
842     // Remove the last character
843     char *errormsg = (char*) data.c_str();
844     while (*errormsg != '\0') errormsg++;
845     errormsg -= 1;
846     *errormsg = '\0';
848     textview->get_buffer()->set_text(_(data.c_str()));
850     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
851     scrollwindow->add(*textview);
852     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
853     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
854     scrollwindow->show();
856     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
858     warning.run();
860     return;
863 bool Script::cancelProcessing (void) {
864     _canceled = true;
865     _main_loop->quit();
866     Glib::spawn_close_pid(_pid);
868     return true;
872 /** \brief    This is the core of the extension file as it actually does
873               the execution of the extension.
874     \param    in_command  The command to be executed
875     \param    filein      Filename coming in
876     \param    fileout     Filename of the out file
877     \return   Number of bytes that were read into the output file.
879     The first thing that this function does is build the command to be
880     executed.  This consists of the first string (in_command) and then
881     the filename for input (filein).  This file is put on the command
882     line.
884     The next thing is that this function does is open a pipe to the
885     command and get the file handle in the ppipe variable.  It then
886     opens the output file with the output file handle.  Both of these
887     operations are checked extensively for errors.
889     After both are opened, then the data is copied from the output
890     of the pipe into the file out using fread and fwrite.  These two
891     functions are used because of their primitive nature they make
892     no assumptions about the data.  A buffer is used in the transfer,
893     but the output of fread is stored so the exact number of bytes
894     is handled gracefully.
896     At the very end (after the data has been copied) both of the files
897     are closed, and we return to what we were doing.
898 */
899 int Script::execute (const std::list<std::string> &in_command,
900                  const std::list<std::string> &in_params,
901                  const Glib::ustring &filein,
902                  file_listener &fileout)
904     g_return_val_if_fail(!in_command.empty(), 0);
905     // printf("Executing\n");
907     std::vector<std::string> argv;
909     bool interpreted = (in_command.size() == 2);
910     std::string program = in_command.front();
911     std::string script = interpreted ? in_command.back() : "";
912     std::string working_directory = "";
914     // Use Glib::find_program_in_path instead of the equivalent
915     // Glib::spawn_* functionality, because _wspawnp is broken on Windows:
916     // it doesn't work when PATH contains Unicode directories
917     if (!Glib::path_is_absolute(program)) {
918         program = Glib::find_program_in_path(program);
919     }
920     argv.push_back(program);
922     if (interpreted) {
923         // On Windows, Python garbles Unicode command line parameters
924         // in an useless way. This means extensions fail when Inkscape
925         // is run from an Unicode directory.
926         // As a workaround, we set the working directory to the one
927         // containing the script.
928         working_directory = Glib::path_get_dirname(script);
929         script = Glib::path_get_basename(script);
930         #ifdef G_OS_WIN32
931         // ANNOYING: glibmm does not wrap g_win32_locale_filename_from_utf8
932         gchar *workdir_s = g_win32_locale_filename_from_utf8(working_directory.data());
933         working_directory = workdir_s;
934         g_free(workdir_s);
935         #endif
937         argv.push_back(script);
938     }
940     // assemble the rest of argv
941     std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
942     if (!filein.empty()) {
943         argv.push_back(filein);
944     }
946     int stdout_pipe, stderr_pipe;
948     try {
949         Glib::spawn_async_with_pipes(working_directory, // working directory
950                                      argv,  // arg v
951                                      static_cast<Glib::SpawnFlags>(0), // no flags
952                                      sigc::slot<void>(),
953                                      &_pid,          // Pid
954                                      NULL,           // STDIN
955                                      &stdout_pipe,   // STDOUT
956                                      &stderr_pipe);  // STDERR
957     } catch (Glib::Error e) {
958         printf("Can't Spawn!!! spawn returns: %s\n", e.what().data());
959         return 0;
960     }
962     _main_loop = Glib::MainLoop::create(false);
964     file_listener fileerr;
965     fileout.init(stdout_pipe, _main_loop);
966     fileerr.init(stderr_pipe, _main_loop);
968     _canceled = false;
969     _main_loop->run();
971     // Ensure all the data is out of the pipe
972     while (!fileout.isDead()) {
973         fileout.read(Glib::IO_IN);
974     }
975     while (!fileerr.isDead()) {
976         fileerr.read(Glib::IO_IN);
977     }
979     if (_canceled) {
980         // std::cout << "Script Canceled" << std::endl;
981         return 0;
982     }
984     Glib::ustring stderr_data = fileerr.string();
985     if (stderr_data.length() != 0 &&
986         inkscape_use_gui()
987        ) {
988         checkStderr(stderr_data, Gtk::MESSAGE_INFO,
989                                  _("Inkscape has received additional data from the script executed.  "
990                                    "The script did not return an error, but this may indicate the results will not be as expected."));
991     }
993     Glib::ustring stdout_data = fileout.string();
994     if (stdout_data.length() == 0) {
995         return 0;
996     }
998     // std::cout << "Finishing Execution." << std::endl;
999     return stdout_data.length();
1005 }  // namespace Implementation
1006 }  // namespace Extension
1007 }  // namespace Inkscape
1009 /*
1010   Local Variables:
1011   mode:c++
1012   c-file-style:"stroustrup"
1013   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1014   indent-tabs-mode:nil
1015   fill-column:99
1016   End:
1017 */
1018 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :