renamed: 'Tools', move 'delete duplicate' to 'Tools', re-arrange 'Tools' menu
[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, "/Tools/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                 NULL, NULL, NULL, 0, "<Branch>"};
918         ifentry.path = branch_path;
919         menuitem = gtk_item_factory_get_item(ifactory, branch_path);    
920         g_return_if_fail(menuitem);
921         /* FIXME: is there a better way to remove unknown submenu items? */
922         /* Deleting and recreating the parent looses the translation */
923         menu = GTK_WIDGET(GTK_MENU_ITEM(menuitem)->submenu);
924         if (GTK_MENU_SHELL(menu)->children) {
925                 GList *amenu, *alist;
926                 for (amenu = (GTK_MENU_SHELL(menu)->children); amenu; ) {
927                         alist = amenu->next;
928                         gtk_widget_destroy(GTK_WIDGET(amenu->data));
929                         amenu = alist;
930                 }
931         }
932
933         ifentry.accelerator     = NULL;
934         ifentry.callback_action = 0;
935         ifentry.callback        = callback;
936         ifentry.item_type       = NULL;
937
938         for (cur = prefs_common.actionslst; cur; cur = cur->next) {
939                 action   = g_strdup((gchar *) cur->data);
940                 action_p = strstr(action, ": ");
941                 if (action_p && action_p[2] &&
942                     get_action_type(&action_p[2]) != ACTION_ERROR) {
943                         action_p[0] = 0x00;
944                         menu_path = g_strdup_printf("%s/%s", branch_path, 
945                                                     action);
946                         ifentry.path = menu_path;
947                         gtk_item_factory_create_item(ifactory, &ifentry, data,
948                                                      1);
949                         g_free(menu_path);
950                 }
951                 g_free(action);
952                 ifentry.callback_action++;      
953         }
954 }
955
956 static void compose_actions_execute_cb  (Compose        *compose,
957                                          guint           action_nb,
958                                          GtkWidget      *widget)
959 {
960         gchar *buf, *action;
961         guint action_type;      
962
963         g_return_if_fail(action_nb < g_slist_length(prefs_common.actionslst));
964         
965         buf = (gchar *) g_slist_nth_data(prefs_common.actionslst, action_nb);
966         
967         g_return_if_fail(buf);
968         g_return_if_fail(action = strstr(buf, ": "));
969
970         /* Point to the beginning of the command-line */
971         action++;
972         action++;
973         
974         action_type = get_action_type(action);
975         if (action_type & ACTION_MULTIPLE ||
976             action_type & ACTION_SINGLE   ) {           
977                 alertpanel_warning(_("The selected action is not a pipe "
978                                      "action.\n You can only use pipe actions "
979                                      "when composing a message."));
980                 return;
981         }
982
983         execute_actions(action, compose->window, NULL, compose->text);
984 }
985
986 static void mainwin_actions_execute_cb  (MainWindow     *mainwin,
987                                          guint           action_nb,
988                                          GtkWidget      *widget)
989 {
990         MessageView *messageview = mainwin->messageview;
991         GtkWidget   *text = NULL;
992         gchar       *buf,
993                     *action;
994         
995         g_return_if_fail (action_nb < g_slist_length(prefs_common.actionslst));
996
997         buf = (gchar *) g_slist_nth_data(prefs_common.actionslst, action_nb);
998         
999         g_return_if_fail(buf);
1000         g_return_if_fail(action = strstr(buf, ": "));
1001
1002         /* Point to the beginning of the command-line */
1003         action++;
1004         action++;
1005
1006         switch (messageview->type) {
1007                 case MVIEW_TEXT:
1008                         if (messageview->textview && 
1009                             messageview->textview->text)
1010                                 text = messageview->textview->text;
1011                         break;
1012                 case MVIEW_MIME:
1013                         if (messageview->mimeview &&
1014                             messageview->mimeview->type == MIMEVIEW_TEXT &&
1015                             messageview->mimeview->textview &&
1016                             messageview->mimeview->textview->text)
1017                                 text = messageview->mimeview->textview->text;
1018                         break;
1019         }
1020
1021         execute_actions(action, mainwin->window,
1022                         GTK_CTREE(mainwin->summaryview->ctree), text);
1023 }
1024
1025 static gboolean execute_actions(gchar *action, 
1026                                 GtkWidget *window,
1027                                 GtkCTree *ctree, 
1028                                 GtkWidget *text)
1029 {
1030         GList *cur, *selection;
1031         GSList *children_list = NULL;
1032         gint is_ok  = TRUE;
1033         gint selection_len;
1034         Children *children;
1035         ChildInfo *child_info;
1036         gint action_type;
1037         MsgInfo *msginfo;
1038         gchar *cmd;
1039
1040         g_return_val_if_fail(action && *action, FALSE);
1041
1042         action_type = get_action_type(action);
1043
1044         if (action_type == ACTION_ERROR)
1045                 return FALSE;         /* ERR: syntax error */
1046
1047         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE) && 
1048             !(ctree && GTK_CLIST(ctree)->selection))
1049                 return FALSE;         /* ERR: file command without selection */
1050
1051         if (ctree) {
1052                 selection = GTK_CLIST(ctree)->selection;
1053                 selection_len = g_list_length(selection);
1054         }
1055         
1056         if ((action_type & (ACTION_PIPE_OUT | ACTION_PIPE_IN))) {
1057                 if (ctree && selection_len > 1)
1058                         return FALSE; /* ERR: pipe + multiple selection */
1059                 if (!text)
1060                         return FALSE; /* ERR: pipe and no displayed text */
1061         }
1062
1063         if (!(action_type & ACTION_ASYNC) && window) {
1064                 gtk_widget_set_sensitive(window, FALSE);
1065         }
1066
1067         children = g_new0(Children, 1);
1068
1069         if (action_type & ACTION_SINGLE) {
1070                 for (cur = selection; cur && is_ok == TRUE; cur = cur->next) {
1071                         msginfo = gtk_ctree_node_get_row_data(ctree,
1072                                         GTK_CTREE_NODE(cur->data));
1073                         if (!msginfo) {
1074                                 is_ok  = FALSE; /* ERR: msginfo missing */
1075                                 break;
1076                         }
1077                         cmd = parse_action_cmd(action, msginfo, ctree);
1078                         if (!cmd) {
1079                                 debug_print(_("Action command error\n"));
1080                                 is_ok  = FALSE; /* ERR: incorrect command */
1081                                 break;
1082                         }
1083                         if ((child_info = fork_child(cmd, action_type, text,
1084                                                      children))) {
1085                                 children_list = g_slist_append(children_list,
1086                                                                child_info);
1087                                 children->open_in = (selection_len == 1) ? 
1088                                                     (action_type & 
1089                                                      (ACTION_OPEN_IN |
1090                                                       ACTION_HIDE_IN)) : 0;
1091                         }
1092                         g_free(cmd);
1093                 }
1094         } else {
1095                 cmd = parse_action_cmd(action, NULL, ctree);
1096                 if (cmd) {
1097                         if ((child_info = fork_child(cmd, action_type, text,
1098                                                      children))) {
1099                                 children_list = g_slist_append(children_list,
1100                                                                child_info);
1101                                 children->open_in = action_type &
1102                                                     (ACTION_OPEN_IN |
1103                                                      ACTION_HIDE_IN);
1104                         }
1105                         g_free(cmd);
1106                 } else
1107                         is_ok  = FALSE;         /* ERR: incorrect command */
1108         }
1109
1110         if (!children_list) {
1111                  /* If not waiting for children, return */
1112                 g_free(children);
1113                 if (!(action_type & ACTION_ASYNC) && window) {
1114                         gtk_widget_set_sensitive(window, TRUE);
1115                 }
1116         } else {
1117                 GSList *cur;
1118                 
1119                 children->action  = g_strdup(action);
1120                 children->window  = window;
1121                 children->dialog  = NULL;
1122                 children->list    = children_list;
1123                 children->nb      = g_slist_length(children_list);
1124
1125                 for (cur = children_list; cur; cur = cur->next) {
1126                         child_info = (ChildInfo *) cur->data;
1127                         child_info->tag_status = 
1128                                 gdk_input_add(child_info->chld_status,
1129                                               GDK_INPUT_READ,
1130                                               catch_status, child_info);
1131                 }
1132                 children->timer = children->open_in ? 0 :
1133                                   gtk_timeout_add(WAIT_LAP, wait_for_children,
1134                                                   children);
1135         }
1136         if (children->open_in)
1137                 create_io_dialog(children);
1138
1139         return is_ok ;
1140 }
1141
1142 ChildInfo *fork_child(gchar *cmd,
1143                       gint action_type,
1144                       GtkWidget *text,
1145                       Children *children)
1146 {
1147         gint chld_in[2], chld_out[2], chld_err[2], chld_status[2];
1148         gchar **cmdline;
1149         gint start, end, is_selection;
1150         gchar *selection;
1151         pid_t pid_c, pid_gc;
1152         ChildInfo *child_info;
1153
1154         if (action_type & ACTION_ASYNC) {
1155                 execute_command_line(cmd, TRUE);
1156                 return NULL; /* Asynchronous command */
1157         }
1158
1159         if (pipe(chld_in) || pipe(chld_out) || pipe(chld_err) ||
1160             pipe(chld_status)) {
1161                 alertpanel_error(_("Command could not started. Pipe creation"
1162                                    " failed.\n%s"), g_strerror(errno));
1163                 return NULL; /* Pipe error */
1164         }
1165         
1166         debug_print(_("Forking child and grandchild.\n"));
1167
1168         pid_c = fork();
1169         if (pid_c == (pid_t) 0) {/* Child */
1170                 if (setpgid(0, 0))
1171                         perror("setpgid");
1172
1173                 close(ConnectionNumber(gdk_display));
1174
1175                 pid_gc = fork();
1176
1177                 if (pid_gc == 0) {
1178                         if (setpgid(0, getppid()))
1179                                 perror("setpgid");
1180                         if (action_type & 
1181                            (ACTION_PIPE_IN | ACTION_OPEN_IN | ACTION_HIDE_IN)) {
1182                         close(fileno(stdin));
1183                         dup  (chld_in[0]);
1184                         }
1185                         close(chld_in[0]);
1186                         close(chld_in[1]);
1187
1188                         close(fileno(stdout));
1189                         dup  (chld_out[1]);
1190                         close(chld_out[0]);
1191                         close(chld_out[1]);
1192
1193                         close(fileno(stderr));
1194                         dup  (chld_err[1]);
1195                         close(chld_err[0]);
1196                         close(chld_err[1]);
1197
1198                         cmdline = strsplit_with_quote(cmd, " ", 1024);
1199
1200                         execvp(cmdline[0], cmdline);
1201                         perror("execvp");
1202                         g_strfreev(cmdline);
1203
1204                         _exit(1);
1205                 } else if (pid_gc < (pid_t) 0) {/* Fork erro */
1206                         write(chld_status[1], "1\n", 2);
1207                         perror("fork");
1208                         _exit(1);
1209                 } else {/* Child */
1210                         close(chld_in[0]);
1211                         close(chld_in[1]);
1212                         close(chld_out[0]);
1213                         close(chld_out[1]);
1214                         close(chld_err[0]);
1215                         close(chld_err[1]);
1216
1217                         close(chld_status[0]);
1218                         debug_print(_("Child: Waiting for grandchild\n"));
1219                         waitpid(pid_gc, NULL, 0);
1220                         debug_print(_("Child: grandchild ended\n"));
1221                         write(chld_status[1], "0\n", 2);
1222                         close(chld_status[1]);
1223                         _exit(0);
1224                 }
1225         } else if (pid_c < (pid_t) 0) {/* Fork error */
1226                 alertpanel_error(_("Could not fork to execute the following "
1227                                    "command:\n%s\n%s"), 
1228                                  cmd, g_strerror(errno));
1229                 return NULL; 
1230         }
1231         /* Parent */
1232
1233         close(chld_in[0]);
1234         if (!(action_type & (ACTION_PIPE_IN | ACTION_OPEN_IN | ACTION_HIDE_IN)))
1235                 close(chld_in[1]);
1236         close(chld_out[1]);
1237         close(chld_err[1]);
1238         close(chld_status[1]);
1239
1240         child_info = g_new0(ChildInfo, 1);
1241
1242         child_info->children = children;
1243         
1244         child_info->pid      = pid_c;
1245         child_info->cmd      = g_strdup(cmd);
1246         child_info->type     = action_type;
1247         child_info->new_out  = FALSE;
1248         child_info->output   = g_string_sized_new(0);
1249         child_info->chld_in  = (action_type & (ACTION_PIPE_IN | ACTION_OPEN_IN
1250                                 | ACTION_HIDE_IN))
1251                                ? chld_in [1] : -1;
1252         child_info->chld_out = chld_out[0];
1253         child_info->chld_err = chld_err[0];
1254         child_info->chld_status = chld_status[0];
1255         child_info->tag_in   = -1;
1256         child_info->tag_out  = gdk_input_add(chld_out[0], GDK_INPUT_READ,
1257                                              catch_output, child_info);
1258         child_info->tag_err  = gdk_input_add(chld_err[0], GDK_INPUT_READ,
1259                                              catch_output, child_info);
1260
1261         if (!(action_type & (ACTION_PIPE_IN | ACTION_PIPE_OUT)))
1262                 return child_info;
1263
1264         child_info->text     = text;
1265
1266         start = 0;
1267         end   = gtk_stext_get_length(GTK_STEXT(text));
1268
1269         if (GTK_EDITABLE(text)->has_selection) {
1270                 start = GTK_EDITABLE(text)->selection_start_pos;
1271                 end   = GTK_EDITABLE(text)->selection_end_pos;
1272                 if (start > end) {
1273                         gint tmp;
1274                         tmp = start;
1275                         start = end;
1276                         end = tmp;
1277                 }
1278                 is_selection = TRUE;
1279                 if (start == end) {
1280                         start = 0;
1281                         end   = gtk_stext_get_length(GTK_STEXT(text));
1282                         is_selection = FALSE;
1283                 }
1284         }
1285
1286         selection = gtk_editable_get_chars(GTK_EDITABLE(text), start, end);
1287
1288
1289         if (action_type & ACTION_PIPE_IN) {
1290                 write(chld_in[1], selection, strlen(selection));
1291                 if (!(action_type & (ACTION_OPEN_IN | ACTION_HIDE_IN)))
1292                         close(chld_in[1]);
1293                 child_info->chld_in = -1; /* No more input */
1294         }
1295         g_free(selection);
1296
1297         gtk_stext_freeze(GTK_STEXT(text));
1298         if (action_type & ACTION_PIPE_OUT) {
1299                 gtk_stext_set_point(GTK_STEXT(text), start);
1300                 gtk_stext_forward_delete(GTK_STEXT(text), end - start);
1301         }
1302
1303         gtk_stext_thaw(GTK_STEXT(text));
1304         
1305         return child_info;
1306 }
1307         
1308 static void kill_children_cb(GtkWidget *widget, gpointer data)
1309 {
1310         GSList *cur;
1311         Children *children = (Children *) data;
1312         ChildInfo *child_info;
1313
1314         for (cur = children->list; cur; cur = cur->next) {
1315                 child_info = (ChildInfo *)(cur->data);
1316                 debug_print(_("Killing child group id %d\n"), child_info->pid);
1317                 if (child_info->pid && (kill(-child_info->pid, SIGTERM) < 0))
1318                                 perror("kill");
1319         }
1320 }
1321
1322 static gint wait_for_children(gpointer data)
1323 {
1324         gboolean new_output;
1325         Children *children = (Children *) data;
1326         ChildInfo *child_info;
1327         GSList *cur;
1328         gint nb = children->nb;
1329
1330         children->nb = 0;
1331                 
1332         cur = children->list;
1333         new_output = FALSE;
1334         while (cur) {
1335                 child_info = (ChildInfo *) cur->data;
1336                 if (child_info->pid)
1337                         children->nb++;
1338                 new_output |= child_info->new_out;
1339                 cur = cur->next;
1340         }
1341
1342         if (!children->dialog && 
1343             (new_output || children->timer))
1344                 create_io_dialog(children);
1345         
1346         if (children->timer) {
1347                 gtk_timeout_remove(children->timer);
1348                 children->timer = 0;
1349         }
1350         children->output |= new_output;
1351
1352         if (new_output || (children->dialog && (nb != children->nb)))
1353                 update_io_dialog(children);
1354
1355         if (children->nb)
1356                 return FALSE;
1357
1358         if (!children->dialog) {
1359                 gtk_widget_set_sensitive(children->window, TRUE);
1360                 free_children(children);
1361         } else if (!children->output) {
1362                 gtk_widget_destroy(children->dialog);
1363         }
1364         return FALSE;
1365 }
1366
1367 static void send_input(GtkWidget *w, gpointer data)
1368 {
1369         Children *children = (Children *) data;
1370         ChildInfo *child_info = (ChildInfo *) children->list->data;
1371         
1372         child_info->tag_in = gdk_input_add(child_info->chld_in,
1373                                            GDK_INPUT_WRITE,
1374                                            catch_input, children);
1375         gtk_widget_set_sensitive(children->input_hbox, FALSE);
1376 }
1377
1378 static gint delete_io_dialog_cb(GtkWidget *w, GdkEvent *e, gpointer data)
1379 {
1380         hide_io_dialog_cb(w, data);
1381         return TRUE;
1382 }
1383
1384 static void hide_io_dialog_cb(GtkWidget *w, gpointer data)
1385 {
1386
1387         Children *children = (Children *) data;
1388
1389         if (!children->nb) {
1390                 gtk_widget_set_sensitive(children->window, TRUE);
1391                 gtk_signal_disconnect_by_data(GTK_OBJECT(children->dialog),
1392                                               children);
1393                 gtk_widget_destroy(children->dialog);
1394                 free_children(children);
1395         }
1396 }
1397
1398 static void childinfo_close_pipes(ChildInfo *child_info)
1399 {
1400         if (child_info->tag_in > 0)
1401                 gdk_input_remove(child_info->tag_in);
1402         gdk_input_remove(child_info->tag_out);
1403         gdk_input_remove(child_info->tag_err);
1404
1405         if (child_info->chld_in >= 0)
1406                 close(child_info->chld_in);
1407         close(child_info->chld_out);
1408         close(child_info->chld_err);
1409         close(child_info->chld_status);
1410 }
1411
1412 static void free_children(Children *children)
1413 {
1414         GSList *cur;
1415         ChildInfo *child_info;
1416
1417         debug_print(_("Freeing children data %x\n"), (guint) children);
1418                                 
1419         g_free(children->action);
1420         for (cur = children->list; cur;) {
1421                 child_info = (ChildInfo *) cur->data;
1422                 g_free(child_info->cmd);
1423                 g_string_free(child_info->output, TRUE);
1424                 children->list = g_slist_remove(children->list, child_info);
1425                 g_free(child_info);
1426                 cur = children->list;
1427         }
1428         g_free(children);
1429 }       
1430
1431 static void update_io_dialog(Children *children)
1432 {
1433         GSList *cur;
1434
1435         debug_print(_("Updating actions input/output dialog.\n"));
1436
1437         if (!children->nb) {
1438                 gtk_widget_set_sensitive(children->abort_btn, FALSE);
1439                 gtk_widget_set_sensitive(children->hide_btn, TRUE);
1440                 gtk_widget_set_sensitive(children->window, TRUE);
1441                 if (children->input_hbox)
1442                         gtk_widget_set_sensitive(children->input_hbox, FALSE);
1443         }
1444
1445         if (children->output) {
1446                 GtkWidget *text = children->text;
1447                 gchar *caption;
1448                 ChildInfo *child_info;
1449
1450                 gtk_widget_show(children->scrolledwin);
1451                 gtk_text_freeze(GTK_TEXT(text));
1452                 gtk_text_set_point(GTK_TEXT(text), 0);
1453                 gtk_text_forward_delete(GTK_TEXT(text), 
1454                                         gtk_text_get_length(GTK_TEXT(text)));
1455                 for (cur = children->list; cur; cur = cur->next)
1456                 {
1457                         child_info = (ChildInfo *) cur->data;
1458                         if (child_info->pid)
1459                                 caption = g_strdup_printf(_("--- Running: %s\n")
1460                                                     , child_info->cmd);
1461                         else
1462                                 caption = g_strdup_printf(_("--- Ended: %s\n")
1463                                                     , child_info->cmd);
1464                         
1465                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1466                                          caption, -1);
1467                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1468                                          child_info->output->str, -1);
1469                         g_free(caption);
1470                         child_info->new_out = FALSE;
1471                 }
1472                 gtk_text_thaw(GTK_TEXT(text));
1473         }
1474 }
1475         
1476         
1477 static void create_io_dialog(Children *children)
1478 {
1479         GtkWidget *dialog;
1480         GtkWidget *entry;
1481         GtkWidget *input_hbox;
1482         GtkWidget *send_button;
1483         GtkWidget *label;
1484         GtkWidget *text;
1485         GtkWidget *scrolledwin;
1486         GtkWidget *hbox;
1487         GtkWidget *abort_button;
1488         GtkWidget *hide_button;
1489         
1490         debug_print(_("Creating actions dialog\n"));
1491
1492         dialog = gtk_dialog_new();
1493         label = gtk_label_new(children->action);
1494         gtk_misc_set_padding(GTK_MISC(label), 8, 8);
1495         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, FALSE, 
1496                            FALSE, 0);
1497
1498         gtk_window_set_title(GTK_WINDOW(dialog), _("Actions' input/output"));
1499         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
1500                         GTK_SIGNAL_FUNC(delete_io_dialog_cb), children);
1501         gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
1502                         GTK_SIGNAL_FUNC(hide_io_dialog_cb),
1503                         children);
1504
1505         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
1506         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
1507                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
1508         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), scrolledwin, TRUE,
1509                            TRUE, 0);
1510         gtk_widget_set_usize(scrolledwin, 600, 200);
1511         gtk_widget_hide(scrolledwin);
1512
1513         text = gtk_text_new(gtk_scrolled_window_get_hadjustment
1514                             (GTK_SCROLLED_WINDOW(scrolledwin)),
1515                             gtk_scrolled_window_get_vadjustment
1516                             (GTK_SCROLLED_WINDOW(scrolledwin)));
1517         gtk_text_set_editable(GTK_TEXT(text), FALSE);
1518         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
1519         gtk_widget_show(text);
1520
1521         if (children->open_in) {
1522                 input_hbox = gtk_hbox_new(FALSE, 8);
1523                 gtk_widget_show(input_hbox);
1524
1525                 entry = gtk_entry_new();
1526                 gtk_signal_connect(GTK_OBJECT(entry), "activate",
1527                                    GTK_SIGNAL_FUNC(send_input), children);
1528                 gtk_box_pack_start(GTK_BOX(input_hbox), entry, TRUE, TRUE, 8);
1529                 if (children->open_in & ACTION_HIDE_IN)
1530                         gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
1531                 gtk_widget_show(entry);
1532
1533                 send_button = gtk_button_new_with_label(_("Send"));
1534                 gtk_signal_connect(GTK_OBJECT(send_button), "clicked",
1535                                    GTK_SIGNAL_FUNC(send_input), children);
1536                 gtk_box_pack_start(GTK_BOX(input_hbox), send_button, FALSE, 
1537                                    FALSE, 8);
1538                 gtk_widget_show(send_button);
1539
1540                 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), 
1541                                 input_hbox, FALSE, FALSE, 8);
1542                 gtk_widget_grab_focus(entry);
1543         }
1544
1545         hbox = gtk_hbox_new(TRUE, 0);
1546         gtk_widget_show(hbox);
1547
1548         abort_button = gtk_button_new_with_label(_("Abort actions"));
1549         gtk_signal_connect(GTK_OBJECT(abort_button), "clicked",
1550                         GTK_SIGNAL_FUNC(kill_children_cb), children);
1551         gtk_box_pack_start(GTK_BOX(hbox), abort_button, TRUE, TRUE, 8);
1552         gtk_widget_show(abort_button);
1553
1554         hide_button = gtk_button_new_with_label(_("Close window"));
1555         gtk_signal_connect(GTK_OBJECT(hide_button), "clicked",
1556                         GTK_SIGNAL_FUNC(hide_io_dialog_cb), children);
1557         gtk_box_pack_start(GTK_BOX(hbox), hide_button, TRUE, TRUE, 8);
1558         gtk_widget_show(hide_button);
1559
1560         if (children->nb)
1561                 gtk_widget_set_sensitive(hide_button, FALSE);
1562
1563         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 
1564                           hbox);
1565
1566         children->dialog      = dialog;
1567         children->scrolledwin = scrolledwin;
1568         children->text        = text;
1569         children->input_hbox  = children->open_in ? input_hbox : NULL;
1570         children->input_entry = children->open_in ? entry : NULL;
1571         children->abort_btn   = abort_button;
1572         children->hide_btn    = hide_button;
1573
1574         gtk_widget_show(dialog);
1575 }
1576
1577 static void catch_status(gpointer data, gint source, GdkInputCondition cond)
1578 {
1579         ChildInfo *child_info = (ChildInfo *) data;
1580         gchar buf;
1581         gint c;
1582
1583         gdk_input_remove(child_info->tag_status);
1584
1585         c = read(source, &buf, 1);
1586         debug_print(_("Child returned %c\n"), buf);
1587
1588         waitpid(-child_info->pid, NULL, 0);
1589         childinfo_close_pipes(child_info);
1590         child_info->pid = 0;
1591         
1592         wait_for_children(child_info->children);
1593 }
1594         
1595 static void catch_input(gpointer data, gint source, GdkInputCondition cond)
1596 {
1597         Children *children = (Children *) data;
1598         ChildInfo *child_info = (ChildInfo *) children->list->data;
1599         gchar *input;
1600         gint c;
1601
1602         debug_print(_("Sending input to grand child.\n"));
1603         if (!(cond && GDK_INPUT_WRITE))
1604                 return;
1605         
1606         gdk_input_remove(child_info->tag_in);
1607         child_info->tag_in = -1;
1608
1609         input = gtk_editable_get_chars(GTK_EDITABLE(children->input_entry), 0,
1610                                        -1);
1611         c = write(child_info->chld_in, input, strlen(input));
1612
1613         g_free(input);
1614
1615         write(child_info->chld_in, "\n", 2);
1616
1617         gtk_entry_set_text(GTK_ENTRY(children->input_entry), "");
1618         gtk_widget_set_sensitive(children->input_hbox, TRUE);
1619         debug_print(_("Input to grand child sent.\n"));
1620 }
1621
1622 static void catch_output(gpointer data, gint source, GdkInputCondition cond)
1623 {
1624         ChildInfo *child_info = (ChildInfo *) data;
1625         gint c, i;
1626         gchar buf[PREFSBUFSIZE];
1627
1628         debug_print(_("Catching grand child's output.\n"));
1629         if (child_info->type & ACTION_PIPE_OUT
1630             && source == child_info->chld_out) {
1631                 gboolean is_selection = FALSE;
1632                 GtkWidget *text = child_info->text;
1633                 if (GTK_EDITABLE(text)->has_selection)
1634                         is_selection = TRUE;
1635                 gtk_stext_freeze(GTK_STEXT(text));
1636                 while (TRUE) {
1637                         c =read(source, buf, PREFSBUFSIZE - 1);
1638                         if (c == 0)
1639                                 break;
1640                         gtk_stext_insert(GTK_STEXT(text), NULL, NULL, NULL,
1641                                          buf, c);
1642                 }
1643                 if (is_selection) {
1644                         /* Using the select_region draws things. Should not.
1645                          * so we just change selection position and 
1646                          * defere drawing when thawing. Hack?
1647                          */
1648                         GTK_EDITABLE(text)->selection_end_pos = 
1649                                         gtk_stext_get_point(GTK_STEXT(text));
1650                 }
1651                 gtk_stext_thaw(GTK_STEXT(child_info->text));
1652         } else {
1653                 while (TRUE) {
1654                         c = read(source, buf, PREFSBUFSIZE - 1);
1655                         if (c == 0) 
1656                                 break;
1657                         for (i = 0; i < c; i++)
1658                                 child_info->output = g_string_append_c(
1659                                                 child_info->output, buf[i]);
1660                         child_info->new_out = TRUE;
1661                 }
1662         }
1663 }