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.h>
25 #include "ui/view/view.h"
26 #include "desktop-handles.h"
27 #include "selection.h"
28 #include "sp-namedview.h"
29 #include "io/sys.h"
30 #include "registrytool.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"
42 #ifdef WIN32
43 #include <windows.h>
44 #include <sys/stat.h>
45 #endif
49 /** This is the command buffer that gets allocated from the stack */
50 #define BUFSIZE (255)
54 /* Namespaces */
55 namespace Inkscape {
56 namespace Extension {
57 namespace Implementation {
61 //Interpreter lookup table
62 struct interpreter_t {
63 gchar * identity;
64 gchar * prefstring;
65 gchar * defaultval;
66 };
69 static interpreter_t interpreterTab[] = {
70 {"perl", "perl-interpreter", "perl" },
71 {"python", "python-interpreter", "python" },
72 {"ruby", "ruby-interpreter", "ruby" },
73 {"shell", "shell-interpreter", "sh" },
74 { NULL, NULL, NULL }
75 };
79 /**
80 * Look up an interpreter name, and translate to something that
81 * is executable
82 */
83 static Glib::ustring
84 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
85 {
87 Glib::ustring interpName = interpNameArg;
89 interpreter_t *interp;
90 bool foundInterp = false;
91 for (interp = interpreterTab ; interp->identity ; interp++ ){
92 if (interpName == interp->identity) {
93 foundInterp = true;
94 break;
95 }
96 }
98 // Do we have a supported interpreter type?
99 if (!foundInterp)
100 return "";
101 interpName = interp->defaultval;
103 // 1. Check preferences
104 gchar *prefInterp = (gchar *)prefs_get_string_attribute(
105 "extensions", interp->prefstring);
107 if (prefInterp) {
108 interpName = prefInterp;
109 return interpName;
110 }
112 #ifdef _WIN32
114 // 2. Windows. Try looking relative to inkscape.exe
115 RegistryTool rt;
116 Glib::ustring fullPath;
117 Glib::ustring path;
118 Glib::ustring exeName;
119 if (rt.getExeInfo(fullPath, path, exeName)) {
120 Glib::ustring interpPath = path;
121 interpPath.append("\\");
122 interpPath.append(interpName);
123 interpPath.append("\\");
124 interpPath.append(interpName);
125 interpPath.append(".exe");
126 struct stat finfo;
127 if (stat(interpPath .c_str(), &finfo) ==0) {
128 g_message("Found local interpreter, '%s', Size: %d",
129 interpPath .c_str(),
130 finfo.st_size);
131 return interpPath;
132 }
133 }
135 // 3. Try searching the path
136 char szExePath[MAX_PATH];
137 char szCurrentDir[MAX_PATH];
138 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
139 unsigned int ret = (unsigned int)FindExecutable(
140 interpName.c_str(), szCurrentDir, szExePath);
141 if (ret > 32) {
142 interpName = szExePath;
143 return interpName;
144 }
146 #endif // win32
149 return interpName;
150 }
157 /**
158 \return A script object
159 \brief This function creates a script object and sets up the
160 variables.
162 This function just sets the command to NULL. It should get built
163 officially in the load function. This allows for less allocation
164 of memory in the unloaded state.
165 */
166 Script::Script() :
167 Implementation()
168 {
169 }
172 /**
173 * brief Destructor
174 */
175 Script::~Script()
176 {
177 }
181 /**
182 \return A string with the complete string with the relative directory expanded
183 \brief This function takes in a Repr that contains a reldir entry
184 and returns that data with the relative directory expanded.
185 Mostly it is here so that relative directories all get used
186 the same way.
187 \param reprin The Inkscape::XML::Node with the reldir in it.
189 Basically this function looks at an attribute of the Repr, and makes
190 a decision based on that. Currently, it is only working with the
191 'extensions' relative directory, but there will be more of them.
192 One thing to notice is that this function always returns an allocated
193 string. This means that the caller of this function can always
194 free what they are given (and should do it too!).
195 */
196 Glib::ustring
197 Script::solve_reldir(Inkscape::XML::Node *reprin) {
199 gchar const *s = reprin->attribute("reldir");
201 if (!s) {
202 Glib::ustring str = sp_repr_children(reprin)->content();
203 return str;
204 }
206 Glib::ustring reldir = s;
208 if (reldir == "extensions") {
210 for (unsigned int i=0;
211 i < Inkscape::Extension::Extension::search_path.size();
212 i++) {
214 gchar * fname = g_build_filename(
215 Inkscape::Extension::Extension::search_path[i],
216 sp_repr_children(reprin)->content(),
217 NULL);
218 Glib::ustring filename = fname;
219 g_free(fname);
221 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
222 return filename;
224 }
225 } else {
226 Glib::ustring str = sp_repr_children(reprin)->content();
227 return str;
228 }
230 return "";
231 }
235 /**
236 \return Whether the command given exists, including in the path
237 \brief This function is used to find out if something exists for
238 the check command. It can look in the path if required.
239 \param command The command or file that should be looked for
241 The first thing that this function does is check to see if the
242 incoming file name has a directory delimiter in it. This would
243 mean that it wants to control the directories, and should be
244 used directly.
246 If not, the path is used. Each entry in the path is stepped through,
247 attached to the string, and then tested. If the file is found
248 then a TRUE is returned. If we get all the way through the path
249 then a FALSE is returned, the command could not be found.
250 */
251 bool
252 Script::check_existance(const Glib::ustring &command)
253 {
255 // Check the simple case first
256 if (command.size() == 0) {
257 return false;
258 }
260 //Don't search when it contains a slash. */
261 if (command.find(G_DIR_SEPARATOR) != command.npos) {
262 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
263 return true;
264 else
265 return false;
266 }
269 Glib::ustring path;
270 gchar *s = (gchar *) g_getenv("PATH");
271 if (s)
272 path = s;
273 else
274 /* There is no `PATH' in the environment.
275 The default search path is the current directory */
276 path = G_SEARCHPATH_SEPARATOR_S;
278 unsigned int pos = 0;
279 unsigned int pos2 = 0;
280 while ( pos < path.size() ) {
282 Glib::ustring localPath;
284 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
285 if (pos2 == path.npos) {
286 localPath = path.substr(pos);
287 pos = path.size();
288 } else {
289 localPath = path.substr(pos, pos2-pos);
290 pos = pos2+1;
291 }
293 //printf("### %s\n", localPath.c_str());
294 Glib::ustring candidatePath =
295 Glib::build_filename(localPath, command);
297 if (Inkscape::IO::file_test(candidatePath .c_str(),
298 G_FILE_TEST_EXISTS))
299 return true;
301 }
303 return false;
304 }
310 /**
311 \return none
312 \brief This function 'loads' an extention, basically it determines
313 the full command for the extention and stores that.
314 \param module The extention to be loaded.
316 The most difficult part about this function is finding the actual
317 command through all of the Reprs. Basically it is hidden down a
318 couple of layers, and so the code has to move down too. When
319 the command is actually found, it has its relative directory
320 solved.
322 At that point all of the loops are exited, and there is an
323 if statement to make sure they didn't exit because of not finding
324 the command. If that's the case, the extention doesn't get loaded
325 and should error out at a higher level.
326 */
328 bool
329 Script::load(Inkscape::Extension::Extension *module)
330 {
331 if (module->loaded())
332 return TRUE;
334 helper_extension = "";
336 /* This should probably check to find the executable... */
337 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
338 Glib::ustring command_text;
339 while (child_repr != NULL) {
340 if (!strcmp(child_repr->name(), "script")) {
341 child_repr = sp_repr_children(child_repr);
342 while (child_repr != NULL) {
343 if (!strcmp(child_repr->name(), "command")) {
344 command_text = solve_reldir(child_repr);
346 const gchar *interpretstr = child_repr->attribute("interpreter");
347 if (interpretstr != NULL) {
348 Glib::ustring interpString =
349 resolveInterpreterExecutable(interpretstr);
350 interpString .append(" ");
351 interpString .append(command_text);
352 command_text = interpString;
353 }
354 }
355 if (!strcmp(child_repr->name(), "helper_extension"))
356 helper_extension = sp_repr_children(child_repr)->content();
357 child_repr = sp_repr_next(child_repr);
358 }
360 break;
361 }
362 child_repr = sp_repr_next(child_repr);
363 }
365 g_return_val_if_fail(command_text.size() > 0, FALSE);
367 command = command_text;
368 return true;
369 }
372 /**
373 \return None.
374 \brief Unload this puppy!
375 \param module Extension to be unloaded.
377 This function just sets the module to unloaded. It free's the
378 command if it has been allocated.
379 */
380 void
381 Script::unload(Inkscape::Extension::Extension *module)
382 {
383 command = "";
384 helper_extension = "";
385 }
390 /**
391 \return Whether the check passed or not
392 \brief Check every dependency that was given to make sure we should keep this extension
393 \param module The Extension in question
395 */
396 bool
397 Script::check(Inkscape::Extension::Extension *module)
398 {
399 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
400 while (child_repr != NULL) {
401 if (!strcmp(child_repr->name(), "script")) {
402 child_repr = sp_repr_children(child_repr);
403 while (child_repr != NULL) {
404 if (!strcmp(child_repr->name(), "check")) {
405 Glib::ustring command_text = solve_reldir(child_repr);
406 if (command_text.size() > 0) {
407 /* I've got the command */
408 bool existance = check_existance(command_text);
409 if (!existance)
410 return FALSE;
411 }
412 }
414 if (!strcmp(child_repr->name(), "helper_extension")) {
415 gchar const *helper = sp_repr_children(child_repr)->content();
416 if (Inkscape::Extension::db.get(helper) == NULL) {
417 return FALSE;
418 }
419 }
421 child_repr = sp_repr_next(child_repr);
422 }
424 break;
425 }
426 child_repr = sp_repr_next(child_repr);
427 }
429 return true;
430 }
434 /**
435 \return A dialog for preferences
436 \brief A stub funtion right now
437 \param module Module who's preferences need getting
438 \param filename Hey, the file you're getting might be important
440 This function should really do something, right now it doesn't.
441 */
442 Gtk::Widget *
443 Script::prefs_input(Inkscape::Extension::Input *module,
444 const Glib::ustring &filename)
445 {
446 /*return module->autogui(); */
447 return NULL;
448 }
452 /**
453 \return A dialog for preferences
454 \brief A stub funtion right now
455 \param module Module whose preferences need getting
457 This function should really do something, right now it doesn't.
458 */
459 Gtk::Widget *
460 Script::prefs_output(Inkscape::Extension::Output *module)
461 {
462 return module->autogui(NULL, NULL);
463 }
467 /**
468 \return A dialog for preferences
469 \brief A stub funtion right now
470 \param module Module who's preferences need getting
472 This function should really do something, right now it doesn't.
473 */
474 Gtk::Widget *
475 Script::prefs_effect(Inkscape::Extension::Effect *module,
476 Inkscape::UI::View::View *view)
477 {
479 SPDocument * current_document = view->doc();
481 using Inkscape::Util::GSListConstIterator;
482 GSListConstIterator<SPItem *> selected =
483 sp_desktop_selection((SPDesktop *)view)->itemList();
484 Inkscape::XML::Node * first_select = NULL;
485 if (selected != NULL)
486 first_select = SP_OBJECT_REPR(*selected);
488 return module->autogui(current_document, first_select);
489 }
494 /**
495 \return A new document that has been opened
496 \brief This function uses a filename that is put in, and calls
497 the extension's command to create an SVG file which is
498 returned.
499 \param module Extension to use.
500 \param filename File to open.
502 First things first, this function needs a temporary file name. To
503 create on of those the function g_file_open_tmp is used with
504 the header of ink_ext_.
506 The extension is then executed using the 'execute' function
507 with the filname coming in, and the temporary filename. After
508 That executing, the SVG should be in the temporary file.
510 Finally, the temporary file is opened using the SVG input module and
511 a document is returned. That document has its filename set to
512 the incoming filename (so that it's not the temporary filename).
513 That document is then returned from this function.
514 */
515 SPDocument *
516 Script::open(Inkscape::Extension::Input *module,
517 const Glib::ustring &filename)
518 {
520 gchar *tmpname;
522 // FIXME: process the GError instead of passing NULL
523 gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
524 if (tempfd == -1) {
525 /* Error, couldn't create temporary filename */
526 if (errno == EINVAL) {
527 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
528 perror("Extension::Script: template for filenames is misconfigured.\n");
529 exit(-1);
530 } else if (errno == EEXIST) {
531 /* Now the contents of template are undefined. */
532 perror("Extension::Script: Could not create a unique temporary filename\n");
533 return NULL;
534 } else {
535 perror("Extension::Script: Unknown error creating temporary filename\n");
536 exit(-1);
537 }
538 }
540 Glib::ustring tempfilename_out = tmpname;
541 g_free(tmpname);
543 gsize bytesRead = 0;
544 gsize bytesWritten = 0;
545 GError *error = NULL;
546 Glib::ustring local_filename =
547 g_filename_from_utf8( filename.c_str(), -1,
548 &bytesRead, &bytesWritten, &error);
550 int data_read = execute(command, local_filename, tempfilename_out);
553 SPDocument *mydoc = NULL;
554 if (data_read > 10) {
555 if (helper_extension.size()==0) {
556 mydoc = Inkscape::Extension::open(
557 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
558 tempfilename_out.c_str());
559 } else {
560 mydoc = Inkscape::Extension::open(
561 Inkscape::Extension::db.get(helper_extension.c_str()),
562 tempfilename_out.c_str());
563 }
564 }
566 if (mydoc != NULL)
567 sp_document_set_uri(mydoc, (const gchar *)filename.c_str());
569 // make sure we don't leak file descriptors from g_file_open_tmp
570 close(tempfd);
571 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
572 unlink(tempfilename_out.c_str());
575 return mydoc;
576 }
580 /**
581 \return none
582 \brief This function uses an extention to save a document. It first
583 creates an SVG file of the document, and then runs it through
584 the script.
585 \param module Extention to be used
586 \param doc Document to be saved
587 \param filename The name to save the final file as
589 Well, at some point people need to save - it is really what makes
590 the entire application useful. And, it is possible that someone
591 would want to use an extetion for this, so we need a function to
592 do that eh?
594 First things first, the document is saved to a temporary file that
595 is an SVG file. To get the temporary filename g_file_open_tmp is used with
596 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
597 end of the function.
599 After we have the SVG file, then extention_execute is called with
600 the temporary file name and the final output filename. This should
601 put the output of the script into the final output file. We then
602 delete the temporary file.
603 */
604 void
605 Script::save(Inkscape::Extension::Output *module,
606 SPDocument *doc,
607 const Glib::ustring &filename)
608 {
610 gchar *tmpname;
611 // FIXME: process the GError instead of passing NULL
612 gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
613 if (tempfd == -1) {
614 /* Error, couldn't create temporary filename */
615 if (errno == EINVAL) {
616 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
617 perror("Extension::Script: template for filenames is misconfigured.\n");
618 exit(-1);
619 } else if (errno == EEXIST) {
620 /* Now the contents of template are undefined. */
621 perror("Extension::Script: Could not create a unique temporary filename\n");
622 return;
623 } else {
624 perror("Extension::Script: Unknown error creating temporary filename\n");
625 exit(-1);
626 }
627 }
629 Glib::ustring tempfilename_in = tmpname;
630 g_free(tmpname);
632 if (helper_extension.size() == 0) {
633 Inkscape::Extension::save(
634 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
635 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
636 } else {
637 Inkscape::Extension::save(
638 Inkscape::Extension::db.get(helper_extension.c_str()),
639 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
640 }
642 gsize bytesRead = 0;
643 gsize bytesWritten = 0;
644 GError *error = NULL;
645 Glib::ustring local_filename =
646 g_filename_from_utf8( filename.c_str(), -1,
647 &bytesRead, &bytesWritten, &error);
649 Glib::ustring local_command = command;
650 Glib::ustring paramString = *module->paramString();
651 local_command.append(paramString);
653 execute(local_command, tempfilename_in, local_filename);
656 // make sure we don't leak file descriptors from g_file_open_tmp
657 close(tempfd);
658 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
659 unlink(tempfilename_in.c_str());
660 }
664 /**
665 \return none
666 \brief This function uses an extention as a effect on a document.
667 \param module Extention to effect with.
668 \param doc Document to run through the effect.
670 This function is a little bit trickier than the previous two. It
671 needs two temporary files to get it's work done. Both of these
672 files have random names created for them using the g_file_open_temp function
673 with the sp_ext_ prefix in the temporary directory. Like the other
674 functions, the temporary files are deleted at the end.
676 To save/load the two temporary documents (both are SVG) the internal
677 modules for SVG load and save are used. They are both used through
678 the module system function by passing their keys into the functions.
680 The command itself is built a little bit differently than in other
681 functions because the effect support selections. So on the command
682 line a list of all the ids that are selected is included. Currently,
683 this only works for a single selected object, but there will be more.
684 The command string is filled with the data, and then after the execution
685 it is freed.
687 The execute function is used at the core of this function
688 to execute the Script on the two SVG documents (actually only one
689 exists at the time, the other is created by that script). At that
690 point both should be full, and the second one is loaded.
691 */
692 void
693 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
694 {
695 SPDocument * mydoc = NULL;
697 gchar *tmpname;
698 // FIXME: process the GError instead of passing NULL
699 gint tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
700 if (tempfd_in == -1) {
701 /* Error, couldn't create temporary filename */
702 if (errno == EINVAL) {
703 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
704 perror("Extension::Script: template for filenames is misconfigured.\n");
705 exit(-1);
706 } else if (errno == EEXIST) {
707 /* Now the contents of template are undefined. */
708 perror("Extension::Script: Could not create a unique temporary filename\n");
709 return;
710 } else {
711 perror("Extension::Script: Unknown error creating temporary filename\n");
712 exit(-1);
713 }
714 }
716 Glib::ustring tempfilename_in = tmpname;
717 g_free(tmpname);
720 // FIXME: process the GError instead of passing NULL
721 gint tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
722 if (tempfd_out == -1) {
723 /* Error, couldn't create temporary filename */
724 if (errno == EINVAL) {
725 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
726 perror("Extension::Script: template for filenames is misconfigured.\n");
727 exit(-1);
728 } else if (errno == EEXIST) {
729 /* Now the contents of template are undefined. */
730 perror("Extension::Script: Could not create a unique temporary filename\n");
731 return;
732 } else {
733 perror("Extension::Script: Unknown error creating temporary filename\n");
734 exit(-1);
735 }
736 }
738 Glib::ustring tempfilename_out= tmpname;
739 g_free(tmpname);
741 Inkscape::Extension::save(
742 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
743 doc->doc(), tempfilename_in.c_str(), FALSE, FALSE, FALSE);
745 Glib::ustring local_command(command);
747 /* fixme: Should be some sort of checking here. Don't know how to do this with structs instead
748 * of classes. */
749 SPDesktop *desktop = (SPDesktop *) doc;
750 if (desktop != NULL) {
751 Inkscape::Util::GSListConstIterator<SPItem *> selected =
752 sp_desktop_selection(desktop)->itemList();
753 while ( selected != NULL ) {
754 local_command += " --id=";
755 local_command += SP_OBJECT_ID(*selected);
756 ++selected;
757 }
758 }
760 Glib::ustring paramString = *module->paramString();
761 local_command.append(paramString);
764 // std::cout << local_command << std::endl;
766 int data_read = execute(local_command, tempfilename_in, tempfilename_out);
768 if (data_read > 10)
769 mydoc = Inkscape::Extension::open(
770 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
771 tempfilename_out.c_str());
773 // make sure we don't leak file descriptors from g_file_open_tmp
774 close(tempfd_in);
775 close(tempfd_out);
777 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
778 unlink(tempfilename_in.c_str());
779 unlink(tempfilename_out.c_str());
782 /* Do something with mydoc.... */
783 if (mydoc) {
784 doc->doc()->emitReconstructionStart();
785 copy_doc(doc->doc()->rroot, mydoc->rroot);
786 doc->doc()->emitReconstructionFinish();
787 mydoc->release();
788 }
789 }
793 /**
794 \brief A function to take all the svg elements from one document
795 and put them in another.
796 \param oldroot The root node of the document to be replaced
797 \param newroot The root node of the document to replace it with
799 This function first deletes all of the data in the old document. It
800 does this by creating a list of what needs to be deleted, and then
801 goes through the list. This two pass approach removes issues with
802 the list being change while parsing through it. Lots of nasty bugs.
804 Then, it goes through the new document, duplicating all of the
805 elements and putting them into the old document. The copy
806 is then complete.
807 */
808 void
809 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
810 {
811 std::vector<Inkscape::XML::Node *> delete_list;
812 for (Inkscape::XML::Node * child = oldroot->firstChild();
813 child != NULL;
814 child = child->next()) {
815 if (!strcmp("sodipodi:namedview", child->name()))
816 continue;
817 if (!strcmp("svg:defs", child->name()))
818 continue;
819 delete_list.push_back(child);
820 }
821 for (unsigned int i = 0; i < delete_list.size(); i++)
822 sp_repr_unparent(delete_list[i]);
824 for (Inkscape::XML::Node * child = newroot->firstChild();
825 child != NULL;
826 child = child->next()) {
827 if (!strcmp("sodipodi:namedview", child->name()))
828 continue;
829 if (!strcmp("svg:defs", child->name()))
830 continue;
831 oldroot->appendChild(child->duplicate());
832 }
834 /** \todo Restore correct layer */
835 /** \todo Restore correct selection */
836 }
840 /* Helper class used by Script::execute */
841 class pipe_t {
842 public:
843 /* These functions set errno if they return false.
844 I'm not sure whether that's a good idea or not, but it should be reasonably
845 straightforward to change it if needed. */
846 bool open(const Glib::ustring &command,
847 const Glib::ustring &errorFile,
848 int mode);
849 bool close();
851 /* These return the number of bytes read/written. */
852 size_t read(void *buffer, size_t size);
853 size_t write(void const *buffer, size_t size);
855 enum {
856 mode_read = 1 << 0,
857 mode_write = 1 << 1,
858 };
860 private:
861 #ifdef WIN32
862 /* This is used to translate win32 errors into errno errors.
863 It only recognizes a few win32 errors for the moment though. */
864 static int translate_error(DWORD err);
866 HANDLE hpipe;
867 #else
868 FILE *ppipe;
869 #endif
870 };
875 /**
876 \return none
877 \brief This is the core of the extension file as it actually does
878 the execution of the extension.
879 \param in_command The command to be executed
880 \param filein Filename coming in
881 \param fileout Filename of the out file
882 \return Number of bytes that were read into the output file.
884 The first thing that this function does is build the command to be
885 executed. This consists of the first string (in_command) and then
886 the filename for input (filein). This file is put on the command
887 line.
889 The next thing is that this function does is open a pipe to the
890 command and get the file handle in the ppipe variable. It then
891 opens the output file with the output file handle. Both of these
892 operations are checked extensively for errors.
894 After both are opened, then the data is copied from the output
895 of the pipe into the file out using fread and fwrite. These two
896 functions are used because of their primitive nature they make
897 no assumptions about the data. A buffer is used in the transfer,
898 but the output of fread is stored so the exact number of bytes
899 is handled gracefully.
901 At the very end (after the data has been copied) both of the files
902 are closed, and we return to what we were doing.
903 */
904 int
905 Script::execute (const Glib::ustring &in_command,
906 const Glib::ustring &filein,
907 const Glib::ustring &fileout)
908 {
909 g_return_val_if_fail(in_command.size() > 0, 0);
910 // printf("Executing: %s\n", in_command);
912 gchar *tmpname;
913 gint errorFileNum;
914 errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &tmpname, NULL);
915 if (errorFileNum != 0) {
916 close(errorFileNum);
917 } else {
918 g_free(tmpname);
919 }
921 Glib::ustring errorFile = tmpname;
922 g_free(tmpname);
924 Glib::ustring localCommand = in_command;
925 localCommand .append(" \"");
926 localCommand .append(filein);
927 localCommand .append("\"");
929 // std::cout << "Command to run: " << command << std::endl;
931 pipe_t pipe;
932 bool open_success = pipe.open((char *)localCommand.c_str(),
933 errorFile.c_str(),
934 pipe_t::mode_read);
936 /* Run script */
937 if (!open_success) {
938 /* Error - could not open pipe - check errno */
939 if (errno == EINVAL) {
940 perror("Extension::Script: Invalid mode argument in popen\n");
941 } else if (errno == ECHILD) {
942 perror("Extension::Script: Cannot obtain child extension status in popen\n");
943 } else {
944 perror("Extension::Script: Unknown error for popen\n");
945 }
946 return 0;
947 }
949 Inkscape::IO::dump_fopen_call(fileout.c_str(), "J");
950 FILE *pfile = Inkscape::IO::fopen_utf8name(fileout.c_str(), "w");
952 if (pfile == NULL) {
953 /* Error - could not open file */
954 if (errno == EINVAL) {
955 perror("Extension::Script: The mode provided to fopen was invalid\n");
956 } else {
957 perror("Extension::Script: Unknown error attempting to open temporary file\n");
958 }
959 return 0;
960 }
962 /* Copy pipe output to a temporary file */
963 int amount_read = 0;
964 char buf[BUFSIZE];
965 int num_read;
966 while ((num_read = pipe.read(buf, BUFSIZE)) != 0) {
967 amount_read += num_read;
968 fwrite(buf, 1, num_read, pfile);
969 }
971 /* Close file */
972 if (fclose(pfile) == EOF) {
973 if (errno == EBADF) {
974 perror("Extension::Script: The filedescriptor for the temporary file is invalid\n");
975 return 0;
976 } else {
977 perror("Extension::Script: Unknown error closing temporary file\n");
978 }
979 }
981 /* Close pipe */
982 if (!pipe.close()) {
983 if (errno == EINVAL) {
984 perror("Extension::Script: Invalid mode set for pclose\n");
985 } else if (errno == ECHILD) {
986 perror("Extension::Script: Could not obtain child status for pclose\n");
987 } else {
988 if (errorFile != NULL) {
989 checkStderr(errorFile, Gtk::MESSAGE_ERROR,
990 _("Inkscape has received an error from the script that it called. "
991 "The text returned with the error is included below. "
992 "Inkscape will continue working, but the action you requested has been cancelled."));
993 } else {
994 perror("Extension::Script: Unknown error for pclose\n");
995 }
996 }
997 /* Could be a lie, but if there is an error, we don't want
998 * to count on what was read being good */
999 amount_read = 0;
1000 } else {
1001 if (errorFile.size()>0) {
1002 checkStderr(errorFile, Gtk::MESSAGE_INFO,
1003 _("Inkscape has received additional data from the script executed. "
1004 "The script did not return an error, but this may indicate the results will not be as expected."));
1005 }
1006 }
1008 if (errorFile.size()>0) {
1009 unlink(errorFile.c_str());
1010 }
1012 return amount_read;
1013 }
1018 /** \brief This function checks the stderr file, and if it has data,
1019 shows it in a warning dialog to the user
1020 \param filename Filename of the stderr file
1021 */
1022 void
1023 Script::checkStderr (const Glib::ustring &filename,
1024 Gtk::MessageType type,
1025 const Glib::ustring &message)
1026 {
1028 // magic win32 crlf->lf conversion means the file length is not the same as
1029 // the text length, but luckily gtk will accept crlf in textviews so we can
1030 // just use binary mode
1031 std::ifstream stderrf (filename.c_str(), std::ios_base::in | std::ios_base::binary);
1032 if (!stderrf.is_open()) return;
1034 stderrf.seekg(0, std::ios::end);
1035 int length = stderrf.tellg();
1036 if (0 == length) return;
1037 stderrf.seekg(0, std::ios::beg);
1039 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
1040 warning.set_resizable(true);
1042 Gtk::VBox * vbox = warning.get_vbox();
1044 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
1045 Gtk::TextView * textview = new Gtk::TextView();
1046 textview->set_editable(false);
1047 textview->set_wrap_mode(Gtk::WRAP_WORD);
1048 textview->show();
1050 char * buffer = new char [length];
1051 stderrf.read(buffer, length);
1052 textview->get_buffer()->set_text(buffer, buffer + length);
1053 delete buffer;
1054 stderrf.close();
1056 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
1057 scrollwindow->add(*textview);
1058 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
1059 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
1060 scrollwindow->show();
1062 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
1064 warning.run();
1066 return;
1067 }
1072 #ifdef WIN32
1075 bool pipe_t::open(const Glib::ustring &command,
1076 const Glib::ustring &errorFile,
1077 int mode_p) {
1078 HANDLE pipe_write;
1080 //############### Create pipe
1081 SECURITY_ATTRIBUTES secattrs;
1082 ZeroMemory(&secattrs, sizeof(secattrs));
1083 secattrs.nLength = sizeof(secattrs);
1084 secattrs.lpSecurityDescriptor = 0;
1085 secattrs.bInheritHandle = TRUE;
1086 HANDLE t_pipe_read = 0;
1087 if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) {
1088 errno = translate_error(GetLastError());
1089 return false;
1090 }
1091 // This duplicate handle makes the read pipe uninheritable
1092 BOOL ret = DuplicateHandle(GetCurrentProcess(),
1093 t_pipe_read,
1094 GetCurrentProcess(),
1095 &hpipe, 0, FALSE,
1096 DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1097 if (!ret) {
1098 int en = translate_error(GetLastError());
1099 CloseHandle(t_pipe_read);
1100 CloseHandle(pipe_write);
1101 errno = en;
1102 return false;
1103 }
1105 //############### Open stderr file
1106 HANDLE hStdErrFile = CreateFile(errorFile.c_str(),
1107 GENERIC_WRITE,
1108 FILE_SHARE_READ | FILE_SHARE_WRITE,
1109 NULL, CREATE_ALWAYS, 0, NULL);
1110 HANDLE hInheritableStdErr;
1111 DuplicateHandle(GetCurrentProcess(),
1112 hStdErrFile,
1113 GetCurrentProcess(),
1114 &hInheritableStdErr,
1115 0,
1116 TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1118 //############### Create process
1119 PROCESS_INFORMATION procinfo;
1120 STARTUPINFO startupinfo;
1121 ZeroMemory(&procinfo, sizeof(procinfo));
1122 ZeroMemory(&startupinfo, sizeof(startupinfo));
1123 startupinfo.cb = sizeof(startupinfo);
1124 //startupinfo.lpReserved = 0;
1125 //startupinfo.lpDesktop = 0;
1126 //startupinfo.lpTitle = 0;
1127 startupinfo.dwFlags = STARTF_USESTDHANDLES;
1128 startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
1129 startupinfo.hStdOutput = pipe_write;
1130 startupinfo.hStdError = hInheritableStdErr;
1132 if ( !CreateProcess(NULL, (CHAR *)command.c_str(),
1133 NULL, NULL, TRUE,
1134 0, NULL, NULL,
1135 &startupinfo, &procinfo) ) {
1136 errno = translate_error(GetLastError());
1137 return false;
1138 }
1139 CloseHandle(procinfo.hThread);
1140 CloseHandle(procinfo.hProcess);
1142 // Close our copy of the write handle
1143 CloseHandle(hInheritableStdErr);
1144 CloseHandle(pipe_write);
1146 return true;
1147 }
1151 bool pipe_t::close() {
1152 BOOL retval = CloseHandle(hpipe);
1153 if ( !retval )
1154 errno = translate_error(GetLastError());
1155 return retval != FALSE;
1156 }
1158 size_t pipe_t::read(void *buffer, size_t size) {
1159 DWORD bytes_read = 0;
1160 ReadFile(hpipe, buffer, size, &bytes_read, 0);
1161 return bytes_read;
1162 }
1164 size_t pipe_t::write(void const *buffer, size_t size) {
1165 DWORD bytes_written = 0;
1166 WriteFile(hpipe, buffer, size, &bytes_written, 0);
1167 return bytes_written;
1168 }
1170 int pipe_t::translate_error(DWORD err) {
1171 switch (err) {
1172 case ERROR_FILE_NOT_FOUND:
1173 return ENOENT;
1174 case ERROR_INVALID_HANDLE:
1175 case ERROR_INVALID_PARAMETER:
1176 return EINVAL;
1177 default:
1178 return 0;
1179 }
1180 }
1183 #else // not Win32
1186 bool pipe_t::open(const Glib::ustring &command,
1187 const Glib::ustring &errorFile,
1188 int mode_p) {
1190 Glib::ustring popen_mode;
1192 if ( (mode_p & mode_read) != 0 )
1193 popen_mode_cur.append("r");
1195 if ( (mode_p & mode_write) != 0 )
1196 popen_mode_cur.append("w");
1198 // Get the commandline to be run
1199 Glib::ustring pipeStr = command;
1200 if (errorFile.size()>0) {
1201 pipeStr .append(" 2> ");
1202 pipeStr .append(errorFile);
1203 }
1205 ppipe = popen(pipeStr.c_str(), popen_mode.c_str());
1207 return ppipe != NULL;
1208 }
1211 bool pipe_t::close() {
1212 return fclose(ppipe) == 0;
1213 }
1216 size_t pipe_t::read(void *buffer, size_t size) {
1217 return fread(buffer, 1, size, ppipe);
1218 }
1221 size_t pipe_t::write(void const *buffer, size_t size) {
1222 return fwrite(buffer, 1, size, ppipe);
1223 }
1228 #endif // (Non-)Win32
1233 } // namespace Implementation
1234 } // namespace Extension
1235 } // namespace Inkscape
1240 /*
1241 Local Variables:
1242 mode:c++
1243 c-file-style:"stroustrup"
1244 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1245 indent-tabs-mode:nil
1246 fill-column:99
1247 End:
1248 */
1249 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :