Code

Allow Inkscape to use its own python in preference to something in the path on win32...
[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  *
9  * Copyright (C) 2002-2005 Authors
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
20 #include <unistd.h>
22 #include <errno.h>
23 #include <gtkmm.h>
25 #include "ui/view/view.h"
26 #include "desktop-handles.h"
27 #include "selection.h"
28 #include "sp-namedview.h"
29 #include "io/sys.h"
30 #include "registrytool.h"
31 #include "prefs-utils.h"
32 #include "../system.h"
33 #include "extension/effect.h"
34 #include "extension/output.h"
35 #include "extension/db.h"
36 #include "script.h"
38 #include "util/glib-list-iterators.h"
42 #ifdef WIN32
43 #include <windows.h>
44 #include <sys/stat.h>
45 #endif
49 /** This is the command buffer that gets allocated from the stack */
50 #define BUFSIZE (255)
54 /* Namespaces */
55 namespace Inkscape {
56 namespace Extension {
57 namespace Implementation {
61 //Interpreter lookup table
62 struct interpreter_t {
63         gchar * identity;
64         gchar * prefstring;
65         gchar * defaultval;
66 };
69 static interpreter_t interpreterTab[] = {
70         {"perl",   "perl-interpreter",   "perl"   },
71         {"python", "python-interpreter", "python" },
72         {"ruby",   "ruby-interpreter",   "ruby"   },
73         {"shell",  "shell-interpreter",  "sh"     },
74         { NULL,    NULL,                  NULL    }
75 };
79 /**
80  * Look up an interpreter name, and translate to something that
81  * is executable
82  */
83 static Glib::ustring
84 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
85 {
87     Glib::ustring interpName = interpNameArg;
89     interpreter_t *interp;
90     bool foundInterp = false;
91     for (interp =  interpreterTab ; interp->identity ; interp++ ){
92         if (interpName == interp->identity) {
93             foundInterp = true;
94             break;
95         }
96     }
98     // Do we have a supported interpreter type?
99     if (!foundInterp)
100         return "";
101     interpName = interp->defaultval;
103     // 1.  Check preferences
104     gchar *prefInterp = (gchar *)prefs_get_string_attribute(
105                                 "extensions", interp->prefstring);
107     if (prefInterp) {
108         interpName = prefInterp;
109         return interpName;
110     }
112 #ifdef _WIN32
114     // 2.  Windows.  Try looking relative to inkscape.exe
115     RegistryTool rt;
116     Glib::ustring fullPath;
117     Glib::ustring path;
118     Glib::ustring exeName;
119     if (rt.getExeInfo(fullPath, path, exeName)) {
120         Glib::ustring interpPath = path;
121         interpPath.append("\\");
122         interpPath.append(interpName);
123         interpPath.append("\\");
124         interpPath.append(interpName);
125         interpPath.append(".exe");
126         struct stat finfo;
127         if (stat(interpPath .c_str(), &finfo) ==0) {
128             g_message("Found local interpreter, '%s',  Size: %d",
129                       interpPath .c_str(),
130                       finfo.st_size);
131             return interpPath;
132         }                       
133     }
135     // 3. Try searching the path
136     char szExePath[MAX_PATH];
137     char szCurrentDir[MAX_PATH];
138     GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
139     unsigned int ret = (unsigned int)FindExecutable(
140                   interpName.c_str(), szCurrentDir, szExePath);
141     if (ret > 32) {
142         interpName = szExePath;
143         return interpName;
144     }
146 #endif // win32
149     return interpName;
157 /**
158     \return    A script object
159     \brief     This function creates a script object and sets up the
160                variables.
162    This function just sets the command to NULL.  It should get built
163    officially in the load function.  This allows for less allocation
164    of memory in the unloaded state.
165 */
166 Script::Script() :
167     Implementation()
172 /**
173  *   brief     Destructor
174  */
175 Script::~Script()
181 /**
182     \return    A string with the complete string with the relative directory expanded
183     \brief     This function takes in a Repr that contains a reldir entry
184                and returns that data with the relative directory expanded.
185                Mostly it is here so that relative directories all get used
186                the same way.
187     \param     reprin   The Inkscape::XML::Node with the reldir in it.
189     Basically this function looks at an attribute of the Repr, and makes
190     a decision based on that.  Currently, it is only working with the
191     'extensions' relative directory, but there will be more of them.
192     One thing to notice is that this function always returns an allocated
193     string.  This means that the caller of this function can always
194     free what they are given (and should do it too!).
195 */
196 Glib::ustring
197 Script::solve_reldir(Inkscape::XML::Node *reprin) {
199     gchar const *s = reprin->attribute("reldir");
201     if (!s) {
202         Glib::ustring str = sp_repr_children(reprin)->content();
203         return str;
204     }
206     Glib::ustring reldir = s;
208     if (reldir == "extensions") {
210         for (unsigned int i=0;
211             i < Inkscape::Extension::Extension::search_path.size();
212             i++) {
214             gchar * fname = g_build_filename(
215                Inkscape::Extension::Extension::search_path[i],
216                sp_repr_children(reprin)->content(),
217                NULL);
218             Glib::ustring filename = fname;
219             g_free(fname);
221             if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
222                 return filename;
224         }
225     } else {
226         Glib::ustring str = sp_repr_children(reprin)->content();
227         return str;
228     }
230     return "";
235 /**
236     \return   Whether the command given exists, including in the path
237     \brief    This function is used to find out if something exists for
238               the check command.  It can look in the path if required.
239     \param    command   The command or file that should be looked for
241     The first thing that this function does is check to see if the
242     incoming file name has a directory delimiter in it.  This would
243     mean that it wants to control the directories, and should be
244     used directly.
246     If not, the path is used.  Each entry in the path is stepped through,
247     attached to the string, and then tested.  If the file is found
248     then a TRUE is returned.  If we get all the way through the path
249     then a FALSE is returned, the command could not be found.
250 */
251 bool
252 Script::check_existance(const Glib::ustring &command)
255     // Check the simple case first
256     if (command.size() == 0) {
257         return false;
258     }
260     //Don't search when it contains a slash. */
261     if (command.find(G_DIR_SEPARATOR) != command.npos) {
262         if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
263             return true;
264         else
265             return false;
266     }
269     Glib::ustring path; 
270     gchar *s = (gchar *) g_getenv("PATH");
271     if (s)
272         path = s;
273     else
274        /* There is no `PATH' in the environment.
275            The default search path is the current directory */
276         path = G_SEARCHPATH_SEPARATOR_S;
278     unsigned int pos  = 0;
279     unsigned int pos2 = 0;
280     while ( pos < path.size() ) {
282         Glib::ustring localPath;
284         pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
285         if (pos2 == path.npos) {
286             localPath = path.substr(pos);
287             pos = path.size();
288         } else {
289             localPath = path.substr(pos, pos2-pos);
290             pos = pos2+1;
291         }
292         
293         //printf("### %s\n", localPath.c_str());
294         Glib::ustring candidatePath = 
295                       Glib::build_filename(localPath, command);
297         if (Inkscape::IO::file_test(candidatePath .c_str(),
298                       G_FILE_TEST_EXISTS))
299             return true;
301     }
303     return false;
310 /**
311     \return   none
312     \brief    This function 'loads' an extention, basically it determines
313               the full command for the extention and stores that.
314     \param    module  The extention to be loaded.
316     The most difficult part about this function is finding the actual
317     command through all of the Reprs.  Basically it is hidden down a
318     couple of layers, and so the code has to move down too.  When
319     the command is actually found, it has its relative directory
320     solved.
322     At that point all of the loops are exited, and there is an
323     if statement to make sure they didn't exit because of not finding
324     the command.  If that's the case, the extention doesn't get loaded
325     and should error out at a higher level.
326 */
328 bool
329 Script::load(Inkscape::Extension::Extension *module)
331     if (module->loaded())
332         return TRUE;
334     helper_extension = "";
336     /* This should probably check to find the executable... */
337     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
338     Glib::ustring command_text;
339     while (child_repr != NULL) {
340         if (!strcmp(child_repr->name(), "script")) {
341             child_repr = sp_repr_children(child_repr);
342             while (child_repr != NULL) {
343                 if (!strcmp(child_repr->name(), "command")) {
344                     command_text = solve_reldir(child_repr);
346                     const gchar *interpretstr = child_repr->attribute("interpreter");
347                     if (interpretstr != NULL) {
348                         Glib::ustring interpString =
349                             resolveInterpreterExecutable(interpretstr);
350                         interpString .append(" ");
351                         interpString .append(command_text);
352                         command_text = interpString;
353                     }
354                 }
355                 if (!strcmp(child_repr->name(), "helper_extension"))
356                     helper_extension = sp_repr_children(child_repr)->content();
357                 child_repr = sp_repr_next(child_repr);
358             }
360             break;
361         }
362         child_repr = sp_repr_next(child_repr);
363     }
365     g_return_val_if_fail(command_text.size() > 0, FALSE);
367     command = command_text;
368     return true;
372 /**
373     \return   None.
374     \brief    Unload this puppy!
375     \param    module  Extension to be unloaded.
377     This function just sets the module to unloaded.  It free's the
378     command if it has been allocated.
379 */
380 void
381 Script::unload(Inkscape::Extension::Extension *module)
383     command          = "";
384     helper_extension = "";
390 /**
391     \return   Whether the check passed or not
392     \brief    Check every dependency that was given to make sure we should keep this extension
393     \param    module  The Extension in question
395 */
396 bool
397 Script::check(Inkscape::Extension::Extension *module)
399     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
400     while (child_repr != NULL) {
401         if (!strcmp(child_repr->name(), "script")) {
402             child_repr = sp_repr_children(child_repr);
403             while (child_repr != NULL) {
404                 if (!strcmp(child_repr->name(), "check")) {
405                     Glib::ustring command_text = solve_reldir(child_repr);
406                     if (command_text.size() > 0) {
407                         /* I've got the command */
408                         bool existance = check_existance(command_text);
409                         if (!existance)
410                             return FALSE;
411                     }
412                 }
414                 if (!strcmp(child_repr->name(), "helper_extension")) {
415                     gchar const *helper = sp_repr_children(child_repr)->content();
416                     if (Inkscape::Extension::db.get(helper) == NULL) {
417                         return FALSE;
418                     }
419                 }
421                 child_repr = sp_repr_next(child_repr);
422             }
424             break;
425         }
426         child_repr = sp_repr_next(child_repr);
427     }
429     return true;
434 /**
435     \return   A dialog for preferences
436     \brief    A stub funtion right now
437     \param    module    Module who's preferences need getting
438     \param    filename  Hey, the file you're getting might be important
440     This function should really do something, right now it doesn't.
441 */
442 Gtk::Widget *
443 Script::prefs_input(Inkscape::Extension::Input *module,
444                     const Glib::ustring &filename)
446     /*return module->autogui(); */
447     return NULL;
452 /**
453     \return   A dialog for preferences
454     \brief    A stub funtion right now
455     \param    module    Module whose preferences need getting
457     This function should really do something, right now it doesn't.
458 */
459 Gtk::Widget *
460 Script::prefs_output(Inkscape::Extension::Output *module)
462     return module->autogui(NULL, NULL); 
467 /**
468     \return   A dialog for preferences
469     \brief    A stub funtion right now
470     \param    module    Module who's preferences need getting
472     This function should really do something, right now it doesn't.
473 */
474 Gtk::Widget *
475 Script::prefs_effect(Inkscape::Extension::Effect *module,
476                      Inkscape::UI::View::View *view)
479     SPDocument * current_document = view->doc();
481     using Inkscape::Util::GSListConstIterator;
482     GSListConstIterator<SPItem *> selected =
483            sp_desktop_selection((SPDesktop *)view)->itemList();
484     Inkscape::XML::Node * first_select = NULL;
485     if (selected != NULL) 
486            first_select = SP_OBJECT_REPR(*selected);
488     return module->autogui(current_document, first_select);
494 /**
495     \return  A new document that has been opened
496     \brief   This function uses a filename that is put in, and calls
497              the extension's command to create an SVG file which is
498              returned.
499     \param   module   Extension to use.
500     \param   filename File to open.
502     First things first, this function needs a temporary file name.  To
503     create on of those the function g_file_open_tmp is used with
504     the header of ink_ext_.
506     The extension is then executed using the 'execute' function
507     with the filname coming in, and the temporary filename.  After
508     That executing, the SVG should be in the temporary file.
510     Finally, the temporary file is opened using the SVG input module and
511     a document is returned.  That document has its filename set to
512     the incoming filename (so that it's not the temporary filename).
513     That document is then returned from this function.
514 */
515 SPDocument *
516 Script::open(Inkscape::Extension::Input *module,
517              const Glib::ustring &filename)
520     gchar *tmpname;
522     // FIXME: process the GError instead of passing NULL
523     gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
524     if (tempfd == -1) {
525         /* Error, couldn't create temporary filename */
526         if (errno == EINVAL) {
527             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
528             perror("Extension::Script:  template for filenames is misconfigured.\n");
529             exit(-1);
530         } else if (errno == EEXIST) {
531             /* Now the  contents of template are undefined. */
532             perror("Extension::Script:  Could not create a unique temporary filename\n");
533             return NULL;
534         } else {
535             perror("Extension::Script:  Unknown error creating temporary filename\n");
536             exit(-1);
537         }
538     }
540     Glib::ustring tempfilename_out = tmpname;
541     g_free(tmpname);
543     gsize bytesRead = 0;
544     gsize bytesWritten = 0;
545     GError *error = NULL;
546     Glib::ustring local_filename =
547             g_filename_from_utf8( filename.c_str(), -1,
548                                   &bytesRead,  &bytesWritten, &error);
550     int data_read = execute(command, local_filename, tempfilename_out);
553     SPDocument *mydoc = NULL;
554     if (data_read > 10) {
555         if (helper_extension.size()==0) {
556             mydoc = Inkscape::Extension::open(
557                 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
558                                             tempfilename_out.c_str());
559         } else {
560             mydoc = Inkscape::Extension::open(
561                 Inkscape::Extension::db.get(helper_extension.c_str()),
562                                             tempfilename_out.c_str());
563         }
564     }
566     if (mydoc != NULL)
567         sp_document_set_uri(mydoc, (const gchar *)filename.c_str());
569     // make sure we don't leak file descriptors from g_file_open_tmp
570     close(tempfd);
571     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
572     unlink(tempfilename_out.c_str());
575     return mydoc;
580 /**
581     \return   none
582     \brief    This function uses an extention to save a document.  It first
583               creates an SVG file of the document, and then runs it through
584               the script.
585     \param    module    Extention to be used
586     \param    doc       Document to be saved
587     \param    filename  The name to save the final file as
589     Well, at some point people need to save - it is really what makes
590     the entire application useful.  And, it is possible that someone
591     would want to use an extetion for this, so we need a function to
592     do that eh?
594     First things first, the document is saved to a temporary file that
595     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
596     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
597     end of the function.
599     After we have the SVG file, then extention_execute is called with
600     the temporary file name and the final output filename.  This should
601     put the output of the script into the final output file.  We then
602     delete the temporary file.
603 */
604 void
605 Script::save(Inkscape::Extension::Output *module,
606              SPDocument *doc,
607              const Glib::ustring &filename)
610     gchar *tmpname;
611     // FIXME: process the GError instead of passing NULL
612     gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
613     if (tempfd == -1) {
614         /* Error, couldn't create temporary filename */
615         if (errno == EINVAL) {
616             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
617             perror("Extension::Script:  template for filenames is misconfigured.\n");
618             exit(-1);
619         } else if (errno == EEXIST) {
620             /* Now the  contents of template are undefined. */
621             perror("Extension::Script:  Could not create a unique temporary filename\n");
622             return;
623         } else {
624             perror("Extension::Script:  Unknown error creating temporary filename\n");
625             exit(-1);
626         }
627     }
629     Glib::ustring tempfilename_in = tmpname;
630     g_free(tmpname);
632     if (helper_extension.size() == 0) {
633         Inkscape::Extension::save(
634                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
635                    doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
636     } else {
637         Inkscape::Extension::save(
638                    Inkscape::Extension::db.get(helper_extension.c_str()),
639                    doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
640     }
642     gsize bytesRead = 0;
643     gsize bytesWritten = 0;
644     GError *error = NULL;
645     Glib::ustring local_filename =
646             g_filename_from_utf8( filename.c_str(), -1,
647                                  &bytesRead,  &bytesWritten, &error);
649     Glib::ustring local_command = command;
650     Glib::ustring paramString   = *module->paramString();
651     local_command.append(paramString);
653     execute(local_command, tempfilename_in, local_filename);
656     // make sure we don't leak file descriptors from g_file_open_tmp
657     close(tempfd);
658     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
659     unlink(tempfilename_in.c_str());
664 /**
665     \return    none
666     \brief     This function uses an extention as a effect on a document.
667     \param     module   Extention to effect with.
668     \param     doc      Document to run through the effect.
670     This function is a little bit trickier than the previous two.  It
671     needs two temporary files to get it's work done.  Both of these
672     files have random names created for them using the g_file_open_temp function
673     with the sp_ext_ prefix in the temporary directory.  Like the other
674     functions, the temporary files are deleted at the end.
676     To save/load the two temporary documents (both are SVG) the internal
677     modules for SVG load and save are used.  They are both used through
678     the module system function by passing their keys into the functions.
680     The command itself is built a little bit differently than in other
681     functions because the effect support selections.  So on the command
682     line a list of all the ids that are selected is included.  Currently,
683     this only works for a single selected object, but there will be more.
684     The command string is filled with the data, and then after the execution
685     it is freed.
687     The execute function is used at the core of this function
688     to execute the Script on the two SVG documents (actually only one
689     exists at the time, the other is created by that script).  At that
690     point both should be full, and the second one is loaded.
691 */
692 void
693 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
695     SPDocument * mydoc = NULL;
697     gchar *tmpname;
698     // FIXME: process the GError instead of passing NULL
699     gint tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
700     if (tempfd_in == -1) {
701         /* Error, couldn't create temporary filename */
702         if (errno == EINVAL) {
703             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
704             perror("Extension::Script:  template for filenames is misconfigured.\n");
705             exit(-1);
706         } else if (errno == EEXIST) {
707             /* Now the  contents of template are undefined. */
708             perror("Extension::Script:  Could not create a unique temporary filename\n");
709             return;
710         } else {
711             perror("Extension::Script:  Unknown error creating temporary filename\n");
712             exit(-1);
713         }
714     }
716     Glib::ustring tempfilename_in = tmpname;
717     g_free(tmpname);
720     // FIXME: process the GError instead of passing NULL
721     gint tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
722     if (tempfd_out == -1) {
723         /* Error, couldn't create temporary filename */
724         if (errno == EINVAL) {
725             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
726             perror("Extension::Script:  template for filenames is misconfigured.\n");
727             exit(-1);
728         } else if (errno == EEXIST) {
729             /* Now the  contents of template are undefined. */
730             perror("Extension::Script:  Could not create a unique temporary filename\n");
731             return;
732         } else {
733             perror("Extension::Script:  Unknown error creating temporary filename\n");
734             exit(-1);
735         }
736     }
738     Glib::ustring tempfilename_out= tmpname;
739     g_free(tmpname);
741     Inkscape::Extension::save(
742               Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
743               doc->doc(), tempfilename_in.c_str(), FALSE, FALSE, FALSE);
745     Glib::ustring local_command(command);
747     /* fixme: Should be some sort of checking here.  Don't know how to do this with structs instead
748      * of classes. */
749     SPDesktop *desktop = (SPDesktop *) doc;
750     if (desktop != NULL) {
751         Inkscape::Util::GSListConstIterator<SPItem *> selected =
752              sp_desktop_selection(desktop)->itemList();
753         while ( selected != NULL ) {
754             local_command += " --id=";
755             local_command += SP_OBJECT_ID(*selected);
756             ++selected;
757         }
758     }
760     Glib::ustring paramString = *module->paramString();
761     local_command.append(paramString);
764     // std::cout << local_command << std::endl;
766     int data_read = execute(local_command, tempfilename_in, tempfilename_out);
768     if (data_read > 10)
769         mydoc = Inkscape::Extension::open(
770               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
771               tempfilename_out.c_str());
773     // make sure we don't leak file descriptors from g_file_open_tmp
774     close(tempfd_in);
775     close(tempfd_out);
777     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
778     unlink(tempfilename_in.c_str());
779     unlink(tempfilename_out.c_str());
782     /* Do something with mydoc.... */
783     if (mydoc) {
784         doc->doc()->emitReconstructionStart();
785         copy_doc(doc->doc()->rroot, mydoc->rroot);
786         doc->doc()->emitReconstructionFinish();
787         mydoc->release();
788     }
793 /**
794     \brief  A function to take all the svg elements from one document
795             and put them in another.
796     \param  oldroot  The root node of the document to be replaced
797     \param  newroot  The root node of the document to replace it with
799     This function first deletes all of the data in the old document.  It
800     does this by creating a list of what needs to be deleted, and then
801     goes through the list.  This two pass approach removes issues with
802     the list being change while parsing through it.  Lots of nasty bugs.
804     Then, it goes through the new document, duplicating all of the
805     elements and putting them into the old document.  The copy
806     is then complete.
807 */
808 void
809 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
811     std::vector<Inkscape::XML::Node *> delete_list;
812     for (Inkscape::XML::Node * child = oldroot->firstChild();
813             child != NULL;
814             child = child->next()) {
815         if (!strcmp("sodipodi:namedview", child->name()))
816             continue;
817         if (!strcmp("svg:defs", child->name()))
818             continue;
819         delete_list.push_back(child);
820     }
821     for (unsigned int i = 0; i < delete_list.size(); i++)
822         sp_repr_unparent(delete_list[i]);
824     for (Inkscape::XML::Node * child = newroot->firstChild();
825             child != NULL;
826             child = child->next()) {
827         if (!strcmp("sodipodi:namedview", child->name()))
828             continue;
829         if (!strcmp("svg:defs", child->name()))
830             continue;
831         oldroot->appendChild(child->duplicate());
832     }
834     /** \todo  Restore correct layer */
835     /** \todo  Restore correct selection */
840 /* Helper class used by Script::execute */
841 class pipe_t {
842 public:
843     /* These functions set errno if they return false.
844        I'm not sure whether that's a good idea or not, but it should be reasonably
845        straightforward to change it if needed. */
846     bool open(const Glib::ustring &command,
847               const Glib::ustring &errorFile,
848               int mode);
849     bool close();
851     /* These return the number of bytes read/written. */
852     size_t read(void *buffer, size_t size);
853     size_t write(void const *buffer, size_t size);
855     enum {
856         mode_read  = 1 << 0,
857         mode_write = 1 << 1,
858     };
860 private:
861 #ifdef WIN32
862     /* This is used to translate win32 errors into errno errors.
863        It only recognizes a few win32 errors for the moment though. */
864     static int translate_error(DWORD err);
866     HANDLE hpipe;
867 #else
868     FILE *ppipe;
869 #endif
870 };
875 /**
876     \return   none
877     \brief    This is the core of the extension file as it actually does
878               the execution of the extension.
879     \param    in_command  The command to be executed
880     \param    filein      Filename coming in
881     \param    fileout     Filename of the out file
882     \return   Number of bytes that were read into the output file.
884     The first thing that this function does is build the command to be
885     executed.  This consists of the first string (in_command) and then
886     the filename for input (filein).  This file is put on the command
887     line.
889     The next thing is that this function does is open a pipe to the
890     command and get the file handle in the ppipe variable.  It then
891     opens the output file with the output file handle.  Both of these
892     operations are checked extensively for errors.
894     After both are opened, then the data is copied from the output
895     of the pipe into the file out using fread and fwrite.  These two
896     functions are used because of their primitive nature they make
897     no assumptions about the data.  A buffer is used in the transfer,
898     but the output of fread is stored so the exact number of bytes
899     is handled gracefully.
901     At the very end (after the data has been copied) both of the files
902     are closed, and we return to what we were doing.
903 */
904 int
905 Script::execute (const Glib::ustring &in_command,
906                  const Glib::ustring &filein,
907                  const Glib::ustring &fileout)
909     g_return_val_if_fail(in_command.size() > 0, 0);
910     // printf("Executing: %s\n", in_command);
912     gchar *tmpname;
913     gint errorFileNum;
914     errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &tmpname, NULL);
915     if (errorFileNum != 0) {
916         close(errorFileNum);
917     } else {
918         g_free(tmpname);
919     }
921     Glib::ustring errorFile = tmpname;
922     g_free(tmpname);
924     Glib::ustring localCommand = in_command;
925     localCommand .append(" \"");
926     localCommand .append(filein);
927     localCommand .append("\"");
929     // std::cout << "Command to run: " << command << std::endl;
931     pipe_t pipe;
932     bool open_success = pipe.open((char *)localCommand.c_str(),
933                                   errorFile.c_str(),
934                                   pipe_t::mode_read);
936     /* Run script */
937     if (!open_success) {
938         /* Error - could not open pipe - check errno */
939         if (errno == EINVAL) {
940             perror("Extension::Script:  Invalid mode argument in popen\n");
941         } else if (errno == ECHILD) {
942             perror("Extension::Script:  Cannot obtain child extension status in popen\n");
943         } else {
944             perror("Extension::Script:  Unknown error for popen\n");
945         }
946         return 0;
947     }
949     Inkscape::IO::dump_fopen_call(fileout.c_str(), "J");
950     FILE *pfile = Inkscape::IO::fopen_utf8name(fileout.c_str(), "w");
952     if (pfile == NULL) {
953         /* Error - could not open file */
954         if (errno == EINVAL) {
955             perror("Extension::Script:  The mode provided to fopen was invalid\n");
956         } else {
957             perror("Extension::Script:  Unknown error attempting to open temporary file\n");
958         }
959         return 0;
960     }
962     /* Copy pipe output to a temporary file */
963     int amount_read = 0;
964     char buf[BUFSIZE];
965     int num_read;
966     while ((num_read = pipe.read(buf, BUFSIZE)) != 0) {
967         amount_read += num_read;
968         fwrite(buf, 1, num_read, pfile);
969     }
971     /* Close file */
972     if (fclose(pfile) == EOF) {
973         if (errno == EBADF) {
974             perror("Extension::Script:  The filedescriptor for the temporary file is invalid\n");
975             return 0;
976         } else {
977             perror("Extension::Script:  Unknown error closing temporary file\n");
978         }
979     }
981     /* Close pipe */
982     if (!pipe.close()) {
983         if (errno == EINVAL) {
984             perror("Extension::Script:  Invalid mode set for pclose\n");
985         } else if (errno == ECHILD) {
986             perror("Extension::Script:  Could not obtain child status for pclose\n");
987         } else {
988             if (errorFile != NULL) {
989                 checkStderr(errorFile, Gtk::MESSAGE_ERROR,
990                     _("Inkscape has received an error from the script that it called.  "
991                       "The text returned with the error is included below.  "
992                       "Inkscape will continue working, but the action you requested has been cancelled."));
993             } else {
994                 perror("Extension::Script:  Unknown error for pclose\n");
995             }
996         }
997         /* Could be a lie, but if there is an error, we don't want
998          * to count on what was read being good */
999         amount_read = 0;
1000     } else {
1001         if (errorFile.size()>0) {
1002             checkStderr(errorFile, Gtk::MESSAGE_INFO,
1003                 _("Inkscape has received additional data from the script executed.  "
1004                   "The script did not return an error, but this may indicate the results will not be as expected."));
1005         }
1006     }
1008     if (errorFile.size()>0) {
1009         unlink(errorFile.c_str());
1010     }
1012     return amount_read;
1018 /**  \brief  This function checks the stderr file, and if it has data,
1019              shows it in a warning dialog to the user
1020      \param  filename  Filename of the stderr file
1021 */
1022 void
1023 Script::checkStderr (const Glib::ustring &filename,
1024                      Gtk::MessageType type,
1025                      const Glib::ustring &message)
1028     // magic win32 crlf->lf conversion means the file length is not the same as
1029     // the text length, but luckily gtk will accept crlf in textviews so we can
1030     // just use binary mode
1031     std::ifstream stderrf (filename.c_str(), std::ios_base::in | std::ios_base::binary);
1032     if (!stderrf.is_open()) return;
1034     stderrf.seekg(0, std::ios::end);
1035     int length = stderrf.tellg();
1036     if (0 == length) return;
1037     stderrf.seekg(0, std::ios::beg);
1039     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
1040     warning.set_resizable(true);
1042     Gtk::VBox * vbox = warning.get_vbox();
1044     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
1045     Gtk::TextView * textview = new Gtk::TextView();
1046     textview->set_editable(false);
1047     textview->set_wrap_mode(Gtk::WRAP_WORD);
1048     textview->show();
1050     char * buffer = new char [length];
1051     stderrf.read(buffer, length);
1052     textview->get_buffer()->set_text(buffer, buffer + length);
1053     delete buffer;
1054     stderrf.close();
1056     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
1057     scrollwindow->add(*textview);
1058     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
1059     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
1060     scrollwindow->show();
1062     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
1064     warning.run();
1066     return;
1072 #ifdef WIN32
1075 bool pipe_t::open(const Glib::ustring &command,
1076                   const Glib::ustring &errorFile,
1077                   int mode_p) {
1078     HANDLE pipe_write;
1080     //###############  Create pipe
1081     SECURITY_ATTRIBUTES secattrs;
1082     ZeroMemory(&secattrs, sizeof(secattrs));
1083     secattrs.nLength = sizeof(secattrs);
1084     secattrs.lpSecurityDescriptor = 0;
1085     secattrs.bInheritHandle = TRUE;
1086     HANDLE t_pipe_read = 0;
1087     if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) {
1088         errno = translate_error(GetLastError());
1089         return false;
1090     }
1091     // This duplicate handle makes the read pipe uninheritable
1092     BOOL ret = DuplicateHandle(GetCurrentProcess(),
1093                                t_pipe_read,
1094                                GetCurrentProcess(),
1095                                &hpipe, 0, FALSE,
1096                                DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1097     if (!ret) {
1098         int en = translate_error(GetLastError());
1099         CloseHandle(t_pipe_read);
1100         CloseHandle(pipe_write);
1101         errno = en;
1102         return false;
1103     }
1105     //############### Open stderr file
1106     HANDLE hStdErrFile = CreateFile(errorFile.c_str(),
1107                       GENERIC_WRITE,
1108                       FILE_SHARE_READ | FILE_SHARE_WRITE,
1109                       NULL, CREATE_ALWAYS, 0, NULL);
1110     HANDLE hInheritableStdErr;
1111     DuplicateHandle(GetCurrentProcess(),
1112                     hStdErrFile,
1113                     GetCurrentProcess(),
1114                     &hInheritableStdErr,
1115                     0, 
1116                     TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1118     //############### Create process
1119     PROCESS_INFORMATION procinfo;
1120     STARTUPINFO startupinfo;
1121     ZeroMemory(&procinfo, sizeof(procinfo));
1122     ZeroMemory(&startupinfo, sizeof(startupinfo));
1123     startupinfo.cb = sizeof(startupinfo);
1124     //startupinfo.lpReserved = 0;
1125     //startupinfo.lpDesktop = 0;
1126     //startupinfo.lpTitle = 0;
1127     startupinfo.dwFlags = STARTF_USESTDHANDLES;
1128     startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
1129     startupinfo.hStdOutput = pipe_write;
1130     startupinfo.hStdError = hInheritableStdErr;
1132     if ( !CreateProcess(NULL, (CHAR *)command.c_str(),
1133                         NULL, NULL, TRUE,
1134                         0, NULL, NULL,
1135                         &startupinfo, &procinfo) ) {
1136         errno = translate_error(GetLastError());
1137         return false;
1138     }
1139     CloseHandle(procinfo.hThread);
1140     CloseHandle(procinfo.hProcess);
1142     // Close our copy of the write handle
1143     CloseHandle(hInheritableStdErr);
1144     CloseHandle(pipe_write);
1146     return true;
1151 bool pipe_t::close() {
1152     BOOL retval = CloseHandle(hpipe);
1153     if ( !retval )
1154         errno = translate_error(GetLastError());
1155     return retval != FALSE;
1158 size_t pipe_t::read(void *buffer, size_t size) {
1159     DWORD bytes_read = 0;
1160     ReadFile(hpipe, buffer, size, &bytes_read, 0);
1161     return bytes_read;
1164 size_t pipe_t::write(void const *buffer, size_t size) {
1165     DWORD bytes_written = 0;
1166     WriteFile(hpipe, buffer, size, &bytes_written, 0);
1167     return bytes_written;
1170 int pipe_t::translate_error(DWORD err) {
1171     switch (err) {
1172         case ERROR_FILE_NOT_FOUND:
1173             return ENOENT;
1174         case ERROR_INVALID_HANDLE:
1175         case ERROR_INVALID_PARAMETER:
1176             return EINVAL;
1177         default:
1178             return 0;
1179     }
1183 #else // not Win32
1186 bool pipe_t::open(const Glib::ustring &command,
1187                   const Glib::ustring &errorFile,
1188                   int mode_p) {
1190     Glib::ustring popen_mode;
1192     if ( (mode_p & mode_read) != 0 )
1193         popen_mode_cur.append("r");
1195     if ( (mode_p & mode_write) != 0 )
1196         popen_mode_cur.append("w");
1198     // Get the commandline to be run
1199     Glib::ustring pipeStr = command;
1200     if (errorFile.size()>0) {
1201         pipeStr .append(" 2> ");
1202         pipeStr .append(errorFile);
1203     }
1205     ppipe = popen(pipeStr.c_str(), popen_mode.c_str());
1207     return ppipe != NULL;
1211 bool pipe_t::close() {
1212     return fclose(ppipe) == 0;
1216 size_t pipe_t::read(void *buffer, size_t size) {
1217     return fread(buffer, 1, size, ppipe);
1221 size_t pipe_t::write(void const *buffer, size_t size) {
1222     return fwrite(buffer, 1, size, ppipe);
1228 #endif // (Non-)Win32
1233 }  // namespace Implementation
1234 }  // namespace Extension
1235 }  // namespace Inkscape
1240 /*
1241   Local Variables:
1242   mode:c++
1243   c-file-style:"stroustrup"
1244   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1245   indent-tabs-mode:nil
1246   fill-column:99
1247   End:
1248 */
1249 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :