/* Terminal handling. Copyright 2002 Johan Rydberg, jrydberg@rtmk.org. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This file is from the GNU Hurd project (modified of course) Copyright (C) 1995, 1996, 1999 Free Software Foundation, Inc. Written by Michael I. Bushnell, p/BSG. */ #include #include #include #include #include #include #include "term.h" /* #define TTYDEFCHARS */ static cc_t ttydefchars[NCCS] = { CEOF, CEOL, CEOL, CERASE, CWERASE, CKILL, CREPRINT, _POSIX_VDISABLE, CINTR, CQUIT, CSUSP, CDSUSP, CSTART, CSTOP, CLNEXT, CDISCARD, CMIN, CTIME, CSTATUS, _POSIX_VDISABLE }; void drop_output (struct term *term); /* Allocate a new terminal. BOTTOM is the bottomhalf for the terminal. Will set TERM_AUX to AUX. */ struct term * term_allocate (struct bottomhalf *bottom, void *aux) { struct term *term; term = malloc (sizeof (struct term)); assert (term); memset (term, 0, sizeof (struct term)); term->inputq = create_queue (256, QUEUE_LOWAT, QUEUE_HIWAT); term->rawq = create_queue (256, QUEUE_LOWAT, QUEUE_HIWAT); term->outputq = create_queue (256, QUEUE_LOWAT, QUEUE_HIWAT); term->term_aux = aux; term->bottom = bottom; #if 0 term->termflags = NO_CARRIER | NO_OWNER; #endif pthread_mutex_init (&term->global_lock, 0); pthread_cond_init (&term->carrier_alert, 0); pthread_cond_init (&term->select_alert, 0); term->termstate.c_iflag = TTYDEF_IFLAG; term->termstate.c_oflag = TTYDEF_OFLAG; term->termstate.c_lflag = TTYDEF_LFLAG; term->termstate.c_cflag = TTYDEF_CFLAG; term->termstate.__ospeed = TTYDEF_SPEED; term->termstate.__ispeed = TTYDEF_SPEED; memcpy (&term->termstate.c_cc, ttydefchars, sizeof ttydefchars); return term; } /* Actually drop character onto output queue. This should be the only place where we actually enqueue characters on the output queue; it is responsible for keeping track of cursor positions. */ inline void poutput (struct term *term, int c) { if (term->termflags & FLUSH_OUTPUT) return; /* never mind */ if ((c >= ' ') && (c < '\177')) term->output_psize++; else if (c == '\r') term->output_psize = 0; else if (c == '\t') { term->output_psize++; while (term->output_psize % 8) term->output_psize++; } else if (c == '\b') term->output_psize--; enqueue (term, &term->outputq, c); } /* Place C on output queue, doing normal output processing. Only echo routines should directly call this function. Others should call write_character below. */ void output_character (struct term *term, int c) { int oflag = term->termstate.c_oflag; /* One might think we should turn of INHDERASE here, but, no in U*x it is only turned off by echoed characters. See echo_char in input.c. */ if (oflag & OPOST) { /* Characters we write specially */ if ((oflag & ONLCR) && c == '\n') { poutput (term, '\r'); poutput (term, '\n'); } else if (!term->external_processing && (oflag & OXTABS) && c == '\t') { poutput (term, ' '); while (term->output_psize % 8) poutput (term, ' '); } else if ((oflag & ONOEOT) && c == CHAR_EOT) ; else if ((oflag & OTILDE) && c == '~') { poutput (term, '\\'); poutput (term, '`'); } else if ((oflag & OLCASE) && isalpha (c)) { if (isupper (c)) poutput (term, '\\'); else c = toupper (c); poutput (term, c); } else poutput (term, c); } else poutput (term, c); } /* Place C on output queue, doing normal processing. */ void write_character (struct term *term, int c) { output_character (term, c); term->echo_qsize = 0; term->echo_pstart = term->output_psize; } /* Report the width of character C as printed by output_character, if output_psize were at LOC. . */ int output_width (struct term *term, int c, int loc) { int oflag = term->termstate.c_oflag; if (oflag & OPOST) { if ((oflag & OTILDE) && c == '~') return 2; if ((oflag & OLCASE) && isalpha (c) && isupper (c)) return 2; } if (c == '\t') { int n = loc + 1; while (n % 8) n++; return n - loc; } if ((c >= ' ') && (c < '\177')) return 1; return 0; } /* For each character in this table, if the element is set, then the character is EVEN */ char const char_parity[] = { 1, 0, 0, 1, 0, 1, 1, 0, /* nul - bel */ 0, 1, 1, 0, 1, 0, 0, 1, /* bs - si */ 0, 1, 1, 0, 1, 0, 0, 1, /* dle - etb */ 1, 0, 0, 1, 0, 1, 1, 0, /* can - us */ 0, 1, 1, 0, 1, 0, 0, 1, /* sp - ' */ 1, 0, 0, 1, 0, 1, 1, 0, /* ( - / */ 1, 0, 0, 1, 0, 1, 1, 0, /* 0 - 7 */ 0, 1, 1, 0, 1, 0, 0, 1, /* 8 - ? */ 0, 1, 1, 0, 1, 0, 0, 1, /* @ - G */ 1, 0, 0, 1, 0, 1, 1, 0, /* H - O */ 1, 0, 0, 1, 0, 1, 1, 0, /* P - W */ 0, 1, 1, 0, 1, 0, 0, 1, /* X - _ */ 1, 0, 0, 1, 0, 1, 1, 0, /* ` - g */ 0, 1, 1, 0, 1, 0, 0, 1, /* h - o */ 0, 1, 1, 0, 1, 0, 0, 1, /* p - w */ 1, 0, 0, 1, 0, 1, 1, 0, /* x - del */ }; #define checkevenpar(c) (((c)&0x80) \ ? !char_parity[(c)&0x7f] \ : char_parity[(c)&0x7f]) #define checkoddpar(c) (((c)&0x80) \ ? char_parity[(c)&0x7f] \ : !char_parity[(c)&0x7f]) /* These functions are used by both echo and erase code */ /* Tell if we should echo a character at all */ static inline int echo_p (struct term *term, char c, int quoted) { if (term->external_processing) return 0; return ((term->termstate.c_lflag & ECHO) || (c == '\n' && (term->termstate.c_lflag & ECHONL) && !quoted)); } /* Tell if this character deserves double-width cntrl processing */ static inline int echo_double (struct term *term, char c, int quoted) { return (iscntrl (c) && (term->termstate.c_lflag & ECHOCTL) && !((c == '\n' || c == '\t') && !quoted)); } /* Do a single C-h SPC C-h sequence */ static inline void write_erase_sequence (struct term *term) { poutput (term, '\b'); poutput (term, ' '); poutput (term, '\b'); } /* Echo a single character to the output */ /* If this is an echo of a character which is being hard-erased, set hderase. If this is a newline or tab which should not receive their normal special processing, set quoted. */ static void echo_char (struct term *term, char c, int hderase, int quoted) { term->echo_qsize++; if (echo_p (term, c, quoted)) { if (!hderase && (term->termflags & INSIDE_HDERASE)) { write_character (term, '/'); term->termflags &= ~INSIDE_HDERASE; } if (hderase && !(term->termflags & INSIDE_HDERASE)) { output_character (term, '\\'); term->termflags |= INSIDE_HDERASE; } /* Control characters that should use caret-letter */ if (echo_double (term, c, quoted)) { output_character (term, '^'); output_character (term, c ^ CTRL_BIT); } else output_character (term, c); } } /* Re-echo the current rawq preceded by the VREPRINT char. */ static inline void reprint_line (struct term *term) { short *cp; if (term->termstate.c_cc[VREPRINT] != _POSIX_VDISABLE) echo_char (term, term->termstate.c_cc[VREPRINT], 0, 0); else echo_char (term, CHAR_DC2, 0, 0); echo_char (term, '\n', 0, 0); term->echo_qsize = 0; term->echo_pstart = term->output_psize; for (cp = term->rawq->cs; cp != term->rawq->ce; cp++) echo_char (term, unquote_char (*cp), 0, char_quoted_p (*cp)); } /* Erase a single character off the end of the rawq, and delete it from the screen appropriately. Set ERASE_CHAR if this is being done by the VERASE character, to that character. */ static void erase_1 (struct term *term, char erase_char) { int quoted; char c; quoted_char cq; if (qsize (term->rawq) == 0) return; cq = queue_erase (term->rawq); c = unquote_char (cq); quoted = char_quoted_p (cq); if (!echo_p (term, c, quoted)) return; /* The code for WERASE knows that we echo erase_char iff !ECHOPRT && !ECHO. */ if (term->echo_qsize--) { if (term->termstate.c_lflag & ECHOPRT) echo_char (term, c, 1, quoted); else if (!(term->termstate.c_lflag & ECHOE) && erase_char) echo_char (term, erase_char, 0, 0); else { int nerase; if (echo_double (term, c, quoted)) nerase = 2; else if (c == '\t') { quoted_char *cp; int loc = term->echo_pstart; for (cp = term->rawq->ce - term->echo_qsize; cp != term->rawq->ce; cp++) loc += (echo_double (term, unquote_char (*cp), char_quoted_p (*cp)) ? 2 : output_width (term, *cp, loc)); nerase = term->output_psize - loc; } else nerase = output_width (term, c, term->output_psize); while (nerase--) write_erase_sequence (term); } if (term->echo_qsize == 0) assert (term->echo_pstart == term->output_psize); } else reprint_line (term); } /* Place newly input character C on the input queue. If all remaining pending input characters should be dropped, return 1; else return 0. */ int input_character (struct term *term, int c) { int lflag = term->termstate.c_lflag; int iflag = term->termstate.c_iflag; int cflag = term->termstate.c_cflag; cc_t *cc = term->termstate.c_cc; struct queue **qp = (lflag & ICANON) ? &term->rawq : &term->inputq; int flush = 0; /* Handle parity errors */ if ((iflag & INPCK) && ((cflag & PARODD) ? checkoddpar (c) : checkevenpar (c))) { if (iflag & IGNPAR) goto alldone; else if (iflag & PARMRK) { enqueue_quote (term, qp, CHAR_USER_QUOTE); enqueue_quote (term, qp, '\0'); enqueue_quote (term, qp, c); goto alldone; } else c = 0; } /* Check to see if we should send IXOFF */ if ((iflag & IXOFF) && !qavail (*qp) && (cc[VSTOP] != _POSIX_VDISABLE)) { poutput (term, cc[VSTOP]); term->termflags |= SENT_VSTOP; } /* Character mutations */ if (!(iflag & ISTRIP) && (iflag & PARMRK) && (c == CHAR_USER_QUOTE)) enqueue_quote (term, qp, CHAR_USER_QUOTE); /* cause doubling */ if (iflag & ISTRIP) c &= 0x7f; /* Handle LNEXT right away */ if (!term->external_processing && (term->termflags & LAST_LNEXT)) { enqueue_quote (term, qp, c); echo_char (term, c, 0, 1); term->termflags &= ~LAST_LNEXT; goto alldone; } /* Mutate ILCASE */ if (!term->external_processing && (iflag & ILCASE) && isalpha(c)) { if (term->termflags & LAST_SLASH) erase_1 (term, 0); /* remove the slash from input */ else c = isupper(c) ? tolower (c) : c; } /* IEXTEN control chars */ if (!term->external_processing && (lflag & IEXTEN)) { if (CCEQ (cc[VLNEXT], c)) { if (lflag & ECHO) { poutput (term, '^'); poutput (term, '\b'); } term->termflags |= LAST_LNEXT; goto alldone; } if (CCEQ (cc[VDISCARD], c)) { if (term->termflags & FLUSH_OUTPUT) term->termflags &= ~FLUSH_OUTPUT; else { drop_output (term); poutput (term, cc[VDISCARD]); term->termflags |= FLUSH_OUTPUT; } goto alldone; } } #if 0 /* Signals */ if (!term->external_processing && (lflag & ISIG)) { if (CCEQ (cc[VINTR], c) || CCEQ (cc[VQUIT], c)) { if (!(lflag & NOFLSH)) { drop_output (term); clear_queue (term->inputq); clear_queue (term->rawq); flush = 1; } echo_char (term, c, 0, 0); term->echo_qsize = 0; term->echo_pstart = term->output_psize; send_signal (CCEQ (cc[VINTR], c) ? SIGINT : SIGQUIT); goto alldone; } if (CCEQ (cc[VSUSP], c)) { if (!(lflag & NOFLSH)) { flush = 1; clear_queue (term->inputq); clear_queue (term->rawq); } echo_char (term, c, 0, 0); term->echo_qsize = 0; term->echo_pstart = term->output_psize; send_signal (SIGTSTP); goto alldone; } } #endif /* IXON */ if (!term->external_processing && (iflag & IXON)) { if (CCEQ (cc[VSTOP], c)) { term->termflags |= USER_OUTPUT_SUSP; (*term->bottom->suspend_physical_output) (term); if (!(CCEQ(cc[VSTART], c))) /* toggle if VSTART == VSTOP */ /* Alldone code always turns off USER_OUTPUT_SUSP. */ goto alldone; return flush; } if (CCEQ (cc[VSTART], c)) goto alldone; } if (!term->external_processing) { /* Newline and carriage-return frobbing */ if (c == '\r') { if (iflag & ICRNL) c = '\n'; else if (iflag & IGNCR) goto alldone; } else if ((c == '\n') && (iflag & INLCR)) c = '\r'; } /* Canonical mode processing */ if (!term->external_processing && (lflag & ICANON)) { if (CCEQ (cc[VERASE], c)) { if (qsize(term->rawq)) erase_1 (term, c); if (!(term->termflags & LAST_SLASH) || !(lflag & IEXTEN)) goto alldone; } if (CCEQ (cc[VKILL], c)) { if (!(term->termflags & LAST_SLASH) || !(lflag & IEXTEN)) { if ((lflag & ECHOKE) && !(lflag & ECHOPRT) && (term->echo_qsize == qsize (term->rawq))) { while (term->output_psize > term->echo_pstart) write_erase_sequence (term); } else { echo_char (term, c, 0, 0); if ((lflag & ECHOK) || (lflag & ECHOKE)) echo_char (term, '\n', 0, 0); } clear_queue (term->rawq); term->echo_qsize = 0; term->echo_pstart = term->output_psize; term->termflags &= ~(LAST_SLASH|LAST_LNEXT|INSIDE_HDERASE); goto alldone; } else erase_1 (term, 0); /* remove \ */ } if (CCEQ (cc[VWERASE], c)) { /* If we are not going to echo the erase, then echo a WERASE character right now. (If we passed it to erase_1; it would echo it multiple times.) */ if (!(lflag & (ECHOPRT|ECHOE))) echo_char (term, cc[VWERASE], 0, 1); /* Erase whitespace */ while (qsize (term->rawq) && isblank (unquote_char (term->rawq->ce[-1]))) erase_1 (term, 0); /* Erase word. */ if (lflag & ALTWERASE) /* For ALTWERASE, we erase back to the first blank */ while (qsize (term->rawq) && !isblank (unquote_char (term->rawq->ce[-1]))) erase_1 (term, 0); else /* For regular WERASE, we erase back to the first nonalpha/_ */ while (qsize (term->rawq) && !isblank (unquote_char (term->rawq->ce[-1])) && (isalnum (unquote_char (term->rawq->ce[-1])) || (unquote_char (term->rawq->ce[-1]) != '_'))) erase_1 (term, 0); goto alldone; } if (CCEQ (cc[VREPRINT], c) && (lflag & IEXTEN)) { reprint_line (term); goto alldone; } #if 0 if (CCEQ (cc[VSTATUS], c) && (lflag & ISIG) && (lflag & IEXTEN)) { send_signal (SIGINFO); goto alldone; } #endif } /* Now we have a character intended as input. See if it will fit. */ if (!qavail (*qp)) { if (iflag & IMAXBEL) poutput (term, '\a'); else { /* Drop everything */ drop_output (term); clear_queue (term->inputq); clear_queue (term->rawq); term->echo_pstart = 0; term->echo_qsize = 0; flush = 1; } goto alldone; } /* Echo */ echo_char (term, c, 0, 0); if (CCEQ (cc[VEOF], c) && (lflag & ECHO)) { /* Special bizzare echo processing for VEOF character. */ int n; n = echo_double (term, c, 0) ? 2 : output_width (term, c, term->output_psize); while (n--) poutput (term, '\b'); } /* Put character on input queue */ enqueue (term, qp, c); /* Check for break characters in canonical input processing */ if (lflag & ICANON) { if (CCEQ (cc[VEOL], c) || CCEQ (cc[VEOL2], c) || CCEQ (cc[VEOF], c) || c == '\n') /* Make input available */ while (qsize (term->rawq)) enqueue (term, &term->inputq, dequeue (term, term->rawq)); } alldone: /* Restart output */ if ((iflag & IXANY) || (CCEQ (cc[VSTART], c))) term->termflags &= ~USER_OUTPUT_SUSP; (*term->bottom->start_output) (term); return flush; } /* This is called by the lower half when a break is received. */ void input_break (struct term *term) { struct queue **qp = term->termstate.c_lflag & ICANON ? &term->rawq : &term->inputq; /* Don't do anything if IGNBRK is set. */ if (term->termstate.c_iflag & IGNBRK) return; #if 0 /* If BRKINT is set, then flush queues and send SIGINT. */ if (term->termstate.c_iflag & BRKINT) { drop_output (term); /* XXX drop pending input How?? */ send_signal (SIGINT); return; } #endif /* A break is then read as a null byte; marked specially if PARMRK is set. */ if (term->termstate.c_iflag & PARMRK) { enqueue_quote (term, qp, CHAR_USER_QUOTE); enqueue_quote (term, qp, '\0'); } enqueue_quote (term, qp, '\0'); } /* Called when a character is recived with a framing error. */ void input_framing_error (struct term *term, int c) { struct queue **qp = term->termstate.c_lflag & ICANON ? &term->rawq : &term->inputq; /* Ignore it if IGNPAR is set. */ if (term->termstate.c_iflag & IGNPAR) return; /* If PARMRK is set, pass it specially marked. */ if (term->termstate.c_iflag & PARMRK) { enqueue_quote (term, qp, CHAR_USER_QUOTE); enqueue_quote (term, qp, '\0'); enqueue_quote (term, qp, c); } else /* Otherwise, it looks like a null. */ enqueue_quote (term, qp, '\0'); } /* Copy the characters in RAWQ to the end of INPUTQ and clear RAWQ. */ void copy_rawq (struct term *term) { while (qsize (term->rawq)) enqueue (term, &term->inputq, dequeue (term, term->rawq)); } /* Process all the characters in INPUTQ as if they had just been read. */ void rescan_inputq (struct term *term) { short *buf; int i, n; n = qsize (term->inputq); buf = alloca (n * sizeof (quoted_char)); bcopy (term->inputq->cs, buf, n * sizeof (quoted_char)); clear_queue (term->inputq); for (i = 0; i < n; i++) input_character (term, unquote_char (buf[i])); } void drop_output (struct term *term) { clear_queue (term->outputq); (*term->bottom->abandon_physical_output) (term); } error_t drain_output (struct term *term) { int cancel = 0; while ((qsize (term->outputq) || (*term->bottom->pending_output_size) (term)) && (!(term->termflags & NO_CARRIER) || (term->termstate.c_cflag & CLOCAL)) && !cancel) cancel = pthread_cond_wait (&term->outputq->wait, &term->global_lock); return cancel ? EINTR : 0; } /* Create and return a new queue. */ struct queue * create_queue (int size, int lowat, int hiwat) { struct queue *q; q = malloc (sizeof (struct queue) + size * sizeof (quoted_char)); assert (q); q->susp = 0; q->lowat = lowat; q->hiwat = hiwat; q->cs = q->ce = q->array; q->arraylen = size; pthread_cond_init (&q->wait, 0); return q; } /* Make Q able to have more characters added to it. */ struct queue * reallocate_queue (struct queue *q) { int len; struct queue *newq; len = qsize (q); if (len < q->arraylen / 2) { /* Shift the characters to the front of the queue. */ memmove (q->array, q->cs, len * sizeof (quoted_char)); q->cs = q->array; q->ce = q->cs + len; } else { /* Make the queue twice as large. */ newq = malloc (sizeof (struct queue) + q->arraylen * 2 * sizeof (quoted_char)); newq->susp = q->susp; newq->lowat = q->lowat; newq->hiwat = q->hiwat; newq->cs = newq->array; newq->ce = newq->array + len; newq->arraylen = q->arraylen * 2; newq->wait = q->wait; memmove (newq->array, q->cs, len * sizeof (quoted_char)); free (q); q = newq; } return q; }