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 /*
15 TODO:
16 FIXME:
17 After Inkscape makes a formal requirement for a GTK version above 2.11.4, please
18 replace all the instances of ink_ext_XXXXXX in this file that represent
19 svg files with ink_ext_XXXXXX.svg . Doing so will prevent errors in extensions
20 that call inkscape to manipulate the file.
22 "** (inkscape:5848): WARNING **: Format autodetect failed. The file is being opened as SVG."
24 references:
25 http://www.gtk.org/api/2.6/glib/glib-File-Utilities.html#g-mkstemp
26 http://ftp.gnome.org/pub/gnome/sources/glib/2.11/glib-2.11.4.changes
27 http://developer.gnome.org/doc/API/2.0/glib/glib-File-Utilities.html#g-mkstemp
29 --Aaron Spike
30 */
31 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
33 #ifdef HAVE_CONFIG_H
34 # include <config.h>
35 #endif
37 #include <unistd.h>
39 #include <errno.h>
40 #include <gtkmm.h>
42 #include "ui/view/view.h"
43 #include "desktop-handles.h"
44 #include "selection.h"
45 #include "sp-namedview.h"
46 #include "io/sys.h"
47 #include "prefs-utils.h"
48 #include "../system.h"
49 #include "extension/effect.h"
50 #include "extension/output.h"
51 #include "extension/db.h"
52 #include "script.h"
53 #include "dialogs/dialog-events.h"
55 #include "util/glib-list-iterators.h"
59 #ifdef WIN32
60 #include <windows.h>
61 #include <sys/stat.h>
62 #include "registrytool.h"
63 #endif
67 /** This is the command buffer that gets allocated from the stack */
68 #define BUFSIZE (255)
72 /* Namespaces */
73 namespace Inkscape {
74 namespace Extension {
75 namespace Implementation {
79 //Interpreter lookup table
80 struct interpreter_t {
81 gchar * identity;
82 gchar * prefstring;
83 gchar * defaultval;
84 };
87 static interpreter_t interpreterTab[] = {
88 {"perl", "perl-interpreter", "perl" },
89 {"python", "python-interpreter", "python" },
90 {"ruby", "ruby-interpreter", "ruby" },
91 {"shell", "shell-interpreter", "sh" },
92 { NULL, NULL, NULL }
93 };
97 /**
98 * Look up an interpreter name, and translate to something that
99 * is executable
100 */
101 static Glib::ustring
102 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
103 {
105 Glib::ustring interpName = interpNameArg;
107 interpreter_t *interp;
108 bool foundInterp = false;
109 for (interp = interpreterTab ; interp->identity ; interp++ ){
110 if (interpName == interp->identity) {
111 foundInterp = true;
112 break;
113 }
114 }
116 // Do we have a supported interpreter type?
117 if (!foundInterp)
118 return "";
119 interpName = interp->defaultval;
121 // 1. Check preferences
122 gchar *prefInterp = (gchar *)prefs_get_string_attribute(
123 "extensions", interp->prefstring);
125 if (prefInterp) {
126 interpName = prefInterp;
127 return interpName;
128 }
130 #ifdef _WIN32
132 // 2. Windows. Try looking relative to inkscape.exe
133 RegistryTool rt;
134 Glib::ustring fullPath;
135 Glib::ustring path;
136 Glib::ustring exeName;
137 if (rt.getExeInfo(fullPath, path, exeName)) {
138 Glib::ustring interpPath = path;
139 interpPath.append("\\");
140 interpPath.append(interpName);
141 interpPath.append("\\");
142 interpPath.append(interpName);
143 interpPath.append(".exe");
144 struct stat finfo;
145 if (stat(interpPath .c_str(), &finfo) ==0) {
146 g_message("Found local interpreter, '%s', Size: %d",
147 interpPath .c_str(),
148 (int)finfo.st_size);
149 return interpPath;
150 }
151 }
153 // 3. Try searching the path
154 char szExePath[MAX_PATH];
155 char szCurrentDir[MAX_PATH];
156 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
157 unsigned int ret = (unsigned int)FindExecutable(
158 interpName.c_str(), szCurrentDir, szExePath);
159 if (ret > 32) {
160 interpName = szExePath;
161 return interpName;
162 }
164 #endif // win32
167 return interpName;
168 }
175 /**
176 \return A script object
177 \brief This function creates a script object and sets up the
178 variables.
180 This function just sets the command to NULL. It should get built
181 officially in the load function. This allows for less allocation
182 of memory in the unloaded state.
183 */
184 Script::Script() :
185 Implementation()
186 {
187 }
190 /**
191 * brief Destructor
192 */
193 Script::~Script()
194 {
195 }
199 /**
200 \return A string with the complete string with the relative directory expanded
201 \brief This function takes in a Repr that contains a reldir entry
202 and returns that data with the relative directory expanded.
203 Mostly it is here so that relative directories all get used
204 the same way.
205 \param reprin The Inkscape::XML::Node with the reldir in it.
207 Basically this function looks at an attribute of the Repr, and makes
208 a decision based on that. Currently, it is only working with the
209 'extensions' relative directory, but there will be more of them.
210 One thing to notice is that this function always returns an allocated
211 string. This means that the caller of this function can always
212 free what they are given (and should do it too!).
213 */
214 Glib::ustring
215 Script::solve_reldir(Inkscape::XML::Node *reprin) {
217 gchar const *s = reprin->attribute("reldir");
219 if (!s) {
220 Glib::ustring str = sp_repr_children(reprin)->content();
221 return str;
222 }
224 Glib::ustring reldir = s;
226 if (reldir == "extensions") {
228 for (unsigned int i=0;
229 i < Inkscape::Extension::Extension::search_path.size();
230 i++) {
232 gchar * fname = g_build_filename(
233 Inkscape::Extension::Extension::search_path[i],
234 sp_repr_children(reprin)->content(),
235 NULL);
236 Glib::ustring filename = fname;
237 g_free(fname);
239 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
240 return filename;
242 }
243 } else {
244 Glib::ustring str = sp_repr_children(reprin)->content();
245 return str;
246 }
248 return "";
249 }
253 /**
254 \return Whether the command given exists, including in the path
255 \brief This function is used to find out if something exists for
256 the check command. It can look in the path if required.
257 \param command The command or file that should be looked for
259 The first thing that this function does is check to see if the
260 incoming file name has a directory delimiter in it. This would
261 mean that it wants to control the directories, and should be
262 used directly.
264 If not, the path is used. Each entry in the path is stepped through,
265 attached to the string, and then tested. If the file is found
266 then a TRUE is returned. If we get all the way through the path
267 then a FALSE is returned, the command could not be found.
268 */
269 bool
270 Script::check_existance(const Glib::ustring &command)
271 {
273 // Check the simple case first
274 if (command.size() == 0) {
275 return false;
276 }
278 //Don't search when it contains a slash. */
279 if (command.find(G_DIR_SEPARATOR) != command.npos) {
280 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
281 return true;
282 else
283 return false;
284 }
287 Glib::ustring path;
288 gchar *s = (gchar *) g_getenv("PATH");
289 if (s)
290 path = s;
291 else
292 /* There is no `PATH' in the environment.
293 The default search path is the current directory */
294 path = G_SEARCHPATH_SEPARATOR_S;
296 std::string::size_type pos = 0;
297 std::string::size_type pos2 = 0;
298 while ( pos < path.size() ) {
300 Glib::ustring localPath;
302 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
303 if (pos2 == path.npos) {
304 localPath = path.substr(pos);
305 pos = path.size();
306 } else {
307 localPath = path.substr(pos, pos2-pos);
308 pos = pos2+1;
309 }
311 //printf("### %s\n", localPath.c_str());
312 Glib::ustring candidatePath =
313 Glib::build_filename(localPath, command);
315 if (Inkscape::IO::file_test(candidatePath .c_str(),
316 G_FILE_TEST_EXISTS))
317 return true;
319 }
321 return false;
322 }
328 /**
329 \return none
330 \brief This function 'loads' an extention, basically it determines
331 the full command for the extention and stores that.
332 \param module The extention to be loaded.
334 The most difficult part about this function is finding the actual
335 command through all of the Reprs. Basically it is hidden down a
336 couple of layers, and so the code has to move down too. When
337 the command is actually found, it has its relative directory
338 solved.
340 At that point all of the loops are exited, and there is an
341 if statement to make sure they didn't exit because of not finding
342 the command. If that's the case, the extention doesn't get loaded
343 and should error out at a higher level.
344 */
346 bool
347 Script::load(Inkscape::Extension::Extension *module)
348 {
349 if (module->loaded())
350 return TRUE;
352 helper_extension = "";
354 /* This should probably check to find the executable... */
355 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
356 Glib::ustring command_text;
357 while (child_repr != NULL) {
358 if (!strcmp(child_repr->name(), "script")) {
359 child_repr = sp_repr_children(child_repr);
360 while (child_repr != NULL) {
361 if (!strcmp(child_repr->name(), "command")) {
362 command_text = solve_reldir(child_repr);
364 const gchar *interpretstr = child_repr->attribute("interpreter");
365 if (interpretstr != NULL) {
366 Glib::ustring interpString =
367 resolveInterpreterExecutable(interpretstr);
368 interpString .append(" \"");
369 interpString .append(command_text);
370 interpString .append("\"");
371 command_text = interpString;
372 }
373 }
374 if (!strcmp(child_repr->name(), "helper_extension"))
375 helper_extension = sp_repr_children(child_repr)->content();
376 child_repr = sp_repr_next(child_repr);
377 }
379 break;
380 }
381 child_repr = sp_repr_next(child_repr);
382 }
384 g_return_val_if_fail(command_text.size() > 0, FALSE);
386 command = command_text;
387 return true;
388 }
391 /**
392 \return None.
393 \brief Unload this puppy!
394 \param module Extension to be unloaded.
396 This function just sets the module to unloaded. It free's the
397 command if it has been allocated.
398 */
399 void
400 Script::unload(Inkscape::Extension::Extension *module)
401 {
402 command = "";
403 helper_extension = "";
404 }
409 /**
410 \return Whether the check passed or not
411 \brief Check every dependency that was given to make sure we should keep this extension
412 \param module The Extension in question
414 */
415 bool
416 Script::check(Inkscape::Extension::Extension *module)
417 {
418 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
419 while (child_repr != NULL) {
420 if (!strcmp(child_repr->name(), "script")) {
421 child_repr = sp_repr_children(child_repr);
422 while (child_repr != NULL) {
423 if (!strcmp(child_repr->name(), "check")) {
424 Glib::ustring command_text = solve_reldir(child_repr);
425 if (command_text.size() > 0) {
426 /* I've got the command */
427 bool existance = check_existance(command_text);
428 if (!existance)
429 return FALSE;
430 }
431 }
433 if (!strcmp(child_repr->name(), "helper_extension")) {
434 gchar const *helper = sp_repr_children(child_repr)->content();
435 if (Inkscape::Extension::db.get(helper) == NULL) {
436 return FALSE;
437 }
438 }
440 child_repr = sp_repr_next(child_repr);
441 }
443 break;
444 }
445 child_repr = sp_repr_next(child_repr);
446 }
448 return true;
449 }
453 /**
454 \return A dialog for preferences
455 \brief A stub funtion right now
456 \param module Module who's preferences need getting
457 \param filename Hey, the file you're getting might be important
459 This function should really do something, right now it doesn't.
460 */
461 Gtk::Widget *
462 Script::prefs_input(Inkscape::Extension::Input *module,
463 const gchar *filename)
464 {
465 /*return module->autogui(); */
466 return NULL;
467 }
471 /**
472 \return A dialog for preferences
473 \brief A stub funtion right now
474 \param module Module whose preferences need getting
476 This function should really do something, right now it doesn't.
477 */
478 Gtk::Widget *
479 Script::prefs_output(Inkscape::Extension::Output *module)
480 {
481 return module->autogui(NULL, NULL);
482 }
486 /**
487 \return A dialog for preferences
488 \brief A stub funtion right now
489 \param module Module who's preferences need getting
491 This function should really do something, right now it doesn't.
492 */
493 Gtk::Widget *
494 Script::prefs_effect(Inkscape::Extension::Effect *module,
495 Inkscape::UI::View::View *view)
496 {
498 SPDocument * current_document = view->doc();
500 using Inkscape::Util::GSListConstIterator;
501 GSListConstIterator<SPItem *> selected =
502 sp_desktop_selection((SPDesktop *)view)->itemList();
503 Inkscape::XML::Node * first_select = NULL;
504 if (selected != NULL)
505 first_select = SP_OBJECT_REPR(*selected);
507 return module->autogui(current_document, first_select);
508 }
513 /**
514 \return A new document that has been opened
515 \brief This function uses a filename that is put in, and calls
516 the extension's command to create an SVG file which is
517 returned.
518 \param module Extension to use.
519 \param filename File to open.
521 First things first, this function needs a temporary file name. To
522 create on of those the function g_file_open_tmp is used with
523 the header of ink_ext_.
525 The extension is then executed using the 'execute' function
526 with the filname coming in, and the temporary filename. After
527 That executing, the SVG should be in the temporary file.
529 Finally, the temporary file is opened using the SVG input module and
530 a document is returned. That document has its filename set to
531 the incoming filename (so that it's not the temporary filename).
532 That document is then returned from this function.
533 */
534 SPDocument *
535 Script::open(Inkscape::Extension::Input *module,
536 const gchar *filenameArg)
537 {
539 Glib::ustring filename = filenameArg;
541 gchar *tmpname;
543 // FIXME: process the GError instead of passing NULL
544 gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
545 if (tempfd == -1) {
546 /* Error, couldn't create temporary filename */
547 if (errno == EINVAL) {
548 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
549 perror("Extension::Script: template for filenames is misconfigured.\n");
550 exit(-1);
551 } else if (errno == EEXIST) {
552 /* Now the contents of template are undefined. */
553 perror("Extension::Script: Could not create a unique temporary filename\n");
554 return NULL;
555 } else {
556 perror("Extension::Script: Unknown error creating temporary filename\n");
557 exit(-1);
558 }
559 }
561 Glib::ustring tempfilename_out = tmpname;
562 g_free(tmpname);
564 gsize bytesRead = 0;
565 gsize bytesWritten = 0;
566 GError *error = NULL;
567 Glib::ustring local_filename =
568 g_filename_from_utf8( filename.c_str(), -1,
569 &bytesRead, &bytesWritten, &error);
571 int data_read = execute(command, local_filename, tempfilename_out);
573 SPDocument *mydoc = NULL;
574 if (data_read > 10) {
575 if (helper_extension.size()==0) {
576 mydoc = Inkscape::Extension::open(
577 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
578 tempfilename_out.c_str());
579 } else {
580 mydoc = Inkscape::Extension::open(
581 Inkscape::Extension::db.get(helper_extension.c_str()),
582 tempfilename_out.c_str());
583 }
584 }
586 if (mydoc != NULL)
587 sp_document_set_uri(mydoc, (const gchar *)filename.c_str());
589 // make sure we don't leak file descriptors from g_file_open_tmp
590 close(tempfd);
591 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
592 unlink(tempfilename_out.c_str());
595 return mydoc;
596 }
600 /**
601 \return none
602 \brief This function uses an extention to save a document. It first
603 creates an SVG file of the document, and then runs it through
604 the script.
605 \param module Extention to be used
606 \param doc Document to be saved
607 \param filename The name to save the final file as
609 Well, at some point people need to save - it is really what makes
610 the entire application useful. And, it is possible that someone
611 would want to use an extetion for this, so we need a function to
612 do that eh?
614 First things first, the document is saved to a temporary file that
615 is an SVG file. To get the temporary filename g_file_open_tmp is used with
616 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
617 end of the function.
619 After we have the SVG file, then extention_execute is called with
620 the temporary file name and the final output filename. This should
621 put the output of the script into the final output file. We then
622 delete the temporary file.
623 */
624 void
625 Script::save(Inkscape::Extension::Output *module,
626 SPDocument *doc,
627 const gchar *filenameArg)
628 {
630 Glib::ustring filename = filenameArg;
632 gchar *tmpname;
633 // FIXME: process the GError instead of passing NULL
634 gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
635 if (tempfd == -1) {
636 /* Error, couldn't create temporary filename */
637 if (errno == EINVAL) {
638 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
639 perror("Extension::Script: template for filenames is misconfigured.\n");
640 exit(-1);
641 } else if (errno == EEXIST) {
642 /* Now the contents of template are undefined. */
643 perror("Extension::Script: Could not create a unique temporary filename\n");
644 return;
645 } else {
646 perror("Extension::Script: Unknown error creating temporary filename\n");
647 exit(-1);
648 }
649 }
651 Glib::ustring tempfilename_in = tmpname;
652 g_free(tmpname);
654 if (helper_extension.size() == 0) {
655 Inkscape::Extension::save(
656 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
657 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
658 } else {
659 Inkscape::Extension::save(
660 Inkscape::Extension::db.get(helper_extension.c_str()),
661 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
662 }
664 gsize bytesRead = 0;
665 gsize bytesWritten = 0;
666 GError *error = NULL;
667 Glib::ustring local_filename =
668 g_filename_from_utf8( filename.c_str(), -1,
669 &bytesRead, &bytesWritten, &error);
671 Glib::ustring local_command = command;
672 Glib::ustring paramString = *module->paramString();
673 local_command.append(paramString);
675 execute(local_command, tempfilename_in, local_filename);
678 // make sure we don't leak file descriptors from g_file_open_tmp
679 close(tempfd);
680 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
681 unlink(tempfilename_in.c_str());
682 }
686 /**
687 \return none
688 \brief This function uses an extention as a effect on a document.
689 \param module Extention to effect with.
690 \param doc Document to run through the effect.
692 This function is a little bit trickier than the previous two. It
693 needs two temporary files to get it's work done. Both of these
694 files have random names created for them using the g_file_open_temp function
695 with the ink_ext_ prefix in the temporary directory. Like the other
696 functions, the temporary files are deleted at the end.
698 To save/load the two temporary documents (both are SVG) the internal
699 modules for SVG load and save are used. They are both used through
700 the module system function by passing their keys into the functions.
702 The command itself is built a little bit differently than in other
703 functions because the effect support selections. So on the command
704 line a list of all the ids that are selected is included. Currently,
705 this only works for a single selected object, but there will be more.
706 The command string is filled with the data, and then after the execution
707 it is freed.
709 The execute function is used at the core of this function
710 to execute the Script on the two SVG documents (actually only one
711 exists at the time, the other is created by that script). At that
712 point both should be full, and the second one is loaded.
713 */
714 void
715 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
716 {
717 if (module->no_doc) {
718 // this is a no-doc extension, e.g. a Help menu command;
719 // just run the command without any files, ignoring errors
720 Glib::ustring local_command(command);
721 Glib::ustring paramString = *module->paramString();
722 local_command.append(paramString);
724 Glib::ustring empty;
725 execute(local_command, empty, empty);
727 return;
728 }
730 gchar *tmpname;
731 // FIXME: process the GError instead of passing NULL
732 gint tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
733 if (tempfd_in == -1) {
734 /* Error, couldn't create temporary filename */
735 if (errno == EINVAL) {
736 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
737 perror("Extension::Script: template for filenames is misconfigured.\n");
738 exit(-1);
739 } else if (errno == EEXIST) {
740 /* Now the contents of template are undefined. */
741 perror("Extension::Script: Could not create a unique temporary filename\n");
742 return;
743 } else {
744 perror("Extension::Script: Unknown error creating temporary filename\n");
745 exit(-1);
746 }
747 }
749 Glib::ustring tempfilename_in = tmpname;
750 g_free(tmpname);
753 // FIXME: process the GError instead of passing NULL
754 gint tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
755 if (tempfd_out == -1) {
756 /* Error, couldn't create temporary filename */
757 if (errno == EINVAL) {
758 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
759 perror("Extension::Script: template for filenames is misconfigured.\n");
760 exit(-1);
761 } else if (errno == EEXIST) {
762 /* Now the contents of template are undefined. */
763 perror("Extension::Script: Could not create a unique temporary filename\n");
764 return;
765 } else {
766 perror("Extension::Script: Unknown error creating temporary filename\n");
767 exit(-1);
768 }
769 }
771 Glib::ustring tempfilename_out= tmpname;
772 g_free(tmpname);
774 SPDesktop *desktop = (SPDesktop *) doc;
775 sp_namedview_document_from_window(desktop);
777 Inkscape::Extension::save(
778 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
779 doc->doc(), tempfilename_in.c_str(), FALSE, FALSE, FALSE);
781 Glib::ustring local_command(command);
783 /* fixme: Should be some sort of checking here. Don't know how to do this with structs instead
784 * of classes. */
785 if (desktop != NULL) {
786 Inkscape::Util::GSListConstIterator<SPItem *> selected =
787 sp_desktop_selection(desktop)->itemList();
788 while ( selected != NULL ) {
789 local_command += " --id=";
790 local_command += SP_OBJECT_ID(*selected);
791 ++selected;
792 }
793 }
795 Glib::ustring paramString = *module->paramString();
796 local_command.append(paramString);
799 // std::cout << local_command << std::endl;
801 int data_read = execute(local_command, tempfilename_in, tempfilename_out);
803 SPDocument * mydoc = NULL;
804 if (data_read > 10)
805 mydoc = Inkscape::Extension::open(
806 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
807 tempfilename_out.c_str());
809 // make sure we don't leak file descriptors from g_file_open_tmp
810 close(tempfd_in);
811 close(tempfd_out);
813 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
814 unlink(tempfilename_in.c_str());
815 unlink(tempfilename_out.c_str());
818 /* Do something with mydoc.... */
819 if (mydoc) {
820 doc->doc()->emitReconstructionStart();
821 copy_doc(doc->doc()->rroot, mydoc->rroot);
822 doc->doc()->emitReconstructionFinish();
823 mydoc->release();
824 sp_namedview_update_layers_from_document(desktop);
825 }
826 }
830 /**
831 \brief A function to take all the svg elements from one document
832 and put them in another.
833 \param oldroot The root node of the document to be replaced
834 \param newroot The root node of the document to replace it with
836 This function first deletes all of the data in the old document. It
837 does this by creating a list of what needs to be deleted, and then
838 goes through the list. This two pass approach removes issues with
839 the list being change while parsing through it. Lots of nasty bugs.
841 Then, it goes through the new document, duplicating all of the
842 elements and putting them into the old document. The copy
843 is then complete.
844 */
845 void
846 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
847 {
848 std::vector<Inkscape::XML::Node *> delete_list;
849 for (Inkscape::XML::Node * child = oldroot->firstChild();
850 child != NULL;
851 child = child->next()) {
852 if (!strcmp("sodipodi:namedview", child->name()))
853 continue;
854 delete_list.push_back(child);
855 }
856 for (unsigned int i = 0; i < delete_list.size(); i++)
857 sp_repr_unparent(delete_list[i]);
859 for (Inkscape::XML::Node * child = newroot->firstChild();
860 child != NULL;
861 child = child->next()) {
862 if (!strcmp("sodipodi:namedview", child->name()))
863 continue;
864 oldroot->appendChild(child->duplicate());
865 }
867 /** \todo Restore correct layer */
868 /** \todo Restore correct selection */
869 }
873 /* Helper class used by Script::execute */
874 class pipe_t {
875 public:
876 /* These functions set errno if they return false.
877 I'm not sure whether that's a good idea or not, but it should be reasonably
878 straightforward to change it if needed. */
879 bool open(const Glib::ustring &command,
880 const Glib::ustring &errorFile,
881 int mode);
882 bool close();
884 /* These return the number of bytes read/written. */
885 size_t read(void *buffer, size_t size);
886 size_t write(void const *buffer, size_t size);
888 enum {
889 mode_read = 1 << 0,
890 mode_write = 1 << 1,
891 };
893 private:
894 #ifdef WIN32
895 /* This is used to translate win32 errors into errno errors.
896 It only recognizes a few win32 errors for the moment though. */
897 static int translate_error(DWORD err);
899 HANDLE hpipe;
900 #else
901 FILE *ppipe;
902 #endif
903 };
908 /**
909 \brief This is the core of the extension file as it actually does
910 the execution of the extension.
911 \param in_command The command to be executed
912 \param filein Filename coming in
913 \param fileout Filename of the out file
914 \return Number of bytes that were read into the output file.
916 The first thing that this function does is build the command to be
917 executed. This consists of the first string (in_command) and then
918 the filename for input (filein). This file is put on the command
919 line.
921 The next thing is that this function does is open a pipe to the
922 command and get the file handle in the ppipe variable. It then
923 opens the output file with the output file handle. Both of these
924 operations are checked extensively for errors.
926 After both are opened, then the data is copied from the output
927 of the pipe into the file out using fread and fwrite. These two
928 functions are used because of their primitive nature they make
929 no assumptions about the data. A buffer is used in the transfer,
930 but the output of fread is stored so the exact number of bytes
931 is handled gracefully.
933 At the very end (after the data has been copied) both of the files
934 are closed, and we return to what we were doing.
935 */
936 int
937 Script::execute (const Glib::ustring &in_command,
938 const Glib::ustring &filein,
939 const Glib::ustring &fileout)
940 {
941 g_return_val_if_fail(in_command.size() > 0, 0);
942 // printf("Executing: %s\n", in_command);
944 gchar *tmpname;
945 gint errorFileNum;
946 errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &tmpname, NULL);
947 if (errorFileNum != 0) {
948 close(errorFileNum);
949 } else {
950 g_free(tmpname);
951 }
953 Glib::ustring errorFile = tmpname;
954 g_free(tmpname);
956 Glib::ustring localCommand = in_command;
958 if (!(filein.empty())) {
959 localCommand .append(" \"");
960 localCommand .append(filein);
961 localCommand .append("\"");
962 }
964 // std::cout << "Command to run: " << command << std::endl;
966 pipe_t pipe;
967 bool open_success = pipe.open((char *)localCommand.c_str(),
968 errorFile.c_str(),
969 pipe_t::mode_read);
971 /* Run script */
972 if (!open_success) {
973 /* Error - could not open pipe - check errno */
974 if (errno == EINVAL) {
975 perror("Extension::Script: Invalid mode argument in popen\n");
976 } else if (errno == ECHILD) {
977 perror("Extension::Script: Cannot obtain child extension status in popen\n");
978 } else {
979 perror("Extension::Script: Unknown error for popen\n");
980 }
981 return 0;
982 }
984 if (fileout.empty()) { // no output file to create; just close everything and return 0
985 if (errorFile.size()>0) {
986 unlink(errorFile.c_str());
987 }
988 pipe.close();
989 return 0;
990 }
992 /* Copy pipe output to fileout (temporary file) */
993 Inkscape::IO::dump_fopen_call(fileout.c_str(), "J");
994 FILE *pfile = Inkscape::IO::fopen_utf8name(fileout.c_str(), "w");
996 if (pfile == NULL) {
997 /* Error - could not open file */
998 if (errno == EINVAL) {
999 perror("Extension::Script: The mode provided to fopen was invalid\n");
1000 } else {
1001 perror("Extension::Script: Unknown error attempting to open temporary file\n");
1002 }
1003 return 0;
1004 }
1006 int amount_read = 0;
1007 char buf[BUFSIZE];
1008 int num_read;
1009 while ((num_read = pipe.read(buf, BUFSIZE)) != 0) {
1010 amount_read += num_read;
1011 fwrite(buf, 1, num_read, pfile);
1012 }
1014 /* Close file */
1015 if (fclose(pfile) == EOF) {
1016 if (errno == EBADF) {
1017 perror("Extension::Script: The filedescriptor for the temporary file is invalid\n");
1018 return 0;
1019 } else {
1020 perror("Extension::Script: Unknown error closing temporary file\n");
1021 }
1022 }
1024 /* Close pipe */
1025 if (!pipe.close()) {
1026 if (errno == EINVAL) {
1027 perror("Extension::Script: Invalid mode set for pclose\n");
1028 } else if (errno == ECHILD) {
1029 perror("Extension::Script: Could not obtain child status for pclose\n");
1030 } else {
1031 if (!errorFile.empty()) {
1032 checkStderr(errorFile, Gtk::MESSAGE_ERROR,
1033 _("Inkscape has received an error from the script that it called. "
1034 "The text returned with the error is included below. "
1035 "Inkscape will continue working, but the action you requested has been cancelled."));
1036 } else {
1037 perror("Extension::Script: Unknown error for pclose\n");
1038 }
1039 }
1040 /* Could be a lie, but if there is an error, we don't want
1041 * to count on what was read being good */
1042 amount_read = 0;
1043 } else {
1044 if (errorFile.size()>0) {
1045 checkStderr(errorFile, Gtk::MESSAGE_INFO,
1046 _("Inkscape has received additional data from the script executed. "
1047 "The script did not return an error, but this may indicate the results will not be as expected."));
1048 }
1049 }
1051 if (errorFile.size()>0) {
1052 unlink(errorFile.c_str());
1053 }
1055 return amount_read;
1056 }
1061 /** \brief This function checks the stderr file, and if it has data,
1062 shows it in a warning dialog to the user
1063 \param filename Filename of the stderr file
1064 */
1065 void
1066 Script::checkStderr (const Glib::ustring &filename,
1067 Gtk::MessageType type,
1068 const Glib::ustring &message)
1069 {
1071 // magic win32 crlf->lf conversion means the file length is not the same as
1072 // the text length, but luckily gtk will accept crlf in textviews so we can
1073 // just use binary mode
1074 std::ifstream stderrf (filename.c_str(), std::ios_base::in | std::ios_base::binary);
1075 if (!stderrf.is_open()) return;
1077 stderrf.seekg(0, std::ios::end);
1078 int length = stderrf.tellg();
1079 if (0 == length) return;
1080 stderrf.seekg(0, std::ios::beg);
1082 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
1083 warning.set_resizable(true);
1084 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
1085 sp_transientize(dlg);
1087 Gtk::VBox * vbox = warning.get_vbox();
1089 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
1090 Gtk::TextView * textview = new Gtk::TextView();
1091 textview->set_editable(false);
1092 textview->set_wrap_mode(Gtk::WRAP_WORD);
1093 textview->show();
1095 char * buffer = new char [length];
1096 stderrf.read(buffer, length);
1097 textview->get_buffer()->set_text(buffer, buffer + length);
1098 delete buffer;
1099 stderrf.close();
1101 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
1102 scrollwindow->add(*textview);
1103 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
1104 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
1105 scrollwindow->show();
1107 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
1109 warning.run();
1111 return;
1112 }
1117 #ifdef WIN32
1120 bool pipe_t::open(const Glib::ustring &command,
1121 const Glib::ustring &errorFile,
1122 int mode_p) {
1123 HANDLE pipe_write;
1125 //############### Create pipe
1126 SECURITY_ATTRIBUTES secattrs;
1127 ZeroMemory(&secattrs, sizeof(secattrs));
1128 secattrs.nLength = sizeof(secattrs);
1129 secattrs.lpSecurityDescriptor = 0;
1130 secattrs.bInheritHandle = TRUE;
1131 HANDLE t_pipe_read = 0;
1132 if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) {
1133 errno = translate_error(GetLastError());
1134 return false;
1135 }
1136 // This duplicate handle makes the read pipe uninheritable
1137 BOOL ret = DuplicateHandle(GetCurrentProcess(),
1138 t_pipe_read,
1139 GetCurrentProcess(),
1140 &hpipe, 0, FALSE,
1141 DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1142 if (!ret) {
1143 int en = translate_error(GetLastError());
1144 CloseHandle(t_pipe_read);
1145 CloseHandle(pipe_write);
1146 errno = en;
1147 return false;
1148 }
1150 //############### Open stderr file
1151 HANDLE hStdErrFile = CreateFile(errorFile.c_str(),
1152 GENERIC_WRITE,
1153 FILE_SHARE_READ | FILE_SHARE_WRITE,
1154 NULL, CREATE_ALWAYS, 0, NULL);
1155 HANDLE hInheritableStdErr;
1156 DuplicateHandle(GetCurrentProcess(),
1157 hStdErrFile,
1158 GetCurrentProcess(),
1159 &hInheritableStdErr,
1160 0,
1161 TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1163 //############### Create process
1164 PROCESS_INFORMATION procinfo;
1165 STARTUPINFO startupinfo;
1166 ZeroMemory(&procinfo, sizeof(procinfo));
1167 ZeroMemory(&startupinfo, sizeof(startupinfo));
1168 startupinfo.cb = sizeof(startupinfo);
1169 //startupinfo.lpReserved = 0;
1170 //startupinfo.lpDesktop = 0;
1171 //startupinfo.lpTitle = 0;
1172 startupinfo.dwFlags = STARTF_USESTDHANDLES;
1173 startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
1174 startupinfo.hStdOutput = pipe_write;
1175 startupinfo.hStdError = hInheritableStdErr;
1177 if ( !CreateProcess(NULL, (CHAR *)command.c_str(),
1178 NULL, NULL, TRUE,
1179 0, NULL, NULL,
1180 &startupinfo, &procinfo) ) {
1181 errno = translate_error(GetLastError());
1182 return false;
1183 }
1184 CloseHandle(procinfo.hThread);
1185 CloseHandle(procinfo.hProcess);
1187 // Close our copy of the write handle
1188 CloseHandle(hInheritableStdErr);
1189 CloseHandle(pipe_write);
1191 return true;
1192 }
1196 bool pipe_t::close() {
1197 BOOL retval = CloseHandle(hpipe);
1198 if ( !retval )
1199 errno = translate_error(GetLastError());
1200 return retval != FALSE;
1201 }
1203 size_t pipe_t::read(void *buffer, size_t size) {
1204 DWORD bytes_read = 0;
1205 ReadFile(hpipe, buffer, size, &bytes_read, 0);
1206 return bytes_read;
1207 }
1209 size_t pipe_t::write(void const *buffer, size_t size) {
1210 DWORD bytes_written = 0;
1211 WriteFile(hpipe, buffer, size, &bytes_written, 0);
1212 return bytes_written;
1213 }
1215 int pipe_t::translate_error(DWORD err) {
1216 switch (err) {
1217 case ERROR_FILE_NOT_FOUND:
1218 return ENOENT;
1219 case ERROR_INVALID_HANDLE:
1220 case ERROR_INVALID_PARAMETER:
1221 return EINVAL;
1222 default:
1223 return 0;
1224 }
1225 }
1228 #else // not Win32
1231 bool pipe_t::open(const Glib::ustring &command,
1232 const Glib::ustring &errorFile,
1233 int mode_p) {
1235 Glib::ustring popen_mode;
1237 if ( (mode_p & mode_read) != 0 )
1238 popen_mode.append("r");
1240 if ( (mode_p & mode_write) != 0 )
1241 popen_mode.append("w");
1243 // Get the commandline to be run
1244 Glib::ustring pipeStr = command;
1245 if (errorFile.size()>0) {
1246 pipeStr .append(" 2> ");
1247 pipeStr .append(errorFile);
1248 }
1250 ppipe = popen(pipeStr.c_str(), popen_mode.c_str());
1252 return ppipe != NULL;
1253 }
1256 bool pipe_t::close() {
1257 return fclose(ppipe) == 0;
1258 }
1261 size_t pipe_t::read(void *buffer, size_t size) {
1262 return fread(buffer, 1, size, ppipe);
1263 }
1266 size_t pipe_t::write(void const *buffer, size_t size) {
1267 return fwrite(buffer, 1, size, ppipe);
1268 }
1273 #endif // (Non-)Win32
1278 } // namespace Implementation
1279 } // namespace Extension
1280 } // namespace Inkscape
1285 /*
1286 Local Variables:
1287 mode:c++
1288 c-file-style:"stroustrup"
1289 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1290 indent-tabs-mode:nil
1291 fill-column:99
1292 End:
1293 */
1294 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :