bb025dd8087457b7bc50e8c0808b1511f40af164
[claws.git] / src / prefs_actions.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2003 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_gtk.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 #include "mimeview.h"
53 #include "description_window.h"
54 #include "textview.h"
55
56 typedef enum
57 {
58         ACTION_NONE             = 1 << 0,
59         ACTION_PIPE_IN          = 1 << 1,
60         ACTION_PIPE_OUT         = 1 << 2,
61         ACTION_SINGLE           = 1 << 3,
62         ACTION_MULTIPLE         = 1 << 4,
63         ACTION_ASYNC            = 1 << 5,
64         ACTION_USER_IN          = 1 << 6,
65         ACTION_USER_HIDDEN_IN   = 1 << 7,
66         ACTION_INSERT           = 1 << 8,
67         ACTION_USER_STR         = 1 << 9,
68         ACTION_USER_HIDDEN_STR  = 1 << 10,
69         ACTION_SELECTION_STR    = 1 << 11,
70         ACTION_ERROR            = 1 << 30,
71 } ActionType;
72
73 static struct Actions
74 {
75         GtkWidget *window;
76
77         GtkWidget *ok_btn;
78
79         GtkWidget *name_entry;
80         GtkWidget *cmd_entry;
81
82         GtkWidget *actions_clist;
83 } actions;
84
85 typedef struct _Children Children;
86 typedef struct _ChildInfo ChildInfo;
87 typedef struct _UserStringDialog UserStringDialog;
88
89 struct _Children
90 {
91         GtkWidget       *dialog;
92         GtkWidget       *text;
93         GtkWidget       *input_entry;
94         GtkWidget       *input_hbox;
95         GtkWidget       *abort_btn;
96         GtkWidget       *close_btn;
97         GtkWidget       *scrolledwin;
98
99         gchar           *action;
100         GSList          *list;
101         gint             nb;
102         gint             open_in;
103         gboolean         output;
104 };
105
106 struct _ChildInfo
107 {
108         Children        *children;
109         gchar           *cmd;
110         guint            type;
111         pid_t            pid;
112         gint             chld_in;
113         gint             chld_out;
114         gint             chld_err;
115         gint             chld_status;
116         gint             tag_in;
117         gint             tag_out;
118         gint             tag_err;
119         gint             tag_status;
120         gint             new_out;
121         GString         *output;
122         GtkWidget       *text;
123         GdkFont         *msgfont;
124 };
125
126 struct _UserStringDialog
127 {
128         GtkWidget       *dialog;
129         GtkWidget       *entry;
130         gchar           *user_str;
131 };
132
133 /* widget creating functions */
134 static void prefs_actions_create        (MainWindow *mainwin);
135 static void prefs_actions_set_dialog    (void);
136 static gint prefs_actions_clist_set_row (gint row);
137
138 /* callback functions */
139 static void prefs_actions_help_cb       (GtkWidget      *w,
140                                          gpointer        data);
141 static void prefs_actions_register_cb   (GtkWidget      *w,
142                                          gpointer        data);
143 static void prefs_actions_substitute_cb (GtkWidget      *w,
144                                          gpointer        data);
145 static void prefs_actions_delete_cb     (GtkWidget      *w,
146                                          gpointer        data);
147 static void prefs_actions_up            (GtkWidget      *w,
148                                          gpointer        data);
149 static void prefs_actions_down          (GtkWidget      *w,
150                                          gpointer        data);
151 static void prefs_actions_select        (GtkCList       *clist,
152                                          gint            row,
153                                          gint            column,
154                                          GdkEvent       *event);
155 static void prefs_actions_row_move      (GtkCList       *clist,
156                                          gint            source_row,
157                                          gint            dest_row);
158 static gint prefs_actions_deleted       (GtkWidget      *widget,
159                                          GdkEventAny    *event,
160                                          gpointer       *data);
161 static void prefs_actions_key_pressed   (GtkWidget      *widget,
162                                          GdkEventKey    *event,
163                                          gpointer        data);
164 static void prefs_actions_cancel        (GtkWidget      *w,
165                                          gpointer        data);
166 static void prefs_actions_ok            (GtkWidget      *w,
167                                          gpointer        data);
168 static void update_actions_menu         (GtkItemFactory *ifactory,
169                                          gchar          *branch_path,
170                                          gpointer        callback,
171                                          gpointer        data);
172 static void compose_actions_execute_cb  (Compose        *compose,
173                                          guint           action_nb,
174                                          GtkWidget      *widget);
175 static void mainwin_actions_execute_cb  (MainWindow     *mainwin,
176                                          guint           action_nb,
177                                          GtkWidget      *widget);
178 static void msgview_actions_execute_cb  (MessageView    *msgview,
179                                          guint           action_nb,
180                                          GtkWidget      *widget);
181 static void message_actions_execute     (MessageView    *msgview,
182                                          guint           action_nb,
183                                          GtkCTree       *ctree);
184 static guint get_action_type            (gchar          *action);
185
186 static gboolean execute_actions         (gchar          *action, 
187                                          GtkCTree       *ctree, 
188                                          GtkWidget      *text,
189                                          GdkFont        *msgfont,
190                                          gint            body_pos,
191                                          MimeView       *mimeview);
192
193 static gchar *parse_action_cmd          (gchar          *action,
194                                          MsgInfo        *msginfo,
195                                          GtkCTree       *ctree,
196                                          MimeView       *mimeview,
197                                          const gchar    *user_str,
198                                          const gchar    *user_hidden_str,
199                                          const gchar    *sel_str);
200 static gboolean parse_append_filename   (GString        **cmd,
201                                          MsgInfo        *msginfo);
202
203 static gboolean parse_append_msgpart    (GString        **cmd,
204                                          MsgInfo        *msginfo,
205                                          MimeView       *mimeview);
206
207 ChildInfo *fork_child                   (gchar          *cmd,
208                                          gint            action_type,
209                                          GtkWidget      *text,
210                                          GdkFont        *msgfont,
211                                          gint            body_pos,
212                                          Children       *children);
213
214 static gint wait_for_children           (gpointer        data);
215
216 static void free_children               (Children       *children);
217
218 static void childinfo_close_pipes       (ChildInfo      *child_info);
219
220 static void create_io_dialog            (Children       *children);
221 static void update_io_dialog            (Children       *children);
222
223 static void hide_io_dialog_cb           (GtkWidget      *widget,
224                                          gpointer        data);
225 static gint io_dialog_key_pressed_cb    (GtkWidget      *widget,
226                                          GdkEventKey    *event,
227                                          gpointer        data);
228
229 static void catch_output                (gpointer                data,
230                                          gint                    source,
231                                          GdkInputCondition       cond);
232 static void catch_input                 (gpointer                data, 
233                                          gint                    source,
234                                          GdkInputCondition       cond);
235 static void catch_status                (gpointer                data,
236                                          gint                    source,
237                                          GdkInputCondition       cond);
238
239 static gboolean user_string_dialog_delete_cb
240                                         (GtkWidget      *widget,
241                                          GdkEvent       *event,
242                                          gpointer        data);
243 static void user_string_dialog_destroy_cb
244                                         (GtkWidget      *widget,
245                                          gpointer        data);
246 static void user_string_dialog_activate_cb
247                                         (GtkWidget      *widget,
248                                          gpointer        data);
249
250 static gchar *get_user_string           (const gchar    *action,
251                                          ActionType      type);
252
253
254 void prefs_actions_open(MainWindow *mainwin)
255 {
256         inc_lock();
257
258         if (!actions.window)
259                 prefs_actions_create(mainwin);
260
261         manage_window_set_transient(GTK_WINDOW(actions.window));
262         gtk_widget_grab_focus(actions.ok_btn);
263
264         prefs_actions_set_dialog();
265
266         gtk_widget_show(actions.window);
267 }
268
269 static void prefs_actions_create(MainWindow *mainwin)
270 {
271         GtkWidget *window;
272         GtkWidget *vbox;
273         GtkWidget *ok_btn;
274         GtkWidget *cancel_btn;
275         GtkWidget *confirm_area;
276
277         GtkWidget *vbox1;
278
279         GtkWidget *entry_vbox;
280         GtkWidget *hbox;
281         GtkWidget *name_label;
282         GtkWidget *name_entry;
283         GtkWidget *cmd_label;
284         GtkWidget *cmd_entry;
285
286         GtkWidget *reg_hbox;
287         GtkWidget *btn_hbox;
288         GtkWidget *arrow;
289         GtkWidget *reg_btn;
290         GtkWidget *subst_btn;
291         GtkWidget *del_btn;
292
293         GtkWidget *cond_hbox;
294         GtkWidget *cond_scrolledwin;
295         GtkWidget *cond_clist;
296
297         GtkWidget *help_button;
298
299         GtkWidget *btn_vbox;
300         GtkWidget *up_btn;
301         GtkWidget *down_btn;
302
303         gchar *title[1];
304
305         debug_print("Creating actions configuration window...\n");
306
307         window = gtk_window_new (GTK_WINDOW_DIALOG);
308
309         gtk_container_set_border_width(GTK_CONTAINER (window), 8);
310         gtk_window_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
311         gtk_window_set_modal(GTK_WINDOW(window), TRUE);
312         gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, TRUE);
313         gtk_window_set_default_size(GTK_WINDOW(window), 400, -1);
314
315         vbox = gtk_vbox_new(FALSE, 6);
316         gtk_widget_show(vbox);
317         gtk_container_add(GTK_CONTAINER(window), vbox);
318
319         gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"),
320                                 &cancel_btn, _("Cancel"), NULL, NULL);
321         gtk_widget_show(confirm_area);
322         gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
323         gtk_widget_grab_default(ok_btn);
324
325         gtk_window_set_title(GTK_WINDOW(window), _("Actions configuration"));
326         gtk_signal_connect(GTK_OBJECT(window), "delete_event",
327                            GTK_SIGNAL_FUNC(prefs_actions_deleted), NULL);
328         gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
329                            GTK_SIGNAL_FUNC(prefs_actions_key_pressed), NULL);
330         MANAGE_WINDOW_SIGNALS_CONNECT(window);
331         gtk_signal_connect(GTK_OBJECT(ok_btn), "clicked",
332                            GTK_SIGNAL_FUNC(prefs_actions_ok), mainwin);
333         gtk_signal_connect(GTK_OBJECT(cancel_btn), "clicked",
334                            GTK_SIGNAL_FUNC(prefs_actions_cancel), NULL);
335
336         vbox1 = gtk_vbox_new(FALSE, 8);
337         gtk_widget_show(vbox1);
338         gtk_box_pack_start(GTK_BOX(vbox), vbox1, TRUE, TRUE, 0);
339         gtk_container_set_border_width(GTK_CONTAINER(vbox1), 2);
340
341         entry_vbox = gtk_vbox_new(FALSE, 4);
342         gtk_box_pack_start(GTK_BOX(vbox1), entry_vbox, FALSE, FALSE, 0);
343
344         hbox = gtk_hbox_new(FALSE, 8);
345         gtk_box_pack_start(GTK_BOX(entry_vbox), hbox, FALSE, FALSE, 0);
346
347         name_label = gtk_label_new(_("Menu name:"));
348         gtk_box_pack_start(GTK_BOX(hbox), name_label, FALSE, FALSE, 0);
349
350         name_entry = gtk_entry_new();
351         gtk_box_pack_start(GTK_BOX(hbox), name_entry, TRUE, TRUE, 0);
352
353         hbox = gtk_hbox_new(FALSE, 8);
354         gtk_box_pack_start(GTK_BOX(entry_vbox), hbox, TRUE, TRUE, 0);
355
356         cmd_label = gtk_label_new(_("Command line:"));
357         gtk_box_pack_start(GTK_BOX(hbox), cmd_label, FALSE, FALSE, 0);
358
359         cmd_entry = gtk_entry_new();
360         gtk_box_pack_start(GTK_BOX(hbox), cmd_entry, TRUE, TRUE, 0);
361
362         gtk_widget_show_all(entry_vbox);
363
364         /* register / substitute / delete */
365
366         reg_hbox = gtk_hbox_new(FALSE, 4);
367         gtk_widget_show(reg_hbox);
368         gtk_box_pack_start(GTK_BOX(vbox1), reg_hbox, FALSE, FALSE, 0);
369
370         arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
371         gtk_widget_show(arrow);
372         gtk_box_pack_start(GTK_BOX(reg_hbox), arrow, FALSE, FALSE, 0);
373         gtk_widget_set_usize(arrow, -1, 16);
374
375         btn_hbox = gtk_hbox_new(TRUE, 4);
376         gtk_widget_show(btn_hbox);
377         gtk_box_pack_start(GTK_BOX(reg_hbox), btn_hbox, FALSE, FALSE, 0);
378
379         reg_btn = gtk_button_new_with_label(_("Add"));
380         gtk_widget_show(reg_btn);
381         gtk_box_pack_start(GTK_BOX(btn_hbox), reg_btn, FALSE, TRUE, 0);
382         gtk_signal_connect(GTK_OBJECT(reg_btn), "clicked",
383                            GTK_SIGNAL_FUNC(prefs_actions_register_cb), NULL);
384
385         subst_btn = gtk_button_new_with_label(_(" Replace "));
386         gtk_widget_show(subst_btn);
387         gtk_box_pack_start(GTK_BOX(btn_hbox), subst_btn, FALSE, TRUE, 0);
388         gtk_signal_connect(GTK_OBJECT(subst_btn), "clicked",
389                            GTK_SIGNAL_FUNC(prefs_actions_substitute_cb),
390                            NULL);
391
392         del_btn = gtk_button_new_with_label(_("Delete"));
393         gtk_widget_show(del_btn);
394         gtk_box_pack_start(GTK_BOX(btn_hbox), del_btn, FALSE, TRUE, 0);
395         gtk_signal_connect(GTK_OBJECT(del_btn), "clicked",
396                            GTK_SIGNAL_FUNC(prefs_actions_delete_cb), NULL);
397
398         help_button = gtk_button_new_with_label(_(" Syntax help "));
399         gtk_widget_show(help_button);
400         gtk_box_pack_end(GTK_BOX(reg_hbox), help_button, FALSE, FALSE, 0);
401         gtk_signal_connect(GTK_OBJECT(help_button), "clicked",
402                            GTK_SIGNAL_FUNC(prefs_actions_help_cb), NULL);
403
404         cond_hbox = gtk_hbox_new(FALSE, 8);
405         gtk_widget_show(cond_hbox);
406         gtk_box_pack_start(GTK_BOX(vbox1), cond_hbox, TRUE, TRUE, 0);
407
408         cond_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
409         gtk_widget_show(cond_scrolledwin);
410         gtk_widget_set_usize(cond_scrolledwin, -1, 150);
411         gtk_box_pack_start(GTK_BOX(cond_hbox), cond_scrolledwin,
412                            TRUE, TRUE, 0);
413         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (cond_scrolledwin),
414                                        GTK_POLICY_AUTOMATIC,
415                                        GTK_POLICY_AUTOMATIC);
416
417         title[0] = _("Current actions");
418         cond_clist = gtk_clist_new_with_titles(1, title);
419         gtk_widget_show(cond_clist);
420         gtk_container_add(GTK_CONTAINER (cond_scrolledwin), cond_clist);
421         gtk_clist_set_column_width(GTK_CLIST (cond_clist), 0, 80);
422         gtk_clist_set_selection_mode(GTK_CLIST (cond_clist),
423                                      GTK_SELECTION_BROWSE);
424         GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(cond_clist)->column[0].button,
425                                GTK_CAN_FOCUS);
426         gtk_signal_connect(GTK_OBJECT(cond_clist), "select_row",
427                            GTK_SIGNAL_FUNC(prefs_actions_select), NULL);
428         gtk_signal_connect_after(GTK_OBJECT(cond_clist), "row_move",
429                                  GTK_SIGNAL_FUNC(prefs_actions_row_move),
430                                  NULL);
431
432         btn_vbox = gtk_vbox_new(FALSE, 8);
433         gtk_widget_show(btn_vbox);
434         gtk_box_pack_start(GTK_BOX(cond_hbox), btn_vbox, FALSE, FALSE, 0);
435
436         up_btn = gtk_button_new_with_label(_("Up"));
437         gtk_widget_show(up_btn);
438         gtk_box_pack_start(GTK_BOX(btn_vbox), up_btn, FALSE, FALSE, 0);
439         gtk_signal_connect(GTK_OBJECT(up_btn), "clicked",
440                            GTK_SIGNAL_FUNC(prefs_actions_up), NULL);
441
442         down_btn = gtk_button_new_with_label(_("Down"));
443         gtk_widget_show(down_btn);
444         gtk_box_pack_start(GTK_BOX(btn_vbox), down_btn, FALSE, FALSE, 0);
445         gtk_signal_connect(GTK_OBJECT(down_btn), "clicked",
446                            GTK_SIGNAL_FUNC(prefs_actions_down), NULL);
447
448         gtk_widget_show(window);
449
450         actions.window = window;
451         actions.ok_btn = ok_btn;
452
453         actions.name_entry = name_entry;
454         actions.cmd_entry  = cmd_entry;
455
456         actions.actions_clist = cond_clist;
457 }
458
459
460 void prefs_actions_read_config(void)
461 {
462         gchar *rcpath;
463         FILE *fp;
464         gchar buf[PREFSBUFSIZE];
465         gchar *act;
466
467         debug_print("Reading actions configurations...\n");
468
469         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL);
470         if ((fp = fopen(rcpath, "rb")) == NULL) {
471                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
472                 g_free(rcpath);
473                 return;
474         }
475         g_free(rcpath);
476
477         while (prefs_common.actions_list != NULL) {
478                 act = (gchar *)prefs_common.actions_list->data;
479                 prefs_common.actions_list =
480                         g_slist_remove(prefs_common.actions_list, act);
481                 g_free(act);
482         }
483
484         while (fgets(buf, sizeof(buf), fp) != NULL) {
485                 g_strchomp(buf);
486                 act = strstr(buf, ": ");
487                 if (act && act[2] && 
488                     get_action_type(&act[2]) != ACTION_ERROR)
489                         prefs_common.actions_list =
490                                 g_slist_append(prefs_common.actions_list,
491                                                g_strdup(buf));
492         }
493         fclose(fp);
494 }
495
496 void prefs_actions_write_config(void)
497 {
498         gchar *rcpath;
499         PrefFile *pfile;
500         GSList *cur;
501
502         debug_print("Writing actions configuration...\n");
503
504         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL);
505         if ((pfile= prefs_write_open(rcpath)) == NULL) {
506                 g_warning("failed to write configuration to file\n");
507                 g_free(rcpath);
508                 return;
509         }
510
511         for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) {
512                 gchar *act = (gchar *)cur->data;
513                 if (fputs(act, pfile->fp) == EOF ||
514                     fputc('\n', pfile->fp) == EOF) {
515                         FILE_OP_ERROR(rcpath, "fputs || fputc");
516                         prefs_file_close_revert(pfile);
517                         g_free(rcpath);
518                         return;
519                 }
520         }
521         
522         g_free(rcpath);
523
524         if (prefs_file_close(pfile) < 0) {
525                 g_warning("failed to write configuration to file\n");
526                 return;
527         }
528 }
529
530 static guint get_action_type(gchar *action)
531 {
532         gchar *p;
533         guint action_type = ACTION_NONE;
534
535         g_return_val_if_fail(action,  ACTION_ERROR);
536         g_return_val_if_fail(*action, ACTION_ERROR);
537
538         p = action;
539
540         if (p[0] == '|') {
541                 action_type |= ACTION_PIPE_IN;
542                 p++;
543         } else if (p[0] == '>') {
544                 action_type |= ACTION_USER_IN;
545                 p++;
546         } else if (p[0] == '*') {
547                 action_type |= ACTION_USER_HIDDEN_IN;
548                 p++;
549         }
550
551         if (p[0] == 0x00)
552                 return ACTION_ERROR;
553
554         while (*p && action_type != ACTION_ERROR) {
555                 if (p[0] == '%') {
556                         switch (p[1]) {
557                         case 'f':
558                                 action_type |= ACTION_SINGLE;
559                                 break;
560                         case 'F':
561                                 action_type |= ACTION_MULTIPLE;
562                                 break;
563                         case 'p':
564                                 action_type |= ACTION_SINGLE;
565                                 break;
566                         case 's':
567                                 action_type |= ACTION_SELECTION_STR;
568                                 break;
569                         case 'u':
570                                 action_type |= ACTION_USER_STR;
571                                 break;
572                         case 'h':
573                                 action_type |= ACTION_USER_HIDDEN_STR;
574                                 break;
575                         default:
576                                 action_type = ACTION_ERROR;
577                                 break;
578                         }
579                 } else if (p[0] == '|') {
580                         if (p[1] == 0x00)
581                                 action_type |= ACTION_PIPE_OUT;
582                 } else if (p[0] == '>') {
583                         if (p[1] == 0x00)
584                                 action_type |= ACTION_INSERT;
585                 } else if (p[0] == '&') {
586                         if (p[1] == 0x00)
587                                 action_type |= ACTION_ASYNC;
588                 }
589                 p++;
590         }
591
592         return action_type;
593 }
594
595 static gchar *parse_action_cmd(gchar *action, MsgInfo *msginfo,
596                                GtkCTree *ctree, MimeView *mimeview,
597                                const gchar *user_str,
598                                const gchar *user_hidden_str,
599                                const gchar *sel_str)
600 {
601         GString *cmd;
602         gchar *p;
603         GList *cur;
604         MsgInfo *msg;
605         
606         p = action;
607         
608         if (p[0] == '|' || p[0] == '>' || p[0] == '*')
609                 p++;
610
611         cmd = g_string_sized_new(strlen(action));
612
613         while (p[0] &&
614                !((p[0] == '|' || p[0] == '>' || p[0] == '&') && !p[1])) {
615                 if (p[0] == '%' && p[1]) {
616                         switch (p[1]) {
617                         case 'f':
618                                 if (!parse_append_filename(&cmd, msginfo)) {
619                                         g_string_free(cmd, TRUE);
620                                         return NULL;
621                                 }
622                                 p++;
623                                 break;
624                         case 'F':
625                                 for (cur = GTK_CLIST(ctree)->selection;
626                                      cur != NULL; cur = cur->next) {
627                                         msg = gtk_ctree_node_get_row_data(ctree,
628                                               GTK_CTREE_NODE(cur->data));
629                                         if (!parse_append_filename(&cmd, msg)) {
630                                                 g_string_free(cmd, TRUE);
631                                                 return NULL;
632                                         }
633                                         if (cur->next)
634                                                 cmd = g_string_append_c(cmd, ' ');
635                                 }
636                                 p++;
637                                 break;
638                         case 'p':
639                                 if (!parse_append_msgpart(&cmd, msginfo,
640                                                           mimeview)) {
641                                         g_string_free(cmd, TRUE);
642                                         return NULL;
643                                 }
644                                 p++;
645                                 break;
646                         case 's':
647                                 if (sel_str)
648                                         cmd = g_string_append(cmd, sel_str);
649                                 p++;
650                                 break;
651                         case 'u':
652                                 if (user_str)
653                                         cmd = g_string_append(cmd, user_str);
654                                 p++;
655                                 break;
656                         case 'h':
657                                 if (user_hidden_str)
658                                         cmd = g_string_append(cmd,
659                                                               user_hidden_str);
660                                 p++;
661                                 break;
662                         default:
663                                 cmd = g_string_append_c(cmd, p[0]);
664                                 cmd = g_string_append_c(cmd, p[1]);
665                                 p++;
666                         }
667                 } else {
668                         cmd = g_string_append_c(cmd, p[0]);
669                 }
670                 p++;
671         }
672         if (cmd->len == 0) {
673                 g_string_free(cmd, TRUE);
674                 return NULL;
675         }
676
677         p = cmd->str;
678         g_string_free(cmd, FALSE);
679         return p;
680 }
681
682 static gboolean parse_append_filename(GString **cmd, MsgInfo *msginfo)
683 {
684         gchar *filename;
685
686         g_return_val_if_fail(msginfo, FALSE);
687
688         filename = procmsg_get_message_file(msginfo);
689
690         if (filename) {
691                 *cmd = g_string_append(*cmd, filename);
692                 g_free(filename);
693         } else {
694                 alertpanel_error(_("Could not get message file %d"),
695                                  msginfo->msgnum);
696                 return FALSE;
697         }
698
699         return TRUE;
700 }
701
702 static gboolean parse_append_msgpart(GString **cmd, MsgInfo *msginfo,
703                                      MimeView *mimeview)
704 {
705         gchar    *filename;
706         gchar    *partname;
707         MimeInfo *partinfo;
708         gint      ret;
709         FILE     *fp;
710
711         if (!mimeview) {
712 #if USE_GPGME
713                 if ((fp = procmsg_open_message_decrypted(msginfo, &partinfo))
714                     == NULL) {
715                         alertpanel_error(_("Could not get message file."));
716                         return FALSE;
717                 }
718 #else
719                 if ((fp = procmsg_open_message(msginfo)) == NULL) {
720                         alertpanel_error(_("Could not get message file."));
721                         return FALSE;
722                 }
723                 partinfo = procmime_scan_mime_header(fp);
724 #endif
725                 fclose(fp);
726                 if (!partinfo) {
727                         procmime_mimeinfo_free_all(partinfo);
728                         alertpanel_error(_("Could not get message part."));
729                         return FALSE;
730                 }
731                 filename = procmsg_get_message_file(msginfo);
732         } else {
733                 if (!mimeview->opened) {
734                         alertpanel_error(_("No message part selected."));
735                         return FALSE;
736                 }
737                 if (!mimeview->file) {
738                         alertpanel_error(_("No message file selected."));
739                         return FALSE;
740                 }
741                 partinfo = gtk_ctree_node_get_row_data
742                                 (GTK_CTREE(mimeview->ctree),
743                                  mimeview->opened);
744                 g_return_val_if_fail(partinfo != NULL, FALSE);
745                 filename = mimeview->file;
746         }
747         partname = procmime_get_tmp_file_name(partinfo);
748
749         ret = procmime_get_part(partname, filename, partinfo); 
750
751         if (!mimeview) {
752                 procmime_mimeinfo_free_all(partinfo);
753                 g_free(filename);
754         }
755
756         if (ret < 0) {
757                 alertpanel_error(_("Can't get part of multipart message"));
758                 g_free(partname);
759                 return FALSE;
760         }
761
762         *cmd = g_string_append(*cmd, partname);
763
764         g_free(partname);
765
766         return TRUE;
767 }
768
769 static void prefs_actions_set_dialog(void)
770 {
771         GtkCList *clist = GTK_CLIST(actions.actions_clist);
772         GSList *cur;
773         gchar *action_str[1];
774         gint row;
775
776         gtk_clist_freeze(clist);
777         gtk_clist_clear(clist);
778
779         action_str[0] = _("(New)");
780         row = gtk_clist_append(clist, action_str);
781         gtk_clist_set_row_data(clist, row, NULL);
782
783         for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) {
784                 gchar *action[1];
785
786                 action[0] = (gchar *)cur->data;
787                 row = gtk_clist_append(clist, action);
788                 gtk_clist_set_row_data(clist, row, action[0]);
789         }
790
791         gtk_clist_thaw(clist);
792 }
793
794 static void prefs_actions_set_list(void)
795 {
796         gint row = 1;
797         gchar *action;
798
799         g_slist_free(prefs_common.actions_list);
800         prefs_common.actions_list = NULL;
801
802         while ((action = (gchar *)gtk_clist_get_row_data
803                 (GTK_CLIST(actions.actions_clist), row)) != NULL) {
804                 prefs_common.actions_list =
805                         g_slist_append(prefs_common.actions_list, action);
806                 row++;
807         }
808 }
809
810 #define GET_ENTRY(entry) \
811         entry_text = gtk_entry_get_text(GTK_ENTRY(entry))
812
813 static gint prefs_actions_clist_set_row(gint row)
814 {
815         GtkCList *clist = GTK_CLIST(actions.actions_clist);
816         gchar *entry_text;
817         gint len;
818         gchar action[PREFSBUFSIZE];
819         gchar *buf[1];
820
821         g_return_val_if_fail(row != 0, -1);
822
823         GET_ENTRY(actions.name_entry);
824         if (entry_text[0] == '\0') {
825                 alertpanel_error(_("Menu name is not set."));
826                 return -1;
827         }
828
829         if (strchr(entry_text, ':')) {
830                 alertpanel_error(_("Colon ':' is not allowed in the menu name."));
831                 return -1;
832         }
833
834         strncpy(action, entry_text, PREFSBUFSIZE - 1);
835         g_strstrip(action);
836
837         /* Keep space for the ': ' delimiter */
838         len = strlen(action) + 2;
839         if (len >= PREFSBUFSIZE - 1) {
840                 alertpanel_error(_("Menu name is too long."));
841                 return -1;
842         }
843
844         strcat(action, ": ");
845
846         GET_ENTRY(actions.cmd_entry);
847
848         if (entry_text[0] == '\0') {
849                 alertpanel_error(_("Command line not set."));
850                 return -1;
851         }
852
853         if (len + strlen(entry_text) >= PREFSBUFSIZE - 1) {
854                 alertpanel_error(_("Menu name and command are too long."));
855                 return -1;
856         }
857
858         if (get_action_type(entry_text) == ACTION_ERROR) {
859                 alertpanel_error(_("The command\n%s\nhas a syntax error."), 
860                                  entry_text);
861                 return -1;
862         }
863
864         strcat(action, entry_text);
865
866         buf[0] = action;
867         if (row < 0)
868                 row = gtk_clist_append(clist, buf);
869         else {
870                 gchar *old_action;
871                 gtk_clist_set_text(clist, row, 0, action);
872                 old_action = (gchar *) gtk_clist_get_row_data(clist, row);
873                 if (old_action)
874                         g_free(old_action);
875         }
876
877         buf[0] = g_strdup(action);
878
879         gtk_clist_set_row_data(clist, row, buf[0]);
880
881         prefs_actions_set_list();
882
883         return 0;
884 }
885
886 /* callback functions */
887
888 static void prefs_actions_register_cb(GtkWidget *w, gpointer data)
889 {
890         prefs_actions_clist_set_row(-1);
891 }
892
893 static void prefs_actions_substitute_cb(GtkWidget *w, gpointer data)
894 {
895         GtkCList *clist = GTK_CLIST(actions.actions_clist);
896         gchar *action;
897         gint row;
898
899         if (!clist->selection) return;
900
901         row = GPOINTER_TO_INT(clist->selection->data);
902         if (row == 0) return;
903
904         action = gtk_clist_get_row_data(clist, row);
905         if (!action) return;
906
907         prefs_actions_clist_set_row(row);
908 }
909
910 static void prefs_actions_delete_cb(GtkWidget *w, gpointer data)
911 {
912         GtkCList *clist = GTK_CLIST(actions.actions_clist);
913         gchar *action;
914         gint row;
915
916         if (!clist->selection) return;
917         row = GPOINTER_TO_INT(clist->selection->data);
918         if (row == 0) return;
919
920         if (alertpanel(_("Delete action"),
921                        _("Do you really want to delete this action?"),
922                        _("Yes"), _("No"), NULL) == G_ALERTALTERNATE)
923                 return;
924
925         action = gtk_clist_get_row_data(clist, row);
926         g_free(action);
927         gtk_clist_remove(clist, row);
928         prefs_common.actions_list = g_slist_remove(prefs_common.actions_list,
929                                                    action);
930 }
931
932 static void prefs_actions_up(GtkWidget *w, gpointer data)
933 {
934         GtkCList *clist = GTK_CLIST(actions.actions_clist);
935         gint row;
936
937         if (!clist->selection) return;
938
939         row = GPOINTER_TO_INT(clist->selection->data);
940         if (row > 1)
941                 gtk_clist_row_move(clist, row, row - 1);
942 }
943
944 static void prefs_actions_down(GtkWidget *w, gpointer data)
945 {
946         GtkCList *clist = GTK_CLIST(actions.actions_clist);
947         gint row;
948
949         if (!clist->selection) return;
950
951         row = GPOINTER_TO_INT(clist->selection->data);
952         if (row > 0 && row < clist->rows - 1)
953                 gtk_clist_row_move(clist, row, row + 1);
954 }
955
956 #define ENTRY_SET_TEXT(entry, str) \
957         gtk_entry_set_text(GTK_ENTRY(entry), str ? str : "")
958
959 static void prefs_actions_select(GtkCList *clist, gint row, gint column,
960                                  GdkEvent *event)
961 {
962         gchar *action;
963         gchar *cmd;
964         gchar buf[PREFSBUFSIZE];
965         action = gtk_clist_get_row_data(clist, row);
966
967         if (!action) {
968                 ENTRY_SET_TEXT(actions.name_entry, "");
969                 ENTRY_SET_TEXT(actions.cmd_entry, "");
970                 return;
971         }
972
973         strncpy(buf, action, PREFSBUFSIZE - 1);
974         buf[PREFSBUFSIZE - 1] = 0x00;
975         cmd = strstr(buf, ": ");
976
977         if (cmd && cmd[2])
978                 ENTRY_SET_TEXT(actions.cmd_entry, &cmd[2]);
979         else
980                 return;
981
982         *cmd = 0x00;
983         ENTRY_SET_TEXT(actions.name_entry, buf);
984 }
985
986 static void prefs_actions_row_move(GtkCList *clist,
987                                    gint source_row, gint dest_row)
988 {
989         prefs_actions_set_list();
990         if (gtk_clist_row_is_visible(clist, dest_row) != GTK_VISIBILITY_FULL) {
991                 gtk_clist_moveto(clist, dest_row, -1,
992                                  source_row < dest_row ? 1.0 : 0.0, 0.0);
993         }
994 }
995
996 static gint prefs_actions_deleted(GtkWidget *widget, GdkEventAny *event,
997                                   gpointer *data)
998 {
999         prefs_actions_cancel(widget, data);
1000         return TRUE;
1001 }
1002
1003 static void prefs_actions_key_pressed(GtkWidget *widget, GdkEventKey *event,
1004                                       gpointer data)
1005 {
1006         if (event && event->keyval == GDK_Escape)
1007                 prefs_actions_cancel(widget, data);
1008 }
1009
1010 static void prefs_actions_cancel(GtkWidget *w, gpointer data)
1011 {
1012         prefs_actions_read_config();
1013         gtk_widget_hide(actions.window);
1014         inc_unlock();
1015 }
1016
1017 static void prefs_actions_ok(GtkWidget *widget, gpointer data)
1018 {
1019         GtkItemFactory *ifactory;
1020         MainWindow *mainwin = (MainWindow *)data;
1021
1022         prefs_actions_write_config();
1023         ifactory = gtk_item_factory_from_widget(mainwin->menubar);
1024         update_mainwin_actions_menu(ifactory, mainwin);
1025         gtk_widget_hide(actions.window);
1026         inc_unlock();
1027 }
1028
1029 void update_mainwin_actions_menu(GtkItemFactory *ifactory,
1030                                  MainWindow *mainwin)
1031 {
1032         update_actions_menu(ifactory, "/Tools/Actions",
1033                             mainwin_actions_execute_cb,
1034                             mainwin);
1035 }
1036
1037 void update_compose_actions_menu(GtkItemFactory *ifactory,
1038                                  gchar *branch_path,
1039                                  Compose *compose)
1040 {
1041         update_actions_menu(ifactory, branch_path,
1042                             compose_actions_execute_cb,
1043                             compose);
1044 }
1045
1046
1047 void actions_execute(gpointer data, 
1048                      guint action_nb,
1049                      GtkWidget *widget,
1050                      gint source)
1051 {
1052         if (source == TOOLBAR_MAIN) 
1053                 mainwin_actions_execute_cb((MainWindow*)data, action_nb, widget);
1054         else if (source == TOOLBAR_COMPOSE)
1055                 compose_actions_execute_cb((Compose*)data, action_nb, widget);
1056         else if (source == TOOLBAR_MSGVIEW)
1057                 msgview_actions_execute_cb((MessageView*)data, action_nb, widget);      
1058 }
1059
1060
1061 static void update_actions_menu(GtkItemFactory *ifactory,
1062                                 gchar *branch_path,
1063                                 gpointer callback,
1064                                 gpointer data)
1065 {
1066         GtkWidget *menuitem;
1067         gchar *menu_path;
1068         GSList *cur;
1069         gchar *action, *action_p;
1070         GList *amenu;
1071         GtkItemFactoryEntry ifentry = {NULL, NULL, NULL, 0, "<Branch>"};
1072
1073         ifentry.path = branch_path;
1074         menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
1075         g_return_if_fail(menuitem != NULL);
1076
1077         amenu = GTK_MENU_SHELL(menuitem)->children;
1078         while (amenu != NULL) {
1079                 GList *alist = amenu->next;
1080                 gtk_widget_destroy(GTK_WIDGET(amenu->data));
1081                 amenu = alist;
1082         }
1083
1084         ifentry.accelerator     = NULL;
1085         ifentry.callback_action = 0;
1086         ifentry.callback        = callback;
1087         ifentry.item_type       = NULL;
1088
1089         for (cur = prefs_common.actions_list; cur; cur = cur->next) {
1090                 action   = g_strdup((gchar *)cur->data);
1091                 action_p = strstr(action, ": ");
1092                 if (action_p && action_p[2] &&
1093                     get_action_type(&action_p[2]) != ACTION_ERROR) {
1094                         action_p[0] = 0x00;
1095                         menu_path = g_strdup_printf("%s/%s", branch_path,
1096                                                     action);
1097                         ifentry.path = menu_path;
1098                         gtk_item_factory_create_item(ifactory, &ifentry, data,
1099                                                      1);
1100                         g_free(menu_path);
1101                 }
1102                 g_free(action);
1103                 ifentry.callback_action++;
1104         }
1105 }
1106
1107 static void compose_actions_execute_cb(Compose *compose, guint action_nb,
1108                                        GtkWidget *widget)
1109 {
1110         gchar *buf, *action;
1111         guint action_type;
1112
1113         g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list));
1114
1115         buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb);
1116         g_return_if_fail(buf != NULL);
1117         action = strstr(buf, ": ");
1118         g_return_if_fail(action != NULL);
1119
1120         /* Point to the beginning of the command-line */
1121         action += 2;
1122
1123         action_type = get_action_type(action);
1124         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE)) {
1125                 alertpanel_warning
1126                         (_("The selected action cannot be used in the compose window\n"
1127                            "because it contains %%f, %%F or %%p."));
1128                 return;
1129         }
1130
1131         execute_actions(action, NULL, compose->text, NULL, 0, NULL);
1132 }
1133
1134 static void mainwin_actions_execute_cb(MainWindow *mainwin, guint action_nb,
1135                                        GtkWidget *widget)
1136 {
1137         message_actions_execute(mainwin->messageview, action_nb,
1138                                 GTK_CTREE(mainwin->summaryview->ctree));
1139 }
1140
1141 static void msgview_actions_execute_cb(MessageView *msgview, guint action_nb,
1142                                        GtkWidget *widget)
1143 {
1144         message_actions_execute(msgview, action_nb, NULL);
1145 }
1146
1147 static void message_actions_execute(MessageView *msgview, guint action_nb,
1148                                     GtkCTree *ctree)
1149 {
1150         TextView *textview = NULL;
1151         MimeView *mimeview = NULL;
1152         gchar *buf;
1153         gchar *action;
1154         GtkWidget *text = NULL;
1155         GdkFont *msgfont = NULL;
1156         guint body_pos = 0;
1157
1158         g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list));
1159
1160         buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb);
1161
1162         g_return_if_fail(buf);
1163         g_return_if_fail(action = strstr(buf, ": "));
1164
1165         /* Point to the beginning of the command-line */
1166         action += 2;
1167
1168         switch (msgview->type) {
1169         case MVIEW_TEXT:
1170                 if (msgview->textview && msgview->textview->text)
1171                         textview = msgview->textview;
1172                 break;
1173         case MVIEW_MIME:
1174                 if (msgview->mimeview) {
1175                         mimeview = msgview->mimeview;
1176                         if (msgview->mimeview->type == MIMEVIEW_TEXT &&
1177                             msgview->mimeview->textview &&
1178                             msgview->mimeview->textview->text)
1179                                 textview = msgview->mimeview->textview;
1180                 }
1181                 break;
1182         }
1183
1184         if (textview) {
1185                 text     = textview->text;
1186                 msgfont  = textview->msgfont;
1187                 body_pos = textview->body_pos;
1188         }
1189
1190         execute_actions(action, ctree, text, msgfont, body_pos, mimeview);
1191 }
1192
1193 static gboolean execute_actions(gchar *action, GtkCTree *ctree,
1194                                 GtkWidget *text, GdkFont *msgfont,
1195                                 gint body_pos, MimeView *mimeview)
1196 {
1197         GList *cur, *selection = NULL;
1198         GSList *children_list = NULL;
1199         gint is_ok  = TRUE;
1200         gint selection_len = 0;
1201         Children *children;
1202         ChildInfo *child_info;
1203         gint action_type;
1204         MsgInfo *msginfo;
1205         gchar *cmd;
1206         gchar *sel_str = NULL;
1207         gchar *user_str = NULL;
1208         gchar *user_hidden_str = NULL;
1209
1210         g_return_val_if_fail(action && *action, FALSE);
1211
1212         action_type = get_action_type(action);
1213
1214         if (action_type == ACTION_ERROR)
1215                 return FALSE;         /* ERR: syntax error */
1216
1217         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE) && 
1218             !(ctree && GTK_CLIST(ctree)->selection))
1219                 return FALSE;         /* ERR: file command without selection */
1220
1221         if (ctree) {
1222                 selection = GTK_CLIST(ctree)->selection;
1223                 selection_len = g_list_length(selection);
1224         }
1225
1226         if (action_type & (ACTION_PIPE_OUT | ACTION_PIPE_IN | ACTION_INSERT)) {
1227                 if (ctree && selection_len > 1)
1228                         return FALSE; /* ERR: pipe + multiple selection */
1229                 if (!text)
1230                         return FALSE; /* ERR: pipe and no displayed text */
1231         }
1232
1233         if (action_type & ACTION_SELECTION_STR) {
1234                 if (!text)
1235                         return FALSE; /* ERR: selection string but no text */
1236                 else {
1237                         guint start = 0, end = 0;
1238                         if (GTK_EDITABLE(text)->has_selection) {
1239                                 start = GTK_EDITABLE(text)->selection_start_pos;
1240                                 end   = GTK_EDITABLE(text)->selection_end_pos;
1241                                 if (start > end) {
1242                                         guint tmp;
1243                                         tmp = start;
1244                                         start = end;
1245                                         end = tmp;
1246                                 }
1247                         }
1248                         sel_str = gtk_editable_get_chars(GTK_EDITABLE(text),
1249                                                          start, end);
1250                 }
1251         }
1252
1253         if (action_type & (ACTION_USER_STR))
1254                 if (!(user_str = get_user_string(action, ACTION_USER_STR)))
1255                         return FALSE;
1256
1257         if (action_type & (ACTION_USER_HIDDEN_STR))
1258                 if (!(user_hidden_str =
1259                         get_user_string(action, ACTION_USER_HIDDEN_STR)))
1260                         return FALSE;
1261
1262         children = g_new0(Children, 1);
1263
1264         if (action_type & ACTION_SINGLE) {
1265                 for (cur = selection; cur && is_ok == TRUE; cur = cur->next) {
1266                         msginfo = gtk_ctree_node_get_row_data(ctree,
1267                                         GTK_CTREE_NODE(cur->data));
1268                         if (!msginfo) {
1269                                 is_ok  = FALSE; /* ERR: msginfo missing */
1270                                 break;
1271                         }
1272                         cmd = parse_action_cmd(action, msginfo, ctree,
1273                                                mimeview, user_str,
1274                                                user_hidden_str, sel_str);
1275                         if (!cmd) {
1276                                 debug_print("Action command error\n");
1277                                 is_ok  = FALSE; /* ERR: incorrect command */
1278                                 break;
1279                         }
1280                         if ((child_info = fork_child(cmd, action_type, text,
1281                                                      msgfont, body_pos,
1282                                                      children))) {
1283                                 children_list = g_slist_append(children_list,
1284                                                                child_info);
1285                                 children->open_in = (selection_len == 1) ?
1286                                                     (action_type &
1287                                                      (ACTION_USER_IN |
1288                                                       ACTION_USER_HIDDEN_IN)) : 0;
1289                         }
1290                         g_free(cmd);
1291                 }
1292         } else {
1293                 cmd = parse_action_cmd(action, NULL, ctree, mimeview, user_str,
1294                                        user_hidden_str, sel_str);
1295                 if (cmd) {
1296                         if ((child_info = fork_child(cmd, action_type, text,
1297                                                      msgfont, body_pos,
1298                                                      children))) {
1299                                 children_list = g_slist_append(children_list,
1300                                                                child_info);
1301                                 children->open_in = action_type &
1302                                                     (ACTION_USER_IN |
1303                                                      ACTION_USER_HIDDEN_IN);
1304                         }
1305                         g_free(cmd);
1306                 } else
1307                         is_ok  = FALSE;         /* ERR: incorrect command */
1308         }
1309
1310         g_free(user_str);
1311         g_free(user_hidden_str);
1312
1313         if (!children_list) {
1314                  /* If not waiting for children, return */
1315                 g_free(children);
1316         } else {
1317                 GSList *cur;
1318
1319                 children->action  = g_strdup(action);
1320                 children->dialog  = NULL;
1321                 children->list    = children_list;
1322                 children->nb      = g_slist_length(children_list);
1323
1324                 for (cur = children_list; cur; cur = cur->next) {
1325                         child_info = (ChildInfo *) cur->data;
1326                         child_info->tag_status = 
1327                                 gdk_input_add(child_info->chld_status,
1328                                               GDK_INPUT_READ,
1329                                               catch_status, child_info);
1330                 }
1331
1332                 create_io_dialog(children);
1333         }
1334
1335         return is_ok;
1336 }
1337
1338 ChildInfo *fork_child(gchar *cmd, gint action_type, GtkWidget *text,
1339                       GdkFont *msgfont, gint body_pos, Children *children)
1340 {
1341         gint chld_in[2], chld_out[2], chld_err[2], chld_status[2];
1342         gchar *cmdline[4];
1343         guint start, end;
1344         gint is_selection;
1345         gchar *selection;
1346         pid_t pid, gch_pid;
1347         ChildInfo *child_info;
1348         gint sync;
1349
1350         sync = !(action_type & ACTION_ASYNC);
1351
1352         chld_in[0] = chld_in[1] = chld_out[0] = chld_out[1] = chld_err[0]
1353                 = chld_err[1] = chld_status[0] = chld_status[1] = -1;
1354
1355         if (sync) {
1356                 if (pipe(chld_status) || pipe(chld_in) || pipe(chld_out) ||
1357                     pipe(chld_err)) {
1358                         alertpanel_error(_("Command could not be started. "
1359                                            "Pipe creation failed.\n%s"),
1360                                         g_strerror(errno));
1361                         /* Closing fd = -1 fails silently */
1362                         close(chld_in[0]);
1363                         close(chld_in[1]);
1364                         close(chld_out[0]);
1365                         close(chld_out[1]);
1366                         close(chld_err[0]);
1367                         close(chld_err[1]);
1368                         close(chld_status[0]);
1369                         close(chld_status[1]);
1370                         return NULL; /* Pipe error */
1371                 }
1372         }
1373
1374         debug_print("Forking child and grandchild.\n");
1375
1376         pid = fork();
1377         if (pid == 0) { /* Child */
1378                 if (setpgid(0, 0))
1379                         perror("setpgid");
1380
1381                 close(ConnectionNumber(gdk_display));
1382
1383                 gch_pid = fork();
1384
1385                 if (gch_pid == 0) {
1386                         if (setpgid(0, getppid()))
1387                                 perror("setpgid");
1388                         if (sync) {
1389                                 if (action_type &
1390                                     (ACTION_PIPE_IN |
1391                                      ACTION_USER_IN |
1392                                      ACTION_USER_HIDDEN_IN)) {
1393                                         close(fileno(stdin));
1394                                         dup  (chld_in[0]);
1395                                 }
1396                                 close(chld_in[0]);
1397                                 close(chld_in[1]);
1398
1399                                 close(fileno(stdout));
1400                                 dup  (chld_out[1]);
1401                                 close(chld_out[0]);
1402                                 close(chld_out[1]);
1403
1404                                 close(fileno(stderr));
1405                                 dup  (chld_err[1]);
1406                                 close(chld_err[0]);
1407                                 close(chld_err[1]);
1408                         }
1409
1410                         cmdline[0] = "sh";
1411                         cmdline[1] = "-c";
1412                         cmdline[2] = cmd;
1413                         cmdline[3] = 0;
1414                         execvp("/bin/sh", cmdline);
1415
1416                         perror("execvp");
1417                         _exit(1);
1418                 } else if (gch_pid < (pid_t) 0) { /* Fork error */
1419                         if (sync)
1420                                 write(chld_status[1], "1\n", 2);
1421                         perror("fork");
1422                         _exit(1);
1423                 } else { /* Child */
1424                         if (sync) {
1425                                 close(chld_in[0]);
1426                                 close(chld_in[1]);
1427                                 close(chld_out[0]);
1428                                 close(chld_out[1]);
1429                                 close(chld_err[0]);
1430                                 close(chld_err[1]);
1431                                 close(chld_status[0]);
1432                         }
1433                         if (sync) {
1434                                 debug_print("Child: Waiting for grandchild\n");
1435                                 waitpid(gch_pid, NULL, 0);
1436                                 debug_print("Child: grandchild ended\n");
1437                                 write(chld_status[1], "0\n", 2);
1438                                 close(chld_status[1]);
1439                         }
1440                         _exit(0);
1441                 }
1442         } else if (pid < 0) { /* Fork error */
1443                 alertpanel_error(_("Could not fork to execute the following "
1444                                    "command:\n%s\n%s"),
1445                                  cmd, g_strerror(errno));
1446                 return NULL; 
1447         }
1448
1449         /* Parent */
1450
1451         if (!sync) {
1452                 waitpid(pid, NULL, 0);
1453                 return NULL;
1454         }
1455
1456         close(chld_in[0]);
1457         if (!(action_type & (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN)))
1458                 close(chld_in[1]);
1459         close(chld_out[1]);
1460         close(chld_err[1]);
1461         close(chld_status[1]);
1462
1463         child_info = g_new0(ChildInfo, 1);
1464
1465         child_info->children    = children;
1466
1467         child_info->pid         = pid;
1468         child_info->cmd         = g_strdup(cmd);
1469         child_info->type        = action_type;
1470         child_info->new_out     = FALSE;
1471         child_info->output      = g_string_sized_new(0);
1472         child_info->chld_in     =
1473                 (action_type &
1474                  (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN))
1475                         ? chld_in [1] : -1;
1476         child_info->chld_out    = chld_out[0];
1477         child_info->chld_err    = chld_err[0];
1478         child_info->chld_status = chld_status[0];
1479         child_info->tag_in      = -1;
1480         child_info->tag_out     = gdk_input_add(chld_out[0], GDK_INPUT_READ,
1481                                                 catch_output, child_info);
1482         child_info->tag_err     = gdk_input_add(chld_err[0], GDK_INPUT_READ,
1483                                                 catch_output, child_info);
1484
1485         if (!(action_type & (ACTION_PIPE_IN | ACTION_PIPE_OUT | ACTION_INSERT)))
1486                 return child_info;
1487
1488         child_info->text        = text;
1489         child_info->msgfont     = msgfont;
1490
1491         start = body_pos;
1492         end   = gtk_stext_get_length(GTK_STEXT(text));
1493
1494         if (GTK_EDITABLE(text)->has_selection) {
1495                 start = GTK_EDITABLE(text)->selection_start_pos;
1496                 end   = GTK_EDITABLE(text)->selection_end_pos;
1497                 if (start > end) {
1498                         guint tmp;
1499                         tmp = start;
1500                         start = end;
1501                         end = tmp;
1502                 }
1503                 is_selection = TRUE;
1504                 if (start == end) {
1505                         start = 0;
1506                         end = gtk_stext_get_length(GTK_STEXT(text));
1507                         is_selection = FALSE;
1508                 }
1509         }
1510
1511         selection = gtk_editable_get_chars(GTK_EDITABLE(text), start, end);
1512
1513         if (action_type & ACTION_PIPE_IN) {
1514                 write(chld_in[1], selection, strlen(selection));
1515                 if (!(action_type & (ACTION_USER_IN | ACTION_USER_HIDDEN_IN)))
1516                         close(chld_in[1]);
1517                 child_info->chld_in = -1; /* No more input */
1518         }
1519         g_free(selection);
1520
1521         gtk_stext_freeze(GTK_STEXT(text));
1522         if (action_type & ACTION_PIPE_OUT) {
1523                 gtk_stext_set_point(GTK_STEXT(text), start);
1524                 gtk_stext_forward_delete(GTK_STEXT(text), end - start);
1525         }
1526
1527         gtk_stext_thaw(GTK_STEXT(text));
1528
1529         return child_info;
1530 }
1531
1532 static void kill_children_cb(GtkWidget *widget, gpointer data)
1533 {
1534         GSList *cur;
1535         Children *children = (Children *) data;
1536         ChildInfo *child_info;
1537
1538         for (cur = children->list; cur; cur = cur->next) {
1539                 child_info = (ChildInfo *)(cur->data);
1540                 debug_print("Killing child group id %d\n", child_info->pid);
1541                 if (child_info->pid && kill(-child_info->pid, SIGTERM) < 0)
1542                         perror("kill");
1543         }
1544 }
1545
1546 static gint wait_for_children(gpointer data)
1547 {
1548         gboolean new_output;
1549         Children *children = (Children *)data;
1550         ChildInfo *child_info;
1551         GSList *cur;
1552         gint nb = children->nb;
1553
1554         children->nb = 0;
1555
1556         cur = children->list;
1557         new_output = FALSE;
1558         while (cur) {
1559                 child_info = (ChildInfo *)cur->data;
1560                 if (child_info->pid)
1561                         children->nb++;
1562                 new_output |= child_info->new_out;
1563                 cur = cur->next;
1564         }
1565
1566         children->output |= new_output;
1567
1568         if (new_output || (children->dialog && (nb != children->nb)))
1569                 update_io_dialog(children);
1570
1571         if (children->nb)
1572                 return FALSE;
1573
1574         if (!children->dialog) {
1575                 free_children(children);
1576         } else if (!children->output) {
1577                 gtk_widget_destroy(children->dialog);
1578         }
1579
1580         return FALSE;
1581 }
1582
1583 static void send_input(GtkWidget *w, gpointer data)
1584 {
1585         Children *children = (Children *) data;
1586         ChildInfo *child_info = (ChildInfo *) children->list->data;
1587
1588         child_info->tag_in = gdk_input_add(child_info->chld_in,
1589                                            GDK_INPUT_WRITE,
1590                                            catch_input, children);
1591         gtk_widget_set_sensitive(children->input_hbox, FALSE);
1592 }
1593
1594 static gint delete_io_dialog_cb(GtkWidget *w, GdkEvent *e, gpointer data)
1595 {
1596         hide_io_dialog_cb(w, data);
1597         return TRUE;
1598 }
1599
1600 static void hide_io_dialog_cb(GtkWidget *w, gpointer data)
1601 {
1602
1603         Children *children = (Children *)data;
1604
1605         if (!children->nb) {
1606                 gtk_signal_disconnect_by_data(GTK_OBJECT(children->dialog),
1607                                               children);
1608                 gtk_widget_destroy(children->dialog);
1609                 free_children(children);
1610         }
1611 }
1612
1613 static gint io_dialog_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
1614                                      gpointer data)
1615 {
1616         if (event && event->keyval == GDK_Escape)
1617                 hide_io_dialog_cb(widget, data);
1618         return TRUE;
1619 }
1620
1621 static void childinfo_close_pipes(ChildInfo *child_info)
1622 {
1623         if (child_info->tag_in > 0)
1624                 gdk_input_remove(child_info->tag_in);
1625         gdk_input_remove(child_info->tag_out);
1626         gdk_input_remove(child_info->tag_err);
1627
1628         if (child_info->chld_in >= 0)
1629                 close(child_info->chld_in);
1630         close(child_info->chld_out);
1631         close(child_info->chld_err);
1632         close(child_info->chld_status);
1633 }
1634
1635 static void free_children(Children *children)
1636 {
1637         GSList *cur;
1638         ChildInfo *child_info;
1639
1640         debug_print("Freeing children data %p\n", children);
1641
1642         g_free(children->action);
1643         for (cur = children->list; cur;) {
1644                 child_info = (ChildInfo *)cur->data;
1645                 g_free(child_info->cmd);
1646                 g_string_free(child_info->output, TRUE);
1647                 children->list = g_slist_remove(children->list, child_info);
1648                 g_free(child_info);
1649                 cur = children->list;
1650         }
1651         g_free(children);
1652 }
1653
1654 static void update_io_dialog(Children *children)
1655 {
1656         GSList *cur;
1657
1658         debug_print("Updating actions input/output dialog.\n");
1659
1660         if (!children->nb) {
1661                 gtk_widget_set_sensitive(children->abort_btn, FALSE);
1662                 gtk_widget_set_sensitive(children->close_btn, TRUE);
1663                 if (children->input_hbox)
1664                         gtk_widget_set_sensitive(children->input_hbox, FALSE);
1665                 gtk_widget_grab_focus(children->close_btn);
1666                 gtk_signal_connect(GTK_OBJECT(children->dialog),
1667                                    "key_press_event",
1668                                    GTK_SIGNAL_FUNC(io_dialog_key_pressed_cb),
1669                                    children);
1670         }
1671
1672         if (children->output) {
1673                 GtkWidget *text = children->text;
1674                 gchar *caption;
1675                 ChildInfo *child_info;
1676
1677                 gtk_widget_show(children->scrolledwin);
1678                 gtk_text_freeze(GTK_TEXT(text));
1679                 gtk_text_set_point(GTK_TEXT(text), 0);
1680                 gtk_text_forward_delete(GTK_TEXT(text), 
1681                                         gtk_text_get_length(GTK_TEXT(text)));
1682                 for (cur = children->list; cur; cur = cur->next) {
1683                         child_info = (ChildInfo *)cur->data;
1684                         if (child_info->pid)
1685                                 caption = g_strdup_printf
1686                                         (_("--- Running: %s\n"),
1687                                          child_info->cmd);
1688                         else
1689                                 caption = g_strdup_printf
1690                                         (_("--- Ended: %s\n"),
1691                                          child_info->cmd);
1692
1693                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1694                                         caption, -1);
1695                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1696                                         child_info->output->str, -1);
1697                         g_free(caption);
1698                         child_info->new_out = FALSE;
1699                 }
1700                 gtk_text_thaw(GTK_TEXT(text));
1701         }
1702 }
1703
1704 static void create_io_dialog(Children *children)
1705 {
1706         GtkWidget *dialog;
1707         GtkWidget *vbox;
1708         GtkWidget *entry = NULL;
1709         GtkWidget *input_hbox = NULL;
1710         GtkWidget *send_button;
1711         GtkWidget *label;
1712         GtkWidget *text;
1713         GtkWidget *scrolledwin;
1714         GtkWidget *hbox;
1715         GtkWidget *abort_button;
1716         GtkWidget *close_button;
1717
1718         debug_print("Creating action IO dialog\n");
1719
1720         dialog = gtk_dialog_new();
1721         gtk_container_set_border_width
1722                 (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
1723         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
1724         gtk_window_set_title(GTK_WINDOW(dialog), _("Action's input/output"));
1725         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1726         manage_window_set_transient(GTK_WINDOW(dialog));
1727         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
1728                         GTK_SIGNAL_FUNC(delete_io_dialog_cb), children);
1729         gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
1730                         GTK_SIGNAL_FUNC(hide_io_dialog_cb),
1731                         children);
1732
1733         vbox = gtk_vbox_new(FALSE, 8);
1734         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
1735         gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1736         gtk_widget_show(vbox);
1737
1738         label = gtk_label_new(children->action);
1739         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1740         gtk_widget_show(label);
1741
1742         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
1743         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
1744                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
1745         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
1746         gtk_widget_set_usize(scrolledwin, 480, 200);
1747         gtk_widget_hide(scrolledwin);
1748
1749         text = gtk_text_new(gtk_scrolled_window_get_hadjustment
1750                             (GTK_SCROLLED_WINDOW(scrolledwin)),
1751                             gtk_scrolled_window_get_vadjustment
1752                             (GTK_SCROLLED_WINDOW(scrolledwin)));
1753         gtk_text_set_editable(GTK_TEXT(text), FALSE);
1754         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
1755         gtk_widget_show(text);
1756
1757         if (children->open_in) {
1758                 input_hbox = gtk_hbox_new(FALSE, 8);
1759                 gtk_widget_show(input_hbox);
1760
1761                 entry = gtk_entry_new();
1762                 gtk_widget_set_usize(entry, 320, -1);
1763                 gtk_signal_connect(GTK_OBJECT(entry), "activate",
1764                                    GTK_SIGNAL_FUNC(send_input), children);
1765                 gtk_box_pack_start(GTK_BOX(input_hbox), entry, TRUE, TRUE, 0);
1766                 if (children->open_in & ACTION_USER_HIDDEN_IN)
1767                         gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
1768                 gtk_widget_show(entry);
1769
1770                 send_button = gtk_button_new_with_label(_(" Send "));
1771                 gtk_signal_connect(GTK_OBJECT(send_button), "clicked",
1772                                    GTK_SIGNAL_FUNC(send_input), children);
1773                 gtk_box_pack_start(GTK_BOX(input_hbox), send_button, FALSE,
1774                                    FALSE, 0);
1775                 gtk_widget_show(send_button);
1776
1777                 gtk_box_pack_start(GTK_BOX(vbox), input_hbox, FALSE, FALSE, 0);
1778                 gtk_widget_grab_focus(entry);
1779         }
1780
1781         gtkut_button_set_create(&hbox, &abort_button, _("Abort"),
1782                                 &close_button, _("Close"), NULL, NULL);
1783         gtk_signal_connect(GTK_OBJECT(abort_button), "clicked",
1784                         GTK_SIGNAL_FUNC(kill_children_cb), children);
1785         gtk_signal_connect(GTK_OBJECT(close_button), "clicked",
1786                         GTK_SIGNAL_FUNC(hide_io_dialog_cb), children);
1787         gtk_widget_show(hbox);
1788
1789         if (children->nb)
1790                 gtk_widget_set_sensitive(close_button, FALSE);
1791
1792         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
1793
1794         children->dialog      = dialog;
1795         children->scrolledwin = scrolledwin;
1796         children->text        = text;
1797         children->input_hbox  = children->open_in ? input_hbox : NULL;
1798         children->input_entry = children->open_in ? entry : NULL;
1799         children->abort_btn   = abort_button;
1800         children->close_btn   = close_button;
1801
1802         gtk_widget_show(dialog);
1803 }
1804
1805 static void catch_status(gpointer data, gint source, GdkInputCondition cond)
1806 {
1807         ChildInfo *child_info = (ChildInfo *)data;
1808         gchar buf;
1809         gint c;
1810
1811         gdk_input_remove(child_info->tag_status);
1812
1813         c = read(source, &buf, 1);
1814         debug_print("Child returned %c\n", buf);
1815
1816         waitpid(-child_info->pid, NULL, 0);
1817         childinfo_close_pipes(child_info);
1818         child_info->pid = 0;
1819
1820         wait_for_children(child_info->children);
1821 }
1822         
1823 static void catch_input(gpointer data, gint source, GdkInputCondition cond)
1824 {
1825         Children *children = (Children *)data;
1826         ChildInfo *child_info = (ChildInfo *)children->list->data;
1827         gchar *input;
1828         gint c, count, len;
1829
1830         debug_print("Sending input to grand child.\n");
1831         if (!(cond && GDK_INPUT_WRITE))
1832                 return;
1833
1834         gdk_input_remove(child_info->tag_in);
1835         child_info->tag_in = -1;
1836
1837         input = gtk_editable_get_chars(GTK_EDITABLE(children->input_entry),
1838                                        0, -1);
1839         len = strlen(input);
1840         count = 0;
1841
1842         do {
1843                 c = write(child_info->chld_in, input + count, len - count);
1844                 if (c >= 0)
1845                         count += c;
1846         } while (c >= 0 && count < len);
1847
1848         if (c >= 0)
1849                 write(child_info->chld_in, "\n", 2);
1850
1851         g_free(input);
1852
1853         gtk_entry_set_text(GTK_ENTRY(children->input_entry), "");
1854         gtk_widget_set_sensitive(children->input_hbox, TRUE);
1855         close(child_info->chld_in);
1856         child_info->chld_in = -1;
1857         debug_print("Input to grand child sent.\n");
1858 }
1859
1860 static void catch_output(gpointer data, gint source, GdkInputCondition cond)
1861 {
1862         ChildInfo *child_info = (ChildInfo *)data;
1863         gint c, i;
1864         gchar buf[PREFSBUFSIZE];
1865
1866         debug_print("Catching grand child's output.\n");
1867         if (child_info->type & (ACTION_PIPE_OUT | ACTION_INSERT)
1868             && source == child_info->chld_out) {
1869                 gboolean is_selection = FALSE;
1870                 GtkWidget *text = child_info->text;
1871                 if (GTK_EDITABLE(text)->has_selection)
1872                         is_selection = TRUE;
1873                 gtk_stext_freeze(GTK_STEXT(text));
1874                 while (TRUE) {
1875                         c = read(source, buf, PREFSBUFSIZE - 1);
1876                         if (c == 0)
1877                                 break;
1878                         gtk_stext_insert(GTK_STEXT(text), child_info->msgfont,
1879                                          NULL, NULL, buf, c);
1880                 }
1881                 if (is_selection) {
1882                         /* Using the select_region draws things. Should not.
1883                          * so we just change selection position and 
1884                          * defere drawing when thawing. Hack?
1885                          */
1886                         GTK_EDITABLE(text)->selection_end_pos =
1887                                         gtk_stext_get_point(GTK_STEXT(text));
1888                 }
1889                 gtk_stext_thaw(GTK_STEXT(child_info->text));
1890         } else {
1891                 c = read(source, buf, PREFSBUFSIZE - 1);
1892                 for (i = 0; i < c; i++)
1893                         child_info->output = g_string_append_c
1894                                 (child_info->output, buf[i]);
1895                 if (c > 0)
1896                         child_info->new_out = TRUE;
1897         }
1898         wait_for_children(child_info->children);
1899 }
1900
1901 static gboolean user_string_dialog_delete_cb(GtkWidget *widget,
1902                                              GdkEvent *event, gpointer data)
1903 {
1904         return FALSE;
1905 }
1906
1907 static void user_string_dialog_destroy_cb(GtkWidget *widget, gpointer data)
1908 {
1909         gtk_main_quit();
1910 }
1911
1912 static void user_string_dialog_activate_cb(GtkWidget *widget, gpointer data)
1913 {
1914         UserStringDialog *user_dialog = (UserStringDialog *) data;
1915
1916         g_free(user_dialog->user_str);
1917         user_dialog->user_str =
1918                 gtk_editable_get_chars(GTK_EDITABLE(user_dialog->entry), 0, -1);
1919         gtk_widget_destroy(user_dialog->dialog);
1920 }
1921
1922 static gchar *get_user_string(const gchar *action, ActionType type)
1923 {
1924         GtkWidget *dialog;
1925         GtkWidget *label;
1926         GtkWidget *entry;
1927         GtkWidget *ok_button;
1928         GtkWidget *cancel_button;
1929         gchar *label_text;
1930         UserStringDialog user_dialog;
1931
1932         dialog = gtk_dialog_new();
1933         gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
1934         gtk_container_set_border_width
1935                 (GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), 8);
1936         gtk_container_set_border_width
1937                 (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
1938         gtk_window_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
1939
1940         switch (type) {
1941         case ACTION_USER_HIDDEN_STR:
1942                 gtk_window_set_title(GTK_WINDOW(dialog),
1943                                      _("Action's hidden user argument"));
1944                 label_text = g_strdup_printf(_("Enter the '%%h' "
1945                                              "argument for the following "
1946                                              "action:\n%s"), action);
1947                 break;
1948         case ACTION_USER_STR:
1949                 gtk_window_set_title(GTK_WINDOW(dialog),
1950                                      _("Action's user argument"));
1951                 label_text = g_strdup_printf(_("Enter the '%%u' "
1952                                              "argument for the following "
1953                                              "action:\n%s"), action);
1954                 break;
1955         default:
1956                 label_text = NULL;
1957                 debug_print("Unsupported action type %d", type);
1958         }
1959
1960         label = gtk_label_new(label_text);
1961         g_free(label_text);
1962         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label,
1963                            TRUE, TRUE, 0);
1964
1965         entry = gtk_entry_new();
1966         gtk_entry_set_visibility(GTK_ENTRY(entry),
1967                                  type != ACTION_USER_HIDDEN_STR);
1968         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), entry,
1969                            TRUE, TRUE, 0);
1970
1971         ok_button = gtk_button_new_with_label(_("OK"));
1972         gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1973                          ok_button, TRUE, TRUE, 0);
1974
1975         cancel_button = gtk_button_new_with_label(_("Cancel"));
1976         gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1977                          cancel_button, TRUE, TRUE, 0);
1978
1979         user_dialog.dialog   = dialog;
1980         user_dialog.user_str = NULL;
1981         user_dialog.entry    = entry;
1982
1983         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
1984                            GTK_SIGNAL_FUNC(user_string_dialog_delete_cb),
1985                            &user_dialog);
1986         gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
1987                            GTK_SIGNAL_FUNC(user_string_dialog_destroy_cb),
1988                            &user_dialog);
1989         gtk_signal_connect(GTK_OBJECT(entry), "activate",
1990                            GTK_SIGNAL_FUNC(user_string_dialog_activate_cb),
1991                            &user_dialog);
1992         gtk_signal_connect(GTK_OBJECT(ok_button), "clicked",
1993                            GTK_SIGNAL_FUNC(user_string_dialog_activate_cb),
1994                            &user_dialog);
1995         gtk_signal_connect_object(GTK_OBJECT(cancel_button), "clicked",
1996                                   GTK_SIGNAL_FUNC(gtk_widget_destroy),
1997                                   GTK_OBJECT(dialog));
1998
1999         gtk_widget_grab_focus(entry);
2000         gtk_widget_show_all(dialog);
2001         gtk_main();
2002
2003         return user_dialog.user_str;
2004 }
2005
2006 /*
2007  * Strings describing action format strings
2008  * 
2009  * When adding new lines, remember to put one string for each line
2010  */
2011 static gchar *actions_desc_strings[] = {
2012         N_("MENU NAME:"), NULL,
2013         "      ",   N_("Use / in menu name to make submenus."),
2014         "", NULL,
2015         N_("COMMAND LINE:"), NULL,
2016         N_("Begin with:"), NULL,
2017         "     |",   N_("to send message body or selection to command"),
2018         "     >",   N_("to send user provided text to command"),
2019         "     *",   N_("to send user provided hidden text to command"),
2020         N_("End with:"), NULL, 
2021         "     |",   N_("to replace message body or selection with command output"),
2022         "     >",   N_("to insert command's output without replacing old text"),
2023         "     &",   N_("to run command asynchronously"),
2024         N_("Use:"), NULL, 
2025         "     %f",  N_("for message file name"),
2026         "     %F",  N_("for the list of the file names of selected messages"),
2027         "     %p",  N_("for the selected message MIME part"),
2028         "     %u",  N_("for a user provided argument"),
2029         "     %h",  N_("for a user provided hidden argument"),
2030         "     %s",  N_("for the text selection"),
2031         NULL
2032 };
2033
2034
2035 static DescriptionWindow actions_desc_win = { 
2036         NULL, 
2037         2,
2038         N_("Description of symbols"),
2039         actions_desc_strings
2040 };
2041
2042
2043 static void prefs_actions_help_cb(GtkWidget *w, gpointer data)
2044 {
2045         description_window_create(&actions_desc_win);
2046 }