move Edit/Actions to Tool/Actions
[claws.git] / src / prefs_actions.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2002 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gdk/gdkx.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <errno.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <signal.h>
37 #include <unistd.h>
38
39 #include "intl.h"
40 #include "prefs.h"
41 #include "inc.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 "prefs_actions.h"
49 #include "compose.h"
50 #include "procmsg.h"
51 #include "gtkstext.h"
52
53 #define WAIT_LAP 10000
54                                                 
55 typedef enum {
56 ACTION_NONE             = 1 << 0,
57 ACTION_PIPE_IN          = 1 << 1,
58 ACTION_PIPE_OUT         = 1 << 2,
59 ACTION_SINGLE           = 1 << 3,
60 ACTION_MULTIPLE         = 1 << 4,
61 ACTION_ASYNC            = 1 << 5,
62 ACTION_OPEN_IN          = 1 << 6,
63 ACTION_HIDE_IN          = 1 << 7,
64 ACTION_ERROR            = 1 << 8,
65 } ActionsTypes;
66
67 static struct Actions {
68         GtkWidget *window;
69
70         GtkWidget *ok_btn;
71
72         GtkWidget *name_entry;
73         GtkWidget *cmd_entry;
74
75         GtkWidget *actions_clist;
76 } actions;
77
78 typedef struct _Children Children;
79 struct _Children {
80         GtkWidget       *window;
81         GtkWidget       *dialog;
82         GtkWidget       *text;
83         GtkWidget       *input_entry;
84         GtkWidget       *input_hbox;
85         GtkWidget       *abort_btn;
86         GtkWidget       *hide_btn;
87         GtkWidget       *scrolledwin;
88
89         gchar           *action;
90         guint            timer;
91         GSList          *list;
92         gint             nb;
93         gint             open_in;
94         gboolean         output;
95 };
96
97 typedef struct _ChildInfo ChildInfo;
98 struct _ChildInfo {
99         Children        *children;
100         gchar           *cmd;
101         guint            type;
102         pid_t            pid;
103         gint             chld_in;
104         gint             chld_out;
105         gint             chld_err;
106         gint             chld_status;
107         gint             tag_in;
108         gint             tag_out;
109         gint             tag_err;
110         gint             tag_status;
111         gint             new_out;
112         GString         *output;
113         GtkWidget       *text;
114 };
115
116 /* widget creating functions */
117 static void prefs_actions_create        (MainWindow *mainwin);
118 static void prefs_actions_set_dialog    (void);
119 static gint prefs_actions_clist_set_row (gint row);
120
121 /* callback functions */
122 static void prefs_actions_help_cb       (GtkWidget *w, gpointer data);
123 static void prefs_actions_register_cb   (GtkWidget *w, gpointer data);
124 static void prefs_actions_substitute_cb (GtkWidget *w, gpointer data);
125 static void prefs_actions_delete_cb     (GtkWidget *w, gpointer data);
126 static void prefs_actions_up            (GtkWidget *w, gpointer data);
127 static void prefs_actions_down          (GtkWidget *w, gpointer data);
128 static void prefs_actions_select        (GtkCList       *clist,
129                                          gint            row,
130                                          gint            column,
131                                          GdkEvent       *event);
132 static void prefs_actions_row_move      (GtkCList       *clist,
133                                          gint            source_row,
134                                          gint            dest_row);
135 static gint prefs_actions_deleted       (GtkWidget      *widget,
136                                          GdkEventAny    *event,
137                                          gpointer       *data);
138 static void prefs_actions_key_pressed   (GtkWidget      *widget,
139                                          GdkEventKey    *event,
140                                          gpointer        data);
141 static void prefs_actions_cancel        (GtkWidget *w, gpointer data);
142 static void prefs_actions_ok            (GtkWidget *w, gpointer data);
143 static void update_actions_menu         (GtkItemFactory *ifactory,
144                                          gchar *branch_path,
145                                          gpointer callback,
146                                          gpointer data);
147 static void mainwin_actions_execute_cb  (MainWindow     *mainwin,
148                                          guint           action_nb,
149                                          GtkWidget      *widget);
150 static void compose_actions_execute_cb  (Compose        *compose,
151                                          guint           action_nb,
152                                          GtkWidget      *widget);
153 static guint get_action_type            (gchar *action);
154
155 static gboolean execute_actions         (gchar *action, 
156                                          GtkWidget *window,
157                                          GtkCTree *ctree, 
158                                          GtkWidget *text);
159
160 static gchar *parse_action_cmd          (gchar *action,
161                                          MsgInfo *msginfo,
162                                          GtkCTree *ctree);
163 static GString *parse_append_filename   (GString *cmd,
164                                          MsgInfo *msginfo);
165
166 ChildInfo *fork_child                   (gchar *cmd,
167                                          gint action_type,
168                                          GtkWidget *text,
169                                          Children *children);
170
171 static gint wait_for_children           (gpointer data);
172
173 static void free_children               (Children *children);
174
175 static void childinfo_close_pipes       (ChildInfo *child_info);
176
177 static void create_io_dialog            (Children *children);
178
179 static void update_io_dialog            (Children *children);
180
181 static void hide_io_dialog_cb           (GtkWidget *widget,
182                                          gpointer data);
183
184 static void catch_output                (gpointer data,
185                                          gint source,
186                                          GdkInputCondition cond);
187 static void catch_input                 (gpointer data, 
188                                          gint source,
189                                          GdkInputCondition cond);
190 static void catch_status                (gpointer data,
191                                          gint source,
192                                          GdkInputCondition cond);
193
194 void prefs_actions_open(MainWindow *mainwin)
195 {
196         if (prefs_rc_is_readonly(ACTIONS_RC))
197                 return;
198         inc_lock();
199
200         if (!actions.window)
201                 prefs_actions_create(mainwin);
202         
203         manage_window_set_transient(GTK_WINDOW(actions.window));
204         gtk_widget_grab_focus(actions.ok_btn);
205
206         prefs_actions_set_dialog();
207
208         gtk_widget_show(actions.window);
209 }
210
211 static void prefs_actions_create(MainWindow *mainwin)
212 {
213         GtkWidget *window;
214         GtkWidget *vbox;
215         GtkWidget *ok_btn;
216         GtkWidget *cancel_btn;
217         GtkWidget *confirm_area;
218
219         GtkWidget *vbox1;
220         GtkWidget *hbox;
221         GtkWidget *name_label;
222         GtkWidget *name_entry;
223         GtkWidget *cmd_label;
224         GtkWidget *cmd_entry;
225
226         GtkWidget *reg_hbox;
227         GtkWidget *btn_hbox;
228         GtkWidget *arrow;
229         GtkWidget *reg_btn;
230         GtkWidget *subst_btn;
231         GtkWidget *del_btn;
232
233         GtkWidget *cond_hbox;
234         GtkWidget *cond_scrolledwin;
235         GtkWidget *cond_clist;
236
237         GtkWidget *help_vbox;
238         GtkWidget *help_label;
239         GtkWidget *help_toggle;
240
241         GtkWidget *btn_vbox;
242         GtkWidget *up_btn;
243         GtkWidget *down_btn;
244         
245         gchar *title[1];
246
247         debug_print(_("Creating actions setting window...\n"));
248
249         window = gtk_window_new (GTK_WINDOW_DIALOG);
250                 
251         gtk_container_set_border_width (GTK_CONTAINER (window), 8);
252         gtk_window_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
253         gtk_window_set_modal (GTK_WINDOW (window), TRUE);
254         gtk_window_set_policy (GTK_WINDOW (window), FALSE, TRUE, TRUE);
255
256         vbox = gtk_vbox_new (FALSE, 6);
257         gtk_widget_show (vbox);
258         gtk_container_add (GTK_CONTAINER (window), vbox);
259
260         gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"),
261                                 &cancel_btn, _("Cancel"), NULL, NULL);
262         gtk_widget_show (confirm_area);
263         gtk_box_pack_end (GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
264         gtk_widget_grab_default (ok_btn);
265                                          
266         gtk_window_set_title (GTK_WINDOW(window),
267                               _("Actions setting"));
268         gtk_signal_connect (GTK_OBJECT(window), "delete_event",
269                             GTK_SIGNAL_FUNC(prefs_actions_deleted), NULL);
270         gtk_signal_connect (GTK_OBJECT(window), "key_press_event",
271                             GTK_SIGNAL_FUNC(prefs_actions_key_pressed), NULL);
272         MANAGE_WINDOW_SIGNALS_CONNECT (window);
273         gtk_signal_connect (GTK_OBJECT(ok_btn), "clicked",
274                             GTK_SIGNAL_FUNC(prefs_actions_ok), mainwin);
275         gtk_signal_connect (GTK_OBJECT(cancel_btn), "clicked",
276                             GTK_SIGNAL_FUNC(prefs_actions_cancel), NULL);
277
278         vbox1 = gtk_vbox_new (FALSE, VSPACING);
279         gtk_widget_show (vbox1);
280         gtk_box_pack_start (GTK_BOX (vbox), vbox1, TRUE, TRUE, 0);
281         gtk_container_set_border_width (GTK_CONTAINER (vbox1), 2);
282
283         hbox = gtk_hbox_new (FALSE, 0);
284         gtk_box_pack_start (GTK_BOX(vbox1), hbox, TRUE, TRUE, 0);
285         
286         name_label = gtk_label_new(_("Menu name: "));
287         gtk_widget_show(name_label);
288         gtk_box_pack_start (GTK_BOX (hbox), name_label, FALSE, FALSE, 0);
289
290         name_entry = gtk_entry_new();
291         gtk_widget_show(name_entry);
292         gtk_box_pack_start (GTK_BOX (hbox), name_entry, TRUE, TRUE, 0);
293
294         gtk_widget_show_all(hbox);
295
296         hbox = gtk_hbox_new (FALSE, 0);
297         gtk_box_pack_start (GTK_BOX(vbox1), hbox, TRUE, TRUE, 0);
298                  
299         cmd_label = gtk_label_new(_("Command line: "));
300         gtk_box_pack_start (GTK_BOX (hbox), cmd_label, FALSE, FALSE, 0);
301
302         cmd_entry = gtk_entry_new();
303         gtk_box_pack_start (GTK_BOX (hbox), cmd_entry, TRUE, TRUE, 0);
304
305         gtk_widget_show_all(hbox);
306
307         help_vbox = gtk_vbox_new(FALSE, 8);
308         gtk_box_pack_start (GTK_BOX (vbox1), help_vbox, TRUE, TRUE, 0);
309
310         help_label = gtk_label_new (_("Menu name:\n"
311                                       " Use '/' in menu name to make submenus.\n"
312                                       "Command line:\n"
313                                       " Begin with:\n   '|' to send message "
314                                       "body or selection "
315                                       "to command\n   '>' to send user provided"
316                                       " text to command\n   '*' to send user "
317                                       "provided hidden text to command\n"
318                                       " End with:\n   '|' to replace "
319                                       "message body or selection with command "
320                                       "output\n   '&' to "
321                                       "run command asynchronously\n Use '%f' "
322                                       "for message file name\n and '%F' for the"
323                                       " list of the file names of selected "
324                                       "messages."));
325         gtk_misc_set_alignment(GTK_MISC(help_label), 0, 0.5);
326         gtk_label_set_justify (GTK_LABEL(help_label), GTK_JUSTIFY_LEFT);
327         gtk_widget_show (help_label);
328         gtk_box_pack_start (GTK_BOX (help_vbox), help_label, TRUE, TRUE, 0);
329         gtk_widget_hide(help_vbox);
330
331                 /* register / substitute / delete */
332
333         reg_hbox = gtk_hbox_new (FALSE, 4);
334         gtk_widget_show (reg_hbox);
335         gtk_box_pack_start (GTK_BOX (vbox1), reg_hbox, FALSE, FALSE, 0);
336
337         arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
338         gtk_widget_show (arrow);
339         gtk_box_pack_start (GTK_BOX (reg_hbox), arrow, FALSE, FALSE, 0);
340         gtk_widget_set_usize (arrow, -1, 16);
341
342         btn_hbox = gtk_hbox_new (FALSE, 4);
343         gtk_widget_show (btn_hbox);
344         gtk_box_pack_start (GTK_BOX (reg_hbox), btn_hbox, FALSE, FALSE, 0);
345
346         reg_btn = gtk_button_new_with_label (_("Register"));
347         gtk_widget_show (reg_btn);
348         gtk_box_pack_start (GTK_BOX (btn_hbox), reg_btn, FALSE, TRUE, 0);
349         gtk_signal_connect (GTK_OBJECT (reg_btn), "clicked",
350                             GTK_SIGNAL_FUNC (prefs_actions_register_cb), NULL);
351
352         subst_btn = gtk_button_new_with_label (_(" Substitute "));
353         gtk_widget_show (subst_btn);
354         gtk_box_pack_start (GTK_BOX (btn_hbox), subst_btn, FALSE, TRUE, 0);
355         gtk_signal_connect (GTK_OBJECT (subst_btn), "clicked",
356                             GTK_SIGNAL_FUNC (prefs_actions_substitute_cb),
357                             NULL);
358
359         del_btn = gtk_button_new_with_label (_("Delete"));
360         gtk_widget_show (del_btn);
361         gtk_box_pack_start (GTK_BOX (btn_hbox), del_btn, FALSE, TRUE, 0);
362         gtk_signal_connect (GTK_OBJECT (del_btn), "clicked",
363                             GTK_SIGNAL_FUNC (prefs_actions_delete_cb), NULL);
364
365         help_toggle = gtk_toggle_button_new_with_label (_("Help on syntax"));
366         gtk_widget_show (help_toggle);
367         gtk_box_pack_end (GTK_BOX (btn_hbox), help_toggle, FALSE, TRUE, 0);
368         gtk_signal_connect (GTK_OBJECT (help_toggle), "toggled",
369                             GTK_SIGNAL_FUNC (prefs_actions_help_cb), help_vbox);
370
371         cond_hbox = gtk_hbox_new (FALSE, 8);
372         gtk_widget_show (cond_hbox);
373         gtk_box_pack_start (GTK_BOX (vbox1), cond_hbox, TRUE, TRUE, 0);
374
375         cond_scrolledwin = gtk_scrolled_window_new (NULL, NULL);
376         gtk_widget_show (cond_scrolledwin);
377         gtk_widget_set_usize (cond_scrolledwin, -1, 150);
378         gtk_box_pack_start (GTK_BOX (cond_hbox), cond_scrolledwin,
379                             TRUE, TRUE, 0);
380         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (cond_scrolledwin),
381                                         GTK_POLICY_AUTOMATIC,
382                                         GTK_POLICY_AUTOMATIC);
383
384         title[0] = _("Registered actions");
385         cond_clist = gtk_clist_new_with_titles(1, title);
386         gtk_widget_show (cond_clist);
387         gtk_container_add (GTK_CONTAINER (cond_scrolledwin), cond_clist);
388         gtk_clist_set_column_width (GTK_CLIST (cond_clist), 0, 80);
389         gtk_clist_set_selection_mode (GTK_CLIST (cond_clist),
390                                       GTK_SELECTION_BROWSE);
391         GTK_WIDGET_UNSET_FLAGS (GTK_CLIST (cond_clist)->column[0].button,
392                                 GTK_CAN_FOCUS);
393         gtk_signal_connect (GTK_OBJECT (cond_clist), "select_row",
394                             GTK_SIGNAL_FUNC (prefs_actions_select), NULL);
395         gtk_signal_connect_after (GTK_OBJECT (cond_clist), "row_move",
396                                   GTK_SIGNAL_FUNC (prefs_actions_row_move),
397                                   NULL);
398
399         btn_vbox = gtk_vbox_new (FALSE, 8);
400         gtk_widget_show (btn_vbox);
401         gtk_box_pack_start (GTK_BOX (cond_hbox), btn_vbox, FALSE, FALSE, 0);
402
403         up_btn = gtk_button_new_with_label (_("Up"));
404         gtk_widget_show (up_btn);
405         gtk_box_pack_start (GTK_BOX (btn_vbox), up_btn, FALSE, FALSE, 0);
406         gtk_signal_connect (GTK_OBJECT (up_btn), "clicked",
407                             GTK_SIGNAL_FUNC (prefs_actions_up), NULL);
408
409         down_btn = gtk_button_new_with_label (_("Down"));
410         gtk_widget_show (down_btn);
411         gtk_box_pack_start (GTK_BOX (btn_vbox), down_btn, FALSE, FALSE, 0);
412         gtk_signal_connect (GTK_OBJECT (down_btn), "clicked",
413                             GTK_SIGNAL_FUNC (prefs_actions_down), NULL);
414
415         gtk_widget_show(window);
416
417         actions.window = window;
418         actions.ok_btn = ok_btn;
419
420         actions.name_entry = name_entry;
421         actions.cmd_entry  = cmd_entry;
422         
423         actions.actions_clist = cond_clist;
424 }
425
426 static void prefs_actions_help_cb(GtkWidget *w, gpointer data)
427 {
428         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)))
429                 gtk_widget_show(GTK_WIDGET(data));
430         else
431                 gtk_widget_hide(GTK_WIDGET(data));
432 }
433
434 void prefs_actions_read_config()
435 {
436         gchar *rcpath;
437         FILE *fp;
438         gchar buf[PREFSBUFSIZE];
439         gchar *act;
440
441         debug_print(_("Reading actions configurations...\n"));
442
443         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL);
444         if ((fp = fopen(rcpath, "rb")) == NULL) {
445                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
446                 g_free(rcpath);
447                 return;
448         }
449         g_free(rcpath);
450
451         while (prefs_common.actionslst != NULL) {
452                 act = (gchar *) prefs_common.actionslst->data;
453                 prefs_common.actionslst = g_slist_remove(
454                                                 prefs_common.actionslst,
455                                                 act);
456                 g_free(act);
457         }
458         
459         while (fgets(buf, sizeof(buf), fp) != NULL) {
460                 g_strchomp(buf);
461                 act = strstr(buf, ": ");
462                 if (act && act[2] && 
463                     get_action_type(&act[2]) != ACTION_ERROR)
464                         prefs_common.actionslst = g_slist_append(
465                                                         prefs_common.actionslst,
466                                                         g_strdup(buf));
467         }
468         fclose(fp);
469 }
470
471 void prefs_actions_write_config()
472 {
473         gchar *rcpath;
474         PrefFile *pfile;
475         GSList *cur;
476
477         debug_print(_("Writing actions configuration...\n"));
478
479         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL);
480         if ((pfile= prefs_write_open(rcpath)) == NULL) {
481                 g_warning(_("failed to write configuration to file\n"));
482                 g_free(rcpath);
483                 return;
484         }
485
486         for (cur = prefs_common.actionslst; cur != NULL; cur = cur->next) {
487                 gchar *act = (gchar *) cur->data;
488                 if (fputs(act, pfile->fp) == EOF ||
489                     fputc('\n', pfile->fp) == EOF) {
490                         FILE_OP_ERROR(rcpath, "fputs || fputc");
491                         prefs_write_close_revert(pfile);
492                         g_free(rcpath);
493                         return;
494                 }
495         }
496         
497         g_free(rcpath);
498
499         if (prefs_write_close(pfile) < 0) {
500                 g_warning(_("failed to write configuration to file\n"));
501                 return;
502         }
503 }
504
505 static guint get_action_type(gchar *action)
506 {
507         gchar *p;
508         guint action_type = ACTION_NONE;
509         
510         g_return_val_if_fail(action,  ACTION_ERROR);
511         g_return_val_if_fail(*action, ACTION_ERROR);
512
513         p = action;
514
515         if (p[0] == '|') {
516                 action_type |= ACTION_PIPE_IN;
517                 p++;
518         } else if (p[0] == '>') {
519                 action_type |= ACTION_OPEN_IN;
520                 p++;
521         } else if (p[0] == '*') {
522                 action_type |= ACTION_HIDE_IN;
523                 p++;
524         }
525
526         if (p[0] == 0x00)
527                 return ACTION_ERROR;
528
529         while (*p && action_type != ACTION_ERROR) {
530                 if (p[0] == '%') {
531                         switch (p[1]) {
532                                 case 'f': action_type |= ACTION_SINGLE;
533                                           break;
534                                 case 'F': action_type |= ACTION_MULTIPLE;
535                                           break;
536                                 default:  action_type  = ACTION_ERROR;
537                                           break;
538                         }
539                 } else if (p[0] == '|') {
540                         if (p[1] == 0x00)
541                                 action_type |= ACTION_PIPE_OUT;
542                         else
543                                 action_type  = ACTION_ERROR;
544                 } else if (p[0] == '&') {
545                         if (p[1] == 0x00)
546                                 action_type |= ACTION_ASYNC;
547                         else
548                                 action_type  = ACTION_ERROR;
549                 }
550                 p++;
551         }
552
553         return action_type;
554 }
555
556 static gchar *parse_action_cmd          (gchar *action,
557                                          MsgInfo *msginfo,
558                                          GtkCTree *ctree)
559 {
560         GString *cmd;
561         gchar *p;
562         GList *cur;
563         MsgInfo *msg;
564         
565         p = action;
566         
567         if (p[0] == '|' || p[0] == '>' || p[0] == '*')
568                 p++;
569
570         cmd = g_string_sized_new(strlen(action));
571
572         while (p[0] && p[0] != '|' && p[0] != '&') {
573                 if (p[0] == '%' && p[1]) {
574                         switch (p[1]) {
575                            case 'f': cmd = parse_append_filename(cmd, msginfo);
576                                      p++;       
577                                      break;
578                            case 'F':
579                                      for (cur =GTK_CLIST(ctree)->selection;
580                                           cur; cur = cur->next) {
581                                         msg = gtk_ctree_node_get_row_data(ctree,
582                                               GTK_CTREE_NODE(cur->data));
583                                         cmd = parse_append_filename(cmd, msg);
584                                         cmd = g_string_append_c(cmd, ' ');
585                                      }
586                                      p++;
587                                      break;
588                            default: cmd = g_string_append_c(cmd, p[0]);
589                                     cmd = g_string_append_c(cmd, p[1]);
590                                     p++;
591                         }
592                 } else {
593                         cmd = g_string_append_c(cmd, p[0]);
594                 }
595                 p++;
596         }
597         if (!cmd->len) {
598                 g_string_free(cmd, TRUE);
599                 return NULL;
600         }
601
602         p = cmd->str;
603         g_string_free(cmd, FALSE);
604         return p;
605 }
606
607 static GString *parse_append_filename(GString *cmd, MsgInfo *msginfo)
608 {
609         gchar *filename;
610
611         g_return_val_if_fail(msginfo, cmd);
612
613         filename = procmsg_get_message_file(msginfo);
614
615         if (filename) {
616                 cmd = g_string_append(cmd, filename);
617                 g_free(filename);
618         } 
619
620         return cmd;
621 }
622
623 static void prefs_actions_set_dialog    (void)
624 {
625         GtkCList *clist = GTK_CLIST(actions.actions_clist);
626         GSList *cur;
627         gchar *action_str[1];
628         gint row;
629
630         gtk_clist_freeze(clist);
631         gtk_clist_clear(clist);
632
633         action_str[0] = _("(New)");
634         row = gtk_clist_append(clist, action_str);
635         gtk_clist_set_row_data(clist, row, NULL);
636
637         for (cur = prefs_common.actionslst; cur != NULL; cur = cur->next) {
638                 gchar *action[1];
639                 action[0] = (gchar *) cur->data;
640
641                 row = gtk_clist_append(clist, action);
642                 gtk_clist_set_row_data(clist, row, action[0]);
643         }
644         
645         gtk_clist_thaw(clist);
646 }
647 static void prefs_actions_set_list(void)
648 {
649         gint row = 1;
650         gchar *action;
651         
652         g_slist_free(prefs_common.actionslst);
653         prefs_common.actionslst = NULL;
654
655         while ( (action = (gchar *) gtk_clist_get_row_data(GTK_CLIST(
656                                                  actions.actions_clist), row))
657                 != NULL) {
658                 prefs_common.actionslst = g_slist_append(prefs_common.actionslst,
659                                                          action);
660                 row++;
661         }
662 }
663                 
664
665 #define GET_ENTRY(entry) \
666         entry_text = gtk_entry_get_text(GTK_ENTRY(entry))
667 static gint prefs_actions_clist_set_row (gint row)
668 {
669         GtkCList *clist = GTK_CLIST(actions.actions_clist);
670         gchar *entry_text;
671         gint len;
672         gchar action[PREFSBUFSIZE];
673         gchar *buf[1];
674         
675         
676         g_return_val_if_fail(row != 0, -1);
677
678         
679
680         GET_ENTRY(actions.name_entry);
681         if (entry_text[0] == '\0') {
682                 alertpanel_error(_("Menu name is not set."));
683                 return -1;
684         }
685
686         if (strchr(entry_text, ':')) {
687                 alertpanel_error(_("Colon ':' is not allowed in the menu name."));
688                 return -1;
689         }
690         
691         strncpy(action, entry_text, PREFSBUFSIZE - 1);
692         g_strstrip(action);
693
694         /* Keep space for the ': ' delimiter */
695         len = strlen(action) + 2;
696         if (len >= PREFSBUFSIZE - 1) {
697                 alertpanel_error(_("Menu name is too long."));
698                 return -1;
699         }
700
701         strcat(action, ": ");
702
703         GET_ENTRY(actions.cmd_entry);
704
705         if (entry_text[0] == '\0') {
706                 alertpanel_error(_("Command line not set."));
707                 return -1;
708         }
709
710         if (len + strlen(entry_text) >= PREFSBUFSIZE - 1) {
711                 alertpanel_error(_("Menu name and command are too long."));
712                 return -1;
713         }
714
715         if (get_action_type(entry_text) == ACTION_ERROR) {
716                 alertpanel_error(_("The command\n%s\nhas a syntax error."), 
717                                  entry_text);
718                 return -1;
719         }
720         
721         strcat(action, entry_text);
722
723         buf[0] = action;
724         if (row < 0)
725                 row = gtk_clist_append(clist, buf);
726         else {
727                 gchar *old_action;
728                 gtk_clist_set_text(clist, row, 0, action);
729                 old_action = (gchar *) gtk_clist_get_row_data(clist, row);
730                 if (old_action)
731                         g_free(old_action);
732         }
733
734         buf[0] = g_strdup(action);
735         
736         gtk_clist_set_row_data(clist, row, buf[0]);
737
738         prefs_actions_set_list();
739
740         return 0;
741 }
742         
743 /* callback functions */
744 static void prefs_actions_register_cb   (GtkWidget *w, gpointer data)
745 {
746         prefs_actions_clist_set_row(-1);
747 }
748
749 static void prefs_actions_substitute_cb (GtkWidget *w, gpointer data)
750 {
751         GtkCList *clist = GTK_CLIST(actions.actions_clist);
752         gchar *action;
753         gint row;
754
755         if (!clist->selection) return;
756
757         row = GPOINTER_TO_INT(clist->selection->data);
758         if (row == 0) return;
759
760         action = gtk_clist_get_row_data(clist, row);
761         if (!action) return;
762         
763         prefs_actions_clist_set_row(row);
764 }
765
766 static void prefs_actions_delete_cb     (GtkWidget *w, gpointer data)
767 {
768         GtkCList *clist = GTK_CLIST(actions.actions_clist);
769         gchar *action;
770         gint row;
771
772         if (!clist->selection) return;
773         row = GPOINTER_TO_INT(clist->selection->data);
774         if (row == 0) return;
775
776         if (alertpanel(_("Delete action"),
777                        _("Do you really want to delete this action?"),
778                        _("Yes"), _("No"), NULL) == G_ALERTALTERNATE)
779                 return;
780
781         action = gtk_clist_get_row_data(clist, row);
782         g_free(action);
783         gtk_clist_remove(clist, row);
784         prefs_common.actionslst = g_slist_remove(prefs_common.actionslst, action);
785 }
786
787 static void prefs_actions_up            (GtkWidget *w, gpointer data)
788 {
789         GtkCList *clist = GTK_CLIST(actions.actions_clist);
790         gint row;
791
792         if (!clist->selection) return;
793
794         row = GPOINTER_TO_INT(clist->selection->data);
795         if (row > 1)
796                 gtk_clist_row_move(clist, row, row - 1);
797 }
798
799 static void prefs_actions_down          (GtkWidget *w, gpointer data)
800 {
801         GtkCList *clist = GTK_CLIST(actions.actions_clist);
802         gint row;
803
804         if (!clist->selection) return;
805
806         row = GPOINTER_TO_INT(clist->selection->data);
807         if (row > 0 && row < clist->rows - 1)
808                 gtk_clist_row_move(clist, row, row + 1);
809 }
810
811 #define ENTRY_SET_TEXT(entry, str) \
812         gtk_entry_set_text(GTK_ENTRY(entry), str ? str : "")
813 static void prefs_actions_select        (GtkCList       *clist,
814                                          gint            row,
815                                          gint            column,
816                                          GdkEvent       *event)
817 {
818         gchar *action;
819         gchar *cmd;
820         gchar buf[PREFSBUFSIZE];
821         action = gtk_clist_get_row_data(clist, row);
822
823         if (!action) {
824                 ENTRY_SET_TEXT(actions.name_entry, "");
825                 ENTRY_SET_TEXT(actions.cmd_entry, "");
826                 return;
827         }
828         
829         strncpy(buf, action, PREFSBUFSIZE - 1);
830         buf[PREFSBUFSIZE - 1] = 0x00;
831         cmd = strstr2(buf, ": ");
832
833         if (cmd && cmd[2])
834                 ENTRY_SET_TEXT(actions.cmd_entry, &cmd[2]);
835         else
836                 return;
837
838         *cmd = 0x00;
839         ENTRY_SET_TEXT(actions.name_entry, buf);
840 }
841         
842 static void prefs_actions_row_move      (GtkCList       *clist,
843                                          gint            source_row,
844                                          gint            dest_row)
845 {
846         prefs_actions_set_list();
847         if (gtk_clist_row_is_visible(clist, dest_row) != GTK_VISIBILITY_FULL) {
848                 gtk_clist_moveto(clist, dest_row, -1,
849                                  source_row < dest_row ? 1.0 : 0.0, 0.0);
850         }
851 }
852
853 static gint prefs_actions_deleted       (GtkWidget      *widget,
854                                          GdkEventAny    *event,
855                                          gpointer       *data)
856 {
857         prefs_actions_cancel(widget, data);
858         return TRUE;
859 }
860
861 static void prefs_actions_key_pressed   (GtkWidget      *widget,
862                                          GdkEventKey    *event,
863                                          gpointer        data)
864 {
865         if (event && event->keyval == GDK_Escape)
866                 prefs_actions_cancel(widget, data);
867 }
868
869 static void prefs_actions_cancel        (GtkWidget *w, gpointer data)
870 {
871         prefs_actions_read_config();
872         gtk_widget_hide(actions.window);
873         inc_unlock();
874 }
875
876 static void prefs_actions_ok            (GtkWidget *widget, gpointer data)
877 {
878         GtkItemFactory *ifactory;
879         MainWindow *mainwin = (MainWindow *) data;
880
881         prefs_actions_write_config();
882         ifactory = gtk_item_factory_from_widget(mainwin->menubar);
883         update_mainwin_actions_menu(ifactory, mainwin);
884         gtk_widget_hide(actions.window);
885         inc_unlock();
886 }
887
888 void update_mainwin_actions_menu(GtkItemFactory *ifactory, 
889                                  MainWindow *mainwin)
890 {
891         update_actions_menu(ifactory, "/Tool/Actions", 
892                             mainwin_actions_execute_cb, 
893                             mainwin);
894 }
895
896 void update_compose_actions_menu(GtkItemFactory *ifactory, 
897                                  gchar *branch_path,
898                                  Compose *compose)
899 {
900         update_actions_menu(ifactory, branch_path, 
901                             compose_actions_execute_cb, 
902                             compose);
903 }
904                                  
905 static void update_actions_menu(GtkItemFactory *ifactory,
906                                 gchar *branch_path,
907                                 gpointer callback,
908                                 gpointer data)
909 {
910         GtkWidget *menuitem;
911         gchar *menu_path;
912         GSList *cur;
913         gchar *action, *action_p;
914         GtkWidget *menu;
915         
916         GtkItemFactoryEntry ifentry = {
917                 branch_path, NULL, NULL, 0, "<Branch>"};
918         menuitem = gtk_item_factory_get_item(ifactory, branch_path);    
919         /* FIXME: is there a better way to remove unknown submenu items? */
920         /* Deleting and recreating the parent looses the translation */
921         menu = GTK_WIDGET(GTK_MENU_ITEM(menuitem)->submenu);
922         if (GTK_MENU_SHELL(menu)->children) {
923                 GList *amenu, *alist;
924                 for (amenu = (GTK_MENU_SHELL(menu)->children); amenu; ) {
925                         alist = amenu->next;
926                         gtk_widget_destroy(GTK_WIDGET(amenu->data));
927                         amenu = alist;
928                 }
929         }
930
931         ifentry.accelerator     = NULL;
932         ifentry.callback_action = 0;
933         ifentry.callback        = callback;
934         ifentry.item_type       = NULL;
935
936         for (cur = prefs_common.actionslst; cur; cur = cur->next) {
937                 action   = g_strdup((gchar *) cur->data);
938                 action_p = strstr(action, ": ");
939                 if (action_p && action_p[2] &&
940                     get_action_type(&action_p[2]) != ACTION_ERROR) {
941                         action_p[0] = 0x00;
942                         menu_path = g_strdup_printf("%s/%s", branch_path, 
943                                                     action);
944                         ifentry.path = menu_path;
945                         gtk_item_factory_create_item(ifactory, &ifentry, data,
946                                                      1);
947                         g_free(menu_path);
948                 }
949                 g_free(action);
950                 ifentry.callback_action++;      
951         }
952 }
953
954 static void compose_actions_execute_cb  (Compose        *compose,
955                                          guint           action_nb,
956                                          GtkWidget      *widget)
957 {
958         gchar *buf, *action;
959         guint action_type;      
960
961         g_return_if_fail(action_nb < g_slist_length(prefs_common.actionslst));
962         
963         buf = (gchar *) g_slist_nth_data(prefs_common.actionslst, action_nb);
964         
965         g_return_if_fail(buf);
966         g_return_if_fail(action = strstr(buf, ": "));
967
968         /* Point to the beginning of the command-line */
969         action++;
970         action++;
971         
972         action_type = get_action_type(action);
973         if (action_type & ACTION_MULTIPLE ||
974             action_type & ACTION_SINGLE   ) {           
975                 alertpanel_warning(_("The selected action is not a pipe "
976                                      "action.\n You can only use pipe actions "
977                                      "when composing a message."));
978                 return;
979         }
980
981         execute_actions(action, compose->window, NULL, compose->text);
982 }
983
984 static void mainwin_actions_execute_cb  (MainWindow     *mainwin,
985                                          guint           action_nb,
986                                          GtkWidget      *widget)
987 {
988         MessageView *messageview = mainwin->messageview;
989         GtkWidget   *text = NULL;
990         gchar       *buf,
991                     *action;
992         
993         g_return_if_fail (action_nb < g_slist_length(prefs_common.actionslst));
994
995         buf = (gchar *) g_slist_nth_data(prefs_common.actionslst, action_nb);
996         
997         g_return_if_fail(buf);
998         g_return_if_fail(action = strstr(buf, ": "));
999
1000         /* Point to the beginning of the command-line */
1001         action++;
1002         action++;
1003
1004         switch (messageview->type) {
1005                 case MVIEW_TEXT:
1006                         if (messageview->textview && 
1007                             messageview->textview->text)
1008                                 text = messageview->textview->text;
1009                         break;
1010                 case MVIEW_MIME:
1011                         if (messageview->mimeview &&
1012                             messageview->mimeview->type == MIMEVIEW_TEXT &&
1013                             messageview->mimeview->textview &&
1014                             messageview->mimeview->textview->text)
1015                                 text = messageview->mimeview->textview->text;
1016                         break;
1017         }
1018
1019         execute_actions(action, mainwin->window,
1020                         GTK_CTREE(mainwin->summaryview->ctree), text);
1021 }
1022
1023 static gboolean execute_actions(gchar *action, 
1024                                 GtkWidget *window,
1025                                 GtkCTree *ctree, 
1026                                 GtkWidget *text)
1027 {
1028         GList *cur, *selection;
1029         GSList *children_list = NULL;
1030         gint is_ok  = TRUE;
1031         gint selection_len;
1032         Children *children;
1033         ChildInfo *child_info;
1034         gint action_type;
1035         MsgInfo *msginfo;
1036         gchar *cmd;
1037
1038         g_return_val_if_fail(action && *action, FALSE);
1039
1040         action_type = get_action_type(action);
1041
1042         if (action_type == ACTION_ERROR)
1043                 return FALSE;         /* ERR: syntax error */
1044
1045         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE) && 
1046             !(ctree && GTK_CLIST(ctree)->selection))
1047                 return FALSE;         /* ERR: file command without selection */
1048
1049         if (ctree) {
1050                 selection = GTK_CLIST(ctree)->selection;
1051                 selection_len = g_list_length(selection);
1052         }
1053         
1054         if ((action_type & (ACTION_PIPE_OUT | ACTION_PIPE_IN))) {
1055                 if (ctree && selection_len > 1)
1056                         return FALSE; /* ERR: pipe + multiple selection */
1057                 if (!text)
1058                         return FALSE; /* ERR: pipe and no displayed text */
1059         }
1060
1061         if (!(action_type & ACTION_ASYNC) && window) {
1062                 gtk_widget_set_sensitive(window, FALSE);
1063         }
1064
1065         children = g_new0(Children, 1);
1066
1067         if (action_type & ACTION_SINGLE) {
1068                 for (cur = selection; cur && is_ok == TRUE; cur = cur->next) {
1069                         msginfo = gtk_ctree_node_get_row_data(ctree,
1070                                         GTK_CTREE_NODE(cur->data));
1071                         if (!msginfo) {
1072                                 is_ok  = FALSE; /* ERR: msginfo missing */
1073                                 break;
1074                         }
1075                         cmd = parse_action_cmd(action, msginfo, ctree);
1076                         if (!cmd) {
1077                                 debug_print(_("Action command error\n"));
1078                                 is_ok  = FALSE; /* ERR: incorrect command */
1079                                 break;
1080                         }
1081                         if ((child_info = fork_child(cmd, action_type, text,
1082                                                      children))) {
1083                                 children_list = g_slist_append(children_list,
1084                                                                child_info);
1085                                 children->open_in = (selection_len == 1) ? 
1086                                                     (action_type & 
1087                                                      (ACTION_OPEN_IN |
1088                                                       ACTION_HIDE_IN)) : 0;
1089                         }
1090                         g_free(cmd);
1091                 }
1092         } else {
1093                 cmd = parse_action_cmd(action, NULL, ctree);
1094                 if (cmd) {
1095                         if ((child_info = fork_child(cmd, action_type, text,
1096                                                      children))) {
1097                                 children_list = g_slist_append(children_list,
1098                                                                child_info);
1099                                 children->open_in = action_type &
1100                                                     (ACTION_OPEN_IN |
1101                                                      ACTION_HIDE_IN);
1102                         }
1103                         g_free(cmd);
1104                 } else
1105                         is_ok  = FALSE;         /* ERR: incorrect command */
1106         }
1107
1108         if (!children_list) {
1109                  /* If not waiting for children, return */
1110                 g_free(children);
1111                 if (!(action_type & ACTION_ASYNC) && window) {
1112                         gtk_widget_set_sensitive(window, TRUE);
1113                 }
1114         } else {
1115                 GSList *cur;
1116                 
1117                 children->action  = g_strdup(action);
1118                 children->window  = window;
1119                 children->dialog  = NULL;
1120                 children->list    = children_list;
1121                 children->nb      = g_slist_length(children_list);
1122
1123                 for (cur = children_list; cur; cur = cur->next) {
1124                         child_info = (ChildInfo *) cur->data;
1125                         child_info->tag_status = 
1126                                 gdk_input_add(child_info->chld_status,
1127                                               GDK_INPUT_READ,
1128                                               catch_status, child_info);
1129                 }
1130                 children->timer = children->open_in ? 0 :
1131                                   gtk_timeout_add(WAIT_LAP, wait_for_children,
1132                                                   children);
1133         }
1134         if (children->open_in)
1135                 create_io_dialog(children);
1136
1137         return is_ok ;
1138 }
1139
1140 ChildInfo *fork_child(gchar *cmd,
1141                       gint action_type,
1142                       GtkWidget *text,
1143                       Children *children)
1144 {
1145         gint chld_in[2], chld_out[2], chld_err[2], chld_status[2];
1146         gchar **cmdline;
1147         gint start, end, is_selection;
1148         gchar *selection;
1149         pid_t pid_c, pid_gc;
1150         ChildInfo *child_info;
1151
1152         if (action_type & ACTION_ASYNC) {
1153                 execute_command_line(cmd, TRUE);
1154                 return NULL; /* Asynchronous command */
1155         }
1156
1157         if (pipe(chld_in) || pipe(chld_out) || pipe(chld_err) ||
1158             pipe(chld_status)) {
1159                 alertpanel_error(_("Command could not started. Pipe creation"
1160                                    " failed.\n%s"), g_strerror(errno));
1161                 return NULL; /* Pipe error */
1162         }
1163         
1164         debug_print(_("Forking child and granchild.\n"));
1165
1166         pid_c = fork();
1167         if (pid_c == (pid_t) 0) {/* Child */
1168                 if (setpgid(0, 0))
1169                         perror("setpgid");
1170
1171                 close(ConnectionNumber(gdk_display));
1172
1173                 pid_gc = fork();
1174
1175                 if (pid_gc == 0) {
1176                         if (setpgid(0, getppid()))
1177                                 perror("setpgid");
1178                         if (action_type & 
1179                            (ACTION_PIPE_IN | ACTION_OPEN_IN | ACTION_HIDE_IN)) {
1180                         close(fileno(stdin));
1181                         dup  (chld_in[0]);
1182                         }
1183                         close(chld_in[0]);
1184                         close(chld_in[1]);
1185
1186                         close(fileno(stdout));
1187                         dup  (chld_out[1]);
1188                         close(chld_out[0]);
1189                         close(chld_out[1]);
1190
1191                         close(fileno(stderr));
1192                         dup  (chld_err[1]);
1193                         close(chld_err[0]);
1194                         close(chld_err[1]);
1195
1196                         cmdline = strsplit_with_quote(cmd, " ", 1024);
1197
1198                         execvp(cmdline[0], cmdline);
1199                         perror("execvp");
1200                         g_strfreev(cmdline);
1201
1202                         _exit(1);
1203                 } else if (pid_gc < (pid_t) 0) {/* Fork erro */
1204                         write(chld_status[1], "1\n", 2);
1205                         perror("fork");
1206                         _exit(1);
1207                 } else {/* Child */
1208                         close(chld_in[0]);
1209                         close(chld_in[1]);
1210                         close(chld_out[0]);
1211                         close(chld_out[1]);
1212                         close(chld_err[0]);
1213                         close(chld_err[1]);
1214
1215                         close(chld_status[0]);
1216                         debug_print(_("Child: Waiting for grandchild\n"));
1217                         waitpid(pid_gc, NULL, 0);
1218                         debug_print(_("Child: grandchild ended\n"));
1219                         write(chld_status[1], "0\n", 2);
1220                         close(chld_status[1]);
1221                         _exit(0);
1222                 }
1223         } else if (pid_c < (pid_t) 0) {/* Fork error */
1224                 alertpanel_error(_("Could not fork to execute the following "
1225                                    "command:\n%s\n%s"), 
1226                                  cmd, g_strerror(errno));
1227                 return NULL; 
1228         }
1229         /* Parent */
1230
1231         close(chld_in[0]);
1232         if (!(action_type & (ACTION_PIPE_IN | ACTION_OPEN_IN | ACTION_HIDE_IN)))
1233                 close(chld_in[1]);
1234         close(chld_out[1]);
1235         close(chld_err[1]);
1236         close(chld_status[1]);
1237
1238         child_info = g_new0(ChildInfo, 1);
1239
1240         child_info->children = children;
1241         
1242         child_info->pid      = pid_c;
1243         child_info->cmd      = g_strdup(cmd);
1244         child_info->type     = action_type;
1245         child_info->new_out  = FALSE;
1246         child_info->output   = g_string_sized_new(0);
1247         child_info->chld_in  = (action_type & (ACTION_PIPE_IN | ACTION_OPEN_IN
1248                                 | ACTION_HIDE_IN))
1249                                ? chld_in [1] : -1;
1250         child_info->chld_out = chld_out[0];
1251         child_info->chld_err = chld_err[0];
1252         child_info->chld_status = chld_status[0];
1253         child_info->tag_in   = -1;
1254         child_info->tag_out  = gdk_input_add(chld_out[0], GDK_INPUT_READ,
1255                                              catch_output, child_info);
1256         child_info->tag_err  = gdk_input_add(chld_err[0], GDK_INPUT_READ,
1257                                              catch_output, child_info);
1258
1259         if (!(action_type & (ACTION_PIPE_IN | ACTION_PIPE_OUT)))
1260                 return child_info;
1261
1262         child_info->text     = text;
1263
1264         start = 0;
1265         end   = gtk_stext_get_length(GTK_STEXT(text));
1266
1267         if (GTK_EDITABLE(text)->has_selection) {
1268                 start = GTK_EDITABLE(text)->selection_start_pos;
1269                 end   = GTK_EDITABLE(text)->selection_end_pos;
1270                 if (start > end) {
1271                         gint tmp;
1272                         tmp = start;
1273                         start = end;
1274                         end = tmp;
1275                 }
1276                 is_selection = TRUE;
1277                 if (start == end) {
1278                         start = 0;
1279                         end   = gtk_stext_get_length(GTK_STEXT(text));
1280                         is_selection = FALSE;
1281                 }
1282         }
1283
1284         selection = gtk_editable_get_chars(GTK_EDITABLE(text), start, end);
1285
1286
1287         if (action_type & ACTION_PIPE_IN) {
1288                 write(chld_in[1], selection, strlen(selection));
1289                 if (!(action_type & (ACTION_OPEN_IN | ACTION_HIDE_IN)))
1290                         close(chld_in[1]);
1291                 child_info->chld_in = -1; /* No more input */
1292         }
1293         g_free(selection);
1294
1295         gtk_stext_freeze(GTK_STEXT(text));
1296         if (action_type & ACTION_PIPE_OUT) {
1297                 gtk_stext_set_point(GTK_STEXT(text), start);
1298                 gtk_stext_forward_delete(GTK_STEXT(text), end - start);
1299         }
1300
1301         gtk_stext_thaw(GTK_STEXT(text));
1302         
1303         return child_info;
1304 }
1305         
1306 static void kill_children_cb(GtkWidget *widget, gpointer data)
1307 {
1308         GSList *cur;
1309         Children *children = (Children *) data;
1310         ChildInfo *child_info;
1311
1312         for (cur = children->list; cur; cur = cur->next) {
1313                 child_info = (ChildInfo *)(cur->data);
1314                 debug_print(_("Killing child group id %d\n"), child_info->pid);
1315                 if (child_info->pid && (kill(-child_info->pid, SIGTERM) < 0))
1316                                 perror("kill");
1317         }
1318 }
1319
1320 static gint wait_for_children(gpointer data)
1321 {
1322         gboolean new_output;
1323         Children *children = (Children *) data;
1324         ChildInfo *child_info;
1325         GSList *cur;
1326         gint nb = children->nb;
1327
1328         children->nb = 0;
1329                 
1330         cur = children->list;
1331         new_output = FALSE;
1332         while (cur) {
1333                 child_info = (ChildInfo *) cur->data;
1334                 if (child_info->pid)
1335                         children->nb++;
1336                 new_output |= child_info->new_out;
1337                 cur = cur->next;
1338         }
1339
1340         if (!children->dialog && 
1341             (new_output || children->timer))
1342                 create_io_dialog(children);
1343         
1344         if (children->timer) {
1345                 gtk_timeout_remove(children->timer);
1346                 children->timer = 0;
1347         }
1348         children->output |= new_output;
1349
1350         if (new_output || (children->dialog && (nb != children->nb)))
1351                 update_io_dialog(children);
1352
1353         if (children->nb)
1354                 return FALSE;
1355
1356         if (!children->dialog) {
1357                 gtk_widget_set_sensitive(children->window, TRUE);
1358                 free_children(children);
1359         } else if (!children->output) {
1360                 gtk_widget_destroy(children->dialog);
1361         }
1362         return FALSE;
1363 }
1364
1365 static void send_input(GtkWidget *w, gpointer data)
1366 {
1367         Children *children = (Children *) data;
1368         ChildInfo *child_info = (ChildInfo *) children->list->data;
1369         
1370         child_info->tag_in = gdk_input_add(child_info->chld_in,
1371                                            GDK_INPUT_WRITE,
1372                                            catch_input, children);
1373         gtk_widget_set_sensitive(children->input_hbox, FALSE);
1374 }
1375
1376 static gint delete_io_dialog_cb(GtkWidget *w, GdkEvent *e, gpointer data)
1377 {
1378         hide_io_dialog_cb(w, data);
1379         return TRUE;
1380 }
1381
1382 static void hide_io_dialog_cb(GtkWidget *w, gpointer data)
1383 {
1384
1385         Children *children = (Children *) data;
1386
1387         if (!children->nb) {
1388                 gtk_widget_set_sensitive(children->window, TRUE);
1389                 gtk_signal_disconnect_by_data(GTK_OBJECT(children->dialog),
1390                                               children);
1391                 gtk_widget_destroy(children->dialog);
1392                 free_children(children);
1393         }
1394 }
1395
1396 static void childinfo_close_pipes(ChildInfo *child_info)
1397 {
1398         if (child_info->tag_in > 0)
1399                 gdk_input_remove(child_info->tag_in);
1400         gdk_input_remove(child_info->tag_out);
1401         gdk_input_remove(child_info->tag_err);
1402
1403         if (child_info->chld_in >= 0)
1404                 close(child_info->chld_in);
1405         close(child_info->chld_out);
1406         close(child_info->chld_err);
1407         close(child_info->chld_status);
1408 }
1409
1410 static void free_children(Children *children)
1411 {
1412         GSList *cur;
1413         ChildInfo *child_info;
1414
1415         debug_print(_("Freeing children data %x\n"), (guint) children);
1416                                 
1417         g_free(children->action);
1418         for (cur = children->list; cur;) {
1419                 child_info = (ChildInfo *) cur->data;
1420                 g_free(child_info->cmd);
1421                 g_string_free(child_info->output, TRUE);
1422                 children->list = g_slist_remove(children->list, child_info);
1423                 g_free(child_info);
1424                 cur = children->list;
1425         }
1426         g_free(children);
1427 }       
1428
1429 static void update_io_dialog(Children *children)
1430 {
1431         GSList *cur;
1432
1433         debug_print(_("Updating actions input/output dialog.\n"));
1434
1435         if (!children->nb) {
1436                 gtk_widget_set_sensitive(children->abort_btn, FALSE);
1437                 gtk_widget_set_sensitive(children->hide_btn, TRUE);
1438                 if (children->input_hbox)
1439                         gtk_widget_set_sensitive(children->input_hbox, FALSE);
1440         }
1441
1442         if (children->output) {
1443                 GtkWidget *text = children->text;
1444                 gchar *caption;
1445                 ChildInfo *child_info;
1446
1447                 gtk_widget_show(children->scrolledwin);
1448                 gtk_text_freeze(GTK_TEXT(text));
1449                 gtk_text_set_point(GTK_TEXT(text), 0);
1450                 gtk_text_forward_delete(GTK_TEXT(text), 
1451                                         gtk_text_get_length(GTK_TEXT(text)));
1452                 for (cur = children->list; cur; cur = cur->next)
1453                 {
1454                         child_info = (ChildInfo *) cur->data;
1455                         if (child_info->pid)
1456                                 caption = g_strdup_printf(_("--- Running: %s\n")
1457                                                     , child_info->cmd);
1458                         else
1459                                 caption = g_strdup_printf(_("--- Ended: %s\n")
1460                                                     , child_info->cmd);
1461                         
1462                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1463                                          caption, -1);
1464                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1465                                          child_info->output->str, -1);
1466                         g_free(caption);
1467                         child_info->new_out = FALSE;
1468                 }
1469                 gtk_text_thaw(GTK_TEXT(text));
1470         }
1471 }
1472         
1473         
1474 static void create_io_dialog(Children *children)
1475 {
1476         GtkWidget *dialog;
1477         GtkWidget *entry;
1478         GtkWidget *input_hbox;
1479         GtkWidget *send_button;
1480         GtkWidget *label;
1481         GtkWidget *text;
1482         GtkWidget *scrolledwin;
1483         GtkWidget *hbox;
1484         GtkWidget *abort_button;
1485         GtkWidget *hide_button;
1486         
1487         debug_print(_("Creating actions dialog\n"));
1488
1489         dialog = gtk_dialog_new();
1490         label = gtk_label_new(children->action);
1491         gtk_misc_set_padding(GTK_MISC(label), 8, 8);
1492         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, FALSE, 
1493                            FALSE, 0);
1494
1495         gtk_window_set_title(GTK_WINDOW(dialog), _("Actions' input/output"));
1496         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
1497                         GTK_SIGNAL_FUNC(delete_io_dialog_cb), children);
1498         gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
1499                         GTK_SIGNAL_FUNC(hide_io_dialog_cb),
1500                         children);
1501
1502         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
1503         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
1504                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
1505         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), scrolledwin, TRUE,
1506                            TRUE, 0);
1507         gtk_widget_set_usize(scrolledwin, 600, 200);
1508         gtk_widget_hide(scrolledwin);
1509
1510         text = gtk_text_new(gtk_scrolled_window_get_hadjustment
1511                             (GTK_SCROLLED_WINDOW(scrolledwin)),
1512                             gtk_scrolled_window_get_vadjustment
1513                             (GTK_SCROLLED_WINDOW(scrolledwin)));
1514         gtk_text_set_editable(GTK_TEXT(text), FALSE);
1515         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
1516         gtk_widget_show(text);
1517
1518         if (children->open_in) {
1519                 input_hbox = gtk_hbox_new(FALSE, 8);
1520                 gtk_widget_show(input_hbox);
1521
1522                 entry = gtk_entry_new();
1523                 gtk_signal_connect(GTK_OBJECT(entry), "activate",
1524                                    GTK_SIGNAL_FUNC(send_input), children);
1525                 gtk_box_pack_start(GTK_BOX(input_hbox), entry, TRUE, TRUE, 8);
1526                 if (children->open_in & ACTION_HIDE_IN)
1527                         gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
1528                 gtk_widget_show(entry);
1529
1530                 send_button = gtk_button_new_with_label(_("Send"));
1531                 gtk_signal_connect(GTK_OBJECT(send_button), "clicked",
1532                                    GTK_SIGNAL_FUNC(send_input), children);
1533                 gtk_box_pack_start(GTK_BOX(input_hbox), send_button, FALSE, 
1534                                    FALSE, 8);
1535                 gtk_widget_show(send_button);
1536
1537                 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), 
1538                                 input_hbox, FALSE, FALSE, 8);
1539                 gtk_widget_grab_focus(entry);
1540         }
1541
1542         hbox = gtk_hbox_new(TRUE, 0);
1543         gtk_widget_show(hbox);
1544
1545         abort_button = gtk_button_new_with_label(_("Abort actions"));
1546         gtk_signal_connect(GTK_OBJECT(abort_button), "clicked",
1547                         GTK_SIGNAL_FUNC(kill_children_cb), children);
1548         gtk_box_pack_start(GTK_BOX(hbox), abort_button, TRUE, TRUE, 8);
1549         gtk_widget_show(abort_button);
1550
1551         hide_button = gtk_button_new_with_label(_("Close window"));
1552         gtk_signal_connect(GTK_OBJECT(hide_button), "clicked",
1553                         GTK_SIGNAL_FUNC(hide_io_dialog_cb), children);
1554         gtk_box_pack_start(GTK_BOX(hbox), hide_button, TRUE, TRUE, 8);
1555         gtk_widget_show(hide_button);
1556
1557         if (children->nb)
1558                 gtk_widget_set_sensitive(hide_button, FALSE);
1559
1560         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 
1561                           hbox);
1562
1563         children->dialog      = dialog;
1564         children->scrolledwin = scrolledwin;
1565         children->text        = text;
1566         children->input_hbox  = children->open_in ? input_hbox : NULL;
1567         children->input_entry = children->open_in ? entry : NULL;
1568         children->abort_btn   = abort_button;
1569         children->hide_btn    = hide_button;
1570
1571         gtk_widget_show(dialog);
1572 }
1573
1574 static void catch_status(gpointer data, gint source, GdkInputCondition cond)
1575 {
1576         ChildInfo *child_info = (ChildInfo *) data;
1577         gchar buf;
1578         gint c;
1579
1580         gdk_input_remove(child_info->tag_status);
1581
1582         c = read(source, &buf, 1);
1583         debug_print(_("Child returned %c\n"), buf);
1584
1585         waitpid(-child_info->pid, NULL, 0);
1586         childinfo_close_pipes(child_info);
1587         child_info->pid = 0;
1588         
1589         wait_for_children(child_info->children);
1590 }
1591         
1592 static void catch_input(gpointer data, gint source, GdkInputCondition cond)
1593 {
1594         Children *children = (Children *) data;
1595         ChildInfo *child_info = (ChildInfo *) children->list->data;
1596         gchar *input;
1597         gint c;
1598
1599         debug_print(_("Sending input to grand child.\n"));
1600         if (!(cond && GDK_INPUT_WRITE))
1601                 return;
1602         
1603         gdk_input_remove(child_info->tag_in);
1604         child_info->tag_in = -1;
1605
1606         input = gtk_editable_get_chars(GTK_EDITABLE(children->input_entry), 0,
1607                                        -1);
1608         c = write(child_info->chld_in, input, strlen(input));
1609
1610         g_free(input);
1611
1612         write(child_info->chld_in, "\n", 2);
1613
1614         gtk_entry_set_text(GTK_ENTRY(children->input_entry), "");
1615         gtk_widget_set_sensitive(children->input_hbox, TRUE);
1616         debug_print(_("Input to grand child sent.\n"));
1617 }
1618
1619 static void catch_output(gpointer data, gint source, GdkInputCondition cond)
1620 {
1621         ChildInfo *child_info = (ChildInfo *) data;
1622         gint c, i;
1623         gchar buf[PREFSBUFSIZE];
1624
1625         debug_print(_("Catching grand child's output.\n"));
1626         if (child_info->type & ACTION_PIPE_OUT
1627             && source == child_info->chld_out) {
1628                 gboolean is_selection = FALSE;
1629                 GtkWidget *text = child_info->text;
1630                 if (GTK_EDITABLE(text)->has_selection)
1631                         is_selection = TRUE;
1632                 gtk_stext_freeze(GTK_STEXT(text));
1633                 while (TRUE) {
1634                         c =read(source, buf, PREFSBUFSIZE - 1);
1635                         if (c == 0)
1636                                 break;
1637                         gtk_stext_insert(GTK_STEXT(text), NULL, NULL, NULL,
1638                                          buf, c);
1639                 }
1640                 if (is_selection) {
1641                         /* Using the select_region draws things. Should not.
1642                          * so we just change selection position and 
1643                          * defere drawing when thawing. Hack?
1644                          */
1645                         GTK_EDITABLE(text)->selection_end_pos = 
1646                                         gtk_stext_get_point(GTK_STEXT(text));
1647                 }
1648                 gtk_stext_thaw(GTK_STEXT(child_info->text));
1649         } else {
1650                 while (TRUE) {
1651                         c = read(source, buf, PREFSBUFSIZE - 1);
1652                         if (c == 0) 
1653                                 break;
1654                         for (i = 0; i < c; i++)
1655                                 child_info->output = g_string_append_c(
1656                                                 child_info->output, buf[i]);
1657                         child_info->new_out = TRUE;
1658                 }
1659         }
1660 }