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 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);
392 }
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)
417 {
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;
467 }
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)
495 {
496 gint tempfd;
497 gchar *tempfilename_in;
498 // FIXME: process the GError instead of passing NULL
499 if ((tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) {
500 /* Error, couldn't create temporary filename */
501 if (errno == EINVAL) {
502 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
503 perror("Extension::Script: template for filenames is misconfigured.\n");
504 exit(-1);
505 } else if (errno == EEXIST) {
506 /* Now the contents of template are undefined. */
507 perror("Extension::Script: Could not create a unique temporary filename\n");
508 return;
509 } else {
510 perror("Extension::Script: Unknown error creating temporary filename\n");
511 exit(-1);
512 }
513 }
515 if (helper_extension == NULL) {
516 Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), doc, tempfilename_in, FALSE, FALSE, FALSE);
517 } else {
518 Inkscape::Extension::save(Inkscape::Extension::db.get(helper_extension), doc, tempfilename_in, FALSE, FALSE, FALSE);
519 }
521 gsize bytesRead = 0;
522 gsize bytesWritten = 0;
523 GError *error = NULL;
524 gchar *local_filename = g_filename_from_utf8( filename,
525 -1, &bytesRead, &bytesWritten, &error);
527 execute(command, tempfilename_in, local_filename);
529 g_free(local_filename);
531 // make sure we don't leak file descriptors from g_file_open_tmp
532 close(tempfd);
533 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
534 unlink(tempfilename_in);
535 g_free(tempfilename_in);
536 }
538 /**
539 \return none
540 \brief This function uses an extention as a effect on a document.
541 \param module Extention to effect with.
542 \param doc Document to run through the effect.
544 This function is a little bit trickier than the previous two. It
545 needs two temporary files to get it's work done. Both of these
546 files have random names created for them using the g_file_open_temp function
547 with the sp_ext_ prefix in the temporary directory. Like the other
548 functions, the temporary files are deleted at the end.
550 To save/load the two temporary documents (both are SVG) the internal
551 modules for SVG load and save are used. They are both used through
552 the module system function by passing their keys into the functions.
554 The command itself is built a little bit differently than in other
555 functions because the effect support selections. So on the command
556 line a list of all the ids that are selected is included. Currently,
557 this only works for a single selected object, but there will be more.
558 The command string is filled with the data, and then after the execution
559 it is freed.
561 The execute function is used at the core of this function
562 to execute the Script on the two SVG documents (actually only one
563 exists at the time, the other is created by that script). At that
564 point both should be full, and the second one is loaded.
565 */
566 void
567 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
568 {
569 int data_read = 0;
570 SPDocument * mydoc = NULL;
571 gint tempfd_in;
572 gchar *tempfilename_in;
574 // FIXME: process the GError instead of passing NULL
575 if ((tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) {
576 /* Error, couldn't create temporary filename */
577 if (errno == EINVAL) {
578 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
579 perror("Extension::Script: template for filenames is misconfigured.\n");
580 exit(-1);
581 } else if (errno == EEXIST) {
582 /* Now the contents of template are undefined. */
583 perror("Extension::Script: Could not create a unique temporary filename\n");
584 return;
585 } else {
586 perror("Extension::Script: Unknown error creating temporary filename\n");
587 exit(-1);
588 }
589 }
591 gint tempfd_out;
592 gchar *tempfilename_out;
593 // FIXME: process the GError instead of passing NULL
594 if ((tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) {
595 /* Error, couldn't create temporary filename */
596 if (errno == EINVAL) {
597 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
598 perror("Extension::Script: template for filenames is misconfigured.\n");
599 exit(-1);
600 } else if (errno == EEXIST) {
601 /* Now the contents of template are undefined. */
602 perror("Extension::Script: Could not create a unique temporary filename\n");
603 return;
604 } else {
605 perror("Extension::Script: Unknown error creating temporary filename\n");
606 exit(-1);
607 }
608 }
610 Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
611 doc->doc(), tempfilename_in, FALSE, FALSE, FALSE);
613 Glib::ustring local_command(command);
615 /* fixme: Should be some sort of checking here. Don't know how to do this with structs instead
616 * of classes. */
617 SPDesktop *desktop = (SPDesktop *) doc;
618 if (desktop != NULL) {
619 using Inkscape::Util::GSListConstIterator;
620 GSListConstIterator<SPItem *> selected = sp_desktop_selection(desktop)->itemList();
621 while ( selected != NULL ) {
622 local_command += " --id=";
623 local_command += SP_OBJECT_ID(*selected);
624 ++selected;
625 }
626 }
628 Glib::ustring * paramString = module->paramString();
629 local_command += *paramString;
630 delete paramString;
632 // std::cout << local_command << std::endl;
634 data_read = execute(local_command.c_str(), tempfilename_in, tempfilename_out);
636 if (data_read > 10)
637 mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out);
639 // make sure we don't leak file descriptors from g_file_open_tmp
640 close(tempfd_in);
641 close(tempfd_out);
642 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
643 unlink(tempfilename_in);
644 g_free(tempfilename_in);
645 unlink(tempfilename_out);
646 g_free(tempfilename_out);
648 /* Do something with mydoc.... */
649 if (mydoc != NULL) {
650 doc->doc()->emitReconstructionStart();
651 copy_doc(doc->doc()->rroot, mydoc->rroot);
652 doc->doc()->emitReconstructionFinish();
653 mydoc->release();
654 }
655 }
658 /**
659 \brief A function to take all the svg elements from one document
660 and put them in another.
661 \param oldroot The root node of the document to be replaced
662 \param newroot The root node of the document to replace it with
664 This function first deletes all of the data in the old document. It
665 does this by creating a list of what needs to be deleted, and then
666 goes through the list. This two pass approach removes issues with
667 the list being change while parsing through it. Lots of nasty bugs.
669 Then, it goes through the new document, duplicating all of the
670 elements and putting them into the old document. The copy
671 is then complete.
672 */
673 void
674 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
675 {
676 std::vector<Inkscape::XML::Node *> delete_list;
677 for (Inkscape::XML::Node * child = oldroot->firstChild();
678 child != NULL;
679 child = child->next()) {
680 if (!strcmp("sodipodi:namedview", child->name()))
681 continue;
682 if (!strcmp("svg:defs", child->name()))
683 continue;
684 delete_list.push_back(child);
685 }
686 for (unsigned int i = 0; i < delete_list.size(); i++)
687 sp_repr_unparent(delete_list[i]);
689 for (Inkscape::XML::Node * child = newroot->firstChild();
690 child != NULL;
691 child = child->next()) {
692 if (!strcmp("sodipodi:namedview", child->name()))
693 continue;
694 if (!strcmp("svg:defs", child->name()))
695 continue;
696 oldroot->appendChild(child->duplicate());
697 }
699 /** \todo Restore correct layer */
700 /** \todo Restore correct selection */
701 }
703 /* Helper class used by Script::execute */
704 class pipe_t {
705 public:
706 /* These functions set errno if they return false.
707 I'm not sure whether that's a good idea or not, but it should be reasonably
708 straightforward to change it if needed. */
709 bool open(char *command, char const *errorFile, int mode);
710 bool close();
712 /* These return the number of bytes read/written. */
713 size_t read(void *buffer, size_t size);
714 size_t write(void const *buffer, size_t size);
716 enum {
717 mode_read = 1 << 0,
718 mode_write = 1 << 1,
719 };
721 private:
722 #ifdef WIN32
723 /* This is used to translate win32 errors into errno errors.
724 It only recognizes a few win32 errors for the moment though. */
725 static int translate_error(DWORD err);
727 HANDLE hpipe;
728 #else
729 FILE *ppipe;
730 #endif
731 };
733 /**
734 \return none
735 \brief This is the core of the extension file as it actually does
736 the execution of the extension.
737 \param in_command The command to be executed
738 \param filein Filename coming in
739 \param fileout Filename of the out file
740 \return Number of bytes that were read into the output file.
742 The first thing that this function does is build the command to be
743 executed. This consists of the first string (in_command) and then
744 the filename for input (filein). This file is put on the command
745 line.
747 The next thing is that this function does is open a pipe to the
748 command and get the file handle in the ppipe variable. It then
749 opens the output file with the output file handle. Both of these
750 operations are checked extensively for errors.
752 After both are opened, then the data is copied from the output
753 of the pipe into the file out using fread and fwrite. These two
754 functions are used because of their primitive nature they make
755 no assumptions about the data. A buffer is used in the transfer,
756 but the output of fread is stored so the exact number of bytes
757 is handled gracefully.
759 At the very end (after the data has been copied) both of the files
760 are closed, and we return to what we were doing.
761 */
762 int
763 Script::execute (const gchar * in_command, const gchar * filein, const gchar * fileout)
764 {
765 g_return_val_if_fail(in_command != NULL, 0);
766 // printf("Executing: %s\n", in_command);
768 gchar * errorFile;
769 gint errorFileNum;
770 errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &errorFile, NULL);
771 if (errorFileNum != 0) {
772 close(errorFileNum);
773 } else {
774 g_free(errorFile);
775 errorFile = NULL;
776 }
778 char *command = g_strdup_printf("%s \"%s\"", in_command, filein);
779 // std::cout << "Command to run: " << command << std::endl;
781 pipe_t pipe;
782 bool open_success = pipe.open(command, errorFile, pipe_t::mode_read);
783 g_free(command);
785 /* Run script */
786 if (!open_success) {
787 /* Error - could not open pipe - check errno */
788 if (errno == EINVAL) {
789 perror("Extension::Script: Invalid mode argument in popen\n");
790 } else if (errno == ECHILD) {
791 perror("Extension::Script: Cannot obtain child extension status in popen\n");
792 } else {
793 perror("Extension::Script: Unknown error for popen\n");
794 }
795 return 0;
796 }
798 Inkscape::IO::dump_fopen_call(fileout, "J");
799 FILE *pfile = Inkscape::IO::fopen_utf8name(fileout, "w");
801 if (pfile == NULL) {
802 /* Error - could not open file */
803 if (errno == EINVAL) {
804 perror("Extension::Script: The mode provided to fopen was invalid\n");
805 } else {
806 perror("Extension::Script: Unknown error attempting to open temporary file\n");
807 }
808 return 0;
809 }
811 /* Copy pipe output to a temporary file */
812 int amount_read = 0;
813 char buf[BUFSIZE];
814 int num_read;
815 while ((num_read = pipe.read(buf, BUFSIZE)) != 0) {
816 amount_read += num_read;
817 fwrite(buf, 1, num_read, pfile);
818 }
820 /* Close file */
821 if (fclose(pfile) == EOF) {
822 if (errno == EBADF) {
823 perror("Extension::Script: The filedescriptor for the temporary file is invalid\n");
824 return 0;
825 } else {
826 perror("Extension::Script: Unknown error closing temporary file\n");
827 }
828 }
830 /* Close pipe */
831 if (!pipe.close()) {
832 if (errno == EINVAL) {
833 perror("Extension::Script: Invalid mode set for pclose\n");
834 } else if (errno == ECHILD) {
835 perror("Extension::Script: Could not obtain child status for pclose\n");
836 } else {
837 if (errorFile != NULL) {
838 checkStderr(errorFile, Gtk::MESSAGE_ERROR,
839 _("Inkscape has received an error from the script that it called. "
840 "The text returned with the error is included below. "
841 "Inkscape will continue working, but the action you requested has been cancelled."));
842 } else {
843 perror("Extension::Script: Unknown error for pclose\n");
844 }
845 }
846 /* Could be a lie, but if there is an error, we don't want
847 * to count on what was read being good */
848 amount_read = 0;
849 } else {
850 if (errorFile != NULL) {
851 checkStderr(errorFile, Gtk::MESSAGE_INFO,
852 _("Inkscape has received additional data from the script executed. "
853 "The script did not return an error, but this may indicate the results will not be as expected."));
854 }
855 }
857 if (errorFile != NULL) {
858 unlink(errorFile);
859 g_free(errorFile);
860 }
862 return amount_read;
863 }
865 /** \brief This function checks the stderr file, and if it has data,
866 shows it in a warning dialog to the user
867 \param filename Filename of the stderr file
868 */
869 void
870 Script::checkStderr (gchar * filename, Gtk::MessageType type, gchar * message)
871 {
872 // magic win32 crlf->lf conversion means the file length is not the same as
873 // the text length, but luckily gtk will accept crlf in textviews so we can
874 // just use binary mode
875 std::ifstream stderrf (filename, std::ios_base::in | std::ios_base::binary);
876 if (!stderrf.is_open()) return;
878 stderrf.seekg(0, std::ios::end);
879 int length = stderrf.tellg();
880 if (0 == length) return;
881 stderrf.seekg(0, std::ios::beg);
883 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
884 warning.set_resizable(true);
886 Gtk::VBox * vbox = warning.get_vbox();
888 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
889 Gtk::TextView * textview = new Gtk::TextView();
890 textview->set_editable(false);
891 textview->set_wrap_mode(Gtk::WRAP_WORD);
892 textview->show();
894 char * buffer = new char [length];
895 stderrf.read(buffer, length);
896 textview->get_buffer()->set_text(buffer, buffer + length);
897 delete buffer;
898 stderrf.close();
900 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
901 scrollwindow->add(*textview);
902 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
903 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
904 scrollwindow->show();
906 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
908 warning.run();
910 return;
911 }
913 #ifdef WIN32
915 bool pipe_t::open(char *command, char const *errorFile, int mode_p) {
916 HANDLE pipe_write;
918 // Create pipe
919 {
920 SECURITY_ATTRIBUTES secattrs;
921 ZeroMemory(&secattrs, sizeof(secattrs));
922 secattrs.nLength = sizeof(secattrs);
923 secattrs.lpSecurityDescriptor = 0;
924 secattrs.bInheritHandle = TRUE;
925 HANDLE t_pipe_read = 0;
926 if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) {
927 errno = translate_error(GetLastError());
928 return false;
929 }
930 // This duplicate handle makes the read pipe uninheritable
931 if ( !DuplicateHandle(GetCurrentProcess(), t_pipe_read, GetCurrentProcess(), &hpipe, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) ) {
932 int en = translate_error(GetLastError());
933 CloseHandle(t_pipe_read);
934 CloseHandle(pipe_write);
935 errno = en;
936 return false;
937 }
938 }
939 // Open stderr file
940 HANDLE hStdErrFile = CreateFile(errorFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
941 HANDLE hInheritableStdErr;
942 DuplicateHandle(GetCurrentProcess(), hStdErrFile, GetCurrentProcess(), &hInheritableStdErr, 0, TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
944 // Create process
945 {
946 PROCESS_INFORMATION procinfo;
947 STARTUPINFO startupinfo;
948 ZeroMemory(&procinfo, sizeof(procinfo));
949 ZeroMemory(&startupinfo, sizeof(startupinfo));
950 startupinfo.cb = sizeof(startupinfo);
951 //startupinfo.lpReserved = 0;
952 //startupinfo.lpDesktop = 0;
953 //startupinfo.lpTitle = 0;
954 startupinfo.dwFlags = STARTF_USESTDHANDLES;
955 startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
956 startupinfo.hStdOutput = pipe_write;
957 startupinfo.hStdError = hInheritableStdErr;
959 if ( !CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &startupinfo, &procinfo) ) {
960 errno = translate_error(GetLastError());
961 return false;
962 }
963 CloseHandle(procinfo.hThread);
964 CloseHandle(procinfo.hProcess);
965 }
967 // Close our copy of the write handle
968 CloseHandle(hInheritableStdErr);
969 CloseHandle(pipe_write);
971 return true;
972 }
974 bool pipe_t::close() {
975 BOOL retval = CloseHandle(hpipe);
976 if ( !retval ) {
977 errno = translate_error(GetLastError());
978 }
979 return retval != FALSE;
980 }
982 size_t pipe_t::read(void *buffer, size_t size) {
983 DWORD bytes_read = 0;
984 ReadFile(hpipe, buffer, size, &bytes_read, 0);
985 return bytes_read;
986 }
988 size_t pipe_t::write(void const *buffer, size_t size) {
989 DWORD bytes_written = 0;
990 WriteFile(hpipe, buffer, size, &bytes_written, 0);
991 return bytes_written;
992 }
994 int pipe_t::translate_error(DWORD err) {
995 switch (err) {
996 case ERROR_FILE_NOT_FOUND:
997 return ENOENT;
998 case ERROR_INVALID_HANDLE:
999 case ERROR_INVALID_PARAMETER:
1000 return EINVAL;
1001 default:
1002 return 0;
1003 }
1004 }
1006 #else // Win32
1008 bool pipe_t::open(char *command, char const *errorFile, int mode_p) {
1009 char popen_mode[4] = {0,0,0,0};
1010 char *popen_mode_cur = popen_mode;
1012 if ( (mode_p & mode_read) != 0 ) {
1013 *popen_mode_cur++ = 'r';
1014 }
1016 if ( (mode_p & mode_write) != 0 ) {
1017 *popen_mode_cur++ = 'w';
1018 }
1020 /* Get the commandline to be run */
1021 if (errorFile != NULL) {
1022 char * temp;
1023 temp = g_strdup_printf("%s 2> %s", command, errorFile);
1024 ppipe = popen(temp, popen_mode);
1025 g_free(temp);
1026 } else
1027 ppipe = popen(command, popen_mode);
1029 return ppipe != NULL;
1030 }
1032 bool pipe_t::close() {
1033 return fclose(ppipe) == 0;
1034 }
1036 size_t pipe_t::read(void *buffer, size_t size) {
1037 return fread(buffer, 1, size, ppipe);
1038 }
1040 size_t pipe_t::write(void const *buffer, size_t size) {
1041 return fwrite(buffer, 1, size, ppipe);
1042 }
1044 #endif // (Non-)Win32
1047 } /* Inkscape */
1048 } /* module */
1049 } /* Implementation */
1052 /*
1053 Local Variables:
1054 mode:c++
1055 c-file-style:"stroustrup"
1056 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1057 indent-tabs-mode:nil
1058 fill-column:99
1059 End:
1060 */
1061 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :