2 Execution routines for GNU Midnight Commander
4 Copyright (C) 2003-2024
5 Free Software Foundation, Inc.
8 Slava Zanko <slavazanko@gmail.com>, 2013
10 This file is part of the Midnight Commander.
12 The Midnight Commander is free software: you can redistribute it
13 and/or modify it under the terms of the GNU General Public License as
14 published by the Free Software Foundation, either version 3 of the License,
15 or (at your option) any later version.
17 The Midnight Commander is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 * \brief Source: execution routines
37 #include "lib/global.h"
39 #include "lib/tty/tty.h"
40 #include "lib/tty/key.h"
41 #include "lib/tty/win.h"
42 #include "lib/vfs/vfs.h"
43 #include "lib/mcconfig.h"
45 #include "lib/strutil.h" /* str_replace_all_substrings() */
46 #include "lib/widget.h"
48 #include "filemanager/filemanager.h"
49 #include "filemanager/layout.h" /* use_dash() */
50 #include "consaver/cons.saver.h"
51 #ifdef ENABLE_SUBSHELL
52 #include "subshell/subshell.h"
54 #include "setup.h" /* clear_before_exec */
58 /*** global variables ****************************************************************************/
60 int pause_after_run
= pause_on_dumb_terminals
;
62 /*** file scope macro definitions ****************************************************************/
64 /*** file scope type declarations ****************************************************************/
66 /*** forward declarations (file scope functions) *************************************************/
68 void do_execute (const char *shell
, const char *command
, int flags
);
69 void do_executev (const char *shell
, int flags
, char *const argv
[]);
70 char *execute_get_external_cmd_opts_from_config (const char *command
,
71 const vfs_path_t
* filename_vpath
,
74 /*** file scope variables ************************************************************************/
76 /* --------------------------------------------------------------------------------------------- */
77 /*** file scope functions ************************************************************************/
78 /* --------------------------------------------------------------------------------------------- */
81 edition_post_exec (void)
85 /* FIXME: Missing on slang endwin? */
86 tty_reset_prog_mode ();
93 enable_bracketed_paste ();
94 if (mc_global
.tty
.alternate_plus_minus
)
95 application_keypad_mode ();
98 /* --------------------------------------------------------------------------------------------- */
101 edition_pre_exec (void)
103 if (clear_before_exec
)
107 if (!(mc_global
.tty
.console_flag
!= '\0' || mc_global
.tty
.xterm_flag
))
113 disable_bracketed_paste ();
115 tty_reset_shell_mode ();
119 numeric_keypad_mode ();
121 /* on xterms: maybe endwin did not leave the terminal on the shell
122 * screen page: do it now.
124 * Do not move this before endwin: in some systems rmcup includes
125 * a call to clear screen, so it will end up clearing the shell screen.
130 /* --------------------------------------------------------------------------------------------- */
132 #ifdef ENABLE_SUBSHELL
134 do_possible_cd (const vfs_path_t
*new_dir_vpath
)
136 if (!panel_cd (current_panel
, new_dir_vpath
, cd_exact
))
137 message (D_ERROR
, _("Warning"), "%s",
138 _("The Commander can't change to the directory that\n"
139 "the subshell claims you are in. Perhaps you have\n"
140 "deleted your working directory, or given yourself\n"
141 "extra access permissions with the \"su\" command?"));
143 #endif /* ENABLE_SUBSHELL */
145 /* --------------------------------------------------------------------------------------------- */
148 do_suspend_cmd (void)
152 if (mc_global
.tty
.console_flag
!= '\0' && !mc_global
.tty
.use_subshell
)
153 handle_console (CONSOLE_RESTORE
);
157 struct sigaction sigtstp_action
;
159 memset (&sigtstp_action
, 0, sizeof (sigtstp_action
));
160 /* Make sure that the SIGTSTP below will suspend us directly,
161 without calling ncurses' SIGTSTP handler; we *don't* want
162 ncurses to redraw the screen immediately after the SIGCONT */
163 sigaction (SIGTSTP
, &startup_handler
, &sigtstp_action
);
165 kill (getpid (), SIGTSTP
);
167 /* Restore previous SIGTSTP action */
168 sigaction (SIGTSTP
, &sigtstp_action
, NULL
);
172 if (mc_global
.tty
.console_flag
!= '\0' && !mc_global
.tty
.use_subshell
)
173 handle_console (CONSOLE_SAVE
);
175 edition_post_exec ();
178 /* --------------------------------------------------------------------------------------------- */
181 execute_prepare_with_vfs_arg (const vfs_path_t
*filename_vpath
, vfs_path_t
**localcopy_vpath
,
186 /* Simplest case, this file is local */
187 if ((filename_vpath
== NULL
&& vfs_file_is_local (vfs_get_raw_current_dir ()))
188 || vfs_file_is_local (filename_vpath
))
191 /* FIXME: Creation of new files on VFS is not supported */
192 if (filename_vpath
== NULL
)
195 *localcopy_vpath
= mc_getlocalcopy (filename_vpath
);
196 if (*localcopy_vpath
== NULL
)
198 message (D_ERROR
, MSG_ERROR
, _("Cannot fetch a local copy of %s"),
199 vfs_path_as_str (filename_vpath
));
203 mc_stat (*localcopy_vpath
, &st
);
204 *mtime
= st
.st_mtime
;
208 /* --------------------------------------------------------------------------------------------- */
211 execute_cleanup_with_vfs_arg (const vfs_path_t
*filename_vpath
, vfs_path_t
**localcopy_vpath
,
214 if (*localcopy_vpath
!= NULL
)
219 * filename can be an entry on panel, it can be changed by executing
220 * the command, so make a copy. Smarter VFS code would make the code
223 mc_stat (*localcopy_vpath
, &st
);
224 mc_ungetlocalcopy (filename_vpath
, *localcopy_vpath
, *mtime
!= st
.st_mtime
);
225 vfs_path_free (*localcopy_vpath
, TRUE
);
226 *localcopy_vpath
= NULL
;
230 /* --------------------------------------------------------------------------------------------- */
233 execute_get_opts_from_cfg (const char *command
, const char *default_str
)
235 char *str_from_config
;
238 mc_config_get_string_raw (mc_global
.main_config
, CONFIG_EXT_EDITOR_VIEWER_SECTION
, command
,
241 if (str_from_config
== NULL
)
245 cfg
= mc_config_init (mc_global
.profile_name
, TRUE
);
247 return g_strdup (default_str
);
250 mc_config_get_string_raw (cfg
, CONFIG_EXT_EDITOR_VIEWER_SECTION
, command
, default_str
);
252 mc_config_deinit (cfg
);
255 return str_from_config
;
258 /* --------------------------------------------------------------------------------------------- */
259 /*** public functions ****************************************************************************/
260 /* --------------------------------------------------------------------------------------------- */
263 execute_get_external_cmd_opts_from_config (const char *command
, const vfs_path_t
*filename_vpath
,
266 char *str_from_config
, *return_str
;
269 if (filename_vpath
== NULL
)
270 return g_strdup ("");
272 parameter
= g_shell_quote (vfs_path_get_last_path_str (filename_vpath
));
277 str_from_config
= execute_get_opts_from_cfg (command
, "%filename");
279 return_str
= str_replace_all (str_from_config
, "%filename", parameter
);
281 g_free (str_from_config
);
282 str_from_config
= return_str
;
284 parameter
= g_strdup_printf ("%ld", start_line
);
285 return_str
= str_replace_all (str_from_config
, "%lineno", parameter
);
287 g_free (str_from_config
);
292 /* --------------------------------------------------------------------------------------------- */
295 do_executev (const char *shell
, int flags
, char *const argv
[])
297 #ifdef ENABLE_SUBSHELL
298 vfs_path_t
*new_dir_vpath
= NULL
;
299 #endif /* ENABLE_SUBSHELL */
301 vfs_path_t
*old_vfs_dir_vpath
= NULL
;
303 if (!vfs_current_is_local ())
304 old_vfs_dir_vpath
= vfs_path_clone (vfs_get_raw_current_dir ());
306 if (mc_global
.mc_run_mode
== MC_RUN_FULL
)
309 if (mc_global
.tty
.console_flag
!= '\0')
310 handle_console (CONSOLE_RESTORE
);
312 if (!mc_global
.tty
.use_subshell
&& *argv
!= NULL
&& (flags
& EXECUTE_INTERNAL
) == 0)
314 printf ("%s%s\n", mc_prompt
, *argv
);
317 #ifdef ENABLE_SUBSHELL
318 if (mc_global
.tty
.use_subshell
&& (flags
& EXECUTE_INTERNAL
) == 0)
322 /* We don't care if it died, higher level takes care of this */
323 invoke_subshell (*argv
, VISIBLY
, old_vfs_dir_vpath
!= NULL
? NULL
: &new_dir_vpath
);
326 #endif /* ENABLE_SUBSHELL */
327 my_systemv_flags (flags
, shell
, argv
);
329 if ((flags
& EXECUTE_INTERNAL
) == 0)
331 if ((pause_after_run
== pause_always
332 || (pause_after_run
== pause_on_dumb_terminals
&& !mc_global
.tty
.xterm_flag
333 && mc_global
.tty
.console_flag
== '\0')) && quit
== 0
334 #ifdef ENABLE_SUBSHELL
335 && subshell_state
!= RUNNING_COMMAND
336 #endif /* ENABLE_SUBSHELL */
339 printf ("%s", _("Press any key to continue..."));
346 if (mc_global
.tty
.console_flag
!= '\0' && output_lines
!= 0 && mc_global
.keybar_visible
)
353 if (mc_global
.tty
.console_flag
!= '\0')
354 handle_console (CONSOLE_SAVE
);
355 edition_post_exec ();
357 #ifdef ENABLE_SUBSHELL
358 if (new_dir_vpath
!= NULL
)
360 do_possible_cd (new_dir_vpath
);
361 vfs_path_free (new_dir_vpath
, TRUE
);
364 #endif /* ENABLE_SUBSHELL */
366 if (old_vfs_dir_vpath
!= NULL
)
368 mc_chdir (old_vfs_dir_vpath
);
369 vfs_path_free (old_vfs_dir_vpath
, TRUE
);
372 if (mc_global
.mc_run_mode
== MC_RUN_FULL
)
374 update_panels (UP_OPTIMIZE
, UP_KEEPSEL
);
375 update_xterm_title_path ();
376 update_terminal_cwd ();
383 /* --------------------------------------------------------------------------------------------- */
386 do_execute (const char *shell
, const char *command
, int flags
)
388 GPtrArray
*args_array
;
390 args_array
= g_ptr_array_new ();
391 g_ptr_array_add (args_array
, (char *) command
);
392 g_ptr_array_add (args_array
, NULL
);
394 do_executev (shell
, flags
, (char *const *) args_array
->pdata
);
396 g_ptr_array_free (args_array
, TRUE
);
399 /* --------------------------------------------------------------------------------------------- */
401 /** Set up the terminal before executing a program */
410 /* --------------------------------------------------------------------------------------------- */
411 /** Hide the terminal after executing a program */
415 edition_post_exec ();
420 /* --------------------------------------------------------------------------------------------- */
421 /* Executes a command */
424 shell_execute (const char *command
, int flags
)
428 if ((flags
& EXECUTE_HIDE
) != 0)
430 cmd
= g_strconcat (" ", command
, (char *) NULL
);
431 flags
^= EXECUTE_HIDE
;
434 #ifdef ENABLE_SUBSHELL
435 if (mc_global
.tty
.use_subshell
)
437 if (subshell_state
== INACTIVE
)
438 do_execute (mc_global
.shell
->path
, cmd
!= NULL
? cmd
: command
,
439 flags
| EXECUTE_AS_SHELL
);
441 message (D_ERROR
, MSG_ERROR
, "%s", _("The shell is already running a command"));
444 #endif /* ENABLE_SUBSHELL */
445 do_execute (mc_global
.shell
->path
, cmd
!= NULL
? cmd
: command
, flags
| EXECUTE_AS_SHELL
);
450 /* --------------------------------------------------------------------------------------------- */
453 toggle_subshell (void)
455 static gboolean message_flag
= TRUE
;
457 #ifdef ENABLE_SUBSHELL
458 vfs_path_t
*new_dir_vpath
= NULL
;
459 #endif /* ENABLE_SUBSHELL */
461 SIG_ATOMIC_VOLATILE_T was_sigwinch
= 0;
463 if (!(mc_global
.tty
.xterm_flag
|| mc_global
.tty
.console_flag
!= '\0'
464 || mc_global
.tty
.use_subshell
|| output_starts_shell
))
467 message (D_ERROR
, MSG_ERROR
,
468 _("Not an xterm or Linux console;\nthe subshell cannot be toggled."));
469 message_flag
= FALSE
;
475 disable_bracketed_paste ();
476 if (clear_before_exec
)
478 if (mc_global
.tty
.alternate_plus_minus
)
479 numeric_keypad_mode ();
481 /* With slang we don't want any of this, since there
482 * is no raw_mode supported
484 tty_reset_shell_mode ();
485 #endif /* !HAVE_SLANG */
491 if (mc_global
.tty
.console_flag
!= '\0')
492 handle_console (CONSOLE_RESTORE
);
494 #ifdef ENABLE_SUBSHELL
495 if (mc_global
.tty
.use_subshell
)
497 vfs_path_t
**new_dir_p
;
499 new_dir_p
= vfs_current_is_local ()? &new_dir_vpath
: NULL
;
500 invoke_subshell (NULL
, VISIBLY
, new_dir_p
);
503 #endif /* ENABLE_SUBSHELL */
505 if (output_starts_shell
)
507 fputs (_("Type 'exit' to return to the Midnight Commander"), stderr
);
508 fputs ("\n\r\n\r", stderr
);
510 my_system (EXECUTE_INTERNAL
, mc_global
.shell
->path
, NULL
);
516 if (mc_global
.tty
.console_flag
!= '\0')
517 handle_console (CONSOLE_SAVE
);
519 tty_enter_ca_mode ();
521 tty_reset_prog_mode ();
524 /* Prevent screen flash when user did 'exit' or 'logout' within
526 if ((quit
& SUBSHELL_EXIT
) != 0)
528 /* User did 'exit' or 'logout': quit MC */
529 if (quiet_quit_cmd ())
533 #ifdef ENABLE_SUBSHELL
534 /* restart subshell */
535 if (mc_global
.tty
.use_subshell
)
537 #endif /* ENABLE_SUBSHELL */
541 enable_bracketed_paste ();
543 if (mc_global
.tty
.alternate_plus_minus
)
544 application_keypad_mode ();
547 * Save sigwinch flag that will be reset in mc_refresh() called via update_panels().
548 * There is some problem with screen redraw in ncurses-based mc in this situation.
550 was_sigwinch
= tty_got_winch ();
553 #ifdef ENABLE_SUBSHELL
554 if (mc_global
.tty
.use_subshell
)
556 if (mc_global
.mc_run_mode
== MC_RUN_FULL
)
558 if (new_dir_vpath
!= NULL
)
559 do_possible_cd (new_dir_vpath
);
561 else if (new_dir_vpath
!= NULL
&& mc_chdir (new_dir_vpath
) != -1)
565 vfs_path_free (new_dir_vpath
, TRUE
);
566 #endif /* ENABLE_SUBSHELL */
568 if (mc_global
.mc_run_mode
== MC_RUN_FULL
)
570 update_panels (UP_OPTIMIZE
, UP_KEEPSEL
);
571 update_xterm_title_path ();
572 update_terminal_cwd ();
575 if (was_sigwinch
!= 0 || tty_got_winch ())
576 dialog_change_screen_size ();
581 /* --------------------------------------------------------------------------------------------- */
585 execute_suspend (const gchar
*event_group_name
, const gchar
*event_name
,
586 gpointer init_data
, gpointer data
)
588 (void) event_group_name
;
593 if (mc_global
.mc_run_mode
== MC_RUN_FULL
)
596 if (mc_global
.mc_run_mode
== MC_RUN_FULL
)
597 update_panels (UP_OPTIMIZE
, UP_KEEPSEL
);
603 /* --------------------------------------------------------------------------------------------- */
606 * Execute command on a filename that can be on VFS.
607 * Errors are reported to the user.
611 execute_with_vfs_arg (const char *command
, const vfs_path_t
*filename_vpath
)
613 vfs_path_t
*localcopy_vpath
= NULL
;
614 const vfs_path_t
*do_execute_vpath
;
617 if (!execute_prepare_with_vfs_arg (filename_vpath
, &localcopy_vpath
, &mtime
))
620 do_execute_vpath
= (localcopy_vpath
== NULL
) ? filename_vpath
: localcopy_vpath
;
622 do_execute (command
, vfs_path_get_last_path_str (do_execute_vpath
), EXECUTE_INTERNAL
);
624 execute_cleanup_with_vfs_arg (filename_vpath
, &localcopy_vpath
, &mtime
);
627 /* --------------------------------------------------------------------------------------------- */
629 * Execute external editor or viewer.
631 * @param command editor/viewer to run
632 * @param filename_vpath path for edit/view
633 * @param start_line cursor will be placed at the 'start_line' position after opening file
634 * if start_line is 0 or negative, no start line will be passed to editor/viewer
638 execute_external_editor_or_viewer (const char *command
, const vfs_path_t
*filename_vpath
,
641 vfs_path_t
*localcopy_vpath
= NULL
;
642 const vfs_path_t
*do_execute_vpath
;
643 char *extern_cmd_options
;
646 if (!execute_prepare_with_vfs_arg (filename_vpath
, &localcopy_vpath
, &mtime
))
649 do_execute_vpath
= (localcopy_vpath
== NULL
) ? filename_vpath
: localcopy_vpath
;
652 execute_get_external_cmd_opts_from_config (command
, do_execute_vpath
, start_line
);
654 if (extern_cmd_options
!= NULL
)
656 char **argv_cmd_options
;
659 if (g_shell_parse_argv (extern_cmd_options
, &argv_count
, &argv_cmd_options
, NULL
))
661 do_executev (command
, EXECUTE_INTERNAL
, argv_cmd_options
);
662 g_strfreev (argv_cmd_options
);
665 do_executev (command
, EXECUTE_INTERNAL
, NULL
);
667 g_free (extern_cmd_options
);
670 execute_cleanup_with_vfs_arg (filename_vpath
, &localcopy_vpath
, &mtime
);
673 /* --------------------------------------------------------------------------------------------- */