/* Copyright (C) 2015 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2007 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */ /* This file reads the keyboard when the termios emulation is active. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SENSE_NO_KEY 0 #define SENSE_REG_KEY 1 #define SENSE_EXT_KEY 2 struct tty_editline __libc_tty_editline = { 0, { 0 }, { 0 }, }; unsigned char __libc_tty_queue_buffer[_TTY_QUEUE_SIZE]; /* static data */ static unsigned ah_key_sense; static unsigned ah_key_get; static unsigned ah_ctrl_sense; static ssize_t __libc_termios_read (int handle, void *buffer, size_t count, ssize_t *rv); static ssize_t __libc_termios_read_cooked_tty (int handle, void *buffer, size_t count); static ssize_t __libc_termios_read_raw_tty (int handle, void *buffer, size_t count); static void __libc_termios_echo_ctrl (unsigned char ch); static void __libc_termios_maybe_echo_ctrl (unsigned char ch); static void __libc_termios_maybe_echo (unsigned char ch); static void __libc_termios_maybe_erase1 (void); static void __libc_termios_erase_editline (void); static void __libc_termios_kill_editline (void); static void __libc_termios_insert_editline (unsigned char ch); #if 0 /* Not used. */ static void __libc_termios_clear_queue (void); #endif static int __libc_termios_get_queue (void); static int __libc_termios_put_queue (unsigned char ch); static void __libc_termios_fill_queue (void); /* direct I/O functions */ static inline int __direct_keysense (void); static inline unsigned char __direct_keyinput (void); static inline int __direct_ctrlsense (void); void __libc_termios_init_read(void) { if (_farpeekb (_dos_ds, 0x496) & 0x10) { ah_key_get = 0x10; ah_key_sense = 0x11; ah_ctrl_sense = 0x12; } else { ah_key_get = 0x00; ah_key_sense = 0x01; ah_ctrl_sense = 0x02; } /* set special hooks */ __libc_read_termios_hook = __libc_termios_read; } /******************************************************************************/ /* direct I/O function (inlined) **********************************************/ #define _REG_STATUS_ZF 0x40 static inline int __direct_keysense(void) { __dpmi_regs r = {0}; char is_ext_key; if (_farpeekw(_dos_ds, 0x41a) == _farpeekw(_dos_ds, 0x41c)) return SENSE_NO_KEY; r.h.ah = ah_key_sense; __dpmi_int(0x16, &r); if (r.x.flags & _REG_STATUS_ZF) return SENSE_NO_KEY; is_ext_key = (r.h.al == 0x00 || r.h.al == 0xe0); return is_ext_key ? SENSE_EXT_KEY : SENSE_REG_KEY; } static inline unsigned char __direct_keyinput (void) { __dpmi_regs r = {0}; r.h.ah = ah_key_get; __dpmi_int (0x16, &r); return r.h.al; } /* Get an extended key and return its encoding. */ static inline const unsigned char * get_ext_key_string(void) { __dpmi_regs r = {0}; r.h.ah = ah_key_get; __dpmi_int(0x16, &r); return (__get_extended_key_string((int)r.h.ah)); } #define _KEY_INS 0x80 #define _KEY_CAPS 0x40 #define _KEY_NUM 0x20 #define _KEY_SCRL 0x10 #define _KEY_ALT 0x08 #define _KEY_CTRL 0x04 #define _KEY_LSFT 0x02 #define _KEY_RSFT 0x01 static inline int __direct_ctrlsense (void) { __dpmi_regs r = {0}; r.h.ah = 0x02; __dpmi_int (0x16, &r); if (r.h.al & _KEY_CTRL) return 1; return 0; } static inline int wait_keysense(void) { int sense; while ((sense = __direct_keysense()) == SENSE_NO_KEY) __dpmi_yield(); return sense; } /******************************************************************************/ /* special read function ******************************************************/ static ssize_t __libc_termios_read (int handle, void *buffer, size_t count, ssize_t *rv) { short devmod; /* check handle whether valid or not */ devmod = _get_dev_info (handle); if (devmod == -1) { *rv = -1; return 1; } /* special case */ if (count == 0) { *rv = 0; return 1; } /* console only... */ if ((devmod & _DEV_CDEV) && (devmod & (_DEV_STDIN|_DEV_STDOUT))) { /* character device */ if ((devmod & _DEV_RAW) && (__file_handle_modes[handle] & O_BINARY)) *rv = __libc_termios_read_raw_tty (handle, buffer, count); else *rv = __libc_termios_read_cooked_tty (handle, buffer, count); return 1; } return 0; } static ssize_t __libc_termios_read_cooked_tty (int handle, void *buffer, size_t count) { unsigned char *wp; ssize_t n; wp = (unsigned char *)buffer; n = count; if (__libc_tty_p->t_lflag & ICANON) { /* get inputs (wait for NL or EOT) */ if (! __libc_termios_exist_queue ()) __libc_termios_fill_queue (); while (--n >= 0) { if (! __libc_termios_exist_queue ()) break; *wp++ = __libc_termios_get_queue (); } } else { /* block until getting inputs */ while (! __libc_termios_exist_queue ()) { __dpmi_yield (); __libc_termios_fill_queue (); } while (--n >= 0) { *wp++ = __libc_termios_get_queue (); if (! __libc_termios_exist_queue ()) { __libc_termios_fill_queue (); if (! __libc_termios_exist_queue ()) break; } } } return (ssize_t) (wp - (unsigned char *) buffer); } static ssize_t __libc_termios_read_raw_tty (int handle, void *buffer, size_t count) { unsigned char *wp; unsigned char ch; ssize_t n; int sense; n = count; wp = (unsigned char *)buffer; sense = SENSE_NO_KEY; /* block until getting inputs */ if (!__libc_termios_exist_queue()) wait_keysense(); while (--n >= 0) { /* exhaust inputs ? */ if (!__libc_termios_exist_queue() && (sense = __direct_keysense()) == SENSE_NO_KEY) break; if (__libc_termios_exist_queue()) { ch = (unsigned char)__libc_termios_get_queue(); } else if (sense == SENSE_REG_KEY) { ch = __direct_keyinput(); /* replace CTRL+SPACE with 0x00 */ if (ch == ' ' && __direct_ctrlsense ()) ch = '\0'; } else { const unsigned char *ext_key_string; ext_key_string = get_ext_key_string(); if (ext_key_string == NULL) { /* This extended key has no encoding. If there are no keys already stored in the buffer, wait for another key to ensure at least one character is put into the buffer. */ ++n; if (wp == (unsigned char *)buffer) { wait_keysense(); } continue; } ch = *ext_key_string; ++ext_key_string; /* Add the rest of the string to the queue. */ __libc_termios_puts_queue(ext_key_string); } /* copy a character into buffer and echo */ *wp++ = ch; __libc_termios_maybe_echo (ch); } return (ssize_t) (wp - (unsigned char *) buffer); } /******************************************************************************/ /* edit routines **************************************************************/ static void __libc_termios_maybe_erase1 (void) { if (__libc_tty_p->t_lflag & ECHO) { /* eat prev. character by BS SPC BS */ __tty_screen_intface->putch('\b'); if (__libc_tty_p->t_lflag & ECHOE) __tty_screen_intface->putch_at(' '); } } /* erase one prev. character of editline */ static void __libc_termios_erase_editline (void) { int col; char pf; col = __libc_tty_editline.col; if (col == 0) /* no more */ return; /* get position flag */ pf = __libc_tty_editline.flag[col - 1]; if (pf == _TTY_EDITLINE_INVALID || pf < 0) { /* erase all invalid character */ while (col > 0 && __libc_tty_editline.flag[col - 1] < 0) { __libc_termios_maybe_erase1 (); --col; } } else if (pf == _TTY_EDITLINE_CTRL) { if (__libc_tty_p->t_lflag & ECHOCTL) { /* erase one of "^X" */ __libc_termios_maybe_erase1 (); } __libc_termios_maybe_erase1 (); --col; } else { /* erase single or multibyte charcter */ while (--pf >= 0) { __libc_termios_maybe_erase1 (); --col; } } /* set next insert position */ __libc_tty_editline.col = col; } /* erase all editline */ static void __libc_termios_kill_editline (void) { if (__libc_tty_p->t_lflag & ECHOKE) { while (__libc_tty_editline.col != 0) __libc_termios_erase_editline (); } else __libc_tty_editline.col = 0; if (__libc_tty_p->t_lflag & ECHOK) __libc_termios_maybe_echo ('\n'); } static void __libc_termios_insert_editline (unsigned char ch) { int mbsize; unsigned int col; col = __libc_tty_editline.col; if (col >= _TTY_EDITLINE_SIZE) { /* detected over flow */ if (__libc_tty_p->t_iflag & IMAXBEL) __tty_screen_intface->putch_at('\007'); return; } __libc_tty_editline.buf[col] = ch; if (col == 0 || ((col > 0) && __libc_tty_editline.flag[col - 1] != _TTY_EDITLINE_INVALID)) { /* check multibyte length */ mbsize = mblen ((const char *)__libc_tty_editline.buf + col, 1); if (mbsize == 1) { /* single character */ if ((ch < 0x20) || ch == 0x7f) __libc_tty_editline.flag[col] = _TTY_EDITLINE_CTRL; else __libc_tty_editline.flag[col] = _TTY_EDITLINE_SINGLE; __libc_termios_maybe_echo (ch); } else { /* first of multibyte or invalid character */ __libc_tty_editline.flag[col] = _TTY_EDITLINE_INVALID; __libc_tty_p->t_status |= _TS_LNCH; } } else { unsigned int pcol; /* look for non fixed flag */ pcol = col; while (__libc_tty_editline.flag[pcol - 1] == _TTY_EDITLINE_INVALID) if (--pcol <= 0) break; /* check whether it's multibyte sequence */ mbsize = mblen ((const char *)__libc_tty_editline.buf + pcol, (col - pcol + 1)); if (mbsize > 1) { /* multibyte sequence is good */ while (pcol < col) { /* set negative size for dividing sequence */ __libc_tty_editline.flag[pcol] = -mbsize; __libc_termios_maybe_echo (__libc_tty_editline.buf[pcol]); pcol++; } /* last flag is always positive size */ __libc_tty_editline.flag[col] = mbsize; __libc_termios_maybe_echo (ch); } else { if ((col - pcol + 1) > (unsigned)MB_CUR_MAX) { /* it's terrible... */ while (pcol <= col) { /* replace all with valid character */ __libc_tty_editline.flag[pcol] = _TTY_EDITLINE_SINGLE; __libc_tty_editline.buf[pcol] = 'X'; __libc_termios_maybe_echo ('X'); pcol++; } } /* continuous multibyte character */ __libc_tty_editline.flag[col] = _TTY_EDITLINE_INVALID; __libc_tty_p->t_status |= _TS_LNCH; } } /* set next insert position */ col++; __libc_tty_editline.col = col; } /******************************************************************************/ /* queued input routines ******************************************************/ int __libc_termios_exist_queue (void) { return __libc_tty_p->t_count; } #if 0 /* Not used. */ static void __libc_termios_clear_queue (void) { __libc_tty_p->t_count = 0; __libc_tty_p->t_rpos = __libc_tty_p->t_top; __libc_tty_p->t_wpos = __libc_tty_p->t_top; } #endif static int __libc_termios_get_queue (void) { int ch; if (__libc_tty_p->t_count == 0) return -1; ch = *__libc_tty_p->t_rpos++; __libc_tty_p->t_count--; if (__libc_tty_p->t_rpos >= __libc_tty_p->t_bottom) __libc_tty_p->t_rpos = __libc_tty_p->t_top; return ch; } static int __libc_termios_put_queue (unsigned char ch) { if (__libc_tty_p->t_count >= __libc_tty_p->t_size) return -1; *__libc_tty_p->t_wpos++ = ch; __libc_tty_p->t_count++; if (__libc_tty_p->t_wpos >= __libc_tty_p->t_bottom) __libc_tty_p->t_wpos = __libc_tty_p->t_top; return 0; } static void __libc_termios_fill_queue (void) { unsigned char ch; int sense; const unsigned char *ext_key_string; ext_key_string = NULL; while (1) { /* exhausted inputs? */ if (ext_key_string == NULL && (sense = __direct_keysense()) == SENSE_NO_KEY) { if (__libc_tty_p->t_lflag & ICANON) { /* waiting for NL or EOT */ sense = wait_keysense(); } else return; } /* Add a character to the output buffer. */ if (ext_key_string) { ch = *ext_key_string; ++ext_key_string; if (*ext_key_string == '\0') ext_key_string = NULL; } else if (sense == SENSE_REG_KEY) { ch = __direct_keyinput(); /* replace CTRL+SPACE with 0x00 */ if (ch == ' ' && __direct_ctrlsense()) ch = '\0'; } else { ext_key_string = get_ext_key_string(); if (ext_key_string == NULL) continue; ch = *ext_key_string; ++ext_key_string; } /* process input if needed */ if (! (__libc_tty_p->t_status & _TS_LNCH) || ch != (unsigned char) _POSIX_VDISABLE) { /* software signals */ if (__libc_tty_p->t_lflag & ISIG) { if (! (__libc_tty_p->t_iflag & IGNBRK) && _CC_EQU (VINTR, ch)) { /* Only raise the signal if SIGINT generation by the INTR key is disabled; otherwise the signal was already raised and what we see here is the key which generated it that wasn't removed from the keyboard buffer. */ if (__libc_tty_p->t_iflag & BRKINT) { if (__djgpp_set_ctrl_c (-1) == 0) { __libc_termios_maybe_echo_ctrl (ch); kill (getpid (), SIGINT); } continue; } else { ch = '\0'; goto proc_skip; } } /* See the commentary about SIGINT above. */ else if (_CC_EQU (VQUIT, ch)) { if (__djgpp_set_ctrl_c (-1) == 0) { __libc_termios_maybe_echo_ctrl (ch); kill (getpid(), SIGQUIT); } continue; } else if (_CC_EQU (VSUSP, ch)) { __libc_termios_maybe_echo_ctrl (ch); #ifdef SIGTSTP kill (getpid(), SIGTSTP); #else /* djgpp don't have ... */ { char oldcwd[PATH_MAX]; int fds[5] = { -1, -1, -1, -1, -1 }; int i; for (i = 0; i < 5; i++) if ((fds[i] = fcntl (i, F_DUPFD, 20)) < 0) __tty_screen_intface->puts((const unsigned char *)"Suspend: cannot save fds\r\n"); __tty_screen_intface->puts((const unsigned char *)"\r\nSuspended\r\n"); /* keep cwd on exec */ getcwd (oldcwd, sizeof (oldcwd)); system (""); chdir (oldcwd); for (i = 0; i < 5; i++) if (fds[i] >= 0) { dup2 (fds[i], i); close (fds[i]); } } #endif /* !SIGTSTP */ continue; } } /* flow control... */ if (_CC_EQU (VSTART, ch) && (__libc_tty_p->t_iflag & IXOFF)) { continue; } else if (_CC_EQU (VSTOP, ch) && (__libc_tty_p->t_iflag & IXOFF)) { continue; } /* CR/LF process */ if (ch == '\r') { if (__libc_tty_p->t_iflag & IGNCR) continue; if (__libc_tty_p->t_iflag & ICRNL) ch = '\n'; } else if ((ch == '\n') && (__libc_tty_p->t_iflag & INLCR)) ch = '\r'; /* strip 8th-bit */ if (__libc_tty_p->t_iflag & ISTRIP) ch &= 0x7f; } proc_skip: if (__libc_tty_p->t_lflag & ICANON) { if (__libc_tty_p->t_status & _TS_LNCH) { __libc_tty_p->t_status &= ~_TS_LNCH; __libc_termios_insert_editline (ch); } else { if (_CC_EQU (VERASE, ch)) __libc_termios_erase_editline (); else if (_CC_EQU (VKILL, ch)) __libc_termios_kill_editline (); else if (ch == '\n' || _CC_EQU (VEOF, ch) || _CC_EQU (VEOL, ch)) { int col = __libc_tty_editline.col; unsigned char *p = __libc_tty_editline.buf; /* clear column for next access */ __libc_tty_editline.col = 0; /* copy editline into tty queue */ while (--col >= 0) __libc_termios_put_queue (*p++); /* echo terminate character and put it */ __libc_termios_maybe_echo (ch); if (_CC_NEQU (VEOF, ch)) __libc_termios_put_queue (ch); return; } else __libc_termios_insert_editline (ch); } /* !_TS_LNCH */ } else /* !ICANON */ { __libc_termios_maybe_echo (ch); __libc_tty_p->t_status &= ~_TS_LNCH; __libc_termios_put_queue (ch); } } /* end of while (1) */ } int __libc_termios_puts_queue(const unsigned char *str) { while (*str) { if (__libc_tty_p->t_count >= __libc_tty_p->t_size) return -1; *__libc_tty_p->t_wpos++ = *str; ++__libc_tty_p->t_count; ++str; if (__libc_tty_p->t_wpos >= __libc_tty_p->t_bottom) __libc_tty_p->t_wpos = __libc_tty_p->t_top; } return 0; } /******************************************************************************/ /* echo routines **************************************************************/ static void __libc_termios_echo_ctrl (unsigned char ch) { ch ^= 0x40; __tty_screen_intface->putch('^'); __tty_screen_intface->putch(ch); } static void __libc_termios_maybe_echo_ctrl (unsigned char ch) { if (__libc_tty_p->t_lflag & ECHOCTL) __libc_termios_echo_ctrl (ch); } static void __libc_termios_maybe_echo (unsigned char ch) { if (! (__libc_tty_p->t_lflag & ECHO)) { /* don't echo but newline is required ? */ if (ch == '\n' && (__libc_tty_p->t_lflag & ECHONL)) { if (__libc_tty_p->t_oflag & ONLCR) __tty_screen_intface->putch('\r'); __tty_screen_intface->putch('\n'); } return; } /* check literal flag */ if ((__libc_tty_p->t_status & _TS_LNCH) && (ch < 0x20)) { __libc_termios_echo_ctrl (ch); return; } /* output process if need */ if (__libc_tty_p->t_oflag & OPOST) { /* map NL to CRNL */ if (ch == '\n' && (__libc_tty_p->t_oflag & ONLCR)) { __tty_screen_intface->putch('\r'); __tty_screen_intface->putch('\n'); return; } /* map CR to NL */ else if (ch == '\r' && (__libc_tty_p->t_oflag & OCRNL)) { __tty_screen_intface->putch('\n'); return; } /* discard EOT */ else if (_CC_EQU (VEOF, ch) && (__libc_tty_p->t_oflag & ONOEOT)) { return; } } /* control character except newline */ if ((ch < 0x20 || ch == 0x7f) && (__libc_tty_p->t_lflag & ECHOCTL) && ch != '\n') { __libc_termios_echo_ctrl (ch); return; } /* no effect */ __tty_screen_intface->putch(ch); } /******************************************************************************/