Code

Committed patch [ 1591411 ] [PATCH] Paths w/ spaces need to have double quotes
[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 "prefs-utils.h"
31 #include "../system.h"
32 #include "extension/effect.h"
33 #include "extension/output.h"
34 #include "extension/db.h"
35 #include "script.h"
37 #include "util/glib-list-iterators.h"
41 #ifdef WIN32
42 #include <windows.h>
43 #include <sys/stat.h>
44 #include "registrytool.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                       (int)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                         interpString .append("\"");                        
353                         command_text = interpString;
354                     }
355                 }
356                 if (!strcmp(child_repr->name(), "helper_extension"))
357                     helper_extension = sp_repr_children(child_repr)->content();
358                 child_repr = sp_repr_next(child_repr);
359             }
361             break;
362         }
363         child_repr = sp_repr_next(child_repr);
364     }
366     g_return_val_if_fail(command_text.size() > 0, FALSE);
368     command = command_text;
369     return true;
373 /**
374     \return   None.
375     \brief    Unload this puppy!
376     \param    module  Extension to be unloaded.
378     This function just sets the module to unloaded.  It free's the
379     command if it has been allocated.
380 */
381 void
382 Script::unload(Inkscape::Extension::Extension *module)
384     command          = "";
385     helper_extension = "";
391 /**
392     \return   Whether the check passed or not
393     \brief    Check every dependency that was given to make sure we should keep this extension
394     \param    module  The Extension in question
396 */
397 bool
398 Script::check(Inkscape::Extension::Extension *module)
400     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
401     while (child_repr != NULL) {
402         if (!strcmp(child_repr->name(), "script")) {
403             child_repr = sp_repr_children(child_repr);
404             while (child_repr != NULL) {
405                 if (!strcmp(child_repr->name(), "check")) {
406                     Glib::ustring command_text = solve_reldir(child_repr);
407                     if (command_text.size() > 0) {
408                         /* I've got the command */
409                         bool existance = check_existance(command_text);
410                         if (!existance)
411                             return FALSE;
412                     }
413                 }
415                 if (!strcmp(child_repr->name(), "helper_extension")) {
416                     gchar const *helper = sp_repr_children(child_repr)->content();
417                     if (Inkscape::Extension::db.get(helper) == NULL) {
418                         return FALSE;
419                     }
420                 }
422                 child_repr = sp_repr_next(child_repr);
423             }
425             break;
426         }
427         child_repr = sp_repr_next(child_repr);
428     }
430     return true;
435 /**
436     \return   A dialog for preferences
437     \brief    A stub funtion right now
438     \param    module    Module who's preferences need getting
439     \param    filename  Hey, the file you're getting might be important
441     This function should really do something, right now it doesn't.
442 */
443 Gtk::Widget *
444 Script::prefs_input(Inkscape::Extension::Input *module,
445                     const gchar *filename)
447     /*return module->autogui(); */
448     return NULL;
453 /**
454     \return   A dialog for preferences
455     \brief    A stub funtion right now
456     \param    module    Module whose preferences need getting
458     This function should really do something, right now it doesn't.
459 */
460 Gtk::Widget *
461 Script::prefs_output(Inkscape::Extension::Output *module)
463     return module->autogui(NULL, NULL); 
468 /**
469     \return   A dialog for preferences
470     \brief    A stub funtion right now
471     \param    module    Module who's preferences need getting
473     This function should really do something, right now it doesn't.
474 */
475 Gtk::Widget *
476 Script::prefs_effect(Inkscape::Extension::Effect *module,
477                      Inkscape::UI::View::View *view)
480     SPDocument * current_document = view->doc();
482     using Inkscape::Util::GSListConstIterator;
483     GSListConstIterator<SPItem *> selected =
484            sp_desktop_selection((SPDesktop *)view)->itemList();
485     Inkscape::XML::Node * first_select = NULL;
486     if (selected != NULL) 
487            first_select = SP_OBJECT_REPR(*selected);
489     return module->autogui(current_document, first_select);
495 /**
496     \return  A new document that has been opened
497     \brief   This function uses a filename that is put in, and calls
498              the extension's command to create an SVG file which is
499              returned.
500     \param   module   Extension to use.
501     \param   filename File to open.
503     First things first, this function needs a temporary file name.  To
504     create on of those the function g_file_open_tmp is used with
505     the header of ink_ext_.
507     The extension is then executed using the 'execute' function
508     with the filname coming in, and the temporary filename.  After
509     That executing, the SVG should be in the temporary file.
511     Finally, the temporary file is opened using the SVG input module and
512     a document is returned.  That document has its filename set to
513     the incoming filename (so that it's not the temporary filename).
514     That document is then returned from this function.
515 */
516 SPDocument *
517 Script::open(Inkscape::Extension::Input *module,
518              const gchar *filenameArg)
521     Glib::ustring filename = filenameArg;
523     gchar *tmpname;
525     // FIXME: process the GError instead of passing NULL
526     gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
527     if (tempfd == -1) {
528         /* Error, couldn't create temporary filename */
529         if (errno == EINVAL) {
530             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
531             perror("Extension::Script:  template for filenames is misconfigured.\n");
532             exit(-1);
533         } else if (errno == EEXIST) {
534             /* Now the  contents of template are undefined. */
535             perror("Extension::Script:  Could not create a unique temporary filename\n");
536             return NULL;
537         } else {
538             perror("Extension::Script:  Unknown error creating temporary filename\n");
539             exit(-1);
540         }
541     }
543     Glib::ustring tempfilename_out = tmpname;
544     g_free(tmpname);
546     gsize bytesRead = 0;
547     gsize bytesWritten = 0;
548     GError *error = NULL;
549     Glib::ustring local_filename =
550             g_filename_from_utf8( filename.c_str(), -1,
551                                   &bytesRead,  &bytesWritten, &error);
553     int data_read = execute(command, local_filename, tempfilename_out);
555     SPDocument *mydoc = NULL;
556     if (data_read > 10) {
557         if (helper_extension.size()==0) {
558             mydoc = Inkscape::Extension::open(
559                 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
560                                             tempfilename_out.c_str());
561         } else {
562             mydoc = Inkscape::Extension::open(
563                 Inkscape::Extension::db.get(helper_extension.c_str()),
564                                             tempfilename_out.c_str());
565         }
566     }
568     if (mydoc != NULL)
569         sp_document_set_uri(mydoc, (const gchar *)filename.c_str());
571     // make sure we don't leak file descriptors from g_file_open_tmp
572     close(tempfd);
573     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
574     unlink(tempfilename_out.c_str());
577     return mydoc;
582 /**
583     \return   none
584     \brief    This function uses an extention to save a document.  It first
585               creates an SVG file of the document, and then runs it through
586               the script.
587     \param    module    Extention to be used
588     \param    doc       Document to be saved
589     \param    filename  The name to save the final file as
591     Well, at some point people need to save - it is really what makes
592     the entire application useful.  And, it is possible that someone
593     would want to use an extetion for this, so we need a function to
594     do that eh?
596     First things first, the document is saved to a temporary file that
597     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
598     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
599     end of the function.
601     After we have the SVG file, then extention_execute is called with
602     the temporary file name and the final output filename.  This should
603     put the output of the script into the final output file.  We then
604     delete the temporary file.
605 */
606 void
607 Script::save(Inkscape::Extension::Output *module,
608              SPDocument *doc,
609              const gchar *filenameArg)
612     Glib::ustring filename = filenameArg;
614     gchar *tmpname;
615     // FIXME: process the GError instead of passing NULL
616     gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
617     if (tempfd == -1) {
618         /* Error, couldn't create temporary filename */
619         if (errno == EINVAL) {
620             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
621             perror("Extension::Script:  template for filenames is misconfigured.\n");
622             exit(-1);
623         } else if (errno == EEXIST) {
624             /* Now the  contents of template are undefined. */
625             perror("Extension::Script:  Could not create a unique temporary filename\n");
626             return;
627         } else {
628             perror("Extension::Script:  Unknown error creating temporary filename\n");
629             exit(-1);
630         }
631     }
633     Glib::ustring tempfilename_in = tmpname;
634     g_free(tmpname);
636     if (helper_extension.size() == 0) {
637         Inkscape::Extension::save(
638                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
639                    doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
640     } else {
641         Inkscape::Extension::save(
642                    Inkscape::Extension::db.get(helper_extension.c_str()),
643                    doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
644     }
646     gsize bytesRead = 0;
647     gsize bytesWritten = 0;
648     GError *error = NULL;
649     Glib::ustring local_filename =
650             g_filename_from_utf8( filename.c_str(), -1,
651                                  &bytesRead,  &bytesWritten, &error);
653     Glib::ustring local_command = command;
654     Glib::ustring paramString   = *module->paramString();
655     local_command.append(paramString);
657     execute(local_command, tempfilename_in, local_filename);
660     // make sure we don't leak file descriptors from g_file_open_tmp
661     close(tempfd);
662     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
663     unlink(tempfilename_in.c_str());
668 /**
669     \return    none
670     \brief     This function uses an extention as a effect on a document.
671     \param     module   Extention to effect with.
672     \param     doc      Document to run through the effect.
674     This function is a little bit trickier than the previous two.  It
675     needs two temporary files to get it's work done.  Both of these
676     files have random names created for them using the g_file_open_temp function
677     with the sp_ext_ prefix in the temporary directory.  Like the other
678     functions, the temporary files are deleted at the end.
680     To save/load the two temporary documents (both are SVG) the internal
681     modules for SVG load and save are used.  They are both used through
682     the module system function by passing their keys into the functions.
684     The command itself is built a little bit differently than in other
685     functions because the effect support selections.  So on the command
686     line a list of all the ids that are selected is included.  Currently,
687     this only works for a single selected object, but there will be more.
688     The command string is filled with the data, and then after the execution
689     it is freed.
691     The execute function is used at the core of this function
692     to execute the Script on the two SVG documents (actually only one
693     exists at the time, the other is created by that script).  At that
694     point both should be full, and the second one is loaded.
695 */
696 void
697 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
699     SPDocument * mydoc = NULL;
701     gchar *tmpname;
702     // FIXME: process the GError instead of passing NULL
703     gint tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
704     if (tempfd_in == -1) {
705         /* Error, couldn't create temporary filename */
706         if (errno == EINVAL) {
707             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
708             perror("Extension::Script:  template for filenames is misconfigured.\n");
709             exit(-1);
710         } else if (errno == EEXIST) {
711             /* Now the  contents of template are undefined. */
712             perror("Extension::Script:  Could not create a unique temporary filename\n");
713             return;
714         } else {
715             perror("Extension::Script:  Unknown error creating temporary filename\n");
716             exit(-1);
717         }
718     }
720     Glib::ustring tempfilename_in = tmpname;
721     g_free(tmpname);
724     // FIXME: process the GError instead of passing NULL
725     gint tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
726     if (tempfd_out == -1) {
727         /* Error, couldn't create temporary filename */
728         if (errno == EINVAL) {
729             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
730             perror("Extension::Script:  template for filenames is misconfigured.\n");
731             exit(-1);
732         } else if (errno == EEXIST) {
733             /* Now the  contents of template are undefined. */
734             perror("Extension::Script:  Could not create a unique temporary filename\n");
735             return;
736         } else {
737             perror("Extension::Script:  Unknown error creating temporary filename\n");
738             exit(-1);
739         }
740     }
742     Glib::ustring tempfilename_out= tmpname;
743     g_free(tmpname);
745     Inkscape::Extension::save(
746               Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
747               doc->doc(), tempfilename_in.c_str(), FALSE, FALSE, FALSE);
749     Glib::ustring local_command(command);
751     /* fixme: Should be some sort of checking here.  Don't know how to do this with structs instead
752      * of classes. */
753     SPDesktop *desktop = (SPDesktop *) doc;
754     if (desktop != NULL) {
755         Inkscape::Util::GSListConstIterator<SPItem *> selected =
756              sp_desktop_selection(desktop)->itemList();
757         while ( selected != NULL ) {
758             local_command += " --id=";
759             local_command += SP_OBJECT_ID(*selected);
760             ++selected;
761         }
762     }
764     Glib::ustring paramString = *module->paramString();
765     local_command.append(paramString);
768     // std::cout << local_command << std::endl;
770     int data_read = execute(local_command, tempfilename_in, tempfilename_out);
772     if (data_read > 10)
773         mydoc = Inkscape::Extension::open(
774               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
775               tempfilename_out.c_str());
777     // make sure we don't leak file descriptors from g_file_open_tmp
778     close(tempfd_in);
779     close(tempfd_out);
781     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
782     unlink(tempfilename_in.c_str());
783     unlink(tempfilename_out.c_str());
786     /* Do something with mydoc.... */
787     if (mydoc) {
788         doc->doc()->emitReconstructionStart();
789         copy_doc(doc->doc()->rroot, mydoc->rroot);
790         doc->doc()->emitReconstructionFinish();
791         mydoc->release();
792         sp_namedview_update_layers_from_document(desktop);
793     }
798 /**
799     \brief  A function to take all the svg elements from one document
800             and put them in another.
801     \param  oldroot  The root node of the document to be replaced
802     \param  newroot  The root node of the document to replace it with
804     This function first deletes all of the data in the old document.  It
805     does this by creating a list of what needs to be deleted, and then
806     goes through the list.  This two pass approach removes issues with
807     the list being change while parsing through it.  Lots of nasty bugs.
809     Then, it goes through the new document, duplicating all of the
810     elements and putting them into the old document.  The copy
811     is then complete.
812 */
813 void
814 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
816     std::vector<Inkscape::XML::Node *> delete_list;
817     for (Inkscape::XML::Node * child = oldroot->firstChild();
818             child != NULL;
819             child = child->next()) {
820         if (!strcmp("sodipodi:namedview", child->name()))
821             continue;
822         delete_list.push_back(child);
823     }
824     for (unsigned int i = 0; i < delete_list.size(); i++)
825         sp_repr_unparent(delete_list[i]);
827     for (Inkscape::XML::Node * child = newroot->firstChild();
828             child != NULL;
829             child = child->next()) {
830         if (!strcmp("sodipodi:namedview", child->name()))
831             continue;
832         oldroot->appendChild(child->duplicate());
833     }
835     /** \todo  Restore correct layer */
836     /** \todo  Restore correct selection */
841 /* Helper class used by Script::execute */
842 class pipe_t {
843 public:
844     /* These functions set errno if they return false.
845        I'm not sure whether that's a good idea or not, but it should be reasonably
846        straightforward to change it if needed. */
847     bool open(const Glib::ustring &command,
848               const Glib::ustring &errorFile,
849               int mode);
850     bool close();
852     /* These return the number of bytes read/written. */
853     size_t read(void *buffer, size_t size);
854     size_t write(void const *buffer, size_t size);
856     enum {
857         mode_read  = 1 << 0,
858         mode_write = 1 << 1,
859     };
861 private:
862 #ifdef WIN32
863     /* This is used to translate win32 errors into errno errors.
864        It only recognizes a few win32 errors for the moment though. */
865     static int translate_error(DWORD err);
867     HANDLE hpipe;
868 #else
869     FILE *ppipe;
870 #endif
871 };
876 /**
877     \return   none
878     \brief    This is the core of the extension file as it actually does
879               the execution of the extension.
880     \param    in_command  The command to be executed
881     \param    filein      Filename coming in
882     \param    fileout     Filename of the out file
883     \return   Number of bytes that were read into the output file.
885     The first thing that this function does is build the command to be
886     executed.  This consists of the first string (in_command) and then
887     the filename for input (filein).  This file is put on the command
888     line.
890     The next thing is that this function does is open a pipe to the
891     command and get the file handle in the ppipe variable.  It then
892     opens the output file with the output file handle.  Both of these
893     operations are checked extensively for errors.
895     After both are opened, then the data is copied from the output
896     of the pipe into the file out using fread and fwrite.  These two
897     functions are used because of their primitive nature they make
898     no assumptions about the data.  A buffer is used in the transfer,
899     but the output of fread is stored so the exact number of bytes
900     is handled gracefully.
902     At the very end (after the data has been copied) both of the files
903     are closed, and we return to what we were doing.
904 */
905 int
906 Script::execute (const Glib::ustring &in_command,
907                  const Glib::ustring &filein,
908                  const Glib::ustring &fileout)
910     g_return_val_if_fail(in_command.size() > 0, 0);
911     // printf("Executing: %s\n", in_command);
913     gchar *tmpname;
914     gint errorFileNum;
915     errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &tmpname, NULL);
916     if (errorFileNum != 0) {
917         close(errorFileNum);
918     } else {
919         g_free(tmpname);
920     }
922     Glib::ustring errorFile = tmpname;
923     g_free(tmpname);
925     Glib::ustring localCommand = in_command;
926     localCommand .append(" \"");
927     localCommand .append(filein);
928     localCommand .append("\"");
930     // std::cout << "Command to run: " << command << std::endl;
932     pipe_t pipe;
933     bool open_success = pipe.open((char *)localCommand.c_str(),
934                                   errorFile.c_str(),
935                                   pipe_t::mode_read);
937     /* Run script */
938     if (!open_success) {
939         /* Error - could not open pipe - check errno */
940         if (errno == EINVAL) {
941             perror("Extension::Script:  Invalid mode argument in popen\n");
942         } else if (errno == ECHILD) {
943             perror("Extension::Script:  Cannot obtain child extension status in popen\n");
944         } else {
945             perror("Extension::Script:  Unknown error for popen\n");
946         }
947         return 0;
948     }
950     Inkscape::IO::dump_fopen_call(fileout.c_str(), "J");
951     FILE *pfile = Inkscape::IO::fopen_utf8name(fileout.c_str(), "w");
953     if (pfile == NULL) {
954         /* Error - could not open file */
955         if (errno == EINVAL) {
956             perror("Extension::Script:  The mode provided to fopen was invalid\n");
957         } else {
958             perror("Extension::Script:  Unknown error attempting to open temporary file\n");
959         }
960         return 0;
961     }
963     /* Copy pipe output to a temporary file */
964     int amount_read = 0;
965     char buf[BUFSIZE];
966     int num_read;
967     while ((num_read = pipe.read(buf, BUFSIZE)) != 0) {
968         amount_read += num_read;
969         fwrite(buf, 1, num_read, pfile);
970     }
972     /* Close file */
973     if (fclose(pfile) == EOF) {
974         if (errno == EBADF) {
975             perror("Extension::Script:  The filedescriptor for the temporary file is invalid\n");
976             return 0;
977         } else {
978             perror("Extension::Script:  Unknown error closing temporary file\n");
979         }
980     }
982     /* Close pipe */
983     if (!pipe.close()) {
984         if (errno == EINVAL) {
985             perror("Extension::Script:  Invalid mode set for pclose\n");
986         } else if (errno == ECHILD) {
987             perror("Extension::Script:  Could not obtain child status for pclose\n");
988         } else {
989             if (!errorFile.empty()) {
990                 checkStderr(errorFile, Gtk::MESSAGE_ERROR,
991                     _("Inkscape has received an error from the script that it called.  "
992                       "The text returned with the error is included below.  "
993                       "Inkscape will continue working, but the action you requested has been cancelled."));
994             } else {
995                 perror("Extension::Script:  Unknown error for pclose\n");
996             }
997         }
998         /* Could be a lie, but if there is an error, we don't want
999          * to count on what was read being good */
1000         amount_read = 0;
1001     } else {
1002         if (errorFile.size()>0) {
1003             checkStderr(errorFile, Gtk::MESSAGE_INFO,
1004                 _("Inkscape has received additional data from the script executed.  "
1005                   "The script did not return an error, but this may indicate the results will not be as expected."));
1006         }
1007     }
1009     if (errorFile.size()>0) {
1010         unlink(errorFile.c_str());
1011     }
1013     return amount_read;
1019 /**  \brief  This function checks the stderr file, and if it has data,
1020              shows it in a warning dialog to the user
1021      \param  filename  Filename of the stderr file
1022 */
1023 void
1024 Script::checkStderr (const Glib::ustring &filename,
1025                      Gtk::MessageType type,
1026                      const Glib::ustring &message)
1029     // magic win32 crlf->lf conversion means the file length is not the same as
1030     // the text length, but luckily gtk will accept crlf in textviews so we can
1031     // just use binary mode
1032     std::ifstream stderrf (filename.c_str(), std::ios_base::in | std::ios_base::binary);
1033     if (!stderrf.is_open()) return;
1035     stderrf.seekg(0, std::ios::end);
1036     int length = stderrf.tellg();
1037     if (0 == length) return;
1038     stderrf.seekg(0, std::ios::beg);
1040     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
1041     warning.set_resizable(true);
1043     Gtk::VBox * vbox = warning.get_vbox();
1045     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
1046     Gtk::TextView * textview = new Gtk::TextView();
1047     textview->set_editable(false);
1048     textview->set_wrap_mode(Gtk::WRAP_WORD);
1049     textview->show();
1051     char * buffer = new char [length];
1052     stderrf.read(buffer, length);
1053     textview->get_buffer()->set_text(buffer, buffer + length);
1054     delete buffer;
1055     stderrf.close();
1057     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
1058     scrollwindow->add(*textview);
1059     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
1060     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
1061     scrollwindow->show();
1063     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
1065     warning.run();
1067     return;
1073 #ifdef WIN32
1076 bool pipe_t::open(const Glib::ustring &command,
1077                   const Glib::ustring &errorFile,
1078                   int mode_p) {
1079     HANDLE pipe_write;
1081     //###############  Create pipe
1082     SECURITY_ATTRIBUTES secattrs;
1083     ZeroMemory(&secattrs, sizeof(secattrs));
1084     secattrs.nLength = sizeof(secattrs);
1085     secattrs.lpSecurityDescriptor = 0;
1086     secattrs.bInheritHandle = TRUE;
1087     HANDLE t_pipe_read = 0;
1088     if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) {
1089         errno = translate_error(GetLastError());
1090         return false;
1091     }
1092     // This duplicate handle makes the read pipe uninheritable
1093     BOOL ret = DuplicateHandle(GetCurrentProcess(),
1094                                t_pipe_read,
1095                                GetCurrentProcess(),
1096                                &hpipe, 0, FALSE,
1097                                DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1098     if (!ret) {
1099         int en = translate_error(GetLastError());
1100         CloseHandle(t_pipe_read);
1101         CloseHandle(pipe_write);
1102         errno = en;
1103         return false;
1104     }
1106     //############### Open stderr file
1107     HANDLE hStdErrFile = CreateFile(errorFile.c_str(),
1108                       GENERIC_WRITE,
1109                       FILE_SHARE_READ | FILE_SHARE_WRITE,
1110                       NULL, CREATE_ALWAYS, 0, NULL);
1111     HANDLE hInheritableStdErr;
1112     DuplicateHandle(GetCurrentProcess(),
1113                     hStdErrFile,
1114                     GetCurrentProcess(),
1115                     &hInheritableStdErr,
1116                     0, 
1117                     TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1119     //############### Create process
1120     PROCESS_INFORMATION procinfo;
1121     STARTUPINFO startupinfo;
1122     ZeroMemory(&procinfo, sizeof(procinfo));
1123     ZeroMemory(&startupinfo, sizeof(startupinfo));
1124     startupinfo.cb = sizeof(startupinfo);
1125     //startupinfo.lpReserved = 0;
1126     //startupinfo.lpDesktop = 0;
1127     //startupinfo.lpTitle = 0;
1128     startupinfo.dwFlags = STARTF_USESTDHANDLES;
1129     startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
1130     startupinfo.hStdOutput = pipe_write;
1131     startupinfo.hStdError = hInheritableStdErr;
1133     if ( !CreateProcess(NULL, (CHAR *)command.c_str(),
1134                         NULL, NULL, TRUE,
1135                         0, NULL, NULL,
1136                         &startupinfo, &procinfo) ) {
1137         errno = translate_error(GetLastError());
1138         return false;
1139     }
1140     CloseHandle(procinfo.hThread);
1141     CloseHandle(procinfo.hProcess);
1143     // Close our copy of the write handle
1144     CloseHandle(hInheritableStdErr);
1145     CloseHandle(pipe_write);
1147     return true;
1152 bool pipe_t::close() {
1153     BOOL retval = CloseHandle(hpipe);
1154     if ( !retval )
1155         errno = translate_error(GetLastError());
1156     return retval != FALSE;
1159 size_t pipe_t::read(void *buffer, size_t size) {
1160     DWORD bytes_read = 0;
1161     ReadFile(hpipe, buffer, size, &bytes_read, 0);
1162     return bytes_read;
1165 size_t pipe_t::write(void const *buffer, size_t size) {
1166     DWORD bytes_written = 0;
1167     WriteFile(hpipe, buffer, size, &bytes_written, 0);
1168     return bytes_written;
1171 int pipe_t::translate_error(DWORD err) {
1172     switch (err) {
1173         case ERROR_FILE_NOT_FOUND:
1174             return ENOENT;
1175         case ERROR_INVALID_HANDLE:
1176         case ERROR_INVALID_PARAMETER:
1177             return EINVAL;
1178         default:
1179             return 0;
1180     }
1184 #else // not Win32
1187 bool pipe_t::open(const Glib::ustring &command,
1188                   const Glib::ustring &errorFile,
1189                   int mode_p) {
1191     Glib::ustring popen_mode;
1193     if ( (mode_p & mode_read) != 0 )
1194         popen_mode.append("r");
1196     if ( (mode_p & mode_write) != 0 )
1197         popen_mode.append("w");
1199     // Get the commandline to be run
1200     Glib::ustring pipeStr = command;
1201     if (errorFile.size()>0) {
1202         pipeStr .append(" 2> ");
1203         pipeStr .append(errorFile);
1204     }
1206     ppipe = popen(pipeStr.c_str(), popen_mode.c_str());
1208     return ppipe != NULL;
1212 bool pipe_t::close() {
1213     return fclose(ppipe) == 0;
1217 size_t pipe_t::read(void *buffer, size_t size) {
1218     return fread(buffer, 1, size, ppipe);
1222 size_t pipe_t::write(void const *buffer, size_t size) {
1223     return fwrite(buffer, 1, size, ppipe);
1229 #endif // (Non-)Win32
1234 }  // namespace Implementation
1235 }  // namespace Extension
1236 }  // namespace Inkscape
1241 /*
1242   Local Variables:
1243   mode:c++
1244   c-file-style:"stroustrup"
1245   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1246   indent-tabs-mode:nil
1247   fill-column:99
1248   End:
1249 */
1250 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :