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;
104 }
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)
124 {
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;
180 }
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)
202 {
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;
274 }
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)
286 {
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;
297 }
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)
307 {
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;
342 }
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)
354 {
355 /*return module->autogui(); */
356 return NULL;
357 }
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)
368 {
369 /*return module->autogui();*/
370 return NULL;
371 }
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)
382 {
383 return module->autogui();
384 }
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)
409 {
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;
459 }
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)
487 {
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);
528 }
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)
560 {
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 }
647 }
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)
667 {
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 */
693 }
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)
756 {
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;
855 }
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)
863 {
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;
903 }
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;
964 }
966 bool pipe_t::close() {
967 BOOL retval = CloseHandle(hpipe);
968 if ( !retval ) {
969 errno = translate_error(GetLastError());
970 }
971 return retval != FALSE;
972 }
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;
978 }
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;
984 }
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 }
996 }
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;
1022 }
1024 bool pipe_t::close() {
1025 return fclose(ppipe) == 0;
1026 }
1028 size_t pipe_t::read(void *buffer, size_t size) {
1029 return fread(buffer, 1, size, ppipe);
1030 }
1032 size_t pipe_t::write(void const *buffer, size_t size) {
1033 return fwrite(buffer, 1, size, ppipe);
1034 }
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 :