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"
57 #include "util/glib-list-iterators.h"
61 #ifdef WIN32
62 #include <windows.h>
63 #include <sys/stat.h>
64 #include "registrytool.h"
65 #endif
69 /** This is the command buffer that gets allocated from the stack */
70 #define BUFSIZE (255)
74 /* Namespaces */
75 namespace Inkscape {
76 namespace Extension {
77 namespace Implementation {
79 void pump_events (void) {
80 while( Gtk::Main::events_pending() )
81 Gtk::Main::iteration();
82 return;
83 }
85 //Interpreter lookup table
86 struct interpreter_t {
87 gchar const *identity;
88 gchar const *prefstring;
89 gchar const *defaultval;
90 };
93 /** \brief A table of what interpreters to call for a given language
95 This table is used to keep track of all the programs to execute a
96 given script. It also tracks the preference to use to overwrite
97 the given interpreter to a custom one per user.
98 */
99 static interpreter_t const interpreterTab[] = {
100 {"perl", "perl-interpreter", "perl" },
101 #ifdef WIN32
102 {"python", "python-interpreter", "pythonw" },
103 #else
104 {"python", "python-interpreter", "python" },
105 #endif
106 {"ruby", "ruby-interpreter", "ruby" },
107 {"shell", "shell-interpreter", "sh" },
108 { NULL, NULL, NULL }
109 };
113 /**
114 * Look up an interpreter name, and translate to something that
115 * is executable
116 */
117 static Glib::ustring
118 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
119 {
121 Glib::ustring interpName = interpNameArg;
123 interpreter_t const *interp;
124 bool foundInterp = false;
125 for (interp = interpreterTab ; interp->identity ; interp++ ){
126 if (interpName == interp->identity) {
127 foundInterp = true;
128 break;
129 }
130 }
132 // Do we have a supported interpreter type?
133 if (!foundInterp)
134 return "";
135 interpName = interp->defaultval;
137 // 1. Check preferences
138 gchar const *prefInterp = prefs_get_string_attribute("extensions", interp->prefstring);
140 if (prefInterp) {
141 interpName = prefInterp;
142 return interpName;
143 }
145 #ifdef WIN32
147 // 2. Windows. Try looking relative to inkscape.exe
148 RegistryTool rt;
149 Glib::ustring fullPath;
150 Glib::ustring path;
151 Glib::ustring exeName;
152 if (rt.getExeInfo(fullPath, path, exeName)) {
153 Glib::ustring interpPath = path;
154 interpPath.append("\\");
155 interpPath.append(interpNameArg);
156 interpPath.append("\\");
157 interpPath.append(interpName);
158 interpPath.append(".exe");
159 struct stat finfo;
160 if (stat(interpPath .c_str(), &finfo) ==0) {
161 g_message("Found local interpreter, '%s', Size: %d",
162 interpPath .c_str(),
163 (int)finfo.st_size);
164 return interpPath;
165 }
166 }
168 // 3. Try searching the path
169 char szExePath[MAX_PATH];
170 char szCurrentDir[MAX_PATH];
171 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
172 unsigned int ret = (unsigned int)FindExecutable(
173 interpName.c_str(), szCurrentDir, szExePath);
174 if (ret > 32) {
175 interpName = szExePath;
176 return interpName;
177 }
179 #endif // win32
182 return interpName;
183 }
187 /** \brief This function creates a script object and sets up the
188 variables.
189 \return A script object
191 This function just sets the command to NULL. It should get built
192 officially in the load function. This allows for less allocation
193 of memory in the unloaded state.
194 */
195 Script::Script() :
196 Implementation()
197 {
198 }
201 /**
202 * brief Destructor
203 */
204 Script::~Script()
205 {
206 }
210 /**
211 \return A string with the complete string with the relative directory expanded
212 \brief This function takes in a Repr that contains a reldir entry
213 and returns that data with the relative directory expanded.
214 Mostly it is here so that relative directories all get used
215 the same way.
216 \param reprin The Inkscape::XML::Node with the reldir in it.
218 Basically this function looks at an attribute of the Repr, and makes
219 a decision based on that. Currently, it is only working with the
220 'extensions' relative directory, but there will be more of them.
221 One thing to notice is that this function always returns an allocated
222 string. This means that the caller of this function can always
223 free what they are given (and should do it too!).
224 */
225 Glib::ustring
226 Script::solve_reldir(Inkscape::XML::Node *reprin) {
228 gchar const *s = reprin->attribute("reldir");
230 if (!s) {
231 Glib::ustring str = sp_repr_children(reprin)->content();
232 return str;
233 }
235 Glib::ustring reldir = s;
237 if (reldir == "extensions") {
239 for (unsigned int i=0;
240 i < Inkscape::Extension::Extension::search_path.size();
241 i++) {
243 gchar * fname = g_build_filename(
244 Inkscape::Extension::Extension::search_path[i],
245 sp_repr_children(reprin)->content(),
246 NULL);
247 Glib::ustring filename = fname;
248 g_free(fname);
250 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
251 return filename;
253 }
254 } else {
255 Glib::ustring str = sp_repr_children(reprin)->content();
256 return str;
257 }
259 return "";
260 }
264 /**
265 \return Whether the command given exists, including in the path
266 \brief This function is used to find out if something exists for
267 the check command. It can look in the path if required.
268 \param command The command or file that should be looked for
270 The first thing that this function does is check to see if the
271 incoming file name has a directory delimiter in it. This would
272 mean that it wants to control the directories, and should be
273 used directly.
275 If not, the path is used. Each entry in the path is stepped through,
276 attached to the string, and then tested. If the file is found
277 then a TRUE is returned. If we get all the way through the path
278 then a FALSE is returned, the command could not be found.
279 */
280 bool
281 Script::check_existance(const Glib::ustring &command)
282 {
284 // Check the simple case first
285 if (command.size() == 0) {
286 return false;
287 }
289 //Don't search when it contains a slash. */
290 if (command.find(G_DIR_SEPARATOR) != command.npos) {
291 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
292 return true;
293 else
294 return false;
295 }
298 Glib::ustring path;
299 gchar *s = (gchar *) g_getenv("PATH");
300 if (s)
301 path = s;
302 else
303 /* There is no `PATH' in the environment.
304 The default search path is the current directory */
305 path = G_SEARCHPATH_SEPARATOR_S;
307 std::string::size_type pos = 0;
308 std::string::size_type pos2 = 0;
309 while ( pos < path.size() ) {
311 Glib::ustring localPath;
313 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
314 if (pos2 == path.npos) {
315 localPath = path.substr(pos);
316 pos = path.size();
317 } else {
318 localPath = path.substr(pos, pos2-pos);
319 pos = pos2+1;
320 }
322 //printf("### %s\n", localPath.c_str());
323 Glib::ustring candidatePath =
324 Glib::build_filename(localPath, command);
326 if (Inkscape::IO::file_test(candidatePath .c_str(),
327 G_FILE_TEST_EXISTS)) {
328 return true;
329 }
331 }
333 return false;
334 }
340 /**
341 \return none
342 \brief This function 'loads' an extention, basically it determines
343 the full command for the extention and stores that.
344 \param module The extention to be loaded.
346 The most difficult part about this function is finding the actual
347 command through all of the Reprs. Basically it is hidden down a
348 couple of layers, and so the code has to move down too. When
349 the command is actually found, it has its relative directory
350 solved.
352 At that point all of the loops are exited, and there is an
353 if statement to make sure they didn't exit because of not finding
354 the command. If that's the case, the extention doesn't get loaded
355 and should error out at a higher level.
356 */
358 bool
359 Script::load(Inkscape::Extension::Extension *module)
360 {
361 if (module->loaded())
362 return true;
364 helper_extension = "";
366 /* This should probably check to find the executable... */
367 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
368 while (child_repr != NULL) {
369 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
370 child_repr = sp_repr_children(child_repr);
371 while (child_repr != NULL) {
372 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
373 const gchar *interpretstr = child_repr->attribute("interpreter");
374 if (interpretstr != NULL) {
375 Glib::ustring interpString =
376 resolveInterpreterExecutable(interpretstr);
377 //g_message("Found: %s and %s",interpString.c_str(),interpretstr);
378 command.insert(command.end(), interpretstr);
379 }
380 Glib::ustring tmp = "\"";
381 tmp += solve_reldir(child_repr);
382 tmp += "\"";
384 command.insert(command.end(), tmp);
385 }
386 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
387 helper_extension = sp_repr_children(child_repr)->content();
388 }
389 child_repr = sp_repr_next(child_repr);
390 }
392 break;
393 }
394 child_repr = sp_repr_next(child_repr);
395 }
397 //g_return_val_if_fail(command.length() > 0, false);
399 return true;
400 }
403 /**
404 \return None.
405 \brief Unload this puppy!
406 \param module Extension to be unloaded.
408 This function just sets the module to unloaded. It free's the
409 command if it has been allocated.
410 */
411 void
412 Script::unload(Inkscape::Extension::Extension */*module*/)
413 {
414 command.clear();
415 helper_extension = "";
416 }
421 /**
422 \return Whether the check passed or not
423 \brief Check every dependency that was given to make sure we should keep this extension
424 \param module The Extension in question
426 */
427 bool
428 Script::check(Inkscape::Extension::Extension *module)
429 {
430 int script_count = 0;
431 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
432 while (child_repr != NULL) {
433 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
434 script_count++;
435 child_repr = sp_repr_children(child_repr);
436 while (child_repr != NULL) {
437 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
438 Glib::ustring command_text = solve_reldir(child_repr);
439 if (command_text.size() > 0) {
440 /* I've got the command */
441 bool existance = check_existance(command_text);
442 if (!existance)
443 return false;
444 }
445 }
447 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
448 gchar const *helper = sp_repr_children(child_repr)->content();
449 if (Inkscape::Extension::db.get(helper) == NULL) {
450 return false;
451 }
452 }
454 child_repr = sp_repr_next(child_repr);
455 }
457 break;
458 }
459 child_repr = sp_repr_next(child_repr);
460 }
462 if (script_count == 0) {
463 return false;
464 }
466 return true;
467 }
469 class ScriptDocCache : public ImplementationDocumentCache {
470 friend class Script;
471 protected:
472 std::string _filename;
473 int _tempfd;
474 public:
475 ScriptDocCache (Inkscape::UI::View::View * view);
476 ~ScriptDocCache ( );
477 };
479 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
480 ImplementationDocumentCache(view),
481 _filename(""),
482 _tempfd(0)
483 {
484 try {
485 _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
486 } catch (...) {
487 /// \todo Popup dialog here
488 return;
489 }
491 SPDesktop *desktop = (SPDesktop *) view;
492 sp_namedview_document_from_window(desktop);
494 Inkscape::Extension::save(
495 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
496 view->doc(), _filename.c_str(), false, false, false);
498 return;
499 }
501 ScriptDocCache::~ScriptDocCache ( )
502 {
503 close(_tempfd);
504 unlink(_filename.c_str());
505 }
507 ImplementationDocumentCache *
508 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
509 return new ScriptDocCache(view);
510 }
513 /**
514 \return A dialog for preferences
515 \brief A stub funtion right now
516 \param module Module who's preferences need getting
517 \param filename Hey, the file you're getting might be important
519 This function should really do something, right now it doesn't.
520 */
521 Gtk::Widget *
522 Script::prefs_input(Inkscape::Extension::Input *module,
523 const gchar */*filename*/)
524 {
525 return module->autogui(NULL, NULL);
526 }
530 /**
531 \return A dialog for preferences
532 \brief A stub funtion right now
533 \param module Module whose preferences need getting
535 This function should really do something, right now it doesn't.
536 */
537 Gtk::Widget *
538 Script::prefs_output(Inkscape::Extension::Output *module)
539 {
540 return module->autogui(NULL, NULL);
541 }
543 /**
544 \return A new document that has been opened
545 \brief This function uses a filename that is put in, and calls
546 the extension's command to create an SVG file which is
547 returned.
548 \param module Extension to use.
549 \param filename File to open.
551 First things first, this function needs a temporary file name. To
552 create on of those the function g_file_open_tmp is used with
553 the header of ink_ext_.
555 The extension is then executed using the 'execute' function
556 with the filname coming in, and the temporary filename. After
557 That executing, the SVG should be in the temporary file.
559 Finally, the temporary file is opened using the SVG input module and
560 a document is returned. That document has its filename set to
561 the incoming filename (so that it's not the temporary filename).
562 That document is then returned from this function.
563 */
564 SPDocument *
565 Script::open(Inkscape::Extension::Input *module,
566 const gchar *filenameArg)
567 {
568 std::list<std::string> params;
569 module->paramListString(params);
571 std::string tempfilename_out;
572 int tempfd_out = 0;
573 try {
574 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
575 } catch (...) {
576 /// \todo Popup dialog here
577 return NULL;
578 }
580 std::string lfilename = Glib::filename_from_utf8(filenameArg);
582 file_listener fileout;
583 int data_read = execute(command, params, lfilename, fileout);
584 fileout.toFile(tempfilename_out);
586 SPDocument * mydoc = NULL;
587 if (data_read > 10) {
588 if (helper_extension.size()==0) {
589 mydoc = Inkscape::Extension::open(
590 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
591 tempfilename_out.c_str());
592 } else {
593 mydoc = Inkscape::Extension::open(
594 Inkscape::Extension::db.get(helper_extension.c_str()),
595 tempfilename_out.c_str());
596 }
597 } // data_read
599 if (mydoc != NULL) {
600 sp_document_set_uri(mydoc, filenameArg);
601 }
603 // make sure we don't leak file descriptors from g_file_open_tmp
604 close(tempfd_out);
606 unlink(tempfilename_out.c_str());
608 return mydoc;
609 } // open
613 /**
614 \return none
615 \brief This function uses an extention to save a document. It first
616 creates an SVG file of the document, and then runs it through
617 the script.
618 \param module Extention to be used
619 \param doc Document to be saved
620 \param filename The name to save the final file as
622 Well, at some point people need to save - it is really what makes
623 the entire application useful. And, it is possible that someone
624 would want to use an extetion for this, so we need a function to
625 do that eh?
627 First things first, the document is saved to a temporary file that
628 is an SVG file. To get the temporary filename g_file_open_tmp is used with
629 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
630 end of the function.
632 After we have the SVG file, then extention_execute is called with
633 the temporary file name and the final output filename. This should
634 put the output of the script into the final output file. We then
635 delete the temporary file.
636 */
637 void
638 Script::save(Inkscape::Extension::Output *module,
639 SPDocument *doc,
640 const gchar *filenameArg)
641 {
642 std::list<std::string> params;
643 module->paramListString(params);
645 std::string tempfilename_in;
646 int tempfd_in = 0;
647 try {
648 tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
649 } catch (...) {
650 /// \todo Popup dialog here
651 return;
652 }
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 }
665 file_listener fileout;
666 execute(command, params, tempfilename_in, fileout);
668 std::string lfilename = Glib::filename_from_utf8(filenameArg);
669 fileout.toFile(lfilename);
671 // make sure we don't leak file descriptors from g_file_open_tmp
672 close(tempfd_in);
673 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
674 unlink(tempfilename_in.c_str());
676 return;
677 }
681 /**
682 \return none
683 \brief This function uses an extention as a effect on a document.
684 \param module Extention to effect with.
685 \param doc Document to run through the effect.
687 This function is a little bit trickier than the previous two. It
688 needs two temporary files to get it's work done. Both of these
689 files have random names created for them using the g_file_open_temp function
690 with the ink_ext_ prefix in the temporary directory. Like the other
691 functions, the temporary files are deleted at the end.
693 To save/load the two temporary documents (both are SVG) the internal
694 modules for SVG load and save are used. They are both used through
695 the module system function by passing their keys into the functions.
697 The command itself is built a little bit differently than in other
698 functions because the effect support selections. So on the command
699 line a list of all the ids that are selected is included. Currently,
700 this only works for a single selected object, but there will be more.
701 The command string is filled with the data, and then after the execution
702 it is freed.
704 The execute function is used at the core of this function
705 to execute the Script on the two SVG documents (actually only one
706 exists at the time, the other is created by that script). At that
707 point both should be full, and the second one is loaded.
708 */
709 void
710 Script::effect(Inkscape::Extension::Effect *module,
711 Inkscape::UI::View::View *doc,
712 ImplementationDocumentCache * docCache)
713 {
714 if (docCache == NULL) {
715 docCache = newDocCache(module, doc);
716 }
717 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
718 if (dc == NULL) {
719 printf("TOO BAD TO LIVE!!!");
720 exit(1);
721 }
723 SPDesktop *desktop = (SPDesktop *)doc;
724 sp_namedview_document_from_window(desktop);
726 gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
728 std::list<std::string> params;
729 module->paramListString(params);
731 if (module->no_doc) {
732 // this is a no-doc extension, e.g. a Help menu command;
733 // just run the command without any files, ignoring errors
735 Glib::ustring empty;
736 file_listener outfile;
737 execute(command, params, empty, outfile);
739 return;
740 }
742 std::string tempfilename_out;
743 int tempfd_out = 0;
744 try {
745 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
746 } catch (...) {
747 /// \todo Popup dialog here
748 return;
749 }
751 if (desktop != NULL) {
752 Inkscape::Util::GSListConstIterator<SPItem *> selected =
753 sp_desktop_selection(desktop)->itemList();
754 while ( selected != NULL ) {
755 Glib::ustring selected_id;
756 selected_id += "--id=";
757 selected_id += SP_OBJECT_ID(*selected);
758 params.insert(params.begin(), selected_id);
759 ++selected;
760 }
761 }
763 file_listener fileout;
764 int data_read = execute(command, params, dc->_filename, fileout);
765 fileout.toFile(tempfilename_out);
767 pump_events();
769 SPDocument * mydoc = NULL;
770 if (data_read > 10) {
771 mydoc = Inkscape::Extension::open(
772 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
773 tempfilename_out.c_str());
774 } // data_read
776 pump_events();
778 // make sure we don't leak file descriptors from g_file_open_tmp
779 close(tempfd_out);
781 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
782 unlink(tempfilename_out.c_str());
784 /* Do something with mydoc.... */
785 if (mydoc) {
786 doc->doc()->emitReconstructionStart();
787 copy_doc(doc->doc()->rroot, mydoc->rroot);
788 doc->doc()->emitReconstructionFinish();
789 mydoc->release();
790 sp_namedview_update_layers_from_document(desktop);
792 sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
793 }
794 g_free(orig_output_extension);
796 return;
797 }
801 /**
802 \brief A function to take all the svg elements from one document
803 and put them in another.
804 \param oldroot The root node of the document to be replaced
805 \param newroot The root node of the document to replace it with
807 This function first deletes all of the data in the old document. It
808 does this by creating a list of what needs to be deleted, and then
809 goes through the list. This two pass approach removes issues with
810 the list being change while parsing through it. Lots of nasty bugs.
812 Then, it goes through the new document, duplicating all of the
813 elements and putting them into the old document. The copy
814 is then complete.
815 */
816 void
817 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
818 {
819 std::vector<Inkscape::XML::Node *> delete_list;
820 Inkscape::XML::Node * oldroot_namedview = NULL;
822 for (Inkscape::XML::Node * child = oldroot->firstChild();
823 child != NULL;
824 child = child->next()) {
825 if (!strcmp("sodipodi:namedview", child->name())) {
826 oldroot_namedview = child;
827 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
828 oldroot_namedview_child != NULL;
829 oldroot_namedview_child = oldroot_namedview_child->next()) {
830 delete_list.push_back(oldroot_namedview_child);
831 }
832 } else {
833 delete_list.push_back(child);
834 }
835 }
836 for (unsigned int i = 0; i < delete_list.size(); i++)
837 sp_repr_unparent(delete_list[i]);
839 for (Inkscape::XML::Node * child = newroot->firstChild();
840 child != NULL;
841 child = child->next()) {
842 if (!strcmp("sodipodi:namedview", child->name())) {
843 if (oldroot_namedview != NULL) {
844 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
845 newroot_namedview_child != NULL;
846 newroot_namedview_child = newroot_namedview_child->next()) {
847 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(child->document()));
848 }
849 }
850 } else {
851 oldroot->appendChild(child->duplicate(newroot->document()));
852 }
853 }
855 oldroot->setAttribute("width", newroot->attribute("width"));
856 oldroot->setAttribute("height", newroot->attribute("height"));
858 /** \todo Restore correct layer */
859 /** \todo Restore correct selection */
860 }
862 /** \brief This function checks the stderr file, and if it has data,
863 shows it in a warning dialog to the user
864 \param filename Filename of the stderr file
865 */
866 void
867 Script::checkStderr (const Glib::ustring &data,
868 Gtk::MessageType type,
869 const Glib::ustring &message)
870 {
871 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
872 warning.set_resizable(true);
873 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
874 sp_transientize(dlg);
876 Gtk::VBox * vbox = warning.get_vbox();
878 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
879 Gtk::TextView * textview = new Gtk::TextView();
880 textview->set_editable(false);
881 textview->set_wrap_mode(Gtk::WRAP_WORD);
882 textview->show();
884 textview->get_buffer()->set_text(data.c_str());
886 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
887 scrollwindow->add(*textview);
888 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
889 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
890 scrollwindow->show();
892 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
894 warning.run();
896 return;
897 }
899 bool
900 Script::cancelProcessing (void) {
901 _canceled = true;
902 _main_loop->quit();
903 Glib::spawn_close_pid(_pid);
905 return true;
906 }
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 std::list<std::string> &in_command,
938 const std::list<std::string> &in_params,
939 const Glib::ustring &filein,
940 file_listener &fileout)
941 {
942 g_return_val_if_fail(in_command.size() > 0, 0);
943 // printf("Executing\n");
945 std::vector <std::string> argv;
947 /*
948 for (std::list<std::string>::const_iterator i = in_command.begin();
949 i != in_command.end(); i++) {
950 argv.push_back(*i);
951 }
952 */
953 // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
954 // we tokenize so that spwan does not need to quote over all params
955 for (std::list<std::string>::const_iterator i = in_command.begin();
956 i != in_command.end(); i++) {
957 std::string param_str = *i;
958 do {
959 //g_message("param: %s", param_str.c_str());
960 size_t first_space = param_str.find_first_of(' ');
961 size_t first_quote = param_str.find_first_of('"');
962 //std::cout << "first space " << first_space << std::endl;
963 //std::cout << "first quote " << first_quote << std::endl;
965 if((first_quote != std::string::npos) && (first_quote == 0)) {
966 size_t next_quote = param_str.find_first_of('"', first_quote + 1);
967 //std::cout << "next quote " << next_quote << std::endl;
969 if(next_quote != std::string::npos) {
970 //std::cout << "now split " << next_quote << std::endl;
971 //std::cout << "now split " << param_str.substr(1, next_quote - 1) << std::endl;
972 //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
973 std::string part_str = param_str.substr(1, next_quote - 1);
974 if(part_str.size() > 0)
975 argv.push_back(part_str);
976 param_str = param_str.substr(next_quote + 1);
978 }
979 else {
980 if(param_str.size() > 0)
981 argv.push_back(param_str);
982 param_str = "";
983 }
985 }
986 else if(first_space != std::string::npos) {
987 //std::cout << "now split " << first_space << std::endl;
988 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
989 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
990 std::string part_str = param_str.substr(0, first_space);
991 if(part_str.size() > 0)
992 argv.push_back(part_str);
993 param_str = param_str.substr(first_space + 1);
994 }
995 else {
996 if(param_str.size() > 0)
997 argv.push_back(param_str);
998 param_str = "";
999 }
1000 } while(param_str.size() > 0);
1001 }
1003 for (std::list<std::string>::const_iterator i = in_params.begin();
1004 i != in_params.end(); i++) {
1005 //g_message("Script parameter: %s",(*i)g.c_str());
1006 argv.push_back(*i);
1007 }
1009 if (!(filein.empty())) {
1010 argv.push_back(filein);
1011 }
1013 int stdout_pipe, stderr_pipe;
1015 try {
1016 Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1017 argv, // arg v
1018 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1019 sigc::slot<void>(),
1020 &_pid, // Pid
1021 NULL, // STDIN
1022 &stdout_pipe, // STDOUT
1023 &stderr_pipe); // STDERR
1024 } catch (Glib::SpawnError e) {
1025 printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1026 return 0;
1027 }
1029 _main_loop = Glib::MainLoop::create(false);
1031 file_listener fileerr;
1032 fileout.init(stdout_pipe, _main_loop);
1033 fileerr.init(stderr_pipe, _main_loop);
1035 _canceled = false;
1036 _main_loop->run();
1038 // Ensure all the data is out of the pipe
1039 while (!fileout.isDead())
1040 fileout.read(Glib::IO_IN);
1041 while (!fileerr.isDead())
1042 fileerr.read(Glib::IO_IN);
1044 if (_canceled) {
1045 // std::cout << "Script Canceled" << std::endl;
1046 return 0;
1047 }
1049 Glib::ustring stderr_data = fileerr.string();
1050 if (stderr_data.length() != 0) {
1051 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1052 _("Inkscape has received additional data from the script executed. "
1053 "The script did not return an error, but this may indicate the results will not be as expected."));
1054 }
1056 Glib::ustring stdout_data = fileout.string();
1057 if (stdout_data.length() == 0) {
1058 return 0;
1059 }
1061 // std::cout << "Finishing Execution." << std::endl;
1062 return stdout_data.length();
1063 }
1068 } // namespace Implementation
1069 } // namespace Extension
1070 } // namespace Inkscape
1072 /*
1073 Local Variables:
1074 mode:c++
1075 c-file-style:"stroustrup"
1076 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1077 indent-tabs-mode:nil
1078 fill-column:99
1079 End:
1080 */
1081 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :