Code

moving trunk for module inkscape
[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     return module->autogui();
386 /**
387     \return  A new document that has been opened
388     \brief   This function uses a filename that is put in, and calls
389              the extension's command to create an SVG file which is
390              returned.
391     \param   module   Extension to use.
392     \param   filename File to open.
394     First things first, this function needs a temporary file name.  To
395     create on of those the function g_file_open_tmp is used with
396     the header of ink_ext_.
398     The extension is then executed using the 'execute' function
399     with the filname coming in, and the temporary filename.  After
400     That executing, the SVG should be in the temporary file.
402     Finally, the temporary file is opened using the SVG input module and
403     a document is returned.  That document has its filename set to
404     the incoming filename (so that it's not the temporary filename).
405     That document is then returned from this function.
406 */
407 SPDocument *
408 Script::open(Inkscape::Extension::Input *module, gchar const *filename)
410     int data_read = 0;
411     gint tempfd;
412     gchar *tempfilename_out;
414     // FIXME: process the GError instead of passing NULL
415     if ((tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) {
416         /* Error, couldn't create temporary filename */
417         if (errno == EINVAL) {
418             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
419             perror("Extension::Script:  template for filenames is misconfigured.\n");
420             exit(-1);
421         } else if (errno == EEXIST) {
422             /* Now the  contents of template are undefined. */
423             perror("Extension::Script:  Could not create a unique temporary filename\n");
424             return NULL;
425         } else {
426             perror("Extension::Script:  Unknown error creating temporary filename\n");
427             exit(-1);
428         }
429     }
431     gsize bytesRead = 0;
432     gsize bytesWritten = 0;
433     GError *error = NULL;
434     gchar *local_filename = g_filename_from_utf8( filename,
435                                                   -1,  &bytesRead,  &bytesWritten, &error);
437     data_read = execute(command, local_filename, tempfilename_out);
438     g_free(local_filename);
440     SPDocument *mydoc = NULL;
441     if (data_read > 10) {
442         if (helper_extension == NULL) {
443             mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out);
444         } else {
445             mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(helper_extension), tempfilename_out);
446         }
447     }
449     if (mydoc != NULL)
450         sp_document_set_uri(mydoc, (const gchar *)filename);
452     // make sure we don't leak file descriptors from g_file_open_tmp
453     close(tempfd);
454     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
455     unlink(tempfilename_out);
456     g_free(tempfilename_out);
458     return mydoc;
461 /**
462     \return   none
463     \brief    This function uses an extention to save a document.  It first
464               creates an SVG file of the document, and then runs it through
465               the script.
466     \param    module    Extention to be used
467     \param    doc       Document to be saved
468     \param    filename  The name to save the final file as
470     Well, at some point people need to save - it is really what makes
471     the entire application useful.  And, it is possible that someone
472     would want to use an extetion for this, so we need a function to
473     do that eh?
475     First things first, the document is saved to a temporary file that
476     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
477     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
478     end of the function.
480     After we have the SVG file, then extention_execute is called with
481     the temporary file name and the final output filename.  This should
482     put the output of the script into the final output file.  We then
483     delete the temporary file.
484 */
485 void
486 Script::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename)
488     gint tempfd;
489     gchar *tempfilename_in;
490     // FIXME: process the GError instead of passing NULL
491     if ((tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) {
492         /* Error, couldn't create temporary filename */
493         if (errno == EINVAL) {
494             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
495             perror("Extension::Script:  template for filenames is misconfigured.\n");
496             exit(-1);
497         } else if (errno == EEXIST) {
498             /* Now the  contents of template are undefined. */
499             perror("Extension::Script:  Could not create a unique temporary filename\n");
500             return;
501         } else {
502             perror("Extension::Script:  Unknown error creating temporary filename\n");
503             exit(-1);
504         }
505     }
507     if (helper_extension == NULL) {
508         Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), doc, tempfilename_in, FALSE, FALSE, FALSE);
509     } else {
510         Inkscape::Extension::save(Inkscape::Extension::db.get(helper_extension), doc, tempfilename_in, FALSE, FALSE, FALSE);
511     }
513     gsize bytesRead = 0;
514     gsize bytesWritten = 0;
515     GError *error = NULL;
516     gchar *local_filename = g_filename_from_utf8( filename,
517                                                   -1,  &bytesRead,  &bytesWritten, &error);
519     execute(command, tempfilename_in, local_filename);
521     g_free(local_filename);
523     // make sure we don't leak file descriptors from g_file_open_tmp
524     close(tempfd);
525     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
526     unlink(tempfilename_in);
527     g_free(tempfilename_in);
530 /**
531     \return    none
532     \brief     This function uses an extention as a effect on a document.
533     \param     module   Extention to effect with.
534     \param     doc      Document to run through the effect.
536     This function is a little bit trickier than the previous two.  It
537     needs two temporary files to get it's work done.  Both of these
538     files have random names created for them using the g_file_open_temp function
539     with the sp_ext_ prefix in the temporary directory.  Like the other
540     functions, the temporary files are deleted at the end.
542     To save/load the two temporary documents (both are SVG) the internal
543     modules for SVG load and save are used.  They are both used through
544     the module system function by passing their keys into the functions.
546     The command itself is built a little bit differently than in other
547     functions because the effect support selections.  So on the command
548     line a list of all the ids that are selected is included.  Currently,
549     this only works for a single selected object, but there will be more.
550     The command string is filled with the data, and then after the execution
551     it is freed.
553     The execute function is used at the core of this function
554     to execute the Script on the two SVG documents (actually only one
555     exists at the time, the other is created by that script).  At that
556     point both should be full, and the second one is loaded.
557 */
558 void
559 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
561     int data_read = 0;
562     SPDocument * mydoc = NULL;
563     gint tempfd_in;
564     gchar *tempfilename_in;
566     // FIXME: process the GError instead of passing NULL
567     if ((tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) {
568         /* Error, couldn't create temporary filename */
569         if (errno == EINVAL) {
570             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
571             perror("Extension::Script:  template for filenames is misconfigured.\n");
572             exit(-1);
573         } else if (errno == EEXIST) {
574             /* Now the  contents of template are undefined. */
575             perror("Extension::Script:  Could not create a unique temporary filename\n");
576             return;
577         } else {
578             perror("Extension::Script:  Unknown error creating temporary filename\n");
579             exit(-1);
580         }
581     }
583     gint tempfd_out;
584     gchar *tempfilename_out;
585     // FIXME: process the GError instead of passing NULL
586     if ((tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) {
587         /* Error, couldn't create temporary filename */
588         if (errno == EINVAL) {
589             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
590             perror("Extension::Script:  template for filenames is misconfigured.\n");
591             exit(-1);
592         } else if (errno == EEXIST) {
593             /* Now the  contents of template are undefined. */
594             perror("Extension::Script:  Could not create a unique temporary filename\n");
595             return;
596         } else {
597             perror("Extension::Script:  Unknown error creating temporary filename\n");
598             exit(-1);
599         }
600     }
602     Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
603                               doc->doc(), tempfilename_in, FALSE, FALSE, FALSE);
605     Glib::ustring local_command(command);
607     /* fixme: Should be some sort of checking here.  Don't know how to do this with structs instead
608      * of classes. */
609     SPDesktop *desktop = (SPDesktop *) doc;
610     if (desktop != NULL) {
611         using Inkscape::Util::GSListConstIterator;
612         GSListConstIterator<SPItem *> selected = SP_DT_SELECTION(desktop)->itemList();
613         while ( selected != NULL ) {
614             local_command += " --id=";
615             local_command += SP_OBJECT_ID(*selected);
616             ++selected;
617         }
618     }
620     Glib::ustring * paramString = module->paramString();
621     local_command += *paramString;
622     delete paramString;
624     // std::cout << local_command << std::endl;
626     data_read = execute(local_command.c_str(), tempfilename_in, tempfilename_out);
628     if (data_read > 10)
629         mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out);
631     // make sure we don't leak file descriptors from g_file_open_tmp
632     close(tempfd_in);
633     close(tempfd_out);
634     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
635     unlink(tempfilename_in);
636     g_free(tempfilename_in);
637     unlink(tempfilename_out);
638     g_free(tempfilename_out);
640     /* Do something with mydoc.... */
641     if (mydoc != NULL) {
642         doc->doc()->emitReconstructionStart();
643         copy_doc(doc->doc()->rroot, mydoc->rroot);
644         doc->doc()->emitReconstructionFinish();
645         mydoc->release();
646     }
650 /**
651     \brief  A function to take all the svg elements from one document
652             and put them in another.
653     \param  oldroot  The root node of the document to be replaced
654     \param  newroot  The root node of the document to replace it with
656     This function first deletes all of the data in the old document.  It
657     does this by creating a list of what needs to be deleted, and then
658     goes through the list.  This two pass approach removes issues with
659     the list being change while parsing through it.  Lots of nasty bugs.
661     Then, it goes through the new document, duplicating all of the
662     elements and putting them into the old document.  The copy
663     is then complete.
664 */
665 void
666 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
668     std::vector<Inkscape::XML::Node *> delete_list;
669     for (Inkscape::XML::Node * child = oldroot->firstChild();
670             child != NULL;
671             child = child->next()) {
672         if (!strcmp("sodipodi:namedview", child->name()))
673             continue;
674         if (!strcmp("svg:defs", child->name()))
675             continue;
676         delete_list.push_back(child);
677     }
678     for (unsigned int i = 0; i < delete_list.size(); i++)
679         sp_repr_unparent(delete_list[i]);
681     for (Inkscape::XML::Node * child = newroot->firstChild();
682             child != NULL;
683             child = child->next()) {
684         if (!strcmp("sodipodi:namedview", child->name()))
685             continue;
686         if (!strcmp("svg:defs", child->name()))
687             continue;
688         oldroot->appendChild(child->duplicate());
689     }
691     /** \todo  Restore correct layer */
692     /** \todo  Restore correct selection */
695 /* Helper class used by Script::execute */
696 class pipe_t {
697 public:
698     /* These functions set errno if they return false.
699        I'm not sure whether that's a good idea or not, but it should be reasonably
700        straightforward to change it if needed. */
701     bool open(char *command, char const *errorFile, int mode);
702     bool close();
704     /* These return the number of bytes read/written. */
705     size_t read(void *buffer, size_t size);
706     size_t write(void const *buffer, size_t size);
708     enum {
709         mode_read  = 1 << 0,
710         mode_write = 1 << 1,
711     };
713 private:
714 #ifdef WIN32
715     /* This is used to translate win32 errors into errno errors.
716        It only recognizes a few win32 errors for the moment though. */
717     static int translate_error(DWORD err);
719     HANDLE hpipe;
720 #else
721     FILE *ppipe;
722 #endif
723 };
725 /**
726     \return   none
727     \brief    This is the core of the extension file as it actually does
728               the execution of the extension.
729     \param    in_command  The command to be executed
730     \param    filein      Filename coming in
731     \param    fileout     Filename of the out file
732     \return   Number of bytes that were read into the output file.
734     The first thing that this function does is build the command to be
735     executed.  This consists of the first string (in_command) and then
736     the filename for input (filein).  This file is put on the command
737     line.
739     The next thing is that this function does is open a pipe to the
740     command and get the file handle in the ppipe variable.  It then
741     opens the output file with the output file handle.  Both of these
742     operations are checked extensively for errors.
744     After both are opened, then the data is copied from the output
745     of the pipe into the file out using fread and fwrite.  These two
746     functions are used because of their primitive nature they make
747     no assumptions about the data.  A buffer is used in the transfer,
748     but the output of fread is stored so the exact number of bytes
749     is handled gracefully.
751     At the very end (after the data has been copied) both of the files
752     are closed, and we return to what we were doing.
753 */
754 int
755 Script::execute (const gchar * in_command, const gchar * filein, const gchar * fileout)
757     g_return_val_if_fail(in_command != NULL, 0);
758     // printf("Executing: %s\n", in_command);
760     gchar * errorFile;
761     gint errorFileNum;
762     errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &errorFile, NULL);
763     if (errorFileNum != 0) {
764         close(errorFileNum);
765     } else {
766         g_free(errorFile);
767         errorFile = NULL;
768     }
770     char *command = g_strdup_printf("%s \"%s\"", in_command, filein);
771     // std::cout << "Command to run: " << command << std::endl;
773     pipe_t pipe;
774     bool open_success = pipe.open(command, errorFile, pipe_t::mode_read);
775     g_free(command);
777     /* Run script */
778     if (!open_success) {
779         /* Error - could not open pipe - check errno */
780         if (errno == EINVAL) {
781             perror("Extension::Script:  Invalid mode argument in popen\n");
782         } else if (errno == ECHILD) {
783             perror("Extension::Script:  Cannot obtain child extension status in popen\n");
784         } else {
785             perror("Extension::Script:  Unknown error for popen\n");
786         }
787         return 0;
788     }
790     Inkscape::IO::dump_fopen_call(fileout, "J");
791     FILE *pfile = Inkscape::IO::fopen_utf8name(fileout, "w");
793     if (pfile == NULL) {
794         /* Error - could not open file */
795         if (errno == EINVAL) {
796             perror("Extension::Script:  The mode provided to fopen was invalid\n");
797         } else {
798             perror("Extension::Script:  Unknown error attempting to open temporary file\n");
799         }
800         return 0;
801     }
803     /* Copy pipe output to a temporary file */
804     int amount_read = 0;
805     char buf[BUFSIZE];
806     int num_read;
807     while ((num_read = pipe.read(buf, BUFSIZE)) != 0) {
808         amount_read += num_read;
809         fwrite(buf, 1, num_read, pfile);
810     }
812     /* Close file */
813     if (fclose(pfile) == EOF) {
814         if (errno == EBADF) {
815             perror("Extension::Script:  The filedescriptor for the temporary file is invalid\n");
816             return 0;
817         } else {
818             perror("Extension::Script:  Unknown error closing temporary file\n");
819         }
820     }
822     /* Close pipe */
823     if (!pipe.close()) {
824         if (errno == EINVAL) {
825             perror("Extension::Script:  Invalid mode set for pclose\n");
826         } else if (errno == ECHILD) {
827             perror("Extension::Script:  Could not obtain child status for pclose\n");
828         } else {
829             if (errorFile != NULL) {
830                 checkStderr(errorFile, Gtk::MESSAGE_ERROR,
831                     _("Inkscape has received an error from the script that it called.  "
832                       "The text returned with the error is included below.  "
833                       "Inkscape will continue working, but the action you requested has been cancelled."));
834             } else {
835                 perror("Extension::Script:  Unknown error for pclose\n");
836             }
837         }
838         /* Could be a lie, but if there is an error, we don't want
839          * to count on what was read being good */
840         amount_read = 0;
841     } else {
842         if (errorFile != NULL) {
843             checkStderr(errorFile, Gtk::MESSAGE_INFO,
844                 _("Inkscape has received additional data from the script executed.  "
845                   "The script did not return an error, but this may indicate the results will not be as expected."));
846         }
847     }
849     if (errorFile != NULL) {
850         unlink(errorFile);
851         g_free(errorFile);
852     }
854     return amount_read;
857 /**  \brief  This function checks the stderr file, and if it has data,
858              shows it in a warning dialog to the user
859      \param  filename  Filename of the stderr file
860 */
861 void
862 Script::checkStderr (gchar * filename, Gtk::MessageType type, gchar * message)
864     // magic win32 crlf->lf conversion means the file length is not the same as
865     // the text length, but luckily gtk will accept crlf in textviews so we can
866     // just use binary mode
867     std::ifstream stderrf (filename, std::ios_base::in | std::ios_base::binary);
868     if (!stderrf.is_open()) return;
870     stderrf.seekg(0, std::ios::end);
871     int length = stderrf.tellg();
872     if (0 == length) return;
873     stderrf.seekg(0, std::ios::beg);
875     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
876     warning.set_resizable(true);
878     Gtk::VBox * vbox = warning.get_vbox();
880     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
881     Gtk::TextView * textview = new Gtk::TextView();
882     textview->set_editable(false);
883     textview->set_wrap_mode(Gtk::WRAP_WORD);
884     textview->show();
886     char * buffer = new char [length];
887     stderrf.read(buffer, length);
888     textview->get_buffer()->set_text(buffer, buffer + length);
889     delete buffer;
890     stderrf.close();
892     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
893     scrollwindow->add(*textview);
894     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
895     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
896     scrollwindow->show();
898     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
900     warning.run();
902     return;
905 #ifdef WIN32
907 bool pipe_t::open(char *command, char const *errorFile, int mode_p) {
908     HANDLE pipe_write;
910     // Create pipe
911     {
912         SECURITY_ATTRIBUTES secattrs;
913         ZeroMemory(&secattrs, sizeof(secattrs));
914         secattrs.nLength = sizeof(secattrs);
915         secattrs.lpSecurityDescriptor = 0;
916         secattrs.bInheritHandle = TRUE;
917         HANDLE t_pipe_read = 0;
918         if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) {
919             errno = translate_error(GetLastError());
920             return false;
921         }
922         // This duplicate handle makes the read pipe uninheritable
923         if ( !DuplicateHandle(GetCurrentProcess(), t_pipe_read, GetCurrentProcess(), &hpipe, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) ) {
924             int en = translate_error(GetLastError());
925             CloseHandle(t_pipe_read);
926             CloseHandle(pipe_write);
927             errno = en;
928             return false;
929         }
930     }
931     // Open stderr file
932     HANDLE hStdErrFile = CreateFile(errorFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
933     HANDLE hInheritableStdErr;
934     DuplicateHandle(GetCurrentProcess(), hStdErrFile, GetCurrentProcess(), &hInheritableStdErr, 0, TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
936     // Create process
937     {
938         PROCESS_INFORMATION procinfo;
939         STARTUPINFO startupinfo;
940         ZeroMemory(&procinfo, sizeof(procinfo));
941         ZeroMemory(&startupinfo, sizeof(startupinfo));
942         startupinfo.cb = sizeof(startupinfo);
943         //startupinfo.lpReserved = 0;
944         //startupinfo.lpDesktop = 0;
945         //startupinfo.lpTitle = 0;
946         startupinfo.dwFlags = STARTF_USESTDHANDLES;
947         startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
948         startupinfo.hStdOutput = pipe_write;
949         startupinfo.hStdError = hInheritableStdErr;
951         if ( !CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &startupinfo, &procinfo) ) {
952             errno = translate_error(GetLastError());
953             return false;
954         }
955         CloseHandle(procinfo.hThread);
956         CloseHandle(procinfo.hProcess);
957     }
959     // Close our copy of the write handle
960     CloseHandle(hInheritableStdErr);
961     CloseHandle(pipe_write);
963     return true;
966 bool pipe_t::close() {
967     BOOL retval = CloseHandle(hpipe);
968     if ( !retval ) {
969         errno = translate_error(GetLastError());
970     }
971     return retval != FALSE;
974 size_t pipe_t::read(void *buffer, size_t size) {
975     DWORD bytes_read = 0;
976     ReadFile(hpipe, buffer, size, &bytes_read, 0);
977     return bytes_read;
980 size_t pipe_t::write(void const *buffer, size_t size) {
981     DWORD bytes_written = 0;
982     WriteFile(hpipe, buffer, size, &bytes_written, 0);
983     return bytes_written;
986 int pipe_t::translate_error(DWORD err) {
987     switch (err) {
988         case ERROR_FILE_NOT_FOUND:
989             return ENOENT;
990         case ERROR_INVALID_HANDLE:
991         case ERROR_INVALID_PARAMETER:
992             return EINVAL;
993         default:
994             return 0;
995     }
998 #else // Win32
1000 bool pipe_t::open(char *command, char const *errorFile, int mode_p) {
1001     char popen_mode[4] = {0,0,0,0};
1002     char *popen_mode_cur = popen_mode;
1004     if ( (mode_p & mode_read) != 0 ) {
1005         *popen_mode_cur++ = 'r';
1006     }
1008     if ( (mode_p & mode_write) != 0 ) {
1009         *popen_mode_cur++ = 'w';
1010     }
1012     /* Get the commandline to be run */
1013     if (errorFile != NULL) {
1014         char * temp;
1015         temp = g_strdup_printf("%s 2> %s", command, errorFile);
1016         ppipe = popen(temp, popen_mode);
1017         g_free(temp);
1018     } else
1019         ppipe = popen(command, popen_mode);
1021     return ppipe != NULL;
1024 bool pipe_t::close() {
1025     return fclose(ppipe) == 0;
1028 size_t pipe_t::read(void *buffer, size_t size) {
1029     return fread(buffer, 1, size, ppipe);
1032 size_t pipe_t::write(void const *buffer, size_t size) {
1033     return fwrite(buffer, 1, size, ppipe);
1036 #endif // (Non-)Win32
1039 }  /* Inkscape  */
1040 }  /* module  */
1041 }  /* Implementation  */
1044 /*
1045   Local Variables:
1046   mode:c++
1047   c-file-style:"stroustrup"
1048   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1049   indent-tabs-mode:nil
1050   fill-column:99
1051   End:
1052 */
1053 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :