/* -*- Mode:Text -*- */
#ifndef lint
static char Rcs_Id[] =
    "$Id: term.c,v 1.37 1991/12/09 00:40:48 geoff Exp $";
#endif

/*
 * term.c - deal with termcap, and unix terminal mode settings
 *
 * Pace Willisson, 1983
 *
 * Copyright 1987, 1988, 1989, by Geoff Kuenning, Manhattan Beach, CA
 * Permission for non-profit use is hereby granted.
 * All other rights reserved.
 * See "version.h" for a more complete copyright notice.
 */

/*
 * $Log: term.c,v $
 * Revision 1.37  1991/12/09  00:40:48  geoff
 * Don't unlink the tempfile on exit if it has a null name.
 *
 * Revision 1.36  91/09/04  18:00:57  geoff
 * Move the include of config.h.  Since it now contains the definition of
 * USG, it must precede any use of that option.
 * 
 * Revision 1.35  91/07/27  20:48:37  geoff
 * Don't obey the screen size from TIOCGWINSZ if it's zero or negative.
 * 
 * Revision 1.34  91/07/11  19:52:24  geoff
 * Remove the include of stdio.h, since ispell.h now does this.  Change
 * the termination code to put out the te sequence, rather than ti.
 * 
 * Revision 1.33  91/07/05  19:51:55  geoff
 * Fix some lint complaints, including getting rid of a couple of unused
 * variables.
 * 
 * Revision 1.32  91/07/03  18:21:09  geoff
 * Add code to support the "ti" and "te" termcap sequences, since the Sun
 * cmdtool (or is it shelltool) needs it to function properly with ispell.
 * 
 * Revision 1.31  90/12/31  00:59:29  geoff
 * Reformat to follow a consistent convention throughout ispell
 * 
 * Revision 1.30  90/04/26  22:44:25  geoff
 * If it is available, use the TIOCGWINSZ ioctl to determine the dimensions
 * of the terminal.  The enviroment variables LINES and COLUMNS can still
 * be used to override this ioctl.
 * 
 * Revision 1.29  90/04/17  15:35:07  geoff
 * Declare the signal-handling routines to be SIGNAL_TYPE
 * 
 * Revision 1.28  89/12/27  03:18:43  geoff
 * Move all messages to msgs.h so they can be reconfigured
 * 
 * Revision 1.27  89/12/26  23:26:32  geoff
 * Change the signal type to be definable (Israel Pinkas).
 * 
 * Revision 1.26  89/04/28  01:17:17  geoff
 * Change Header to Id;  nobody cares about my pathnames.
 * 
 * Revision 1.25  89/04/03  01:58:34  geoff
 * Fix a bunch of lint complaints.  Add code to let LINES and COLUMNS
 * override the screen size given by the termcap entry.  Rearrange some
 * code so that signal-catching and terminal setting is done only after
 * we are sure the termcap entry is ok.
 * 
 * Revision 1.24  89/02/27  02:23:48  geoff
 * Fix the interpretation of CONTEXTROUNDUP (it was inverted from what
 * the comments in config.X said).
 * 
 * Revision 1.23  89/02/22  23:16:43  geoff
 * Add support for calculating "contextsize" based on the terminal size.
 * 
 * Revision 1.22  89/01/06  00:14:47  geoff
 * Add support for MAX_SCREEN_SIZE (Keith Cantrell).
 * 
 * Revision 1.21  88/12/26  02:32:39  geoff
 * Add a copyright notice.
 * 
 * Revision 1.20  88/11/16  02:20:35  geoff
 * Make sure "tpgrp" is defined if TIOCGPGRP is defined but not TIOCPGRP
 * (a Locus-ism).
 * 
 * Revision 1.19  88/10/20  20:53:09  geoff
 * If a shell escape fails, don't flush stdio buffers on exit from the child.
 * (Ole Bjoern Hessen).
 * 
 * Revision 1.18  88/04/30  22:15:45  geoff
 * Fix some lint complaints, and get rid of some unused variables found
 * by lint.
 * 
 * Revision 1.17  88/03/27  01:05:19  geoff
 * Fix the #ifdefs to handle USG-like systems that nevertheless have job
 * control.
 * 
 * Revision 1.16  87/09/03  23:17:42  geoff
 * Integrate George Sipe's latest changes:  make ^Z spawn a shell on USG.
 * Add the shellescape() routine to handle simple shell commands faster.
 * Also clean up signal handling a bit.
 * 
 * Revision 1.15  87/09/03  17:32:29  geoff
 * Add ifdefs to make it compile on V7
 * 
 * Revision 1.14  87/09/03  17:09:09  geoff
 * Catch signals on USG systems, too (imt3b2!imtsft)
 * 
 * Revision 1.13  87/06/27  12:20:54  geoff
 * Get the sg termcap value for braindamaged terminals (Michael Wester).
 * 
 * Revision 1.12  87/06/07  14:31:12  geoff
 * Integrate Joel Shprentz's changes into the main branch
 * 
 * Revision 1.11  87/05/25  21:24:34  geoff
 * Integrate Mark Davies' changes, and improve some signal catching code.
 * 
 * Revision 1.10  87/04/21  23:29:20  geoff
 * Only catch signals if they aren't ignored;  this makes background stuff
 * work properly.
 * 
 * Revision 1.9  87/04/19  22:53:44  geoff
 * Swap the includes of ispell.h and config.h so config.h can affect ispell.h.
 * 
 * Revision 1.8  87/04/02  12:26:10  geoff
 * Make stdout buffered.
 * 
 * Revision 1.7  87/04/01  15:22:58  geoff
 * Integrate Joe Orost's V7/register changes into the main branch
 * 
 * Revision 1.6  87/03/22  23:21:44  geoff
 * Integrate Perry Smith's changes into the main branch
 * 
 * Revision 1.5  87/02/26  00:22:48  geoff
 * Integrate McQueer's and McMahon's enhancements into the main branch
 * 
 * Revision 1.4  87/01/19  00:25:19  geoff
 * Prohibit non-interactive use on USG systems too
 * 
 * Revision 1.3  87/01/19  00:07:00  geoff
 * Modify to work on USG systems.
 * 
 * Revision 1.2  87/01/17  13:12:11  geoff
 * Add RCS ID keywords
 * 
 */

#include "config.h"
#ifdef USG
#include <termio.h>
#else
#include <sgtty.h>
#endif
#include <signal.h>
#include <stdio.h>
#include "ispell.h"
#include "msgs.h"

extern char *	tgoto ();

extern void	exit ();
extern void	_exit ();
extern char *	getenv ();

int putch();

erase ()
    {

    if (cl)
	tputs (cl, li, putch);
    else
	{
	if (ho)
	    tputs (ho, 100, putch);
	else if (cm)
	    tputs (tgoto (cm, 0, 0), 100, putch);
	tputs (cd, li, putch);
	}
    }

move (row, col)
    {
    tputs (tgoto (cm, col, row), 100, putch);
    }

inverse ()
    {
    tputs (so, 10, putch);
    }

normal ()
    {
    tputs (se, 10, putch);
    }

backup ()
    {
    if (BC)
	tputs (BC, 1, putch);
    else
	(void) putchar ('\b');
    }

putch (c)
    {
    (void) putchar (c);
    }

#ifdef USG
struct termio		sbuf;
struct termio		osbuf;
#else
struct sgttyb		sbuf;
struct sgttyb		osbuf;
#ifdef TIOCSLTC
struct ltchars		ltc;
struct ltchars		oltc;
#endif
#endif
static int		termchanged = 0;
static SIGNAL_TYPE	(*oldint) ();
static SIGNAL_TYPE	(*oldterm) ();
#ifdef SIGTSTP
static SIGNAL_TYPE	(*oldttin) ();
static SIGNAL_TYPE	(*oldttou) ();
static SIGNAL_TYPE	(*oldtstp) ();
#endif

terminit ()
    {
#ifdef OS2
    int			dst[2];
    int                 tgstat;
#endif  /* OS2 */ 
#ifdef TIOCPGRP
    int			tpgrp;
#else
#ifdef TIOCGPGRP
    int			tpgrp;
#endif
#endif
#ifdef SIGTSTP
    SIGNAL_TYPE		onstop ();
#endif
#ifdef TIOCGWINSZ
    struct winsize	wsize;
#endif /* TIOCGWINSZ */

#ifdef OS2      /* check if termcap.dat is found and TERM is valid */
    if ( (tgstat = tgetent (termcap, getenv ("TERM") ) ) == -1 )
        {
        (void) fprintf (stderr, TERM_C_NO_TERMCAP);
        exit();
        }
    else if (tgstat == 0)
        {
        (void) fprintf (stderr, TERM_C_INVALID_TERM, getenv ("TERM"));
        exit();
        }
#else
    tgetent (termcap, getenv ("TERM"));
#endif  /* OS2 */
    termptr = termstr;
    BC = tgetstr ("bc", &termptr);
    cd = tgetstr ("cd", &termptr);
    cl = tgetstr ("cl", &termptr);
    cm = tgetstr ("cm", &termptr);
    ho = tgetstr ("ho", &termptr);
    nd = tgetstr ("nd", &termptr);
    so = tgetstr ("so", &termptr);	/* inverse video on */
    se = tgetstr ("se", &termptr);	/* inverse video off */
    if ((sg = tgetnum ("sg")) < 0)	/* space taken by so/se */
	sg = 0;
    ti = tgetstr ("ti", &termptr);	/* terminal initialization */
    te = tgetstr ("te", &termptr);	/* terminal termination */
    co = tgetnum ("co");
    li = tgetnum ("li");
#ifdef TIOCGWINSZ
    if (ioctl (0, TIOCGWINSZ, &wsize) >= 0)
	{
	if (wsize.ws_col > 0)
	    co = wsize.ws_col;
	if (wsize.ws_row > 0)
	    li = wsize.ws_row;
	}
#endif /* TIOCGWINSZ */

#ifdef OS2      /* get the OS/2 window size */
        _scrsize(dst);
        co = dst[0];
        li = dst[1];
#endif  /* OS2 */


    /*
     * Let the variables "LINES" and "COLUMNS" override the termcap
     * entry.  Technically, this is a terminfo-ism, but I think the
     * vast majority of users will find it pretty handy.
     */
    if (getenv ("COLUMNS") != NULL)
	co = atoi (getenv ("COLUMNS"));
    if (getenv ("LINES") != NULL)
	li = atoi (getenv ("LINES"));
#if MAX_SCREEN_SIZE > 0
    if (li > MAX_SCREEN_SIZE)
	li = MAX_SCREEN_SIZE;
#endif /* MAX_SCREEN_SIZE > 0 */
#if MAXCONTEXT == MINCONTEXT
    contextsize = MINCONTEXT;
#else /* MAXCONTEXT == MINCONTEXT */
    if (contextsize == 0)
#ifdef CONTEXTROUNDUP
	contextsize = (li * CONTEXTPCT + 99) / 100;
#else /* CONTEXTROUNDUP */
	contextsize = (li * CONTEXTPCT) / 100;
#endif /* CONTEXTROUNDUP */
    if (contextsize > MAXCONTEXT)
	contextsize = MAXCONTEXT;
    else if (contextsize < MINCONTEXT)
	contextsize = MINCONTEXT;
#endif /* MAX_CONTEXT == MIN_CONTEXT */
    /*
     * Insist on 2 lines for the screen header, 2 for blank lines
     * separating areas of the screen, 2 for word choices, and 2 for
     * the minimenu, plus however many are needed for context.  If
     * possible, make the context smaller to fit on the screen.
     */
    if (li < contextsize + 8  &&  contextsize > MINCONTEXT)
	{
	contextsize = li - 8;
	if (contextsize < MINCONTEXT)
	    contextsize = MINCONTEXT;
	}
    if (li < MINCONTEXT + 8)
	(void) fprintf (stderr, TERM_C_SMALL_SCREEN, MINCONTEXT + 8);

#ifdef SIGTSTP
#ifdef TIOCPGRP
retry:
#endif /* SIGTSTP */
#endif /* TIOCPGRP */

#ifdef USG
    if (!isatty (0))
	{
	(void) fprintf (stderr, TERM_C_NO_BATCH);
	exit (1);
	}
    (void) ioctl (0, TCGETA, &osbuf);
    termchanged = 1;

    sbuf = osbuf;
#ifdef OS2      /* IDEFAULT added for os/2 -- jbh  8/15/92 */
    sbuf.c_lflag &= ~(ECHO | ECHOK | ECHONL | ICANON | IDEFAULT);
#else
    sbuf.c_lflag &= ~(ECHO | ECHOK | ECHONL | ICANON);
#endif  /* OS2 */

    sbuf.c_oflag &= ~(OPOST);
    sbuf.c_iflag &= ~(INLCR | IGNCR | ICRNL);
    sbuf.c_cc[VMIN] = 1;
    sbuf.c_cc[VTIME] = 1;
    (void) ioctl (0, TCSETAW, &sbuf);

    erasechar = osbuf.c_cc[VERASE];
    killchar = osbuf.c_cc[VKILL];

#endif

#ifdef SIGTSTP
#ifndef USG
    (void) sigsetmask (1<<(SIGTSTP-1) | 1<<(SIGTTIN-1) | 1<<(SIGTTOU-1));
#endif
#endif
#ifdef TIOCGPGRP
    if (ioctl (0, TIOCGPGRP, &tpgrp) != 0)
	{
	(void) fprintf (stderr, TERM_C_NO_BATCH);
	exit (1);
	}
#endif
#ifdef SIGTSTP
#ifdef TIOCPGRP
    if (tpgrp != getpgrp(0)) /* not in foreground */
	{
#ifndef USG
	(void) sigsetmask (1 << (SIGTSTP - 1) | 1 << (SIGTTIN - 1));
#endif
	(void) signal (SIGTTOU, SIG_DFL);
	(void) kill (0, SIGTTOU);
	/* job stops here waiting for SIGCONT */
	goto retry;
	}
#endif
#endif

#ifndef USG
    (void) ioctl (0, TIOCGETP, &osbuf);
#ifdef TIOCGLTC
    (void) ioctl (0, TIOCGLTC, &oltc);
#endif
    termchanged = 1;

    sbuf = osbuf;
    sbuf.sg_flags &= ~ECHO;
    sbuf.sg_flags |= TERM_MODE;
    (void) ioctl (0, TIOCSETP, &sbuf);

    erasechar = sbuf.sg_erase;
    killchar = sbuf.sg_kill;

#ifdef TIOCSLTC
    ltc = oltc;
    ltc.t_suspc = -1;
    (void) ioctl (0, TIOCSLTC, &ltc);
#endif

#endif /* USG */

    if ((oldint = signal (SIGINT, SIG_IGN)) != SIG_IGN)
	(void) signal (SIGINT, done);
    if ((oldterm = signal (SIGTERM, SIG_IGN)) != SIG_IGN)
	(void) signal (SIGTERM, done);

#ifdef SIGTSTP
#ifndef USG
    (void) sigsetmask (0);
#endif
    if ((oldttin = signal (SIGTTIN, SIG_IGN)) != SIG_IGN)
	(void) signal (SIGTTIN, onstop);
    if ((oldttou = signal (SIGTTOU, SIG_IGN)) != SIG_IGN)
	(void) signal (SIGTTOU, onstop);
    if ((oldtstp = signal (SIGTSTP, SIG_IGN)) != SIG_IGN)
	(void) signal (SIGTSTP, onstop);
#endif
    if (ti)
	tputs (ti, 1, putch);
    }

