/*
 * term.c - deal with termcap, and unix terminal mode settings
 *
 * Pace Willisson, 1983
 *
 * Copyright 1987, 1988, 1989, 1992, 1993, Geoff Kuenning, Granada Hills, 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *      This product includes software developed by Geoff Kuenning and
 *      other unpaid contributors.
 * 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.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 "ispell.h"
#include <stdlib.h>
#include "msgs.h"
#ifdef USG
#include <termio.h>
#else
#ifndef __DJGPP__
#include <sgtty.h>
#endif
#endif
#include <signal.h>

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

Local Logical	termchanged = False;

#ifdef __DJGPP__
#include "pc/djterm.c"
#endif

Public void 
erase ()
{
#ifdef __DJGPP__
    clrscr ();
#else

    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);
	}
#endif /* __DJGPP__ */
}

Public void 
move (
    int		row,
    int		col)
{
#ifdef __DJGPP__
    fflush (stdout);
    gotoxy (col + 1, row + 1);
#else
    tputs (tgoto (cm, col, row), 100, putch);
#endif
}

Public void 
inverse ()
{
#ifdef __DJGPP__
    fflush (stdout);
    textattr (ispell_sout_attr);
#else
    tputs (so, 10, putch);
#endif
}

Public void 
normal ()
{
#ifdef __DJGPP__
    fflush (stdout);
    textattr (ispell_norm_attr);
#else
    tputs (se, 10, putch);
#endif
}

Public void 
backup ()
{
#ifndef __DJGPP__
    if (BC)
	tputs (BC, 1, putch);
    else
#endif
    (void) putchar ('\b');
}

#ifndef __DJGPP__
Local int 
putch (int c)
{

    return putchar (c);
}
#endif

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

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

#ifdef __DJGPP__
    djgpp_init_terminal ();
#else  /* ! __DJGPP__ */
    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");
#endif /* ! __DJGPP__ */
#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 = True;

    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 = True;

    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
#ifndef __DJGPP__
    if (ti)
	tputs (ti, 1, putch);
#else
    djgpp_ispell_screen ();
    erase ();
#endif
}

/* ARGSUSED */
Public SIGNAL_TYPE 
done ( int		signo)
{
    if (tempfile[0] != '\0')
	(void) unlink (tempfile);
    if (termchanged)
	{
#ifndef __DJGPP__
	if (te)
	    tputs (te, 1, putch);
#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
#ifdef __DJGPP__
	djgpp_restore_screen ();
	djgpp_deinit_term ();
#endif /* __DJGPP__ */
	}
    exit (0);
}

#ifdef SIGTSTP
Local SIGNAL_TYPE 
onstop (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

#ifndef USESH
#define NEED_SHELLESCAPE
#endif /* USESH */
#ifndef REGEX_LOOKUP
#define NEED_SHELLESCAPE
#endif /* REGEX_LOOKUP */

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

/* 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.
*/
#ifdef NEED_SHELLESCAPE
Public int 
shellescape	( 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
Public void shescape (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

#ifdef __DJGPP__
    djgpp_restore_screen ();
#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

#ifdef __DJGPP__
    djgpp_ispell_screen ();
#endif
}
#endif
