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 printf("Interpreter name: %s\n", interpreter_path.data());
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 g_free(mydoc->base);
537 mydoc->base = NULL;
538 sp_document_change_uri_and_hrefs(mydoc, filenameArg);
539 }
541 // make sure we don't leak file descriptors from g_file_open_tmp
542 close(tempfd_out);
544 unlink(tempfilename_out.c_str());
546 return mydoc;
547 } // open
551 /**
552 \return none
553 \brief This function uses an extention to save a document. It first
554 creates an SVG file of the document, and then runs it through
555 the script.
556 \param module Extention to be used
557 \param doc Document to be saved
558 \param filename The name to save the final file as
559 \return false in case of any failure writing the file, otherwise true
561 Well, at some point people need to save - it is really what makes
562 the entire application useful. And, it is possible that someone
563 would want to use an extetion for this, so we need a function to
564 do that eh?
566 First things first, the document is saved to a temporary file that
567 is an SVG file. To get the temporary filename g_file_open_tmp is used with
568 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
569 end of the function.
571 After we have the SVG file, then extention_execute is called with
572 the temporary file name and the final output filename. This should
573 put the output of the script into the final output file. We then
574 delete the temporary file.
575 */
576 void Script::save(Inkscape::Extension::Output *module,
577 SPDocument *doc,
578 const gchar *filenameArg)
579 {
580 std::list<std::string> params;
581 module->paramListString(params);
583 std::string tempfilename_in;
584 int tempfd_in = 0;
585 try {
586 tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
587 } catch (...) {
588 /// \todo Popup dialog here
589 throw Inkscape::Extension::Output::save_failed();
590 }
592 if (helper_extension.size() == 0) {
593 Inkscape::Extension::save(
594 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
595 doc, tempfilename_in.c_str(), false, false, false,
596 Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
597 } else {
598 Inkscape::Extension::save(
599 Inkscape::Extension::db.get(helper_extension.c_str()),
600 doc, tempfilename_in.c_str(), false, false, false,
601 Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
602 }
605 file_listener fileout;
606 execute(command, params, tempfilename_in, fileout);
608 std::string lfilename = Glib::filename_from_utf8(filenameArg);
609 bool success = fileout.toFile(lfilename);
611 // make sure we don't leak file descriptors from g_file_open_tmp
612 close(tempfd_in);
613 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
614 unlink(tempfilename_in.c_str());
616 if (success == false) {
617 throw Inkscape::Extension::Output::save_failed();
618 }
620 return;
621 }
625 /**
626 \return none
627 \brief This function uses an extention as a effect on a document.
628 \param module Extention to effect with.
629 \param doc Document to run through the effect.
631 This function is a little bit trickier than the previous two. It
632 needs two temporary files to get it's work done. Both of these
633 files have random names created for them using the g_file_open_temp function
634 with the ink_ext_ prefix in the temporary directory. Like the other
635 functions, the temporary files are deleted at the end.
637 To save/load the two temporary documents (both are SVG) the internal
638 modules for SVG load and save are used. They are both used through
639 the module system function by passing their keys into the functions.
641 The command itself is built a little bit differently than in other
642 functions because the effect support selections. So on the command
643 line a list of all the ids that are selected is included. Currently,
644 this only works for a single selected object, but there will be more.
645 The command string is filled with the data, and then after the execution
646 it is freed.
648 The execute function is used at the core of this function
649 to execute the Script on the two SVG documents (actually only one
650 exists at the time, the other is created by that script). At that
651 point both should be full, and the second one is loaded.
652 */
653 void Script::effect(Inkscape::Extension::Effect *module,
654 Inkscape::UI::View::View *doc,
655 ImplementationDocumentCache * docCache)
656 {
657 if (docCache == NULL) {
658 docCache = newDocCache(module, doc);
659 }
660 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
661 if (dc == NULL) {
662 printf("TOO BAD TO LIVE!!!");
663 exit(1);
664 }
666 SPDesktop *desktop = (SPDesktop *)doc;
667 sp_namedview_document_from_window(desktop);
669 std::list<std::string> params;
670 module->paramListString(params);
672 if (module->no_doc) {
673 // this is a no-doc extension, e.g. a Help menu command;
674 // just run the command without any files, ignoring errors
676 Glib::ustring empty;
677 file_listener outfile;
678 execute(command, params, empty, outfile);
680 return;
681 }
683 std::string tempfilename_out;
684 int tempfd_out = 0;
685 try {
686 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
687 } catch (...) {
688 /// \todo Popup dialog here
689 return;
690 }
692 if (desktop != NULL) {
693 Inkscape::Util::GSListConstIterator<SPItem *> selected =
694 sp_desktop_selection(desktop)->itemList();
695 while ( selected != NULL ) {
696 Glib::ustring selected_id;
697 selected_id += "--id=";
698 selected_id += (*selected)->getId();
699 params.insert(params.begin(), selected_id);
700 ++selected;
701 }
702 }
704 file_listener fileout;
705 int data_read = execute(command, params, dc->_filename, fileout);
706 fileout.toFile(tempfilename_out);
708 pump_events();
710 SPDocument * mydoc = NULL;
711 if (data_read > 10) {
712 mydoc = Inkscape::Extension::open(
713 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
714 tempfilename_out.c_str());
715 } // data_read
717 pump_events();
719 // make sure we don't leak file descriptors from g_file_open_tmp
720 close(tempfd_out);
722 g_unlink(tempfilename_out.c_str());
724 /* Do something with mydoc.... */
725 if (mydoc) {
726 doc->doc()->emitReconstructionStart();
727 copy_doc(doc->doc()->rroot, mydoc->rroot);
728 doc->doc()->emitReconstructionFinish();
729 mydoc->release();
730 sp_namedview_update_layers_from_document(desktop);
731 }
733 return;
734 }
738 /**
739 \brief A function to take all the svg elements from one document
740 and put them in another.
741 \param oldroot The root node of the document to be replaced
742 \param newroot The root node of the document to replace it with
744 This function first deletes all of the data in the old document. It
745 does this by creating a list of what needs to be deleted, and then
746 goes through the list. This two pass approach removes issues with
747 the list being change while parsing through it. Lots of nasty bugs.
749 Then, it goes through the new document, duplicating all of the
750 elements and putting them into the old document. The copy
751 is then complete.
752 */
753 void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
754 {
755 std::vector<Inkscape::XML::Node *> delete_list;
756 Inkscape::XML::Node * oldroot_namedview = NULL;
758 for (Inkscape::XML::Node * child = oldroot->firstChild();
759 child != NULL;
760 child = child->next()) {
761 if (!strcmp("sodipodi:namedview", child->name())) {
762 oldroot_namedview = child;
763 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
764 oldroot_namedview_child != NULL;
765 oldroot_namedview_child = oldroot_namedview_child->next()) {
766 delete_list.push_back(oldroot_namedview_child);
767 }
768 } else {
769 delete_list.push_back(child);
770 }
771 }
772 for (unsigned int i = 0; i < delete_list.size(); i++) {
773 sp_repr_unparent(delete_list[i]);
774 }
776 for (Inkscape::XML::Node * child = newroot->firstChild();
777 child != NULL;
778 child = child->next()) {
779 if (!strcmp("sodipodi:namedview", child->name())) {
780 if (oldroot_namedview != NULL) {
781 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
782 newroot_namedview_child != NULL;
783 newroot_namedview_child = newroot_namedview_child->next()) {
784 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
785 }
786 }
787 } else {
788 oldroot->appendChild(child->duplicate(oldroot->document()));
789 }
790 }
792 {
793 using Inkscape::Util::List;
794 using Inkscape::XML::AttributeRecord;
795 std::vector<gchar const *> attribs;
797 // Make a list of all attributes of the old root node.
798 for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
799 attribs.push_back(g_quark_to_string(iter->key));
800 }
802 // Delete the attributes of the old root nodes.
803 for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++) {
804 oldroot->setAttribute(*it, NULL);
805 }
807 // Set the new attributes.
808 for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) {
809 gchar const *name = g_quark_to_string(iter->key);
810 oldroot->setAttribute(name, newroot->attribute(name));
811 }
812 }
814 /** \todo Restore correct layer */
815 /** \todo Restore correct selection */
816 }
818 /** \brief This function checks the stderr file, and if it has data,
819 shows it in a warning dialog to the user
820 \param filename Filename of the stderr file
821 */
822 void Script::checkStderr (const Glib::ustring &data,
823 Gtk::MessageType type,
824 const Glib::ustring &message)
825 {
826 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
827 warning.set_resizable(true);
828 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
829 sp_transientize(dlg);
831 Gtk::VBox * vbox = warning.get_vbox();
833 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
834 Gtk::TextView * textview = new Gtk::TextView();
835 textview->set_editable(false);
836 textview->set_wrap_mode(Gtk::WRAP_WORD);
837 textview->show();
839 textview->get_buffer()->set_text(data.c_str());
841 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
842 scrollwindow->add(*textview);
843 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
844 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
845 scrollwindow->show();
847 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
849 warning.run();
851 return;
852 }
854 bool Script::cancelProcessing (void) {
855 _canceled = true;
856 _main_loop->quit();
857 Glib::spawn_close_pid(_pid);
859 return true;
860 }
863 /** \brief This is the core of the extension file as it actually does
864 the execution of the extension.
865 \param in_command The command to be executed
866 \param filein Filename coming in
867 \param fileout Filename of the out file
868 \return Number of bytes that were read into the output file.
870 The first thing that this function does is build the command to be
871 executed. This consists of the first string (in_command) and then
872 the filename for input (filein). This file is put on the command
873 line.
875 The next thing is that this function does is open a pipe to the
876 command and get the file handle in the ppipe variable. It then
877 opens the output file with the output file handle. Both of these
878 operations are checked extensively for errors.
880 After both are opened, then the data is copied from the output
881 of the pipe into the file out using fread and fwrite. These two
882 functions are used because of their primitive nature they make
883 no assumptions about the data. A buffer is used in the transfer,
884 but the output of fread is stored so the exact number of bytes
885 is handled gracefully.
887 At the very end (after the data has been copied) both of the files
888 are closed, and we return to what we were doing.
889 */
890 int Script::execute (const std::list<std::string> &in_command,
891 const std::list<std::string> &in_params,
892 const Glib::ustring &filein,
893 file_listener &fileout)
894 {
895 g_return_val_if_fail(!in_command.empty(), 0);
896 // printf("Executing\n");
898 std::vector<std::string> argv;
900 bool interpreted = (in_command.size() == 2);
901 std::string program = in_command.front();
902 std::string script = interpreted ? in_command.back() : "";
903 std::string working_directory = "";
905 // Use Glib::find_program_in_path instead of the equivalent
906 // Glib::spawn_* functionality, because _wspawnp is broken on Windows:
907 // it doesn't work when PATH contains Unicode directories
908 if (!Glib::path_is_absolute(program)) {
909 program = Glib::find_program_in_path(program);
910 }
911 argv.push_back(program);
913 if (interpreted) {
914 // On Windows, Python garbles Unicode command line parameters
915 // in an useless way. This means extensions fail when Inkscape
916 // is run from an Unicode directory.
917 // As a workaround, we set the working directory to the one
918 // containing the script.
919 working_directory = Glib::path_get_dirname(script);
920 script = Glib::path_get_basename(script);
921 #ifdef G_OS_WIN32
922 // ANNOYING: glibmm does not wrap g_win32_locale_filename_from_utf8
923 gchar *workdir_s = g_win32_locale_filename_from_utf8(working_directory.data());
924 working_directory = workdir_s;
925 g_free(workdir_s);
926 #endif
928 argv.push_back(script);
929 }
931 // assemble the rest of argv
932 std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
933 if (!filein.empty()) {
934 argv.push_back(filein);
935 }
937 int stdout_pipe, stderr_pipe;
939 try {
940 Glib::spawn_async_with_pipes(working_directory, // working directory
941 argv, // arg v
942 static_cast<Glib::SpawnFlags>(0), // no flags
943 sigc::slot<void>(),
944 &_pid, // Pid
945 NULL, // STDIN
946 &stdout_pipe, // STDOUT
947 &stderr_pipe); // STDERR
948 } catch (Glib::Error e) {
949 printf("Can't Spawn!!! spawn returns: %s\n", e.what().data());
950 return 0;
951 }
953 _main_loop = Glib::MainLoop::create(false);
955 file_listener fileerr;
956 fileout.init(stdout_pipe, _main_loop);
957 fileerr.init(stderr_pipe, _main_loop);
959 _canceled = false;
960 _main_loop->run();
962 // Ensure all the data is out of the pipe
963 while (!fileout.isDead()) {
964 fileout.read(Glib::IO_IN);
965 }
966 while (!fileerr.isDead()) {
967 fileerr.read(Glib::IO_IN);
968 }
970 if (_canceled) {
971 // std::cout << "Script Canceled" << std::endl;
972 return 0;
973 }
975 Glib::ustring stderr_data = fileerr.string();
976 if (stderr_data.length() != 0 &&
977 Inkscape::NSApplication::Application::getUseGui()
978 ) {
979 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
980 _("Inkscape has received additional data from the script executed. "
981 "The script did not return an error, but this may indicate the results will not be as expected."));
982 }
984 Glib::ustring stdout_data = fileout.string();
985 if (stdout_data.length() == 0) {
986 return 0;
987 }
989 // std::cout << "Finishing Execution." << std::endl;
990 return stdout_data.length();
991 }
996 } // namespace Implementation
997 } // namespace Extension
998 } // namespace Inkscape
1000 /*
1001 Local Variables:
1002 mode:c++
1003 c-file-style:"stroustrup"
1004 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1005 indent-tabs-mode:nil
1006 fill-column:99
1007 End:
1008 */
1009 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :