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,2007 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 "desktop.h"
45 #include "selection.h"
46 #include "sp-namedview.h"
47 #include "io/sys.h"
48 #include "prefs-utils.h"
49 #include "../system.h"
50 #include "extension/effect.h"
51 #include "extension/output.h"
52 #include "extension/input.h"
53 #include "extension/db.h"
54 #include "script.h"
55 #include "dialogs/dialog-events.h"
56 #include "application/application.h"
58 #include "util/glib-list-iterators.h"
62 #ifdef WIN32
63 #include <windows.h>
64 #include <sys/stat.h>
65 #include "registrytool.h"
66 #endif
70 /** This is the command buffer that gets allocated from the stack */
71 #define BUFSIZE (255)
75 /* Namespaces */
76 namespace Inkscape {
77 namespace Extension {
78 namespace Implementation {
80 void pump_events (void) {
81 while( Gtk::Main::events_pending() )
82 Gtk::Main::iteration();
83 return;
84 }
86 //Interpreter lookup table
87 struct interpreter_t {
88 gchar const *identity;
89 gchar const *prefstring;
90 gchar const *defaultval;
91 };
94 /** \brief A table of what interpreters to call for a given language
96 This table is used to keep track of all the programs to execute a
97 given script. It also tracks the preference to use to overwrite
98 the given interpreter to a custom one per user.
99 */
100 static interpreter_t const interpreterTab[] = {
101 {"perl", "perl-interpreter", "perl" },
102 #ifdef WIN32
103 {"python", "python-interpreter", "pythonw" },
104 #else
105 {"python", "python-interpreter", "python" },
106 #endif
107 {"ruby", "ruby-interpreter", "ruby" },
108 {"shell", "shell-interpreter", "sh" },
109 { NULL, NULL, NULL }
110 };
114 /**
115 * Look up an interpreter name, and translate to something that
116 * is executable
117 */
118 static Glib::ustring
119 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
120 {
122 Glib::ustring interpName = interpNameArg;
124 interpreter_t const *interp;
125 bool foundInterp = false;
126 for (interp = interpreterTab ; interp->identity ; interp++ ){
127 if (interpName == interp->identity) {
128 foundInterp = true;
129 break;
130 }
131 }
133 // Do we have a supported interpreter type?
134 if (!foundInterp)
135 return "";
136 interpName = interp->defaultval;
138 // 1. Check preferences
139 gchar const *prefInterp = prefs_get_string_attribute("extensions", interp->prefstring);
141 if (prefInterp) {
142 interpName = prefInterp;
143 return interpName;
144 }
146 #ifdef WIN32
148 // 2. Windows. Try looking relative to inkscape.exe
149 RegistryTool rt;
150 Glib::ustring fullPath;
151 Glib::ustring path;
152 Glib::ustring exeName;
153 if (rt.getExeInfo(fullPath, path, exeName)) {
154 Glib::ustring interpPath = path;
155 interpPath.append("\\");
156 interpPath.append(interpNameArg);
157 interpPath.append("\\");
158 interpPath.append(interpName);
159 interpPath.append(".exe");
160 struct stat finfo;
161 if (stat(interpPath .c_str(), &finfo) ==0) {
162 g_message("Found local interpreter, '%s', Size: %d",
163 interpPath .c_str(),
164 (int)finfo.st_size);
165 return interpPath;
166 }
167 }
169 // 3. Try searching the path
170 char szExePath[MAX_PATH];
171 char szCurrentDir[MAX_PATH];
172 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
173 unsigned int ret = (unsigned int)FindExecutable(
174 interpName.c_str(), szCurrentDir, szExePath);
175 if (ret > 32) {
176 interpName = szExePath;
177 return interpName;
178 }
180 #endif // win32
183 return interpName;
184 }
188 /** \brief This function creates a script object and sets up the
189 variables.
190 \return A script object
192 This function just sets the command to NULL. It should get built
193 officially in the load function. This allows for less allocation
194 of memory in the unloaded state.
195 */
196 Script::Script() :
197 Implementation()
198 {
199 }
202 /**
203 * brief Destructor
204 */
205 Script::~Script()
206 {
207 }
211 /**
212 \return A string with the complete string with the relative directory expanded
213 \brief This function takes in a Repr that contains a reldir entry
214 and returns that data with the relative directory expanded.
215 Mostly it is here so that relative directories all get used
216 the same way.
217 \param reprin The Inkscape::XML::Node with the reldir in it.
219 Basically this function looks at an attribute of the Repr, and makes
220 a decision based on that. Currently, it is only working with the
221 'extensions' relative directory, but there will be more of them.
222 One thing to notice is that this function always returns an allocated
223 string. This means that the caller of this function can always
224 free what they are given (and should do it too!).
225 */
226 Glib::ustring
227 Script::solve_reldir(Inkscape::XML::Node *reprin) {
229 gchar const *s = reprin->attribute("reldir");
231 if (!s) {
232 Glib::ustring str = sp_repr_children(reprin)->content();
233 return str;
234 }
236 Glib::ustring reldir = s;
238 if (reldir == "extensions") {
240 for (unsigned int i=0;
241 i < Inkscape::Extension::Extension::search_path.size();
242 i++) {
244 gchar * fname = g_build_filename(
245 Inkscape::Extension::Extension::search_path[i],
246 sp_repr_children(reprin)->content(),
247 NULL);
248 Glib::ustring filename = fname;
249 g_free(fname);
251 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
252 return filename;
254 }
255 } else {
256 Glib::ustring str = sp_repr_children(reprin)->content();
257 return str;
258 }
260 return "";
261 }
265 /**
266 \return Whether the command given exists, including in the path
267 \brief This function is used to find out if something exists for
268 the check command. It can look in the path if required.
269 \param command The command or file that should be looked for
271 The first thing that this function does is check to see if the
272 incoming file name has a directory delimiter in it. This would
273 mean that it wants to control the directories, and should be
274 used directly.
276 If not, the path is used. Each entry in the path is stepped through,
277 attached to the string, and then tested. If the file is found
278 then a TRUE is returned. If we get all the way through the path
279 then a FALSE is returned, the command could not be found.
280 */
281 bool
282 Script::check_existance(const Glib::ustring &command)
283 {
285 // Check the simple case first
286 if (command.size() == 0) {
287 return false;
288 }
290 //Don't search when it contains a slash. */
291 if (command.find(G_DIR_SEPARATOR) != command.npos) {
292 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
293 return true;
294 else
295 return false;
296 }
299 Glib::ustring path;
300 gchar *s = (gchar *) g_getenv("PATH");
301 if (s)
302 path = s;
303 else
304 /* There is no `PATH' in the environment.
305 The default search path is the current directory */
306 path = G_SEARCHPATH_SEPARATOR_S;
308 std::string::size_type pos = 0;
309 std::string::size_type pos2 = 0;
310 while ( pos < path.size() ) {
312 Glib::ustring localPath;
314 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
315 if (pos2 == path.npos) {
316 localPath = path.substr(pos);
317 pos = path.size();
318 } else {
319 localPath = path.substr(pos, pos2-pos);
320 pos = pos2+1;
321 }
323 //printf("### %s\n", localPath.c_str());
324 Glib::ustring candidatePath =
325 Glib::build_filename(localPath, command);
327 if (Inkscape::IO::file_test(candidatePath .c_str(),
328 G_FILE_TEST_EXISTS)) {
329 return true;
330 }
332 }
334 return false;
335 }
341 /**
342 \return none
343 \brief This function 'loads' an extention, basically it determines
344 the full command for the extention and stores that.
345 \param module The extention to be loaded.
347 The most difficult part about this function is finding the actual
348 command through all of the Reprs. Basically it is hidden down a
349 couple of layers, and so the code has to move down too. When
350 the command is actually found, it has its relative directory
351 solved.
353 At that point all of the loops are exited, and there is an
354 if statement to make sure they didn't exit because of not finding
355 the command. If that's the case, the extention doesn't get loaded
356 and should error out at a higher level.
357 */
359 bool
360 Script::load(Inkscape::Extension::Extension *module)
361 {
362 if (module->loaded())
363 return true;
365 helper_extension = "";
367 /* This should probably check to find the executable... */
368 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
369 while (child_repr != NULL) {
370 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
371 child_repr = sp_repr_children(child_repr);
372 while (child_repr != NULL) {
373 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
374 const gchar *interpretstr = child_repr->attribute("interpreter");
375 if (interpretstr != NULL) {
376 Glib::ustring interpString =
377 resolveInterpreterExecutable(interpretstr);
378 //g_message("Found: %s and %s",interpString.c_str(),interpretstr);
379 command.insert(command.end(), interpretstr);
380 }
381 Glib::ustring tmp = "\"";
382 tmp += solve_reldir(child_repr);
383 tmp += "\"";
385 command.insert(command.end(), tmp);
386 }
387 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
388 helper_extension = sp_repr_children(child_repr)->content();
389 }
390 child_repr = sp_repr_next(child_repr);
391 }
393 break;
394 }
395 child_repr = sp_repr_next(child_repr);
396 }
398 //g_return_val_if_fail(command.length() > 0, false);
400 return true;
401 }
404 /**
405 \return None.
406 \brief Unload this puppy!
407 \param module Extension to be unloaded.
409 This function just sets the module to unloaded. It free's the
410 command if it has been allocated.
411 */
412 void
413 Script::unload(Inkscape::Extension::Extension */*module*/)
414 {
415 command.clear();
416 helper_extension = "";
417 }
422 /**
423 \return Whether the check passed or not
424 \brief Check every dependency that was given to make sure we should keep this extension
425 \param module The Extension in question
427 */
428 bool
429 Script::check(Inkscape::Extension::Extension *module)
430 {
431 int script_count = 0;
432 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
433 while (child_repr != NULL) {
434 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
435 script_count++;
436 child_repr = sp_repr_children(child_repr);
437 while (child_repr != NULL) {
438 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
439 Glib::ustring command_text = solve_reldir(child_repr);
440 if (command_text.size() > 0) {
441 /* I've got the command */
442 bool existance = check_existance(command_text);
443 if (!existance)
444 return false;
445 }
446 }
448 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
449 gchar const *helper = sp_repr_children(child_repr)->content();
450 if (Inkscape::Extension::db.get(helper) == NULL) {
451 return false;
452 }
453 }
455 child_repr = sp_repr_next(child_repr);
456 }
458 break;
459 }
460 child_repr = sp_repr_next(child_repr);
461 }
463 if (script_count == 0) {
464 return false;
465 }
467 return true;
468 }
470 class ScriptDocCache : public ImplementationDocumentCache {
471 friend class Script;
472 protected:
473 std::string _filename;
474 int _tempfd;
475 public:
476 ScriptDocCache (Inkscape::UI::View::View * view);
477 ~ScriptDocCache ( );
478 };
480 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
481 ImplementationDocumentCache(view),
482 _filename(""),
483 _tempfd(0)
484 {
485 try {
486 _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
487 } catch (...) {
488 /// \todo Popup dialog here
489 return;
490 }
492 SPDesktop *desktop = (SPDesktop *) view;
493 sp_namedview_document_from_window(desktop);
495 Inkscape::Extension::save(
496 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
497 view->doc(), _filename.c_str(), false, false, false);
499 return;
500 }
502 ScriptDocCache::~ScriptDocCache ( )
503 {
504 close(_tempfd);
505 unlink(_filename.c_str());
506 }
508 ImplementationDocumentCache *
509 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
510 return new ScriptDocCache(view);
511 }
514 /**
515 \return A dialog for preferences
516 \brief A stub funtion right now
517 \param module Module who's preferences need getting
518 \param filename Hey, the file you're getting might be important
520 This function should really do something, right now it doesn't.
521 */
522 Gtk::Widget *
523 Script::prefs_input(Inkscape::Extension::Input *module,
524 const gchar */*filename*/)
525 {
526 return module->autogui(NULL, NULL);
527 }
531 /**
532 \return A dialog for preferences
533 \brief A stub funtion right now
534 \param module Module whose preferences need getting
536 This function should really do something, right now it doesn't.
537 */
538 Gtk::Widget *
539 Script::prefs_output(Inkscape::Extension::Output *module)
540 {
541 return module->autogui(NULL, NULL);
542 }
544 /**
545 \return A new document that has been opened
546 \brief This function uses a filename that is put in, and calls
547 the extension's command to create an SVG file which is
548 returned.
549 \param module Extension to use.
550 \param filename File to open.
552 First things first, this function needs a temporary file name. To
553 create on of those the function g_file_open_tmp is used with
554 the header of ink_ext_.
556 The extension is then executed using the 'execute' function
557 with the filname coming in, and the temporary filename. After
558 That executing, the SVG should be in the temporary file.
560 Finally, the temporary file is opened using the SVG input module and
561 a document is returned. That document has its filename set to
562 the incoming filename (so that it's not the temporary filename).
563 That document is then returned from this function.
564 */
565 SPDocument *
566 Script::open(Inkscape::Extension::Input *module,
567 const gchar *filenameArg)
568 {
569 std::list<std::string> params;
570 module->paramListString(params);
572 std::string tempfilename_out;
573 int tempfd_out = 0;
574 try {
575 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
576 } catch (...) {
577 /// \todo Popup dialog here
578 return NULL;
579 }
581 std::string lfilename = Glib::filename_from_utf8(filenameArg);
583 file_listener fileout;
584 int data_read = execute(command, params, lfilename, fileout);
585 fileout.toFile(tempfilename_out);
587 SPDocument * mydoc = NULL;
588 if (data_read > 10) {
589 if (helper_extension.size()==0) {
590 mydoc = Inkscape::Extension::open(
591 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
592 tempfilename_out.c_str());
593 } else {
594 mydoc = Inkscape::Extension::open(
595 Inkscape::Extension::db.get(helper_extension.c_str()),
596 tempfilename_out.c_str());
597 }
598 } // data_read
600 if (mydoc != NULL) {
601 sp_document_set_uri(mydoc, filenameArg);
602 }
604 // make sure we don't leak file descriptors from g_file_open_tmp
605 close(tempfd_out);
607 unlink(tempfilename_out.c_str());
609 return mydoc;
610 } // open
614 /**
615 \return none
616 \brief This function uses an extention to save a document. It first
617 creates an SVG file of the document, and then runs it through
618 the script.
619 \param module Extention to be used
620 \param doc Document to be saved
621 \param filename The name to save the final file as
623 Well, at some point people need to save - it is really what makes
624 the entire application useful. And, it is possible that someone
625 would want to use an extetion for this, so we need a function to
626 do that eh?
628 First things first, the document is saved to a temporary file that
629 is an SVG file. To get the temporary filename g_file_open_tmp is used with
630 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
631 end of the function.
633 After we have the SVG file, then extention_execute is called with
634 the temporary file name and the final output filename. This should
635 put the output of the script into the final output file. We then
636 delete the temporary file.
637 */
638 void
639 Script::save(Inkscape::Extension::Output *module,
640 SPDocument *doc,
641 const gchar *filenameArg)
642 {
643 std::list<std::string> params;
644 module->paramListString(params);
646 std::string tempfilename_in;
647 int tempfd_in = 0;
648 try {
649 tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
650 } catch (...) {
651 /// \todo Popup dialog here
652 return;
653 }
655 if (helper_extension.size() == 0) {
656 Inkscape::Extension::save(
657 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
658 doc, tempfilename_in.c_str(), false, false, false);
659 } else {
660 Inkscape::Extension::save(
661 Inkscape::Extension::db.get(helper_extension.c_str()),
662 doc, tempfilename_in.c_str(), false, false, false);
663 }
666 file_listener fileout;
667 execute(command, params, tempfilename_in, fileout);
669 std::string lfilename = Glib::filename_from_utf8(filenameArg);
670 fileout.toFile(lfilename);
672 // make sure we don't leak file descriptors from g_file_open_tmp
673 close(tempfd_in);
674 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
675 unlink(tempfilename_in.c_str());
677 return;
678 }
682 /**
683 \return none
684 \brief This function uses an extention as a effect on a document.
685 \param module Extention to effect with.
686 \param doc Document to run through the effect.
688 This function is a little bit trickier than the previous two. It
689 needs two temporary files to get it's work done. Both of these
690 files have random names created for them using the g_file_open_temp function
691 with the ink_ext_ prefix in the temporary directory. Like the other
692 functions, the temporary files are deleted at the end.
694 To save/load the two temporary documents (both are SVG) the internal
695 modules for SVG load and save are used. They are both used through
696 the module system function by passing their keys into the functions.
698 The command itself is built a little bit differently than in other
699 functions because the effect support selections. So on the command
700 line a list of all the ids that are selected is included. Currently,
701 this only works for a single selected object, but there will be more.
702 The command string is filled with the data, and then after the execution
703 it is freed.
705 The execute function is used at the core of this function
706 to execute the Script on the two SVG documents (actually only one
707 exists at the time, the other is created by that script). At that
708 point both should be full, and the second one is loaded.
709 */
710 void
711 Script::effect(Inkscape::Extension::Effect *module,
712 Inkscape::UI::View::View *doc,
713 ImplementationDocumentCache * docCache)
714 {
715 if (docCache == NULL) {
716 docCache = newDocCache(module, doc);
717 }
718 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
719 if (dc == NULL) {
720 printf("TOO BAD TO LIVE!!!");
721 exit(1);
722 }
724 SPDesktop *desktop = (SPDesktop *)doc;
725 sp_namedview_document_from_window(desktop);
727 gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
729 std::list<std::string> params;
730 module->paramListString(params);
732 if (module->no_doc) {
733 // this is a no-doc extension, e.g. a Help menu command;
734 // just run the command without any files, ignoring errors
736 Glib::ustring empty;
737 file_listener outfile;
738 execute(command, params, empty, outfile);
740 return;
741 }
743 std::string tempfilename_out;
744 int tempfd_out = 0;
745 try {
746 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
747 } catch (...) {
748 /// \todo Popup dialog here
749 return;
750 }
752 if (desktop != NULL) {
753 Inkscape::Util::GSListConstIterator<SPItem *> selected =
754 sp_desktop_selection(desktop)->itemList();
755 while ( selected != NULL ) {
756 Glib::ustring selected_id;
757 selected_id += "--id=";
758 selected_id += SP_OBJECT_ID(*selected);
759 params.insert(params.begin(), selected_id);
760 ++selected;
761 }
762 }
764 file_listener fileout;
765 int data_read = execute(command, params, dc->_filename, fileout);
766 fileout.toFile(tempfilename_out);
768 pump_events();
770 SPDocument * mydoc = NULL;
771 if (data_read > 10) {
772 mydoc = Inkscape::Extension::open(
773 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
774 tempfilename_out.c_str());
775 } // data_read
777 pump_events();
779 // make sure we don't leak file descriptors from g_file_open_tmp
780 close(tempfd_out);
782 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
783 unlink(tempfilename_out.c_str());
785 /* Do something with mydoc.... */
786 if (mydoc) {
787 doc->doc()->emitReconstructionStart();
788 copy_doc(doc->doc()->rroot, mydoc->rroot);
789 doc->doc()->emitReconstructionFinish();
790 mydoc->release();
791 sp_namedview_update_layers_from_document(desktop);
793 sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
794 }
795 g_free(orig_output_extension);
797 return;
798 }
802 /**
803 \brief A function to take all the svg elements from one document
804 and put them in another.
805 \param oldroot The root node of the document to be replaced
806 \param newroot The root node of the document to replace it with
808 This function first deletes all of the data in the old document. It
809 does this by creating a list of what needs to be deleted, and then
810 goes through the list. This two pass approach removes issues with
811 the list being change while parsing through it. Lots of nasty bugs.
813 Then, it goes through the new document, duplicating all of the
814 elements and putting them into the old document. The copy
815 is then complete.
816 */
817 void
818 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
819 {
820 std::vector<Inkscape::XML::Node *> delete_list;
821 Inkscape::XML::Node * oldroot_namedview = NULL;
823 for (Inkscape::XML::Node * child = oldroot->firstChild();
824 child != NULL;
825 child = child->next()) {
826 if (!strcmp("sodipodi:namedview", child->name())) {
827 oldroot_namedview = child;
828 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
829 oldroot_namedview_child != NULL;
830 oldroot_namedview_child = oldroot_namedview_child->next()) {
831 delete_list.push_back(oldroot_namedview_child);
832 }
833 } else {
834 delete_list.push_back(child);
835 }
836 }
837 for (unsigned int i = 0; i < delete_list.size(); i++)
838 sp_repr_unparent(delete_list[i]);
840 for (Inkscape::XML::Node * child = newroot->firstChild();
841 child != NULL;
842 child = child->next()) {
843 if (!strcmp("sodipodi:namedview", child->name())) {
844 if (oldroot_namedview != NULL) {
845 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
846 newroot_namedview_child != NULL;
847 newroot_namedview_child = newroot_namedview_child->next()) {
848 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
849 }
850 }
851 } else {
852 oldroot->appendChild(child->duplicate(oldroot->document()));
853 }
854 }
856 oldroot->setAttribute("width", newroot->attribute("width"));
857 oldroot->setAttribute("height", newroot->attribute("height"));
859 /** \todo Restore correct layer */
860 /** \todo Restore correct selection */
861 }
863 /** \brief This function checks the stderr file, and if it has data,
864 shows it in a warning dialog to the user
865 \param filename Filename of the stderr file
866 */
867 void
868 Script::checkStderr (const Glib::ustring &data,
869 Gtk::MessageType type,
870 const Glib::ustring &message)
871 {
872 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
873 warning.set_resizable(true);
874 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
875 sp_transientize(dlg);
877 Gtk::VBox * vbox = warning.get_vbox();
879 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
880 Gtk::TextView * textview = new Gtk::TextView();
881 textview->set_editable(false);
882 textview->set_wrap_mode(Gtk::WRAP_WORD);
883 textview->show();
885 textview->get_buffer()->set_text(data.c_str());
887 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
888 scrollwindow->add(*textview);
889 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
890 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
891 scrollwindow->show();
893 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
895 warning.run();
897 return;
898 }
900 bool
901 Script::cancelProcessing (void) {
902 _canceled = true;
903 _main_loop->quit();
904 Glib::spawn_close_pid(_pid);
906 return true;
907 }
910 /** \brief This is the core of the extension file as it actually does
911 the execution of the extension.
912 \param in_command The command to be executed
913 \param filein Filename coming in
914 \param fileout Filename of the out file
915 \return Number of bytes that were read into the output file.
917 The first thing that this function does is build the command to be
918 executed. This consists of the first string (in_command) and then
919 the filename for input (filein). This file is put on the command
920 line.
922 The next thing is that this function does is open a pipe to the
923 command and get the file handle in the ppipe variable. It then
924 opens the output file with the output file handle. Both of these
925 operations are checked extensively for errors.
927 After both are opened, then the data is copied from the output
928 of the pipe into the file out using fread and fwrite. These two
929 functions are used because of their primitive nature they make
930 no assumptions about the data. A buffer is used in the transfer,
931 but the output of fread is stored so the exact number of bytes
932 is handled gracefully.
934 At the very end (after the data has been copied) both of the files
935 are closed, and we return to what we were doing.
936 */
937 int
938 Script::execute (const std::list<std::string> &in_command,
939 const std::list<std::string> &in_params,
940 const Glib::ustring &filein,
941 file_listener &fileout)
942 {
943 g_return_val_if_fail(in_command.size() > 0, 0);
944 // printf("Executing\n");
946 std::vector <std::string> argv;
948 /*
949 for (std::list<std::string>::const_iterator i = in_command.begin();
950 i != in_command.end(); i++) {
951 argv.push_back(*i);
952 }
953 */
954 // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
955 // we tokenize so that spwan does not need to quote over all params
956 for (std::list<std::string>::const_iterator i = in_command.begin();
957 i != in_command.end(); i++) {
958 std::string param_str = *i;
959 do {
960 //g_message("param: %s", param_str.c_str());
961 size_t first_space = param_str.find_first_of(' ');
962 size_t first_quote = param_str.find_first_of('"');
963 //std::cout << "first space " << first_space << std::endl;
964 //std::cout << "first quote " << first_quote << std::endl;
966 if((first_quote != std::string::npos) && (first_quote == 0)) {
967 size_t next_quote = param_str.find_first_of('"', first_quote + 1);
968 //std::cout << "next quote " << next_quote << std::endl;
970 if(next_quote != std::string::npos) {
971 //std::cout << "now split " << next_quote << std::endl;
972 //std::cout << "now split " << param_str.substr(1, next_quote - 1) << std::endl;
973 //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
974 std::string part_str = param_str.substr(1, next_quote - 1);
975 if(part_str.size() > 0)
976 argv.push_back(part_str);
977 param_str = param_str.substr(next_quote + 1);
979 }
980 else {
981 if(param_str.size() > 0)
982 argv.push_back(param_str);
983 param_str = "";
984 }
986 }
987 else if(first_space != std::string::npos) {
988 //std::cout << "now split " << first_space << std::endl;
989 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
990 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
991 std::string part_str = param_str.substr(0, first_space);
992 if(part_str.size() > 0)
993 argv.push_back(part_str);
994 param_str = param_str.substr(first_space + 1);
995 }
996 else {
997 if(param_str.size() > 0)
998 argv.push_back(param_str);
999 param_str = "";
1000 }
1001 } while(param_str.size() > 0);
1002 }
1004 for (std::list<std::string>::const_iterator i = in_params.begin();
1005 i != in_params.end(); i++) {
1006 //g_message("Script parameter: %s",(*i)g.c_str());
1007 argv.push_back(*i);
1008 }
1010 if (!(filein.empty())) {
1011 argv.push_back(filein);
1012 }
1014 int stdout_pipe, stderr_pipe;
1016 try {
1017 Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1018 argv, // arg v
1019 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1020 sigc::slot<void>(),
1021 &_pid, // Pid
1022 NULL, // STDIN
1023 &stdout_pipe, // STDOUT
1024 &stderr_pipe); // STDERR
1025 } catch (Glib::SpawnError e) {
1026 printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1027 return 0;
1028 }
1030 _main_loop = Glib::MainLoop::create(false);
1032 file_listener fileerr;
1033 fileout.init(stdout_pipe, _main_loop);
1034 fileerr.init(stderr_pipe, _main_loop);
1036 _canceled = false;
1037 _main_loop->run();
1039 // Ensure all the data is out of the pipe
1040 while (!fileout.isDead())
1041 fileout.read(Glib::IO_IN);
1042 while (!fileerr.isDead())
1043 fileerr.read(Glib::IO_IN);
1045 if (_canceled) {
1046 // std::cout << "Script Canceled" << std::endl;
1047 return 0;
1048 }
1050 Glib::ustring stderr_data = fileerr.string();
1051 if (stderr_data.length() != 0 &&
1052 Inkscape::NSApplication::Application::getUseGui()
1053 ) {
1054 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1055 _("Inkscape has received additional data from the script executed. "
1056 "The script did not return an error, but this may indicate the results will not be as expected."));
1057 }
1059 Glib::ustring stdout_data = fileout.string();
1060 if (stdout_data.length() == 0) {
1061 return 0;
1062 }
1064 // std::cout << "Finishing Execution." << std::endl;
1065 return stdout_data.length();
1066 }
1071 } // namespace Implementation
1072 } // namespace Extension
1073 } // namespace Inkscape
1075 /*
1076 Local Variables:
1077 mode:c++
1078 c-file-style:"stroustrup"
1079 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1080 indent-tabs-mode:nil
1081 fill-column:99
1082 End:
1083 */
1084 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :