#ifndef lint
static char Rcs_Id[] =
    "$Id: term.c,v 1.51 2001/07/25 21:51:46 geoff Exp $";
#endif

/*
 * term.c - deal with termcap, and unix terminal mode settings
 *
 * Pace Willisson, 1983
 *
 * Copyright 1987, 1988, 1989, 1992, 1993, 1999, 2001, Geoff Kuenning,
 * Claremont, CA.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All modifications to the source code must be clearly marked as
 *    such.  Binary redistributions based on modified source code
 *    must be clearly marked as modified versions in the documentation
 *    and/or other materials provided with the distribution.
 * 4. Any web site or other electronic service that offers ispell for
 *    download or other electronic transfer as a separate entity, in
 *    either source or binary form, must also include a prominent statement
 *    indicating that information about ispell can be obtained from the
 *    following Web site URL:
 *	http://fmg-www.cs.ucla.edu/geoff/ispell.html
 *    If the offering service supports hyperlinks, the aforementioned
 *    Web site must also be offered as a hyperlink.  Condition #4 does
 *    not apply if ispell is offered only as part of a larger, aggregated
 *    product such as a word processor or packaged operating system.
 * 5. The name of Geoff Kuenning may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GEOFF KUENNING AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL GEOFF KUENNING OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * $Log: term.c,v $
 * Revision 1.51  2001/07/25 21:51:46  geoff
 * Minor license update.
 *
 * Revision 1.50  2001/07/23 20:24:04  geoff
 * Update the copyright and the license.
 *
 * Revision 1.49  1999/01/07 01:22:53  geoff
 * Update the copyright.
 *
 * Revision 1.48  1994/10/25  05:46:11  geoff
 * Fix a couple of places where ifdefs were omitted, though apparently
 * harmlessly.
 *
 * Revision 1.47  1994/09/01  06:06:32  geoff
 * Change erasechar/killchar to uerasechar/ukillchar to avoid
 * shared-library problems on HP systems.
 *
 * Revision 1.46  1994/01/25  07:12:11  geoff
 * Get rid of all old RCS log lines in preparation for the 3.1 release.
 *
 */

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

void		erase P ((void));
void		move P ((int row, int col));
void		inverse P ((void));
void		normal P ((void));
void		backup P ((void));
static int	putch P ((int c));
void		terminit P ((void));
SIGNAL_TYPE	done P ((int signo));
#ifdef SIGTSTP
static SIGNAL_TYPE onstop P ((int signo));
#endif /* SIGTSTP */
void		stop P ((void));
int		shellescape P ((char * buf));
#ifdef USESH
void		shescape P ((char * buf));
#endif /* USESH */

void 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);
	}
    }

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

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

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

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

static int putch (c)
    int			c;
    {

    return putchar (c);
    }

#ifdef USG
static struct termio	sbuf;
static struct termio	osbuf;
#else
static struct sgttyb	sbuf;
static struct sgttyb	osbuf;
#ifdef TIOCSLTC
static struct ltchars	ltc;
static 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

void terminit ()
    {
#ifdef TIOCPGRP
    int			tpgrp;
#else
#ifdef TIOCGPGRP
    int			tpgrp;
#endif
#endif
#ifdef TIOCGWINSZ
    struct winsize	wsize;
#endif /* TIOCGWINSZ */

    tgetent (termcap, getenv ("TERM"));
    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, (char *) &wsize) >= 0)
	{
	if (wsize.ws_col != 0)
	    co = wsize.ws_col;
	if (wsize.ws_row != 0)
	    li = wsize.ws_row;
	}
#endif /* TIOCGWINSZ */
    /*
     * 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, (char *) &osbuf);
    termchanged = 1;

    sbuf = osbuf;
    sbuf.c_lflag &= ~(ECHO | ECHOK | ECHONL | ICANON);
    sbuf.c_oflag &= ~(OPOST);
    sbuf.c_iflag &= ~(INLCR | IGNCR | ICRNL);
    sbuf.c_cc[VMIN] = 1;
    sbuf.c_cc[VTIME] = 1;
    (void) ioctl (0, TCSETAW, (char *) &sbuf);

    uerasechar = osbuf.c_cc[VERASE];
    ukillchar = 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, (char *) &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, (char *) &osbuf);
#ifdef TIOCGLTC
    (void) ioctl (0, TIOCGLTC, (char *) &oltc);
#endif
    termchanged = 1;

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

    uerasechar = sbuf.sg_erase;
    ukillchar = sbuf.sg_kill;

#ifdef TIOCSLTC
    ltc = oltc;
    ltc.t_suspc = -1;
    (void) ioctl (0, TIOCSLTC, (char *) &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);
    }

/* ARGSUSED */
SIGNAL_TYPE done (signo)
    int		signo;
    {
    if (tempfile[0] != '\0')
	(void) unlink (tempfile);
    if (termchanged)
	{
	if (te)
	    tputs (te, 1, putch);
#ifdef USG
	(void) ioctl (0, TCSETAW, (char *) &osbuf);
#else
	(void) ioctl (0, TIOCSETP, (char *) &osbuf);
#ifdef TIOCSLTC
	(void) ioctl (0, TIOCSLTC, (char *) &oltc);
#endif
#endif
	}
    exit (0);
    }

#ifdef SIGTSTP
static SIGNAL_TYPE onstop (signo)
    int		signo;
    {
#ifdef USG
    (void) ioctl (0, TCSETAW, (char *) &osbuf);
#else
    (void) ioctl (0, TIOCSETP, (char *) &osbuf);
#ifdef TIOCSLTC
    (void) ioctl (0, TIOCSLTC, (char *) &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, (char *) &sbuf);
#else
    (void) ioctl (0, TIOCSETP, (char *) &sbuf);
#ifdef TIOCSLTC
    (void) ioctl (0, TIOCSLTC, (char *) &ltc);
#endif
#endif
    }
#endif

void 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.
*/
#ifndef USESH
#define NEED_SHELLESCAPE
#endif /* USESH */
#ifndef REGEX_LOOKUP
#define NEED_SHELLESCAPE
#endif /* REGEX_LOOKUP */
#ifdef NEED_SHELLESCAPE
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, (char *) &osbuf);
#else
    (void) ioctl (0, TIOCSETP, (char *) &osbuf);
#ifdef TIOCSLTC
    (void) ioctl (0, TIOCSLTC, (char *) &oltc);
#endif /* TIOCSLTC */
#endif
    (void) signal (SIGINT, oldint);
    (void) signal (SIGTERM, oldterm);
#ifdef SIGTSTP
    (void) signal (SIGTTIN, oldttin);
    (void) signal (SIGTTOU, oldttou);
    (void) signal (SIGTSTP, oldtstp);
#endif
    if ((i = fork ()) == 0)
	{
	(void) execvp (argv[0], (char **) 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 */
	}

    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, (char *) &sbuf);
#else
    (void) ioctl (0, TIOCSETP, (char *) &sbuf);
#ifdef TIOCSLTC
    (void) ioctl (0, TIOCSLTC, (char *) &ltc);
#endif /* TIOCSLTC */
#endif
    if (termstat)
	{
	(void) printf (TERM_C_TYPE_SPACE);
	(void) fflush (stdout);
#ifdef COMMANDFORSPACE
	i = GETKEYSTROKE ();
	if (i != ' ' && i != '\n' && i != '\r')
	    (void) ungetc (i, stdin);
#else
	while (GETKEYSTROKE () != ' ')
	    ;
#endif
	}
    return (termstat);
    }
#endif /* NEED_SHELLESCAPE */

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

#ifdef USG
    (void) ioctl (0, TCSETAW, (char *) &osbuf);
#else
    (void) ioctl (0, TIOCSETP, (char *) &osbuf);
#ifdef TIOCSLTC
    (void) ioctl (0, TIOCSLTC, (char *) &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, (char *) &sbuf);
#else
    (void) ioctl (0, TIOCSETP, (char *) &sbuf);
#ifdef TIOCSLTC
    (void) ioctl (0, TIOCSLTC, (char *) &ltc);
#endif
#endif
    (void) printf (TERM_C_TYPE_SPACE);
    (void) fflush (stdout);
#ifdef COMMANDFORSPACE
    ch = GETKEYSTROKE ();
    if (ch != ' '  &&  ch != '\n'  &&  ch != '\r')
	(void) ungetc (ch, stdin);
#else
    while (GETKEYSTROKE () != ' ')
	;
#endif
    }
#endif
