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