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