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 }
178 class file_listener {
179 Glib::ustring _string;
180 sigc::connection _conn;
181 Glib::RefPtr<Glib::IOChannel> _channel;
182 Glib::RefPtr<Glib::MainLoop> _main_loop;
184 public:
185 file_listener () { };
186 ~file_listener () {
187 _conn.disconnect();
188 };
190 void init (int fd, Glib::RefPtr<Glib::MainLoop> main) {
191 _channel = Glib::IOChannel::create_from_fd(fd);
192 _channel->set_encoding();
193 _conn = Glib::signal_io().connect(sigc::mem_fun(*this, &file_listener::read), _channel, Glib::IO_IN | Glib::IO_HUP | Glib::IO_ERR);
194 _main_loop = main;
196 return;
197 };
199 bool read (Glib::IOCondition condition) {
200 if (condition != Glib::IO_IN) {
201 _main_loop->quit();
202 return false;
203 }
205 Glib::IOStatus status;
206 Glib::ustring out;
207 status = _channel->read_to_end(out);
209 if (status != Glib::IO_STATUS_NORMAL) {
210 _main_loop->quit();
211 return false;
212 }
214 _string += out;
215 return true;
216 };
218 // Note, doing a copy here, on purpose
219 Glib::ustring string (void) { return _string; };
221 void toFile (const Glib::ustring &name) {
222 Glib::RefPtr<Glib::IOChannel> stdout_file = Glib::IOChannel::create_from_file(name, "w");
223 stdout_file->write(_string);
224 return;
225 };
226 };
228 int execute (const std::list<std::string> &in_command,
229 const std::list<std::string> &in_params,
230 const Glib::ustring &filein,
231 file_listener &fileout);
232 void checkStderr (const Glib::ustring &data,
233 Gtk::MessageType type,
234 const Glib::ustring &message);
237 /** \brief This function creates a script object and sets up the
238 variables.
239 \return A script object
241 This function just sets the command to NULL. It should get built
242 officially in the load function. This allows for less allocation
243 of memory in the unloaded state.
244 */
245 Script::Script() :
246 Implementation()
247 {
248 }
251 /**
252 * brief Destructor
253 */
254 Script::~Script()
255 {
256 }
260 /**
261 \return A string with the complete string with the relative directory expanded
262 \brief This function takes in a Repr that contains a reldir entry
263 and returns that data with the relative directory expanded.
264 Mostly it is here so that relative directories all get used
265 the same way.
266 \param reprin The Inkscape::XML::Node with the reldir in it.
268 Basically this function looks at an attribute of the Repr, and makes
269 a decision based on that. Currently, it is only working with the
270 'extensions' relative directory, but there will be more of them.
271 One thing to notice is that this function always returns an allocated
272 string. This means that the caller of this function can always
273 free what they are given (and should do it too!).
274 */
275 Glib::ustring
276 Script::solve_reldir(Inkscape::XML::Node *reprin) {
278 gchar const *s = reprin->attribute("reldir");
280 if (!s) {
281 Glib::ustring str = sp_repr_children(reprin)->content();
282 return str;
283 }
285 Glib::ustring reldir = s;
287 if (reldir == "extensions") {
289 for (unsigned int i=0;
290 i < Inkscape::Extension::Extension::search_path.size();
291 i++) {
293 gchar * fname = g_build_filename(
294 Inkscape::Extension::Extension::search_path[i],
295 sp_repr_children(reprin)->content(),
296 NULL);
297 Glib::ustring filename = fname;
298 g_free(fname);
300 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
301 return filename;
303 }
304 } else {
305 Glib::ustring str = sp_repr_children(reprin)->content();
306 return str;
307 }
309 return "";
310 }
314 /**
315 \return Whether the command given exists, including in the path
316 \brief This function is used to find out if something exists for
317 the check command. It can look in the path if required.
318 \param command The command or file that should be looked for
320 The first thing that this function does is check to see if the
321 incoming file name has a directory delimiter in it. This would
322 mean that it wants to control the directories, and should be
323 used directly.
325 If not, the path is used. Each entry in the path is stepped through,
326 attached to the string, and then tested. If the file is found
327 then a TRUE is returned. If we get all the way through the path
328 then a FALSE is returned, the command could not be found.
329 */
330 bool
331 Script::check_existance(const Glib::ustring &command)
332 {
334 // Check the simple case first
335 if (command.size() == 0) {
336 return false;
337 }
339 //Don't search when it contains a slash. */
340 if (command.find(G_DIR_SEPARATOR) != command.npos) {
341 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
342 return true;
343 else
344 return false;
345 }
348 Glib::ustring path;
349 gchar *s = (gchar *) g_getenv("PATH");
350 if (s)
351 path = s;
352 else
353 /* There is no `PATH' in the environment.
354 The default search path is the current directory */
355 path = G_SEARCHPATH_SEPARATOR_S;
357 std::string::size_type pos = 0;
358 std::string::size_type pos2 = 0;
359 while ( pos < path.size() ) {
361 Glib::ustring localPath;
363 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
364 if (pos2 == path.npos) {
365 localPath = path.substr(pos);
366 pos = path.size();
367 } else {
368 localPath = path.substr(pos, pos2-pos);
369 pos = pos2+1;
370 }
372 //printf("### %s\n", localPath.c_str());
373 Glib::ustring candidatePath =
374 Glib::build_filename(localPath, command);
376 if (Inkscape::IO::file_test(candidatePath .c_str(),
377 G_FILE_TEST_EXISTS)) {
378 return true;
379 }
381 }
383 return false;
384 }
390 /**
391 \return none
392 \brief This function 'loads' an extention, basically it determines
393 the full command for the extention and stores that.
394 \param module The extention to be loaded.
396 The most difficult part about this function is finding the actual
397 command through all of the Reprs. Basically it is hidden down a
398 couple of layers, and so the code has to move down too. When
399 the command is actually found, it has its relative directory
400 solved.
402 At that point all of the loops are exited, and there is an
403 if statement to make sure they didn't exit because of not finding
404 the command. If that's the case, the extention doesn't get loaded
405 and should error out at a higher level.
406 */
408 bool
409 Script::load(Inkscape::Extension::Extension *module)
410 {
411 if (module->loaded())
412 return TRUE;
414 helper_extension = "";
416 /* This should probably check to find the executable... */
417 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
418 while (child_repr != NULL) {
419 if (!strcmp(child_repr->name(), "script")) {
420 child_repr = sp_repr_children(child_repr);
421 while (child_repr != NULL) {
422 if (!strcmp(child_repr->name(), "command")) {
423 const gchar *interpretstr = child_repr->attribute("interpreter");
424 if (interpretstr != NULL) {
425 Glib::ustring interpString =
426 resolveInterpreterExecutable(interpretstr);
427 command.insert(command.end(), interpretstr);
428 }
430 command.insert(command.end(), solve_reldir(child_repr));
431 }
432 if (!strcmp(child_repr->name(), "helper_extension")) {
433 helper_extension = sp_repr_children(child_repr)->content();
434 }
435 child_repr = sp_repr_next(child_repr);
436 }
438 break;
439 }
440 child_repr = sp_repr_next(child_repr);
441 }
443 //g_return_val_if_fail(command.length() > 0, FALSE);
445 return true;
446 }
449 /**
450 \return None.
451 \brief Unload this puppy!
452 \param module Extension to be unloaded.
454 This function just sets the module to unloaded. It free's the
455 command if it has been allocated.
456 */
457 void
458 Script::unload(Inkscape::Extension::Extension *module)
459 {
460 command.clear();
461 helper_extension = "";
462 }
467 /**
468 \return Whether the check passed or not
469 \brief Check every dependency that was given to make sure we should keep this extension
470 \param module The Extension in question
472 */
473 bool
474 Script::check(Inkscape::Extension::Extension *module)
475 {
476 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
477 while (child_repr != NULL) {
478 if (!strcmp(child_repr->name(), "script")) {
479 child_repr = sp_repr_children(child_repr);
480 while (child_repr != NULL) {
481 if (!strcmp(child_repr->name(), "check")) {
482 Glib::ustring command_text = solve_reldir(child_repr);
483 if (command_text.size() > 0) {
484 /* I've got the command */
485 bool existance = check_existance(command_text);
486 if (!existance)
487 return FALSE;
488 }
489 }
491 if (!strcmp(child_repr->name(), "helper_extension")) {
492 gchar const *helper = sp_repr_children(child_repr)->content();
493 if (Inkscape::Extension::db.get(helper) == NULL) {
494 return FALSE;
495 }
496 }
498 child_repr = sp_repr_next(child_repr);
499 }
501 break;
502 }
503 child_repr = sp_repr_next(child_repr);
504 }
506 return true;
507 }
511 /**
512 \return A dialog for preferences
513 \brief A stub funtion right now
514 \param module Module who's preferences need getting
515 \param filename Hey, the file you're getting might be important
517 This function should really do something, right now it doesn't.
518 */
519 Gtk::Widget *
520 Script::prefs_input(Inkscape::Extension::Input *module,
521 const gchar *filename)
522 {
523 return module->autogui(NULL, NULL);
524 }
528 /**
529 \return A dialog for preferences
530 \brief A stub funtion right now
531 \param module Module whose preferences need getting
533 This function should really do something, right now it doesn't.
534 */
535 Gtk::Widget *
536 Script::prefs_output(Inkscape::Extension::Output *module)
537 {
538 return module->autogui(NULL, NULL);
539 }
543 /**
544 \return A dialog for preferences
545 \brief A stub funtion right now
546 \param module Module who's preferences need getting
548 This function should really do something, right now it doesn't.
549 */
550 Gtk::Widget *
551 Script::prefs_effect(Inkscape::Extension::Effect *module,
552 Inkscape::UI::View::View *view,
553 sigc::signal<void> * changeSignal)
554 {
555 SPDocument * current_document = view->doc();
557 using Inkscape::Util::GSListConstIterator;
558 GSListConstIterator<SPItem *> selected =
559 sp_desktop_selection((SPDesktop *)view)->itemList();
560 Inkscape::XML::Node * first_select = NULL;
561 if (selected != NULL) {
562 const SPItem * item = *selected;
563 first_select = SP_OBJECT_REPR(item);
564 }
566 return module->autogui(current_document, first_select, changeSignal);
567 }
572 /**
573 \return A new document that has been opened
574 \brief This function uses a filename that is put in, and calls
575 the extension's command to create an SVG file which is
576 returned.
577 \param module Extension to use.
578 \param filename File to open.
580 First things first, this function needs a temporary file name. To
581 create on of those the function g_file_open_tmp is used with
582 the header of ink_ext_.
584 The extension is then executed using the 'execute' function
585 with the filname coming in, and the temporary filename. After
586 That executing, the SVG should be in the temporary file.
588 Finally, the temporary file is opened using the SVG input module and
589 a document is returned. That document has its filename set to
590 the incoming filename (so that it's not the temporary filename).
591 That document is then returned from this function.
592 */
593 SPDocument *
594 Script::open(Inkscape::Extension::Input *module,
595 const gchar *filenameArg)
596 {
597 std::list<std::string> params;
598 module->paramListString(params);
600 std::string tempfilename_out;
601 int tempfd_out = 0;
602 try {
603 tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
604 } catch (...) {
605 /// \todo Popup dialog here
606 return NULL;
607 }
609 std::string lfilename = Glib::filename_from_utf8(filenameArg);
611 file_listener fileout;
612 int data_read = execute(command, params, lfilename, fileout);
613 fileout.toFile(tempfilename_out);
615 SPDocument * mydoc = NULL;
616 if (data_read > 10) {
617 if (helper_extension.size()==0) {
618 mydoc = Inkscape::Extension::open(
619 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
620 tempfilename_out.c_str());
621 } else {
622 mydoc = Inkscape::Extension::open(
623 Inkscape::Extension::db.get(helper_extension.c_str()),
624 tempfilename_out.c_str());
625 }
626 } // data_read
628 if (mydoc != NULL) {
629 sp_document_set_uri(mydoc, filenameArg);
630 }
632 // make sure we don't leak file descriptors from g_file_open_tmp
633 close(tempfd_out);
635 unlink(tempfilename_out.c_str());
637 return mydoc;
638 } // open
642 /**
643 \return none
644 \brief This function uses an extention to save a document. It first
645 creates an SVG file of the document, and then runs it through
646 the script.
647 \param module Extention to be used
648 \param doc Document to be saved
649 \param filename The name to save the final file as
651 Well, at some point people need to save - it is really what makes
652 the entire application useful. And, it is possible that someone
653 would want to use an extetion for this, so we need a function to
654 do that eh?
656 First things first, the document is saved to a temporary file that
657 is an SVG file. To get the temporary filename g_file_open_tmp is used with
658 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
659 end of the function.
661 After we have the SVG file, then extention_execute is called with
662 the temporary file name and the final output filename. This should
663 put the output of the script into the final output file. We then
664 delete the temporary file.
665 */
666 void
667 Script::save(Inkscape::Extension::Output *module,
668 SPDocument *doc,
669 const gchar *filenameArg)
670 {
671 std::list<std::string> params;
672 module->paramListString(params);
674 std::string tempfilename_in;
675 int tempfd_in = 0;
676 try {
677 tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
678 } catch (...) {
679 /// \todo Popup dialog here
680 return;
681 }
683 if (helper_extension.size() == 0) {
684 Inkscape::Extension::save(
685 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
686 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
687 } else {
688 Inkscape::Extension::save(
689 Inkscape::Extension::db.get(helper_extension.c_str()),
690 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
691 }
694 file_listener fileout;
695 execute(command, params, tempfilename_in, fileout);
697 std::string lfilename = Glib::filename_from_utf8(filenameArg);
698 fileout.toFile(lfilename);
700 // make sure we don't leak file descriptors from g_file_open_tmp
701 close(tempfd_in);
702 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
703 unlink(tempfilename_in.c_str());
705 return;
706 }
710 /**
711 \return none
712 \brief This function uses an extention as a effect on a document.
713 \param module Extention to effect with.
714 \param doc Document to run through the effect.
716 This function is a little bit trickier than the previous two. It
717 needs two temporary files to get it's work done. Both of these
718 files have random names created for them using the g_file_open_temp function
719 with the ink_ext_ prefix in the temporary directory. Like the other
720 functions, the temporary files are deleted at the end.
722 To save/load the two temporary documents (both are SVG) the internal
723 modules for SVG load and save are used. They are both used through
724 the module system function by passing their keys into the functions.
726 The command itself is built a little bit differently than in other
727 functions because the effect support selections. So on the command
728 line a list of all the ids that are selected is included. Currently,
729 this only works for a single selected object, but there will be more.
730 The command string is filled with the data, and then after the execution
731 it is freed.
733 The execute function is used at the core of this function
734 to execute the Script on the two SVG documents (actually only one
735 exists at the time, the other is created by that script). At that
736 point both should be full, and the second one is loaded.
737 */
738 void
739 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
740 {
741 std::list<std::string> params;
742 module->paramListString(params);
744 if (module->no_doc) {
745 // this is a no-doc extension, e.g. a Help menu command;
746 // just run the command without any files, ignoring errors
748 Glib::ustring empty;
749 file_listener outfile;
750 execute(command, params, empty, outfile);
752 return;
753 }
755 std::string tempfilename_in;
756 int tempfd_in = 0;
757 try {
758 tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
759 } catch (...) {
760 /// \todo Popup dialog here
761 return;
762 }
764 std::string tempfilename_out;
765 int tempfd_out = 0;
766 try {
767 tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
768 } catch (...) {
769 /// \todo Popup dialog here
770 return;
771 }
773 SPDesktop *desktop = (SPDesktop *) doc;
774 sp_namedview_document_from_window(desktop);
776 Inkscape::Extension::save(
777 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
778 doc->doc(), tempfilename_in.c_str(), FALSE, FALSE, FALSE);
780 if (desktop != NULL) {
781 Inkscape::Util::GSListConstIterator<SPItem *> selected =
782 sp_desktop_selection(desktop)->itemList();
783 while ( selected != NULL ) {
784 Glib::ustring selected_id;
785 selected_id += "--id=";
786 selected_id += SP_OBJECT_ID(*selected);
787 params.insert(params.begin(), selected_id);
788 ++selected;
789 }
790 }
792 file_listener fileout;
793 int data_read = execute(command, params, tempfilename_in, fileout);
794 fileout.toFile(tempfilename_out);
796 SPDocument * mydoc = NULL;
797 if (data_read > 10) {
798 mydoc = Inkscape::Extension::open(
799 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
800 tempfilename_out.c_str());
801 } // data_read
803 // make sure we don't leak file descriptors from g_file_open_tmp
804 close(tempfd_in);
805 close(tempfd_out);
807 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
808 unlink(tempfilename_in.c_str());
809 unlink(tempfilename_out.c_str());
811 /* Do something with mydoc.... */
812 if (mydoc) {
813 doc->doc()->emitReconstructionStart();
814 copy_doc(doc->doc()->rroot, mydoc->rroot);
815 doc->doc()->emitReconstructionFinish();
816 mydoc->release();
817 sp_namedview_update_layers_from_document(desktop);
818 }
820 return;
821 }
825 /**
826 \brief A function to take all the svg elements from one document
827 and put them in another.
828 \param oldroot The root node of the document to be replaced
829 \param newroot The root node of the document to replace it with
831 This function first deletes all of the data in the old document. It
832 does this by creating a list of what needs to be deleted, and then
833 goes through the list. This two pass approach removes issues with
834 the list being change while parsing through it. Lots of nasty bugs.
836 Then, it goes through the new document, duplicating all of the
837 elements and putting them into the old document. The copy
838 is then complete.
839 */
840 void
841 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
842 {
843 std::vector<Inkscape::XML::Node *> delete_list;
844 for (Inkscape::XML::Node * child = oldroot->firstChild();
845 child != NULL;
846 child = child->next()) {
847 if (!strcmp("sodipodi:namedview", child->name()))
848 continue;
849 delete_list.push_back(child);
850 }
851 for (unsigned int i = 0; i < delete_list.size(); i++)
852 sp_repr_unparent(delete_list[i]);
854 for (Inkscape::XML::Node * child = newroot->firstChild();
855 child != NULL;
856 child = child->next()) {
857 if (!strcmp("sodipodi:namedview", child->name()))
858 continue;
859 oldroot->appendChild(child->duplicate(newroot->document()));
860 }
862 /** \todo Restore correct layer */
863 /** \todo Restore correct selection */
864 }
866 /** \brief This function checks the stderr file, and if it has data,
867 shows it in a warning dialog to the user
868 \param filename Filename of the stderr file
869 */
870 void
871 checkStderr (const Glib::ustring &data,
872 Gtk::MessageType type,
873 const Glib::ustring &message)
874 {
875 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
876 warning.set_resizable(true);
877 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
878 sp_transientize(dlg);
880 Gtk::VBox * vbox = warning.get_vbox();
882 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
883 Gtk::TextView * textview = new Gtk::TextView();
884 textview->set_editable(false);
885 textview->set_wrap_mode(Gtk::WRAP_WORD);
886 textview->show();
888 textview->get_buffer()->set_text(data.c_str());
890 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
891 scrollwindow->add(*textview);
892 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
893 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
894 scrollwindow->show();
896 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
898 warning.run();
900 return;
901 }
903 /** \brief This is the core of the extension file as it actually does
904 the execution of the extension.
905 \param in_command The command to be executed
906 \param filein Filename coming in
907 \param fileout Filename of the out file
908 \return Number of bytes that were read into the output file.
910 The first thing that this function does is build the command to be
911 executed. This consists of the first string (in_command) and then
912 the filename for input (filein). This file is put on the command
913 line.
915 The next thing is that this function does is open a pipe to the
916 command and get the file handle in the ppipe variable. It then
917 opens the output file with the output file handle. Both of these
918 operations are checked extensively for errors.
920 After both are opened, then the data is copied from the output
921 of the pipe into the file out using fread and fwrite. These two
922 functions are used because of their primitive nature they make
923 no assumptions about the data. A buffer is used in the transfer,
924 but the output of fread is stored so the exact number of bytes
925 is handled gracefully.
927 At the very end (after the data has been copied) both of the files
928 are closed, and we return to what we were doing.
929 */
930 int
931 execute (const std::list<std::string> &in_command,
932 const std::list<std::string> &in_params,
933 const Glib::ustring &filein,
934 file_listener &fileout)
935 {
936 g_return_val_if_fail(in_command.size() > 0, 0);
937 // printf("Executing\n");
939 std::vector <std::string> argv;
941 for (std::list<std::string>::const_iterator i = in_command.begin();
942 i != in_command.end(); i++) {
943 argv.push_back(*i);
944 }
946 if (!(filein.empty())) {
947 argv.push_back(filein);
948 }
950 for (std::list<std::string>::const_iterator i = in_params.begin();
951 i != in_params.end(); i++) {
952 argv.push_back(*i);
953 }
955 /*
956 for (std::vector<std::string>::const_iterator i = argv.begin();
957 i != argv.end(); i++) {
958 std::cout << *i << std::endl;
959 }
960 */
962 Glib::Pid pid;
963 int stdout_pipe, stderr_pipe;
965 try {
966 Glib::spawn_async_with_pipes(Glib::get_tmp_dir(), // working directory
967 argv, // arg v
968 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
969 sigc::slot<void>(),
970 &pid, // Pid
971 NULL, // STDIN
972 &stdout_pipe, // STDOUT
973 &stderr_pipe); // STDERR
974 } catch (Glib::SpawnError e) {
975 printf("Can't Spawn!!! %d\n", e.code());
976 return 0;
977 }
979 Glib::RefPtr<Glib::MainLoop> main_loop = Glib::MainLoop::create(false);
981 file_listener fileerr;
982 fileout.init(stdout_pipe, main_loop);
983 fileerr.init(stderr_pipe, main_loop);
985 main_loop->run();
987 Glib::ustring stderr_data = fileerr.string();
988 if (stderr_data.length() != 0) {
989 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
990 _("Inkscape has received additional data from the script executed. "
991 "The script did not return an error, but this may indicate the results will not be as expected."));
992 }
994 Glib::ustring stdout_data = fileout.string();
995 if (stdout_data.length() == 0) {
996 return 0;
997 }
999 // std::cout << "Finishing Execution." << std::endl;
1000 return stdout_data.length();
1001 }
1006 } // namespace Implementation
1007 } // namespace Extension
1008 } // namespace Inkscape
1010 /*
1011 Local Variables:
1012 mode:c++
1013 c-file-style:"stroustrup"
1014 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1015 indent-tabs-mode:nil
1016 fill-column:99
1017 End:
1018 */
1019 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :