sync with 0.8.11cvs27
[claws.git] / src / action.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 "utils.h"
41 #include "gtkutils.h"
42 #include "manage_window.h"
43 #include "mainwindow.h"
44 #include "prefs_common.h"
45 #include "alertpanel.h"
46 #include "action.h"
47 #include "compose.h"
48 #include "procmsg.h"
49 #include "gtkstext.h"
50 #include "mimeview.h"
51 #include "textview.h"
52
53 typedef struct _Children                Children;
54 typedef struct _ChildInfo               ChildInfo;
55 typedef struct _UserStringDialog        UserStringDialog;
56
57 struct _Children
58 {
59         GtkWidget       *dialog;
60         GtkWidget       *text;
61         GtkWidget       *input_entry;
62         GtkWidget       *input_hbox;
63         GtkWidget       *abort_btn;
64         GtkWidget       *close_btn;
65         GtkWidget       *scrolledwin;
66
67         gchar           *action;
68         GSList          *list;
69         gint             nb;
70         gint             open_in;
71         gboolean         output;
72 };
73
74 struct _ChildInfo
75 {
76         Children        *children;
77         gchar           *cmd;
78         guint            type;
79         pid_t            pid;
80         gint             chld_in;
81         gint             chld_out;
82         gint             chld_err;
83         gint             chld_status;
84         gint             tag_in;
85         gint             tag_out;
86         gint             tag_err;
87         gint             tag_status;
88         gint             new_out;
89         GString         *output;
90         GtkWidget       *text;
91         GdkFont         *msgfont;
92 };
93
94 struct _UserStringDialog
95 {
96         GtkWidget       *dialog;
97         GtkWidget       *entry;
98         gchar           *user_str;
99 };
100
101 static void update_actions_menu         (GtkItemFactory *ifactory,
102                                          gchar          *branch_path,
103                                          gpointer        callback,
104                                          gpointer        data);
105 static void compose_actions_execute_cb  (Compose        *compose,
106                                          guint           action_nb,
107                                          GtkWidget      *widget);
108 static void mainwin_actions_execute_cb  (MainWindow     *mainwin,
109                                          guint           action_nb,
110                                          GtkWidget      *widget);
111 static void msgview_actions_execute_cb  (MessageView    *msgview,
112                                          guint           action_nb,
113                                          GtkWidget      *widget);
114 static void message_actions_execute     (MessageView    *msgview,
115                                          guint           action_nb,
116                                          GtkCTree       *ctree);
117
118 static gboolean execute_actions         (gchar          *action, 
119                                          GtkCTree       *ctree, 
120                                          GtkWidget      *text,
121                                          GdkFont        *msgfont,
122                                          gint            body_pos,
123                                          MimeView       *mimeview);
124
125 static gchar *parse_action_cmd          (gchar          *action,
126                                          MsgInfo        *msginfo,
127                                          GtkCTree       *ctree,
128                                          MimeView       *mimeview,
129                                          const gchar    *user_str,
130                                          const gchar    *user_hidden_str,
131                                          const gchar    *sel_str);
132 static gboolean parse_append_filename   (GString        *cmd,
133                                          MsgInfo        *msginfo);
134
135 static gboolean parse_append_msgpart    (GString        *cmd,
136                                          MsgInfo        *msginfo,
137                                          MimeView       *mimeview);
138
139 static ChildInfo *fork_child            (gchar          *cmd,
140                                          gint            action_type,
141                                          GtkWidget      *text,
142                                          GdkFont        *msgfont,
143                                          gint            body_pos,
144                                          Children       *children);
145
146 static gint wait_for_children           (gpointer        data);
147
148 static void free_children               (Children       *children);
149
150 static void childinfo_close_pipes       (ChildInfo      *child_info);
151
152 static void create_io_dialog            (Children       *children);
153 static void update_io_dialog            (Children       *children);
154
155 static void hide_io_dialog_cb           (GtkWidget      *widget,
156                                          gpointer        data);
157 static gint io_dialog_key_pressed_cb    (GtkWidget      *widget,
158                                          GdkEventKey    *event,
159                                          gpointer        data);
160
161 static void catch_output                (gpointer                data,
162                                          gint                    source,
163                                          GdkInputCondition       cond);
164 static void catch_input                 (gpointer                data, 
165                                          gint                    source,
166                                          GdkInputCondition       cond);
167 static void catch_status                (gpointer                data,
168                                          gint                    source,
169                                          GdkInputCondition       cond);
170
171 static gboolean user_string_dialog_delete_cb
172                                         (GtkWidget      *widget,
173                                          GdkEvent       *event,
174                                          gpointer        data);
175 static void user_string_dialog_destroy_cb
176                                         (GtkWidget      *widget,
177                                          gpointer        data);
178 static void user_string_dialog_activate_cb
179                                         (GtkWidget      *widget,
180                                          gpointer        data);
181
182 static gchar *get_user_string           (const gchar    *action,
183                                          ActionType      type);
184
185
186 ActionType get_action_type(const gchar *action_str)
187 {
188         const gchar *p;
189         ActionType action_type = ACTION_NONE;
190
191         g_return_val_if_fail(action_str,  ACTION_ERROR);
192         g_return_val_if_fail(*action_str, ACTION_ERROR);
193
194         p = action_str;
195
196         if (p[0] == '|') {
197                 action_type |= ACTION_PIPE_IN;
198                 p++;
199         } else if (p[0] == '>') {
200                 action_type |= ACTION_USER_IN;
201                 p++;
202         } else if (p[0] == '*') {
203                 action_type |= ACTION_USER_HIDDEN_IN;
204                 p++;
205         }
206
207         if (p[0] == '\0')
208                 return ACTION_ERROR;
209
210         while (*p && action_type != ACTION_ERROR) {
211                 if (p[0] == '%') {
212                         switch (p[1]) {
213                         case 'f':
214                                 action_type |= ACTION_SINGLE;
215                                 break;
216                         case 'F':
217                                 action_type |= ACTION_MULTIPLE;
218                                 break;
219                         case 'p':
220                                 action_type |= ACTION_SINGLE;
221                                 break;
222                         case 's':
223                                 action_type |= ACTION_SELECTION_STR;
224                                 break;
225                         case 'u':
226                                 action_type |= ACTION_USER_STR;
227                                 break;
228                         case 'h':
229                                 action_type |= ACTION_USER_HIDDEN_STR;
230                                 break;
231                         default:
232                                 action_type = ACTION_ERROR;
233                                 break;
234                         }
235                 } else if (p[0] == '|') {
236                         if (p[1] == '\0')
237                                 action_type |= ACTION_PIPE_OUT;
238                 } else if (p[0] == '>') {
239                         if (p[1] == '\0')
240                                 action_type |= ACTION_INSERT;
241                 } else if (p[0] == '&') {
242                         if (p[1] == '\0')
243                                 action_type |= ACTION_ASYNC;
244                 }
245                 p++;
246         }
247
248         return action_type;
249 }
250
251 static gchar *parse_action_cmd(gchar *action, MsgInfo *msginfo,
252                                GtkCTree *ctree, MimeView *mimeview,
253                                const gchar *user_str,
254                                const gchar *user_hidden_str,
255                                const gchar *sel_str)
256 {
257         GString *cmd;
258         gchar *p;
259         GList *cur;
260         MsgInfo *msg;
261         
262         p = action;
263         
264         if (p[0] == '|' || p[0] == '>' || p[0] == '*')
265                 p++;
266
267         cmd = g_string_sized_new(strlen(action));
268
269         while (p[0] &&
270                !((p[0] == '|' || p[0] == '>' || p[0] == '&') && !p[1])) {
271                 if (p[0] == '%' && p[1]) {
272                         switch (p[1]) {
273                         case 'f':
274                                 if (!parse_append_filename(cmd, msginfo)) {
275                                         g_string_free(cmd, TRUE);
276                                         return NULL;
277                                 }
278                                 p++;
279                                 break;
280                         case 'F':
281                                 for (cur = GTK_CLIST(ctree)->selection;
282                                      cur != NULL; cur = cur->next) {
283                                         msg = gtk_ctree_node_get_row_data(ctree,
284                                               GTK_CTREE_NODE(cur->data));
285                                         if (!parse_append_filename(cmd, msg)) {
286                                                 g_string_free(cmd, TRUE);
287                                                 return NULL;
288                                         }
289                                         if (cur->next)
290                                                 g_string_append_c(cmd, ' ');
291                                 }
292                                 p++;
293                                 break;
294                         case 'p':
295                                 if (!parse_append_msgpart(cmd, msginfo,
296                                                           mimeview)) {
297                                         g_string_free(cmd, TRUE);
298                                         return NULL;
299                                 }
300                                 p++;
301                                 break;
302                         case 's':
303                                 if (sel_str)
304                                         g_string_append(cmd, sel_str);
305                                 p++;
306                                 break;
307                         case 'u':
308                                 if (user_str)
309                                         g_string_append(cmd, user_str);
310                                 p++;
311                                 break;
312                         case 'h':
313                                 if (user_hidden_str)
314                                         g_string_append(cmd, user_hidden_str);
315                                 p++;
316                                 break;
317                         default:
318                                 g_string_append_c(cmd, p[0]);
319                                 g_string_append_c(cmd, p[1]);
320                                 p++;
321                         }
322                 } else {
323                         g_string_append_c(cmd, p[0]);
324                 }
325                 p++;
326         }
327         if (cmd->len == 0) {
328                 g_string_free(cmd, TRUE);
329                 return NULL;
330         }
331
332         p = cmd->str;
333         g_string_free(cmd, FALSE);
334         return p;
335 }
336
337 static gboolean parse_append_filename(GString *cmd, MsgInfo *msginfo)
338 {
339         gchar *filename;
340
341         g_return_val_if_fail(msginfo, FALSE);
342
343         filename = procmsg_get_message_file(msginfo);
344
345         if (filename) {
346                 g_string_append(cmd, filename);
347                 g_free(filename);
348         } else {
349                 alertpanel_error(_("Could not get message file %d"),
350                                  msginfo->msgnum);
351                 return FALSE;
352         }
353
354         return TRUE;
355 }
356
357 static gboolean parse_append_msgpart(GString *cmd, MsgInfo *msginfo,
358                                      MimeView *mimeview)
359 {
360         gchar    *filename;
361         gchar    *partname;
362         MimeInfo *partinfo;
363         gint      ret;
364         FILE     *fp;
365
366         if (!mimeview) {
367 #if USE_GPGME
368                 if ((fp = procmsg_open_message_decrypted(msginfo, &partinfo))
369                     == NULL) {
370                         alertpanel_error(_("Could not get message file."));
371                         return FALSE;
372                 }
373 #else
374                 if ((fp = procmsg_open_message(msginfo)) == NULL) {
375                         alertpanel_error(_("Could not get message file."));
376                         return FALSE;
377                 }
378                 partinfo = procmime_scan_mime_header(fp);
379 #endif
380                 fclose(fp);
381                 if (!partinfo) {
382                         procmime_mimeinfo_free_all(partinfo);
383                         alertpanel_error(_("Could not get message part."));
384                         return FALSE;
385                 }
386                 filename = procmsg_get_message_file(msginfo);
387         } else {
388                 if (!mimeview->opened) {
389                         alertpanel_error(_("No message part selected."));
390                         return FALSE;
391                 }
392                 if (!mimeview->file) {
393                         alertpanel_error(_("No message file selected."));
394                         return FALSE;
395                 }
396                 partinfo = gtk_ctree_node_get_row_data
397                                 (GTK_CTREE(mimeview->ctree),
398                                  mimeview->opened);
399                 g_return_val_if_fail(partinfo != NULL, FALSE);
400                 filename = mimeview->file;
401         }
402         partname = procmime_get_tmp_file_name(partinfo);
403
404         ret = procmime_get_part(partname, filename, partinfo); 
405
406         if (!mimeview) {
407                 procmime_mimeinfo_free_all(partinfo);
408                 g_free(filename);
409         }
410
411         if (ret < 0) {
412                 alertpanel_error(_("Can't get part of multipart message"));
413                 g_free(partname);
414                 return FALSE;
415         }
416
417         g_string_append(cmd, partname);
418
419         g_free(partname);
420
421         return TRUE;
422 }
423
424 void actions_execute(gpointer data, 
425                      guint action_nb,
426                      GtkWidget *widget,
427                      gint source)
428 {
429         if (source == TOOLBAR_MAIN) 
430                 mainwin_actions_execute_cb((MainWindow*)data, action_nb, widget);
431         else if (source == TOOLBAR_COMPOSE)
432                 compose_actions_execute_cb((Compose*)data, action_nb, widget);
433         else if (source == TOOLBAR_MSGVIEW)
434                 msgview_actions_execute_cb((MessageView*)data, action_nb, widget);      
435 }
436
437
438 void update_mainwin_actions_menu(GtkItemFactory *ifactory,
439                                  MainWindow *mainwin)
440 {
441         update_actions_menu(ifactory, "/Tools/Actions",
442                             mainwin_actions_execute_cb,
443                             mainwin);
444 }
445
446 void update_compose_actions_menu(GtkItemFactory *ifactory,
447                                  gchar *branch_path,
448                                  Compose *compose)
449 {
450         update_actions_menu(ifactory, branch_path,
451                             compose_actions_execute_cb,
452                             compose);
453 }
454
455 static void update_actions_menu(GtkItemFactory *ifactory,
456                                 gchar *branch_path,
457                                 gpointer callback,
458                                 gpointer data)
459 {
460         GtkWidget *menuitem;
461         gchar *menu_path;
462         GSList *cur;
463         gchar *action, *action_p;
464         GList *amenu;
465         GtkItemFactoryEntry ifentry = {NULL, NULL, NULL, 0, "<Branch>"};
466
467         ifentry.path = branch_path;
468         menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
469         g_return_if_fail(menuitem != NULL);
470
471         amenu = GTK_MENU_SHELL(menuitem)->children;
472         while (amenu != NULL) {
473                 GList *alist = amenu->next;
474                 gtk_widget_destroy(GTK_WIDGET(amenu->data));
475                 amenu = alist;
476         }
477
478         ifentry.accelerator     = NULL;
479         ifentry.callback_action = 0;
480         ifentry.callback        = callback;
481         ifentry.item_type       = NULL;
482
483         for (cur = prefs_common.actions_list; cur; cur = cur->next) {
484                 action   = g_strdup((gchar *)cur->data);
485                 action_p = strstr(action, ": ");
486                 if (action_p && action_p[2] &&
487                     get_action_type(&action_p[2]) != ACTION_ERROR) {
488                         action_p[0] = 0x00;
489                         menu_path = g_strdup_printf("%s/%s", branch_path,
490                                                     action);
491                         ifentry.path = menu_path;
492                         gtk_item_factory_create_item(ifactory, &ifentry, data,
493                                                      1);
494                         g_free(menu_path);
495                 }
496                 g_free(action);
497                 ifentry.callback_action++;
498         }
499 }
500
501 static void compose_actions_execute_cb(Compose *compose, guint action_nb,
502                                        GtkWidget *widget)
503 {
504         gchar *buf, *action;
505         guint action_type;
506
507         g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list));
508
509         buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb);
510         g_return_if_fail(buf != NULL);
511         action = strstr(buf, ": ");
512         g_return_if_fail(action != NULL);
513
514         /* Point to the beginning of the command-line */
515         action += 2;
516
517         action_type = get_action_type(action);
518         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE)) {
519                 alertpanel_warning
520                         (_("The selected action cannot be used in the compose window\n"
521                            "because it contains %%f, %%F or %%p."));
522                 return;
523         }
524
525         execute_actions(action, NULL, compose->text, NULL, 0, NULL);
526 }
527
528 static void mainwin_actions_execute_cb(MainWindow *mainwin, guint action_nb,
529                                        GtkWidget *widget)
530 {
531         message_actions_execute(mainwin->messageview, action_nb,
532                                 GTK_CTREE(mainwin->summaryview->ctree));
533 }
534
535 static void msgview_actions_execute_cb(MessageView *msgview, guint action_nb,
536                                        GtkWidget *widget)
537 {
538         message_actions_execute(msgview, action_nb, NULL);
539 }
540
541 static void message_actions_execute(MessageView *msgview, guint action_nb,
542                                     GtkCTree *ctree)
543 {
544         TextView *textview = NULL;
545         MimeView *mimeview = NULL;
546         gchar *buf;
547         gchar *action;
548         GtkWidget *text = NULL;
549         GdkFont *msgfont = NULL;
550         guint body_pos = 0;
551
552         g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list));
553
554         buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb);
555
556         g_return_if_fail(buf);
557         g_return_if_fail(action = strstr(buf, ": "));
558
559         /* Point to the beginning of the command-line */
560         action += 2;
561
562         switch (msgview->type) {
563         case MVIEW_TEXT:
564                 if (msgview->textview && msgview->textview->text)
565                         textview = msgview->textview;
566                 break;
567         case MVIEW_MIME:
568                 if (msgview->mimeview) {
569                         mimeview = msgview->mimeview;
570                         if (msgview->mimeview->type == MIMEVIEW_TEXT &&
571                             msgview->mimeview->textview &&
572                             msgview->mimeview->textview->text)
573                                 textview = msgview->mimeview->textview;
574                 }
575                 break;
576         }
577
578         if (textview) {
579                 text     = textview->text;
580                 msgfont  = textview->msgfont;
581                 body_pos = textview->body_pos;
582         }
583
584         execute_actions(action, ctree, text, msgfont, body_pos, mimeview);
585 }
586
587 static gboolean execute_actions(gchar *action, GtkCTree *ctree,
588                                 GtkWidget *text, GdkFont *msgfont,
589                                 gint body_pos, MimeView *mimeview)
590 {
591         GList *cur, *selection = NULL;
592         GSList *children_list = NULL;
593         gint is_ok  = TRUE;
594         gint selection_len = 0;
595         Children *children;
596         ChildInfo *child_info;
597         gint action_type;
598         MsgInfo *msginfo;
599         gchar *cmd;
600         gchar *sel_str = NULL;
601         gchar *user_str = NULL;
602         gchar *user_hidden_str = NULL;
603
604         g_return_val_if_fail(action && *action, FALSE);
605
606         action_type = get_action_type(action);
607
608         if (action_type == ACTION_ERROR)
609                 return FALSE;         /* ERR: syntax error */
610
611         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE) && 
612             !(ctree && GTK_CLIST(ctree)->selection))
613                 return FALSE;         /* ERR: file command without selection */
614
615         if (ctree) {
616                 selection = GTK_CLIST(ctree)->selection;
617                 selection_len = g_list_length(selection);
618         }
619
620         if (action_type & (ACTION_PIPE_OUT | ACTION_PIPE_IN | ACTION_INSERT)) {
621                 if (ctree && selection_len > 1)
622                         return FALSE; /* ERR: pipe + multiple selection */
623                 if (!text)
624                         return FALSE; /* ERR: pipe and no displayed text */
625         }
626
627         if (action_type & ACTION_SELECTION_STR) {
628                 if (!text)
629                         return FALSE; /* ERR: selection string but no text */
630                 else {
631                         guint start = 0, end = 0;
632                         if (GTK_EDITABLE(text)->has_selection) {
633                                 start = GTK_EDITABLE(text)->selection_start_pos;
634                                 end   = GTK_EDITABLE(text)->selection_end_pos;
635                                 if (start > end) {
636                                         guint tmp;
637                                         tmp = start;
638                                         start = end;
639                                         end = tmp;
640                                 }
641                         }
642                         sel_str = gtk_editable_get_chars(GTK_EDITABLE(text),
643                                                          start, end);
644                 }
645         }
646
647         if (action_type & (ACTION_USER_STR))
648                 if (!(user_str = get_user_string(action, ACTION_USER_STR)))
649                         return FALSE;
650
651         if (action_type & (ACTION_USER_HIDDEN_STR))
652                 if (!(user_hidden_str =
653                         get_user_string(action, ACTION_USER_HIDDEN_STR)))
654                         return FALSE;
655
656         children = g_new0(Children, 1);
657
658         if (action_type & ACTION_SINGLE) {
659                 for (cur = selection; cur && is_ok == TRUE; cur = cur->next) {
660                         msginfo = gtk_ctree_node_get_row_data(ctree,
661                                         GTK_CTREE_NODE(cur->data));
662                         if (!msginfo) {
663                                 is_ok  = FALSE; /* ERR: msginfo missing */
664                                 break;
665                         }
666                         cmd = parse_action_cmd(action, msginfo, ctree,
667                                                mimeview, user_str,
668                                                user_hidden_str, sel_str);
669                         if (!cmd) {
670                                 debug_print("Action command error\n");
671                                 is_ok  = FALSE; /* ERR: incorrect command */
672                                 break;
673                         }
674                         if ((child_info = fork_child(cmd, action_type, text,
675                                                      msgfont, body_pos,
676                                                      children))) {
677                                 children_list = g_slist_append(children_list,
678                                                                child_info);
679                                 children->open_in = (selection_len == 1) ?
680                                                     (action_type &
681                                                      (ACTION_USER_IN |
682                                                       ACTION_USER_HIDDEN_IN)) : 0;
683                         }
684                         g_free(cmd);
685                 }
686         } else {
687                 cmd = parse_action_cmd(action, NULL, ctree, mimeview, user_str,
688                                        user_hidden_str, sel_str);
689                 if (cmd) {
690                         if ((child_info = fork_child(cmd, action_type, text,
691                                                      msgfont, body_pos,
692                                                      children))) {
693                                 children_list = g_slist_append(children_list,
694                                                                child_info);
695                                 children->open_in = action_type &
696                                                     (ACTION_USER_IN |
697                                                      ACTION_USER_HIDDEN_IN);
698                         }
699                         g_free(cmd);
700                 } else
701                         is_ok  = FALSE;         /* ERR: incorrect command */
702         }
703
704         g_free(user_str);
705         g_free(user_hidden_str);
706
707         if (!children_list) {
708                  /* If not waiting for children, return */
709                 g_free(children);
710         } else {
711                 GSList *cur;
712
713                 children->action  = g_strdup(action);
714                 children->dialog  = NULL;
715                 children->list    = children_list;
716                 children->nb      = g_slist_length(children_list);
717
718                 for (cur = children_list; cur; cur = cur->next) {
719                         child_info = (ChildInfo *) cur->data;
720                         child_info->tag_status = 
721                                 gdk_input_add(child_info->chld_status,
722                                               GDK_INPUT_READ,
723                                               catch_status, child_info);
724                 }
725
726                 create_io_dialog(children);
727         }
728
729         return is_ok;
730 }
731
732 static ChildInfo *fork_child(gchar *cmd, gint action_type, GtkWidget *text,
733                              GdkFont *msgfont, gint body_pos,
734                              Children *children)
735 {
736         gint chld_in[2], chld_out[2], chld_err[2], chld_status[2];
737         gchar *cmdline[4];
738         guint start, end;
739         gint is_selection;
740         gchar *selection;
741         pid_t pid, gch_pid;
742         ChildInfo *child_info;
743         gint sync;
744
745         sync = !(action_type & ACTION_ASYNC);
746
747         chld_in[0] = chld_in[1] = chld_out[0] = chld_out[1] = chld_err[0]
748                 = chld_err[1] = chld_status[0] = chld_status[1] = -1;
749
750         if (sync) {
751                 if (pipe(chld_status) || pipe(chld_in) || pipe(chld_out) ||
752                     pipe(chld_err)) {
753                         alertpanel_error(_("Command could not be started. "
754                                            "Pipe creation failed.\n%s"),
755                                         g_strerror(errno));
756                         /* Closing fd = -1 fails silently */
757                         close(chld_in[0]);
758                         close(chld_in[1]);
759                         close(chld_out[0]);
760                         close(chld_out[1]);
761                         close(chld_err[0]);
762                         close(chld_err[1]);
763                         close(chld_status[0]);
764                         close(chld_status[1]);
765                         return NULL; /* Pipe error */
766                 }
767         }
768
769         debug_print("Forking child and grandchild.\n");
770
771         pid = fork();
772         if (pid == 0) { /* Child */
773                 if (setpgid(0, 0))
774                         perror("setpgid");
775
776                 close(ConnectionNumber(gdk_display));
777
778                 gch_pid = fork();
779
780                 if (gch_pid == 0) {
781                         if (setpgid(0, getppid()))
782                                 perror("setpgid");
783                         if (sync) {
784                                 if (action_type &
785                                     (ACTION_PIPE_IN |
786                                      ACTION_USER_IN |
787                                      ACTION_USER_HIDDEN_IN)) {
788                                         close(fileno(stdin));
789                                         dup  (chld_in[0]);
790                                 }
791                                 close(chld_in[0]);
792                                 close(chld_in[1]);
793
794                                 close(fileno(stdout));
795                                 dup  (chld_out[1]);
796                                 close(chld_out[0]);
797                                 close(chld_out[1]);
798
799                                 close(fileno(stderr));
800                                 dup  (chld_err[1]);
801                                 close(chld_err[0]);
802                                 close(chld_err[1]);
803                         }
804
805                         cmdline[0] = "sh";
806                         cmdline[1] = "-c";
807                         cmdline[2] = cmd;
808                         cmdline[3] = 0;
809                         execvp("/bin/sh", cmdline);
810
811                         perror("execvp");
812                         _exit(1);
813                 } else if (gch_pid < (pid_t) 0) { /* Fork error */
814                         if (sync)
815                                 write(chld_status[1], "1\n", 2);
816                         perror("fork");
817                         _exit(1);
818                 } else { /* Child */
819                         if (sync) {
820                                 close(chld_in[0]);
821                                 close(chld_in[1]);
822                                 close(chld_out[0]);
823                                 close(chld_out[1]);
824                                 close(chld_err[0]);
825                                 close(chld_err[1]);
826                                 close(chld_status[0]);
827                         }
828                         if (sync) {
829                                 debug_print("Child: Waiting for grandchild\n");
830                                 waitpid(gch_pid, NULL, 0);
831                                 debug_print("Child: grandchild ended\n");
832                                 write(chld_status[1], "0\n", 2);
833                                 close(chld_status[1]);
834                         }
835                         _exit(0);
836                 }
837         } else if (pid < 0) { /* Fork error */
838                 alertpanel_error(_("Could not fork to execute the following "
839                                    "command:\n%s\n%s"),
840                                  cmd, g_strerror(errno));
841                 return NULL; 
842         }
843
844         /* Parent */
845
846         if (!sync) {
847                 waitpid(pid, NULL, 0);
848                 return NULL;
849         }
850
851         close(chld_in[0]);
852         if (!(action_type & (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN)))
853                 close(chld_in[1]);
854         close(chld_out[1]);
855         close(chld_err[1]);
856         close(chld_status[1]);
857
858         child_info = g_new0(ChildInfo, 1);
859
860         child_info->children    = children;
861
862         child_info->pid         = pid;
863         child_info->cmd         = g_strdup(cmd);
864         child_info->type        = action_type;
865         child_info->new_out     = FALSE;
866         child_info->output      = g_string_new(NULL);
867         child_info->chld_in     =
868                 (action_type &
869                  (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN))
870                         ? chld_in [1] : -1;
871         child_info->chld_out    = chld_out[0];
872         child_info->chld_err    = chld_err[0];
873         child_info->chld_status = chld_status[0];
874         child_info->tag_in      = -1;
875         child_info->tag_out     = gdk_input_add(chld_out[0], GDK_INPUT_READ,
876                                                 catch_output, child_info);
877         child_info->tag_err     = gdk_input_add(chld_err[0], GDK_INPUT_READ,
878                                                 catch_output, child_info);
879
880         if (!(action_type & (ACTION_PIPE_IN | ACTION_PIPE_OUT | ACTION_INSERT)))
881                 return child_info;
882
883         child_info->text        = text;
884         child_info->msgfont     = msgfont;
885
886         start = body_pos;
887         end   = gtk_stext_get_length(GTK_STEXT(text));
888
889         if (GTK_EDITABLE(text)->has_selection) {
890                 start = GTK_EDITABLE(text)->selection_start_pos;
891                 end   = GTK_EDITABLE(text)->selection_end_pos;
892                 if (start > end) {
893                         guint tmp;
894                         tmp = start;
895                         start = end;
896                         end = tmp;
897                 }
898                 is_selection = TRUE;
899                 if (start == end) {
900                         start = 0;
901                         end = gtk_stext_get_length(GTK_STEXT(text));
902                         is_selection = FALSE;
903                 }
904         }
905
906         selection = gtk_editable_get_chars(GTK_EDITABLE(text), start, end);
907
908         if (action_type & ACTION_PIPE_IN) {
909                 write(chld_in[1], selection, strlen(selection));
910                 if (!(action_type & (ACTION_USER_IN | ACTION_USER_HIDDEN_IN)))
911                         close(chld_in[1]);
912                 child_info->chld_in = -1; /* No more input */
913         }
914         g_free(selection);
915
916         gtk_stext_freeze(GTK_STEXT(text));
917         if (action_type & ACTION_PIPE_OUT) {
918                 gtk_stext_set_point(GTK_STEXT(text), start);
919                 gtk_stext_forward_delete(GTK_STEXT(text), end - start);
920         }
921
922         gtk_stext_thaw(GTK_STEXT(text));
923
924         return child_info;
925 }
926
927 static void kill_children_cb(GtkWidget *widget, gpointer data)
928 {
929         GSList *cur;
930         Children *children = (Children *) data;
931         ChildInfo *child_info;
932
933         for (cur = children->list; cur; cur = cur->next) {
934                 child_info = (ChildInfo *)(cur->data);
935                 debug_print("Killing child group id %d\n", child_info->pid);
936                 if (child_info->pid && kill(-child_info->pid, SIGTERM) < 0)
937                         perror("kill");
938         }
939 }
940
941 static gint wait_for_children(gpointer data)
942 {
943         gboolean new_output;
944         Children *children = (Children *)data;
945         ChildInfo *child_info;
946         GSList *cur;
947         gint nb = children->nb;
948
949         children->nb = 0;
950
951         cur = children->list;
952         new_output = FALSE;
953         while (cur) {
954                 child_info = (ChildInfo *)cur->data;
955                 if (child_info->pid)
956                         children->nb++;
957                 new_output |= child_info->new_out;
958                 cur = cur->next;
959         }
960
961         children->output |= new_output;
962
963         if (new_output || (children->dialog && (nb != children->nb)))
964                 update_io_dialog(children);
965
966         if (children->nb)
967                 return FALSE;
968
969         if (!children->dialog) {
970                 free_children(children);
971         } else if (!children->output) {
972                 gtk_widget_destroy(children->dialog);
973         }
974
975         return FALSE;
976 }
977
978 static void send_input(GtkWidget *w, gpointer data)
979 {
980         Children *children = (Children *) data;
981         ChildInfo *child_info = (ChildInfo *) children->list->data;
982
983         child_info->tag_in = gdk_input_add(child_info->chld_in,
984                                            GDK_INPUT_WRITE,
985                                            catch_input, children);
986         gtk_widget_set_sensitive(children->input_hbox, FALSE);
987 }
988
989 static gint delete_io_dialog_cb(GtkWidget *w, GdkEvent *e, gpointer data)
990 {
991         hide_io_dialog_cb(w, data);
992         return TRUE;
993 }
994
995 static void hide_io_dialog_cb(GtkWidget *w, gpointer data)
996 {
997
998         Children *children = (Children *)data;
999
1000         if (!children->nb) {
1001                 gtk_signal_disconnect_by_data(GTK_OBJECT(children->dialog),
1002                                               children);
1003                 gtk_widget_destroy(children->dialog);
1004                 free_children(children);
1005         }
1006 }
1007
1008 static gint io_dialog_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
1009                                      gpointer data)
1010 {
1011         if (event && event->keyval == GDK_Escape)
1012                 hide_io_dialog_cb(widget, data);
1013         return TRUE;
1014 }
1015
1016 static void childinfo_close_pipes(ChildInfo *child_info)
1017 {
1018         if (child_info->tag_in > 0)
1019                 gdk_input_remove(child_info->tag_in);
1020         gdk_input_remove(child_info->tag_out);
1021         gdk_input_remove(child_info->tag_err);
1022
1023         if (child_info->chld_in >= 0)
1024                 close(child_info->chld_in);
1025         close(child_info->chld_out);
1026         close(child_info->chld_err);
1027         close(child_info->chld_status);
1028 }
1029
1030 static void free_children(Children *children)
1031 {
1032         GSList *cur;
1033         ChildInfo *child_info;
1034
1035         debug_print("Freeing children data %p\n", children);
1036
1037         g_free(children->action);
1038         for (cur = children->list; cur;) {
1039                 child_info = (ChildInfo *)cur->data;
1040                 g_free(child_info->cmd);
1041                 g_string_free(child_info->output, TRUE);
1042                 children->list = g_slist_remove(children->list, child_info);
1043                 g_free(child_info);
1044                 cur = children->list;
1045         }
1046         g_free(children);
1047 }
1048
1049 static void update_io_dialog(Children *children)
1050 {
1051         GSList *cur;
1052
1053         debug_print("Updating actions input/output dialog.\n");
1054
1055         if (!children->nb) {
1056                 gtk_widget_set_sensitive(children->abort_btn, FALSE);
1057                 gtk_widget_set_sensitive(children->close_btn, TRUE);
1058                 if (children->input_hbox)
1059                         gtk_widget_set_sensitive(children->input_hbox, FALSE);
1060                 gtk_widget_grab_focus(children->close_btn);
1061                 gtk_signal_connect(GTK_OBJECT(children->dialog),
1062                                    "key_press_event",
1063                                    GTK_SIGNAL_FUNC(io_dialog_key_pressed_cb),
1064                                    children);
1065         }
1066
1067         if (children->output) {
1068                 GtkWidget *text = children->text;
1069                 gchar *caption;
1070                 ChildInfo *child_info;
1071
1072                 gtk_widget_show(children->scrolledwin);
1073                 gtk_text_freeze(GTK_TEXT(text));
1074                 gtk_text_set_point(GTK_TEXT(text), 0);
1075                 gtk_text_forward_delete(GTK_TEXT(text), 
1076                                         gtk_text_get_length(GTK_TEXT(text)));
1077                 for (cur = children->list; cur; cur = cur->next) {
1078                         child_info = (ChildInfo *)cur->data;
1079                         if (child_info->pid)
1080                                 caption = g_strdup_printf
1081                                         (_("--- Running: %s\n"),
1082                                          child_info->cmd);
1083                         else
1084                                 caption = g_strdup_printf
1085                                         (_("--- Ended: %s\n"),
1086                                          child_info->cmd);
1087
1088                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1089                                         caption, -1);
1090                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1091                                         child_info->output->str, -1);
1092                         g_free(caption);
1093                         child_info->new_out = FALSE;
1094                 }
1095                 gtk_text_thaw(GTK_TEXT(text));
1096         }
1097 }
1098
1099 static void create_io_dialog(Children *children)
1100 {
1101         GtkWidget *dialog;
1102         GtkWidget *vbox;
1103         GtkWidget *entry = NULL;
1104         GtkWidget *input_hbox = NULL;
1105         GtkWidget *send_button;
1106         GtkWidget *label;
1107         GtkWidget *text;
1108         GtkWidget *scrolledwin;
1109         GtkWidget *hbox;
1110         GtkWidget *abort_button;
1111         GtkWidget *close_button;
1112
1113         debug_print("Creating action IO dialog\n");
1114
1115         dialog = gtk_dialog_new();
1116         gtk_container_set_border_width
1117                 (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
1118         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
1119         gtk_window_set_title(GTK_WINDOW(dialog), _("Action's input/output"));
1120         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1121         manage_window_set_transient(GTK_WINDOW(dialog));
1122         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
1123                         GTK_SIGNAL_FUNC(delete_io_dialog_cb), children);
1124         gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
1125                         GTK_SIGNAL_FUNC(hide_io_dialog_cb),
1126                         children);
1127
1128         vbox = gtk_vbox_new(FALSE, 8);
1129         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
1130         gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1131         gtk_widget_show(vbox);
1132
1133         label = gtk_label_new(children->action);
1134         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1135         gtk_widget_show(label);
1136
1137         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
1138         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
1139                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
1140         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
1141         gtk_widget_set_usize(scrolledwin, 480, 200);
1142         gtk_widget_hide(scrolledwin);
1143
1144         text = gtk_text_new(gtk_scrolled_window_get_hadjustment
1145                             (GTK_SCROLLED_WINDOW(scrolledwin)),
1146                             gtk_scrolled_window_get_vadjustment
1147                             (GTK_SCROLLED_WINDOW(scrolledwin)));
1148         gtk_text_set_editable(GTK_TEXT(text), FALSE);
1149         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
1150         gtk_widget_show(text);
1151
1152         if (children->open_in) {
1153                 input_hbox = gtk_hbox_new(FALSE, 8);
1154                 gtk_widget_show(input_hbox);
1155
1156                 entry = gtk_entry_new();
1157                 gtk_widget_set_usize(entry, 320, -1);
1158                 gtk_signal_connect(GTK_OBJECT(entry), "activate",
1159                                    GTK_SIGNAL_FUNC(send_input), children);
1160                 gtk_box_pack_start(GTK_BOX(input_hbox), entry, TRUE, TRUE, 0);
1161                 if (children->open_in & ACTION_USER_HIDDEN_IN)
1162                         gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
1163                 gtk_widget_show(entry);
1164
1165                 send_button = gtk_button_new_with_label(_(" Send "));
1166                 gtk_signal_connect(GTK_OBJECT(send_button), "clicked",
1167                                    GTK_SIGNAL_FUNC(send_input), children);
1168                 gtk_box_pack_start(GTK_BOX(input_hbox), send_button, FALSE,
1169                                    FALSE, 0);
1170                 gtk_widget_show(send_button);
1171
1172                 gtk_box_pack_start(GTK_BOX(vbox), input_hbox, FALSE, FALSE, 0);
1173                 gtk_widget_grab_focus(entry);
1174         }
1175
1176         gtkut_button_set_create(&hbox, &abort_button, _("Abort"),
1177                                 &close_button, _("Close"), NULL, NULL);
1178         gtk_signal_connect(GTK_OBJECT(abort_button), "clicked",
1179                         GTK_SIGNAL_FUNC(kill_children_cb), children);
1180         gtk_signal_connect(GTK_OBJECT(close_button), "clicked",
1181                         GTK_SIGNAL_FUNC(hide_io_dialog_cb), children);
1182         gtk_widget_show(hbox);
1183
1184         if (children->nb)
1185                 gtk_widget_set_sensitive(close_button, FALSE);
1186
1187         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
1188
1189         children->dialog      = dialog;
1190         children->scrolledwin = scrolledwin;
1191         children->text        = text;
1192         children->input_hbox  = children->open_in ? input_hbox : NULL;
1193         children->input_entry = children->open_in ? entry : NULL;
1194         children->abort_btn   = abort_button;
1195         children->close_btn   = close_button;
1196
1197         gtk_widget_show(dialog);
1198 }
1199
1200 static void catch_status(gpointer data, gint source, GdkInputCondition cond)
1201 {
1202         ChildInfo *child_info = (ChildInfo *)data;
1203         gchar buf;
1204         gint c;
1205
1206         gdk_input_remove(child_info->tag_status);
1207
1208         c = read(source, &buf, 1);
1209         debug_print("Child returned %c\n", buf);
1210
1211         waitpid(-child_info->pid, NULL, 0);
1212         childinfo_close_pipes(child_info);
1213         child_info->pid = 0;
1214
1215         wait_for_children(child_info->children);
1216 }
1217         
1218 static void catch_input(gpointer data, gint source, GdkInputCondition cond)
1219 {
1220         Children *children = (Children *)data;
1221         ChildInfo *child_info = (ChildInfo *)children->list->data;
1222         gchar *input;
1223         gint c, count, len;
1224
1225         debug_print("Sending input to grand child.\n");
1226         if (!(cond && GDK_INPUT_WRITE))
1227                 return;
1228
1229         gdk_input_remove(child_info->tag_in);
1230         child_info->tag_in = -1;
1231
1232         input = gtk_editable_get_chars(GTK_EDITABLE(children->input_entry),
1233                                        0, -1);
1234         len = strlen(input);
1235         count = 0;
1236
1237         do {
1238                 c = write(child_info->chld_in, input + count, len - count);
1239                 if (c >= 0)
1240                         count += c;
1241         } while (c >= 0 && count < len);
1242
1243         if (c >= 0)
1244                 write(child_info->chld_in, "\n", 2);
1245
1246         g_free(input);
1247
1248         gtk_entry_set_text(GTK_ENTRY(children->input_entry), "");
1249         gtk_widget_set_sensitive(children->input_hbox, TRUE);
1250         close(child_info->chld_in);
1251         child_info->chld_in = -1;
1252         debug_print("Input to grand child sent.\n");
1253 }
1254
1255 static void catch_output(gpointer data, gint source, GdkInputCondition cond)
1256 {
1257         ChildInfo *child_info = (ChildInfo *)data;
1258         gint c, i;
1259         gchar buf[BUFFSIZE];
1260
1261         debug_print("Catching grand child's output.\n");
1262         if (child_info->type & (ACTION_PIPE_OUT | ACTION_INSERT)
1263             && source == child_info->chld_out) {
1264                 gboolean is_selection = FALSE;
1265                 GtkWidget *text = child_info->text;
1266                 if (GTK_EDITABLE(text)->has_selection)
1267                         is_selection = TRUE;
1268                 gtk_stext_freeze(GTK_STEXT(text));
1269                 while (TRUE) {
1270                         c = read(source, buf, sizeof(buf) - 1);
1271                         if (c == 0)
1272                                 break;
1273                         gtk_stext_insert(GTK_STEXT(text), child_info->msgfont,
1274                                          NULL, NULL, buf, c);
1275                 }
1276                 if (is_selection) {
1277                         /* Using the select_region draws things. Should not.
1278                          * so we just change selection position and 
1279                          * defere drawing when thawing. Hack?
1280                          */
1281                         GTK_EDITABLE(text)->selection_end_pos =
1282                                         gtk_stext_get_point(GTK_STEXT(text));
1283                 }
1284                 gtk_stext_thaw(GTK_STEXT(child_info->text));
1285         } else {
1286                 c = read(source, buf, sizeof(buf) - 1);
1287                 for (i = 0; i < c; i++)
1288                         g_string_append_c(child_info->output, buf[i]);
1289                 if (c > 0)
1290                         child_info->new_out = TRUE;
1291         }
1292         wait_for_children(child_info->children);
1293 }
1294
1295 static gboolean user_string_dialog_delete_cb(GtkWidget *widget,
1296                                              GdkEvent *event, gpointer data)
1297 {
1298         return FALSE;
1299 }
1300
1301 static void user_string_dialog_destroy_cb(GtkWidget *widget, gpointer data)
1302 {
1303         gtk_main_quit();
1304 }
1305
1306 static void user_string_dialog_activate_cb(GtkWidget *widget, gpointer data)
1307 {
1308         UserStringDialog *user_dialog = (UserStringDialog *) data;
1309
1310         g_free(user_dialog->user_str);
1311         user_dialog->user_str =
1312                 gtk_editable_get_chars(GTK_EDITABLE(user_dialog->entry), 0, -1);
1313         gtk_widget_destroy(user_dialog->dialog);
1314 }
1315
1316 static gchar *get_user_string(const gchar *action, ActionType type)
1317 {
1318         GtkWidget *dialog;
1319         GtkWidget *label;
1320         GtkWidget *entry;
1321         GtkWidget *ok_button;
1322         GtkWidget *cancel_button;
1323         gchar *label_text;
1324         UserStringDialog user_dialog;
1325
1326         dialog = gtk_dialog_new();
1327         gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
1328         gtk_container_set_border_width
1329                 (GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), 8);
1330         gtk_container_set_border_width
1331                 (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
1332         gtk_window_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
1333
1334         switch (type) {
1335         case ACTION_USER_HIDDEN_STR:
1336                 gtk_window_set_title(GTK_WINDOW(dialog),
1337                                      _("Action's hidden user argument"));
1338                 label_text = g_strdup_printf(_("Enter the '%%h' "
1339                                              "argument for the following "
1340                                              "action:\n%s"), action);
1341                 break;
1342         case ACTION_USER_STR:
1343                 gtk_window_set_title(GTK_WINDOW(dialog),
1344                                      _("Action's user argument"));
1345                 label_text = g_strdup_printf(_("Enter the '%%u' "
1346                                              "argument for the following "
1347                                              "action:\n%s"), action);
1348                 break;
1349         default:
1350                 label_text = NULL;
1351                 debug_print("Unsupported action type %d", type);
1352         }
1353
1354         label = gtk_label_new(label_text);
1355         g_free(label_text);
1356         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label,
1357                            TRUE, TRUE, 0);
1358
1359         entry = gtk_entry_new();
1360         gtk_entry_set_visibility(GTK_ENTRY(entry),
1361                                  type != ACTION_USER_HIDDEN_STR);
1362         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), entry,
1363                            TRUE, TRUE, 0);
1364
1365         ok_button = gtk_button_new_with_label(_("OK"));
1366         gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1367                          ok_button, TRUE, TRUE, 0);
1368
1369         cancel_button = gtk_button_new_with_label(_("Cancel"));
1370         gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1371                          cancel_button, TRUE, TRUE, 0);
1372
1373         user_dialog.dialog   = dialog;
1374         user_dialog.user_str = NULL;
1375         user_dialog.entry    = entry;
1376
1377         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
1378                            GTK_SIGNAL_FUNC(user_string_dialog_delete_cb),
1379                            &user_dialog);
1380         gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
1381                            GTK_SIGNAL_FUNC(user_string_dialog_destroy_cb),
1382                            &user_dialog);
1383         gtk_signal_connect(GTK_OBJECT(entry), "activate",
1384                            GTK_SIGNAL_FUNC(user_string_dialog_activate_cb),
1385                            &user_dialog);
1386         gtk_signal_connect(GTK_OBJECT(ok_button), "clicked",
1387                            GTK_SIGNAL_FUNC(user_string_dialog_activate_cb),
1388                            &user_dialog);
1389         gtk_signal_connect_object(GTK_OBJECT(cancel_button), "clicked",
1390                                   GTK_SIGNAL_FUNC(gtk_widget_destroy),
1391                                   GTK_OBJECT(dialog));
1392
1393         gtk_widget_grab_focus(entry);
1394         gtk_widget_show_all(dialog);
1395         gtk_main();
1396
1397         return user_dialog.user_str;
1398 }