2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2021 the Claws Mail Team and Hiroyuki Yamamoto
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 3 of the License, or
8 * (at your option) any later version.
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.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
27 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
30 #ifdef GDK_WINDOWING_X11
31 # include <gdk/gdkx.h>
32 #endif /* GDK_WINDOWING_X11 */
37 #include <sys/types.h>
39 # include <sys/wait.h>
46 #include "manage_window.h"
47 #include "mainwindow.h"
48 #include "prefs_common.h"
49 #include "alertpanel.h"
50 #include "inputdialog.h"
56 #include "matcher_parser.h" /* CLAWS */
57 #include "filtering.h"
58 #include "procheader.h"
64 typedef struct _Children Children
;
65 typedef struct _ChildInfo ChildInfo
;
66 typedef struct _UserStringDialog UserStringDialog
;
72 GtkWidget
*input_entry
;
73 GtkWidget
*input_hbox
;
74 GtkWidget
*progress_bar
;
77 GtkWidget
*scrolledwin
;
80 ActionType action_type
;
89 gboolean is_selection
;
108 void (*callback
)(void *data
);
111 GSList
*msginfo_list
;
114 static void action_update_menu (GtkUIManager
*ui_manager
,
115 const gchar
*accel_group
,
119 static void compose_actions_execute_cb (GtkWidget
*widget
,
121 static void compose_actions_execute (Compose
*compose
,
125 static void mainwin_actions_execute_cb (GtkWidget
*widget
,
127 static void mainwin_actions_execute (MainWindow
*mainwin
,
131 static void msgview_actions_execute_cb (GtkWidget
*widget
,
133 static void msgview_actions_execute (MessageView
*msgview
,
137 static void message_actions_execute (MessageView
*msgview
,
141 static gboolean
execute_filtering_actions(gchar
*action
,
144 static gboolean
execute_actions (gchar
*action
,
149 void (*callback
)(void *data
),
152 static gchar
*parse_action_cmd (gchar
*action
,
156 const gchar
*user_str
,
157 const gchar
*user_hidden_str
,
158 const gchar
*sel_str
);
159 static gboolean
parse_append_filename (GString
*cmd
,
162 static gboolean
parse_append_msgpart (GString
*cmd
,
166 static ChildInfo
*fork_child (gchar
*cmd
,
167 const gchar
*msg_str
,
170 static gint
wait_for_children (Children
*children
);
172 static void free_children (Children
*children
);
174 static void childinfo_close_pipes (ChildInfo
*child_info
);
176 static void create_io_dialog (Children
*children
);
177 static void update_io_dialog (Children
*children
);
179 static void hide_io_dialog_cb (GtkWidget
*widget
,
181 static gint
io_dialog_key_pressed_cb (GtkWidget
*widget
,
185 static void catch_output (gpointer data
,
188 static void catch_input (gpointer data
,
191 static void catch_status (GPid pid
, gint status
, gpointer data
);
193 static gchar
*get_user_string (const gchar
*action
,
197 ActionType
action_get_type(const gchar
*action_str
)
200 gboolean in_filtering_action
= FALSE
;
201 ActionType action_type
= ACTION_NONE
;
203 cm_return_val_if_fail(action_str
, ACTION_ERROR
);
204 cm_return_val_if_fail(*action_str
, ACTION_ERROR
);
209 action_type
|= ACTION_PIPE_IN
;
211 } else if (p
[0] == '>') {
212 action_type
|= ACTION_USER_IN
;
214 } else if (p
[0] == '*') {
215 action_type
|= ACTION_USER_HIDDEN_IN
;
222 while (*p
&& action_type
!= ACTION_ERROR
) {
223 if (!in_filtering_action
) {
224 if (p
[0] == '%' && p
[1]) {
227 /* CLAWS: filtering action is a mutually exclusive
228 * action. we can enable others if needed later. we
229 * add ACTION_SINGLE | ACTION_MULTIPLE so it will
230 * only be executed from the main window toolbar */
231 if (p
[2] == 's') /* source messages */
232 action_type
= ACTION_FILTERING_ACTION
235 in_filtering_action
= TRUE
;
238 action_type
|= ACTION_SINGLE
;
241 action_type
|= ACTION_MULTIPLE
;
244 action_type
|= ACTION_SINGLE
;
247 action_type
|= ACTION_SELECTION_STR
;
250 action_type
|= ACTION_USER_STR
;
253 action_type
|= ACTION_USER_HIDDEN_STR
;
259 action_type
= ACTION_ERROR
;
263 } else if (p
[0] == '|') {
265 action_type
|= ACTION_PIPE_OUT
;
266 } else if (p
[0] == '>') {
268 action_type
|= ACTION_INSERT
;
269 } else if (p
[0] == '&') {
271 action_type
|= ACTION_ASYNC
;
272 } else if (p
[0] == '}') {
273 in_filtering_action
= FALSE
;
282 static gchar
*parse_action_cmd(gchar
*action
, MsgInfo
*msginfo
,
283 GSList
*msg_list
, MimeInfo
*partinfo
,
284 const gchar
*user_str
,
285 const gchar
*user_hidden_str
,
286 const gchar
*sel_str
)
294 if (p
[0] == '|' || p
[0] == '>' || p
[0] == '*')
297 cmd
= g_string_sized_new(strlen(action
));
300 !((p
[0] == '|' || p
[0] == '>' || p
[0] == '&') && !p
[1])) {
301 if (p
[0] == '%' && p
[1]) {
304 if (!parse_append_filename(cmd
, msginfo
)) {
305 g_string_free(cmd
, TRUE
);
311 for (cur
= msg_list
; cur
!= NULL
;
313 MsgInfo
*msg
= (MsgInfo
*)cur
->data
;
315 if (!parse_append_filename(cmd
, msg
)) {
316 g_string_free(cmd
, TRUE
);
320 g_string_append_c(cmd
, ' ');
325 if (!parse_append_msgpart(cmd
, msginfo
,
327 g_string_free(cmd
, TRUE
);
334 g_string_append(cmd
, sel_str
);
339 g_string_append(cmd
, user_str
);
344 g_string_append(cmd
, user_hidden_str
);
348 g_string_append_c(cmd
, p
[1]);
352 g_string_append_c(cmd
, p
[0]);
353 g_string_append_c(cmd
, p
[1]);
357 g_string_append_c(cmd
, p
[0]);
362 g_string_free(cmd
, TRUE
);
367 g_string_free(cmd
, FALSE
);
371 static gboolean
parse_append_filename(GString
*cmd
, MsgInfo
*msginfo
)
375 cm_return_val_if_fail(msginfo
, FALSE
);
377 filename
= procmsg_get_message_file(msginfo
);
380 alertpanel_error(_("Could not get message file %d"),
385 g_string_append(cmd
, "\"");
387 gchar
*p
= filename
, *q
;
388 gchar escape_ch
[] = "\\ ";
389 while ((q
= strpbrk(p
, "$\"`\\~")) != NULL
) {
392 g_string_append(cmd
, p
);
393 g_string_append(cmd
, escape_ch
);
397 g_string_append(cmd
, p
);
399 g_string_append(cmd
, filename
);
401 g_string_append(cmd
, "\"");
407 static gboolean
parse_append_msgpart(GString
*cmd
, MsgInfo
*msginfo
,
410 gboolean single_part
= FALSE
;
412 gchar
*part_filename
;
416 partinfo
= procmime_scan_message(msginfo
);
418 alertpanel_error(_("Could not get message part."));
425 filename
= procmsg_get_message_file_path(msginfo
);
426 part_filename
= procmime_get_tmp_file_name(partinfo
);
428 ret
= procmime_get_part(part_filename
, partinfo
);
431 procmime_mimeinfo_free_all(&partinfo
);
435 alertpanel_error(_("Can't get part of multipart message: %s"), g_strerror(-ret
));
436 g_free(part_filename
);
440 g_string_append(cmd
, part_filename
);
442 g_free(part_filename
);
447 void actions_execute(gpointer data
,
452 if (source
== TOOLBAR_MAIN
)
453 mainwin_actions_execute((MainWindow
*)data
, action_nb
, widget
);
454 else if (source
== TOOLBAR_COMPOSE
)
455 compose_actions_execute((Compose
*)data
, action_nb
, widget
);
456 else if (source
== TOOLBAR_MSGVIEW
)
457 msgview_actions_execute((MessageView
*)data
, action_nb
, widget
);
460 void action_update_mainwin_menu(GtkUIManager
*ui_manager
,
464 action_update_menu(ui_manager
, "<MainwinActions>", branch_path
,
465 mainwin_actions_execute_cb
, mainwin
);
468 void action_update_msgview_menu(GtkUIManager
*ui_manager
,
470 MessageView
*msgview
)
472 action_update_menu(ui_manager
, "<MsgviewActions>", branch_path
,
473 msgview_actions_execute_cb
, msgview
);
476 void action_update_compose_menu(GtkUIManager
*ui_manager
,
480 action_update_menu(ui_manager
, "<ComposeActions>", branch_path
,
481 compose_actions_execute_cb
, compose
);
484 static GtkWidget
*find_item_in_menu(GtkWidget
*menu
, gchar
*name
)
486 GList
*children
= gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menu
)));
487 GList
*amenu
= children
;
488 const gchar
*existing_name
;
490 GtkWidget
*item
= GTK_WIDGET(amenu
->data
);
491 if ((existing_name
= g_object_get_data(G_OBJECT(item
), "s_name")) != NULL
&&
492 !g_strcmp0(name
, existing_name
))
494 g_list_free(children
);
500 g_list_free(children
);
505 static GtkWidget
*create_submenus(GtkWidget
*menu
, const gchar
*action
)
507 gchar
*submenu
= g_strdup(action
);
508 GtkWidget
*new_menu
= NULL
;
510 if (strchr(submenu
, '/')) {
511 const gchar
*end
= (strchr(submenu
, '/')+1);
512 GtkWidget
*menu_item
= NULL
;
514 *strchr(submenu
, '/') = '\0';
515 if ((menu_item
= find_item_in_menu(menu
, submenu
)) == NULL
) {
516 menu_item
= gtk_menu_item_new_with_mnemonic(submenu
);
517 g_object_set_data_full(G_OBJECT(menu_item
), "s_name", g_strdup(submenu
), g_free
);
518 gtk_widget_show(menu_item
);
519 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_item
);
520 new_menu
= gtk_menu_new();
521 gtk_widget_show(new_menu
);
522 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item
), new_menu
);
524 new_menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item
));
526 new_menu
= create_submenus(new_menu
, end
);
530 return new_menu
? new_menu
: menu
;
533 static void action_update_menu(GtkUIManager
*ui_manager
,
534 const gchar
*accel_group
,
536 gpointer callback
, gpointer data
)
539 gchar
*action
, *action_p
;
540 int callback_action
= 0;
541 GtkWidget
*menu
= gtk_menu_new();
544 for (cur
= prefs_common
.actions_list
; cur
!= NULL
; cur
= cur
->next
) {
545 GtkWidget
*cur_menu
= menu
;
546 const gchar
*action_name
= NULL
;
547 action
= g_strdup((gchar
*)cur
->data
);
548 action_p
= strstr(action
, ": ");
549 if (action_p
&& action_p
[2] &&
550 (action_get_type(&action_p
[2]) != ACTION_ERROR
) &&
551 (action
[0] != '/')) {
552 gchar
*accel_path
= NULL
;
555 if (strchr(action
, '/')) {
556 cur_menu
= create_submenus(cur_menu
, action
);
557 action_name
= strrchr(action
, '/')+1;
559 action_name
= action
;
561 gtk_menu_set_accel_group (GTK_MENU (cur_menu
),
562 gtk_ui_manager_get_accel_group(ui_manager
));
563 item
= gtk_menu_item_new_with_label(action_name
);
564 gtk_menu_shell_append(GTK_MENU_SHELL(cur_menu
), item
);
565 g_signal_connect(G_OBJECT(item
), "activate",
566 G_CALLBACK(callback
), data
);
567 g_object_set_data(G_OBJECT(item
), "action_num", GINT_TO_POINTER(callback_action
));
568 gtk_widget_show(item
);
569 accel_path
= g_strconcat(accel_group
,branch_path
, "/", action
, NULL
);
570 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item
), accel_path
);
578 gtk_widget_show(menu
);
579 gtk_menu_item_set_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(ui_manager
, branch_path
)), menu
);
582 static void compose_actions_execute_cb(GtkWidget
*widget
, gpointer data
)
584 Compose
*compose
= (Compose
*)data
;
585 gint action_nb
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget
), "action_num"));
586 compose_actions_execute(compose
, action_nb
, NULL
);
589 static void compose_actions_execute(Compose
*compose
, guint action_nb
, GtkWidget
*widget
)
592 ActionType action_type
;
594 cm_return_if_fail(action_nb
< g_slist_length(prefs_common
.actions_list
));
596 buf
= (gchar
*)g_slist_nth_data(prefs_common
.actions_list
, action_nb
);
597 cm_return_if_fail(buf
!= NULL
);
598 action
= strstr(buf
, ": ");
599 cm_return_if_fail(action
!= NULL
);
601 /* Point to the beginning of the command-line */
604 action_type
= action_get_type(action
);
605 if (action_type
& (ACTION_SINGLE
| ACTION_MULTIPLE
)) {
607 (_("The selected action cannot be used in the compose window\n"
608 "because it contains %%f, %%F, %%as or %%p."));
612 execute_actions(action
, NULL
, compose
->text
, 0, NULL
,
613 compose_action_cb
, compose
);
616 static void mainwin_actions_execute_cb(GtkWidget
*widget
, gpointer data
)
618 MainWindow
*mainwin
= (MainWindow
*)data
;
619 gint action_nb
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget
), "action_num"));
620 mainwin_actions_execute(mainwin
, action_nb
, NULL
);
623 static void _free_msginfos(gpointer data
, gpointer user_data
)
625 MsgInfo
*msginfo
= (MsgInfo
*)data
;
627 procmsg_msginfo_free(&msginfo
);
630 static void mainwin_actions_execute(MainWindow
*mainwin
, guint action_nb
,
635 msg_list
= summary_get_selected_msg_list(mainwin
->summaryview
);
636 message_actions_execute(mainwin
->messageview
, action_nb
, msg_list
);
637 summary_select_by_msg_list(mainwin
->summaryview
, msg_list
);
638 g_slist_foreach(msg_list
, _free_msginfos
, NULL
);
639 g_slist_free(msg_list
);
642 static void msgview_actions_execute_cb(GtkWidget
*widget
, gpointer data
)
644 MessageView
*msgview
= (MessageView
*)data
;
645 gint action_nb
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget
), "action_num"));
646 msgview_actions_execute(msgview
, action_nb
, NULL
);
649 static void msgview_actions_execute(MessageView
*msgview
, guint action_nb
,
652 GSList
*msg_list
= NULL
;
654 if (msgview
->msginfo
)
655 msg_list
= g_slist_append(msg_list
, msgview
->msginfo
);
656 message_actions_execute(msgview
, action_nb
, msg_list
);
657 g_slist_free(msg_list
);
660 static void message_actions_execute(MessageView
*msgview
, guint action_nb
,
667 GtkWidget
*text
= NULL
;
669 ActionType action_type
;
671 cm_return_if_fail(action_nb
< g_slist_length(prefs_common
.actions_list
));
673 buf
= (gchar
*)g_slist_nth_data(prefs_common
.actions_list
, action_nb
);
675 cm_return_if_fail(buf
);
676 cm_return_if_fail((action
= strstr(buf
, ": ")));
678 /* Point to the beginning of the command-line */
681 textview
= messageview_get_current_textview(msgview
);
683 text
= textview
->text
;
684 body_pos
= textview
->body_pos
;
686 partinfo
= messageview_get_selected_mime_part(msgview
);
688 /* this command will alter the message text */
689 action_type
= action_get_type(action
);
690 if (action_type
& (ACTION_PIPE_OUT
| ACTION_INSERT
))
691 msgview
->filtered
= TRUE
;
693 if (action_type
& ACTION_FILTERING_ACTION
)
694 /* CLAWS: most of the above code is not necessary for applying
696 execute_filtering_actions(action
, msg_list
);
698 execute_actions(action
, msg_list
, text
, body_pos
, partinfo
,
702 static gboolean
execute_filtering_actions(gchar
*action
, GSList
*msglist
)
704 GSList
*action_list
, *p
;
705 const gchar
*sbegin
, *send
;
706 gchar
*action_string
;
707 SummaryView
*summaryview
= NULL
;
708 MainWindow
*mainwin
= NULL
;
710 if (mainwindow_get_mainwindow()) {
711 summaryview
= mainwindow_get_mainwindow()->summaryview
;
712 mainwin
= mainwindow_get_mainwindow();
715 if (NULL
== (sbegin
= g_strstr_len(action
, -1, "%as{")))
717 sbegin
+= sizeof "%as{" - 1;
718 if (NULL
== (send
= strrchr(sbegin
, '}')))
720 action_string
= g_strndup(sbegin
, send
- sbegin
);
722 action_list
= matcher_parser_get_action_list(action_string
);
723 if (action_list
== NULL
) {
724 gchar
*tmp
= g_strdup(action_string
);
728 alertpanel_error(_("There is no filtering action set"));
730 alertpanel_error(_("Invalid filtering action(s):\n%s"), tmp
);
731 g_free(action_string
);
735 g_free(action_string
);
737 /* apply actions on each message info */
738 for (p
= msglist
; p
&& p
->data
; p
= g_slist_next(p
)) {
739 filteringaction_apply_action_list(action_list
, (MsgInfo
*) p
->data
);
743 summary_lock(summaryview
);
744 main_window_cursor_wait(mainwin
);
745 summary_freeze(summaryview
);
746 folder_item_update_freeze();
749 filtering_move_and_copy_msgs(msglist
);
752 folder_item_update_thaw();
753 summary_thaw(summaryview
);
754 main_window_cursor_normal(mainwin
);
755 summary_unlock(summaryview
);
756 summary_show(summaryview
, summaryview
->folder_item
, FALSE
);
758 for (p
= action_list
; p
; p
= g_slist_next(p
))
759 if (p
->data
) filteringaction_free(p
->data
);
760 g_slist_free(action_list
);
764 static gboolean
execute_actions(gchar
*action
, GSList
*msg_list
,
766 gint body_pos
, MimeInfo
*partinfo
,
767 void (*callback
)(void *data
), void *data
)
769 GSList
*children_list
= NULL
, *cur
= NULL
;
773 ChildInfo
*child_info
;
774 ActionType action_type
;
777 gchar
*sel_str
= NULL
;
778 gchar
*msg_str
= NULL
;
779 gchar
*user_str
= NULL
;
780 gchar
*user_hidden_str
= NULL
;
781 GtkTextIter start_iter
, end_iter
;
782 gboolean is_selection
= FALSE
;
784 cm_return_val_if_fail(action
&& *action
, FALSE
);
786 action_type
= action_get_type(action
);
788 if (action_type
== ACTION_ERROR
)
789 return FALSE
; /* ERR: syntax error */
791 if (action_type
& (ACTION_SINGLE
| ACTION_MULTIPLE
) && !msg_list
)
792 return FALSE
; /* ERR: file command without selection */
794 msg_list_len
= g_slist_length(msg_list
);
796 if (action_type
& (ACTION_PIPE_OUT
| ACTION_PIPE_IN
| ACTION_INSERT
)) {
797 if (msg_list_len
> 1)
798 return FALSE
; /* ERR: pipe + multiple selection */
800 return FALSE
; /* ERR: pipe and no displayed text */
803 if (action_type
& ACTION_SELECTION_STR
) {
805 return FALSE
; /* ERR: selection string but no text */
809 GtkTextBuffer
*textbuf
;
811 textbuf
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
812 is_selection
= gtk_text_buffer_get_selection_bounds
813 (textbuf
, &start_iter
, &end_iter
);
815 gtk_text_buffer_get_iter_at_offset
816 (textbuf
, &start_iter
, body_pos
);
817 gtk_text_buffer_get_end_iter(textbuf
, &end_iter
);
819 msg_str
= gtk_text_buffer_get_text
820 (textbuf
, &start_iter
, &end_iter
, FALSE
);
822 sel_str
= g_strdup(msg_str
);
825 if (action_type
& ACTION_USER_STR
) {
826 if (!(user_str
= get_user_string(action
, ACTION_USER_STR
))) {
833 if (action_type
& ACTION_USER_HIDDEN_STR
) {
834 if (!(user_hidden_str
=
835 get_user_string(action
, ACTION_USER_HIDDEN_STR
))) {
843 if (text
&& (action_type
& ACTION_PIPE_OUT
)) {
844 GtkTextBuffer
*textbuf
;
845 textbuf
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
846 gtk_text_buffer_delete(textbuf
, &start_iter
, &end_iter
);
849 children
= g_new0(Children
, 1);
852 children
->action
= g_strdup(action
);
853 children
->action_type
= action_type
;
854 children
->msg_text
= text
;
855 children
->is_selection
= is_selection
;
857 if ((action_type
& (ACTION_USER_IN
| ACTION_USER_HIDDEN_IN
)) &&
858 ((action_type
& ACTION_SINGLE
) == 0 || msg_list_len
== 1))
859 children
->open_in
= 1;
861 /* Pre-fetch bodies, makes it easier on IMAP (see bug #3011) */
862 for (cur
= msg_list
; cur
; cur
= cur
->next
) {
864 msginfo
= (MsgInfo
*)cur
->data
;
866 dummy
= procmsg_get_message_file((MsgInfo
*)cur
->data
);
873 if (is_ok
&& (action_type
& ACTION_SINGLE
)) {
874 for (cur
= msg_list
; cur
&& is_ok
== TRUE
; cur
= cur
->next
) {
875 msginfo
= (MsgInfo
*)cur
->data
;
877 is_ok
= FALSE
; /* ERR: msginfo missing */
880 cmd
= parse_action_cmd(action
, msginfo
, msg_list
,
882 user_hidden_str
, sel_str
);
884 debug_print("Action command error\n");
885 is_ok
= FALSE
; /* ERR: incorrect command */
888 if ((child_info
= fork_child(cmd
, msg_str
, children
))) {
889 /* Pass msginfo to catch_status () */
890 if (!(action_type
& (ACTION_PIPE_OUT
| ACTION_INSERT
)))
891 child_info
->msginfo_list
=
892 g_slist_append (NULL
, msginfo
);
893 children_list
= g_slist_append(children_list
,
900 cmd
= parse_action_cmd(action
, NULL
, msg_list
, partinfo
,
901 user_str
, user_hidden_str
, sel_str
);
903 if ((child_info
= fork_child(cmd
, msg_str
, children
))) {
904 if (!(action_type
& (ACTION_PIPE_OUT
| ACTION_INSERT
)))
905 child_info
->msginfo_list
=
906 g_slist_copy (msg_list
);
907 children_list
= g_slist_append(children_list
,
913 is_ok
= FALSE
; /* ERR: incorrect command */
919 g_free(user_hidden_str
);
921 if (!children_list
) {
922 /* If not waiting for children, return */
923 free_children(children
);
927 children
->list
= children_list
;
928 children
->initial_nb
= children
->nb
;
930 for (cur
= children_list
; cur
; cur
= cur
->next
) {
931 child_info
= (ChildInfo
*) cur
->data
;
932 child_info
->callback
= callback
;
933 child_info
->data
= data
;
934 child_info
->tag_status
=
935 g_child_watch_add(child_info
->pid
, catch_status
, child_info
);
938 create_io_dialog(children
);
943 static ChildInfo
*fork_child(gchar
*cmd
, const gchar
*msg_str
,
946 gint chld_in
, chld_out
, chld_err
;
947 gchar
**argv
, *ret_str
, *trim_cmd
;
949 ChildInfo
*child_info
;
951 gssize by_written
= 0, by_read
= 0;
952 gboolean result
= FALSE
;
953 GError
*error
= NULL
;
955 follow_child
= !(children
->action_type
& ACTION_ASYNC
);
957 chld_in
= chld_out
= chld_err
= -1;
959 ret_str
= g_locale_from_utf8(cmd
, strlen(cmd
),
960 &by_read
, &by_written
,
962 if (!ret_str
|| !by_written
)
963 ret_str
= g_strdup(cmd
);
967 while (g_ascii_isspace(trim_cmd
[0]))
971 argv
= g_new0(gchar
*, 4);
972 argv
[0] = g_strdup("/bin/sh");
973 argv
[1] = g_strdup("-c");
974 argv
[2] = g_strdup(trim_cmd
);
977 argv
= strsplit_with_quote(trim_cmd
, " ", 0);
982 result
= g_spawn_async_with_pipes(NULL
, argv
, NULL
,
983 G_SPAWN_DO_NOT_REAP_CHILD
| G_SPAWN_SEARCH_PATH
,
984 NULL
, NULL
, &pid
, &chld_in
, &chld_out
,
987 result
= g_spawn_async(NULL
, argv
, NULL
,
988 G_SPAWN_DO_NOT_REAP_CHILD
| G_SPAWN_SEARCH_PATH
, NULL
, NULL
,
992 debug_print("spawning %s: %d\n", cmd
, result
);
997 alertpanel_error(_("Could not fork to execute the following "
999 cmd
, error
? error
->message
: _("Unknown error"));
1001 g_error_free(error
);
1005 if (!(children
->action_type
&
1006 (ACTION_PIPE_IN
| ACTION_USER_IN
| ACTION_USER_HIDDEN_IN
)))
1007 (void)close(chld_in
);
1009 if (!follow_child
) {
1010 g_spawn_close_pid(pid
);
1013 child_info
= g_new0(ChildInfo
, 1);
1015 child_info
->children
= children
;
1017 child_info
->pid
= pid
;
1019 child_info
->next_sig
= SIGTERM
;
1021 child_info
->cmd
= g_strdup(cmd
);
1022 child_info
->new_out
= FALSE
;
1023 child_info
->output
= g_string_new(NULL
);
1024 child_info
->chld_in
=
1025 (children
->action_type
&
1026 (ACTION_PIPE_IN
| ACTION_USER_IN
| ACTION_USER_HIDDEN_IN
))
1028 child_info
->chld_out
= chld_out
;
1029 child_info
->chld_err
= chld_err
;
1030 child_info
->tag_status
= -1;
1031 child_info
->tag_in
= -1;
1032 child_info
->tag_out
= claws_input_add(chld_out
, G_IO_IN
| G_IO_HUP
| G_IO_ERR
,
1033 catch_output
, child_info
, FALSE
);
1034 child_info
->tag_err
= claws_input_add(chld_err
, G_IO_IN
| G_IO_HUP
| G_IO_ERR
,
1035 catch_output
, child_info
, FALSE
);
1037 if (!(children
->action_type
&
1038 (ACTION_PIPE_IN
| ACTION_PIPE_OUT
| ACTION_INSERT
)))
1041 if ((children
->action_type
& ACTION_PIPE_IN
) && msg_str
) {
1043 ret_str
= g_locale_from_utf8(msg_str
, strlen(msg_str
),
1044 &by_read
, &by_written
, NULL
);
1045 if (ret_str
&& by_written
) {
1046 r
= write(chld_in
, ret_str
, strlen(ret_str
));
1049 r
= write(chld_in
, msg_str
, strlen(msg_str
));
1050 if (!(children
->action_type
&
1051 (ACTION_USER_IN
| ACTION_USER_HIDDEN_IN
)))
1053 child_info
->chld_in
= -1; /* No more input */
1055 debug_print("piping to child process: %s (%d)\n", g_strerror(errno
), errno
);
1061 static void kill_children_cb(GtkWidget
*widget
, gpointer data
)
1064 Children
*children
= (Children
*) data
;
1065 ChildInfo
*child_info
;
1067 for (cur
= children
->list
; cur
; cur
= cur
->next
) {
1068 child_info
= (ChildInfo
*)(cur
->data
);
1070 debug_print("Killing child group HANDLE %p\n", child_info
->pid
);
1071 TerminateProcess(child_info
->pid
, 0);
1073 debug_print("Killing child group id %d\n", child_info
->pid
);
1074 if (child_info
->pid
&& kill(child_info
->pid
, child_info
->next_sig
) < 0)
1076 child_info
->next_sig
= SIGKILL
;
1081 static gint
wait_for_children(Children
*children
)
1083 gboolean new_output
;
1084 ChildInfo
*child_info
;
1087 cur
= children
->list
;
1090 child_info
= (ChildInfo
*)cur
->data
;
1091 new_output
|= child_info
->new_out
;
1095 children
->output
|= new_output
;
1097 if (new_output
|| (children
->dialog
&& (children
->initial_nb
!= children
->nb
)))
1098 update_io_dialog(children
);
1103 if (!children
->dialog
) {
1104 free_children(children
);
1105 } else if (!children
->output
) {
1106 gtk_widget_destroy(children
->dialog
);
1112 static void send_input(GtkWidget
*w
, gpointer data
)
1114 Children
*children
= (Children
*) data
;
1115 ChildInfo
*child_info
= (ChildInfo
*) children
->list
->data
;
1117 child_info
->tag_in
= claws_input_add(child_info
->chld_in
,
1118 G_IO_OUT
| G_IO_ERR
,
1119 catch_input
, children
, FALSE
);
1122 static gint
delete_io_dialog_cb(GtkWidget
*w
, GdkEvent
*e
, gpointer data
)
1124 hide_io_dialog_cb(w
, data
);
1128 static void hide_io_dialog_cb(GtkWidget
*w
, gpointer data
)
1131 Children
*children
= (Children
*)data
;
1133 if (!children
->nb
) {
1134 g_signal_handlers_disconnect_matched
1135 (G_OBJECT(children
->dialog
), G_SIGNAL_MATCH_DATA
,
1136 0, 0, NULL
, NULL
, children
);
1137 gtk_widget_destroy(children
->dialog
);
1138 free_children(children
);
1142 static gint
io_dialog_key_pressed_cb(GtkWidget
*widget
, GdkEventKey
*event
,
1145 if (event
&& (event
->keyval
== GDK_KEY_Escape
||
1146 event
->keyval
== GDK_KEY_Return
||
1147 event
->keyval
== GDK_KEY_KP_Enter
))
1148 hide_io_dialog_cb(widget
, data
);
1152 static void childinfo_close_pipes(ChildInfo
*child_info
)
1154 /* stdout and stderr pipes are guaranteed to be removed by
1155 * their handler, but in case where we receive child exit notification
1156 * before grand-child's pipes closing signals, we check them and close
1159 if (child_info
->tag_in
> 0)
1160 g_source_remove(child_info
->tag_in
);
1161 if (child_info
->tag_out
> 0)
1162 g_source_remove(child_info
->tag_out
);
1163 if (child_info
->tag_err
> 0)
1164 g_source_remove(child_info
->tag_err
);
1166 if (child_info
->chld_in
>= 0)
1167 (void)close(child_info
->chld_in
);
1168 if (child_info
->chld_out
>= 0)
1169 (void)close(child_info
->chld_out
);
1170 if (child_info
->chld_err
>= 0)
1171 (void)close(child_info
->chld_err
);
1174 static void free_children(Children
*children
)
1176 ChildInfo
*child_info
;
1177 void (*callback
)(void *data
) = NULL
;
1180 debug_print("Freeing children data %p\n", children
);
1182 g_free(children
->action
);
1183 while (children
->list
!= NULL
) {
1184 child_info
= (ChildInfo
*)children
->list
->data
;
1185 g_free(child_info
->cmd
);
1186 g_string_free(child_info
->output
, TRUE
);
1187 children
->list
= g_slist_remove(children
->list
, child_info
);
1188 callback
= child_info
->callback
;
1189 data
= child_info
->data
;
1199 static void update_io_dialog(Children
*children
)
1203 debug_print("Updating actions input/output dialog.\n");
1205 if (children
->progress_bar
) {
1208 /* use a more compact format */
1209 const gchar
*format
= "%s %d/%d";
1211 const gchar
*format
= "%s %d / %d";
1214 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(children
->progress_bar
),
1215 (children
->initial_nb
== 0) ? 0 :
1216 (gdouble
) (children
->initial_nb
- children
->nb
) /
1217 (gdouble
) children
->initial_nb
);
1218 text
= g_strdup_printf(format
, _("Completed"),
1219 children
->initial_nb
- children
->nb
,
1220 children
->initial_nb
);
1221 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(children
->progress_bar
), TRUE
);
1222 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(children
->progress_bar
), text
);
1226 if (!children
->nb
) {
1227 gtk_widget_set_sensitive(children
->abort_btn
, FALSE
);
1228 gtk_widget_set_sensitive(children
->close_btn
, TRUE
);
1229 if (children
->input_hbox
)
1230 gtk_widget_set_sensitive(children
->input_hbox
, FALSE
);
1231 gtk_widget_grab_focus(children
->close_btn
);
1232 g_signal_connect(G_OBJECT(children
->dialog
),
1234 G_CALLBACK(io_dialog_key_pressed_cb
),
1238 if (children
->output
) {
1239 GtkWidget
*text
= children
->text
;
1240 GtkTextBuffer
*textbuf
;
1241 GtkTextIter iter
, start_iter
, end_iter
;
1243 ChildInfo
*child_info
;
1245 gtk_widget_show(children
->scrolledwin
);
1246 textbuf
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
1247 gtk_text_buffer_get_bounds(textbuf
, &start_iter
, &end_iter
);
1248 gtk_text_buffer_delete(textbuf
, &start_iter
, &end_iter
);
1249 gtk_text_buffer_get_start_iter(textbuf
, &iter
);
1251 for (cur
= children
->list
; cur
; cur
= cur
->next
) {
1252 child_info
= (ChildInfo
*)cur
->data
;
1253 if (child_info
->pid
)
1254 caption
= g_strdup_printf
1255 (_("--- Running: %s\n"),
1258 caption
= g_strdup_printf
1259 (_("--- Ended: %s\n"),
1262 gtk_text_buffer_insert(textbuf
, &iter
, caption
, -1);
1263 gtk_text_buffer_insert(textbuf
, &iter
,
1264 child_info
->output
->str
, -1);
1266 child_info
->new_out
= FALSE
;
1272 *\brief Save Gtk object size to prefs dataset
1274 static void actions_io_size_allocate_cb(GtkWidget
*widget
,
1275 GtkAllocation
*allocation
)
1277 cm_return_if_fail(allocation
!= NULL
);
1279 gtk_window_get_size(GTK_WINDOW(widget
),
1280 &prefs_common
.actionsiodialog_width
, &prefs_common
.actionsiodialog_height
);
1283 static void create_io_dialog(Children
*children
)
1287 GtkWidget
*entry
= NULL
;
1288 GtkWidget
*input_hbox
= NULL
;
1289 GtkWidget
*send_button
;
1292 GtkWidget
*scrolledwin
;
1294 GtkWidget
*progress_bar
= NULL
;
1295 GtkWidget
*abort_button
;
1296 GtkWidget
*close_button
;
1297 static GdkGeometry geometry
;
1299 debug_print("Creating action IO dialog\n");
1301 dialog
= gtk_dialog_new();
1302 gtk_container_set_border_width
1303 (GTK_CONTAINER(gtk_dialog_get_action_area(GTK_DIALOG(dialog
))), 5);
1304 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_CENTER
);
1305 gtk_window_set_title(GTK_WINDOW(dialog
), _("Action's input/output"));
1306 gtk_window_set_modal(GTK_WINDOW(dialog
), TRUE
);
1307 manage_window_set_transient(GTK_WINDOW(dialog
));
1308 g_signal_connect(G_OBJECT(dialog
), "delete_event",
1309 G_CALLBACK(delete_io_dialog_cb
), children
);
1310 g_signal_connect(G_OBJECT(dialog
), "destroy",
1311 G_CALLBACK(hide_io_dialog_cb
),
1313 g_signal_connect(G_OBJECT(dialog
), "size_allocate",
1314 G_CALLBACK(actions_io_size_allocate_cb
), NULL
);
1316 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 8);
1317 gtk_container_add(GTK_CONTAINER(
1318 gtk_dialog_get_content_area(GTK_DIALOG(dialog
))), vbox
);
1319 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 8);
1320 gtk_widget_show(vbox
);
1322 label
= gtk_label_new(children
->action
);
1323 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, FALSE
, 0);
1324 gtk_widget_show(label
);
1326 scrolledwin
= gtk_scrolled_window_new(NULL
, NULL
);
1327 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin
),
1328 GTK_POLICY_AUTOMATIC
,
1329 GTK_POLICY_AUTOMATIC
);
1330 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin
),
1332 gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(scrolledwin
), TRUE
);
1333 gtk_box_pack_start(GTK_BOX(vbox
), scrolledwin
, TRUE
, TRUE
, 0);
1334 gtk_widget_hide(scrolledwin
);
1336 text
= gtk_text_view_new();
1338 if (prefs_common
.textfont
) {
1339 PangoFontDescription
*font_desc
;
1340 font_desc
= pango_font_description_from_string
1341 (prefs_common
.textfont
);
1343 gtk_widget_override_font(text
, font_desc
);
1344 pango_font_description_free(font_desc
);
1348 gtk_text_view_set_editable(GTK_TEXT_VIEW(text
), FALSE
);
1349 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text
), GTK_WRAP_WORD
);
1350 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text
), 6);
1351 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text
), 6);
1352 gtk_container_add(GTK_CONTAINER(scrolledwin
), text
);
1353 gtk_widget_show(text
);
1355 if (children
->open_in
) {
1356 input_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 8);
1357 gtk_widget_show(input_hbox
);
1359 entry
= gtk_entry_new();
1360 gtk_widget_set_size_request(entry
, 320, -1);
1361 g_signal_connect(G_OBJECT(entry
), "activate",
1362 G_CALLBACK(send_input
), children
);
1363 gtk_box_pack_start(GTK_BOX(input_hbox
), entry
, TRUE
, TRUE
, 0);
1364 if (children
->action_type
& ACTION_USER_HIDDEN_IN
) {
1365 gtk_entry_set_visibility(GTK_ENTRY(entry
), FALSE
);
1367 gtk_widget_show(entry
);
1369 send_button
= gtkut_stock_button("system-run", _("_Execute"));
1370 g_signal_connect(G_OBJECT(send_button
), "clicked",
1371 G_CALLBACK(send_input
), children
);
1372 gtk_box_pack_start(GTK_BOX(input_hbox
), send_button
, FALSE
,
1374 gtk_widget_show(send_button
);
1376 gtk_box_pack_start(GTK_BOX(vbox
), input_hbox
, FALSE
, FALSE
, 0);
1377 gtk_widget_grab_focus(entry
);
1380 if (children
->initial_nb
> 1) {
1383 /* use a more compact format */
1384 const gchar
*format
= "%s 0/%d\n";
1386 const gchar
*format
= "%s 0 / %d\n";
1389 progress_bar
= gtk_progress_bar_new();
1390 gtk_orientable_set_orientation(GTK_ORIENTABLE(progress_bar
),
1391 GTK_ORIENTATION_HORIZONTAL
);
1392 gtk_progress_bar_set_inverted(GTK_PROGRESS_BAR(progress_bar
),
1395 text
= g_strdup_printf(format
, _("Completed"),
1396 children
->initial_nb
);
1397 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar
),
1400 gtk_box_pack_start(GTK_BOX(vbox
), progress_bar
, FALSE
, FALSE
, 0);
1401 gtk_widget_show(progress_bar
);
1404 gtkut_stock_button_set_create(&hbox
, &abort_button
, "process-stop",
1405 &close_button
, _("_Close"), NULL
, NULL
);
1406 g_signal_connect(G_OBJECT(abort_button
), "clicked",
1407 G_CALLBACK(kill_children_cb
), children
);
1408 g_signal_connect(G_OBJECT(close_button
), "clicked",
1409 G_CALLBACK(hide_io_dialog_cb
), children
);
1410 gtk_widget_show(hbox
);
1413 gtk_widget_set_sensitive(close_button
, FALSE
);
1415 gtk_container_add(GTK_CONTAINER(
1416 gtk_dialog_get_action_area(GTK_DIALOG(dialog
))), hbox
);
1418 if (!geometry
.min_height
) {
1419 geometry
.min_width
= 582;
1420 geometry
.min_height
= 310;
1423 gtk_window_set_geometry_hints(GTK_WINDOW(dialog
), NULL
, &geometry
,
1425 gtk_window_set_default_size(GTK_WINDOW(dialog
), prefs_common
.actionsiodialog_width
,
1426 prefs_common
.actionsiodialog_height
);
1428 gtk_widget_show(dialog
);
1430 children
->dialog
= dialog
;
1431 children
->scrolledwin
= scrolledwin
;
1432 children
->text
= text
;
1433 children
->input_hbox
= children
->open_in
? input_hbox
: NULL
;
1434 children
->input_entry
= children
->open_in
? entry
: NULL
;
1435 children
->progress_bar
= progress_bar
;
1436 children
->abort_btn
= abort_button
;
1437 children
->close_btn
= close_button
;
1440 static void catch_status(GPid pid
, gint status
, gpointer data
)
1442 ChildInfo
*child_info
= (ChildInfo
*)data
;
1444 debug_print("Child returned %d\n", status
);
1446 childinfo_close_pipes(child_info
);
1447 g_spawn_close_pid(child_info
->pid
);
1448 child_info
->pid
= 0;
1450 if (child_info
->children
->action_type
& (ACTION_SINGLE
| ACTION_MULTIPLE
)
1451 && child_info
->msginfo_list
) {
1452 /* Actions on message *files* might change size and
1453 * time stamp, and thus invalidate the cache */
1454 SummaryView
*summaryview
= NULL
;
1456 MsgInfo
*msginfo
, *nmi
; /* newmsginfo */
1458 gboolean modified_something
= FALSE
;
1459 FolderItem
*last_item
= NULL
;
1460 if (mainwindow_get_mainwindow ())
1461 summaryview
= mainwindow_get_mainwindow ()->summaryview
;
1462 for (cur
= child_info
->msginfo_list
; cur
; cur
= cur
->next
) {
1463 msginfo
= (MsgInfo
*)cur
->data
;
1464 if (!(msginfo
&& /* Stuff used valid? */
1465 msginfo
->folder
&& msginfo
->folder
->cache
))
1467 file
= procmsg_get_message_file_path (msginfo
);
1470 nmi
= procheader_parse_file (file
, msginfo
->flags
, TRUE
, FALSE
);
1472 continue; /* Deleted? */
1473 if (msginfo
->mtime
!= nmi
->mtime
|| msginfo
->size
!= nmi
->size
) {
1474 nmi
->folder
= msginfo
->folder
;
1475 nmi
->msgnum
= msginfo
->msgnum
;
1476 msgcache_update_msg (msginfo
->folder
->cache
, nmi
);
1477 modified_something
= TRUE
;
1478 last_item
= nmi
->folder
;
1480 procmsg_msginfo_free (&nmi
);
1481 if (summaryview
&& summaryview
->displayed
&&
1482 summaryview
->folder_item
== msginfo
->folder
&&
1483 summary_get_msgnum(summaryview
, summaryview
->displayed
) == msginfo
->msgnum
)
1484 summary_redisplay_msg(summaryview
);
1487 if (modified_something
&& last_item
&&
1488 summaryview
&& summaryview
->folder_item
== last_item
) {
1489 summary_show (summaryview
, summaryview
->folder_item
, FALSE
);
1491 g_slist_free (child_info
->msginfo_list
);
1492 child_info
->msginfo_list
= NULL
;
1495 if (!child_info
->pid
)
1496 child_info
->children
->nb
--;
1498 wait_for_children(child_info
->children
);
1501 static void catch_input(gpointer data
, gint source
, GIOCondition cond
)
1503 Children
*children
= (Children
*)data
;
1504 ChildInfo
*child_info
= (ChildInfo
*)children
->list
->data
;
1505 gchar
*input
, *ret_str
;
1506 gint c
, count
, len
, r
;
1507 gssize by_read
= 0, by_written
= 0;
1509 debug_print("Sending input to grand child.\n");
1510 if (!(cond
& (G_IO_OUT
| G_IO_ERR
)))
1513 gtk_widget_set_sensitive(children
->input_hbox
, FALSE
);
1514 gtk_widget_grab_focus(children
->abort_btn
);
1516 g_source_remove(child_info
->tag_in
);
1517 child_info
->tag_in
= -1;
1519 input
= gtk_editable_get_chars(GTK_EDITABLE(children
->input_entry
),
1521 ret_str
= g_locale_from_utf8(input
, strlen(input
), &by_read
,
1523 if (ret_str
&& by_written
) {
1528 len
= strlen(input
);
1532 c
= write(child_info
->chld_in
, input
+ count
, len
- count
);
1535 } while (c
>= 0 && count
< len
);
1538 r
= write(child_info
->chld_in
, "\n", 2);
1542 r
= close(child_info
->chld_in
);
1543 child_info
->chld_in
= -1;
1545 debug_print("closing child input fd: %s (%d)\n", g_strerror(errno
), errno
);
1546 child_info
->chld_in
= -1;
1547 debug_print("Input to grand child sent.\n");
1550 static void catch_output(gpointer data
, gint source
, GIOCondition cond
)
1552 ChildInfo
*child_info
= (ChildInfo
*)data
;
1554 gchar buf
[BUFFSIZE
];
1556 debug_print("Catching grand child's output.\n");
1557 if (child_info
->children
->action_type
&
1558 (ACTION_PIPE_OUT
| ACTION_INSERT
)
1559 && source
== child_info
->chld_out
) {
1561 GTK_TEXT_VIEW(child_info
->children
->msg_text
);
1562 GtkTextBuffer
*textbuf
= gtk_text_view_get_buffer(text
);
1567 mark
= gtk_text_buffer_get_insert(textbuf
);
1568 gtk_text_buffer_get_iter_at_mark(textbuf
, &iter
, mark
);
1569 ins_pos
= gtk_text_iter_get_offset(&iter
);
1572 gsize bytes_read
= 0, bytes_written
= 0;
1575 c
= read(source
, buf
, sizeof(buf
) - 1);
1579 ret_str
= g_locale_to_utf8
1580 (buf
, c
- 1, &bytes_read
, &bytes_written
, NULL
);
1581 if (ret_str
&& bytes_written
> 0) {
1582 gtk_text_buffer_insert
1583 (textbuf
, &iter
, ret_str
,
1587 gtk_text_buffer_insert(textbuf
, &iter
, buf
, c
- 1);
1590 if (child_info
->children
->is_selection
) {
1593 gtk_text_buffer_get_iter_at_offset
1594 (textbuf
, &ins
, ins_pos
);
1595 gtk_text_buffer_select_range(textbuf
, &ins
, &iter
);
1598 c
= read(source
, buf
, sizeof(buf
) - 1);
1600 gsize bytes_read
= 0, bytes_written
= 0;
1603 ret_str
= g_locale_to_utf8
1604 (buf
, c
, &bytes_read
, &bytes_written
, NULL
);
1605 if (ret_str
&& bytes_written
> 0) {
1607 (child_info
->output
, ret_str
,
1611 g_string_append_len(child_info
->output
, buf
, c
);
1613 child_info
->new_out
= TRUE
;
1617 if (source
== child_info
->chld_out
) {
1618 g_source_remove(child_info
->tag_out
);
1619 child_info
->tag_out
= -1;
1620 (void)close(child_info
->chld_out
);
1621 child_info
->chld_out
= -1;
1623 g_source_remove(child_info
->tag_err
);
1624 child_info
->tag_err
= -1;
1625 (void)close(child_info
->chld_err
);
1626 child_info
->chld_err
= -1;
1630 wait_for_children(child_info
->children
);
1633 static gchar
*get_user_string(const gchar
*action
, ActionType type
)
1636 gchar
*user_str
= NULL
;
1639 case ACTION_USER_HIDDEN_STR
:
1640 message
= g_strdup_printf
1641 (_("Enter the argument for the following action:\n"
1642 "('%%h' will be replaced with the argument)\n"
1645 user_str
= input_dialog_with_invisible
1646 (_("Action's hidden user argument"), message
, NULL
);
1648 case ACTION_USER_STR
:
1649 message
= g_strdup_printf
1650 (_("Enter the argument for the following action:\n"
1651 "('%%u' will be replaced with the argument)\n"
1654 user_str
= input_dialog
1655 (_("Action's user argument"), message
, NULL
);
1658 g_warning("Unsupported action type %d", type
);