Code

Fixes bug #1495310, allowing parameters for output extensions.
[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/output.h"
35 #include "extension/db.h"
36 #include "script.h"
38 #include "util/glib-list-iterators.h"
40 #ifdef WIN32
41 #include <windows.h>
42 #endif
44 /** This is the command buffer that gets allocated from the stack */
45 #define BUFSIZE (255)
47 /* Namespaces */
48 namespace Inkscape {
49 namespace Extension {
50 namespace Implementation {
52 /* Real functions */
53 /**
54     \return    A script object
55     \brief     This function creates a script object and sets up the
56                variables.
58    This function just sets the command to NULL.  It should get built
59    officially in the load function.  This allows for less allocation
60    of memory in the unloaded state.
61 */
62 Script::Script() :
63     Implementation(),
64     command(NULL),
65     helper_extension(NULL)
66 {
67 }
69 /**
70     \return    A string with the complete string with the relative directory expanded
71     \brief     This function takes in a Repr that contains a reldir entry
72                and returns that data with the relative directory expanded.
73                Mostly it is here so that relative directories all get used
74                the same way.
75     \param     reprin   The Inkscape::XML::Node with the reldir in it.
77     Basically this function looks at an attribute of the Repr, and makes
78     a decision based on that.  Currently, it is only working with the
79     'extensions' relative directory, but there will be more of them.
80     One thing to notice is that this function always returns an allocated
81     string.  This means that the caller of this function can always
82     free what they are given (and should do it too!).
83 */
84 gchar *
85 Script::solve_reldir(Inkscape::XML::Node *reprin) {
86     gchar const *reldir = reprin->attribute("reldir");
88     if (reldir == NULL) {
89         return g_strdup(sp_repr_children(reprin)->content());
90     }
92     if (!strcmp(reldir, "extensions")) {
93         for(unsigned int i=0; i<Inkscape::Extension::Extension::search_path.size(); i++) {
94             gchar * filename = g_build_filename(Inkscape::Extension::Extension::search_path[i], sp_repr_children(reprin)->content(), NULL);
95             if ( Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS) ) {
96                 return filename;
97             }
98             g_free(filename);
99         }
100     } else {
101         return g_strdup(sp_repr_children(reprin)->content());
102     }
104     return NULL;
107 /**
108     \return   Whether the command given exists, including in the path
109     \brief    This function is used to find out if something exists for
110               the check command.  It can look in the path if required.
111     \param    command   The command or file that should be looked for
113     The first thing that this function does is check to see if the
114     incoming file name has a directory delimiter in it.  This would
115     mean that it wants to control the directories, and should be
116     used directly.
118     If not, the path is used.  Each entry in the path is stepped through,
119     attached to the string, and then tested.  If the file is found
120     then a TRUE is returned.  If we get all the way through the path
121     then a FALSE is returned, the command could not be found.
122 */
123 bool
124 Script::check_existance(gchar const *command)
126     if (*command == '\0') {
127         /* We check the simple case first. */
128         return FALSE;
129     }
131     if (g_utf8_strchr(command, -1, G_DIR_SEPARATOR) != NULL) {
132         /* Don't search when it contains a slash. */
133         if (Inkscape::IO::file_test(command, G_FILE_TEST_EXISTS))
134             return TRUE;
135         else
136             return FALSE;
137     }
140     gchar *path = g_strdup(g_getenv("PATH"));
141     if (path == NULL) {
142         /* There is no `PATH' in the environment.
143            The default search path is the current directory */
144         path = g_strdup(G_SEARCHPATH_SEPARATOR_S);
145     }
146     gchar *orig_path = path;
148     for (; path != NULL;) {
149         gchar *const local_path = path;
150         path = g_utf8_strchr(path, -1, G_SEARCHPATH_SEPARATOR);
151         if (path == NULL) {
152             break;
153         }
154         /* Not sure whether this is UTF8 happy, but it would seem
155            like it considering that I'm searching (and finding)
156            the ':' character */
157         if (path != local_path && path != NULL) {
158             path[0] = '\0';
159             path++;
160         } else {
161             path = NULL;
162         }
164         gchar *final_name;
165         if (local_path == '\0') {
166             final_name = g_strdup(command);
167         } else {
168             final_name = g_build_filename(local_path, command, NULL);
169         }
171         if (Inkscape::IO::file_test(final_name, G_FILE_TEST_EXISTS)) {
172             g_free(final_name);
173             g_free(orig_path);
174             return TRUE;
175         }
177         g_free(final_name);
178     }
180     return FALSE;
183 /**
184     \return   none
185     \brief    This function 'loads' an extention, basically it determines
186               the full command for the extention and stores that.
187     \param    module  The extention to be loaded.
189     The most difficult part about this function is finding the actual
190     command through all of the Reprs.  Basically it is hidden down a
191     couple of layers, and so the code has to move down too.  When
192     the command is actually found, it has its relative directory
193     solved.
195     At that point all of the loops are exited, and there is an
196     if statement to make sure they didn't exit because of not finding
197     the command.  If that's the case, the extention doesn't get loaded
198     and should error out at a higher level.
199 */
201 bool
202 Script::load(Inkscape::Extension::Extension *module)
204     if (module->loaded()) {
205         return TRUE;
206     }
208     helper_extension = NULL;
210     /* This should probably check to find the executable... */
211     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
212     gchar *command_text = NULL;
213     while (child_repr != NULL) {
214         if (!strcmp(child_repr->name(), "script")) {
215             child_repr = sp_repr_children(child_repr);
216             while (child_repr != NULL) {
217                 if (!strcmp(child_repr->name(), "command")) {
218                     command_text = solve_reldir(child_repr);
220                     const gchar * interpretstr = child_repr->attribute("interpreter");
221                     if (interpretstr != NULL) {
222                         struct interpreter_t {
223                             gchar * identity;
224                             gchar * prefstring;
225                             gchar * defaultval;
226                         };
227                         const interpreter_t interpreterlst[] = {
228                             {"perl", "perl-interpreter", "perl"},
229                             {"python", "python-interpreter", "python"},
230                             {"ruby", "ruby-interpreter", "ruby"},
231                             {"shell", "shell-interpreter", "sh"}
232                         }; /* Change count below if you change structure */
233                         for (unsigned int i = 0; i < 4; i++) {
234                             if (!strcmp(interpretstr, interpreterlst[i].identity)) {
235                                 const gchar * insertText = interpreterlst[i].defaultval;
236                                 if (prefs_get_string_attribute("extensions", interpreterlst[i].prefstring) != NULL)
237                                     insertText = prefs_get_string_attribute("extensions", interpreterlst[i].prefstring);
238 #ifdef _WIN32
239                                 else {
240                                     char szExePath[MAX_PATH];
241                                     char szCurrentDir[MAX_PATH];
242                                     GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
243                                     if (reinterpret_cast<unsigned>(FindExecutable(command_text, szCurrentDir, szExePath)) > 32)
244                                         insertText = szExePath;
245                                 }
246 #endif
248                                 gchar * temp = command_text;
249                                 command_text = g_strconcat(insertText, " ", temp, NULL);
250                                 g_free(temp);
252                                 break;
253                             }
254                         }
255                     }
256                 }
257                 if (!strcmp(child_repr->name(), "helper_extension")) {
258                     helper_extension = g_strdup(sp_repr_children(child_repr)->content());
259                 }
260                 child_repr = sp_repr_next(child_repr);
261             }
263             break;
264         }
265         child_repr = sp_repr_next(child_repr);
266     }
268     g_return_val_if_fail(command_text != NULL, FALSE);
270     if (command != NULL)
271         g_free(command);
272     command = command_text;
274     return TRUE;
277 /**
278     \return   None.
279     \brief    Unload this puppy!
280     \param    module  Extension to be unloaded.
282     This function just sets the module to unloaded.  It free's the
283     command if it has been allocated.
284 */
285 void
286 Script::unload(Inkscape::Extension::Extension *module)
288     if (command != NULL) {
289         g_free(command);
290         command = NULL;
291     }
292     if (helper_extension != NULL) {
293         g_free(helper_extension);
294         helper_extension = NULL;
295     }
297     return;
300 /**
301     \return   Whether the check passed or not
302     \brief    Check every dependency that was given to make sure we should keep this extension
303     \param    module  The Extension in question
305 */
306 bool
307 Script::check(Inkscape::Extension::Extension *module)
309     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
310     while (child_repr != NULL) {
311         if (!strcmp(child_repr->name(), "script")) {
312             child_repr = sp_repr_children(child_repr);
313             while (child_repr != NULL) {
314                 if (!strcmp(child_repr->name(), "check")) {
315                     gchar *command_text = solve_reldir(child_repr);
316                     if (command_text != NULL) {
317                         /* I've got the command */
318                         bool existance;
320                         existance = check_existance(command_text);
321                         g_free(command_text);
322                         if (!existance)
323                             return FALSE;
324                     }
325                 }
327                 if (!strcmp(child_repr->name(), "helper_extension")) {
328                     gchar const *helper = sp_repr_children(child_repr)->content();
329                     if (Inkscape::Extension::db.get(helper) == NULL) {
330                         return FALSE;
331                     }
332                 }
334                 child_repr = sp_repr_next(child_repr);
335             }
337             break;
338         }
339         child_repr = sp_repr_next(child_repr);
340     }
342     return TRUE;
345 /**
346     \return   A dialog for preferences
347     \brief    A stub funtion right now
348     \param    module    Module who's preferences need getting
349     \param    filename  Hey, the file you're getting might be important
351     This function should really do something, right now it doesn't.
352 */
353 Gtk::Widget *
354 Script::prefs_input(Inkscape::Extension::Input *module, gchar const *filename)
356     /*return module->autogui(); */
357     return NULL;
360 /**
361     \return   A dialog for preferences
362     \brief    A stub funtion right now
363     \param    module    Module whose preferences need getting
365     This function should really do something, right now it doesn't.
366 */
367 Gtk::Widget *
368 Script::prefs_output(Inkscape::Extension::Output *module)
370     return module->autogui(NULL, 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     Glib::ustring local_command(command);
528     Glib::ustring * paramString = module->paramString();
529     local_command += *paramString;
530     delete paramString;
532     execute(local_command.c_str(), tempfilename_in, local_filename);
534     g_free(local_filename);
536     // make sure we don't leak file descriptors from g_file_open_tmp
537     close(tempfd);
538     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
539     unlink(tempfilename_in);
540     g_free(tempfilename_in);
543 /**
544     \return    none
545     \brief     This function uses an extention as a effect on a document.
546     \param     module   Extention to effect with.
547     \param     doc      Document to run through the effect.
549     This function is a little bit trickier than the previous two.  It
550     needs two temporary files to get it's work done.  Both of these
551     files have random names created for them using the g_file_open_temp function
552     with the sp_ext_ prefix in the temporary directory.  Like the other
553     functions, the temporary files are deleted at the end.
555     To save/load the two temporary documents (both are SVG) the internal
556     modules for SVG load and save are used.  They are both used through
557     the module system function by passing their keys into the functions.
559     The command itself is built a little bit differently than in other
560     functions because the effect support selections.  So on the command
561     line a list of all the ids that are selected is included.  Currently,
562     this only works for a single selected object, but there will be more.
563     The command string is filled with the data, and then after the execution
564     it is freed.
566     The execute function is used at the core of this function
567     to execute the Script on the two SVG documents (actually only one
568     exists at the time, the other is created by that script).  At that
569     point both should be full, and the second one is loaded.
570 */
571 void
572 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
574     int data_read = 0;
575     SPDocument * mydoc = NULL;
576     gint tempfd_in;
577     gchar *tempfilename_in;
579     // FIXME: process the GError instead of passing NULL
580     if ((tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) {
581         /* Error, couldn't create temporary filename */
582         if (errno == EINVAL) {
583             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
584             perror("Extension::Script:  template for filenames is misconfigured.\n");
585             exit(-1);
586         } else if (errno == EEXIST) {
587             /* Now the  contents of template are undefined. */
588             perror("Extension::Script:  Could not create a unique temporary filename\n");
589             return;
590         } else {
591             perror("Extension::Script:  Unknown error creating temporary filename\n");
592             exit(-1);
593         }
594     }
596     gint tempfd_out;
597     gchar *tempfilename_out;
598     // FIXME: process the GError instead of passing NULL
599     if ((tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) {
600         /* Error, couldn't create temporary filename */
601         if (errno == EINVAL) {
602             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
603             perror("Extension::Script:  template for filenames is misconfigured.\n");
604             exit(-1);
605         } else if (errno == EEXIST) {
606             /* Now the  contents of template are undefined. */
607             perror("Extension::Script:  Could not create a unique temporary filename\n");
608             return;
609         } else {
610             perror("Extension::Script:  Unknown error creating temporary filename\n");
611             exit(-1);
612         }
613     }
615     Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
616                               doc->doc(), tempfilename_in, FALSE, FALSE, FALSE);
618     Glib::ustring local_command(command);
620     /* fixme: Should be some sort of checking here.  Don't know how to do this with structs instead
621      * of classes. */
622     SPDesktop *desktop = (SPDesktop *) doc;
623     if (desktop != NULL) {
624         using Inkscape::Util::GSListConstIterator;
625         GSListConstIterator<SPItem *> selected = sp_desktop_selection(desktop)->itemList();
626         while ( selected != NULL ) {
627             local_command += " --id=";
628             local_command += SP_OBJECT_ID(*selected);
629             ++selected;
630         }
631     }
633     Glib::ustring * paramString = module->paramString();
634     local_command += *paramString;
635     delete paramString;
637     // std::cout << local_command << std::endl;
639     data_read = execute(local_command.c_str(), tempfilename_in, tempfilename_out);
641     if (data_read > 10)
642         mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out);
644     // make sure we don't leak file descriptors from g_file_open_tmp
645     close(tempfd_in);
646     close(tempfd_out);
647     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
648     unlink(tempfilename_in);
649     g_free(tempfilename_in);
650     unlink(tempfilename_out);
651     g_free(tempfilename_out);
653     /* Do something with mydoc.... */
654     if (mydoc != NULL) {
655         doc->doc()->emitReconstructionStart();
656         copy_doc(doc->doc()->rroot, mydoc->rroot);
657         doc->doc()->emitReconstructionFinish();
658         mydoc->release();
659     }
663 /**
664     \brief  A function to take all the svg elements from one document
665             and put them in another.
666     \param  oldroot  The root node of the document to be replaced
667     \param  newroot  The root node of the document to replace it with
669     This function first deletes all of the data in the old document.  It
670     does this by creating a list of what needs to be deleted, and then
671     goes through the list.  This two pass approach removes issues with
672     the list being change while parsing through it.  Lots of nasty bugs.
674     Then, it goes through the new document, duplicating all of the
675     elements and putting them into the old document.  The copy
676     is then complete.
677 */
678 void
679 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
681     std::vector<Inkscape::XML::Node *> delete_list;
682     for (Inkscape::XML::Node * child = oldroot->firstChild();
683             child != NULL;
684             child = child->next()) {
685         if (!strcmp("sodipodi:namedview", child->name()))
686             continue;
687         if (!strcmp("svg:defs", child->name()))
688             continue;
689         delete_list.push_back(child);
690     }
691     for (unsigned int i = 0; i < delete_list.size(); i++)
692         sp_repr_unparent(delete_list[i]);
694     for (Inkscape::XML::Node * child = newroot->firstChild();
695             child != NULL;
696             child = child->next()) {
697         if (!strcmp("sodipodi:namedview", child->name()))
698             continue;
699         if (!strcmp("svg:defs", child->name()))
700             continue;
701         oldroot->appendChild(child->duplicate());
702     }
704     /** \todo  Restore correct layer */
705     /** \todo  Restore correct selection */
708 /* Helper class used by Script::execute */
709 class pipe_t {
710 public:
711     /* These functions set errno if they return false.
712        I'm not sure whether that's a good idea or not, but it should be reasonably
713        straightforward to change it if needed. */
714     bool open(char *command, char const *errorFile, int mode);
715     bool close();
717     /* These return the number of bytes read/written. */
718     size_t read(void *buffer, size_t size);
719     size_t write(void const *buffer, size_t size);
721     enum {
722         mode_read  = 1 << 0,
723         mode_write = 1 << 1,
724     };
726 private:
727 #ifdef WIN32
728     /* This is used to translate win32 errors into errno errors.
729        It only recognizes a few win32 errors for the moment though. */
730     static int translate_error(DWORD err);
732     HANDLE hpipe;
733 #else
734     FILE *ppipe;
735 #endif
736 };
738 /**
739     \return   none
740     \brief    This is the core of the extension file as it actually does
741               the execution of the extension.
742     \param    in_command  The command to be executed
743     \param    filein      Filename coming in
744     \param    fileout     Filename of the out file
745     \return   Number of bytes that were read into the output file.
747     The first thing that this function does is build the command to be
748     executed.  This consists of the first string (in_command) and then
749     the filename for input (filein).  This file is put on the command
750     line.
752     The next thing is that this function does is open a pipe to the
753     command and get the file handle in the ppipe variable.  It then
754     opens the output file with the output file handle.  Both of these
755     operations are checked extensively for errors.
757     After both are opened, then the data is copied from the output
758     of the pipe into the file out using fread and fwrite.  These two
759     functions are used because of their primitive nature they make
760     no assumptions about the data.  A buffer is used in the transfer,
761     but the output of fread is stored so the exact number of bytes
762     is handled gracefully.
764     At the very end (after the data has been copied) both of the files
765     are closed, and we return to what we were doing.
766 */
767 int
768 Script::execute (const gchar * in_command, const gchar * filein, const gchar * fileout)
770     g_return_val_if_fail(in_command != NULL, 0);
771     // printf("Executing: %s\n", in_command);
773     gchar * errorFile;
774     gint errorFileNum;
775     errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &errorFile, NULL);
776     if (errorFileNum != 0) {
777         close(errorFileNum);
778     } else {
779         g_free(errorFile);
780         errorFile = NULL;
781     }
783     char *command = g_strdup_printf("%s \"%s\"", in_command, filein);
784     // std::cout << "Command to run: " << command << std::endl;
786     pipe_t pipe;
787     bool open_success = pipe.open(command, errorFile, pipe_t::mode_read);
788     g_free(command);
790     /* Run script */
791     if (!open_success) {
792         /* Error - could not open pipe - check errno */
793         if (errno == EINVAL) {
794             perror("Extension::Script:  Invalid mode argument in popen\n");
795         } else if (errno == ECHILD) {
796             perror("Extension::Script:  Cannot obtain child extension status in popen\n");
797         } else {
798             perror("Extension::Script:  Unknown error for popen\n");
799         }
800         return 0;
801     }
803     Inkscape::IO::dump_fopen_call(fileout, "J");
804     FILE *pfile = Inkscape::IO::fopen_utf8name(fileout, "w");
806     if (pfile == NULL) {
807         /* Error - could not open file */
808         if (errno == EINVAL) {
809             perror("Extension::Script:  The mode provided to fopen was invalid\n");
810         } else {
811             perror("Extension::Script:  Unknown error attempting to open temporary file\n");
812         }
813         return 0;
814     }
816     /* Copy pipe output to a temporary file */
817     int amount_read = 0;
818     char buf[BUFSIZE];
819     int num_read;
820     while ((num_read = pipe.read(buf, BUFSIZE)) != 0) {
821         amount_read += num_read;
822         fwrite(buf, 1, num_read, pfile);
823     }
825     /* Close file */
826     if (fclose(pfile) == EOF) {
827         if (errno == EBADF) {
828             perror("Extension::Script:  The filedescriptor for the temporary file is invalid\n");
829             return 0;
830         } else {
831             perror("Extension::Script:  Unknown error closing temporary file\n");
832         }
833     }
835     /* Close pipe */
836     if (!pipe.close()) {
837         if (errno == EINVAL) {
838             perror("Extension::Script:  Invalid mode set for pclose\n");
839         } else if (errno == ECHILD) {
840             perror("Extension::Script:  Could not obtain child status for pclose\n");
841         } else {
842             if (errorFile != NULL) {
843                 checkStderr(errorFile, Gtk::MESSAGE_ERROR,
844                     _("Inkscape has received an error from the script that it called.  "
845                       "The text returned with the error is included below.  "
846                       "Inkscape will continue working, but the action you requested has been cancelled."));
847             } else {
848                 perror("Extension::Script:  Unknown error for pclose\n");
849             }
850         }
851         /* Could be a lie, but if there is an error, we don't want
852          * to count on what was read being good */
853         amount_read = 0;
854     } else {
855         if (errorFile != NULL) {
856             checkStderr(errorFile, Gtk::MESSAGE_INFO,
857                 _("Inkscape has received additional data from the script executed.  "
858                   "The script did not return an error, but this may indicate the results will not be as expected."));
859         }
860     }
862     if (errorFile != NULL) {
863         unlink(errorFile);
864         g_free(errorFile);
865     }
867     return amount_read;
870 /**  \brief  This function checks the stderr file, and if it has data,
871              shows it in a warning dialog to the user
872      \param  filename  Filename of the stderr file
873 */
874 void
875 Script::checkStderr (gchar * filename, Gtk::MessageType type, gchar * message)
877     // magic win32 crlf->lf conversion means the file length is not the same as
878     // the text length, but luckily gtk will accept crlf in textviews so we can
879     // just use binary mode
880     std::ifstream stderrf (filename, std::ios_base::in | std::ios_base::binary);
881     if (!stderrf.is_open()) return;
883     stderrf.seekg(0, std::ios::end);
884     int length = stderrf.tellg();
885     if (0 == length) return;
886     stderrf.seekg(0, std::ios::beg);
888     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
889     warning.set_resizable(true);
891     Gtk::VBox * vbox = warning.get_vbox();
893     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
894     Gtk::TextView * textview = new Gtk::TextView();
895     textview->set_editable(false);
896     textview->set_wrap_mode(Gtk::WRAP_WORD);
897     textview->show();
899     char * buffer = new char [length];
900     stderrf.read(buffer, length);
901     textview->get_buffer()->set_text(buffer, buffer + length);
902     delete buffer;
903     stderrf.close();
905     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
906     scrollwindow->add(*textview);
907     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
908     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
909     scrollwindow->show();
911     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
913     warning.run();
915     return;
918 #ifdef WIN32
920 bool pipe_t::open(char *command, char const *errorFile, int mode_p) {
921     HANDLE pipe_write;
923     // Create pipe
924     {
925         SECURITY_ATTRIBUTES secattrs;
926         ZeroMemory(&secattrs, sizeof(secattrs));
927         secattrs.nLength = sizeof(secattrs);
928         secattrs.lpSecurityDescriptor = 0;
929         secattrs.bInheritHandle = TRUE;
930         HANDLE t_pipe_read = 0;
931         if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) {
932             errno = translate_error(GetLastError());
933             return false;
934         }
935         // This duplicate handle makes the read pipe uninheritable
936         if ( !DuplicateHandle(GetCurrentProcess(), t_pipe_read, GetCurrentProcess(), &hpipe, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) ) {
937             int en = translate_error(GetLastError());
938             CloseHandle(t_pipe_read);
939             CloseHandle(pipe_write);
940             errno = en;
941             return false;
942         }
943     }
944     // Open stderr file
945     HANDLE hStdErrFile = CreateFile(errorFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
946     HANDLE hInheritableStdErr;
947     DuplicateHandle(GetCurrentProcess(), hStdErrFile, GetCurrentProcess(), &hInheritableStdErr, 0, TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
949     // Create process
950     {
951         PROCESS_INFORMATION procinfo;
952         STARTUPINFO startupinfo;
953         ZeroMemory(&procinfo, sizeof(procinfo));
954         ZeroMemory(&startupinfo, sizeof(startupinfo));
955         startupinfo.cb = sizeof(startupinfo);
956         //startupinfo.lpReserved = 0;
957         //startupinfo.lpDesktop = 0;
958         //startupinfo.lpTitle = 0;
959         startupinfo.dwFlags = STARTF_USESTDHANDLES;
960         startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
961         startupinfo.hStdOutput = pipe_write;
962         startupinfo.hStdError = hInheritableStdErr;
964         if ( !CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &startupinfo, &procinfo) ) {
965             errno = translate_error(GetLastError());
966             return false;
967         }
968         CloseHandle(procinfo.hThread);
969         CloseHandle(procinfo.hProcess);
970     }
972     // Close our copy of the write handle
973     CloseHandle(hInheritableStdErr);
974     CloseHandle(pipe_write);
976     return true;
979 bool pipe_t::close() {
980     BOOL retval = CloseHandle(hpipe);
981     if ( !retval ) {
982         errno = translate_error(GetLastError());
983     }
984     return retval != FALSE;
987 size_t pipe_t::read(void *buffer, size_t size) {
988     DWORD bytes_read = 0;
989     ReadFile(hpipe, buffer, size, &bytes_read, 0);
990     return bytes_read;
993 size_t pipe_t::write(void const *buffer, size_t size) {
994     DWORD bytes_written = 0;
995     WriteFile(hpipe, buffer, size, &bytes_written, 0);
996     return bytes_written;
999 int pipe_t::translate_error(DWORD err) {
1000     switch (err) {
1001         case ERROR_FILE_NOT_FOUND:
1002             return ENOENT;
1003         case ERROR_INVALID_HANDLE:
1004         case ERROR_INVALID_PARAMETER:
1005             return EINVAL;
1006         default:
1007             return 0;
1008     }
1011 #else // Win32
1013 bool pipe_t::open(char *command, char const *errorFile, int mode_p) {
1014     char popen_mode[4] = {0,0,0,0};
1015     char *popen_mode_cur = popen_mode;
1017     if ( (mode_p & mode_read) != 0 ) {
1018         *popen_mode_cur++ = 'r';
1019     }
1021     if ( (mode_p & mode_write) != 0 ) {
1022         *popen_mode_cur++ = 'w';
1023     }
1025     /* Get the commandline to be run */
1026     if (errorFile != NULL) {
1027         char * temp;
1028         temp = g_strdup_printf("%s 2> %s", command, errorFile);
1029         ppipe = popen(temp, popen_mode);
1030         g_free(temp);
1031     } else
1032         ppipe = popen(command, popen_mode);
1034     return ppipe != NULL;
1037 bool pipe_t::close() {
1038     return fclose(ppipe) == 0;
1041 size_t pipe_t::read(void *buffer, size_t size) {
1042     return fread(buffer, 1, size, ppipe);
1045 size_t pipe_t::write(void const *buffer, size_t size) {
1046     return fwrite(buffer, 1, size, ppipe);
1049 #endif // (Non-)Win32
1052 }  /* Inkscape  */
1053 }  /* module  */
1054 }  /* Implementation  */
1057 /*
1058   Local Variables:
1059   mode:c++
1060   c-file-style:"stroustrup"
1061   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1062   indent-tabs-mode:nil
1063   fill-column:99
1064   End:
1065 */
1066 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :