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 Authors
10 *
11 * Released under GNU GPL, read the file 'COPYING' for more information
12 */
14 /*
15 TODO:
16 FIXME:
17 After Inkscape makes a formal requirement for a GTK version above 2.11.4, please
18 replace all the instances of ink_ext_XXXXXX in this file that represent
19 svg files with ink_ext_XXXXXX.svg . Doing so will prevent errors in extensions
20 that call inkscape to manipulate the file.
22 "** (inkscape:5848): WARNING **: Format autodetect failed. The file is being opened as SVG."
24 references:
25 http://www.gtk.org/api/2.6/glib/glib-File-Utilities.html#g-mkstemp
26 http://ftp.gnome.org/pub/gnome/sources/glib/2.11/glib-2.11.4.changes
27 http://developer.gnome.org/doc/API/2.0/glib/glib-File-Utilities.html#g-mkstemp
29 --Aaron Spike
30 */
31 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
33 #ifdef HAVE_CONFIG_H
34 # include <config.h>
35 #endif
37 #include <unistd.h>
39 #include <errno.h>
40 #include <gtkmm.h>
42 #include "ui/view/view.h"
43 #include "desktop-handles.h"
44 #include "selection.h"
45 #include "sp-namedview.h"
46 #include "io/sys.h"
47 #include "prefs-utils.h"
48 #include "../system.h"
49 #include "extension/effect.h"
50 #include "extension/output.h"
51 #include "extension/db.h"
52 #include "script.h"
53 #include "dialogs/dialog-events.h"
55 #include "util/glib-list-iterators.h"
59 #ifdef WIN32
60 #include <windows.h>
61 #include <sys/stat.h>
62 #include "registrytool.h"
63 #endif
67 /** This is the command buffer that gets allocated from the stack */
68 #define BUFSIZE (255)
72 /* Namespaces */
73 namespace Inkscape {
74 namespace Extension {
75 namespace Implementation {
79 //Interpreter lookup table
80 struct interpreter_t {
81 gchar * identity;
82 gchar * prefstring;
83 gchar * defaultval;
84 };
87 /** \brief A table of what interpreters to call for a given language
89 This table is used to keep track of all the programs to execute a
90 given script. It also tracks the preference to use to overwrite
91 the given interpreter to a custom one per user.
92 */
93 static interpreter_t interpreterTab[] = {
94 {"perl", "perl-interpreter", "perl" },
95 {"python", "python-interpreter", "python" },
96 {"ruby", "ruby-interpreter", "ruby" },
97 {"shell", "shell-interpreter", "sh" },
98 { NULL, NULL, NULL }
99 };
103 /**
104 * Look up an interpreter name, and translate to something that
105 * is executable
106 */
107 static Glib::ustring
108 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
109 {
111 Glib::ustring interpName = interpNameArg;
113 interpreter_t *interp;
114 bool foundInterp = false;
115 for (interp = interpreterTab ; interp->identity ; interp++ ){
116 if (interpName == interp->identity) {
117 foundInterp = true;
118 break;
119 }
120 }
122 // Do we have a supported interpreter type?
123 if (!foundInterp)
124 return "";
125 interpName = interp->defaultval;
127 // 1. Check preferences
128 gchar *prefInterp = (gchar *)prefs_get_string_attribute(
129 "extensions", interp->prefstring);
131 if (prefInterp) {
132 interpName = prefInterp;
133 return interpName;
134 }
136 #ifdef _WIN32
138 // 2. Windows. Try looking relative to inkscape.exe
139 RegistryTool rt;
140 Glib::ustring fullPath;
141 Glib::ustring path;
142 Glib::ustring exeName;
143 if (rt.getExeInfo(fullPath, path, exeName)) {
144 Glib::ustring interpPath = path;
145 interpPath.append("\\");
146 interpPath.append(interpName);
147 interpPath.append("\\");
148 interpPath.append(interpName);
149 interpPath.append(".exe");
150 struct stat finfo;
151 if (stat(interpPath .c_str(), &finfo) ==0) {
152 g_message("Found local interpreter, '%s', Size: %d",
153 interpPath .c_str(),
154 (int)finfo.st_size);
155 return interpPath;
156 }
157 }
159 // 3. Try searching the path
160 char szExePath[MAX_PATH];
161 char szCurrentDir[MAX_PATH];
162 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
163 unsigned int ret = (unsigned int)FindExecutable(
164 interpName.c_str(), szCurrentDir, szExePath);
165 if (ret > 32) {
166 interpName = szExePath;
167 return interpName;
168 }
170 #endif // win32
173 return interpName;
174 }
177 class file_listener {
178 Glib::ustring _string;
179 sigc::connection _conn;
180 Glib::RefPtr<Glib::IOChannel> _channel;
181 Glib::RefPtr<Glib::MainLoop> _main_loop;
183 public:
184 file_listener () { };
185 ~file_listener () {
186 _conn.disconnect();
187 };
189 void init (int fd, Glib::RefPtr<Glib::MainLoop> main) {
190 _channel = Glib::IOChannel::create_from_fd(fd);
191 _channel->set_encoding();
192 _conn = Glib::signal_io().connect(sigc::mem_fun(*this, &file_listener::read), _channel, Glib::IO_IN | Glib::IO_HUP | Glib::IO_ERR);
193 _main_loop = main;
195 return;
196 };
198 bool read (Glib::IOCondition condition) {
199 if (condition != Glib::IO_IN) {
200 _main_loop->quit();
201 return false;
202 }
204 Glib::IOStatus status;
205 Glib::ustring out;
206 status = _channel->read_to_end(out);
208 if (status != Glib::IO_STATUS_NORMAL) {
209 _main_loop->quit();
210 return false;
211 }
213 _string += out;
214 return true;
215 };
217 // Note, doing a copy here, on purpose
218 Glib::ustring string (void) { return _string; };
220 void toFile (const Glib::ustring &name) {
221 Glib::RefPtr<Glib::IOChannel> stdout_file = Glib::IOChannel::create_from_file(name, "w");
222 stdout_file->write(_string);
223 return;
224 };
225 };
227 int execute (const std::list<std::string> &in_command,
228 const std::list<std::string> &in_params,
229 const Glib::ustring &filein,
230 file_listener &fileout);
231 void checkStderr (const Glib::ustring &data,
232 Gtk::MessageType type,
233 const Glib::ustring &message);
236 /** \brief This function creates a script object and sets up the
237 variables.
238 \return A script object
240 This function just sets the command to NULL. It should get built
241 officially in the load function. This allows for less allocation
242 of memory in the unloaded state.
243 */
244 Script::Script() :
245 Implementation()
246 {
247 }
250 /**
251 * brief Destructor
252 */
253 Script::~Script()
254 {
255 }
259 /**
260 \return A string with the complete string with the relative directory expanded
261 \brief This function takes in a Repr that contains a reldir entry
262 and returns that data with the relative directory expanded.
263 Mostly it is here so that relative directories all get used
264 the same way.
265 \param reprin The Inkscape::XML::Node with the reldir in it.
267 Basically this function looks at an attribute of the Repr, and makes
268 a decision based on that. Currently, it is only working with the
269 'extensions' relative directory, but there will be more of them.
270 One thing to notice is that this function always returns an allocated
271 string. This means that the caller of this function can always
272 free what they are given (and should do it too!).
273 */
274 Glib::ustring
275 Script::solve_reldir(Inkscape::XML::Node *reprin) {
277 gchar const *s = reprin->attribute("reldir");
279 if (!s) {
280 Glib::ustring str = sp_repr_children(reprin)->content();
281 return str;
282 }
284 Glib::ustring reldir = s;
286 if (reldir == "extensions") {
288 for (unsigned int i=0;
289 i < Inkscape::Extension::Extension::search_path.size();
290 i++) {
292 gchar * fname = g_build_filename(
293 Inkscape::Extension::Extension::search_path[i],
294 sp_repr_children(reprin)->content(),
295 NULL);
296 Glib::ustring filename = fname;
297 g_free(fname);
299 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
300 return filename;
302 }
303 } else {
304 Glib::ustring str = sp_repr_children(reprin)->content();
305 return str;
306 }
308 return "";
309 }
313 /**
314 \return Whether the command given exists, including in the path
315 \brief This function is used to find out if something exists for
316 the check command. It can look in the path if required.
317 \param command The command or file that should be looked for
319 The first thing that this function does is check to see if the
320 incoming file name has a directory delimiter in it. This would
321 mean that it wants to control the directories, and should be
322 used directly.
324 If not, the path is used. Each entry in the path is stepped through,
325 attached to the string, and then tested. If the file is found
326 then a TRUE is returned. If we get all the way through the path
327 then a FALSE is returned, the command could not be found.
328 */
329 bool
330 Script::check_existance(const Glib::ustring &command)
331 {
333 // Check the simple case first
334 if (command.size() == 0) {
335 return false;
336 }
338 //Don't search when it contains a slash. */
339 if (command.find(G_DIR_SEPARATOR) != command.npos) {
340 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
341 return true;
342 else
343 return false;
344 }
347 Glib::ustring path;
348 gchar *s = (gchar *) g_getenv("PATH");
349 if (s)
350 path = s;
351 else
352 /* There is no `PATH' in the environment.
353 The default search path is the current directory */
354 path = G_SEARCHPATH_SEPARATOR_S;
356 std::string::size_type pos = 0;
357 std::string::size_type pos2 = 0;
358 while ( pos < path.size() ) {
360 Glib::ustring localPath;
362 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
363 if (pos2 == path.npos) {
364 localPath = path.substr(pos);
365 pos = path.size();
366 } else {
367 localPath = path.substr(pos, pos2-pos);
368 pos = pos2+1;
369 }
371 //printf("### %s\n", localPath.c_str());
372 Glib::ustring candidatePath =
373 Glib::build_filename(localPath, command);
375 if (Inkscape::IO::file_test(candidatePath .c_str(),
376 G_FILE_TEST_EXISTS)) {
377 return true;
378 }
380 }
382 return false;
383 }
389 /**
390 \return none
391 \brief This function 'loads' an extention, basically it determines
392 the full command for the extention and stores that.
393 \param module The extention to be loaded.
395 The most difficult part about this function is finding the actual
396 command through all of the Reprs. Basically it is hidden down a
397 couple of layers, and so the code has to move down too. When
398 the command is actually found, it has its relative directory
399 solved.
401 At that point all of the loops are exited, and there is an
402 if statement to make sure they didn't exit because of not finding
403 the command. If that's the case, the extention doesn't get loaded
404 and should error out at a higher level.
405 */
407 bool
408 Script::load(Inkscape::Extension::Extension *module)
409 {
410 if (module->loaded())
411 return TRUE;
413 helper_extension = "";
415 /* This should probably check to find the executable... */
416 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
417 while (child_repr != NULL) {
418 if (!strcmp(child_repr->name(), "script")) {
419 child_repr = sp_repr_children(child_repr);
420 while (child_repr != NULL) {
421 if (!strcmp(child_repr->name(), "command")) {
422 const gchar *interpretstr = child_repr->attribute("interpreter");
423 if (interpretstr != NULL) {
424 Glib::ustring interpString =
425 resolveInterpreterExecutable(interpretstr);
426 command.insert(command.end(), interpretstr);
427 }
429 command.insert(command.end(), solve_reldir(child_repr));
430 }
431 if (!strcmp(child_repr->name(), "helper_extension")) {
432 helper_extension = sp_repr_children(child_repr)->content();
433 }
434 child_repr = sp_repr_next(child_repr);
435 }
437 break;
438 }
439 child_repr = sp_repr_next(child_repr);
440 }
442 //g_return_val_if_fail(command.length() > 0, FALSE);
444 return true;
445 }
448 /**
449 \return None.
450 \brief Unload this puppy!
451 \param module Extension to be unloaded.
453 This function just sets the module to unloaded. It free's the
454 command if it has been allocated.
455 */
456 void
457 Script::unload(Inkscape::Extension::Extension *module)
458 {
459 command.clear();
460 helper_extension = "";
461 }
466 /**
467 \return Whether the check passed or not
468 \brief Check every dependency that was given to make sure we should keep this extension
469 \param module The Extension in question
471 */
472 bool
473 Script::check(Inkscape::Extension::Extension *module)
474 {
475 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
476 while (child_repr != NULL) {
477 if (!strcmp(child_repr->name(), "script")) {
478 child_repr = sp_repr_children(child_repr);
479 while (child_repr != NULL) {
480 if (!strcmp(child_repr->name(), "check")) {
481 Glib::ustring command_text = solve_reldir(child_repr);
482 if (command_text.size() > 0) {
483 /* I've got the command */
484 bool existance = check_existance(command_text);
485 if (!existance)
486 return FALSE;
487 }
488 }
490 if (!strcmp(child_repr->name(), "helper_extension")) {
491 gchar const *helper = sp_repr_children(child_repr)->content();
492 if (Inkscape::Extension::db.get(helper) == NULL) {
493 return FALSE;
494 }
495 }
497 child_repr = sp_repr_next(child_repr);
498 }
500 break;
501 }
502 child_repr = sp_repr_next(child_repr);
503 }
505 return true;
506 }
510 /**
511 \return A dialog for preferences
512 \brief A stub funtion right now
513 \param module Module who's preferences need getting
514 \param filename Hey, the file you're getting might be important
516 This function should really do something, right now it doesn't.
517 */
518 Gtk::Widget *
519 Script::prefs_input(Inkscape::Extension::Input *module,
520 const gchar *filename)
521 {
522 /*return module->autogui(); */
523 return NULL;
524 }
528 /**
529 \return A dialog for preferences
530 \brief A stub funtion right now
531 \param module Module whose preferences need getting
533 This function should really do something, right now it doesn't.
534 */
535 Gtk::Widget *
536 Script::prefs_output(Inkscape::Extension::Output *module)
537 {
538 return module->autogui(NULL, NULL);
539 }
543 /**
544 \return A dialog for preferences
545 \brief A stub funtion right now
546 \param module Module who's preferences need getting
548 This function should really do something, right now it doesn't.
549 */
550 Gtk::Widget *
551 Script::prefs_effect(Inkscape::Extension::Effect *module,
552 Inkscape::UI::View::View *view)
553 {
555 SPDocument * current_document = view->doc();
557 using Inkscape::Util::GSListConstIterator;
558 GSListConstIterator<SPItem *> selected =
559 sp_desktop_selection((SPDesktop *)view)->itemList();
560 Inkscape::XML::Node * first_select = NULL;
561 if (selected != NULL) {
562 const SPItem * item = *selected;
563 first_select = SP_OBJECT_REPR(item);
564 }
566 return module->autogui(current_document, first_select);
567 }
572 /**
573 \return A new document that has been opened
574 \brief This function uses a filename that is put in, and calls
575 the extension's command to create an SVG file which is
576 returned.
577 \param module Extension to use.
578 \param filename File to open.
580 First things first, this function needs a temporary file name. To
581 create on of those the function g_file_open_tmp is used with
582 the header of ink_ext_.
584 The extension is then executed using the 'execute' function
585 with the filname coming in, and the temporary filename. After
586 That executing, the SVG should be in the temporary file.
588 Finally, the temporary file is opened using the SVG input module and
589 a document is returned. That document has its filename set to
590 the incoming filename (so that it's not the temporary filename).
591 That document is then returned from this function.
592 */
593 SPDocument *
594 Script::open(Inkscape::Extension::Input *module,
595 const gchar *filenameArg)
596 {
597 #if 0
598 Glib::ustring filename = filenameArg;
600 gchar *tmpname;
602 // FIXME: process the GError instead of passing NULL
603 gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
604 if (tempfd == -1) {
605 /* Error, couldn't create temporary filename */
606 if (errno == EINVAL) {
607 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
608 perror("Extension::Script: template for filenames is misconfigured.\n");
609 exit(-1);
610 } else if (errno == EEXIST) {
611 /* Now the contents of template are undefined. */
612 perror("Extension::Script: Could not create a unique temporary filename\n");
613 return NULL;
614 } else {
615 perror("Extension::Script: Unknown error creating temporary filename\n");
616 exit(-1);
617 }
618 }
620 Glib::ustring tempfilename_out = tmpname;
621 g_free(tmpname);
623 gsize bytesRead = 0;
624 gsize bytesWritten = 0;
625 GError *error = NULL;
626 Glib::ustring local_filename =
627 g_filename_from_utf8( filename.c_str(), -1,
628 &bytesRead, &bytesWritten, &error);
630 int data_read = execute(command, local_filename, tempfilename_out);
632 SPDocument *mydoc = NULL;
633 if (data_read > 10) {
634 if (helper_extension.size()==0) {
635 mydoc = Inkscape::Extension::open(
636 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
637 tempfilename_out.c_str());
638 } else {
639 mydoc = Inkscape::Extension::open(
640 Inkscape::Extension::db.get(helper_extension.c_str()),
641 tempfilename_out.c_str());
642 }
643 }
645 if (mydoc != NULL)
646 sp_document_set_uri(mydoc, (const gchar *)filename.c_str());
648 // make sure we don't leak file descriptors from g_file_open_tmp
649 close(tempfd);
650 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
651 unlink(tempfilename_out.c_str());
654 return mydoc;
655 #endif
656 }
660 /**
661 \return none
662 \brief This function uses an extention to save a document. It first
663 creates an SVG file of the document, and then runs it through
664 the script.
665 \param module Extention to be used
666 \param doc Document to be saved
667 \param filename The name to save the final file as
669 Well, at some point people need to save - it is really what makes
670 the entire application useful. And, it is possible that someone
671 would want to use an extetion for this, so we need a function to
672 do that eh?
674 First things first, the document is saved to a temporary file that
675 is an SVG file. To get the temporary filename g_file_open_tmp is used with
676 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
677 end of the function.
679 After we have the SVG file, then extention_execute is called with
680 the temporary file name and the final output filename. This should
681 put the output of the script into the final output file. We then
682 delete the temporary file.
683 */
684 void
685 Script::save(Inkscape::Extension::Output *module,
686 SPDocument *doc,
687 const gchar *filenameArg)
688 {
689 #if 0
690 Glib::ustring filename = filenameArg;
692 gchar *tmpname;
693 // FIXME: process the GError instead of passing NULL
694 gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
695 if (tempfd == -1) {
696 /* Error, couldn't create temporary filename */
697 if (errno == EINVAL) {
698 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
699 perror("Extension::Script: template for filenames is misconfigured.\n");
700 exit(-1);
701 } else if (errno == EEXIST) {
702 /* Now the contents of template are undefined. */
703 perror("Extension::Script: Could not create a unique temporary filename\n");
704 return;
705 } else {
706 perror("Extension::Script: Unknown error creating temporary filename\n");
707 exit(-1);
708 }
709 }
711 Glib::ustring tempfilename_in = tmpname;
712 g_free(tmpname);
714 if (helper_extension.size() == 0) {
715 Inkscape::Extension::save(
716 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
717 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
718 } else {
719 Inkscape::Extension::save(
720 Inkscape::Extension::db.get(helper_extension.c_str()),
721 doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
722 }
724 gsize bytesRead = 0;
725 gsize bytesWritten = 0;
726 GError *error = NULL;
727 Glib::ustring local_filename =
728 g_filename_from_utf8( filename.c_str(), -1,
729 &bytesRead, &bytesWritten, &error);
731 Glib::ustring local_command = command;
732 Glib::ustring paramString = *module->paramString();
733 local_command.append(paramString);
735 execute(local_command, tempfilename_in, local_filename);
738 // make sure we don't leak file descriptors from g_file_open_tmp
739 close(tempfd);
740 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
741 unlink(tempfilename_in.c_str());
742 #endif
743 }
747 /**
748 \return none
749 \brief This function uses an extention as a effect on a document.
750 \param module Extention to effect with.
751 \param doc Document to run through the effect.
753 This function is a little bit trickier than the previous two. It
754 needs two temporary files to get it's work done. Both of these
755 files have random names created for them using the g_file_open_temp function
756 with the ink_ext_ prefix in the temporary directory. Like the other
757 functions, the temporary files are deleted at the end.
759 To save/load the two temporary documents (both are SVG) the internal
760 modules for SVG load and save are used. They are both used through
761 the module system function by passing their keys into the functions.
763 The command itself is built a little bit differently than in other
764 functions because the effect support selections. So on the command
765 line a list of all the ids that are selected is included. Currently,
766 this only works for a single selected object, but there will be more.
767 The command string is filled with the data, and then after the execution
768 it is freed.
770 The execute function is used at the core of this function
771 to execute the Script on the two SVG documents (actually only one
772 exists at the time, the other is created by that script). At that
773 point both should be full, and the second one is loaded.
774 */
775 void
776 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
777 {
778 std::list<std::string> params;
780 if (module->no_doc) {
781 // this is a no-doc extension, e.g. a Help menu command;
782 // just run the command without any files, ignoring errors
783 module->paramListString(params);
785 Glib::ustring empty;
786 file_listener outfile;
787 execute(command, params, empty, outfile);
789 return;
790 }
792 gchar * dlgmessage = g_strdup_printf(_("The effect '%s' is working on your document. Please wait."), module->get_name());
794 Gtk::MessageDialog working(dlgmessage,
795 false, // use markup
796 Gtk::MESSAGE_INFO,
797 Gtk::BUTTONS_CANCEL,
798 true); // modal
799 g_free(dlgmessage);
800 working.show();
803 gchar *tmpname;
804 // FIXME: process the GError instead of passing NULL
805 gint tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
806 if (tempfd_in == -1) {
807 /* Error, couldn't create temporary filename */
808 if (errno == EINVAL) {
809 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
810 perror("Extension::Script: template for filenames is misconfigured.\n");
811 exit(-1);
812 } else if (errno == EEXIST) {
813 /* Now the contents of template are undefined. */
814 perror("Extension::Script: Could not create a unique temporary filename\n");
815 return;
816 } else {
817 perror("Extension::Script: Unknown error creating temporary filename\n");
818 exit(-1);
819 }
820 }
822 Glib::ustring tempfilename_in = tmpname;
823 g_free(tmpname);
826 // FIXME: process the GError instead of passing NULL
827 gint tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
828 if (tempfd_out == -1) {
829 /* Error, couldn't create temporary filename */
830 if (errno == EINVAL) {
831 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
832 perror("Extension::Script: template for filenames is misconfigured.\n");
833 exit(-1);
834 } else if (errno == EEXIST) {
835 /* Now the contents of template are undefined. */
836 perror("Extension::Script: Could not create a unique temporary filename\n");
837 return;
838 } else {
839 perror("Extension::Script: Unknown error creating temporary filename\n");
840 exit(-1);
841 }
842 }
844 Glib::ustring tempfilename_out= tmpname;
845 g_free(tmpname);
847 SPDesktop *desktop = (SPDesktop *) doc;
848 sp_namedview_document_from_window(desktop);
850 Inkscape::Extension::save(
851 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
852 doc->doc(), tempfilename_in.c_str(), FALSE, FALSE, FALSE);
854 if (desktop != NULL) {
855 Inkscape::Util::GSListConstIterator<SPItem *> selected =
856 sp_desktop_selection(desktop)->itemList();
857 while ( selected != NULL ) {
858 Glib::ustring selected_id;
859 selected_id += "--id=";
860 selected_id += SP_OBJECT_ID(*selected);
861 params.insert(params.begin(), selected_id);
862 ++selected;
863 }
864 }
866 file_listener fileout;
867 int data_read = execute(command, params, tempfilename_in, fileout);
868 fileout.toFile(tempfilename_out);
870 SPDocument * mydoc = NULL;
871 if (data_read > 10)
872 mydoc = Inkscape::Extension::open(
873 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
874 tempfilename_out.c_str());
876 // make sure we don't leak file descriptors from g_file_open_tmp
877 close(tempfd_in);
878 close(tempfd_out);
880 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
881 unlink(tempfilename_in.c_str());
882 unlink(tempfilename_out.c_str());
884 /* Do something with mydoc.... */
885 if (mydoc) {
886 doc->doc()->emitReconstructionStart();
887 copy_doc(doc->doc()->rroot, mydoc->rroot);
888 doc->doc()->emitReconstructionFinish();
889 mydoc->release();
890 sp_namedview_update_layers_from_document(desktop);
891 }
893 working.hide();
894 return;
895 }
899 /**
900 \brief A function to take all the svg elements from one document
901 and put them in another.
902 \param oldroot The root node of the document to be replaced
903 \param newroot The root node of the document to replace it with
905 This function first deletes all of the data in the old document. It
906 does this by creating a list of what needs to be deleted, and then
907 goes through the list. This two pass approach removes issues with
908 the list being change while parsing through it. Lots of nasty bugs.
910 Then, it goes through the new document, duplicating all of the
911 elements and putting them into the old document. The copy
912 is then complete.
913 */
914 void
915 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
916 {
917 std::vector<Inkscape::XML::Node *> delete_list;
918 for (Inkscape::XML::Node * child = oldroot->firstChild();
919 child != NULL;
920 child = child->next()) {
921 if (!strcmp("sodipodi:namedview", child->name()))
922 continue;
923 delete_list.push_back(child);
924 }
925 for (unsigned int i = 0; i < delete_list.size(); i++)
926 sp_repr_unparent(delete_list[i]);
928 for (Inkscape::XML::Node * child = newroot->firstChild();
929 child != NULL;
930 child = child->next()) {
931 if (!strcmp("sodipodi:namedview", child->name()))
932 continue;
933 oldroot->appendChild(child->duplicate(newroot->document()));
934 }
936 /** \todo Restore correct layer */
937 /** \todo Restore correct selection */
938 }
940 /** \brief This function checks the stderr file, and if it has data,
941 shows it in a warning dialog to the user
942 \param filename Filename of the stderr file
943 */
944 void
945 checkStderr (const Glib::ustring &data,
946 Gtk::MessageType type,
947 const Glib::ustring &message)
948 {
949 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
950 warning.set_resizable(true);
951 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
952 sp_transientize(dlg);
954 Gtk::VBox * vbox = warning.get_vbox();
956 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
957 Gtk::TextView * textview = new Gtk::TextView();
958 textview->set_editable(false);
959 textview->set_wrap_mode(Gtk::WRAP_WORD);
960 textview->show();
962 textview->get_buffer()->set_text(data.c_str());
964 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
965 scrollwindow->add(*textview);
966 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
967 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
968 scrollwindow->show();
970 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
972 warning.run();
974 return;
975 }
977 /** \brief This is the core of the extension file as it actually does
978 the execution of the extension.
979 \param in_command The command to be executed
980 \param filein Filename coming in
981 \param fileout Filename of the out file
982 \return Number of bytes that were read into the output file.
984 The first thing that this function does is build the command to be
985 executed. This consists of the first string (in_command) and then
986 the filename for input (filein). This file is put on the command
987 line.
989 The next thing is that this function does is open a pipe to the
990 command and get the file handle in the ppipe variable. It then
991 opens the output file with the output file handle. Both of these
992 operations are checked extensively for errors.
994 After both are opened, then the data is copied from the output
995 of the pipe into the file out using fread and fwrite. These two
996 functions are used because of their primitive nature they make
997 no assumptions about the data. A buffer is used in the transfer,
998 but the output of fread is stored so the exact number of bytes
999 is handled gracefully.
1001 At the very end (after the data has been copied) both of the files
1002 are closed, and we return to what we were doing.
1003 */
1004 int
1005 execute (const std::list<std::string> &in_command,
1006 const std::list<std::string> &in_params,
1007 const Glib::ustring &filein,
1008 file_listener &fileout)
1009 {
1010 g_return_val_if_fail(in_command.size() > 0, 0);
1011 // printf("Executing: %s\n", in_command);
1013 std::vector <std::string> argv;
1015 for (std::list<std::string>::const_iterator i = in_command.begin();
1016 i != in_command.end(); i++) {
1017 argv.push_back(*i);
1018 }
1020 if (!(filein.empty())) {
1021 argv.push_back(filein);
1022 }
1024 for (std::list<std::string>::const_iterator i = in_params.begin();
1025 i != in_params.end(); i++) {
1026 argv.push_back(*i);
1027 }
1029 /*
1030 for (std::vector<std::string>::const_iterator i = argv.begin();
1031 i != argv.end(); i++) {
1032 std::cout << *i << std::endl;
1033 }
1034 */
1036 Glib::Pid pid;
1037 int stdout_pipe, stderr_pipe;
1039 try {
1040 Glib::spawn_async_with_pipes(Glib::get_tmp_dir(), // working directory
1041 argv, // arg v
1042 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1043 sigc::slot<void>(),
1044 &pid, // Pid
1045 NULL, // STDIN
1046 &stdout_pipe, // STDOUT
1047 &stderr_pipe); // STDERR
1048 } catch (Glib::SpawnError e) {
1049 printf("Can't Spawn!!! %d\n", e.code());
1050 return 0;
1051 }
1053 Glib::RefPtr<Glib::MainLoop> main_loop = Glib::MainLoop::create(false);
1055 file_listener fileerr;
1056 fileout.init(stdout_pipe, main_loop);
1057 fileerr.init(stderr_pipe, main_loop);
1059 main_loop->run();
1061 Glib::ustring stderr_data = fileerr.string();
1062 if (stderr_data.length() != 0) {
1063 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1064 _("Inkscape has received additional data from the script executed. "
1065 "The script did not return an error, but this may indicate the results will not be as expected."));
1066 }
1068 Glib::ustring stdout_data = fileout.string();
1069 if (stdout_data.length() == 0) {
1070 return 0;
1071 }
1073 // std::cout << "Finishing Execution." << std::endl;
1074 return stdout_data.length();
1075 }
1080 } // namespace Implementation
1081 } // namespace Extension
1082 } // namespace Inkscape
1084 /*
1085 Local Variables:
1086 mode:c++
1087 c-file-style:"stroustrup"
1088 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1089 indent-tabs-mode:nil
1090 fill-column:99
1091 End:
1092 */
1093 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :