/* -*-C-*- $Id: uxctty.c,v 1.14 2000/12/05 21:23:48 cph Exp $ Copyright (c) 1990-2000 Massachusetts Institute of Technology This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "ux.h" #include "osctty.h" #include "ossig.h" /* If `ctty_fildes' is nonnegative, it is an open file descriptor for the controlling terminal of the process. If `ctty_fildes' is negative, Scheme should not alter the control terminal's settings. */ static int ctty_fildes; /* This flag says whether Scheme was in the foreground when it was last entered. Provided that no other process forces Scheme out of the foreground, it will remain in the foreground until it exits or is stopped. If `scheme_in_foreground' is zero, Scheme should not alter the control terminal's settings, nor should it alter the settings of stdin, stdout, or stderr if they are terminals. */ int scheme_in_foreground; /* This flag, set during initialization, says whether we are permitted to change the settings of the control terminal. */ static int permit_ctty_control; /* Original states of the control terminal, stdin, and stdout when Scheme was last continued or stopped, respectively. If the corresponding `_recorded' flag is zero, then no information is saved. */ struct terminal_state_recording { int fd; int recorded_p; Ttty_state state; int flags; }; static struct terminal_state_recording outside_ctty_state; static struct terminal_state_recording outside_stdin_state; static struct terminal_state_recording outside_stdout_state; static struct terminal_state_recording inside_ctty_state; static struct terminal_state_recording inside_stdin_state; static struct terminal_state_recording inside_stdout_state; static void EXFUN (ctty_update_interrupt_chars, (void)); static int DEFUN (get_terminal_state, (fd, s), int fd AND Ttty_state * s) { while (1) { int scr = (UX_terminal_get_state (fd, s)); if ((scr >= 0) || (errno != EINTR)) return (scr); } } static int DEFUN (set_terminal_state, (fd, s), int fd AND Ttty_state * s) { while (1) { int scr = (UX_terminal_set_state (fd, s)); if ((scr >= 0) || (errno != EINTR)) return (scr); } } static int DEFUN (get_flags, (fd, flags), int fd AND int * flags) { #ifdef FCNTL_NONBLOCK while (1) { int scr = (UX_fcntl (fd, F_GETFL, 0)); if (scr >= 0) { (*flags) = scr; return (0); } if (errno != EINTR) return (-1); } #else return (0); #endif } static int DEFUN (set_flags, (fd, flags), int fd AND int * flags) { #ifdef FCNTL_NONBLOCK while (1) { int scr = (UX_fcntl (fd, F_SETFL, (*flags))); if ((scr >= 0) || (errno != EINTR)) return (scr); } #else return (0); #endif } static void DEFUN (save_external_state, (s), struct terminal_state_recording * s) { (s -> recorded_p) = (scheme_in_foreground && (isatty (s -> fd)) && ((get_terminal_state ((s -> fd), (& (s -> state)))) >= 0) && ((get_flags ((s -> fd), (& (s -> flags)))) >= 0)); } static void DEFUN (restore_external_state, (s), struct terminal_state_recording * s) { if (s -> recorded_p) { set_terminal_state ((s -> fd), (& (s -> state))); set_flags ((s -> fd), (& (s -> flags))); (s -> recorded_p) = 0; } } void DEFUN (save_internal_state, (s, es), struct terminal_state_recording * s AND struct terminal_state_recording * es) { /* Don't do anything unless we have a recording of the external state. Otherwise, we should preserve the previous recording of the internal state, if any. */ if (es -> recorded_p) (s -> recorded_p) = (((get_terminal_state ((s -> fd), (& (s -> state)))) >= 0) && ((get_flags ((s -> fd), (& (s -> flags)))) >= 0)); } static void DEFUN (restore_internal_state, (s, es), struct terminal_state_recording * s AND struct terminal_state_recording * es) { /* When we recorded the internal state, we had a recording of the external state. But since we've stopped Scheme and restarted it, we may no longer have a current recording of the external state. If we don't, then we can't restore the internal state. The usual reason that we don't have a recording is that Scheme is in the background. In that case it would be nice to preserve the previous internal state until we go back to the foreground. But doing that transparently would also require tracking all attempted state changes in the recording, which is a pain. So if we can't restore the internal state, we just thrown it away. */ if (s -> recorded_p) { if (es -> recorded_p) { set_terminal_state ((s -> fd), (& (s -> state))); set_flags ((s -> fd), (& (s -> flags))); } (s -> recorded_p) = 0; } } void DEFUN_VOID (UX_ctty_save_external_state) { if (permit_ctty_control && (ctty_fildes >= 0)) { pid_t pgrp_id = (UX_tcgetpgrp (ctty_fildes)); scheme_in_foreground = ((pgrp_id < 0) /* If no job control, assume we're in foreground. */ ? (errno == ENOSYS) : ((UX_getpgrp ()) == pgrp_id)); } else scheme_in_foreground = 0; save_external_state (&outside_ctty_state); save_external_state (&outside_stdin_state); save_external_state (&outside_stdout_state); } void DEFUN_VOID (UX_ctty_restore_external_state) { restore_external_state (&outside_ctty_state); restore_external_state (&outside_stdin_state); restore_external_state (&outside_stdout_state); } void DEFUN_VOID (UX_ctty_save_internal_state) { save_internal_state ((&inside_ctty_state), (&outside_ctty_state)); save_internal_state ((&inside_stdin_state), (&outside_stdin_state)); save_internal_state ((&inside_stdout_state), (&outside_stdout_state)); } void DEFUN_VOID (UX_ctty_restore_internal_state) { int do_update = ((inside_ctty_state . recorded_p) && (outside_ctty_state . recorded_p)); restore_internal_state ((&inside_ctty_state), (&outside_ctty_state)); restore_internal_state ((&inside_stdin_state), (&outside_stdin_state)); restore_internal_state ((&inside_stdout_state), (&outside_stdout_state)); if (do_update) ctty_update_interrupt_chars (); } int DEFUN_VOID (OS_ctty_interrupt_control) { return (outside_ctty_state . recorded_p); } int DEFUN (UX_terminal_control_ok, (fd), int fd) { return ((fd == STDIN_FILENO) ? (outside_stdin_state . recorded_p) : (fd == STDOUT_FILENO) ? (outside_stdout_state . recorded_p) : 1); } /* Keyboard Interrupt Characters */ typedef struct { cc_t quit; cc_t intrpt; cc_t tstp; cc_t dtstp; } Tinterrupt_chars; static Tinterrupt_enables current_interrupt_enables; static Tinterrupt_chars current_interrupt_chars; #define DEFAULT_SIGQUIT_CHAR ((cc_t) '\003') /* ^C */ #define DEFAULT_SIGINT_CHAR ((cc_t) '\007') /* ^G */ #define DEFAULT_SIGTSTP_CHAR ((cc_t) '\032') /* ^Z */ #define KEYBOARD_QUIT_INTERRUPT 0x1 #define KEYBOARD_INTRPT_INTERRUPT 0x2 #define KEYBOARD_TSTP_INTERRUPT 0x4 #define KEYBOARD_ALL_INTERRUPTS 0x7 cc_t DEFUN_VOID (OS_ctty_quit_char) { return (current_interrupt_chars . quit); } cc_t DEFUN_VOID (OS_ctty_int_char) { return (current_interrupt_chars . intrpt); } cc_t DEFUN_VOID (OS_ctty_tstp_char) { return (current_interrupt_chars . tstp); } cc_t DEFUN_VOID (OS_ctty_disabled_char) { return ((ctty_fildes >= 0) ? (UX_PC_VDISABLE (ctty_fildes)) : '\377'); } int DEFUN_VOID (OS_ctty_fd) { return (ctty_fildes); } #if 0 /* not currently used */ static void DEFUN (ctty_get_interrupt_chars, (ic), Tinterrupt_chars * ic) { Ttty_state s; if ((get_terminal_state (ctty_fildes, (&s))) == 0) { #ifdef HAVE_TERMIOS_H (ic -> quit) = ((s . tio . c_cc) [VQUIT]); (ic -> intrpt) = ((s . tio . c_cc) [VINTR]); (ic -> tstp) = ((s . tio . c_cc) [VSUSP]); #ifdef VDSUSP (ic -> dtstp) = ((s . tio . c_cc) [VDSUSP]); #else /* not VDSUSP */ #ifdef __HPUX__ (ic -> dtstp) = (s . ltc . t_dsuspc); #endif /* __HPUX__ */ #endif /* not VDSUSP */ #else /* not HAVE_TERMIOS_H */ #ifdef HAVE_TERMIO_H (ic -> quit) = ((s . tio . c_cc) [VQUIT]); (ic -> intrpt) = ((s . tio . c_cc) [VINTR]); #ifdef HAVE_STRUCT_LTCHARS (ic -> tstp) = (s . ltc . t_suspc); (ic -> dtstp) = (s . ltc . t_dsuspc); #else /* not HAVE_STRUCT_LTCHARS */ { cc_t disabled_char = (UX_PC_VDISABLE (ctty_fildes)); (ic -> tstp) = disabled_char; (ic -> dtstp) = disabled_char; } #endif /* not HAVE_STRUCT_LTCHARS */ #else /* not HAVE_TERMIO_H */ #ifdef HAVE_SGTTY_H (ic -> quit) = (s . tc . t_quitc); (ic -> intrpt) = (s . tc . t_intrc); #ifdef HAVE_STRUCT_LTCHARS (ic -> tstp) = (s . ltc . t_suspc); (ic -> dtstp) = (s . ltc . t_dsuspc); #else /* not HAVE_STRUCT_LTCHARS */ { cc_t disabled_char = (UX_PC_VDISABLE (ctty_fildes)); (ic -> tstp) = disabled_char; (ic -> dtstp) = disabled_char; } #endif /* not HAVE_STRUCT_LTCHARS */ #endif /* HAVE_SGTTY_H */ #endif /* HAVE_TERMIO_H */ #endif /* HAVE_TERMIOS_H */ } else { cc_t disabled_char = (UX_PC_VDISABLE (ctty_fildes)); (ic -> quit) = disabled_char; (ic -> intrpt) = disabled_char; (ic -> tstp) = disabled_char; (ic -> dtstp) = disabled_char; } } #endif /* 0 */ static void DEFUN (ctty_set_interrupt_chars, (ic), Tinterrupt_chars * ic) { Ttty_state s; if ((get_terminal_state (ctty_fildes, (&s))) == 0) { #ifdef HAVE_TERMIOS_H ((s . tio . c_cc) [VQUIT]) = (ic -> quit); ((s . tio . c_cc) [VINTR]) = (ic -> intrpt); ((s . tio . c_cc) [VSUSP]) = (ic -> tstp); #ifdef VDSUSP ((s . tio . c_cc) [VDSUSP]) = (ic -> dtstp); #else /* not VDSUSP */ #ifdef __HPUX__ (s . ltc . t_suspc) = (ic -> tstp); (s . ltc . t_dsuspc) = (ic -> dtstp); #endif /* __HPUX__ */ #endif /* not VDSUSP */ #else /* not HAVE_TERMIOS_H */ #ifdef HAVE_TERMIO_H ((s . tio . c_cc) [VQUIT]) = (ic -> quit); ((s . tio . c_cc) [VINTR]) = (ic -> intrpt); #ifdef HAVE_STRUCT_LTCHARS (s . ltc . t_suspc) = (ic -> tstp); (s . ltc . t_dsuspc) = (ic -> dtstp); #endif #else /* not HAVE_TERMIO_H */ #ifdef HAVE_SGTTY_H (s . tc . t_quitc) = (ic -> quit); (s . tc . t_intrc) = (ic -> intrpt); #ifdef HAVE_STRUCT_LTCHARS (s . ltc . t_suspc) = (ic -> tstp); (s . ltc . t_dsuspc) = (ic -> dtstp); #endif #endif /* HAVE_SGTTY_H */ #endif /* HAVE_TERMIO_H */ #endif /* HAVE_TERMIOS_H */ set_terminal_state (ctty_fildes, (&s)); } } static void DEFUN_VOID (ctty_update_interrupt_chars) { if (outside_ctty_state . recorded_p) { cc_t disabled_char = (UX_PC_VDISABLE (ctty_fildes)); /* Must split declaration and assignment because some compilers do not permit aggregate initializers. */ Tinterrupt_chars active_interrupt_chars; active_interrupt_chars = current_interrupt_chars; if ((current_interrupt_enables & KEYBOARD_QUIT_INTERRUPT) == 0) (active_interrupt_chars . quit) = disabled_char; if ((current_interrupt_enables & KEYBOARD_INTRPT_INTERRUPT) == 0) (active_interrupt_chars . intrpt) = disabled_char; if ((current_interrupt_enables & KEYBOARD_TSTP_INTERRUPT) == 0) (active_interrupt_chars . tstp) = disabled_char; (active_interrupt_chars . dtstp) = disabled_char; ctty_set_interrupt_chars (&active_interrupt_chars); } } void DEFUN (OS_ctty_get_interrupt_enables, (mask), Tinterrupt_enables * mask) { (*mask) = current_interrupt_enables; } void DEFUN (OS_ctty_set_interrupt_enables, (mask), Tinterrupt_enables * mask) { current_interrupt_enables = (*mask); ctty_update_interrupt_chars (); } #if 0 void DEFUN (OS_ctty_set_interrupt_chars, (quit_char, int_char, tstp_char), cc_t quit_char AND cc_t int_char AND cc_t tstp_char) { (current_interrupt_chars . quit) = quit_char; (current_interrupt_chars . intrpt) = int_char; (current_interrupt_chars . tstp) = tstp_char; ctty_update_interrupt_chars (); } #endif unsigned int DEFUN_VOID (OS_ctty_num_int_chars) { return (3); } cc_t * DEFUN_VOID (OS_ctty_get_int_chars) { static cc_t int_chars [3]; int_chars[0] = current_interrupt_chars.quit; int_chars[1] = current_interrupt_chars.intrpt; int_chars[2] = current_interrupt_chars.tstp; return (& int_chars [0]); } void DEFUN (OS_ctty_set_int_chars, (int_chars), cc_t * int_chars) { current_interrupt_chars.quit = int_chars[0]; current_interrupt_chars.intrpt = int_chars[1]; current_interrupt_chars.tstp = int_chars[2]; ctty_update_interrupt_chars (); return; } extern enum interrupt_handler EXFUN (OS_signal_quit_handler, (void)); extern enum interrupt_handler EXFUN (OS_signal_int_handler, (void)); extern enum interrupt_handler EXFUN (OS_signal_tstp_handler, (void)); extern void EXFUN (OS_signal_set_interrupt_handlers, (enum interrupt_handler quit_handler, enum interrupt_handler int_handler, enum interrupt_handler tstp_handler)); cc_t * DEFUN_VOID (OS_ctty_get_int_char_handlers) { static cc_t int_handlers [3]; int_handlers[0] = ((cc_t) (OS_signal_quit_handler ())); int_handlers[1] = ((cc_t) (OS_signal_int_handler ())); int_handlers[2] = ((cc_t) (OS_signal_tstp_handler ())); return (& int_handlers [0]); } void DEFUN (OS_ctty_set_int_char_handlers, (int_handlers), cc_t * int_handlers) { OS_signal_set_interrupt_handlers (((enum interrupt_handler) (int_handlers [0])), ((enum interrupt_handler) (int_handlers [1])), ((enum interrupt_handler) (int_handlers [2]))); return; } void DEFUN (UX_initialize_ctty, (interactive), int interactive) { { char * tty = (UX_ctermid (0)); ctty_fildes = (((tty == 0) || ((tty[0]) == 0)) ? (-1) : (UX_open (tty, O_RDWR, 0))); } permit_ctty_control = interactive; (inside_ctty_state . fd) = (outside_ctty_state . fd) = ctty_fildes; (inside_stdin_state . fd) = (outside_stdin_state . fd) = STDIN_FILENO; (inside_stdout_state . fd) = (outside_stdout_state . fd) = STDOUT_FILENO; UX_ctty_save_external_state (); (inside_ctty_state . recorded_p) = 0; (inside_stdin_state . recorded_p) = 0; (inside_stdout_state . recorded_p) = 0; (current_interrupt_chars . quit) = DEFAULT_SIGQUIT_CHAR; (current_interrupt_chars . intrpt) = DEFAULT_SIGINT_CHAR; (current_interrupt_chars . tstp) = DEFAULT_SIGTSTP_CHAR; (current_interrupt_chars . dtstp) = (UX_PC_VDISABLE (ctty_fildes)); current_interrupt_enables = KEYBOARD_ALL_INTERRUPTS; if (outside_ctty_state . recorded_p) ctty_set_interrupt_chars (¤t_interrupt_chars); }