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