sync with 0.8.11cvs29
[claws.git] / src / action.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2003 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gdk/gdkx.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <errno.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <signal.h>
37 #include <unistd.h>
38
39 #include "intl.h"
40 #include "utils.h"
41 #include "gtkutils.h"
42 #include "manage_window.h"
43 #include "mainwindow.h"
44 #include "prefs_common.h"
45 #include "alertpanel.h"
46 #include "inputdialog.h"
47 #include "action.h"
48 #include "compose.h"
49 #include "procmsg.h"
50 #include "gtkstext.h"
51 #include "mimeview.h"
52 #include "textview.h"
53
54 typedef struct _Children                Children;
55 typedef struct _ChildInfo               ChildInfo;
56 typedef struct _UserStringDialog        UserStringDialog;
57
58 struct _Children
59 {
60         GtkWidget       *dialog;
61         GtkWidget       *text;
62         GtkWidget       *input_entry;
63         GtkWidget       *input_hbox;
64         GtkWidget       *abort_btn;
65         GtkWidget       *close_btn;
66         GtkWidget       *scrolledwin;
67
68         gchar           *action;
69         GSList          *list;
70         gint             nb;
71         gint             open_in;
72         gboolean         output;
73 };
74
75 struct _ChildInfo
76 {
77         Children        *children;
78         gchar           *cmd;
79         guint            type;
80         pid_t            pid;
81         gint             chld_in;
82         gint             chld_out;
83         gint             chld_err;
84         gint             chld_status;
85         gint             tag_in;
86         gint             tag_out;
87         gint             tag_err;
88         gint             tag_status;
89         gint             new_out;
90         GString         *output;
91         GtkWidget       *text;
92         GdkFont         *msgfont;
93 };
94
95 static void action_update_menu          (GtkItemFactory *ifactory,
96                                          gchar          *branch_path,
97                                          gpointer        callback,
98                                          gpointer        data);
99 static void compose_actions_execute_cb  (Compose        *compose,
100                                          guint           action_nb,
101                                          GtkWidget      *widget);
102 static void mainwin_actions_execute_cb  (MainWindow     *mainwin,
103                                          guint           action_nb,
104                                          GtkWidget      *widget);
105 static void msgview_actions_execute_cb  (MessageView    *msgview,
106                                          guint           action_nb,
107                                          GtkWidget      *widget);
108 static void message_actions_execute     (MessageView    *msgview,
109                                          guint           action_nb,
110                                          GtkCTree       *ctree);
111
112 static gboolean execute_actions         (gchar          *action, 
113                                          GtkCTree       *ctree, 
114                                          GtkWidget      *text,
115                                          GdkFont        *msgfont,
116                                          gint            body_pos,
117                                          MimeView       *mimeview);
118
119 static gchar *parse_action_cmd          (gchar          *action,
120                                          MsgInfo        *msginfo,
121                                          GtkCTree       *ctree,
122                                          MimeView       *mimeview,
123                                          const gchar    *user_str,
124                                          const gchar    *user_hidden_str,
125                                          const gchar    *sel_str);
126 static gboolean parse_append_filename   (GString        *cmd,
127                                          MsgInfo        *msginfo);
128
129 static gboolean parse_append_msgpart    (GString        *cmd,
130                                          MsgInfo        *msginfo,
131                                          MimeView       *mimeview);
132
133 static ChildInfo *fork_child            (gchar          *cmd,
134                                          gint            action_type,
135                                          GtkWidget      *text,
136                                          GdkFont        *msgfont,
137                                          gint            body_pos,
138                                          Children       *children);
139
140 static gint wait_for_children           (Children       *children);
141
142 static void free_children               (Children       *children);
143
144 static void childinfo_close_pipes       (ChildInfo      *child_info);
145
146 static void create_io_dialog            (Children       *children);
147 static void update_io_dialog            (Children       *children);
148
149 static void hide_io_dialog_cb           (GtkWidget      *widget,
150                                          gpointer        data);
151 static gint io_dialog_key_pressed_cb    (GtkWidget      *widget,
152                                          GdkEventKey    *event,
153                                          gpointer        data);
154
155 static void catch_output                (gpointer                data,
156                                          gint                    source,
157                                          GdkInputCondition       cond);
158 static void catch_input                 (gpointer                data, 
159                                          gint                    source,
160                                          GdkInputCondition       cond);
161 static void catch_status                (gpointer                data,
162                                          gint                    source,
163                                          GdkInputCondition       cond);
164
165 static gchar *get_user_string           (const gchar    *action,
166                                          ActionType      type);
167
168
169 ActionType action_get_type(const gchar *action_str)
170 {
171         const gchar *p;
172         ActionType action_type = ACTION_NONE;
173
174         g_return_val_if_fail(action_str,  ACTION_ERROR);
175         g_return_val_if_fail(*action_str, ACTION_ERROR);
176
177         p = action_str;
178
179         if (p[0] == '|') {
180                 action_type |= ACTION_PIPE_IN;
181                 p++;
182         } else if (p[0] == '>') {
183                 action_type |= ACTION_USER_IN;
184                 p++;
185         } else if (p[0] == '*') {
186                 action_type |= ACTION_USER_HIDDEN_IN;
187                 p++;
188         }
189
190         if (p[0] == '\0')
191                 return ACTION_ERROR;
192
193         while (*p && action_type != ACTION_ERROR) {
194                 if (p[0] == '%') {
195                         switch (p[1]) {
196                         case 'f':
197                                 action_type |= ACTION_SINGLE;
198                                 break;
199                         case 'F':
200                                 action_type |= ACTION_MULTIPLE;
201                                 break;
202                         case 'p':
203                                 action_type |= ACTION_SINGLE;
204                                 break;
205                         case 's':
206                                 action_type |= ACTION_SELECTION_STR;
207                                 break;
208                         case 'u':
209                                 action_type |= ACTION_USER_STR;
210                                 break;
211                         case 'h':
212                                 action_type |= ACTION_USER_HIDDEN_STR;
213                                 break;
214                         default:
215                                 action_type = ACTION_ERROR;
216                                 break;
217                         }
218                 } else if (p[0] == '|') {
219                         if (p[1] == '\0')
220                                 action_type |= ACTION_PIPE_OUT;
221                 } else if (p[0] == '>') {
222                         if (p[1] == '\0')
223                                 action_type |= ACTION_INSERT;
224                 } else if (p[0] == '&') {
225                         if (p[1] == '\0')
226                                 action_type |= ACTION_ASYNC;
227                 }
228                 p++;
229         }
230
231         return action_type;
232 }
233
234 static gchar *parse_action_cmd(gchar *action, MsgInfo *msginfo,
235                                GtkCTree *ctree, MimeView *mimeview,
236                                const gchar *user_str,
237                                const gchar *user_hidden_str,
238                                const gchar *sel_str)
239 {
240         GString *cmd;
241         gchar *p;
242         GList *cur;
243         MsgInfo *msg;
244         
245         p = action;
246         
247         if (p[0] == '|' || p[0] == '>' || p[0] == '*')
248                 p++;
249
250         cmd = g_string_sized_new(strlen(action));
251
252         while (p[0] &&
253                !((p[0] == '|' || p[0] == '>' || p[0] == '&') && !p[1])) {
254                 if (p[0] == '%' && p[1]) {
255                         switch (p[1]) {
256                         case 'f':
257                                 if (!parse_append_filename(cmd, msginfo)) {
258                                         g_string_free(cmd, TRUE);
259                                         return NULL;
260                                 }
261                                 p++;
262                                 break;
263                         case 'F':
264                                 for (cur = GTK_CLIST(ctree)->selection;
265                                      cur != NULL; cur = cur->next) {
266                                         msg = gtk_ctree_node_get_row_data(ctree,
267                                               GTK_CTREE_NODE(cur->data));
268                                         if (!parse_append_filename(cmd, msg)) {
269                                                 g_string_free(cmd, TRUE);
270                                                 return NULL;
271                                         }
272                                         if (cur->next)
273                                                 g_string_append_c(cmd, ' ');
274                                 }
275                                 p++;
276                                 break;
277                         case 'p':
278                                 if (!parse_append_msgpart(cmd, msginfo,
279                                                           mimeview)) {
280                                         g_string_free(cmd, TRUE);
281                                         return NULL;
282                                 }
283                                 p++;
284                                 break;
285                         case 's':
286                                 if (sel_str)
287                                         g_string_append(cmd, sel_str);
288                                 p++;
289                                 break;
290                         case 'u':
291                                 if (user_str)
292                                         g_string_append(cmd, user_str);
293                                 p++;
294                                 break;
295                         case 'h':
296                                 if (user_hidden_str)
297                                         g_string_append(cmd, user_hidden_str);
298                                 p++;
299                                 break;
300                         default:
301                                 g_string_append_c(cmd, p[0]);
302                                 g_string_append_c(cmd, p[1]);
303                                 p++;
304                         }
305                 } else {
306                         g_string_append_c(cmd, p[0]);
307                 }
308                 p++;
309         }
310         if (cmd->len == 0) {
311                 g_string_free(cmd, TRUE);
312                 return NULL;
313         }
314
315         p = cmd->str;
316         g_string_free(cmd, FALSE);
317         return p;
318 }
319
320 static gboolean parse_append_filename(GString *cmd, MsgInfo *msginfo)
321 {
322         gchar *filename;
323
324         g_return_val_if_fail(msginfo, FALSE);
325
326         filename = procmsg_get_message_file(msginfo);
327
328         if (filename) {
329                 g_string_append(cmd, filename);
330                 g_free(filename);
331         } else {
332                 alertpanel_error(_("Could not get message file %d"),
333                                  msginfo->msgnum);
334                 return FALSE;
335         }
336
337         return TRUE;
338 }
339
340 static gboolean parse_append_msgpart(GString *cmd, MsgInfo *msginfo,
341                                      MimeView *mimeview)
342 {
343         gchar    *filename;
344         gchar    *partname;
345         MimeInfo *partinfo;
346         gint      ret;
347         FILE     *fp;
348
349         if (!mimeview) {
350 #if USE_GPGME
351                 if ((fp = procmsg_open_message_decrypted(msginfo, &partinfo))
352                     == NULL) {
353                         alertpanel_error(_("Could not get message file."));
354                         return FALSE;
355                 }
356 #else
357                 if ((fp = procmsg_open_message(msginfo)) == NULL) {
358                         alertpanel_error(_("Could not get message file."));
359                         return FALSE;
360                 }
361                 partinfo = procmime_scan_mime_header(fp);
362 #endif
363                 fclose(fp);
364                 if (!partinfo) {
365                         procmime_mimeinfo_free_all(partinfo);
366                         alertpanel_error(_("Could not get message part."));
367                         return FALSE;
368                 }
369                 filename = procmsg_get_message_file(msginfo);
370         } else {
371                 if (!mimeview->opened) {
372                         alertpanel_error(_("No message part selected."));
373                         return FALSE;
374                 }
375                 if (!mimeview->file) {
376                         alertpanel_error(_("No message file selected."));
377                         return FALSE;
378                 }
379                 partinfo = gtk_ctree_node_get_row_data
380                                 (GTK_CTREE(mimeview->ctree),
381                                  mimeview->opened);
382                 g_return_val_if_fail(partinfo != NULL, FALSE);
383                 filename = mimeview->file;
384         }
385         partname = procmime_get_tmp_file_name(partinfo);
386
387         ret = procmime_get_part(partname, filename, partinfo); 
388
389         if (!mimeview) {
390                 procmime_mimeinfo_free_all(partinfo);
391                 g_free(filename);
392         }
393
394         if (ret < 0) {
395                 alertpanel_error(_("Can't get part of multipart message"));
396                 g_free(partname);
397                 return FALSE;
398         }
399
400         g_string_append(cmd, partname);
401
402         g_free(partname);
403
404         return TRUE;
405 }
406
407 void actions_execute(gpointer data, 
408                      guint action_nb,
409                      GtkWidget *widget,
410                      gint source)
411 {
412         if (source == TOOLBAR_MAIN) 
413                 mainwin_actions_execute_cb((MainWindow*)data, action_nb, widget);
414         else if (source == TOOLBAR_COMPOSE)
415                 compose_actions_execute_cb((Compose*)data, action_nb, widget);
416         else if (source == TOOLBAR_MSGVIEW)
417                 msgview_actions_execute_cb((MessageView*)data, action_nb, widget);      
418 }
419
420 void action_update_mainwin_menu(GtkItemFactory *ifactory, MainWindow *mainwin)
421 {
422         action_update_menu(ifactory, "/Tools/Actions",
423                            mainwin_actions_execute_cb, mainwin);
424 }
425
426 void action_update_compose_menu(GtkItemFactory *ifactory, Compose *compose)
427 {
428         action_update_menu(ifactory, "/Tools/Actions",
429                            compose_actions_execute_cb, compose);
430 }
431
432 static void action_update_menu(GtkItemFactory *ifactory, gchar *branch_path,
433                                gpointer callback, gpointer data)
434 {
435         GtkWidget *menuitem;
436         gchar *menu_path;
437         GSList *cur;
438         gchar *action, *action_p;
439         GList *amenu;
440         GtkItemFactoryEntry ifentry = {NULL, NULL, NULL, 0, "<Branch>"};
441
442         ifentry.path = branch_path;
443         menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
444         g_return_if_fail(menuitem != NULL);
445
446         amenu = GTK_MENU_SHELL(menuitem)->children;
447         while (amenu != NULL) {
448                 GList *alist = amenu->next;
449                 gtk_widget_destroy(GTK_WIDGET(amenu->data));
450                 amenu = alist;
451         }
452
453         ifentry.accelerator     = NULL;
454         ifentry.callback_action = 0;
455         ifentry.callback        = callback;
456         ifentry.item_type       = NULL;
457
458         for (cur = prefs_common.actions_list; cur; cur = cur->next) {
459                 action   = g_strdup((gchar *)cur->data);
460                 action_p = strstr(action, ": ");
461                 if (action_p && action_p[2] &&
462                     action_get_type(&action_p[2]) != ACTION_ERROR) {
463                         action_p[0] = 0x00;
464                         menu_path = g_strdup_printf("%s/%s", branch_path,
465                                                     action);
466                         ifentry.path = menu_path;
467                         gtk_item_factory_create_item(ifactory, &ifentry, data,
468                                                      1);
469                         g_free(menu_path);
470                 }
471                 g_free(action);
472                 ifentry.callback_action++;
473         }
474 }
475
476 static void compose_actions_execute_cb(Compose *compose, guint action_nb,
477                                        GtkWidget *widget)
478 {
479         gchar *buf, *action;
480         guint action_type;
481
482         g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list));
483
484         buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb);
485         g_return_if_fail(buf != NULL);
486         action = strstr(buf, ": ");
487         g_return_if_fail(action != NULL);
488
489         /* Point to the beginning of the command-line */
490         action += 2;
491
492         action_type = action_get_type(action);
493         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE)) {
494                 alertpanel_warning
495                         (_("The selected action cannot be used in the compose window\n"
496                            "because it contains %%f, %%F or %%p."));
497                 return;
498         }
499
500         execute_actions(action, NULL, compose->text, NULL, 0, NULL);
501 }
502
503 static void mainwin_actions_execute_cb(MainWindow *mainwin, guint action_nb,
504                                        GtkWidget *widget)
505 {
506         message_actions_execute(mainwin->messageview, action_nb,
507                                 GTK_CTREE(mainwin->summaryview->ctree));
508 }
509
510 static void msgview_actions_execute_cb(MessageView *msgview, guint action_nb,
511                                        GtkWidget *widget)
512 {
513         message_actions_execute(msgview, action_nb, NULL);
514 }
515
516 static void message_actions_execute(MessageView *msgview, guint action_nb,
517                                     GtkCTree *ctree)
518 {
519         TextView *textview = NULL;
520         MimeView *mimeview = NULL;
521         gchar *buf;
522         gchar *action;
523         GtkWidget *text = NULL;
524         GdkFont *msgfont = NULL;
525         guint body_pos = 0;
526
527         g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list));
528
529         buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb);
530
531         g_return_if_fail(buf);
532         g_return_if_fail(action = strstr(buf, ": "));
533
534         /* Point to the beginning of the command-line */
535         action += 2;
536
537         switch (msgview->type) {
538         case MVIEW_TEXT:
539                 if (msgview->textview && msgview->textview->text)
540                         textview = msgview->textview;
541                 break;
542         case MVIEW_MIME:
543                 if (msgview->mimeview) {
544                         mimeview = msgview->mimeview;
545                         if (msgview->mimeview->type == MIMEVIEW_TEXT &&
546                             msgview->mimeview->textview &&
547                             msgview->mimeview->textview->text)
548                                 textview = msgview->mimeview->textview;
549                 }
550                 break;
551         }
552
553         if (textview) {
554                 text     = textview->text;
555                 msgfont  = textview->msgfont;
556                 body_pos = textview->body_pos;
557         }
558
559         execute_actions(action, ctree, text, msgfont, body_pos, mimeview);
560 }
561
562 static gboolean execute_actions(gchar *action, GtkCTree *ctree,
563                                 GtkWidget *text, GdkFont *msgfont,
564                                 gint body_pos, MimeView *mimeview)
565 {
566         GList *cur, *selection = NULL;
567         GSList *children_list = NULL;
568         gint is_ok  = TRUE;
569         gint selection_len = 0;
570         Children *children;
571         ChildInfo *child_info;
572         gint action_type;
573         MsgInfo *msginfo;
574         gchar *cmd;
575         gchar *sel_str = NULL;
576         gchar *user_str = NULL;
577         gchar *user_hidden_str = NULL;
578
579         g_return_val_if_fail(action && *action, FALSE);
580
581         action_type = action_get_type(action);
582
583         if (action_type == ACTION_ERROR)
584                 return FALSE;         /* ERR: syntax error */
585
586         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE) && 
587             !(ctree && GTK_CLIST(ctree)->selection))
588                 return FALSE;         /* ERR: file command without selection */
589
590         if (ctree) {
591                 selection = GTK_CLIST(ctree)->selection;
592                 selection_len = g_list_length(selection);
593         }
594
595         if (action_type & (ACTION_PIPE_OUT | ACTION_PIPE_IN | ACTION_INSERT)) {
596                 if (ctree && selection_len > 1)
597                         return FALSE; /* ERR: pipe + multiple selection */
598                 if (!text)
599                         return FALSE; /* ERR: pipe and no displayed text */
600         }
601
602         if (action_type & ACTION_SELECTION_STR) {
603                 if (!text)
604                         return FALSE; /* ERR: selection string but no text */
605                 else {
606                         guint start = 0, end = 0;
607                         if (GTK_EDITABLE(text)->has_selection) {
608                                 start = GTK_EDITABLE(text)->selection_start_pos;
609                                 end   = GTK_EDITABLE(text)->selection_end_pos;
610                                 if (start > end) {
611                                         guint tmp;
612                                         tmp = start;
613                                         start = end;
614                                         end = tmp;
615                                 }
616                         }
617                         sel_str = gtk_editable_get_chars(GTK_EDITABLE(text),
618                                                          start, end);
619                 }
620         }
621
622         if (action_type & (ACTION_USER_STR))
623                 if (!(user_str = get_user_string(action, ACTION_USER_STR)))
624                         return FALSE;
625
626         if (action_type & (ACTION_USER_HIDDEN_STR))
627                 if (!(user_hidden_str =
628                         get_user_string(action, ACTION_USER_HIDDEN_STR)))
629                         return FALSE;
630
631         children = g_new0(Children, 1);
632
633         if (action_type & ACTION_SINGLE) {
634                 for (cur = selection; cur && is_ok == TRUE; cur = cur->next) {
635                         msginfo = gtk_ctree_node_get_row_data(ctree,
636                                         GTK_CTREE_NODE(cur->data));
637                         if (!msginfo) {
638                                 is_ok  = FALSE; /* ERR: msginfo missing */
639                                 break;
640                         }
641                         cmd = parse_action_cmd(action, msginfo, ctree,
642                                                mimeview, user_str,
643                                                user_hidden_str, sel_str);
644                         if (!cmd) {
645                                 debug_print("Action command error\n");
646                                 is_ok  = FALSE; /* ERR: incorrect command */
647                                 break;
648                         }
649                         if ((child_info = fork_child(cmd, action_type, text,
650                                                      msgfont, body_pos,
651                                                      children))) {
652                                 children_list = g_slist_append(children_list,
653                                                                child_info);
654                                 children->open_in = (selection_len == 1) ?
655                                                     (action_type &
656                                                      (ACTION_USER_IN |
657                                                       ACTION_USER_HIDDEN_IN)) : 0;
658                         }
659                         g_free(cmd);
660                 }
661         } else {
662                 cmd = parse_action_cmd(action, NULL, ctree, mimeview, user_str,
663                                        user_hidden_str, sel_str);
664                 if (cmd) {
665                         if ((child_info = fork_child(cmd, action_type, text,
666                                                      msgfont, body_pos,
667                                                      children))) {
668                                 children_list = g_slist_append(children_list,
669                                                                child_info);
670                                 children->open_in = action_type &
671                                                     (ACTION_USER_IN |
672                                                      ACTION_USER_HIDDEN_IN);
673                         }
674                         g_free(cmd);
675                 } else
676                         is_ok  = FALSE;         /* ERR: incorrect command */
677         }
678
679         g_free(user_str);
680         g_free(user_hidden_str);
681
682         if (!children_list) {
683                  /* If not waiting for children, return */
684                 g_free(children);
685         } else {
686                 GSList *cur;
687
688                 children->action  = g_strdup(action);
689                 children->dialog  = NULL;
690                 children->list    = children_list;
691                 children->nb      = g_slist_length(children_list);
692
693                 for (cur = children_list; cur; cur = cur->next) {
694                         child_info = (ChildInfo *) cur->data;
695                         child_info->tag_status = 
696                                 gdk_input_add(child_info->chld_status,
697                                               GDK_INPUT_READ,
698                                               catch_status, child_info);
699                 }
700
701                 create_io_dialog(children);
702         }
703
704         return is_ok;
705 }
706
707 static ChildInfo *fork_child(gchar *cmd, gint action_type, GtkWidget *text,
708                              GdkFont *msgfont, gint body_pos,
709                              Children *children)
710 {
711         gint chld_in[2], chld_out[2], chld_err[2], chld_status[2];
712         gchar *cmdline[4];
713         guint start, end;
714         gint is_selection;
715         gchar *selection;
716         pid_t pid, gch_pid;
717         ChildInfo *child_info;
718         gint sync;
719
720         sync = !(action_type & ACTION_ASYNC);
721
722         chld_in[0] = chld_in[1] = chld_out[0] = chld_out[1] = chld_err[0]
723                 = chld_err[1] = chld_status[0] = chld_status[1] = -1;
724
725         if (sync) {
726                 if (pipe(chld_status) || pipe(chld_in) || pipe(chld_out) ||
727                     pipe(chld_err)) {
728                         alertpanel_error(_("Command could not be started. "
729                                            "Pipe creation failed.\n%s"),
730                                         g_strerror(errno));
731                         /* Closing fd = -1 fails silently */
732                         close(chld_in[0]);
733                         close(chld_in[1]);
734                         close(chld_out[0]);
735                         close(chld_out[1]);
736                         close(chld_err[0]);
737                         close(chld_err[1]);
738                         close(chld_status[0]);
739                         close(chld_status[1]);
740                         return NULL; /* Pipe error */
741                 }
742         }
743
744         debug_print("Forking child and grandchild.\n");
745
746         pid = fork();
747         if (pid == 0) { /* Child */
748                 if (setpgid(0, 0))
749                         perror("setpgid");
750
751                 close(ConnectionNumber(gdk_display));
752
753                 gch_pid = fork();
754
755                 if (gch_pid == 0) {
756                         if (setpgid(0, getppid()))
757                                 perror("setpgid");
758                         if (sync) {
759                                 if (action_type &
760                                     (ACTION_PIPE_IN |
761                                      ACTION_USER_IN |
762                                      ACTION_USER_HIDDEN_IN)) {
763                                         close(fileno(stdin));
764                                         dup  (chld_in[0]);
765                                 }
766                                 close(chld_in[0]);
767                                 close(chld_in[1]);
768
769                                 close(fileno(stdout));
770                                 dup  (chld_out[1]);
771                                 close(chld_out[0]);
772                                 close(chld_out[1]);
773
774                                 close(fileno(stderr));
775                                 dup  (chld_err[1]);
776                                 close(chld_err[0]);
777                                 close(chld_err[1]);
778                         }
779
780                         cmdline[0] = "sh";
781                         cmdline[1] = "-c";
782                         cmdline[2] = cmd;
783                         cmdline[3] = 0;
784                         execvp("/bin/sh", cmdline);
785
786                         perror("execvp");
787                         _exit(1);
788                 } else if (gch_pid < (pid_t) 0) { /* Fork error */
789                         if (sync)
790                                 write(chld_status[1], "1\n", 2);
791                         perror("fork");
792                         _exit(1);
793                 } else { /* Child */
794                         if (sync) {
795                                 close(chld_in[0]);
796                                 close(chld_in[1]);
797                                 close(chld_out[0]);
798                                 close(chld_out[1]);
799                                 close(chld_err[0]);
800                                 close(chld_err[1]);
801                                 close(chld_status[0]);
802                         }
803                         if (sync) {
804                                 debug_print("Child: Waiting for grandchild\n");
805                                 waitpid(gch_pid, NULL, 0);
806                                 debug_print("Child: grandchild ended\n");
807                                 write(chld_status[1], "0\n", 2);
808                                 close(chld_status[1]);
809                         }
810                         _exit(0);
811                 }
812         } else if (pid < 0) { /* Fork error */
813                 alertpanel_error(_("Could not fork to execute the following "
814                                    "command:\n%s\n%s"),
815                                  cmd, g_strerror(errno));
816                 return NULL; 
817         }
818
819         /* Parent */
820
821         if (!sync) {
822                 waitpid(pid, NULL, 0);
823                 return NULL;
824         }
825
826         close(chld_in[0]);
827         if (!(action_type & (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN)))
828                 close(chld_in[1]);
829         close(chld_out[1]);
830         close(chld_err[1]);
831         close(chld_status[1]);
832
833         child_info = g_new0(ChildInfo, 1);
834
835         child_info->children    = children;
836
837         child_info->pid         = pid;
838         child_info->cmd         = g_strdup(cmd);
839         child_info->type        = action_type;
840         child_info->new_out     = FALSE;
841         child_info->output      = g_string_new(NULL);
842         child_info->chld_in     =
843                 (action_type &
844                  (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN))
845                         ? chld_in [1] : -1;
846         child_info->chld_out    = chld_out[0];
847         child_info->chld_err    = chld_err[0];
848         child_info->chld_status = chld_status[0];
849         child_info->tag_in      = -1;
850         child_info->tag_out     = gdk_input_add(chld_out[0], GDK_INPUT_READ,
851                                                 catch_output, child_info);
852         child_info->tag_err     = gdk_input_add(chld_err[0], GDK_INPUT_READ,
853                                                 catch_output, child_info);
854
855         if (!(action_type & (ACTION_PIPE_IN | ACTION_PIPE_OUT | ACTION_INSERT)))
856                 return child_info;
857
858         child_info->text        = text;
859         child_info->msgfont     = msgfont;
860
861         start = body_pos;
862         end   = gtk_stext_get_length(GTK_STEXT(text));
863
864         if (GTK_EDITABLE(text)->has_selection) {
865                 start = GTK_EDITABLE(text)->selection_start_pos;
866                 end   = GTK_EDITABLE(text)->selection_end_pos;
867                 if (start > end) {
868                         guint tmp;
869                         tmp = start;
870                         start = end;
871                         end = tmp;
872                 }
873                 is_selection = TRUE;
874                 if (start == end) {
875                         start = 0;
876                         end = gtk_stext_get_length(GTK_STEXT(text));
877                         is_selection = FALSE;
878                 }
879         }
880
881         selection = gtk_editable_get_chars(GTK_EDITABLE(text), start, end);
882
883         if (action_type & ACTION_PIPE_IN) {
884                 write(chld_in[1], selection, strlen(selection));
885                 if (!(action_type & (ACTION_USER_IN | ACTION_USER_HIDDEN_IN)))
886                         close(chld_in[1]);
887                 child_info->chld_in = -1; /* No more input */
888         }
889         g_free(selection);
890
891         gtk_stext_freeze(GTK_STEXT(text));
892         if (action_type & ACTION_PIPE_OUT) {
893                 gtk_stext_set_point(GTK_STEXT(text), start);
894                 gtk_stext_forward_delete(GTK_STEXT(text), end - start);
895         }
896
897         gtk_stext_thaw(GTK_STEXT(text));
898
899         return child_info;
900 }
901
902 static void kill_children_cb(GtkWidget *widget, gpointer data)
903 {
904         GSList *cur;
905         Children *children = (Children *) data;
906         ChildInfo *child_info;
907
908         for (cur = children->list; cur; cur = cur->next) {
909                 child_info = (ChildInfo *)(cur->data);
910                 debug_print("Killing child group id %d\n", child_info->pid);
911                 if (child_info->pid && kill(-child_info->pid, SIGTERM) < 0)
912                         perror("kill");
913         }
914 }
915
916 static gint wait_for_children(Children *children)
917 {
918         gboolean new_output;
919         ChildInfo *child_info;
920         GSList *cur;
921         gint nb = children->nb;
922
923         children->nb = 0;
924
925         cur = children->list;
926         new_output = FALSE;
927         while (cur) {
928                 child_info = (ChildInfo *)cur->data;
929                 if (child_info->pid)
930                         children->nb++;
931                 new_output |= child_info->new_out;
932                 cur = cur->next;
933         }
934
935         children->output |= new_output;
936
937         if (new_output || (children->dialog && (nb != children->nb)))
938                 update_io_dialog(children);
939
940         if (children->nb)
941                 return FALSE;
942
943         if (!children->dialog) {
944                 free_children(children);
945         } else if (!children->output) {
946                 gtk_widget_destroy(children->dialog);
947         }
948
949         return FALSE;
950 }
951
952 static void send_input(GtkWidget *w, gpointer data)
953 {
954         Children *children = (Children *) data;
955         ChildInfo *child_info = (ChildInfo *) children->list->data;
956
957         child_info->tag_in = gdk_input_add(child_info->chld_in,
958                                            GDK_INPUT_WRITE,
959                                            catch_input, children);
960         gtk_widget_set_sensitive(children->input_hbox, FALSE);
961 }
962
963 static gint delete_io_dialog_cb(GtkWidget *w, GdkEvent *e, gpointer data)
964 {
965         hide_io_dialog_cb(w, data);
966         return TRUE;
967 }
968
969 static void hide_io_dialog_cb(GtkWidget *w, gpointer data)
970 {
971
972         Children *children = (Children *)data;
973
974         if (!children->nb) {
975                 gtk_signal_disconnect_by_data(GTK_OBJECT(children->dialog),
976                                               children);
977                 gtk_widget_destroy(children->dialog);
978                 free_children(children);
979         }
980 }
981
982 static gint io_dialog_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
983                                      gpointer data)
984 {
985         if (event && event->keyval == GDK_Escape)
986                 hide_io_dialog_cb(widget, data);
987         return TRUE;
988 }
989
990 static void childinfo_close_pipes(ChildInfo *child_info)
991 {
992         if (child_info->tag_in > 0)
993                 gdk_input_remove(child_info->tag_in);
994         gdk_input_remove(child_info->tag_out);
995         gdk_input_remove(child_info->tag_err);
996
997         if (child_info->chld_in >= 0)
998                 close(child_info->chld_in);
999         close(child_info->chld_out);
1000         close(child_info->chld_err);
1001         close(child_info->chld_status);
1002 }
1003
1004 static void free_children(Children *children)
1005 {
1006         GSList *cur;
1007         ChildInfo *child_info;
1008
1009         debug_print("Freeing children data %p\n", children);
1010
1011         g_free(children->action);
1012         for (cur = children->list; cur;) {
1013                 child_info = (ChildInfo *)cur->data;
1014                 g_free(child_info->cmd);
1015                 g_string_free(child_info->output, TRUE);
1016                 children->list = g_slist_remove(children->list, child_info);
1017                 g_free(child_info);
1018                 cur = children->list;
1019         }
1020         g_free(children);
1021 }
1022
1023 static void update_io_dialog(Children *children)
1024 {
1025         GSList *cur;
1026
1027         debug_print("Updating actions input/output dialog.\n");
1028
1029         if (!children->nb) {
1030                 gtk_widget_set_sensitive(children->abort_btn, FALSE);
1031                 gtk_widget_set_sensitive(children->close_btn, TRUE);
1032                 if (children->input_hbox)
1033                         gtk_widget_set_sensitive(children->input_hbox, FALSE);
1034                 gtk_widget_grab_focus(children->close_btn);
1035                 gtk_signal_connect(GTK_OBJECT(children->dialog),
1036                                    "key_press_event",
1037                                    GTK_SIGNAL_FUNC(io_dialog_key_pressed_cb),
1038                                    children);
1039         }
1040
1041         if (children->output) {
1042                 GtkWidget *text = children->text;
1043                 gchar *caption;
1044                 ChildInfo *child_info;
1045
1046                 gtk_widget_show(children->scrolledwin);
1047                 gtk_text_freeze(GTK_TEXT(text));
1048                 gtk_text_set_point(GTK_TEXT(text), 0);
1049                 gtk_text_forward_delete(GTK_TEXT(text), 
1050                                         gtk_text_get_length(GTK_TEXT(text)));
1051                 for (cur = children->list; cur; cur = cur->next) {
1052                         child_info = (ChildInfo *)cur->data;
1053                         if (child_info->pid)
1054                                 caption = g_strdup_printf
1055                                         (_("--- Running: %s\n"),
1056                                          child_info->cmd);
1057                         else
1058                                 caption = g_strdup_printf
1059                                         (_("--- Ended: %s\n"),
1060                                          child_info->cmd);
1061
1062                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1063                                         caption, -1);
1064                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1065                                         child_info->output->str, -1);
1066                         g_free(caption);
1067                         child_info->new_out = FALSE;
1068                 }
1069                 gtk_text_thaw(GTK_TEXT(text));
1070         }
1071 }
1072
1073 static void create_io_dialog(Children *children)
1074 {
1075         GtkWidget *dialog;
1076         GtkWidget *vbox;
1077         GtkWidget *entry = NULL;
1078         GtkWidget *input_hbox = NULL;
1079         GtkWidget *send_button;
1080         GtkWidget *label;
1081         GtkWidget *text;
1082         GtkWidget *scrolledwin;
1083         GtkWidget *hbox;
1084         GtkWidget *abort_button;
1085         GtkWidget *close_button;
1086
1087         debug_print("Creating action IO dialog\n");
1088
1089         dialog = gtk_dialog_new();
1090         gtk_container_set_border_width
1091                 (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
1092         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
1093         gtk_window_set_title(GTK_WINDOW(dialog), _("Action's input/output"));
1094         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1095         manage_window_set_transient(GTK_WINDOW(dialog));
1096         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
1097                         GTK_SIGNAL_FUNC(delete_io_dialog_cb), children);
1098         gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
1099                         GTK_SIGNAL_FUNC(hide_io_dialog_cb),
1100                         children);
1101
1102         vbox = gtk_vbox_new(FALSE, 8);
1103         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
1104         gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1105         gtk_widget_show(vbox);
1106
1107         label = gtk_label_new(children->action);
1108         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1109         gtk_widget_show(label);
1110
1111         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
1112         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
1113                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
1114         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
1115         gtk_widget_set_usize(scrolledwin, 480, 200);
1116         gtk_widget_hide(scrolledwin);
1117
1118         text = gtk_text_new(gtk_scrolled_window_get_hadjustment
1119                             (GTK_SCROLLED_WINDOW(scrolledwin)),
1120                             gtk_scrolled_window_get_vadjustment
1121                             (GTK_SCROLLED_WINDOW(scrolledwin)));
1122         gtk_text_set_editable(GTK_TEXT(text), FALSE);
1123         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
1124         gtk_widget_show(text);
1125
1126         if (children->open_in) {
1127                 input_hbox = gtk_hbox_new(FALSE, 8);
1128                 gtk_widget_show(input_hbox);
1129
1130                 entry = gtk_entry_new();
1131                 gtk_widget_set_usize(entry, 320, -1);
1132                 gtk_signal_connect(GTK_OBJECT(entry), "activate",
1133                                    GTK_SIGNAL_FUNC(send_input), children);
1134                 gtk_box_pack_start(GTK_BOX(input_hbox), entry, TRUE, TRUE, 0);
1135                 if (children->open_in & ACTION_USER_HIDDEN_IN)
1136                         gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
1137                 gtk_widget_show(entry);
1138
1139                 send_button = gtk_button_new_with_label(_(" Send "));
1140                 gtk_signal_connect(GTK_OBJECT(send_button), "clicked",
1141                                    GTK_SIGNAL_FUNC(send_input), children);
1142                 gtk_box_pack_start(GTK_BOX(input_hbox), send_button, FALSE,
1143                                    FALSE, 0);
1144                 gtk_widget_show(send_button);
1145
1146                 gtk_box_pack_start(GTK_BOX(vbox), input_hbox, FALSE, FALSE, 0);
1147                 gtk_widget_grab_focus(entry);
1148         }
1149
1150         gtkut_button_set_create(&hbox, &abort_button, _("Abort"),
1151                                 &close_button, _("Close"), NULL, NULL);
1152         gtk_signal_connect(GTK_OBJECT(abort_button), "clicked",
1153                         GTK_SIGNAL_FUNC(kill_children_cb), children);
1154         gtk_signal_connect(GTK_OBJECT(close_button), "clicked",
1155                         GTK_SIGNAL_FUNC(hide_io_dialog_cb), children);
1156         gtk_widget_show(hbox);
1157
1158         if (children->nb)
1159                 gtk_widget_set_sensitive(close_button, FALSE);
1160
1161         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
1162
1163         children->dialog      = dialog;
1164         children->scrolledwin = scrolledwin;
1165         children->text        = text;
1166         children->input_hbox  = children->open_in ? input_hbox : NULL;
1167         children->input_entry = children->open_in ? entry : NULL;
1168         children->abort_btn   = abort_button;
1169         children->close_btn   = close_button;
1170
1171         gtk_widget_show(dialog);
1172 }
1173
1174 static void catch_status(gpointer data, gint source, GdkInputCondition cond)
1175 {
1176         ChildInfo *child_info = (ChildInfo *)data;
1177         gchar buf;
1178         gint c;
1179
1180         gdk_input_remove(child_info->tag_status);
1181
1182         c = read(source, &buf, 1);
1183         debug_print("Child returned %c\n", buf);
1184
1185         waitpid(-child_info->pid, NULL, 0);
1186         childinfo_close_pipes(child_info);
1187         child_info->pid = 0;
1188
1189         wait_for_children(child_info->children);
1190 }
1191         
1192 static void catch_input(gpointer data, gint source, GdkInputCondition cond)
1193 {
1194         Children *children = (Children *)data;
1195         ChildInfo *child_info = (ChildInfo *)children->list->data;
1196         gchar *input;
1197         gint c, count, len;
1198
1199         debug_print("Sending input to grand child.\n");
1200         if (!(cond && GDK_INPUT_WRITE))
1201                 return;
1202
1203         gdk_input_remove(child_info->tag_in);
1204         child_info->tag_in = -1;
1205
1206         input = gtk_editable_get_chars(GTK_EDITABLE(children->input_entry),
1207                                        0, -1);
1208         len = strlen(input);
1209         count = 0;
1210
1211         do {
1212                 c = write(child_info->chld_in, input + count, len - count);
1213                 if (c >= 0)
1214                         count += c;
1215         } while (c >= 0 && count < len);
1216
1217         if (c >= 0)
1218                 write(child_info->chld_in, "\n", 2);
1219
1220         g_free(input);
1221
1222         gtk_entry_set_text(GTK_ENTRY(children->input_entry), "");
1223         gtk_widget_set_sensitive(children->input_hbox, TRUE);
1224         close(child_info->chld_in);
1225         child_info->chld_in = -1;
1226         debug_print("Input to grand child sent.\n");
1227 }
1228
1229 static void catch_output(gpointer data, gint source, GdkInputCondition cond)
1230 {
1231         ChildInfo *child_info = (ChildInfo *)data;
1232         gint c, i;
1233         gchar buf[BUFFSIZE];
1234
1235         debug_print("Catching grand child's output.\n");
1236         if (child_info->type & (ACTION_PIPE_OUT | ACTION_INSERT)
1237             && source == child_info->chld_out) {
1238                 gboolean is_selection = FALSE;
1239                 GtkWidget *text = child_info->text;
1240                 if (GTK_EDITABLE(text)->has_selection)
1241                         is_selection = TRUE;
1242                 gtk_stext_freeze(GTK_STEXT(text));
1243                 while (TRUE) {
1244                         c = read(source, buf, sizeof(buf) - 1);
1245                         if (c == 0)
1246                                 break;
1247                         gtk_stext_insert(GTK_STEXT(text), child_info->msgfont,
1248                                          NULL, NULL, buf, c);
1249                 }
1250                 if (is_selection) {
1251                         /* Using the select_region draws things. Should not.
1252                          * so we just change selection position and 
1253                          * defere drawing when thawing. Hack?
1254                          */
1255                         GTK_EDITABLE(text)->selection_end_pos =
1256                                         gtk_stext_get_point(GTK_STEXT(text));
1257                 }
1258                 gtk_stext_thaw(GTK_STEXT(child_info->text));
1259         } else {
1260                 c = read(source, buf, sizeof(buf) - 1);
1261                 for (i = 0; i < c; i++)
1262                         g_string_append_c(child_info->output, buf[i]);
1263                 if (c > 0)
1264                         child_info->new_out = TRUE;
1265         }
1266         wait_for_children(child_info->children);
1267 }
1268
1269 static gboolean user_string_dialog_delete_cb(GtkWidget *widget,
1270                                              GdkEvent *event, gpointer data)
1271 {
1272         return FALSE;
1273 }
1274
1275 static void user_string_dialog_destroy_cb(GtkWidget *widget, gpointer data)
1276 {
1277         gtk_main_quit();
1278 }
1279
1280 static gchar *get_user_string(const gchar *action, ActionType type)
1281 {
1282         gchar *message;
1283         gchar *user_str = NULL;
1284
1285         switch (type) {
1286         case ACTION_USER_HIDDEN_STR:
1287                 message = g_strdup_printf
1288                         (_("Enter the argument for the following action:\n"
1289                            "(`%%h' will be replaced with the argument)\n"
1290                            "  %s"),
1291                          action);
1292                 user_str = input_dialog_with_invisible
1293                         (_("Action's hidden user argument"), message, NULL);
1294                 break;
1295         case ACTION_USER_STR:
1296                 message = g_strdup_printf
1297                         (_("Enter the argument for the following action:\n"
1298                            "(`%%u' will be replaced with the argument)\n"
1299                            "  %s"),
1300                          action);
1301                 user_str = input_dialog
1302                         (_("Action's user argument"), message, NULL);
1303                 break;
1304         default:
1305                 g_warning("Unsupported action type %d", type);
1306         }
1307
1308         return user_str;
1309 }