9475b479623ed6e98865d48cd5baf03c8bf5a6e2
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 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
20 #include <unistd.h>
22 #include <errno.h>
23 #include <gtkmm.h>
25 #include "ui/view/view.h"
26 #include "desktop-handles.h"
27 #include "desktop.h"
28 #include "selection.h"
29 #include "sp-namedview.h"
30 #include "io/sys.h"
31 #include "preferences.h"
32 #include "../system.h"
33 #include "extension/effect.h"
34 #include "extension/output.h"
35 #include "extension/input.h"
36 #include "extension/db.h"
37 #include "script.h"
38 #include "dialogs/dialog-events.h"
39 #include "application/application.h"
41 #include "util/glib-list-iterators.h"
45 #ifdef WIN32
46 #include <windows.h>
47 #include <sys/stat.h>
48 #include "registrytool.h"
49 #endif
53 /** This is the command buffer that gets allocated from the stack */
54 #define BUFSIZE (255)
58 /* Namespaces */
59 namespace Inkscape {
60 namespace Extension {
61 namespace Implementation {
63 /** \brief Make GTK+ events continue to come through a little bit
65 This just keeps coming the events through so that we'll make the GUI
66 update and look pretty.
67 */
68 void
69 Script::pump_events (void) {
70 while( Gtk::Main::events_pending() )
71 Gtk::Main::iteration();
72 return;
73 }
76 /** \brief A table of what interpreters to call for a given language
78 This table is used to keep track of all the programs to execute a
79 given script. It also tracks the preference to use to overwrite
80 the given interpreter to a custom one per user.
81 */
82 Script::interpreter_t const Script::interpreterTab[] = {
83 {"perl", "perl-interpreter", "perl" },
84 #ifdef WIN32
85 {"python", "python-interpreter", "pythonw" },
86 #else
87 {"python", "python-interpreter", "python" },
88 #endif
89 {"ruby", "ruby-interpreter", "ruby" },
90 {"shell", "shell-interpreter", "sh" },
91 { NULL, NULL, NULL }
92 };
96 /** \brief Look up an interpreter name, and translate to something that
97 is executable
98 \param interpNameArg The name of the interpreter that we're looking
99 for, should be an entry in interpreterTab
100 */
101 Glib::ustring
102 Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
103 {
105 Glib::ustring interpName = interpNameArg;
107 interpreter_t const *interp;
108 bool foundInterp = false;
109 for (interp = interpreterTab ; interp->identity ; interp++ ){
110 if (interpName == interp->identity) {
111 foundInterp = true;
112 break;
113 }
114 }
116 // Do we have a supported interpreter type?
117 if (!foundInterp)
118 return "";
119 interpName = interp->defaultval;
121 // 1. Check preferences
122 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
123 Glib::ustring prefInterp = prefs->getString("extensions", interp->prefstring);
125 if (!prefInterp.empty()) {
126 interpName = prefInterp;
127 return interpName;
128 }
130 #ifdef WIN32
132 // 2. Windows. Try looking relative to inkscape.exe
133 RegistryTool rt;
134 Glib::ustring fullPath;
135 Glib::ustring path;
136 Glib::ustring exeName;
137 if (rt.getExeInfo(fullPath, path, exeName)) {
138 Glib::ustring interpPath = path;
139 interpPath.append("\\");
140 interpPath.append(interpNameArg);
141 interpPath.append("\\");
142 interpPath.append(interpName);
143 interpPath.append(".exe");
144 struct stat finfo;
145 if (stat(interpPath .c_str(), &finfo) ==0) {
146 g_message("Found local interpreter, '%s', Size: %d",
147 interpPath .c_str(),
148 (int)finfo.st_size);
149 return interpPath;
150 }
151 }
153 // 3. Try searching the path
154 char szExePath[MAX_PATH];
155 char szCurrentDir[MAX_PATH];
156 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
157 unsigned int ret = (unsigned int)FindExecutable(
158 interpName.c_str(), szCurrentDir, szExePath);
159 if (ret > 32) {
160 interpName = szExePath;
161 return interpName;
162 }
164 #endif // win32
167 return interpName;
168 }
170 /** \brief This function creates a script object and sets up the
171 variables.
172 \return A script object
174 This function just sets the command to NULL. It should get built
175 officially in the load function. This allows for less allocation
176 of memory in the unloaded state.
177 */
178 Script::Script() :
179 Implementation()
180 {
181 }
183 /**
184 * brief Destructor
185 */
186 Script::~Script()
187 {
188 }
192 /**
193 \return A string with the complete string with the relative directory expanded
194 \brief This function takes in a Repr that contains a reldir entry
195 and returns that data with the relative directory expanded.
196 Mostly it is here so that relative directories all get used
197 the same way.
198 \param reprin The Inkscape::XML::Node with the reldir in it.
200 Basically this function looks at an attribute of the Repr, and makes
201 a decision based on that. Currently, it is only working with the
202 'extensions' relative directory, but there will be more of them.
203 One thing to notice is that this function always returns an allocated
204 string. This means that the caller of this function can always
205 free what they are given (and should do it too!).
206 */
207 Glib::ustring
208 Script::solve_reldir(Inkscape::XML::Node *reprin) {
210 gchar const *s = reprin->attribute("reldir");
212 if (!s) {
213 Glib::ustring str = sp_repr_children(reprin)->content();
214 return str;
215 }
217 Glib::ustring reldir = s;
219 if (reldir == "extensions") {
221 for (unsigned int i=0;
222 i < Inkscape::Extension::Extension::search_path.size();
223 i++) {
225 gchar * fname = g_build_filename(
226 Inkscape::Extension::Extension::search_path[i],
227 sp_repr_children(reprin)->content(),
228 NULL);
229 Glib::ustring filename = fname;
230 g_free(fname);
232 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
233 return filename;
235 }
236 } else {
237 Glib::ustring str = sp_repr_children(reprin)->content();
238 return str;
239 }
241 return "";
242 }
246 /**
247 \return Whether the command given exists, including in the path
248 \brief This function is used to find out if something exists for
249 the check command. It can look in the path if required.
250 \param command The command or file that should be looked for
252 The first thing that this function does is check to see if the
253 incoming file name has a directory delimiter in it. This would
254 mean that it wants to control the directories, and should be
255 used directly.
257 If not, the path is used. Each entry in the path is stepped through,
258 attached to the string, and then tested. If the file is found
259 then a TRUE is returned. If we get all the way through the path
260 then a FALSE is returned, the command could not be found.
261 */
262 bool
263 Script::check_existance(const Glib::ustring &command)
264 {
266 // Check the simple case first
267 if (command.size() == 0) {
268 return false;
269 }
271 //Don't search when it contains a slash. */
272 if (command.find(G_DIR_SEPARATOR) != command.npos) {
273 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
274 return true;
275 else
276 return false;
277 }
280 Glib::ustring path;
281 gchar *s = (gchar *) g_getenv("PATH");
282 if (s)
283 path = s;
284 else
285 /* There is no `PATH' in the environment.
286 The default search path is the current directory */
287 path = G_SEARCHPATH_SEPARATOR_S;
289 std::string::size_type pos = 0;
290 std::string::size_type pos2 = 0;
291 while ( pos < path.size() ) {
293 Glib::ustring localPath;
295 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
296 if (pos2 == path.npos) {
297 localPath = path.substr(pos);
298 pos = path.size();
299 } else {
300 localPath = path.substr(pos, pos2-pos);
301 pos = pos2+1;
302 }
304 //printf("### %s\n", localPath.c_str());
305 Glib::ustring candidatePath =
306 Glib::build_filename(localPath, command);
308 if (Inkscape::IO::file_test(candidatePath .c_str(),
309 G_FILE_TEST_EXISTS)) {
310 return true;
311 }
313 }
315 return false;
316 }
322 /**
323 \return none
324 \brief This function 'loads' an extention, basically it determines
325 the full command for the extention and stores that.
326 \param module The extention to be loaded.
328 The most difficult part about this function is finding the actual
329 command through all of the Reprs. Basically it is hidden down a
330 couple of layers, and so the code has to move down too. When
331 the command is actually found, it has its relative directory
332 solved.
334 At that point all of the loops are exited, and there is an
335 if statement to make sure they didn't exit because of not finding
336 the command. If that's the case, the extention doesn't get loaded
337 and should error out at a higher level.
338 */
340 bool
341 Script::load(Inkscape::Extension::Extension *module)
342 {
343 if (module->loaded())
344 return true;
346 helper_extension = "";
348 /* This should probably check to find the executable... */
349 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
350 while (child_repr != NULL) {
351 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
352 child_repr = sp_repr_children(child_repr);
353 while (child_repr != NULL) {
354 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
355 const gchar *interpretstr = child_repr->attribute("interpreter");
356 if (interpretstr != NULL) {
357 Glib::ustring interpString =
358 resolveInterpreterExecutable(interpretstr);
359 //g_message("Found: %s and %s",interpString.c_str(),interpretstr);
360 command.insert(command.end(), interpretstr);
361 }
362 Glib::ustring tmp = "\"";
363 tmp += solve_reldir(child_repr);
364 tmp += "\"";
366 command.insert(command.end(), tmp);
367 }
368 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
369 helper_extension = sp_repr_children(child_repr)->content();
370 }
371 child_repr = sp_repr_next(child_repr);
372 }
374 break;
375 }
376 child_repr = sp_repr_next(child_repr);
377 }
379 //g_return_val_if_fail(command.length() > 0, false);
381 return true;
382 }
385 /**
386 \return None.
387 \brief Unload this puppy!
388 \param module Extension to be unloaded.
390 This function just sets the module to unloaded. It free's the
391 command if it has been allocated.
392 */
393 void
394 Script::unload(Inkscape::Extension::Extension */*module*/)
395 {
396 command.clear();
397 helper_extension = "";
398 }
403 /**
404 \return Whether the check passed or not
405 \brief Check every dependency that was given to make sure we should keep this extension
406 \param module The Extension in question
408 */
409 bool
410 Script::check(Inkscape::Extension::Extension *module)
411 {
412 int script_count = 0;
413 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
414 while (child_repr != NULL) {
415 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
416 script_count++;
417 child_repr = sp_repr_children(child_repr);
418 while (child_repr != NULL) {
419 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
420 Glib::ustring command_text = solve_reldir(child_repr);
421 if (command_text.size() > 0) {
422 /* I've got the command */
423 bool existance = check_existance(command_text);
424 if (!existance)
425 return false;
426 }
427 }
429 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
430 gchar const *helper = sp_repr_children(child_repr)->content();
431 if (Inkscape::Extension::db.get(helper) == NULL) {
432 return false;
433 }
434 }
436 child_repr = sp_repr_next(child_repr);
437 }
439 break;
440 }
441 child_repr = sp_repr_next(child_repr);
442 }
444 if (script_count == 0) {
445 return false;
446 }
448 return true;
449 }
451 class ScriptDocCache : public ImplementationDocumentCache {
452 friend class Script;
453 protected:
454 std::string _filename;
455 int _tempfd;
456 public:
457 ScriptDocCache (Inkscape::UI::View::View * view);
458 ~ScriptDocCache ( );
459 };
461 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
462 ImplementationDocumentCache(view),
463 _filename(""),
464 _tempfd(0)
465 {
466 try {
467 _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
468 } catch (...) {
469 /// \todo Popup dialog here
470 return;
471 }
473 SPDesktop *desktop = (SPDesktop *) view;
474 sp_namedview_document_from_window(desktop);
476 Inkscape::Extension::save(
477 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
478 view->doc(), _filename.c_str(), false, false, false);
480 return;
481 }
483 ScriptDocCache::~ScriptDocCache ( )
484 {
485 close(_tempfd);
486 unlink(_filename.c_str());
487 }
489 ImplementationDocumentCache *
490 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
491 return new ScriptDocCache(view);
492 }
495 /**
496 \return A dialog for preferences
497 \brief A stub funtion right now
498 \param module Module who's preferences need getting
499 \param filename Hey, the file you're getting might be important
501 This function should really do something, right now it doesn't.
502 */
503 Gtk::Widget *
504 Script::prefs_input(Inkscape::Extension::Input *module,
505 const gchar */*filename*/)
506 {
507 return module->autogui(NULL, NULL);
508 }
512 /**
513 \return A dialog for preferences
514 \brief A stub funtion right now
515 \param module Module whose preferences need getting
517 This function should really do something, right now it doesn't.
518 */
519 Gtk::Widget *
520 Script::prefs_output(Inkscape::Extension::Output *module)
521 {
522 return module->autogui(NULL, NULL);
523 }
525 /**
526 \return A new document that has been opened
527 \brief This function uses a filename that is put in, and calls
528 the extension's command to create an SVG file which is
529 returned.
530 \param module Extension to use.
531 \param filename File to open.
533 First things first, this function needs a temporary file name. To
534 create on of those the function g_file_open_tmp is used with
535 the header of ink_ext_.
537 The extension is then executed using the 'execute' function
538 with the filname coming in, and the temporary filename. After
539 That executing, the SVG should be in the temporary file.
541 Finally, the temporary file is opened using the SVG input module and
542 a document is returned. That document has its filename set to
543 the incoming filename (so that it's not the temporary filename).
544 That document is then returned from this function.
545 */
546 SPDocument *
547 Script::open(Inkscape::Extension::Input *module,
548 const gchar *filenameArg)
549 {
550 std::list<std::string> params;
551 module->paramListString(params);
553 std::string tempfilename_out;
554 int tempfd_out = 0;
555 try {
556 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
557 } catch (...) {
558 /// \todo Popup dialog here
559 return NULL;
560 }
562 std::string lfilename = Glib::filename_from_utf8(filenameArg);
564 file_listener fileout;
565 int data_read = execute(command, params, lfilename, fileout);
566 fileout.toFile(tempfilename_out);
568 SPDocument * mydoc = NULL;
569 if (data_read > 10) {
570 if (helper_extension.size()==0) {
571 mydoc = Inkscape::Extension::open(
572 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
573 tempfilename_out.c_str());
574 } else {
575 mydoc = Inkscape::Extension::open(
576 Inkscape::Extension::db.get(helper_extension.c_str()),
577 tempfilename_out.c_str());
578 }
579 } // data_read
581 if (mydoc != NULL) {
582 sp_document_set_uri(mydoc, filenameArg);
583 }
585 // make sure we don't leak file descriptors from g_file_open_tmp
586 close(tempfd_out);
588 unlink(tempfilename_out.c_str());
590 return mydoc;
591 } // open
595 /**
596 \return none
597 \brief This function uses an extention to save a document. It first
598 creates an SVG file of the document, and then runs it through
599 the script.
600 \param module Extention to be used
601 \param doc Document to be saved
602 \param filename The name to save the final file as
604 Well, at some point people need to save - it is really what makes
605 the entire application useful. And, it is possible that someone
606 would want to use an extetion for this, so we need a function to
607 do that eh?
609 First things first, the document is saved to a temporary file that
610 is an SVG file. To get the temporary filename g_file_open_tmp is used with
611 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
612 end of the function.
614 After we have the SVG file, then extention_execute is called with
615 the temporary file name and the final output filename. This should
616 put the output of the script into the final output file. We then
617 delete the temporary file.
618 */
619 void
620 Script::save(Inkscape::Extension::Output *module,
621 SPDocument *doc,
622 const gchar *filenameArg)
623 {
624 std::list<std::string> params;
625 module->paramListString(params);
627 std::string tempfilename_in;
628 int tempfd_in = 0;
629 try {
630 tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
631 } catch (...) {
632 /// \todo Popup dialog here
633 return;
634 }
636 if (helper_extension.size() == 0) {
637 Inkscape::Extension::save(
638 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
639 doc, tempfilename_in.c_str(), false, false, false);
640 } else {
641 Inkscape::Extension::save(
642 Inkscape::Extension::db.get(helper_extension.c_str()),
643 doc, tempfilename_in.c_str(), false, false, false);
644 }
647 file_listener fileout;
648 execute(command, params, tempfilename_in, fileout);
650 std::string lfilename = Glib::filename_from_utf8(filenameArg);
651 fileout.toFile(lfilename);
653 // make sure we don't leak file descriptors from g_file_open_tmp
654 close(tempfd_in);
655 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
656 unlink(tempfilename_in.c_str());
658 return;
659 }
663 /**
664 \return none
665 \brief This function uses an extention as a effect on a document.
666 \param module Extention to effect with.
667 \param doc Document to run through the effect.
669 This function is a little bit trickier than the previous two. It
670 needs two temporary files to get it's work done. Both of these
671 files have random names created for them using the g_file_open_temp function
672 with the ink_ext_ prefix in the temporary directory. Like the other
673 functions, the temporary files are deleted at the end.
675 To save/load the two temporary documents (both are SVG) the internal
676 modules for SVG load and save are used. They are both used through
677 the module system function by passing their keys into the functions.
679 The command itself is built a little bit differently than in other
680 functions because the effect support selections. So on the command
681 line a list of all the ids that are selected is included. Currently,
682 this only works for a single selected object, but there will be more.
683 The command string is filled with the data, and then after the execution
684 it is freed.
686 The execute function is used at the core of this function
687 to execute the Script on the two SVG documents (actually only one
688 exists at the time, the other is created by that script). At that
689 point both should be full, and the second one is loaded.
690 */
691 void
692 Script::effect(Inkscape::Extension::Effect *module,
693 Inkscape::UI::View::View *doc,
694 ImplementationDocumentCache * docCache)
695 {
696 if (docCache == NULL) {
697 docCache = newDocCache(module, doc);
698 }
699 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
700 if (dc == NULL) {
701 printf("TOO BAD TO LIVE!!!");
702 exit(1);
703 }
705 SPDesktop *desktop = (SPDesktop *)doc;
706 sp_namedview_document_from_window(desktop);
708 gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
710 std::list<std::string> params;
711 module->paramListString(params);
713 if (module->no_doc) {
714 // this is a no-doc extension, e.g. a Help menu command;
715 // just run the command without any files, ignoring errors
717 Glib::ustring empty;
718 file_listener outfile;
719 execute(command, params, empty, outfile);
721 return;
722 }
724 std::string tempfilename_out;
725 int tempfd_out = 0;
726 try {
727 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
728 } catch (...) {
729 /// \todo Popup dialog here
730 return;
731 }
733 if (desktop != NULL) {
734 Inkscape::Util::GSListConstIterator<SPItem *> selected =
735 sp_desktop_selection(desktop)->itemList();
736 while ( selected != NULL ) {
737 Glib::ustring selected_id;
738 selected_id += "--id=";
739 selected_id += SP_OBJECT_ID(*selected);
740 params.insert(params.begin(), selected_id);
741 ++selected;
742 }
743 }
745 file_listener fileout;
746 int data_read = execute(command, params, dc->_filename, fileout);
747 fileout.toFile(tempfilename_out);
749 pump_events();
751 SPDocument * mydoc = NULL;
752 if (data_read > 10) {
753 mydoc = Inkscape::Extension::open(
754 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
755 tempfilename_out.c_str());
756 } // data_read
758 pump_events();
760 // make sure we don't leak file descriptors from g_file_open_tmp
761 close(tempfd_out);
763 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
764 unlink(tempfilename_out.c_str());
766 /* Do something with mydoc.... */
767 if (mydoc) {
768 doc->doc()->emitReconstructionStart();
769 copy_doc(doc->doc()->rroot, mydoc->rroot);
770 doc->doc()->emitReconstructionFinish();
771 mydoc->release();
772 sp_namedview_update_layers_from_document(desktop);
774 sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
775 }
776 g_free(orig_output_extension);
778 return;
779 }
783 /**
784 \brief A function to take all the svg elements from one document
785 and put them in another.
786 \param oldroot The root node of the document to be replaced
787 \param newroot The root node of the document to replace it with
789 This function first deletes all of the data in the old document. It
790 does this by creating a list of what needs to be deleted, and then
791 goes through the list. This two pass approach removes issues with
792 the list being change while parsing through it. Lots of nasty bugs.
794 Then, it goes through the new document, duplicating all of the
795 elements and putting them into the old document. The copy
796 is then complete.
797 */
798 void
799 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
800 {
801 std::vector<Inkscape::XML::Node *> delete_list;
802 Inkscape::XML::Node * oldroot_namedview = NULL;
804 for (Inkscape::XML::Node * child = oldroot->firstChild();
805 child != NULL;
806 child = child->next()) {
807 if (!strcmp("sodipodi:namedview", child->name())) {
808 oldroot_namedview = child;
809 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
810 oldroot_namedview_child != NULL;
811 oldroot_namedview_child = oldroot_namedview_child->next()) {
812 delete_list.push_back(oldroot_namedview_child);
813 }
814 } else {
815 delete_list.push_back(child);
816 }
817 }
818 for (unsigned int i = 0; i < delete_list.size(); i++)
819 sp_repr_unparent(delete_list[i]);
821 for (Inkscape::XML::Node * child = newroot->firstChild();
822 child != NULL;
823 child = child->next()) {
824 if (!strcmp("sodipodi:namedview", child->name())) {
825 if (oldroot_namedview != NULL) {
826 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
827 newroot_namedview_child != NULL;
828 newroot_namedview_child = newroot_namedview_child->next()) {
829 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
830 }
831 }
832 } else {
833 oldroot->appendChild(child->duplicate(oldroot->document()));
834 }
835 }
837 oldroot->setAttribute("width", newroot->attribute("width"));
838 oldroot->setAttribute("height", newroot->attribute("height"));
840 /** \todo Restore correct layer */
841 /** \todo Restore correct selection */
842 }
844 /** \brief This function checks the stderr file, and if it has data,
845 shows it in a warning dialog to the user
846 \param filename Filename of the stderr file
847 */
848 void
849 Script::checkStderr (const Glib::ustring &data,
850 Gtk::MessageType type,
851 const Glib::ustring &message)
852 {
853 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
854 warning.set_resizable(true);
855 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
856 sp_transientize(dlg);
858 Gtk::VBox * vbox = warning.get_vbox();
860 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
861 Gtk::TextView * textview = new Gtk::TextView();
862 textview->set_editable(false);
863 textview->set_wrap_mode(Gtk::WRAP_WORD);
864 textview->show();
866 textview->get_buffer()->set_text(data.c_str());
868 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
869 scrollwindow->add(*textview);
870 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
871 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
872 scrollwindow->show();
874 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
876 warning.run();
878 return;
879 }
881 bool
882 Script::cancelProcessing (void) {
883 _canceled = true;
884 _main_loop->quit();
885 Glib::spawn_close_pid(_pid);
887 return true;
888 }
891 /** \brief This is the core of the extension file as it actually does
892 the execution of the extension.
893 \param in_command The command to be executed
894 \param filein Filename coming in
895 \param fileout Filename of the out file
896 \return Number of bytes that were read into the output file.
898 The first thing that this function does is build the command to be
899 executed. This consists of the first string (in_command) and then
900 the filename for input (filein). This file is put on the command
901 line.
903 The next thing is that this function does is open a pipe to the
904 command and get the file handle in the ppipe variable. It then
905 opens the output file with the output file handle. Both of these
906 operations are checked extensively for errors.
908 After both are opened, then the data is copied from the output
909 of the pipe into the file out using fread and fwrite. These two
910 functions are used because of their primitive nature they make
911 no assumptions about the data. A buffer is used in the transfer,
912 but the output of fread is stored so the exact number of bytes
913 is handled gracefully.
915 At the very end (after the data has been copied) both of the files
916 are closed, and we return to what we were doing.
917 */
918 int
919 Script::execute (const std::list<std::string> &in_command,
920 const std::list<std::string> &in_params,
921 const Glib::ustring &filein,
922 file_listener &fileout)
923 {
924 g_return_val_if_fail(in_command.size() > 0, 0);
925 // printf("Executing\n");
927 std::vector <std::string> argv;
929 /*
930 for (std::list<std::string>::const_iterator i = in_command.begin();
931 i != in_command.end(); i++) {
932 argv.push_back(*i);
933 }
934 */
935 // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
936 // we tokenize so that spwan does not need to quote over all params
937 for (std::list<std::string>::const_iterator i = in_command.begin();
938 i != in_command.end(); i++) {
939 std::string param_str = *i;
940 do {
941 //g_message("param: %s", param_str.c_str());
942 size_t first_space = param_str.find_first_of(' ');
943 size_t first_quote = param_str.find_first_of('"');
944 //std::cout << "first space " << first_space << std::endl;
945 //std::cout << "first quote " << first_quote << std::endl;
947 if((first_quote != std::string::npos) && (first_quote == 0)) {
948 size_t next_quote = param_str.find_first_of('"', first_quote + 1);
949 //std::cout << "next quote " << next_quote << std::endl;
951 if(next_quote != std::string::npos) {
952 //std::cout << "now split " << next_quote << std::endl;
953 //std::cout << "now split " << param_str.substr(1, next_quote - 1) << std::endl;
954 //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
955 std::string part_str = param_str.substr(1, next_quote - 1);
956 if(part_str.size() > 0)
957 argv.push_back(part_str);
958 param_str = param_str.substr(next_quote + 1);
960 }
961 else {
962 if(param_str.size() > 0)
963 argv.push_back(param_str);
964 param_str = "";
965 }
967 }
968 else if(first_space != std::string::npos) {
969 //std::cout << "now split " << first_space << std::endl;
970 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
971 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
972 std::string part_str = param_str.substr(0, first_space);
973 if(part_str.size() > 0)
974 argv.push_back(part_str);
975 param_str = param_str.substr(first_space + 1);
976 }
977 else {
978 if(param_str.size() > 0)
979 argv.push_back(param_str);
980 param_str = "";
981 }
982 } while(param_str.size() > 0);
983 }
985 for (std::list<std::string>::const_iterator i = in_params.begin();
986 i != in_params.end(); i++) {
987 //g_message("Script parameter: %s",(*i)g.c_str());
988 argv.push_back(*i);
989 }
991 if (!(filein.empty())) {
992 argv.push_back(filein);
993 }
995 int stdout_pipe, stderr_pipe;
997 try {
998 Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
999 argv, // arg v
1000 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1001 sigc::slot<void>(),
1002 &_pid, // Pid
1003 NULL, // STDIN
1004 &stdout_pipe, // STDOUT
1005 &stderr_pipe); // STDERR
1006 } catch (Glib::SpawnError e) {
1007 printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1008 return 0;
1009 }
1011 _main_loop = Glib::MainLoop::create(false);
1013 file_listener fileerr;
1014 fileout.init(stdout_pipe, _main_loop);
1015 fileerr.init(stderr_pipe, _main_loop);
1017 _canceled = false;
1018 _main_loop->run();
1020 // Ensure all the data is out of the pipe
1021 while (!fileout.isDead())
1022 fileout.read(Glib::IO_IN);
1023 while (!fileerr.isDead())
1024 fileerr.read(Glib::IO_IN);
1026 if (_canceled) {
1027 // std::cout << "Script Canceled" << std::endl;
1028 return 0;
1029 }
1031 Glib::ustring stderr_data = fileerr.string();
1032 if (stderr_data.length() != 0 &&
1033 Inkscape::NSApplication::Application::getUseGui()
1034 ) {
1035 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1036 _("Inkscape has received additional data from the script executed. "
1037 "The script did not return an error, but this may indicate the results will not be as expected."));
1038 }
1040 Glib::ustring stdout_data = fileout.string();
1041 if (stdout_data.length() == 0) {
1042 return 0;
1043 }
1045 // std::cout << "Finishing Execution." << std::endl;
1046 return stdout_data.length();
1047 }
1052 } // namespace Implementation
1053 } // namespace Extension
1054 } // namespace Inkscape
1056 /*
1057 Local Variables:
1058 mode:c++
1059 c-file-style:"stroustrup"
1060 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1061 indent-tabs-mode:nil
1062 fill-column:99
1063 End:
1064 */
1065 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :