root/usr/src/cmd/mailx/tty.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */


/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

/*
 * mailx -- a modified version of a University of California at Berkeley
 *      mail program
 *
 * Generally useful tty stuff.
 */

#include "rcv.h"
#include <locale.h>

#ifdef  USG_TTY

static char     *readtty(char pr[], char src[]);
static int      savetty(void);
static void     ttycont(int);

static  int     c_erase;                /* Current erase char */
static  int     c_kill;                 /* Current kill char */
static  int     c_intr;                 /* interrupt char */
static  int     c_quit;                 /* quit character */
static  struct termio savtty;
static  char canonb[LINESIZE];          /* canonical buffer for input */
                                        /* processing */

#ifndef TIOCSTI
static void     Echo(int cc);
static int      countcol(void);
static void     outstr(register char *s);
static void     resetty(void);
static void     rubout(register char *cp);
static int      setty(void);

static  int     c_word;                 /* Current word erase char */
static  int     Col;                    /* current output column */
static  int     Pcol;                   /* end column of prompt string */
static  int     Out;                    /* file descriptor of stdout */
static  int     erasing;                /* we are erasing characters */
static  struct termio ttybuf;
#else
static  jmp_buf rewrite;                /* Place to go when continued */
#endif

#ifdef SIGCONT
# ifdef preSVr4
typedef int     sig_atomic_t;
# endif
static  sig_atomic_t    hadcont;                /* Saw continue signal */

/*ARGSUSED*/
static void
#ifdef  __cplusplus
ttycont(int)
#else
/* ARGSUSED */
ttycont(int s)
#endif
{
        hadcont++;
        longjmp(rewrite, 1);
}

#ifndef TIOCSTI
/*ARGSUSED*/
static void
ttystop(int s)
{
        resetty();
        kill(mypid, SIGSTOP);
}
#endif
#endif

/*
 * Read all relevant header fields.
 */

int
grabh(register struct header *hp, int gflags, int subjtop)
{
#ifdef SIGCONT
        void (*savecont)(int);
#ifndef TIOCSTI
        void (*savestop)(int);
#endif
#endif
        if (savetty())
                return -1;
#ifdef SIGCONT
        savecont = sigset(SIGCONT, ttycont);
#ifndef TIOCSTI
        savestop = sigset(SIGTSTP, ttystop);
#endif
#endif
        if (gflags & GTO) {
                hp->h_to = addto(NOSTR, readtty("To: ", hp->h_to));
                if (hp->h_to != NOSTR)
                        hp->h_seq++;
        }
        if (gflags & GSUBJECT && subjtop) {
                hp->h_subject = readtty("Subject: ", hp->h_subject);
                if (hp->h_subject != NOSTR)
                        hp->h_seq++;
        }
        if (gflags & GCC) {
                hp->h_cc = addto(NOSTR, readtty("Cc: ", hp->h_cc));
                if (hp->h_cc != NOSTR)
                        hp->h_seq++;
        }
        if (gflags & GBCC) {
                hp->h_bcc = addto(NOSTR, readtty("Bcc: ", hp->h_bcc));
                if (hp->h_bcc != NOSTR)
                        hp->h_seq++;
        }
        if (gflags & GSUBJECT && !subjtop) {
                hp->h_subject = readtty("Subject: ", hp->h_subject);
                if (hp->h_subject != NOSTR)
                        hp->h_seq++;
        }
#ifdef SIGCONT
        (void) sigset(SIGCONT, savecont);
#ifndef TIOCSTI
        (void) sigset(SIGTSTP, savestop);
#endif
#endif
        return(0);
}

/*
 * Read up a header from standard input.
 * The source string has the preliminary contents to
 * be read.
 *
 */

static char *
readtty(char pr[], char src[])
{
        int c;
        register char *cp;

#ifndef TIOCSTI
        register char *cp2;

        erasing = 0;
        Col = 0;
        outstr(pr);
        Pcol = Col;
#else
        fputs(pr, stdout);
#endif
        fflush(stdout);
        if (src != NOSTR && (int)strlen(src) > LINESIZE - 2) {
                printf(gettext("too long to edit\n"));
                return(src);
        }
#ifndef TIOCSTI
        if (setty())
                return(src);
        cp2 = src==NOSTR ? "" : src;
        for (cp=canonb; *cp2; cp++, cp2++)
                *cp = *cp2;
        *cp = '\0';
        outstr(canonb);
#else
        cp = src == NOSTR ? "" : src;
        while (c = *cp++) {
                char ch;

                if (c == c_erase || c == c_kill) {
                        ch = '\\';
                        ioctl(0, TIOCSTI, &ch);
                }
                ch = c;
                ioctl(0, TIOCSTI, &ch);
        }
        cp = canonb;
        *cp = 0;
        if (setjmp(rewrite))
                goto redo;
#endif

        for (;;) {
                fflush(stdout);
#ifdef SIGCONT
                hadcont = 0;
#endif
                c = getc(stdin);

#ifndef TIOCSTI
                if (c==c_erase) {
                        if (cp > canonb)
                                if (cp[-1]=='\\' && !erasing) {
                                        *cp++ = (char)c;
                                        Echo(c);
                                } else {
                                        rubout(--cp);
                                }
                } else if (c==c_kill) {
                        if (cp > canonb && cp[-1]=='\\') {
                                *cp++ = (char)c;
                                Echo(c);
                        } else while (cp > canonb) {
                                rubout(--cp);
                        }
                } else if (c==c_word) {
                        if (cp > canonb)
                                if (cp[-1]=='\\' && !erasing) {
                                        *cp++ = (char)c;
                                        Echo(c);
                                } else {
                                        while (--cp >= canonb)
                                                if (!isspace(*cp))
                                                        break;
                                                else
                                                        rubout(cp);
                                        while (cp >= canonb)
                                                if (!isspace(*cp))
                                                        rubout(cp--);
                                                else
                                                        break;
                                        if (cp < canonb)
                                                cp = canonb;
                                        else if (*cp)
                                                cp++;
                                }
                } else
#endif
                if (c==EOF || ferror(stdin) || c==c_intr || c==c_quit) {
#ifdef SIGCONT
                        if (hadcont) {
#ifndef TIOCSTI
                                (void) setty();
                                outstr("(continue)\n");
                                Col = 0;
                                outstr(pr);
                                *cp = '\0';
                                outstr(canonb);
                                clearerr(stdin);
                                continue;
#else
                        redo:
                                hadcont = 0;
                                cp = canonb[0] != 0 ? canonb : src;
                                clearerr(stdin);
                                return(readtty(pr, cp));
#endif
                        }
#endif
#ifndef TIOCSTI
                        resetty();
#endif
                        savedead(c==c_quit? SIGQUIT: SIGINT);
                } else switch (c) {
                        case '\n':
                        case '\r':
#ifndef TIOCSTI
                                resetty();
                                putchar('\n');
                                fflush(stdout);
#endif
                                if (canonb[0]=='\0')
                                        return(NOSTR);
                                return(savestr(canonb));
                        default:
                                *cp++ = (char)c;
                                *cp = '\0';
#ifndef TIOCSTI
                                erasing = 0;
                                Echo(c);
#endif
                }
        }
}

static int
savetty(void)
{
        if (ioctl(fileno(stdout), TCGETA, &savtty) < 0)
        {       perror("ioctl");
                return(-1);
        }
        c_erase = savtty.c_cc[VERASE];
        c_kill = savtty.c_cc[VKILL];
        c_intr = savtty.c_cc[VINTR];
        c_quit = savtty.c_cc[VQUIT];
#ifndef TIOCSTI
        c_word = 'W' & 037;     /* erase word character */
        Out = fileno(stdout);
        ttybuf = savtty;
#ifdef  u370
        ttybuf.c_cflag &= ~PARENB;      /* disable parity */
        ttybuf.c_cflag |= CS8;          /* character size = 8 */
#endif  /* u370 */
        ttybuf.c_cc[VTIME] = 0;
        ttybuf.c_cc[VMIN] = 1;
        ttybuf.c_iflag &= ~(BRKINT);
        ttybuf.c_lflag &= ~(ICANON|ISIG|ECHO);
#endif
        return 0;
}

#ifndef TIOCSTI
static int
setty(void)
{
        if (ioctl(Out, TCSETAW, &ttybuf) < 0) {
                perror("ioctl");
                return(-1);
        }
        return(0);
}

static void
resetty(void)
{
        if (ioctl(Out, TCSETAW, &savtty) < 0)
                perror("ioctl");
}

static void
outstr(register char *s)
{
        while (*s)
                Echo(*s++);
}

static void
rubout(register char *cp)
{
        register int oldcol;
        register int c = *cp;

        erasing = 1;
        *cp = '\0';
        switch (c) {
        case '\t':
                oldcol = countcol();
                do
                        putchar('\b');
                while (--Col > oldcol);
                break;
        case '\b':
                if (isprint(cp[-1]))
                        putchar(*(cp-1));
                else
                        putchar(' ');
                Col++;
                break;
        default:
                if (isprint(c)) {
                        fputs("\b \b", stdout);
                        Col--;
                }
        }
}

static int
countcol(void)
{
        register int col;
        register char *s;

        for (col=Pcol, s=canonb; *s; s++)
                switch (*s) {
                case '\t':
                        while (++col % 8)
                                ;
                        break;
                case '\b':
                        col--;
                        break;
                default:
                        if (isprint(*s))
                                col++;
                }
        return(col);
}

static void
Echo(int cc)
{
        char c = (char)cc;

        switch (c) {
        case '\t':
                do
                        putchar(' ');
                while (++Col % 8);
                break;
        case '\b':
                if (Col > 0) {
                        putchar('\b');
                        Col--;
                }
                break;
        case '\r':
        case '\n':
                Col = 0;
                fputs("\r\n", stdout);
                break;
        default:
                if (isprint(c)) {
                        Col++;
                        putchar(c);
                }
        }
}
#endif

#else

#ifdef SIGCONT
static void     signull(int);
#endif

static  int     c_erase;                /* Current erase char */
static  int     c_kill;                 /* Current kill char */
static  int     hadcont;                /* Saw continue signal */
static  jmp_buf rewrite;                /* Place to go when continued */
#ifndef TIOCSTI
static  int     ttyset;                 /* We must now do erase/kill */
#endif

/*
 * Read all relevant header fields.
 */

int
grabh(struct header *hp, int gflags, int subjtop)
{
        struct sgttyb ttybuf;
        void (*savecont)(int);
        register int s;
        int errs;
#ifndef TIOCSTI
        void (*savesigs[2])(int);
#endif

#ifdef SIGCONT
        savecont = sigset(SIGCONT, signull);
#endif
        errs = 0;
#ifndef TIOCSTI
        ttyset = 0;
#endif
        if (gtty(fileno(stdin), &ttybuf) < 0) {
                perror("gtty");
                return(-1);
        }
        c_erase = ttybuf.sg_erase;
        c_kill = ttybuf.sg_kill;
#ifndef TIOCSTI
        ttybuf.sg_erase = 0;
        ttybuf.sg_kill = 0;
        for (s = SIGINT; s <= SIGQUIT; s++)
                if ((savesigs[s-SIGINT] = sigset(s, SIG_IGN)) == SIG_DFL)
                        sigset(s, SIG_DFL);
#endif
        if (gflags & GTO) {
#ifndef TIOCSTI
                if (!ttyset && hp->h_to != NOSTR)
                        ttyset++, stty(fileno(stdin), &ttybuf);
#endif
                hp->h_to = addto(NOSTR, readtty("To: ", hp->h_to));
                if (hp->h_to != NOSTR)
                        hp->h_seq++;
        }
        if (gflags & GSUBJECT && subjtop) {
#ifndef TIOCSTI
                if (!ttyset && hp->h_subject != NOSTR)
                        ttyset++, stty(fileno(stdin), &ttybuf);
#endif
                hp->h_subject = readtty("Subject: ", hp->h_subject);
                if (hp->h_subject != NOSTR)
                        hp->h_seq++;
        }
        if (gflags & GCC) {
#ifndef TIOCSTI
                if (!ttyset && hp->h_cc != NOSTR)
                        ttyset++, stty(fileno(stdin), &ttybuf);
#endif
                hp->h_cc = addto(NOSTR, readtty("Cc: ", hp->h_cc));
                if (hp->h_cc != NOSTR)
                        hp->h_seq++;
        }
        if (gflags & GBCC) {
#ifndef TIOCSTI
                if (!ttyset && hp->h_bcc != NOSTR)
                        ttyset++, stty(fileno(stdin), &ttybuf);
#endif
                hp->h_bcc = addto(NOSTR, readtty("Bcc: ", hp->h_bcc));
                if (hp->h_bcc != NOSTR)
                        hp->h_seq++;
        }
        if (gflags & GSUBJECT && !subjtop) {
#ifndef TIOCSTI
                if (!ttyset && hp->h_subject != NOSTR)
                        ttyset++, stty(fileno(stdin), &ttybuf);
#endif
                hp->h_subject = readtty("Subject: ", hp->h_subject);
                if (hp->h_subject != NOSTR)
                        hp->h_seq++;
        }
#ifdef SIGCONT
        sigset(SIGCONT, savecont);
#endif
#ifndef TIOCSTI
        ttybuf.sg_erase = c_erase;
        ttybuf.sg_kill = c_kill;
        if (ttyset)
                stty(fileno(stdin), &ttybuf);
        for (s = SIGINT; s <= SIGQUIT; s++)
                sigset(s, savesigs[s-SIGINT]);
#endif
        return(errs);
}

/*
 * Read up a header from standard input.
 * The source string has the preliminary contents to
 * be read.
 *
 */

char *
readtty(char pr[], char src[])
{
        char ch, canonb[LINESIZE];
        int c;
        register char *cp, *cp2;

        fputs(pr, stdout);
        fflush(stdout);
        if (src != NOSTR && strlen(src) > LINESIZE - 2) {
                printf(gettext("too long to edit\n"));
                return(src);
        }
#ifndef TIOCSTI
        if (src != NOSTR)
                cp = copy(src, canonb);
        else
                cp = copy("", canonb);
        fputs(canonb, stdout);
        fflush(stdout);
#else
        cp = src == NOSTR ? "" : src;
        while (c = *cp++) {
                if (c == c_erase || c == c_kill) {
                        ch = '\\';
                        ioctl(0, TIOCSTI, &ch);
                }
                ch = c;
                ioctl(0, TIOCSTI, &ch);
        }
        cp = canonb;
        *cp = 0;
#endif
        cp2 = cp;
        while (cp2 < canonb + LINESIZE)
                *cp2++ = 0;
        cp2 = cp;
        if (setjmp(rewrite))
                goto redo;
#ifdef SIGCONT
        sigset(SIGCONT, ttycont);
#endif
        clearerr(stdin);
        while (cp2 < canonb + LINESIZE) {
                c = getc(stdin);
                if (c == EOF || c == '\n')
                        break;
                *cp2++ = c;
        }
        *cp2 = 0;
#ifdef SIGCONT
        sigset(SIGCONT, signull);
#endif
        if (c == EOF && ferror(stdin) && hadcont) {
redo:
                hadcont = 0;
                cp = strlen(canonb) > 0 ? canonb : NOSTR;
                clearerr(stdin);
                return(readtty(pr, cp));
        }
        clearerr(stdin);
#ifndef TIOCSTI
        if (cp == NOSTR || *cp == '\0')
                return(src);
        cp2 = cp;
        if (!ttyset)
                return(strlen(canonb) > 0 ? savestr(canonb) : NOSTR);
        while (*cp != '\0') {
                c = *cp++;
                if (c == c_erase) {
                        if (cp2 == canonb)
                                continue;
                        if (cp2[-1] == '\\') {
                                cp2[-1] = c;
                                continue;
                        }
                        cp2--;
                        continue;
                }
                if (c == c_kill) {
                        if (cp2 == canonb)
                                continue;
                        if (cp2[-1] == '\\') {
                                cp2[-1] = c;
                                continue;
                        }
                        cp2 = canonb;
                        continue;
                }
                *cp2++ = c;
        }
        *cp2 = '\0';
#endif
        if (equal("", canonb))
                return(NOSTR);
        return(savestr(canonb));
}

#ifdef SIGCONT
/*
 * Receipt continuation.
 */
/*ARGSUSED*/
void
ttycont(int)
{

        hadcont++;
        longjmp(rewrite, 1);
}

/*
 * Null routine to allow us to hold SIGCONT
 */
/*ARGSUSED*/
static void
signull(int)
{}
#endif
#endif  /* USG_TTY */