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 "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/input.h"
52 #include "extension/db.h"
53 #include "script.h"
54 #include "dialogs/dialog-events.h"
56 #include "util/glib-list-iterators.h"
60 #ifdef WIN32
61 #include <windows.h>
62 #include <sys/stat.h>
63 #include "registrytool.h"
64 #endif
68 /** This is the command buffer that gets allocated from the stack */
69 #define BUFSIZE (255)
73 /* Namespaces */
74 namespace Inkscape {
75 namespace Extension {
76 namespace Implementation {
80 //Interpreter lookup table
81 struct interpreter_t {
82 gchar * identity;
83 gchar * prefstring;
84 gchar * defaultval;
85 };
88 /** \brief A table of what interpreters to call for a given language
90 This table is used to keep track of all the programs to execute a
91 given script. It also tracks the preference to use to overwrite
92 the given interpreter to a custom one per user.
93 */
94 static interpreter_t interpreterTab[] = {
95 {"perl", "perl-interpreter", "perl" },
96 {"python", "python-interpreter", "python" },
97 {"ruby", "ruby-interpreter", "ruby" },
98 {"shell", "shell-interpreter", "sh" },
99 { NULL, NULL, NULL }
100 };
104 /**
105 * Look up an interpreter name, and translate to something that
106 * is executable
107 */
108 static Glib::ustring
109 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
110 {
112 Glib::ustring interpName = interpNameArg;
114 interpreter_t *interp;
115 bool foundInterp = false;
116 for (interp = interpreterTab ; interp->identity ; interp++ ){
117 if (interpName == interp->identity) {
118 foundInterp = true;
119 break;
120 }
121 }
123 // Do we have a supported interpreter type?
124 if (!foundInterp)
125 return "";
126 interpName = interp->defaultval;
128 // 1. Check preferences
129 gchar *prefInterp = (gchar *)prefs_get_string_attribute(
130 "extensions", interp->prefstring);
132 if (prefInterp) {
133 interpName = prefInterp;
134 return interpName;
135 }
137 #ifdef _WIN32
139 // 2. Windows. Try looking relative to inkscape.exe
140 RegistryTool rt;
141 Glib::ustring fullPath;
142 Glib::ustring path;
143 Glib::ustring exeName;
144 if (rt.getExeInfo(fullPath, path, exeName)) {
145 Glib::ustring interpPath = path;
146 interpPath.append("\\");
147 interpPath.append(interpName);
148 interpPath.append("\\");
149 interpPath.append(interpName);
150 interpPath.append(".exe");
151 struct stat finfo;
152 if (stat(interpPath .c_str(), &finfo) ==0) {
153 g_message("Found local interpreter, '%s', Size: %d",
154 interpPath .c_str(),
155 (int)finfo.st_size);
156 return interpPath;
157 }
158 }
160 // 3. Try searching the path
161 char szExePath[MAX_PATH];
162 char szCurrentDir[MAX_PATH];
163 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
164 unsigned int ret = (unsigned int)FindExecutable(
165 interpName.c_str(), szCurrentDir, szExePath);
166 if (ret > 32) {
167 interpName = szExePath;
168 return interpName;
169 }
171 #endif // win32
174 return interpName;
175 }
179 /** \brief This function creates a script object and sets up the
180 variables.
181 \return A script object
183 This function just sets the command to NULL. It should get built
184 officially in the load function. This allows for less allocation
185 of memory in the unloaded state.
186 */
187 Script::Script() :
188 Implementation()
189 {
190 }
193 /**
194 * brief Destructor
195 */
196 Script::~Script()
197 {
198 }
202 /**
203 \return A string with the complete string with the relative directory expanded
204 \brief This function takes in a Repr that contains a reldir entry
205 and returns that data with the relative directory expanded.
206 Mostly it is here so that relative directories all get used
207 the same way.
208 \param reprin The Inkscape::XML::Node with the reldir in it.
210 Basically this function looks at an attribute of the Repr, and makes
211 a decision based on that. Currently, it is only working with the
212 'extensions' relative directory, but there will be more of them.
213 One thing to notice is that this function always returns an allocated
214 string. This means that the caller of this function can always
215 free what they are given (and should do it too!).
216 */
217 Glib::ustring
218 Script::solve_reldir(Inkscape::XML::Node *reprin) {
220 gchar const *s = reprin->attribute("reldir");
222 if (!s) {
223 Glib::ustring str = sp_repr_children(reprin)->content();
224 return str;
225 }
227 Glib::ustring reldir = s;
229 if (reldir == "extensions") {
231 for (unsigned int i=0;
232 i < Inkscape::Extension::Extension::search_path.size();
233 i++) {
235 gchar * fname = g_build_filename(
236 Inkscape::Extension::Extension::search_path[i],
237 sp_repr_children(reprin)->content(),
238 NULL);
239 Glib::ustring filename = fname;
240 g_free(fname);
242 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
243 return filename;
245 }
246 } else {
247 Glib::ustring str = sp_repr_children(reprin)->content();
248 return str;
249 }
251 return "";
252 }
256 /**
257 \return Whether the command given exists, including in the path
258 \brief This function is used to find out if something exists for
259 the check command. It can look in the path if required.
260 \param command The command or file that should be looked for
262 The first thing that this function does is check to see if the
263 incoming file name has a directory delimiter in it. This would
264 mean that it wants to control the directories, and should be
265 used directly.
267 If not, the path is used. Each entry in the path is stepped through,
268 attached to the string, and then tested. If the file is found
269 then a TRUE is returned. If we get all the way through the path
270 then a FALSE is returned, the command could not be found.
271 */
272 bool
273 Script::check_existance(const Glib::ustring &command)
274 {
276 // Check the simple case first
277 if (command.size() == 0) {
278 return false;
279 }
281 //Don't search when it contains a slash. */
282 if (command.find(G_DIR_SEPARATOR) != command.npos) {
283 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
284 return true;
285 else
286 return false;
287 }
290 Glib::ustring path;
291 gchar *s = (gchar *) g_getenv("PATH");
292 if (s)
293 path = s;
294 else
295 /* There is no `PATH' in the environment.
296 The default search path is the current directory */
297 path = G_SEARCHPATH_SEPARATOR_S;
299 std::string::size_type pos = 0;
300 std::string::size_type pos2 = 0;
301 while ( pos < path.size() ) {
303 Glib::ustring localPath;
305 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
306 if (pos2 == path.npos) {
307 localPath = path.substr(pos);
308 pos = path.size();
309 } else {
310 localPath = path.substr(pos, pos2-pos);
311 pos = pos2+1;
312 }
314 //printf("### %s\n", localPath.c_str());
315 Glib::ustring candidatePath =
316 Glib::build_filename(localPath, command);
318 if (Inkscape::IO::file_test(candidatePath .c_str(),
319 G_FILE_TEST_EXISTS)) {
320 return true;
321 }
323 }
325 return false;
326 }
332 /**
333 \return none
334 \brief This function 'loads' an extention, basically it determines
335 the full command for the extention and stores that.
336 \param module The extention to be loaded.
338 The most difficult part about this function is finding the actual
339 command through all of the Reprs. Basically it is hidden down a
340 couple of layers, and so the code has to move down too. When
341 the command is actually found, it has its relative directory
342 solved.
344 At that point all of the loops are exited, and there is an
345 if statement to make sure they didn't exit because of not finding
346 the command. If that's the case, the extention doesn't get loaded
347 and should error out at a higher level.
348 */
350 bool
351 Script::load(Inkscape::Extension::Extension *module)
352 {
353 if (module->loaded())
354 return TRUE;
356 helper_extension = "";
358 /* This should probably check to find the executable... */
359 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
360 while (child_repr != NULL) {
361 if (!strcmp(child_repr->name(), "script")) {
362 child_repr = sp_repr_children(child_repr);
363 while (child_repr != NULL) {
364 if (!strcmp(child_repr->name(), "command")) {
365 const gchar *interpretstr = child_repr->attribute("interpreter");
366 if (interpretstr != NULL) {
367 Glib::ustring interpString =
368 resolveInterpreterExecutable(interpretstr);
369 command.insert(command.end(), interpretstr);
370 }
372 command.insert(command.end(), solve_reldir(child_repr));
373 }
374 if (!strcmp(child_repr->name(), "helper_extension")) {
375 helper_extension = sp_repr_children(child_repr)->content();
376 }
377 child_repr = sp_repr_next(child_repr);
378 }
380 break;
381 }
382 child_repr = sp_repr_next(child_repr);
383 }
385 //g_return_val_if_fail(command.length() > 0, FALSE);
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.clear();
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(NULL, NULL);
466 }
470 /**
471 \return A dialog for preferences
472 \brief A stub funtion right now
473 \param module Module whose preferences need getting
475 This function should really do something, right now it doesn't.
476 */
477 Gtk::Widget *
478 Script::prefs_output(Inkscape::Extension::Output *module)
479 {
480 return module->autogui(NULL, NULL);
481 }
485 /**
486 \return A dialog for preferences
487 \brief A stub funtion right now
488 \param module Module who's preferences need getting
490 This function should really do something, right now it doesn't.
491 */
492 Gtk::Widget *
493 Script::prefs_effect(Inkscape::Extension::Effect *module,
494 Inkscape::UI::View::View *view,
495 sigc::signal<void> * changeSignal)
496 {
497 SPDocument * current_document = view->doc();
499 using Inkscape::Util::GSListConstIterator;
500 GSListConstIterator<SPItem *> selected =
501 sp_desktop_selection((SPDesktop *)view)->itemList();
502 Inkscape::XML::Node * first_select = NULL;
503 if (selected != NULL) {
504 const SPItem * item = *selected;
505 first_select = SP_OBJECT_REPR(item);
506 }
508 return module->autogui(current_document, first_select, changeSignal);
509 }
514 /**
515 \return A new document that has been opened
516 \brief This function uses a filename that is put in, and calls
517 the extension's command to create an SVG file which is
518 returned.
519 \param module Extension to use.
520 \param filename File to open.
522 First things first, this function needs a temporary file name. To
523 create on of those the function g_file_open_tmp is used with
524 the header of ink_ext_.
526 The extension is then executed using the 'execute' function
527 with the filname coming in, and the temporary filename. After
528 That executing, the SVG should be in the temporary file.
530 Finally, the temporary file is opened using the SVG input module and
531 a document is returned. That document has its filename set to
532 the incoming filename (so that it's not the temporary filename).
533 That document is then returned from this function.
534 */
535 SPDocument *
536 Script::open(Inkscape::Extension::Input *module,
537 const gchar *filenameArg)
538 {
539 std::list<std::string> params;
540 module->paramListString(params);
542 std::string tempfilename_out;
543 int tempfd_out = 0;
544 try {
545 tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
546 } catch (...) {
547 /// \todo Popup dialog here
548 return NULL;
549 }
551 std::string lfilename = Glib::filename_from_utf8(filenameArg);
553 file_listener fileout;
554 int data_read = execute(command, params, lfilename, fileout);
555 fileout.toFile(tempfilename_out);
557 SPDocument * mydoc = NULL;
558 if (data_read > 10) {
559 if (helper_extension.size()==0) {
560 mydoc = Inkscape::Extension::open(
561 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
562 tempfilename_out.c_str());
563 } else {
564 mydoc = Inkscape::Extension::open(
565 Inkscape::Extension::db.get(helper_extension.c_str()),
566 tempfilename_out.c_str());
567 }
568 } // data_read
570 if (mydoc != NULL) {
571 sp_document_set_uri(mydoc, filenameArg);
572 }
574 // make sure we don't leak file descriptors from g_file_open_tmp
575 close(tempfd_out);
577 unlink(tempfilename_out.c_str());
579 return mydoc;
580 } // open
584 /**
585 \return none
586 \brief This function uses an extention to save a document. It first
587 creates an SVG file of the document, and then runs it through
588 the script.
589 \param module Extention to be used
590 \param doc Document to be saved
591 \param filename The name to save the final file as
593 Well, at some point people need to save - it is really what makes
594 the entire application useful. And, it is possible that someone
595 would want to use an extetion for this, so we need a function to
596 do that eh?
598 First things first, the document is saved to a temporary file that
599 is an SVG file. To get the temporary filename g_file_open_tmp is used with
600 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
601 end of the function.
603 After we have the SVG file, then extention_execute is called with
604 the temporary file name and the final output filename. This should
605 put the output of the script into the final output file. We then
606 delete the temporary file.
607 */
608 void
609 Script::save(Inkscape::Extension::Output *module,
610 SPDocument *doc,
611 const gchar *filenameArg)
612 {
613 std::list<std::string> params;
614 module->paramListString(params);
616 std::string tempfilename_in;
617 int tempfd_in = 0;
618 try {
619 tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
620 } catch (...) {
621 /// \todo Popup dialog here
622 return;
623 }
625 if (helper_extension.size() == 0) {
626 Inkscape::Extension::save(
627 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
628 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
629 } else {
630 Inkscape::Extension::save(
631 Inkscape::Extension::db.get(helper_extension.c_str()),
632 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
633 }
636 file_listener fileout;
637 execute(command, params, tempfilename_in, fileout);
639 std::string lfilename = Glib::filename_from_utf8(filenameArg);
640 fileout.toFile(lfilename);
642 // make sure we don't leak file descriptors from g_file_open_tmp
643 close(tempfd_in);
644 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
645 unlink(tempfilename_in.c_str());
647 return;
648 }
652 /**
653 \return none
654 \brief This function uses an extention as a effect on a document.
655 \param module Extention to effect with.
656 \param doc Document to run through the effect.
658 This function is a little bit trickier than the previous two. It
659 needs two temporary files to get it's work done. Both of these
660 files have random names created for them using the g_file_open_temp function
661 with the ink_ext_ prefix in the temporary directory. Like the other
662 functions, the temporary files are deleted at the end.
664 To save/load the two temporary documents (both are SVG) the internal
665 modules for SVG load and save are used. They are both used through
666 the module system function by passing their keys into the functions.
668 The command itself is built a little bit differently than in other
669 functions because the effect support selections. So on the command
670 line a list of all the ids that are selected is included. Currently,
671 this only works for a single selected object, but there will be more.
672 The command string is filled with the data, and then after the execution
673 it is freed.
675 The execute function is used at the core of this function
676 to execute the Script on the two SVG documents (actually only one
677 exists at the time, the other is created by that script). At that
678 point both should be full, and the second one is loaded.
679 */
680 void
681 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
682 {
683 std::list<std::string> params;
684 module->paramListString(params);
686 if (module->no_doc) {
687 // this is a no-doc extension, e.g. a Help menu command;
688 // just run the command without any files, ignoring errors
690 Glib::ustring empty;
691 file_listener outfile;
692 execute(command, params, empty, outfile);
694 return;
695 }
697 std::string tempfilename_in;
698 int tempfd_in = 0;
699 try {
700 tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
701 } catch (...) {
702 /// \todo Popup dialog here
703 return;
704 }
706 std::string tempfilename_out;
707 int tempfd_out = 0;
708 try {
709 tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
710 } catch (...) {
711 /// \todo Popup dialog here
712 return;
713 }
715 SPDesktop *desktop = (SPDesktop *) doc;
716 sp_namedview_document_from_window(desktop);
718 Inkscape::Extension::save(
719 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
720 doc->doc(), tempfilename_in.c_str(), FALSE, FALSE, FALSE);
722 if (desktop != NULL) {
723 Inkscape::Util::GSListConstIterator<SPItem *> selected =
724 sp_desktop_selection(desktop)->itemList();
725 while ( selected != NULL ) {
726 Glib::ustring selected_id;
727 selected_id += "--id=";
728 selected_id += SP_OBJECT_ID(*selected);
729 params.insert(params.begin(), selected_id);
730 ++selected;
731 }
732 }
734 file_listener fileout;
735 int data_read = execute(command, params, tempfilename_in, fileout);
736 fileout.toFile(tempfilename_out);
738 SPDocument * mydoc = NULL;
739 if (data_read > 10) {
740 mydoc = Inkscape::Extension::open(
741 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
742 tempfilename_out.c_str());
743 } // data_read
745 // make sure we don't leak file descriptors from g_file_open_tmp
746 close(tempfd_in);
747 close(tempfd_out);
749 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
750 unlink(tempfilename_in.c_str());
751 unlink(tempfilename_out.c_str());
753 /* Do something with mydoc.... */
754 if (mydoc) {
755 doc->doc()->emitReconstructionStart();
756 copy_doc(doc->doc()->rroot, mydoc->rroot);
757 doc->doc()->emitReconstructionFinish();
758 mydoc->release();
759 sp_namedview_update_layers_from_document(desktop);
760 }
762 return;
763 }
767 /**
768 \brief A function to take all the svg elements from one document
769 and put them in another.
770 \param oldroot The root node of the document to be replaced
771 \param newroot The root node of the document to replace it with
773 This function first deletes all of the data in the old document. It
774 does this by creating a list of what needs to be deleted, and then
775 goes through the list. This two pass approach removes issues with
776 the list being change while parsing through it. Lots of nasty bugs.
778 Then, it goes through the new document, duplicating all of the
779 elements and putting them into the old document. The copy
780 is then complete.
781 */
782 void
783 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
784 {
785 std::vector<Inkscape::XML::Node *> delete_list;
786 for (Inkscape::XML::Node * child = oldroot->firstChild();
787 child != NULL;
788 child = child->next()) {
789 if (!strcmp("sodipodi:namedview", child->name()))
790 continue;
791 delete_list.push_back(child);
792 }
793 for (unsigned int i = 0; i < delete_list.size(); i++)
794 sp_repr_unparent(delete_list[i]);
796 for (Inkscape::XML::Node * child = newroot->firstChild();
797 child != NULL;
798 child = child->next()) {
799 if (!strcmp("sodipodi:namedview", child->name()))
800 continue;
801 oldroot->appendChild(child->duplicate(newroot->document()));
802 }
804 /** \todo Restore correct layer */
805 /** \todo Restore correct selection */
806 }
808 /** \brief This function checks the stderr file, and if it has data,
809 shows it in a warning dialog to the user
810 \param filename Filename of the stderr file
811 */
812 void
813 Script::checkStderr (const Glib::ustring &data,
814 Gtk::MessageType type,
815 const Glib::ustring &message)
816 {
817 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
818 warning.set_resizable(true);
819 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
820 sp_transientize(dlg);
822 Gtk::VBox * vbox = warning.get_vbox();
824 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
825 Gtk::TextView * textview = new Gtk::TextView();
826 textview->set_editable(false);
827 textview->set_wrap_mode(Gtk::WRAP_WORD);
828 textview->show();
830 textview->get_buffer()->set_text(data.c_str());
832 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
833 scrollwindow->add(*textview);
834 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
835 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
836 scrollwindow->show();
838 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
840 warning.run();
842 return;
843 }
845 bool
846 Script::cancelProcessing (void) {
847 _canceled = true;
848 _main_loop->quit();
849 Glib::spawn_close_pid(_pid);
851 return true;
852 }
855 /** \brief This is the core of the extension file as it actually does
856 the execution of the extension.
857 \param in_command The command to be executed
858 \param filein Filename coming in
859 \param fileout Filename of the out file
860 \return Number of bytes that were read into the output file.
862 The first thing that this function does is build the command to be
863 executed. This consists of the first string (in_command) and then
864 the filename for input (filein). This file is put on the command
865 line.
867 The next thing is that this function does is open a pipe to the
868 command and get the file handle in the ppipe variable. It then
869 opens the output file with the output file handle. Both of these
870 operations are checked extensively for errors.
872 After both are opened, then the data is copied from the output
873 of the pipe into the file out using fread and fwrite. These two
874 functions are used because of their primitive nature they make
875 no assumptions about the data. A buffer is used in the transfer,
876 but the output of fread is stored so the exact number of bytes
877 is handled gracefully.
879 At the very end (after the data has been copied) both of the files
880 are closed, and we return to what we were doing.
881 */
882 int
883 Script::execute (const std::list<std::string> &in_command,
884 const std::list<std::string> &in_params,
885 const Glib::ustring &filein,
886 file_listener &fileout)
887 {
888 g_return_val_if_fail(in_command.size() > 0, 0);
889 // printf("Executing\n");
891 std::vector <std::string> argv;
893 for (std::list<std::string>::const_iterator i = in_command.begin();
894 i != in_command.end(); i++) {
895 argv.push_back(*i);
896 }
898 if (!(filein.empty())) {
899 argv.push_back(filein);
900 }
902 for (std::list<std::string>::const_iterator i = in_params.begin();
903 i != in_params.end(); i++) {
904 argv.push_back(*i);
905 }
907 /*
908 for (std::vector<std::string>::const_iterator i = argv.begin();
909 i != argv.end(); i++) {
910 std::cout << *i << std::endl;
911 }
912 */
914 int stdout_pipe, stderr_pipe;
916 try {
917 Glib::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
918 argv, // arg v
919 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
920 sigc::slot<void>(),
921 &_pid, // Pid
922 NULL, // STDIN
923 &stdout_pipe, // STDOUT
924 &stderr_pipe); // STDERR
925 } catch (Glib::SpawnError e) {
926 printf("Can't Spawn!!! %d\n", e.code());
927 return 0;
928 }
930 _main_loop = Glib::MainLoop::create(false);
932 file_listener fileerr;
933 fileout.init(stdout_pipe, _main_loop);
934 fileerr.init(stderr_pipe, _main_loop);
936 _canceled = false;
937 _main_loop->run();
939 // Ensure all the data is out of the pipe
940 while (!fileout.isDead())
941 fileout.read(Glib::IO_IN);
942 while (!fileerr.isDead())
943 fileerr.read(Glib::IO_IN);
945 if (_canceled) {
946 // std::cout << "Script Canceled" << std::endl;
947 return 0;
948 }
950 Glib::ustring stderr_data = fileerr.string();
951 if (stderr_data.length() != 0) {
952 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
953 _("Inkscape has received additional data from the script executed. "
954 "The script did not return an error, but this may indicate the results will not be as expected."));
955 }
957 Glib::ustring stdout_data = fileout.string();
958 if (stdout_data.length() == 0) {
959 return 0;
960 }
962 // std::cout << "Finishing Execution." << std::endl;
963 return stdout_data.length();
964 }
969 } // namespace Implementation
970 } // namespace Extension
971 } // namespace Inkscape
973 /*
974 Local Variables:
975 mode:c++
976 c-file-style:"stroustrup"
977 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
978 indent-tabs-mode:nil
979 fill-column:99
980 End:
981 */
982 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :