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