SIGNAL_TYPE done ()
    {
    if (tempfile[0] != '\0')
#ifdef OS2
        {
        fcloseall();    /* OS/2 won't unlink an open file */
	(void) unlink (tempfile); 
        }
#else  /* OS2 */
	(void) unlink (tempfile);
#endif  /* OS2 */

    if (termchanged)
	{
	if (te)
	    tputs (te, 1, putch);
#ifdef USG
	(void) ioctl (0, TCSETAW, &osbuf);
#else
	(void) ioctl (0, TIOCSETP, &osbuf);
#ifdef TIOCSLTC
	(void) ioctl (0, TIOCSLTC, &oltc);
#endif
#endif
	}
#ifdef OS2      /* returns screen to the original color */
    erase ();
#endif  /* OS2 */
    exit (0);
    }

#ifdef SIGTSTP
SIGNAL_TYPE onstop (signo)
    int		signo;
    {
#ifdef USG
    (void) ioctl (0, TCSETAW, &osbuf);
#else
    (void) ioctl (0, TIOCSETP, &osbuf);
#ifdef TIOCSLTC
    (void) ioctl (0, TIOCSLTC, &oltc);
#endif
#endif
    (void) signal (signo, SIG_DFL);
#ifndef USG
    (void) sigsetmask (sigblock (0) & ~(1 << (signo - 1)));
#endif
    (void) kill (0, signo);
    /* stop here until continued */
    (void) signal (signo, onstop);
#ifdef USG
    (void) ioctl (0, TCSETAW, &sbuf);
#else
    (void) ioctl (0, TIOCSETP, &sbuf);
#ifdef TIOCSLTC
    (void) ioctl (0, TIOCSLTC, &ltc);
#endif
#endif
    }
#endif

stop ()
    {
#ifdef SIGTSTP
    onstop (SIGTSTP);
#else
    /* for System V */
    move (li - 1, 0);
    (void) fflush (stdout);
    if (getenv ("SHELL"))
	(void) shellescape (getenv ("SHELL"));
    else
	(void) shellescape ("sh");
#endif
    }

/* Fork and exec a process.  Returns NZ if command found, regardless of
** command's return status.  Returns zero if command was not found.
** Doesn't use a shell.
*/
int shellescape	(buf)
    char *	buf;
    {
    char *	argv[100];
    char *	cp = buf;
    int		i = 0;
    int		termstat;

    /* parse buf to args (destroying it in the process) */
    while (*cp != '\0')
	{
	while (*cp == ' '  ||  *cp == '\t')
	    ++cp;
	if (*cp == '\0')
	    break;
	argv[i++] = cp;
	while (*cp != ' '  &&  *cp != '\t'  &&  *cp != '\0')
	    ++cp;
	if (*cp != '\0')
	    *cp++ = '\0';
	}
    argv[i] = NULL;

#ifdef USG
    (void) ioctl (0, TCSETAW, &osbuf);
#else
    (void) ioctl (0, TIOCSETP, &osbuf);
    (void) ioctl (0, TIOCSLTC, &oltc);
#endif
    (void) signal (SIGINT, oldint);
    (void) signal (SIGTERM, oldterm);
#ifdef SIGTSTP
    (void) signal (SIGTTIN, oldttin);
    (void) signal (SIGTTOU, oldttou);
    (void) signal (SIGTSTP, oldtstp);
#endif

/* ifdef OS2      no fork available under OS/2 - needs to be rewritted */
    /* (void) printf(TERM_C_NO_FORK); */
/* else */
    if ((i = fork ()) == 0)
	{
	(void) execvp (argv[0], argv);
	_exit (123);		/* Command not found */
	}
    else if (i > 0)
	{
	while (wait (&termstat) != i)
	    ;
	termstat = (termstat == (123 << 8)) ? 0 : -1;
	}
    else
	{
	(void) printf (TERM_C_CANT_FORK);
	termstat = -1;		/* Couldn't fork */
	}

/* endif   OS2 */


    if (oldint != SIG_IGN)
	(void) signal (SIGINT, done);
    if (oldterm != SIG_IGN)
	(void) signal (SIGTERM, done);

#ifdef SIGTSTP
    if (oldttin != SIG_IGN)
	(void) signal (SIGTTIN, onstop);
    if (oldttou != SIG_IGN)
	(void) signal (SIGTTOU, onstop);
    if (oldtstp != SIG_IGN)
	(void) signal (SIGTSTP, onstop);
#endif

#ifdef USG
    (void) ioctl (0, TCSETAW, &sbuf);
#else
    (void) ioctl (0, TIOCSETP, &sbuf);
    (void) ioctl (0, TIOCSLTC, &ltc);
#endif
    if (termstat)
	{
	(void) printf (TERM_C_TYPE_SPACE);
	(void) fflush (stdout);
#ifdef COMMANDFORSPACE
	i = getchar ();
	if (i != ' ' && i != '\n' && i != '\r')
	    ungetc (i, stdin);
#else
	while (getchar () != ' ')
	    ;
#endif
	}
    return (termstat);
    }

#ifdef	USESH
shescape (buf)
    char *	buf;
    {
#ifdef COMMANDFORSPACE
    int		ch;
#endif

#ifdef USG
    (void) ioctl (0, TCSETAW, &osbuf);
#else
    (void) ioctl (0, TIOCSETP, &osbuf);
#ifdef TIOCSLTC
    (void) ioctl (0, TIOCSLTC, &oltc);
#endif
#endif
    (void) signal (SIGINT, oldint);
    (void) signal (SIGTERM, oldterm);
#ifdef SIGTSTP
    (void) signal (SIGTTIN, oldttin);
    (void) signal (SIGTTOU, oldttou);
    (void) signal (SIGTSTP, oldtstp);
#endif

    (void) system (buf);

    if (oldint != SIG_IGN)
	(void) signal (SIGINT, done);
    if (oldterm != SIG_IGN)
	(void) signal (SIGTERM, done);

#ifdef SIGTSTP
    if (oldttin != SIG_IGN)
	(void) signal (SIGTTIN, onstop);
    if (oldttou != SIG_IGN)
	(void) signal (SIGTTOU, onstop);
    if (oldtstp != SIG_IGN)
	(void) signal (SIGTSTP, onstop);
#endif

#ifdef USG
    (void) ioctl (0, TCSETAW, &sbuf);
#else
    (void) ioctl (0, TIOCSETP, &sbuf);
#ifdef TIOCSLTC
    (void) ioctl (0, TIOCSLTC, &ltc);
#endif
#endif
    (void) printf (TERM_C_TYPE_SPACE);
    (void) fflush (stdout);
#ifdef COMMANDFORSPACE
    ch = getchar ();
    if (ch != ' '  &&  ch != '\n'  &&  ch != '\r')
	ungetc (ch, stdin);
#else
    while (getchar () != ' ')
	;
#endif
    }
#endif
