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 *
10 * Copyright (C) 2002-2005,2007 Authors
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
15 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
21 #include <unistd.h>
23 #include <errno.h>
24 #include <glib.h>
25 #include <glib/gstdio.h>
26 #include <gtkmm.h>
28 #include "ui/view/view.h"
29 #include "desktop-handles.h"
30 #include "desktop.h"
31 #include "selection.h"
32 #include "sp-namedview.h"
33 #include "io/sys.h"
34 #include "preferences.h"
35 #include "../system.h"
36 #include "extension/effect.h"
37 #include "extension/output.h"
38 #include "extension/input.h"
39 #include "extension/db.h"
40 #include "script.h"
41 #include "dialogs/dialog-events.h"
42 #include "application/application.h"
43 #include "xml/node.h"
44 #include "xml/attribute-record.h"
46 #include "util/glib-list-iterators.h"
50 #ifdef WIN32
51 #include <windows.h>
52 #include <sys/stat.h>
53 #include "registrytool.h"
54 #endif
58 /** This is the command buffer that gets allocated from the stack */
59 #define BUFSIZE (255)
63 /* Namespaces */
64 namespace Inkscape {
65 namespace Extension {
66 namespace Implementation {
68 /** \brief Make GTK+ events continue to come through a little bit
70 This just keeps coming the events through so that we'll make the GUI
71 update and look pretty.
72 */
73 void Script::pump_events (void) {
74 while ( Gtk::Main::events_pending() ) {
75 Gtk::Main::iteration();
76 }
77 return;
78 }
81 /** \brief A table of what interpreters to call for a given language
83 This table is used to keep track of all the programs to execute a
84 given script. It also tracks the preference to use to overwrite
85 the given interpreter to a custom one per user.
86 */
87 Script::interpreter_t const Script::interpreterTab[] = {
88 {"perl", "perl-interpreter", "perl" },
89 #ifdef WIN32
90 {"python", "python-interpreter", "pythonw" },
91 #else
92 {"python", "python-interpreter", "python" },
93 #endif
94 {"ruby", "ruby-interpreter", "ruby" },
95 {"shell", "shell-interpreter", "sh" },
96 { NULL, NULL, NULL }
97 };
101 /** \brief Look up an interpreter name, and translate to something that
102 is executable
103 \param interpNameArg The name of the interpreter that we're looking
104 for, should be an entry in interpreterTab
105 */
106 std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
107 {
108 interpreter_t const *interp = 0;
109 bool foundInterp = false;
110 for (interp = interpreterTab ; interp->identity ; interp++ ){
111 if (interpNameArg == interp->identity) {
112 foundInterp = true;
113 break;
114 }
115 }
117 // Do we have a supported interpreter type?
118 if (!foundInterp) {
119 return "";
120 }
121 std::string interpreter_path = Glib::filename_from_utf8(interp->defaultval);
123 // 1. Check preferences for an override.
124 // Note: this must be an absolute path.
125 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
126 Glib::ustring prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->prefstring));
128 if (!prefInterp.empty()) {
129 interpreter_path = Glib::filename_from_utf8(prefInterp);
130 }
132 // 2. Search the path.
133 // Do this on all systems, for consistency.
134 // PATH is set up to contain the Python and Perl binary directories
135 // on Windows, so no extra code is necessary.
136 if (!Glib::path_is_absolute(interpreter_path)) {
137 interpreter_path = Glib::find_program_in_path(interpreter_path);
138 }
139 return interpreter_path;
140 }
142 /** \brief This function creates a script object and sets up the
143 variables.
144 \return A script object
146 This function just sets the command to NULL. It should get built
147 officially in the load function. This allows for less allocation
148 of memory in the unloaded state.
149 */
150 Script::Script() :
151 Implementation()
152 {
153 }
155 /**
156 * brief Destructor
157 */
158 Script::~Script()
159 {
160 }
164 /**
165 \return A string with the complete string with the relative directory expanded
166 \brief This function takes in a Repr that contains a reldir entry
167 and returns that data with the relative directory expanded.
168 Mostly it is here so that relative directories all get used
169 the same way.
170 \param reprin The Inkscape::XML::Node with the reldir in it.
172 Basically this function looks at an attribute of the Repr, and makes
173 a decision based on that. Currently, it is only working with the
174 'extensions' relative directory, but there will be more of them.
175 One thing to notice is that this function always returns an allocated
176 string. This means that the caller of this function can always
177 free what they are given (and should do it too!).
178 */
179 std::string
180 Script::solve_reldir(Inkscape::XML::Node *reprin) {
182 gchar const *s = reprin->attribute("reldir");
184 // right now the only recognized relative directory is "extensions"
185 if (!s || Glib::ustring(s) != "extensions") {
186 Glib::ustring str = sp_repr_children(reprin)->content();
187 return str;
188 }
190 Glib::ustring reldir = s;
192 for (unsigned int i=0;
193 i < Inkscape::Extension::Extension::search_path.size();
194 i++) {
196 gchar * fname = g_build_filename(
197 Inkscape::Extension::Extension::search_path[i],
198 sp_repr_children(reprin)->content(),
199 NULL);
200 Glib::ustring filename = fname;
201 g_free(fname);
203 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) ) {
204 return Glib::filename_from_utf8(filename);
205 }
206 }
208 return "";
209 }
213 /**
214 \return Whether the command given exists, including in the path
215 \brief This function is used to find out if something exists for
216 the check command. It can look in the path if required.
217 \param command The command or file that should be looked for
219 The first thing that this function does is check to see if the
220 incoming file name has a directory delimiter in it. This would
221 mean that it wants to control the directories, and should be
222 used directly.
224 If not, the path is used. Each entry in the path is stepped through,
225 attached to the string, and then tested. If the file is found
226 then a TRUE is returned. If we get all the way through the path
227 then a FALSE is returned, the command could not be found.
228 */
229 bool Script::check_existence(const std::string &command)
230 {
232 // Check the simple case first
233 if (command.empty()) {
234 return false;
235 }
237 //Don't search when it is an absolute path. */
238 if (!Glib::path_is_absolute(command)) {
239 if (Glib::file_test(command, Glib::FILE_TEST_EXISTS)) {
240 return true;
241 } else {
242 return false;
243 }
244 }
246 std::string path = Glib::getenv("PATH");
247 if (path.empty()) {
248 /* There is no `PATH' in the environment.
249 The default search path is the current directory */
250 path = G_SEARCHPATH_SEPARATOR_S;
251 }
253 std::string::size_type pos = 0;
254 std::string::size_type pos2 = 0;
255 while ( pos < path.size() ) {
257 std::string localPath;
259 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
260 if (pos2 == path.npos) {
261 localPath = path.substr(pos);
262 pos = path.size();
263 } else {
264 localPath = path.substr(pos, pos2-pos);
265 pos = pos2+1;
266 }
268 //printf("### %s\n", localPath.c_str());
269 std::string candidatePath =
270 Glib::build_filename(localPath, command);
272 if (Glib::file_test(candidatePath,
273 Glib::FILE_TEST_EXISTS)) {
274 return true;
275 }
277 }
279 return false;
280 }
286 /**
287 \return none
288 \brief This function 'loads' an extention, basically it determines
289 the full command for the extention and stores that.
290 \param module The extention to be loaded.
292 The most difficult part about this function is finding the actual
293 command through all of the Reprs. Basically it is hidden down a
294 couple of layers, and so the code has to move down too. When
295 the command is actually found, it has its relative directory
296 solved.
298 At that point all of the loops are exited, and there is an
299 if statement to make sure they didn't exit because of not finding
300 the command. If that's the case, the extention doesn't get loaded
301 and should error out at a higher level.
302 */
304 bool Script::load(Inkscape::Extension::Extension *module)
305 {
306 if (module->loaded()) {
307 return true;
308 }
310 helper_extension = "";
312 /* This should probably check to find the executable... */
313 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
314 while (child_repr != NULL) {
315 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
316 child_repr = sp_repr_children(child_repr);
317 while (child_repr != NULL) {
318 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
319 const gchar *interpretstr = child_repr->attribute("interpreter");
320 if (interpretstr != NULL) {
321 std::string interpString = resolveInterpreterExecutable(interpretstr);
322 command.insert(command.end(), interpString);
323 }
324 command.insert(command.end(), solve_reldir(child_repr));
325 }
326 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
327 helper_extension = sp_repr_children(child_repr)->content();
328 }
329 child_repr = sp_repr_next(child_repr);
330 }
332 break;
333 }
334 child_repr = sp_repr_next(child_repr);
335 }
337 //g_return_val_if_fail(command.length() > 0, false);
339 return true;
340 }
343 /**
344 \return None.
345 \brief Unload this puppy!
346 \param module Extension to be unloaded.
348 This function just sets the module to unloaded. It free's the
349 command if it has been allocated.
350 */
351 void Script::unload(Inkscape::Extension::Extension */*module*/)
352 {
353 command.clear();
354 helper_extension = "";
355 }
360 /**
361 \return Whether the check passed or not
362 \brief Check every dependency that was given to make sure we should keep this extension
363 \param module The Extension in question
365 */
366 bool
367 Script::check(Inkscape::Extension::Extension *module)
368 {
369 int script_count = 0;
370 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
371 while (child_repr != NULL) {
372 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
373 script_count++;
374 child_repr = sp_repr_children(child_repr);
375 while (child_repr != NULL) {
376 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
377 std::string command_text = solve_reldir(child_repr);
378 if (!command_text.empty()) {
379 /* I've got the command */
380 bool existance = check_existence(command_text);
381 if (!existance)
382 return false;
383 }
384 }
386 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
387 gchar const *helper = sp_repr_children(child_repr)->content();
388 if (Inkscape::Extension::db.get(helper) == NULL) {
389 return false;
390 }
391 }
393 child_repr = sp_repr_next(child_repr);
394 }
396 break;
397 }
398 child_repr = sp_repr_next(child_repr);
399 }
401 if (script_count == 0) {
402 return false;
403 }
405 return true;
406 }
408 class ScriptDocCache : public ImplementationDocumentCache {
409 friend class Script;
410 protected:
411 std::string _filename;
412 int _tempfd;
413 public:
414 ScriptDocCache (Inkscape::UI::View::View * view);
415 ~ScriptDocCache ( );
416 };
418 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
419 ImplementationDocumentCache(view),
420 _filename(""),
421 _tempfd(0)
422 {
423 try {
424 _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
425 } catch (...) {
426 /// \todo Popup dialog here
427 return;
428 }
430 SPDesktop *desktop = (SPDesktop *) view;
431 sp_namedview_document_from_window(desktop);
433 Inkscape::Extension::save(
434 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
435 view->doc(), _filename.c_str(), false, false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
437 return;
438 }
440 ScriptDocCache::~ScriptDocCache ( )
441 {
442 close(_tempfd);
443 unlink(_filename.c_str());
444 }
446 ImplementationDocumentCache *Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
447 return new ScriptDocCache(view);
448 }
451 /**
452 \return A dialog for preferences
453 \brief A stub funtion right now
454 \param module Module who's preferences need getting
455 \param filename Hey, the file you're getting might be important
457 This function should really do something, right now it doesn't.
458 */
459 Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module,
460 const gchar */*filename*/)
461 {
462 return module->autogui(NULL, NULL);
463 }
467 /**
468 \return A dialog for preferences
469 \brief A stub funtion right now
470 \param module Module whose preferences need getting
472 This function should really do something, right now it doesn't.
473 */
474 Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
475 {
476 return module->autogui(NULL, NULL);
477 }
479 /**
480 \return A new document that has been opened
481 \brief This function uses a filename that is put in, and calls
482 the extension's command to create an SVG file which is
483 returned.
484 \param module Extension to use.
485 \param filename File to open.
487 First things first, this function needs a temporary file name. To
488 create on of those the function g_file_open_tmp is used with
489 the header of ink_ext_.
491 The extension is then executed using the 'execute' function
492 with the filname coming in, and the temporary filename. After
493 That executing, the SVG should be in the temporary file.
495 Finally, the temporary file is opened using the SVG input module and
496 a document is returned. That document has its filename set to
497 the incoming filename (so that it's not the temporary filename).
498 That document is then returned from this function.
499 */
500 SPDocument *Script::open(Inkscape::Extension::Input *module,
501 const gchar *filenameArg)
502 {
503 std::list<std::string> params;
504 module->paramListString(params);
506 std::string tempfilename_out;
507 int tempfd_out = 0;
508 try {
509 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
510 } catch (...) {
511 /// \todo Popup dialog here
512 return NULL;
513 }
515 std::string lfilename = Glib::filename_from_utf8(filenameArg);
517 file_listener fileout;
518 int data_read = execute(command, params, lfilename, fileout);
519 fileout.toFile(tempfilename_out);
521 SPDocument * mydoc = NULL;
522 if (data_read > 10) {
523 if (helper_extension.size()==0) {
524 mydoc = Inkscape::Extension::open(
525 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
526 tempfilename_out.c_str());
527 } else {
528 mydoc = Inkscape::Extension::open(
529 Inkscape::Extension::db.get(helper_extension.c_str()),
530 tempfilename_out.c_str());
531 }
532 } // data_read
534 if (mydoc != NULL) {
535 g_free(mydoc->base);
536 mydoc->base = NULL;
537 sp_document_change_uri_and_hrefs(mydoc, 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 execute(command, params, tempfilename_in, fileout);
607 std::string lfilename = Glib::filename_from_utf8(filenameArg);
608 bool success = fileout.toFile(lfilename);
610 // make sure we don't leak file descriptors from g_file_open_tmp
611 close(tempfd_in);
612 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
613 unlink(tempfilename_in.c_str());
615 if (success == false) {
616 throw Inkscape::Extension::Output::save_failed();
617 }
619 return;
620 }
624 /**
625 \return none
626 \brief This function uses an extention as a effect on a document.
627 \param module Extention to effect with.
628 \param doc Document to run through the effect.
630 This function is a little bit trickier than the previous two. It
631 needs two temporary files to get it's work done. Both of these
632 files have random names created for them using the g_file_open_temp function
633 with the ink_ext_ prefix in the temporary directory. Like the other
634 functions, the temporary files are deleted at the end.
636 To save/load the two temporary documents (both are SVG) the internal
637 modules for SVG load and save are used. They are both used through
638 the module system function by passing their keys into the functions.
640 The command itself is built a little bit differently than in other
641 functions because the effect support selections. So on the command
642 line a list of all the ids that are selected is included. Currently,
643 this only works for a single selected object, but there will be more.
644 The command string is filled with the data, and then after the execution
645 it is freed.
647 The execute function is used at the core of this function
648 to execute the Script on the two SVG documents (actually only one
649 exists at the time, the other is created by that script). At that
650 point both should be full, and the second one is loaded.
651 */
652 void Script::effect(Inkscape::Extension::Effect *module,
653 Inkscape::UI::View::View *doc,
654 ImplementationDocumentCache * docCache)
655 {
656 if (docCache == NULL) {
657 docCache = newDocCache(module, doc);
658 }
659 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
660 if (dc == NULL) {
661 printf("TOO BAD TO LIVE!!!");
662 exit(1);
663 }
665 SPDesktop *desktop = (SPDesktop *)doc;
666 sp_namedview_document_from_window(desktop);
668 std::list<std::string> params;
669 module->paramListString(params);
671 if (module->no_doc) {
672 // this is a no-doc extension, e.g. a Help menu command;
673 // just run the command without any files, ignoring errors
675 Glib::ustring empty;
676 file_listener outfile;
677 execute(command, params, empty, outfile);
679 return;
680 }
682 std::string tempfilename_out;
683 int tempfd_out = 0;
684 try {
685 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
686 } catch (...) {
687 /// \todo Popup dialog here
688 return;
689 }
691 if (desktop != NULL) {
692 Inkscape::Util::GSListConstIterator<SPItem *> selected =
693 sp_desktop_selection(desktop)->itemList();
694 while ( selected != NULL ) {
695 Glib::ustring selected_id;
696 selected_id += "--id=";
697 selected_id += (*selected)->getId();
698 params.insert(params.begin(), selected_id);
699 ++selected;
700 }
701 }
703 file_listener fileout;
704 int data_read = execute(command, params, dc->_filename, fileout);
705 fileout.toFile(tempfilename_out);
707 pump_events();
709 SPDocument * mydoc = NULL;
710 if (data_read > 10) {
711 mydoc = Inkscape::Extension::open(
712 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
713 tempfilename_out.c_str());
714 } // data_read
716 pump_events();
718 // make sure we don't leak file descriptors from g_file_open_tmp
719 close(tempfd_out);
721 g_unlink(tempfilename_out.c_str());
723 /* Do something with mydoc.... */
724 if (mydoc) {
725 doc->doc()->emitReconstructionStart();
726 copy_doc(doc->doc()->rroot, mydoc->rroot);
727 doc->doc()->emitReconstructionFinish();
728 mydoc->release();
729 sp_namedview_update_layers_from_document(desktop);
730 }
732 return;
733 }
737 /**
738 \brief A function to take all the svg elements from one document
739 and put them in another.
740 \param oldroot The root node of the document to be replaced
741 \param newroot The root node of the document to replace it with
743 This function first deletes all of the data in the old document. It
744 does this by creating a list of what needs to be deleted, and then
745 goes through the list. This two pass approach removes issues with
746 the list being change while parsing through it. Lots of nasty bugs.
748 Then, it goes through the new document, duplicating all of the
749 elements and putting them into the old document. The copy
750 is then complete.
751 */
752 void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
753 {
754 std::vector<Inkscape::XML::Node *> delete_list;
755 Inkscape::XML::Node * oldroot_namedview = NULL;
757 for (Inkscape::XML::Node * child = oldroot->firstChild();
758 child != NULL;
759 child = child->next()) {
760 if (!strcmp("sodipodi:namedview", child->name())) {
761 oldroot_namedview = child;
762 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
763 oldroot_namedview_child != NULL;
764 oldroot_namedview_child = oldroot_namedview_child->next()) {
765 delete_list.push_back(oldroot_namedview_child);
766 }
767 } else {
768 delete_list.push_back(child);
769 }
770 }
771 for (unsigned int i = 0; i < delete_list.size(); i++) {
772 sp_repr_unparent(delete_list[i]);
773 }
775 for (Inkscape::XML::Node * child = newroot->firstChild();
776 child != NULL;
777 child = child->next()) {
778 if (!strcmp("sodipodi:namedview", child->name())) {
779 if (oldroot_namedview != NULL) {
780 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
781 newroot_namedview_child != NULL;
782 newroot_namedview_child = newroot_namedview_child->next()) {
783 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
784 }
785 }
786 } else {
787 oldroot->appendChild(child->duplicate(oldroot->document()));
788 }
789 }
791 {
792 using Inkscape::Util::List;
793 using Inkscape::XML::AttributeRecord;
794 std::vector<gchar const *> attribs;
796 // Make a list of all attributes of the old root node.
797 for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
798 attribs.push_back(g_quark_to_string(iter->key));
799 }
801 // Delete the attributes of the old root nodes.
802 for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++) {
803 oldroot->setAttribute(*it, NULL);
804 }
806 // Set the new attributes.
807 for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) {
808 gchar const *name = g_quark_to_string(iter->key);
809 oldroot->setAttribute(name, newroot->attribute(name));
810 }
811 }
813 /** \todo Restore correct layer */
814 /** \todo Restore correct selection */
815 }
817 /** \brief This function checks the stderr file, and if it has data,
818 shows it in a warning dialog to the user
819 \param filename Filename of the stderr file
820 */
821 void Script::checkStderr (const Glib::ustring &data,
822 Gtk::MessageType type,
823 const Glib::ustring &message)
824 {
825 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
826 warning.set_resizable(true);
827 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
828 sp_transientize(dlg);
830 Gtk::VBox * vbox = warning.get_vbox();
832 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
833 Gtk::TextView * textview = new Gtk::TextView();
834 textview->set_editable(false);
835 textview->set_wrap_mode(Gtk::WRAP_WORD);
836 textview->show();
838 textview->get_buffer()->set_text(data.c_str());
840 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
841 scrollwindow->add(*textview);
842 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
843 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
844 scrollwindow->show();
846 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
848 warning.run();
850 return;
851 }
853 bool Script::cancelProcessing (void) {
854 _canceled = true;
855 _main_loop->quit();
856 Glib::spawn_close_pid(_pid);
858 return true;
859 }
862 /** \brief This is the core of the extension file as it actually does
863 the execution of the extension.
864 \param in_command The command to be executed
865 \param filein Filename coming in
866 \param fileout Filename of the out file
867 \return Number of bytes that were read into the output file.
869 The first thing that this function does is build the command to be
870 executed. This consists of the first string (in_command) and then
871 the filename for input (filein). This file is put on the command
872 line.
874 The next thing is that this function does is open a pipe to the
875 command and get the file handle in the ppipe variable. It then
876 opens the output file with the output file handle. Both of these
877 operations are checked extensively for errors.
879 After both are opened, then the data is copied from the output
880 of the pipe into the file out using fread and fwrite. These two
881 functions are used because of their primitive nature they make
882 no assumptions about the data. A buffer is used in the transfer,
883 but the output of fread is stored so the exact number of bytes
884 is handled gracefully.
886 At the very end (after the data has been copied) both of the files
887 are closed, and we return to what we were doing.
888 */
889 int Script::execute (const std::list<std::string> &in_command,
890 const std::list<std::string> &in_params,
891 const Glib::ustring &filein,
892 file_listener &fileout)
893 {
894 g_return_val_if_fail(!in_command.empty(), 0);
895 // printf("Executing\n");
897 std::vector<std::string> argv;
899 bool interpreted = (in_command.size() == 2);
900 std::string program = in_command.front();
901 std::string script = interpreted ? in_command.back() : "";
902 std::string working_directory = "";
904 // Use Glib::find_program_in_path instead of the equivalent
905 // Glib::spawn_* functionality, because _wspawnp is broken on Windows:
906 // it doesn't work when PATH contains Unicode directories
907 if (!Glib::path_is_absolute(program)) {
908 program = Glib::find_program_in_path(program);
909 }
910 argv.push_back(program);
912 if (interpreted) {
913 // On Windows, Python garbles Unicode command line parameters
914 // in an useless way. This means extensions fail when Inkscape
915 // is run from an Unicode directory.
916 // As a workaround, we set the working directory to the one
917 // containing the script.
918 working_directory = Glib::path_get_dirname(script);
919 script = Glib::path_get_basename(script);
920 #ifdef G_OS_WIN32
921 // ANNOYING: glibmm does not wrap g_win32_locale_filename_from_utf8
922 gchar *workdir_s = g_win32_locale_filename_from_utf8(working_directory.data());
923 working_directory = workdir_s;
924 g_free(workdir_s);
925 #endif
927 argv.push_back(script);
928 }
930 // assemble the rest of argv
931 std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
932 if (!filein.empty()) {
933 argv.push_back(filein);
934 }
936 int stdout_pipe, stderr_pipe;
938 try {
939 Glib::spawn_async_with_pipes(working_directory, // working directory
940 argv, // arg v
941 static_cast<Glib::SpawnFlags>(0), // no flags
942 sigc::slot<void>(),
943 &_pid, // Pid
944 NULL, // STDIN
945 &stdout_pipe, // STDOUT
946 &stderr_pipe); // STDERR
947 } catch (Glib::Error e) {
948 printf("Can't Spawn!!! spawn returns: %s\n", e.what().data());
949 return 0;
950 }
952 _main_loop = Glib::MainLoop::create(false);
954 file_listener fileerr;
955 fileout.init(stdout_pipe, _main_loop);
956 fileerr.init(stderr_pipe, _main_loop);
958 _canceled = false;
959 _main_loop->run();
961 // Ensure all the data is out of the pipe
962 while (!fileout.isDead()) {
963 fileout.read(Glib::IO_IN);
964 }
965 while (!fileerr.isDead()) {
966 fileerr.read(Glib::IO_IN);
967 }
969 if (_canceled) {
970 // std::cout << "Script Canceled" << std::endl;
971 return 0;
972 }
974 Glib::ustring stderr_data = fileerr.string();
975 if (stderr_data.length() != 0 &&
976 Inkscape::NSApplication::Application::getUseGui()
977 ) {
978 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
979 _("Inkscape has received additional data from the script executed. "
980 "The script did not return an error, but this may indicate the results will not be as expected."));
981 }
983 Glib::ustring stdout_data = fileout.string();
984 if (stdout_data.length() == 0) {
985 return 0;
986 }
988 // std::cout << "Finishing Execution." << std::endl;
989 return stdout_data.length();
990 }
995 } // namespace Implementation
996 } // namespace Extension
997 } // namespace Inkscape
999 /*
1000 Local Variables:
1001 mode:c++
1002 c-file-style:"stroustrup"
1003 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1004 indent-tabs-mode:nil
1005 fill-column:99
1006 End:
1007 */
1008 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :