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