diff --git a/doc/emacs/cmdargs.texi b/doc/emacs/cmdargs.texi index 5a3c1c14ab1..f9904d1f2e6 100644 --- a/doc/emacs/cmdargs.texi +++ b/doc/emacs/cmdargs.texi @@ -260,9 +260,14 @@ on. To invoke a Lisp program, use the @samp{-batch} option in conjunction with one or more of @samp{-l}, @samp{-f} or @samp{--eval} (@pxref{Action Arguments}). @xref{Command Example}, for an example. +@vindex kill-emacs-on-sigint In batch mode, Emacs does not display the text being edited, and the standard terminal interrupt characters such as @kbd{C-z} and @kbd{C-c} -have their usual effect. Emacs functions that normally print a +have their usual effect: for @kbd{C-c} that effect is either to +exit Emacs or to signal @code{quit}, depending on the variable +@code{kill-emacs-on-sigint}. + +Emacs functions that normally print a message in the echo area will print to either the standard output stream (@code{stdout}) or the standard error stream (@code{stderr}) instead. (To be precise, functions like @code{prin1}, @code{princ} diff --git a/etc/NEWS b/etc/NEWS index dd9db73285c..8caf6d5cbfa 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -4593,6 +4593,14 @@ is a proper list, in which case the list will be returned as is, otherwise the function will return the object wrapped in a singleton list. +--- +** In batch mode, 'C-c' (i.e. SIGINT) can either 'quit' or kill Emacs. +By default it kills Emacs, as before, but 'kill-emacs-on-sigint' +can be set to nil to change that. +The response to SIGINT in interactive sessions is unaffected, +e.g. in a normal GUI session it still kills Emacs whereas in a terminal +it causes 'quit' since it is used for 'C-g'. + * Changes in Emacs 31.1 on Non-Free Operating Systems diff --git a/src/keyboard.c b/src/keyboard.c index c64b2a6dd57..6f0b5fbb2ec 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -12543,11 +12543,17 @@ handle_interrupt_signal (int sig) struct terminal *terminal = get_named_terminal (dev_tty); if (!terminal) { - /* If there are no frames there, let's pretend that we are a - well-behaving UN*X program and quit. We must not call Lisp + /* There are no frames, so either 'quit' or exit. + If 'kill-emacs-on-sigint', pretend that we are a + well-behaving UN*X program and exit. We must not call Lisp in a signal handler, so tell maybe_quit to exit when it is safe. */ - Vquit_flag = Qkill_emacs; + Vquit_flag = (kill_emacs_on_sigint + /* Don't risk running ELisp code while shutting down + and limit the effect of 'kill_emacs_on_sigint' + to batch sessions. */ + || NILP (Vrun_hooks) || !noninteractive + ? Qkill_emacs : Qt); } else { @@ -13224,17 +13230,18 @@ init_keyboard (void) it for the initial terminal since there is no window system there. */ init_kboard (current_kboard, Qnil); + /* Before multi-tty support, these handlers used to be installed + only if the current session was a tty session. Now an Emacs + session may have multiple display types, so we always handle + SIGINT. There is special code in handle_interrupt_signal to exit + Emacs on SIGINT when there are no termcap frames on the + controlling terminal. */ + struct sigaction action; + emacs_sigaction_init (&action, deliver_interrupt_signal); + sigaction (SIGINT, &action, 0); + if (!noninteractive) { - /* Before multi-tty support, these handlers used to be installed - only if the current session was a tty session. Now an Emacs - session may have multiple display types, so we always handle - SIGINT. There is special code in handle_interrupt_signal to exit - Emacs on SIGINT when there are no termcap frames on the - controlling terminal. */ - struct sigaction action; - emacs_sigaction_init (&action, deliver_interrupt_signal); - sigaction (SIGINT, &action, 0); #ifndef DOS_NT /* For systems with SysV TERMIO, C-g is set up for both SIGINT and SIGQUIT and we can't tell which one it will give us. */ @@ -13401,6 +13408,12 @@ syms_of_keyboard (void) doc: /* Message displayed by `normal-top-level'. */); Vinternal__top_level_message = regular_top_level_message; + DEFVAR_BOOL ("kill-emacs-on-sigint", kill_emacs_on_sigint, + doc: /* If non-nil, a SIGINT event causes Emacs to exit. +If nil, a SIGINT event causes a `quit` signal instead. +This is effective only in `noninteractive' sessions. */); + kill_emacs_on_sigint = true; + /* Tool-bars. */ DEFSYM (QCimage, ":image"); DEFSYM (Qhelp_echo, "help-echo"); diff --git a/test/src/keyboard-tests.el b/test/src/keyboard-tests.el index ae47e4b3b4b..e4a1bf36a63 100644 --- a/test/src/keyboard-tests.el +++ b/test/src/keyboard-tests.el @@ -81,6 +81,34 @@ (should-error (read-event "foo: ")) (should-error (read-char-exclusive "foo: ")))) +(ert-deftest keyboard-sigint-to-quit () ;; bug#80942 + (with-temp-buffer + (let* ((exit-msg "Exit via Quit") + (proc + (make-process + :name "keyboard-sigint-to-quit" + :buffer (current-buffer) + :command + `(,(expand-file-name invocation-name invocation-directory) + "-Q" "--batch" "--eval" + ,(prin1-to-string + `(progn (setq kill-emacs-on-sigint nil) + (message "Ready!") + (condition-case nil + (dotimes (_ 3) (sit-for 1)) + (quit (message "%s" ,exit-msg))))))))) + (while (progn (accept-process-output proc 1.0) + (goto-char (point-min)) + (not (re-search-forward "Ready!" nil t))) + ) ;; (message "Waiting for subprocess to be ready") + ;; (message "Subprocess is ready") + (interrupt-process proc) + (while (prog1 (memq (process-status proc) '(run)) + (accept-process-output proc 1.0)) + ) ;; (message "Waiting for subprocess to exit") + (goto-char (point-min)) + (should (re-search-forward exit-msg nil t))))) + ;;; Tests for `read-key-sequence' code paths. ;;;; Helpers