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 #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 "selection.h"
28 #include "sp-namedview.h"
29 #include "io/sys.h"
30 #include "prefs-utils.h"
31 #include "../system.h"
32 #include "extension/effect.h"
33 #include "extension/output.h"
34 #include "extension/db.h"
35 #include "script.h"
37 #include "util/glib-list-iterators.h"
41 #ifdef WIN32
42 #include <windows.h>
43 #include <sys/stat.h>
44 #include "registrytool.h"
45 #endif
49 /** This is the command buffer that gets allocated from the stack */
50 #define BUFSIZE (255)
54 /* Namespaces */
55 namespace Inkscape {
56 namespace Extension {
57 namespace Implementation {
61 //Interpreter lookup table
62 struct interpreter_t {
63 gchar * identity;
64 gchar * prefstring;
65 gchar * defaultval;
66 };
69 static interpreter_t interpreterTab[] = {
70 {"perl", "perl-interpreter", "perl" },
71 {"python", "python-interpreter", "python" },
72 {"ruby", "ruby-interpreter", "ruby" },
73 {"shell", "shell-interpreter", "sh" },
74 { NULL, NULL, NULL }
75 };
79 /**
80 * Look up an interpreter name, and translate to something that
81 * is executable
82 */
83 static Glib::ustring
84 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
85 {
87 Glib::ustring interpName = interpNameArg;
89 interpreter_t *interp;
90 bool foundInterp = false;
91 for (interp = interpreterTab ; interp->identity ; interp++ ){
92 if (interpName == interp->identity) {
93 foundInterp = true;
94 break;
95 }
96 }
98 // Do we have a supported interpreter type?
99 if (!foundInterp)
100 return "";
101 interpName = interp->defaultval;
103 // 1. Check preferences
104 gchar *prefInterp = (gchar *)prefs_get_string_attribute(
105 "extensions", interp->prefstring);
107 if (prefInterp) {
108 interpName = prefInterp;
109 return interpName;
110 }
112 #ifdef _WIN32
114 // 2. Windows. Try looking relative to inkscape.exe
115 RegistryTool rt;
116 Glib::ustring fullPath;
117 Glib::ustring path;
118 Glib::ustring exeName;
119 if (rt.getExeInfo(fullPath, path, exeName)) {
120 Glib::ustring interpPath = path;
121 interpPath.append("\\");
122 interpPath.append(interpName);
123 interpPath.append("\\");
124 interpPath.append(interpName);
125 interpPath.append(".exe");
126 struct stat finfo;
127 if (stat(interpPath .c_str(), &finfo) ==0) {
128 g_message("Found local interpreter, '%s', Size: %d",
129 interpPath .c_str(),
130 (int)finfo.st_size);
131 return interpPath;
132 }
133 }
135 // 3. Try searching the path
136 char szExePath[MAX_PATH];
137 char szCurrentDir[MAX_PATH];
138 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
139 unsigned int ret = (unsigned int)FindExecutable(
140 interpName.c_str(), szCurrentDir, szExePath);
141 if (ret > 32) {
142 interpName = szExePath;
143 return interpName;
144 }
146 #endif // win32
149 return interpName;
150 }
157 /**
158 \return A script object
159 \brief This function creates a script object and sets up the
160 variables.
162 This function just sets the command to NULL. It should get built
163 officially in the load function. This allows for less allocation
164 of memory in the unloaded state.
165 */
166 Script::Script() :
167 Implementation()
168 {
169 }
172 /**
173 * brief Destructor
174 */
175 Script::~Script()
176 {
177 }
181 /**
182 \return A string with the complete string with the relative directory expanded
183 \brief This function takes in a Repr that contains a reldir entry
184 and returns that data with the relative directory expanded.
185 Mostly it is here so that relative directories all get used
186 the same way.
187 \param reprin The Inkscape::XML::Node with the reldir in it.
189 Basically this function looks at an attribute of the Repr, and makes
190 a decision based on that. Currently, it is only working with the
191 'extensions' relative directory, but there will be more of them.
192 One thing to notice is that this function always returns an allocated
193 string. This means that the caller of this function can always
194 free what they are given (and should do it too!).
195 */
196 Glib::ustring
197 Script::solve_reldir(Inkscape::XML::Node *reprin) {
199 gchar const *s = reprin->attribute("reldir");
201 if (!s) {
202 Glib::ustring str = sp_repr_children(reprin)->content();
203 return str;
204 }
206 Glib::ustring reldir = s;
208 if (reldir == "extensions") {
210 for (unsigned int i=0;
211 i < Inkscape::Extension::Extension::search_path.size();
212 i++) {
214 gchar * fname = g_build_filename(
215 Inkscape::Extension::Extension::search_path[i],
216 sp_repr_children(reprin)->content(),
217 NULL);
218 Glib::ustring filename = fname;
219 g_free(fname);
221 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
222 return filename;
224 }
225 } else {
226 Glib::ustring str = sp_repr_children(reprin)->content();
227 return str;
228 }
230 return "";
231 }
235 /**
236 \return Whether the command given exists, including in the path
237 \brief This function is used to find out if something exists for
238 the check command. It can look in the path if required.
239 \param command The command or file that should be looked for
241 The first thing that this function does is check to see if the
242 incoming file name has a directory delimiter in it. This would
243 mean that it wants to control the directories, and should be
244 used directly.
246 If not, the path is used. Each entry in the path is stepped through,
247 attached to the string, and then tested. If the file is found
248 then a TRUE is returned. If we get all the way through the path
249 then a FALSE is returned, the command could not be found.
250 */
251 bool
252 Script::check_existance(const Glib::ustring &command)
253 {
255 // Check the simple case first
256 if (command.size() == 0) {
257 return false;
258 }
260 //Don't search when it contains a slash. */
261 if (command.find(G_DIR_SEPARATOR) != command.npos) {
262 if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
263 return true;
264 else
265 return false;
266 }
269 Glib::ustring path;
270 gchar *s = (gchar *) g_getenv("PATH");
271 if (s)
272 path = s;
273 else
274 /* There is no `PATH' in the environment.
275 The default search path is the current directory */
276 path = G_SEARCHPATH_SEPARATOR_S;
278 unsigned int pos = 0;
279 unsigned int pos2 = 0;
280 while ( pos < path.size() ) {
282 Glib::ustring localPath;
284 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
285 if (pos2 == path.npos) {
286 localPath = path.substr(pos);
287 pos = path.size();
288 } else {
289 localPath = path.substr(pos, pos2-pos);
290 pos = pos2+1;
291 }
293 //printf("### %s\n", localPath.c_str());
294 Glib::ustring candidatePath =
295 Glib::build_filename(localPath, command);
297 if (Inkscape::IO::file_test(candidatePath .c_str(),
298 G_FILE_TEST_EXISTS))
299 return true;
301 }
303 return false;
304 }
310 /**
311 \return none
312 \brief This function 'loads' an extention, basically it determines
313 the full command for the extention and stores that.
314 \param module The extention to be loaded.
316 The most difficult part about this function is finding the actual
317 command through all of the Reprs. Basically it is hidden down a
318 couple of layers, and so the code has to move down too. When
319 the command is actually found, it has its relative directory
320 solved.
322 At that point all of the loops are exited, and there is an
323 if statement to make sure they didn't exit because of not finding
324 the command. If that's the case, the extention doesn't get loaded
325 and should error out at a higher level.
326 */
328 bool
329 Script::load(Inkscape::Extension::Extension *module)
330 {
331 if (module->loaded())
332 return TRUE;
334 helper_extension = "";
336 /* This should probably check to find the executable... */
337 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
338 Glib::ustring command_text;
339 while (child_repr != NULL) {
340 if (!strcmp(child_repr->name(), "script")) {
341 child_repr = sp_repr_children(child_repr);
342 while (child_repr != NULL) {
343 if (!strcmp(child_repr->name(), "command")) {
344 command_text = solve_reldir(child_repr);
346 const gchar *interpretstr = child_repr->attribute("interpreter");
347 if (interpretstr != NULL) {
348 Glib::ustring interpString =
349 resolveInterpreterExecutable(interpretstr);
350 interpString .append(" ");
351 interpString .append(command_text);
352 command_text = interpString;
353 }
354 }
355 if (!strcmp(child_repr->name(), "helper_extension"))
356 helper_extension = sp_repr_children(child_repr)->content();
357 child_repr = sp_repr_next(child_repr);
358 }
360 break;
361 }
362 child_repr = sp_repr_next(child_repr);
363 }
365 g_return_val_if_fail(command_text.size() > 0, FALSE);
367 command = command_text;
368 return true;
369 }
372 /**
373 \return None.
374 \brief Unload this puppy!
375 \param module Extension to be unloaded.
377 This function just sets the module to unloaded. It free's the
378 command if it has been allocated.
379 */
380 void
381 Script::unload(Inkscape::Extension::Extension *module)
382 {
383 command = "";
384 helper_extension = "";
385 }
390 /**
391 \return Whether the check passed or not
392 \brief Check every dependency that was given to make sure we should keep this extension
393 \param module The Extension in question
395 */
396 bool
397 Script::check(Inkscape::Extension::Extension *module)
398 {
399 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
400 while (child_repr != NULL) {
401 if (!strcmp(child_repr->name(), "script")) {
402 child_repr = sp_repr_children(child_repr);
403 while (child_repr != NULL) {
404 if (!strcmp(child_repr->name(), "check")) {
405 Glib::ustring command_text = solve_reldir(child_repr);
406 if (command_text.size() > 0) {
407 /* I've got the command */
408 bool existance = check_existance(command_text);
409 if (!existance)
410 return FALSE;
411 }
412 }
414 if (!strcmp(child_repr->name(), "helper_extension")) {
415 gchar const *helper = sp_repr_children(child_repr)->content();
416 if (Inkscape::Extension::db.get(helper) == NULL) {
417 return FALSE;
418 }
419 }
421 child_repr = sp_repr_next(child_repr);
422 }
424 break;
425 }
426 child_repr = sp_repr_next(child_repr);
427 }
429 return true;
430 }
434 /**
435 \return A dialog for preferences
436 \brief A stub funtion right now
437 \param module Module who's preferences need getting
438 \param filename Hey, the file you're getting might be important
440 This function should really do something, right now it doesn't.
441 */
442 Gtk::Widget *
443 Script::prefs_input(Inkscape::Extension::Input *module,
444 const gchar *filename)
445 {
446 /*return module->autogui(); */
447 return NULL;
448 }
452 /**
453 \return A dialog for preferences
454 \brief A stub funtion right now
455 \param module Module whose preferences need getting
457 This function should really do something, right now it doesn't.
458 */
459 Gtk::Widget *
460 Script::prefs_output(Inkscape::Extension::Output *module)
461 {
462 return module->autogui(NULL, NULL);
463 }
467 /**
468 \return A dialog for preferences
469 \brief A stub funtion right now
470 \param module Module who's preferences need getting
472 This function should really do something, right now it doesn't.
473 */
474 Gtk::Widget *
475 Script::prefs_effect(Inkscape::Extension::Effect *module,
476 Inkscape::UI::View::View *view)
477 {
479 SPDocument * current_document = view->doc();
481 using Inkscape::Util::GSListConstIterator;
482 GSListConstIterator<SPItem *> selected =
483 sp_desktop_selection((SPDesktop *)view)->itemList();
484 Inkscape::XML::Node * first_select = NULL;
485 if (selected != NULL)
486 first_select = SP_OBJECT_REPR(*selected);
488 return module->autogui(current_document, first_select);
489 }
494 /**
495 \return A new document that has been opened
496 \brief This function uses a filename that is put in, and calls
497 the extension's command to create an SVG file which is
498 returned.
499 \param module Extension to use.
500 \param filename File to open.
502 First things first, this function needs a temporary file name. To
503 create on of those the function g_file_open_tmp is used with
504 the header of ink_ext_.
506 The extension is then executed using the 'execute' function
507 with the filname coming in, and the temporary filename. After
508 That executing, the SVG should be in the temporary file.
510 Finally, the temporary file is opened using the SVG input module and
511 a document is returned. That document has its filename set to
512 the incoming filename (so that it's not the temporary filename).
513 That document is then returned from this function.
514 */
515 SPDocument *
516 Script::open(Inkscape::Extension::Input *module,
517 const gchar *filenameArg)
518 {
520 Glib::ustring filename = filenameArg;
522 gchar *tmpname;
524 // FIXME: process the GError instead of passing NULL
525 gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
526 if (tempfd == -1) {
527 /* Error, couldn't create temporary filename */
528 if (errno == EINVAL) {
529 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
530 perror("Extension::Script: template for filenames is misconfigured.\n");
531 exit(-1);
532 } else if (errno == EEXIST) {
533 /* Now the contents of template are undefined. */
534 perror("Extension::Script: Could not create a unique temporary filename\n");
535 return NULL;
536 } else {
537 perror("Extension::Script: Unknown error creating temporary filename\n");
538 exit(-1);
539 }
540 }
542 Glib::ustring tempfilename_out = tmpname;
543 g_free(tmpname);
545 gsize bytesRead = 0;
546 gsize bytesWritten = 0;
547 GError *error = NULL;
548 Glib::ustring local_filename =
549 g_filename_from_utf8( filename.c_str(), -1,
550 &bytesRead, &bytesWritten, &error);
552 int data_read = execute(command, local_filename, tempfilename_out);
555 SPDocument *mydoc = NULL;
556 if (data_read > 10) {
557 if (helper_extension.size()==0) {
558 mydoc = Inkscape::Extension::open(
559 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
560 tempfilename_out.c_str());
561 } else {
562 mydoc = Inkscape::Extension::open(
563 Inkscape::Extension::db.get(helper_extension.c_str()),
564 tempfilename_out.c_str());
565 }
566 }
568 if (mydoc != NULL)
569 sp_document_set_uri(mydoc, (const gchar *)filename.c_str());
571 // make sure we don't leak file descriptors from g_file_open_tmp
572 close(tempfd);
573 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
574 unlink(tempfilename_out.c_str());
577 return mydoc;
578 }
582 /**
583 \return none
584 \brief This function uses an extention to save a document. It first
585 creates an SVG file of the document, and then runs it through
586 the script.
587 \param module Extention to be used
588 \param doc Document to be saved
589 \param filename The name to save the final file as
591 Well, at some point people need to save - it is really what makes
592 the entire application useful. And, it is possible that someone
593 would want to use an extetion for this, so we need a function to
594 do that eh?
596 First things first, the document is saved to a temporary file that
597 is an SVG file. To get the temporary filename g_file_open_tmp is used with
598 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
599 end of the function.
601 After we have the SVG file, then extention_execute is called with
602 the temporary file name and the final output filename. This should
603 put the output of the script into the final output file. We then
604 delete the temporary file.
605 */
606 void
607 Script::save(Inkscape::Extension::Output *module,
608 SPDocument *doc,
609 const gchar *filenameArg)
610 {
612 Glib::ustring filename = filenameArg;
614 gchar *tmpname;
615 // FIXME: process the GError instead of passing NULL
616 gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
617 if (tempfd == -1) {
618 /* Error, couldn't create temporary filename */
619 if (errno == EINVAL) {
620 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
621 perror("Extension::Script: template for filenames is misconfigured.\n");
622 exit(-1);
623 } else if (errno == EEXIST) {
624 /* Now the contents of template are undefined. */
625 perror("Extension::Script: Could not create a unique temporary filename\n");
626 return;
627 } else {
628 perror("Extension::Script: Unknown error creating temporary filename\n");
629 exit(-1);
630 }
631 }
633 Glib::ustring tempfilename_in = tmpname;
634 g_free(tmpname);
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 }
646 gsize bytesRead = 0;
647 gsize bytesWritten = 0;
648 GError *error = NULL;
649 Glib::ustring local_filename =
650 g_filename_from_utf8( filename.c_str(), -1,
651 &bytesRead, &bytesWritten, &error);
653 Glib::ustring local_command = command;
654 Glib::ustring paramString = *module->paramString();
655 local_command.append(paramString);
657 execute(local_command, tempfilename_in, local_filename);
660 // make sure we don't leak file descriptors from g_file_open_tmp
661 close(tempfd);
662 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
663 unlink(tempfilename_in.c_str());
664 }
668 /**
669 \return none
670 \brief This function uses an extention as a effect on a document.
671 \param module Extention to effect with.
672 \param doc Document to run through the effect.
674 This function is a little bit trickier than the previous two. It
675 needs two temporary files to get it's work done. Both of these
676 files have random names created for them using the g_file_open_temp function
677 with the sp_ext_ prefix in the temporary directory. Like the other
678 functions, the temporary files are deleted at the end.
680 To save/load the two temporary documents (both are SVG) the internal
681 modules for SVG load and save are used. They are both used through
682 the module system function by passing their keys into the functions.
684 The command itself is built a little bit differently than in other
685 functions because the effect support selections. So on the command
686 line a list of all the ids that are selected is included. Currently,
687 this only works for a single selected object, but there will be more.
688 The command string is filled with the data, and then after the execution
689 it is freed.
691 The execute function is used at the core of this function
692 to execute the Script on the two SVG documents (actually only one
693 exists at the time, the other is created by that script). At that
694 point both should be full, and the second one is loaded.
695 */
696 void
697 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
698 {
699 SPDocument * mydoc = NULL;
701 gchar *tmpname;
702 // FIXME: process the GError instead of passing NULL
703 gint tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
704 if (tempfd_in == -1) {
705 /* Error, couldn't create temporary filename */
706 if (errno == EINVAL) {
707 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
708 perror("Extension::Script: template for filenames is misconfigured.\n");
709 exit(-1);
710 } else if (errno == EEXIST) {
711 /* Now the contents of template are undefined. */
712 perror("Extension::Script: Could not create a unique temporary filename\n");
713 return;
714 } else {
715 perror("Extension::Script: Unknown error creating temporary filename\n");
716 exit(-1);
717 }
718 }
720 Glib::ustring tempfilename_in = tmpname;
721 g_free(tmpname);
724 // FIXME: process the GError instead of passing NULL
725 gint tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
726 if (tempfd_out == -1) {
727 /* Error, couldn't create temporary filename */
728 if (errno == EINVAL) {
729 /* The last six characters of template were not XXXXXX. Now template is unchanged. */
730 perror("Extension::Script: template for filenames is misconfigured.\n");
731 exit(-1);
732 } else if (errno == EEXIST) {
733 /* Now the contents of template are undefined. */
734 perror("Extension::Script: Could not create a unique temporary filename\n");
735 return;
736 } else {
737 perror("Extension::Script: Unknown error creating temporary filename\n");
738 exit(-1);
739 }
740 }
742 Glib::ustring tempfilename_out= tmpname;
743 g_free(tmpname);
745 Inkscape::Extension::save(
746 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
747 doc->doc(), tempfilename_in.c_str(), FALSE, FALSE, FALSE);
749 Glib::ustring local_command(command);
751 /* fixme: Should be some sort of checking here. Don't know how to do this with structs instead
752 * of classes. */
753 SPDesktop *desktop = (SPDesktop *) doc;
754 if (desktop != NULL) {
755 Inkscape::Util::GSListConstIterator<SPItem *> selected =
756 sp_desktop_selection(desktop)->itemList();
757 while ( selected != NULL ) {
758 local_command += " --id=";
759 local_command += SP_OBJECT_ID(*selected);
760 ++selected;
761 }
762 }
764 Glib::ustring paramString = *module->paramString();
765 local_command.append(paramString);
768 // std::cout << local_command << std::endl;
770 int data_read = execute(local_command, tempfilename_in, tempfilename_out);
772 if (data_read > 10)
773 mydoc = Inkscape::Extension::open(
774 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
775 tempfilename_out.c_str());
777 // make sure we don't leak file descriptors from g_file_open_tmp
778 close(tempfd_in);
779 close(tempfd_out);
781 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
782 unlink(tempfilename_in.c_str());
783 unlink(tempfilename_out.c_str());
786 /* Do something with mydoc.... */
787 if (mydoc) {
788 doc->doc()->emitReconstructionStart();
789 copy_doc(doc->doc()->rroot, mydoc->rroot);
790 doc->doc()->emitReconstructionFinish();
791 mydoc->release();
792 }
793 }
797 /**
798 \brief A function to take all the svg elements from one document
799 and put them in another.
800 \param oldroot The root node of the document to be replaced
801 \param newroot The root node of the document to replace it with
803 This function first deletes all of the data in the old document. It
804 does this by creating a list of what needs to be deleted, and then
805 goes through the list. This two pass approach removes issues with
806 the list being change while parsing through it. Lots of nasty bugs.
808 Then, it goes through the new document, duplicating all of the
809 elements and putting them into the old document. The copy
810 is then complete.
811 */
812 void
813 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
814 {
815 std::vector<Inkscape::XML::Node *> delete_list;
816 for (Inkscape::XML::Node * child = oldroot->firstChild();
817 child != NULL;
818 child = child->next()) {
819 if (!strcmp("sodipodi:namedview", child->name()))
820 continue;
821 if (!strcmp("svg:defs", child->name()))
822 continue;
823 delete_list.push_back(child);
824 }
825 for (unsigned int i = 0; i < delete_list.size(); i++)
826 sp_repr_unparent(delete_list[i]);
828 for (Inkscape::XML::Node * child = newroot->firstChild();
829 child != NULL;
830 child = child->next()) {
831 if (!strcmp("sodipodi:namedview", child->name()))
832 continue;
833 if (!strcmp("svg:defs", child->name()))
834 continue;
835 oldroot->appendChild(child->duplicate());
836 }
838 /** \todo Restore correct layer */
839 /** \todo Restore correct selection */
840 }
844 /* Helper class used by Script::execute */
845 class pipe_t {
846 public:
847 /* These functions set errno if they return false.
848 I'm not sure whether that's a good idea or not, but it should be reasonably
849 straightforward to change it if needed. */
850 bool open(const Glib::ustring &command,
851 const Glib::ustring &errorFile,
852 int mode);
853 bool close();
855 /* These return the number of bytes read/written. */
856 size_t read(void *buffer, size_t size);
857 size_t write(void const *buffer, size_t size);
859 enum {
860 mode_read = 1 << 0,
861 mode_write = 1 << 1,
862 };
864 private:
865 #ifdef WIN32
866 /* This is used to translate win32 errors into errno errors.
867 It only recognizes a few win32 errors for the moment though. */
868 static int translate_error(DWORD err);
870 HANDLE hpipe;
871 #else
872 FILE *ppipe;
873 #endif
874 };
879 /**
880 \return none
881 \brief This is the core of the extension file as it actually does
882 the execution of the extension.
883 \param in_command The command to be executed
884 \param filein Filename coming in
885 \param fileout Filename of the out file
886 \return Number of bytes that were read into the output file.
888 The first thing that this function does is build the command to be
889 executed. This consists of the first string (in_command) and then
890 the filename for input (filein). This file is put on the command
891 line.
893 The next thing is that this function does is open a pipe to the
894 command and get the file handle in the ppipe variable. It then
895 opens the output file with the output file handle. Both of these
896 operations are checked extensively for errors.
898 After both are opened, then the data is copied from the output
899 of the pipe into the file out using fread and fwrite. These two
900 functions are used because of their primitive nature they make
901 no assumptions about the data. A buffer is used in the transfer,
902 but the output of fread is stored so the exact number of bytes
903 is handled gracefully.
905 At the very end (after the data has been copied) both of the files
906 are closed, and we return to what we were doing.
907 */
908 int
909 Script::execute (const Glib::ustring &in_command,
910 const Glib::ustring &filein,
911 const Glib::ustring &fileout)
912 {
913 g_return_val_if_fail(in_command.size() > 0, 0);
914 // printf("Executing: %s\n", in_command);
916 gchar *tmpname;
917 gint errorFileNum;
918 errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &tmpname, NULL);
919 if (errorFileNum != 0) {
920 close(errorFileNum);
921 } else {
922 g_free(tmpname);
923 }
925 Glib::ustring errorFile = tmpname;
926 g_free(tmpname);
928 Glib::ustring localCommand = in_command;
929 localCommand .append(" \"");
930 localCommand .append(filein);
931 localCommand .append("\"");
933 // std::cout << "Command to run: " << command << std::endl;
935 pipe_t pipe;
936 bool open_success = pipe.open((char *)localCommand.c_str(),
937 errorFile.c_str(),
938 pipe_t::mode_read);
940 /* Run script */
941 if (!open_success) {
942 /* Error - could not open pipe - check errno */
943 if (errno == EINVAL) {
944 perror("Extension::Script: Invalid mode argument in popen\n");
945 } else if (errno == ECHILD) {
946 perror("Extension::Script: Cannot obtain child extension status in popen\n");
947 } else {
948 perror("Extension::Script: Unknown error for popen\n");
949 }
950 return 0;
951 }
953 Inkscape::IO::dump_fopen_call(fileout.c_str(), "J");
954 FILE *pfile = Inkscape::IO::fopen_utf8name(fileout.c_str(), "w");
956 if (pfile == NULL) {
957 /* Error - could not open file */
958 if (errno == EINVAL) {
959 perror("Extension::Script: The mode provided to fopen was invalid\n");
960 } else {
961 perror("Extension::Script: Unknown error attempting to open temporary file\n");
962 }
963 return 0;
964 }
966 /* Copy pipe output to a temporary file */
967 int amount_read = 0;
968 char buf[BUFSIZE];
969 int num_read;
970 while ((num_read = pipe.read(buf, BUFSIZE)) != 0) {
971 amount_read += num_read;
972 fwrite(buf, 1, num_read, pfile);
973 }
975 /* Close file */
976 if (fclose(pfile) == EOF) {
977 if (errno == EBADF) {
978 perror("Extension::Script: The filedescriptor for the temporary file is invalid\n");
979 return 0;
980 } else {
981 perror("Extension::Script: Unknown error closing temporary file\n");
982 }
983 }
985 /* Close pipe */
986 if (!pipe.close()) {
987 if (errno == EINVAL) {
988 perror("Extension::Script: Invalid mode set for pclose\n");
989 } else if (errno == ECHILD) {
990 perror("Extension::Script: Could not obtain child status for pclose\n");
991 } else {
992 if (errorFile != NULL) {
993 checkStderr(errorFile, Gtk::MESSAGE_ERROR,
994 _("Inkscape has received an error from the script that it called. "
995 "The text returned with the error is included below. "
996 "Inkscape will continue working, but the action you requested has been cancelled."));
997 } else {
998 perror("Extension::Script: Unknown error for pclose\n");
999 }
1000 }
1001 /* Could be a lie, but if there is an error, we don't want
1002 * to count on what was read being good */
1003 amount_read = 0;
1004 } else {
1005 if (errorFile.size()>0) {
1006 checkStderr(errorFile, Gtk::MESSAGE_INFO,
1007 _("Inkscape has received additional data from the script executed. "
1008 "The script did not return an error, but this may indicate the results will not be as expected."));
1009 }
1010 }
1012 if (errorFile.size()>0) {
1013 unlink(errorFile.c_str());
1014 }
1016 return amount_read;
1017 }
1022 /** \brief This function checks the stderr file, and if it has data,
1023 shows it in a warning dialog to the user
1024 \param filename Filename of the stderr file
1025 */
1026 void
1027 Script::checkStderr (const Glib::ustring &filename,
1028 Gtk::MessageType type,
1029 const Glib::ustring &message)
1030 {
1032 // magic win32 crlf->lf conversion means the file length is not the same as
1033 // the text length, but luckily gtk will accept crlf in textviews so we can
1034 // just use binary mode
1035 std::ifstream stderrf (filename.c_str(), std::ios_base::in | std::ios_base::binary);
1036 if (!stderrf.is_open()) return;
1038 stderrf.seekg(0, std::ios::end);
1039 int length = stderrf.tellg();
1040 if (0 == length) return;
1041 stderrf.seekg(0, std::ios::beg);
1043 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
1044 warning.set_resizable(true);
1046 Gtk::VBox * vbox = warning.get_vbox();
1048 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
1049 Gtk::TextView * textview = new Gtk::TextView();
1050 textview->set_editable(false);
1051 textview->set_wrap_mode(Gtk::WRAP_WORD);
1052 textview->show();
1054 char * buffer = new char [length];
1055 stderrf.read(buffer, length);
1056 textview->get_buffer()->set_text(buffer, buffer + length);
1057 delete buffer;
1058 stderrf.close();
1060 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
1061 scrollwindow->add(*textview);
1062 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
1063 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
1064 scrollwindow->show();
1066 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
1068 warning.run();
1070 return;
1071 }
1076 #ifdef WIN32
1079 bool pipe_t::open(const Glib::ustring &command,
1080 const Glib::ustring &errorFile,
1081 int mode_p) {
1082 HANDLE pipe_write;
1084 //############### Create pipe
1085 SECURITY_ATTRIBUTES secattrs;
1086 ZeroMemory(&secattrs, sizeof(secattrs));
1087 secattrs.nLength = sizeof(secattrs);
1088 secattrs.lpSecurityDescriptor = 0;
1089 secattrs.bInheritHandle = TRUE;
1090 HANDLE t_pipe_read = 0;
1091 if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) {
1092 errno = translate_error(GetLastError());
1093 return false;
1094 }
1095 // This duplicate handle makes the read pipe uninheritable
1096 BOOL ret = DuplicateHandle(GetCurrentProcess(),
1097 t_pipe_read,
1098 GetCurrentProcess(),
1099 &hpipe, 0, FALSE,
1100 DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1101 if (!ret) {
1102 int en = translate_error(GetLastError());
1103 CloseHandle(t_pipe_read);
1104 CloseHandle(pipe_write);
1105 errno = en;
1106 return false;
1107 }
1109 //############### Open stderr file
1110 HANDLE hStdErrFile = CreateFile(errorFile.c_str(),
1111 GENERIC_WRITE,
1112 FILE_SHARE_READ | FILE_SHARE_WRITE,
1113 NULL, CREATE_ALWAYS, 0, NULL);
1114 HANDLE hInheritableStdErr;
1115 DuplicateHandle(GetCurrentProcess(),
1116 hStdErrFile,
1117 GetCurrentProcess(),
1118 &hInheritableStdErr,
1119 0,
1120 TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1122 //############### Create process
1123 PROCESS_INFORMATION procinfo;
1124 STARTUPINFO startupinfo;
1125 ZeroMemory(&procinfo, sizeof(procinfo));
1126 ZeroMemory(&startupinfo, sizeof(startupinfo));
1127 startupinfo.cb = sizeof(startupinfo);
1128 //startupinfo.lpReserved = 0;
1129 //startupinfo.lpDesktop = 0;
1130 //startupinfo.lpTitle = 0;
1131 startupinfo.dwFlags = STARTF_USESTDHANDLES;
1132 startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
1133 startupinfo.hStdOutput = pipe_write;
1134 startupinfo.hStdError = hInheritableStdErr;
1136 if ( !CreateProcess(NULL, (CHAR *)command.c_str(),
1137 NULL, NULL, TRUE,
1138 0, NULL, NULL,
1139 &startupinfo, &procinfo) ) {
1140 errno = translate_error(GetLastError());
1141 return false;
1142 }
1143 CloseHandle(procinfo.hThread);
1144 CloseHandle(procinfo.hProcess);
1146 // Close our copy of the write handle
1147 CloseHandle(hInheritableStdErr);
1148 CloseHandle(pipe_write);
1150 return true;
1151 }
1155 bool pipe_t::close() {
1156 BOOL retval = CloseHandle(hpipe);
1157 if ( !retval )
1158 errno = translate_error(GetLastError());
1159 return retval != FALSE;
1160 }
1162 size_t pipe_t::read(void *buffer, size_t size) {
1163 DWORD bytes_read = 0;
1164 ReadFile(hpipe, buffer, size, &bytes_read, 0);
1165 return bytes_read;
1166 }
1168 size_t pipe_t::write(void const *buffer, size_t size) {
1169 DWORD bytes_written = 0;
1170 WriteFile(hpipe, buffer, size, &bytes_written, 0);
1171 return bytes_written;
1172 }
1174 int pipe_t::translate_error(DWORD err) {
1175 switch (err) {
1176 case ERROR_FILE_NOT_FOUND:
1177 return ENOENT;
1178 case ERROR_INVALID_HANDLE:
1179 case ERROR_INVALID_PARAMETER:
1180 return EINVAL;
1181 default:
1182 return 0;
1183 }
1184 }
1187 #else // not Win32
1190 bool pipe_t::open(const Glib::ustring &command,
1191 const Glib::ustring &errorFile,
1192 int mode_p) {
1194 Glib::ustring popen_mode;
1196 if ( (mode_p & mode_read) != 0 )
1197 popen_mode.append("r");
1199 if ( (mode_p & mode_write) != 0 )
1200 popen_mode.append("w");
1202 // Get the commandline to be run
1203 Glib::ustring pipeStr = command;
1204 if (errorFile.size()>0) {
1205 pipeStr .append(" 2> ");
1206 pipeStr .append(errorFile);
1207 }
1209 ppipe = popen(pipeStr.c_str(), popen_mode.c_str());
1211 return ppipe != NULL;
1212 }
1215 bool pipe_t::close() {
1216 return fclose(ppipe) == 0;
1217 }
1220 size_t pipe_t::read(void *buffer, size_t size) {
1221 return fread(buffer, 1, size, ppipe);
1222 }
1225 size_t pipe_t::write(void const *buffer, size_t size) {
1226 return fwrite(buffer, 1, size, ppipe);
1227 }
1232 #endif // (Non-)Win32
1237 } // namespace Implementation
1238 } // namespace Extension
1239 } // namespace Inkscape
1244 /*
1245 Local Variables:
1246 mode:c++
1247 c-file-style:"stroustrup"
1248 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1249 indent-tabs-mode:nil
1250 fill-column:99
1251 End:
1252 */
1253 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :