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 * Jon A. Cruz <jon@joncruz.org>
9 * Abhishek Sharma
10 *
11 * Copyright (C) 2002-2005,2007 Authors
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
22 #include <unistd.h>
24 #include <errno.h>
25 #include <glib.h>
26 #include <glib/gstdio.h>
27 #include <gtkmm.h>
29 #include "ui/view/view.h"
30 #include "desktop-handles.h"
31 #include "desktop.h"
32 #include "selection.h"
33 #include "sp-namedview.h"
34 #include "io/sys.h"
35 #include "preferences.h"
36 #include "../system.h"
37 #include "extension/effect.h"
38 #include "extension/output.h"
39 #include "extension/input.h"
40 #include "extension/db.h"
41 #include "script.h"
42 #include "dialogs/dialog-events.h"
43 #include "inkscape.h"
44 #include "xml/node.h"
45 #include "xml/attribute-record.h"
47 #include "util/glib-list-iterators.h"
51 #ifdef WIN32
52 #include <windows.h>
53 #include <sys/stat.h>
54 #include "registrytool.h"
55 #endif
59 /** This is the command buffer that gets allocated from the stack */
60 #define BUFSIZE (255)
64 /* Namespaces */
65 namespace Inkscape {
66 namespace Extension {
67 namespace Implementation {
69 /** \brief Make GTK+ events continue to come through a little bit
71 This just keeps coming the events through so that we'll make the GUI
72 update and look pretty.
73 */
74 void Script::pump_events (void) {
75 while ( Gtk::Main::events_pending() ) {
76 Gtk::Main::iteration();
77 }
78 return;
79 }
82 /** \brief A table of what interpreters to call for a given language
84 This table is used to keep track of all the programs to execute a
85 given script. It also tracks the preference to use to overwrite
86 the given interpreter to a custom one per user.
87 */
88 Script::interpreter_t const Script::interpreterTab[] = {
89 {"perl", "perl-interpreter", "perl" },
90 #ifdef WIN32
91 {"python", "python-interpreter", "pythonw" },
92 #else
93 {"python", "python-interpreter", "python" },
94 #endif
95 {"ruby", "ruby-interpreter", "ruby" },
96 {"shell", "shell-interpreter", "sh" },
97 { NULL, NULL, NULL }
98 };
102 /** \brief Look up an interpreter name, and translate to something that
103 is executable
104 \param interpNameArg The name of the interpreter that we're looking
105 for, should be an entry in interpreterTab
106 */
107 std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
108 {
109 interpreter_t const *interp = 0;
110 bool foundInterp = false;
111 for (interp = interpreterTab ; interp->identity ; interp++ ){
112 if (interpNameArg == interp->identity) {
113 foundInterp = true;
114 break;
115 }
116 }
118 // Do we have a supported interpreter type?
119 if (!foundInterp) {
120 return "";
121 }
122 std::string interpreter_path = Glib::filename_from_utf8(interp->defaultval);
124 // 1. Check preferences for an override.
125 // Note: this must be an absolute path.
126 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
127 Glib::ustring prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->prefstring));
129 if (!prefInterp.empty()) {
130 interpreter_path = Glib::filename_from_utf8(prefInterp);
131 }
133 // 2. Search the path.
134 // Do this on all systems, for consistency.
135 // PATH is set up to contain the Python and Perl binary directories
136 // on Windows, so no extra code is necessary.
137 if (!Glib::path_is_absolute(interpreter_path)) {
138 interpreter_path = Glib::find_program_in_path(interpreter_path);
139 }
140 return interpreter_path;
141 }
143 /** \brief This function creates a script object and sets up the
144 variables.
145 \return A script object
147 This function just sets the command to NULL. It should get built
148 officially in the load function. This allows for less allocation
149 of memory in the unloaded state.
150 */
151 Script::Script() :
152 Implementation()
153 {
154 }
156 /**
157 * brief Destructor
158 */
159 Script::~Script()
160 {
161 }
165 /**
166 \return A string with the complete string with the relative directory expanded
167 \brief This function takes in a Repr that contains a reldir entry
168 and returns that data with the relative directory expanded.
169 Mostly it is here so that relative directories all get used
170 the same way.
171 \param reprin The Inkscape::XML::Node with the reldir in it.
173 Basically this function looks at an attribute of the Repr, and makes
174 a decision based on that. Currently, it is only working with the
175 'extensions' relative directory, but there will be more of them.
176 One thing to notice is that this function always returns an allocated
177 string. This means that the caller of this function can always
178 free what they are given (and should do it too!).
179 */
180 std::string
181 Script::solve_reldir(Inkscape::XML::Node *reprin) {
183 gchar const *s = reprin->attribute("reldir");
185 // right now the only recognized relative directory is "extensions"
186 if (!s || Glib::ustring(s) != "extensions") {
187 Glib::ustring str = sp_repr_children(reprin)->content();
188 return str;
189 }
191 Glib::ustring reldir = s;
193 for (unsigned int i=0;
194 i < Inkscape::Extension::Extension::search_path.size();
195 i++) {
197 gchar * fname = g_build_filename(
198 Inkscape::Extension::Extension::search_path[i],
199 sp_repr_children(reprin)->content(),
200 NULL);
201 Glib::ustring filename = fname;
202 g_free(fname);
204 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) ) {
205 return Glib::filename_from_utf8(filename);
206 }
207 }
209 return "";
210 }
214 /**
215 \return Whether the command given exists, including in the path
216 \brief This function is used to find out if something exists for
217 the check command. It can look in the path if required.
218 \param command The command or file that should be looked for
220 The first thing that this function does is check to see if the
221 incoming file name has a directory delimiter in it. This would
222 mean that it wants to control the directories, and should be
223 used directly.
225 If not, the path is used. Each entry in the path is stepped through,
226 attached to the string, and then tested. If the file is found
227 then a TRUE is returned. If we get all the way through the path
228 then a FALSE is returned, the command could not be found.
229 */
230 bool Script::check_existence(const std::string &command)
231 {
233 // Check the simple case first
234 if (command.empty()) {
235 return false;
236 }
238 //Don't search when it is an absolute path. */
239 if (!Glib::path_is_absolute(command)) {
240 if (Glib::file_test(command, Glib::FILE_TEST_EXISTS)) {
241 return true;
242 } else {
243 return false;
244 }
245 }
247 std::string path = Glib::getenv("PATH");
248 if (path.empty()) {
249 /* There is no `PATH' in the environment.
250 The default search path is the current directory */
251 path = G_SEARCHPATH_SEPARATOR_S;
252 }
254 std::string::size_type pos = 0;
255 std::string::size_type pos2 = 0;
256 while ( pos < path.size() ) {
258 std::string localPath;
260 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
261 if (pos2 == path.npos) {
262 localPath = path.substr(pos);
263 pos = path.size();
264 } else {
265 localPath = path.substr(pos, pos2-pos);
266 pos = pos2+1;
267 }
269 //printf("### %s\n", localPath.c_str());
270 std::string candidatePath =
271 Glib::build_filename(localPath, command);
273 if (Glib::file_test(candidatePath,
274 Glib::FILE_TEST_EXISTS)) {
275 return true;
276 }
278 }
280 return false;
281 }
287 /**
288 \return none
289 \brief This function 'loads' an extention, basically it determines
290 the full command for the extention and stores that.
291 \param module The extention to be loaded.
293 The most difficult part about this function is finding the actual
294 command through all of the Reprs. Basically it is hidden down a
295 couple of layers, and so the code has to move down too. When
296 the command is actually found, it has its relative directory
297 solved.
299 At that point all of the loops are exited, and there is an
300 if statement to make sure they didn't exit because of not finding
301 the command. If that's the case, the extention doesn't get loaded
302 and should error out at a higher level.
303 */
305 bool Script::load(Inkscape::Extension::Extension *module)
306 {
307 if (module->loaded()) {
308 return true;
309 }
311 helper_extension = "";
313 /* This should probably check to find the executable... */
314 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
315 while (child_repr != NULL) {
316 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
317 child_repr = sp_repr_children(child_repr);
318 while (child_repr != NULL) {
319 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
320 const gchar *interpretstr = child_repr->attribute("interpreter");
321 if (interpretstr != NULL) {
322 std::string interpString = resolveInterpreterExecutable(interpretstr);
323 command.insert(command.end(), interpString);
324 }
325 command.insert(command.end(), solve_reldir(child_repr));
326 }
327 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
328 helper_extension = sp_repr_children(child_repr)->content();
329 }
330 child_repr = sp_repr_next(child_repr);
331 }
333 break;
334 }
335 child_repr = sp_repr_next(child_repr);
336 }
338 //g_return_val_if_fail(command.length() > 0, false);
340 return true;
341 }
344 /**
345 \return None.
346 \brief Unload this puppy!
347 \param module Extension to be unloaded.
349 This function just sets the module to unloaded. It free's the
350 command if it has been allocated.
351 */
352 void Script::unload(Inkscape::Extension::Extension */*module*/)
353 {
354 command.clear();
355 helper_extension = "";
356 }
361 /**
362 \return Whether the check passed or not
363 \brief Check every dependency that was given to make sure we should keep this extension
364 \param module The Extension in question
366 */
367 bool
368 Script::check(Inkscape::Extension::Extension *module)
369 {
370 int script_count = 0;
371 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
372 while (child_repr != NULL) {
373 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
374 script_count++;
375 child_repr = sp_repr_children(child_repr);
376 while (child_repr != NULL) {
377 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
378 std::string command_text = solve_reldir(child_repr);
379 if (!command_text.empty()) {
380 /* I've got the command */
381 bool existance = check_existence(command_text);
382 if (!existance)
383 return false;
384 }
385 }
387 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
388 gchar const *helper = sp_repr_children(child_repr)->content();
389 if (Inkscape::Extension::db.get(helper) == NULL) {
390 return false;
391 }
392 }
394 child_repr = sp_repr_next(child_repr);
395 }
397 break;
398 }
399 child_repr = sp_repr_next(child_repr);
400 }
402 if (script_count == 0) {
403 return false;
404 }
406 return true;
407 }
409 class ScriptDocCache : public ImplementationDocumentCache {
410 friend class Script;
411 protected:
412 std::string _filename;
413 int _tempfd;
414 public:
415 ScriptDocCache (Inkscape::UI::View::View * view);
416 ~ScriptDocCache ( );
417 };
419 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
420 ImplementationDocumentCache(view),
421 _filename(""),
422 _tempfd(0)
423 {
424 try {
425 _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
426 } catch (...) {
427 /// \todo Popup dialog here
428 return;
429 }
431 SPDesktop *desktop = (SPDesktop *) view;
432 sp_namedview_document_from_window(desktop);
434 Inkscape::Extension::save(
435 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
436 view->doc(), _filename.c_str(), false, false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
438 return;
439 }
441 ScriptDocCache::~ScriptDocCache ( )
442 {
443 close(_tempfd);
444 unlink(_filename.c_str());
445 }
447 ImplementationDocumentCache *Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
448 return new ScriptDocCache(view);
449 }
452 /**
453 \return A dialog for preferences
454 \brief A stub funtion right now
455 \param module Module who's preferences need getting
456 \param filename Hey, the file you're getting might be important
458 This function should really do something, right now it doesn't.
459 */
460 Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module,
461 const gchar */*filename*/)
462 {
463 return module->autogui(NULL, NULL);
464 }
468 /**
469 \return A dialog for preferences
470 \brief A stub funtion right now
471 \param module Module whose preferences need getting
473 This function should really do something, right now it doesn't.
474 */
475 Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
476 {
477 return module->autogui(NULL, NULL);
478 }
480 /**
481 \return A new document that has been opened
482 \brief This function uses a filename that is put in, and calls
483 the extension's command to create an SVG file which is
484 returned.
485 \param module Extension to use.
486 \param filename File to open.
488 First things first, this function needs a temporary file name. To
489 create on of those the function g_file_open_tmp is used with
490 the header of ink_ext_.
492 The extension is then executed using the 'execute' function
493 with the filname coming in, and the temporary filename. After
494 That executing, the SVG should be in the temporary file.
496 Finally, the temporary file is opened using the SVG input module and
497 a document is returned. That document has its filename set to
498 the incoming filename (so that it's not the temporary filename).
499 That document is then returned from this function.
500 */
501 SPDocument *Script::open(Inkscape::Extension::Input *module,
502 const gchar *filenameArg)
503 {
504 std::list<std::string> params;
505 module->paramListString(params);
507 std::string tempfilename_out;
508 int tempfd_out = 0;
509 try {
510 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
511 } catch (...) {
512 /// \todo Popup dialog here
513 return NULL;
514 }
516 std::string lfilename = Glib::filename_from_utf8(filenameArg);
518 file_listener fileout;
519 int data_read = execute(command, params, lfilename, fileout);
520 fileout.toFile(tempfilename_out);
522 SPDocument * mydoc = NULL;
523 if (data_read > 10) {
524 if (helper_extension.size()==0) {
525 mydoc = Inkscape::Extension::open(
526 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
527 tempfilename_out.c_str());
528 } else {
529 mydoc = Inkscape::Extension::open(
530 Inkscape::Extension::db.get(helper_extension.c_str()),
531 tempfilename_out.c_str());
532 }
533 } // data_read
535 if (mydoc != NULL) {
536 mydoc->setBase(0);
537 mydoc->changeUriAndHrefs(filenameArg);
538 }
540 // make sure we don't leak file descriptors from g_file_open_tmp
541 close(tempfd_out);
543 unlink(tempfilename_out.c_str());
545 return mydoc;
546 } // open
550 /**
551 \return none
552 \brief This function uses an extention to save a document. It first
553 creates an SVG file of the document, and then runs it through
554 the script.
555 \param module Extention to be used
556 \param doc Document to be saved
557 \param filename The name to save the final file as
558 \return false in case of any failure writing the file, otherwise true
560 Well, at some point people need to save - it is really what makes
561 the entire application useful. And, it is possible that someone
562 would want to use an extetion for this, so we need a function to
563 do that eh?
565 First things first, the document is saved to a temporary file that
566 is an SVG file. To get the temporary filename g_file_open_tmp is used with
567 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
568 end of the function.
570 After we have the SVG file, then extention_execute is called with
571 the temporary file name and the final output filename. This should
572 put the output of the script into the final output file. We then
573 delete the temporary file.
574 */
575 void Script::save(Inkscape::Extension::Output *module,
576 SPDocument *doc,
577 const gchar *filenameArg)
578 {
579 std::list<std::string> params;
580 module->paramListString(params);
582 std::string tempfilename_in;
583 int tempfd_in = 0;
584 try {
585 tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
586 } catch (...) {
587 /// \todo Popup dialog here
588 throw Inkscape::Extension::Output::save_failed();
589 }
591 if (helper_extension.size() == 0) {
592 Inkscape::Extension::save(
593 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
594 doc, tempfilename_in.c_str(), false, false, false,
595 Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
596 } else {
597 Inkscape::Extension::save(
598 Inkscape::Extension::db.get(helper_extension.c_str()),
599 doc, tempfilename_in.c_str(), false, false, false,
600 Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
601 }
604 file_listener fileout;
605 int data_read = execute(command, params, tempfilename_in, fileout);
607 bool success = false;
609 if (data_read > 0) {
610 std::string lfilename = Glib::filename_from_utf8(filenameArg);
611 success = fileout.toFile(lfilename);
612 }
614 // make sure we don't leak file descriptors from g_file_open_tmp
615 close(tempfd_in);
616 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
617 unlink(tempfilename_in.c_str());
619 if (success == false) {
620 throw Inkscape::Extension::Output::save_failed();
621 }
623 return;
624 }
628 /**
629 \return none
630 \brief This function uses an extention as a effect on a document.
631 \param module Extention to effect with.
632 \param doc Document to run through the effect.
634 This function is a little bit trickier than the previous two. It
635 needs two temporary files to get it's work done. Both of these
636 files have random names created for them using the g_file_open_temp function
637 with the ink_ext_ prefix in the temporary directory. Like the other
638 functions, the temporary files are deleted at the end.
640 To save/load the two temporary documents (both are SVG) the internal
641 modules for SVG load and save are used. They are both used through
642 the module system function by passing their keys into the functions.
644 The command itself is built a little bit differently than in other
645 functions because the effect support selections. So on the command
646 line a list of all the ids that are selected is included. Currently,
647 this only works for a single selected object, but there will be more.
648 The command string is filled with the data, and then after the execution
649 it is freed.
651 The execute function is used at the core of this function
652 to execute the Script on the two SVG documents (actually only one
653 exists at the time, the other is created by that script). At that
654 point both should be full, and the second one is loaded.
655 */
656 void Script::effect(Inkscape::Extension::Effect *module,
657 Inkscape::UI::View::View *doc,
658 ImplementationDocumentCache * docCache)
659 {
660 if (docCache == NULL) {
661 docCache = newDocCache(module, doc);
662 }
663 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
664 if (dc == NULL) {
665 printf("TOO BAD TO LIVE!!!");
666 exit(1);
667 }
669 SPDesktop *desktop = (SPDesktop *)doc;
670 sp_namedview_document_from_window(desktop);
672 std::list<std::string> params;
673 module->paramListString(params);
675 if (module->no_doc) {
676 // this is a no-doc extension, e.g. a Help menu command;
677 // just run the command without any files, ignoring errors
679 Glib::ustring empty;
680 file_listener outfile;
681 execute(command, params, empty, outfile);
683 return;
684 }
686 std::string tempfilename_out;
687 int tempfd_out = 0;
688 try {
689 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
690 } catch (...) {
691 /// \todo Popup dialog here
692 return;
693 }
695 if (desktop != NULL) {
696 Inkscape::Util::GSListConstIterator<SPItem *> selected =
697 sp_desktop_selection(desktop)->itemList();
698 while ( selected != NULL ) {
699 Glib::ustring selected_id;
700 selected_id += "--id=";
701 selected_id += (*selected)->getId();
702 params.insert(params.begin(), selected_id);
703 ++selected;
704 }
705 }
707 file_listener fileout;
708 int data_read = execute(command, params, dc->_filename, fileout);
709 fileout.toFile(tempfilename_out);
711 pump_events();
713 SPDocument * mydoc = NULL;
714 if (data_read > 10) {
715 mydoc = Inkscape::Extension::open(
716 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
717 tempfilename_out.c_str());
718 } // data_read
720 pump_events();
722 // make sure we don't leak file descriptors from g_file_open_tmp
723 close(tempfd_out);
725 g_unlink(tempfilename_out.c_str());
727 /* Do something with mydoc.... */
728 if (mydoc) {
729 doc->doc()->emitReconstructionStart();
730 copy_doc(doc->doc()->rroot, mydoc->rroot);
731 doc->doc()->emitReconstructionFinish();
732 mydoc->release();
733 sp_namedview_update_layers_from_document(desktop);
734 }
736 return;
737 }
741 /**
742 \brief A function to take all the svg elements from one document
743 and put them in another.
744 \param oldroot The root node of the document to be replaced
745 \param newroot The root node of the document to replace it with
747 This function first deletes all of the data in the old document. It
748 does this by creating a list of what needs to be deleted, and then
749 goes through the list. This two pass approach removes issues with
750 the list being change while parsing through it. Lots of nasty bugs.
752 Then, it goes through the new document, duplicating all of the
753 elements and putting them into the old document. The copy
754 is then complete.
755 */
756 void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
757 {
758 std::vector<Inkscape::XML::Node *> delete_list;
759 Inkscape::XML::Node * oldroot_namedview = NULL;
761 for (Inkscape::XML::Node * child = oldroot->firstChild();
762 child != NULL;
763 child = child->next()) {
764 if (!strcmp("sodipodi:namedview", child->name())) {
765 oldroot_namedview = child;
766 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
767 oldroot_namedview_child != NULL;
768 oldroot_namedview_child = oldroot_namedview_child->next()) {
769 delete_list.push_back(oldroot_namedview_child);
770 }
771 } else {
772 delete_list.push_back(child);
773 }
774 }
775 for (unsigned int i = 0; i < delete_list.size(); i++) {
776 sp_repr_unparent(delete_list[i]);
777 }
779 for (Inkscape::XML::Node * child = newroot->firstChild();
780 child != NULL;
781 child = child->next()) {
782 if (!strcmp("sodipodi:namedview", child->name())) {
783 if (oldroot_namedview != NULL) {
784 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
785 newroot_namedview_child != NULL;
786 newroot_namedview_child = newroot_namedview_child->next()) {
787 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
788 }
789 }
790 } else {
791 oldroot->appendChild(child->duplicate(oldroot->document()));
792 }
793 }
795 {
796 using Inkscape::Util::List;
797 using Inkscape::XML::AttributeRecord;
798 std::vector<gchar const *> attribs;
800 // Make a list of all attributes of the old root node.
801 for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
802 attribs.push_back(g_quark_to_string(iter->key));
803 }
805 // Delete the attributes of the old root nodes.
806 for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++) {
807 oldroot->setAttribute(*it, NULL);
808 }
810 // Set the new attributes.
811 for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) {
812 gchar const *name = g_quark_to_string(iter->key);
813 oldroot->setAttribute(name, newroot->attribute(name));
814 }
815 }
817 /** \todo Restore correct layer */
818 /** \todo Restore correct selection */
819 }
821 /** \brief This function checks the stderr file, and if it has data,
822 shows it in a warning dialog to the user
823 \param filename Filename of the stderr file
824 */
825 void Script::checkStderr (const Glib::ustring &data,
826 Gtk::MessageType type,
827 const Glib::ustring &message)
828 {
829 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
830 warning.set_resizable(true);
831 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
832 sp_transientize(dlg);
834 Gtk::VBox * vbox = warning.get_vbox();
836 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
837 Gtk::TextView * textview = new Gtk::TextView();
838 textview->set_editable(false);
839 textview->set_wrap_mode(Gtk::WRAP_WORD);
840 textview->show();
842 // Remove the last character
843 char *errormsg = (char*) data.c_str();
844 while (*errormsg != '\0') errormsg++;
845 errormsg -= 1;
846 *errormsg = '\0';
848 textview->get_buffer()->set_text(_(data.c_str()));
850 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
851 scrollwindow->add(*textview);
852 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
853 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
854 scrollwindow->show();
856 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
858 warning.run();
860 return;
861 }
863 bool Script::cancelProcessing (void) {
864 _canceled = true;
865 _main_loop->quit();
866 Glib::spawn_close_pid(_pid);
868 return true;
869 }
872 /** \brief This is the core of the extension file as it actually does
873 the execution of the extension.
874 \param in_command The command to be executed
875 \param filein Filename coming in
876 \param fileout Filename of the out file
877 \return Number of bytes that were read into the output file.
879 The first thing that this function does is build the command to be
880 executed. This consists of the first string (in_command) and then
881 the filename for input (filein). This file is put on the command
882 line.
884 The next thing is that this function does is open a pipe to the
885 command and get the file handle in the ppipe variable. It then
886 opens the output file with the output file handle. Both of these
887 operations are checked extensively for errors.
889 After both are opened, then the data is copied from the output
890 of the pipe into the file out using fread and fwrite. These two
891 functions are used because of their primitive nature they make
892 no assumptions about the data. A buffer is used in the transfer,
893 but the output of fread is stored so the exact number of bytes
894 is handled gracefully.
896 At the very end (after the data has been copied) both of the files
897 are closed, and we return to what we were doing.
898 */
899 int Script::execute (const std::list<std::string> &in_command,
900 const std::list<std::string> &in_params,
901 const Glib::ustring &filein,
902 file_listener &fileout)
903 {
904 g_return_val_if_fail(!in_command.empty(), 0);
905 // printf("Executing\n");
907 std::vector<std::string> argv;
909 bool interpreted = (in_command.size() == 2);
910 std::string program = in_command.front();
911 std::string script = interpreted ? in_command.back() : "";
912 std::string working_directory = "";
914 // Use Glib::find_program_in_path instead of the equivalent
915 // Glib::spawn_* functionality, because _wspawnp is broken on Windows:
916 // it doesn't work when PATH contains Unicode directories
917 if (!Glib::path_is_absolute(program)) {
918 program = Glib::find_program_in_path(program);
919 }
920 argv.push_back(program);
922 if (interpreted) {
923 // On Windows, Python garbles Unicode command line parameters
924 // in an useless way. This means extensions fail when Inkscape
925 // is run from an Unicode directory.
926 // As a workaround, we set the working directory to the one
927 // containing the script.
928 working_directory = Glib::path_get_dirname(script);
929 script = Glib::path_get_basename(script);
930 #ifdef G_OS_WIN32
931 // ANNOYING: glibmm does not wrap g_win32_locale_filename_from_utf8
932 gchar *workdir_s = g_win32_locale_filename_from_utf8(working_directory.data());
933 working_directory = workdir_s;
934 g_free(workdir_s);
935 #endif
937 argv.push_back(script);
938 }
940 // assemble the rest of argv
941 std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
942 if (!filein.empty()) {
943 argv.push_back(filein);
944 }
946 int stdout_pipe, stderr_pipe;
948 try {
949 Glib::spawn_async_with_pipes(working_directory, // working directory
950 argv, // arg v
951 static_cast<Glib::SpawnFlags>(0), // no flags
952 sigc::slot<void>(),
953 &_pid, // Pid
954 NULL, // STDIN
955 &stdout_pipe, // STDOUT
956 &stderr_pipe); // STDERR
957 } catch (Glib::Error e) {
958 printf("Can't Spawn!!! spawn returns: %s\n", e.what().data());
959 return 0;
960 }
962 _main_loop = Glib::MainLoop::create(false);
964 file_listener fileerr;
965 fileout.init(stdout_pipe, _main_loop);
966 fileerr.init(stderr_pipe, _main_loop);
968 _canceled = false;
969 _main_loop->run();
971 // Ensure all the data is out of the pipe
972 while (!fileout.isDead()) {
973 fileout.read(Glib::IO_IN);
974 }
975 while (!fileerr.isDead()) {
976 fileerr.read(Glib::IO_IN);
977 }
979 if (_canceled) {
980 // std::cout << "Script Canceled" << std::endl;
981 return 0;
982 }
984 Glib::ustring stderr_data = fileerr.string();
985 if (stderr_data.length() != 0 &&
986 inkscape_use_gui()
987 ) {
988 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
989 _("Inkscape has received additional data from the script executed. "
990 "The script did not return an error, but this may indicate the results will not be as expected."));
991 }
993 Glib::ustring stdout_data = fileout.string();
994 if (stdout_data.length() == 0) {
995 return 0;
996 }
998 // std::cout << "Finishing Execution." << std::endl;
999 return stdout_data.length();
1000 }
1005 } // namespace Implementation
1006 } // namespace Extension
1007 } // namespace Inkscape
1009 /*
1010 Local Variables:
1011 mode:c++
1012 c-file-style:"stroustrup"
1013 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1014 indent-tabs-mode:nil
1015 fill-column:99
1016 End:
1017 */
1018 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :