Code

r11676@tres: ted | 2006-05-05 21:45:20 -0700
[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/textview.h>
24 #include <gtkmm/scrolledwindow.h>
26 #include "ui/view/view.h"
27 #include "desktop-handles.h"
28 #include "selection.h"
29 #include "sp-namedview.h"
30 #include "io/sys.h"
31 #include "prefs-utils.h"
32 #include "../system.h"
33 #include "extension/effect.h"
34 #include "extension/db.h"
35 #include "script.h"
37 #include "util/glib-list-iterators.h"
39 #ifdef WIN32
40 #include <windows.h>
41 #endif
43 /** This is the command buffer that gets allocated from the stack */
44 #define BUFSIZE (255)
46 /* Namespaces */
47 namespace Inkscape {
48 namespace Extension {
49 namespace Implementation {
51 /* Real functions */
52 /**
53     \return    A script object
54     \brief     This function creates a script object and sets up the
55                variables.
57    This function just sets the command to NULL.  It should get built
58    officially in the load function.  This allows for less allocation
59    of memory in the unloaded state.
60 */
61 Script::Script() :
62     Implementation(),
63     command(NULL),
64     helper_extension(NULL)
65 {
66 }
68 /**
69     \return    A string with the complete string with the relative directory expanded
70     \brief     This function takes in a Repr that contains a reldir entry
71                and returns that data with the relative directory expanded.
72                Mostly it is here so that relative directories all get used
73                the same way.
74     \param     reprin   The Inkscape::XML::Node with the reldir in it.
76     Basically this function looks at an attribute of the Repr, and makes
77     a decision based on that.  Currently, it is only working with the
78     'extensions' relative directory, but there will be more of them.
79     One thing to notice is that this function always returns an allocated
80     string.  This means that the caller of this function can always
81     free what they are given (and should do it too!).
82 */
83 gchar *
84 Script::solve_reldir(Inkscape::XML::Node *reprin) {
85     gchar const *reldir = reprin->attribute("reldir");
87     if (reldir == NULL) {
88         return g_strdup(sp_repr_children(reprin)->content());
89     }
91     if (!strcmp(reldir, "extensions")) {
92         for(unsigned int i=0; i<Inkscape::Extension::Extension::search_path.size(); i++) {
93             gchar * filename = g_build_filename(Inkscape::Extension::Extension::search_path[i], sp_repr_children(reprin)->content(), NULL);
94             if ( Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS) ) {
95                 return filename;
96             }
97             g_free(filename);
98         }
99     } else {
100         return g_strdup(sp_repr_children(reprin)->content());
101     }
103     return NULL;
106 /**
107     \return   Whether the command given exists, including in the path
108     \brief    This function is used to find out if something exists for
109               the check command.  It can look in the path if required.
110     \param    command   The command or file that should be looked for
112     The first thing that this function does is check to see if the
113     incoming file name has a directory delimiter in it.  This would
114     mean that it wants to control the directories, and should be
115     used directly.
117     If not, the path is used.  Each entry in the path is stepped through,
118     attached to the string, and then tested.  If the file is found
119     then a TRUE is returned.  If we get all the way through the path
120     then a FALSE is returned, the command could not be found.
121 */
122 bool
123 Script::check_existance(gchar const *command)
125     if (*command == '\0') {
126         /* We check the simple case first. */
127         return FALSE;
128     }
130     if (g_utf8_strchr(command, -1, G_DIR_SEPARATOR) != NULL) {
131         /* Don't search when it contains a slash. */
132         if (Inkscape::IO::file_test(command, G_FILE_TEST_EXISTS))
133             return TRUE;
134         else
135             return FALSE;
136     }
139     gchar *path = g_strdup(g_getenv("PATH"));
140     if (path == NULL) {
141         /* There is no `PATH' in the environment.
142            The default search path is the current directory */
143         path = g_strdup(G_SEARCHPATH_SEPARATOR_S);
144     }
145     gchar *orig_path = path;
147     for (; path != NULL;) {
148         gchar *const local_path = path;
149         path = g_utf8_strchr(path, -1, G_SEARCHPATH_SEPARATOR);
150         if (path == NULL) {
151             break;
152         }
153         /* Not sure whether this is UTF8 happy, but it would seem
154            like it considering that I'm searching (and finding)
155            the ':' character */
156         if (path != local_path && path != NULL) {
157             path[0] = '\0';
158             path++;
159         } else {
160             path = NULL;
161         }
163         gchar *final_name;
164         if (local_path == '\0') {
165             final_name = g_strdup(command);
166         } else {
167             final_name = g_build_filename(local_path, command, NULL);
168         }
170         if (Inkscape::IO::file_test(final_name, G_FILE_TEST_EXISTS)) {
171             g_free(final_name);
172             g_free(orig_path);
173             return TRUE;
174         }
176         g_free(final_name);
177     }
179     return FALSE;
182 /**
183     \return   none
184     \brief    This function 'loads' an extention, basically it determines
185               the full command for the extention and stores that.
186     \param    module  The extention to be loaded.
188     The most difficult part about this function is finding the actual
189     command through all of the Reprs.  Basically it is hidden down a
190     couple of layers, and so the code has to move down too.  When
191     the command is actually found, it has its relative directory
192     solved.
194     At that point all of the loops are exited, and there is an
195     if statement to make sure they didn't exit because of not finding
196     the command.  If that's the case, the extention doesn't get loaded
197     and should error out at a higher level.
198 */
200 bool
201 Script::load(Inkscape::Extension::Extension *module)
203     if (module->loaded()) {
204         return TRUE;
205     }
207     helper_extension = NULL;
209     /* This should probably check to find the executable... */
210     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
211     gchar *command_text = NULL;
212     while (child_repr != NULL) {
213         if (!strcmp(child_repr->name(), "script")) {
214             child_repr = sp_repr_children(child_repr);
215             while (child_repr != NULL) {
216                 if (!strcmp(child_repr->name(), "command")) {
217                     command_text = solve_reldir(child_repr);
219                     const gchar * interpretstr = child_repr->attribute("interpreter");
220                     if (interpretstr != NULL) {
221                         struct interpreter_t {
222                             gchar * identity;
223                             gchar * prefstring;
224                             gchar * defaultval;
225                         };
226                         const interpreter_t interpreterlst[] = {
227                             {"perl", "perl-interpreter", "perl"},
228                             {"python", "python-interpreter", "python"},
229                             {"ruby", "ruby-interpreter", "ruby"},
230                             {"shell", "shell-interpreter", "sh"}
231                         }; /* Change count below if you change structure */
232                         for (unsigned int i = 0; i < 4; i++) {
233                             if (!strcmp(interpretstr, interpreterlst[i].identity)) {
234                                 const gchar * insertText = interpreterlst[i].defaultval;
235                                 if (prefs_get_string_attribute("extensions", interpreterlst[i].prefstring) != NULL)
236                                     insertText = prefs_get_string_attribute("extensions", interpreterlst[i].prefstring);
237 #ifdef _WIN32
238                                 else {
239                                     char szExePath[MAX_PATH];
240                                     char szCurrentDir[MAX_PATH];
241                                     GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
242                                     if (reinterpret_cast<unsigned>(FindExecutable(command_text, szCurrentDir, szExePath)) > 32)
243                                         insertText = szExePath;
244                                 }
245 #endif
247                                 gchar * temp = command_text;
248                                 command_text = g_strconcat(insertText, " ", temp, NULL);
249                                 g_free(temp);
251                                 break;
252                             }
253                         }
254                     }
255                 }
256                 if (!strcmp(child_repr->name(), "helper_extension")) {
257                     helper_extension = g_strdup(sp_repr_children(child_repr)->content());
258                 }
259                 child_repr = sp_repr_next(child_repr);
260             }
262             break;
263         }
264         child_repr = sp_repr_next(child_repr);
265     }
267     g_return_val_if_fail(command_text != NULL, FALSE);
269     if (command != NULL)
270         g_free(command);
271     command = command_text;
273     return TRUE;
276 /**
277     \return   None.
278     \brief    Unload this puppy!
279     \param    module  Extension to be unloaded.
281     This function just sets the module to unloaded.  It free's the
282     command if it has been allocated.
283 */
284 void
285 Script::unload(Inkscape::Extension::Extension *module)
287     if (command != NULL) {
288         g_free(command);
289         command = NULL;
290     }
291     if (helper_extension != NULL) {
292         g_free(helper_extension);
293         helper_extension = NULL;
294     }
296     return;
299 /**
300     \return   Whether the check passed or not
301     \brief    Check every dependency that was given to make sure we should keep this extension
302     \param    module  The Extension in question
304 */
305 bool
306 Script::check(Inkscape::Extension::Extension *module)
308     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
309     while (child_repr != NULL) {
310         if (!strcmp(child_repr->name(), "script")) {
311             child_repr = sp_repr_children(child_repr);
312             while (child_repr != NULL) {
313                 if (!strcmp(child_repr->name(), "check")) {
314                     gchar *command_text = solve_reldir(child_repr);
315                     if (command_text != NULL) {
316                         /* I've got the command */
317                         bool existance;
319                         existance = check_existance(command_text);
320                         g_free(command_text);
321                         if (!existance)
322                             return FALSE;
323                     }
324                 }
326                 if (!strcmp(child_repr->name(), "helper_extension")) {
327                     gchar const *helper = sp_repr_children(child_repr)->content();
328                     if (Inkscape::Extension::db.get(helper) == NULL) {
329                         return FALSE;
330                     }
331                 }
333                 child_repr = sp_repr_next(child_repr);
334             }
336             break;
337         }
338         child_repr = sp_repr_next(child_repr);
339     }
341     return TRUE;
344 /**
345     \return   A dialog for preferences
346     \brief    A stub funtion right now
347     \param    module    Module who's preferences need getting
348     \param    filename  Hey, the file you're getting might be important
350     This function should really do something, right now it doesn't.
351 */
352 Gtk::Widget *
353 Script::prefs_input(Inkscape::Extension::Input *module, gchar const *filename)
355     /*return module->autogui(); */
356     return NULL;
359 /**
360     \return   A dialog for preferences
361     \brief    A stub funtion right now
362     \param    module    Module whose preferences need getting
364     This function should really do something, right now it doesn't.
365 */
366 Gtk::Widget *
367 Script::prefs_output(Inkscape::Extension::Output *module)
369     /*return module->autogui();*/
370     return NULL;
373 /**
374     \return   A dialog for preferences
375     \brief    A stub funtion right now
376     \param    module    Module who's preferences need getting
378     This function should really do something, right now it doesn't.
379 */
380 Gtk::Widget *
381 Script::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *view)
383     SPDocument * current_document = view->doc();
385     using Inkscape::Util::GSListConstIterator;
386     GSListConstIterator<SPItem *> selected = sp_desktop_selection((SPDesktop *)view)->itemList();
387     Inkscape::XML::Node * first_select = NULL;
388     if (selected != NULL) 
389         first_select = SP_OBJECT_REPR(*selected);
391     return module->autogui(current_document, first_select);
394 /**
395     \return  A new document that has been opened
396     \brief   This function uses a filename that is put in, and calls
397              the extension's command to create an SVG file which is
398              returned.
399     \param   module   Extension to use.
400     \param   filename File to open.
402     First things first, this function needs a temporary file name.  To
403     create on of those the function g_file_open_tmp is used with
404     the header of ink_ext_.
406     The extension is then executed using the 'execute' function
407     with the filname coming in, and the temporary filename.  After
408     That executing, the SVG should be in the temporary file.
410     Finally, the temporary file is opened using the SVG input module and
411     a document is returned.  That document has its filename set to
412     the incoming filename (so that it's not the temporary filename).
413     That document is then returned from this function.
414 */
415 SPDocument *
416 Script::open(Inkscape::Extension::Input *module, gchar const *filename)
418     int data_read = 0;
419     gint tempfd;
420     gchar *tempfilename_out;
422     // FIXME: process the GError instead of passing NULL
423     if ((tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) {
424         /* Error, couldn't create temporary filename */
425         if (errno == EINVAL) {
426             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
427             perror("Extension::Script:  template for filenames is misconfigured.\n");
428             exit(-1);
429         } else if (errno == EEXIST) {
430             /* Now the  contents of template are undefined. */
431             perror("Extension::Script:  Could not create a unique temporary filename\n");
432             return NULL;
433         } else {
434             perror("Extension::Script:  Unknown error creating temporary filename\n");
435             exit(-1);
436         }
437     }
439     gsize bytesRead = 0;
440     gsize bytesWritten = 0;
441     GError *error = NULL;
442     gchar *local_filename = g_filename_from_utf8( filename,
443                                                   -1,  &bytesRead,  &bytesWritten, &error);
445     data_read = execute(command, local_filename, tempfilename_out);
446     g_free(local_filename);
448     SPDocument *mydoc = NULL;
449     if (data_read > 10) {
450         if (helper_extension == NULL) {
451             mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out);
452         } else {
453             mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(helper_extension), tempfilename_out);
454         }
455     }
457     if (mydoc != NULL)
458         sp_document_set_uri(mydoc, (const gchar *)filename);
460     // make sure we don't leak file descriptors from g_file_open_tmp
461     close(tempfd);
462     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
463     unlink(tempfilename_out);
464     g_free(tempfilename_out);
466     return mydoc;
469 /**
470     \return   none
471     \brief    This function uses an extention to save a document.  It first
472               creates an SVG file of the document, and then runs it through
473               the script.
474     \param    module    Extention to be used
475     \param    doc       Document to be saved
476     \param    filename  The name to save the final file as
478     Well, at some point people need to save - it is really what makes
479     the entire application useful.  And, it is possible that someone
480     would want to use an extetion for this, so we need a function to
481     do that eh?
483     First things first, the document is saved to a temporary file that
484     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
485     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
486     end of the function.
488     After we have the SVG file, then extention_execute is called with
489     the temporary file name and the final output filename.  This should
490     put the output of the script into the final output file.  We then
491     delete the temporary file.
492 */
493 void
494 Script::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename)
496     gint tempfd;
497     gchar *tempfilename_in;
498     // FIXME: process the GError instead of passing NULL
499     if ((tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) {
500         /* Error, couldn't create temporary filename */
501         if (errno == EINVAL) {
502             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
503             perror("Extension::Script:  template for filenames is misconfigured.\n");
504             exit(-1);
505         } else if (errno == EEXIST) {
506             /* Now the  contents of template are undefined. */
507             perror("Extension::Script:  Could not create a unique temporary filename\n");
508             return;
509         } else {
510             perror("Extension::Script:  Unknown error creating temporary filename\n");
511             exit(-1);
512         }
513     }
515     if (helper_extension == NULL) {
516         Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), doc, tempfilename_in, FALSE, FALSE, FALSE);
517     } else {
518         Inkscape::Extension::save(Inkscape::Extension::db.get(helper_extension), doc, tempfilename_in, FALSE, FALSE, FALSE);
519     }
521     gsize bytesRead = 0;
522     gsize bytesWritten = 0;
523     GError *error = NULL;
524     gchar *local_filename = g_filename_from_utf8( filename,
525                                                   -1,  &bytesRead,  &bytesWritten, &error);
527     execute(command, tempfilename_in, local_filename);
529     g_free(local_filename);
531     // make sure we don't leak file descriptors from g_file_open_tmp
532     close(tempfd);
533     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
534     unlink(tempfilename_in);
535     g_free(tempfilename_in);
538 /**
539     \return    none
540     \brief     This function uses an extention as a effect on a document.
541     \param     module   Extention to effect with.
542     \param     doc      Document to run through the effect.
544     This function is a little bit trickier than the previous two.  It
545     needs two temporary files to get it's work done.  Both of these
546     files have random names created for them using the g_file_open_temp function
547     with the sp_ext_ prefix in the temporary directory.  Like the other
548     functions, the temporary files are deleted at the end.
550     To save/load the two temporary documents (both are SVG) the internal
551     modules for SVG load and save are used.  They are both used through
552     the module system function by passing their keys into the functions.
554     The command itself is built a little bit differently than in other
555     functions because the effect support selections.  So on the command
556     line a list of all the ids that are selected is included.  Currently,
557     this only works for a single selected object, but there will be more.
558     The command string is filled with the data, and then after the execution
559     it is freed.
561     The execute function is used at the core of this function
562     to execute the Script on the two SVG documents (actually only one
563     exists at the time, the other is created by that script).  At that
564     point both should be full, and the second one is loaded.
565 */
566 void
567 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
569     int data_read = 0;
570     SPDocument * mydoc = NULL;
571     gint tempfd_in;
572     gchar *tempfilename_in;
574     // FIXME: process the GError instead of passing NULL
575     if ((tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) {
576         /* Error, couldn't create temporary filename */
577         if (errno == EINVAL) {
578             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
579             perror("Extension::Script:  template for filenames is misconfigured.\n");
580             exit(-1);
581         } else if (errno == EEXIST) {
582             /* Now the  contents of template are undefined. */
583             perror("Extension::Script:  Could not create a unique temporary filename\n");
584             return;
585         } else {
586             perror("Extension::Script:  Unknown error creating temporary filename\n");
587             exit(-1);
588         }
589     }
591     gint tempfd_out;
592     gchar *tempfilename_out;
593     // FIXME: process the GError instead of passing NULL
594     if ((tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) {
595         /* Error, couldn't create temporary filename */
596         if (errno == EINVAL) {
597             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
598             perror("Extension::Script:  template for filenames is misconfigured.\n");
599             exit(-1);
600         } else if (errno == EEXIST) {
601             /* Now the  contents of template are undefined. */
602             perror("Extension::Script:  Could not create a unique temporary filename\n");
603             return;
604         } else {
605             perror("Extension::Script:  Unknown error creating temporary filename\n");
606             exit(-1);
607         }
608     }
610     Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
611                               doc->doc(), tempfilename_in, FALSE, FALSE, FALSE);
613     Glib::ustring local_command(command);
615     /* fixme: Should be some sort of checking here.  Don't know how to do this with structs instead
616      * of classes. */
617     SPDesktop *desktop = (SPDesktop *) doc;
618     if (desktop != NULL) {
619         using Inkscape::Util::GSListConstIterator;
620         GSListConstIterator<SPItem *> selected = sp_desktop_selection(desktop)->itemList();
621         while ( selected != NULL ) {
622             local_command += " --id=";
623             local_command += SP_OBJECT_ID(*selected);
624             ++selected;
625         }
626     }
628     Glib::ustring * paramString = module->paramString();
629     local_command += *paramString;
630     delete paramString;
632     // std::cout << local_command << std::endl;
634     data_read = execute(local_command.c_str(), tempfilename_in, tempfilename_out);
636     if (data_read > 10)
637         mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out);
639     // make sure we don't leak file descriptors from g_file_open_tmp
640     close(tempfd_in);
641     close(tempfd_out);
642     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
643     unlink(tempfilename_in);
644     g_free(tempfilename_in);
645     unlink(tempfilename_out);
646     g_free(tempfilename_out);
648     /* Do something with mydoc.... */
649     if (mydoc != NULL) {
650         doc->doc()->emitReconstructionStart();
651         copy_doc(doc->doc()->rroot, mydoc->rroot);
652         doc->doc()->emitReconstructionFinish();
653         mydoc->release();
654     }
658 /**
659     \brief  A function to take all the svg elements from one document
660             and put them in another.
661     \param  oldroot  The root node of the document to be replaced
662     \param  newroot  The root node of the document to replace it with
664     This function first deletes all of the data in the old document.  It
665     does this by creating a list of what needs to be deleted, and then
666     goes through the list.  This two pass approach removes issues with
667     the list being change while parsing through it.  Lots of nasty bugs.
669     Then, it goes through the new document, duplicating all of the
670     elements and putting them into the old document.  The copy
671     is then complete.
672 */
673 void
674 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
676     std::vector<Inkscape::XML::Node *> delete_list;
677     for (Inkscape::XML::Node * child = oldroot->firstChild();
678             child != NULL;
679             child = child->next()) {
680         if (!strcmp("sodipodi:namedview", child->name()))
681             continue;
682         if (!strcmp("svg:defs", child->name()))
683             continue;
684         delete_list.push_back(child);
685     }
686     for (unsigned int i = 0; i < delete_list.size(); i++)
687         sp_repr_unparent(delete_list[i]);
689     for (Inkscape::XML::Node * child = newroot->firstChild();
690             child != NULL;
691             child = child->next()) {
692         if (!strcmp("sodipodi:namedview", child->name()))
693             continue;
694         if (!strcmp("svg:defs", child->name()))
695             continue;
696         oldroot->appendChild(child->duplicate());
697     }
699     /** \todo  Restore correct layer */
700     /** \todo  Restore correct selection */
703 /* Helper class used by Script::execute */
704 class pipe_t {
705 public:
706     /* These functions set errno if they return false.
707        I'm not sure whether that's a good idea or not, but it should be reasonably
708        straightforward to change it if needed. */
709     bool open(char *command, char const *errorFile, int mode);
710     bool close();
712     /* These return the number of bytes read/written. */
713     size_t read(void *buffer, size_t size);
714     size_t write(void const *buffer, size_t size);
716     enum {
717         mode_read  = 1 << 0,
718         mode_write = 1 << 1,
719     };
721 private:
722 #ifdef WIN32
723     /* This is used to translate win32 errors into errno errors.
724        It only recognizes a few win32 errors for the moment though. */
725     static int translate_error(DWORD err);
727     HANDLE hpipe;
728 #else
729     FILE *ppipe;
730 #endif
731 };
733 /**
734     \return   none
735     \brief    This is the core of the extension file as it actually does
736               the execution of the extension.
737     \param    in_command  The command to be executed
738     \param    filein      Filename coming in
739     \param    fileout     Filename of the out file
740     \return   Number of bytes that were read into the output file.
742     The first thing that this function does is build the command to be
743     executed.  This consists of the first string (in_command) and then
744     the filename for input (filein).  This file is put on the command
745     line.
747     The next thing is that this function does is open a pipe to the
748     command and get the file handle in the ppipe variable.  It then
749     opens the output file with the output file handle.  Both of these
750     operations are checked extensively for errors.
752     After both are opened, then the data is copied from the output
753     of the pipe into the file out using fread and fwrite.  These two
754     functions are used because of their primitive nature they make
755     no assumptions about the data.  A buffer is used in the transfer,
756     but the output of fread is stored so the exact number of bytes
757     is handled gracefully.
759     At the very end (after the data has been copied) both of the files
760     are closed, and we return to what we were doing.
761 */
762 int
763 Script::execute (const gchar * in_command, const gchar * filein, const gchar * fileout)
765     g_return_val_if_fail(in_command != NULL, 0);
766     // printf("Executing: %s\n", in_command);
768     gchar * errorFile;
769     gint errorFileNum;
770     errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &errorFile, NULL);
771     if (errorFileNum != 0) {
772         close(errorFileNum);
773     } else {
774         g_free(errorFile);
775         errorFile = NULL;
776     }
778     char *command = g_strdup_printf("%s \"%s\"", in_command, filein);
779     // std::cout << "Command to run: " << command << std::endl;
781     pipe_t pipe;
782     bool open_success = pipe.open(command, errorFile, pipe_t::mode_read);
783     g_free(command);
785     /* Run script */
786     if (!open_success) {
787         /* Error - could not open pipe - check errno */
788         if (errno == EINVAL) {
789             perror("Extension::Script:  Invalid mode argument in popen\n");
790         } else if (errno == ECHILD) {
791             perror("Extension::Script:  Cannot obtain child extension status in popen\n");
792         } else {
793             perror("Extension::Script:  Unknown error for popen\n");
794         }
795         return 0;
796     }
798     Inkscape::IO::dump_fopen_call(fileout, "J");
799     FILE *pfile = Inkscape::IO::fopen_utf8name(fileout, "w");
801     if (pfile == NULL) {
802         /* Error - could not open file */
803         if (errno == EINVAL) {
804             perror("Extension::Script:  The mode provided to fopen was invalid\n");
805         } else {
806             perror("Extension::Script:  Unknown error attempting to open temporary file\n");
807         }
808         return 0;
809     }
811     /* Copy pipe output to a temporary file */
812     int amount_read = 0;
813     char buf[BUFSIZE];
814     int num_read;
815     while ((num_read = pipe.read(buf, BUFSIZE)) != 0) {
816         amount_read += num_read;
817         fwrite(buf, 1, num_read, pfile);
818     }
820     /* Close file */
821     if (fclose(pfile) == EOF) {
822         if (errno == EBADF) {
823             perror("Extension::Script:  The filedescriptor for the temporary file is invalid\n");
824             return 0;
825         } else {
826             perror("Extension::Script:  Unknown error closing temporary file\n");
827         }
828     }
830     /* Close pipe */
831     if (!pipe.close()) {
832         if (errno == EINVAL) {
833             perror("Extension::Script:  Invalid mode set for pclose\n");
834         } else if (errno == ECHILD) {
835             perror("Extension::Script:  Could not obtain child status for pclose\n");
836         } else {
837             if (errorFile != NULL) {
838                 checkStderr(errorFile, Gtk::MESSAGE_ERROR,
839                     _("Inkscape has received an error from the script that it called.  "
840                       "The text returned with the error is included below.  "
841                       "Inkscape will continue working, but the action you requested has been cancelled."));
842             } else {
843                 perror("Extension::Script:  Unknown error for pclose\n");
844             }
845         }
846         /* Could be a lie, but if there is an error, we don't want
847          * to count on what was read being good */
848         amount_read = 0;
849     } else {
850         if (errorFile != NULL) {
851             checkStderr(errorFile, Gtk::MESSAGE_INFO,
852                 _("Inkscape has received additional data from the script executed.  "
853                   "The script did not return an error, but this may indicate the results will not be as expected."));
854         }
855     }
857     if (errorFile != NULL) {
858         unlink(errorFile);
859         g_free(errorFile);
860     }
862     return amount_read;
865 /**  \brief  This function checks the stderr file, and if it has data,
866              shows it in a warning dialog to the user
867      \param  filename  Filename of the stderr file
868 */
869 void
870 Script::checkStderr (gchar * filename, Gtk::MessageType type, gchar * message)
872     // magic win32 crlf->lf conversion means the file length is not the same as
873     // the text length, but luckily gtk will accept crlf in textviews so we can
874     // just use binary mode
875     std::ifstream stderrf (filename, std::ios_base::in | std::ios_base::binary);
876     if (!stderrf.is_open()) return;
878     stderrf.seekg(0, std::ios::end);
879     int length = stderrf.tellg();
880     if (0 == length) return;
881     stderrf.seekg(0, std::ios::beg);
883     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
884     warning.set_resizable(true);
886     Gtk::VBox * vbox = warning.get_vbox();
888     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
889     Gtk::TextView * textview = new Gtk::TextView();
890     textview->set_editable(false);
891     textview->set_wrap_mode(Gtk::WRAP_WORD);
892     textview->show();
894     char * buffer = new char [length];
895     stderrf.read(buffer, length);
896     textview->get_buffer()->set_text(buffer, buffer + length);
897     delete buffer;
898     stderrf.close();
900     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
901     scrollwindow->add(*textview);
902     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
903     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
904     scrollwindow->show();
906     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
908     warning.run();
910     return;
913 #ifdef WIN32
915 bool pipe_t::open(char *command, char const *errorFile, int mode_p) {
916     HANDLE pipe_write;
918     // Create pipe
919     {
920         SECURITY_ATTRIBUTES secattrs;
921         ZeroMemory(&secattrs, sizeof(secattrs));
922         secattrs.nLength = sizeof(secattrs);
923         secattrs.lpSecurityDescriptor = 0;
924         secattrs.bInheritHandle = TRUE;
925         HANDLE t_pipe_read = 0;
926         if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) {
927             errno = translate_error(GetLastError());
928             return false;
929         }
930         // This duplicate handle makes the read pipe uninheritable
931         if ( !DuplicateHandle(GetCurrentProcess(), t_pipe_read, GetCurrentProcess(), &hpipe, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) ) {
932             int en = translate_error(GetLastError());
933             CloseHandle(t_pipe_read);
934             CloseHandle(pipe_write);
935             errno = en;
936             return false;
937         }
938     }
939     // Open stderr file
940     HANDLE hStdErrFile = CreateFile(errorFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
941     HANDLE hInheritableStdErr;
942     DuplicateHandle(GetCurrentProcess(), hStdErrFile, GetCurrentProcess(), &hInheritableStdErr, 0, TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
944     // Create process
945     {
946         PROCESS_INFORMATION procinfo;
947         STARTUPINFO startupinfo;
948         ZeroMemory(&procinfo, sizeof(procinfo));
949         ZeroMemory(&startupinfo, sizeof(startupinfo));
950         startupinfo.cb = sizeof(startupinfo);
951         //startupinfo.lpReserved = 0;
952         //startupinfo.lpDesktop = 0;
953         //startupinfo.lpTitle = 0;
954         startupinfo.dwFlags = STARTF_USESTDHANDLES;
955         startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
956         startupinfo.hStdOutput = pipe_write;
957         startupinfo.hStdError = hInheritableStdErr;
959         if ( !CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &startupinfo, &procinfo) ) {
960             errno = translate_error(GetLastError());
961             return false;
962         }
963         CloseHandle(procinfo.hThread);
964         CloseHandle(procinfo.hProcess);
965     }
967     // Close our copy of the write handle
968     CloseHandle(hInheritableStdErr);
969     CloseHandle(pipe_write);
971     return true;
974 bool pipe_t::close() {
975     BOOL retval = CloseHandle(hpipe);
976     if ( !retval ) {
977         errno = translate_error(GetLastError());
978     }
979     return retval != FALSE;
982 size_t pipe_t::read(void *buffer, size_t size) {
983     DWORD bytes_read = 0;
984     ReadFile(hpipe, buffer, size, &bytes_read, 0);
985     return bytes_read;
988 size_t pipe_t::write(void const *buffer, size_t size) {
989     DWORD bytes_written = 0;
990     WriteFile(hpipe, buffer, size, &bytes_written, 0);
991     return bytes_written;
994 int pipe_t::translate_error(DWORD err) {
995     switch (err) {
996         case ERROR_FILE_NOT_FOUND:
997             return ENOENT;
998         case ERROR_INVALID_HANDLE:
999         case ERROR_INVALID_PARAMETER:
1000             return EINVAL;
1001         default:
1002             return 0;
1003     }
1006 #else // Win32
1008 bool pipe_t::open(char *command, char const *errorFile, int mode_p) {
1009     char popen_mode[4] = {0,0,0,0};
1010     char *popen_mode_cur = popen_mode;
1012     if ( (mode_p & mode_read) != 0 ) {
1013         *popen_mode_cur++ = 'r';
1014     }
1016     if ( (mode_p & mode_write) != 0 ) {
1017         *popen_mode_cur++ = 'w';
1018     }
1020     /* Get the commandline to be run */
1021     if (errorFile != NULL) {
1022         char * temp;
1023         temp = g_strdup_printf("%s 2> %s", command, errorFile);
1024         ppipe = popen(temp, popen_mode);
1025         g_free(temp);
1026     } else
1027         ppipe = popen(command, popen_mode);
1029     return ppipe != NULL;
1032 bool pipe_t::close() {
1033     return fclose(ppipe) == 0;
1036 size_t pipe_t::read(void *buffer, size_t size) {
1037     return fread(buffer, 1, size, ppipe);
1040 size_t pipe_t::write(void const *buffer, size_t size) {
1041     return fwrite(buffer, 1, size, ppipe);
1044 #endif // (Non-)Win32
1047 }  /* Inkscape  */
1048 }  /* module  */
1049 }  /* Implementation  */
1052 /*
1053   Local Variables:
1054   mode:c++
1055   c-file-style:"stroustrup"
1056   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1057   indent-tabs-mode:nil
1058   fill-column:99
1059   End:
1060 */
1061 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :