#ifndef lint
static char Rcs_Id[] =
"$Id: term.c,v 1.57 2020-12-30 22:33:41-08 geoff Exp $";
#endif
/*
* term.c - deal with termcap, and unix terminal mode settings
*
* Pace Willisson, 1983
*
* Copyright 1987, 1988, 1989, 1992, 1993, 1999, 2001, 2005, 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. The code that causes the 'ispell -v' command to display a prominent
* link to the official ispell Web site may not be removed.
* 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.57 2020-12-30 22:33:41-08 geoff
* Get rid of an undeclared-function warning. This might not be
* backward-compatible to very old systems.
*
* Revision 1.56 2020-10-26 11:39:55-07 geoff
* Include header files needed for C99 function declarations. This probably
* breaks backwards compatibility but I don't have time to research it and
* add appropriate config.X options. Sigh.
*
* Revision 1.55 2015-02-08 00:35:41-08 geoff
* Add POSIX termios support (thanks to Christian Weisgerber for the patch).
*
* Revision 1.54 2005-04-14 16:11:36-07 geoff
* Correctly handle control-Z, including resetting the terminal. The
* only remaining problem is that the screen isn't automatically
* refreshed. (Doing the latter would either require major changes to
* make the screen-refresh code callable from the signal handler, or
* fixing GETKEYSTROKE to fail on signals. That's probably not hugely
* hard but doing it portably probably is.)
*
* Revision 1.53 2005/04/14 14:38:23 geoff
* Update license. Rename move/erase to avoid library conflicts.
*
* Revision 1.52 2001/09/06 00:30:28 geoff
* Many changes from Eli Zaretskii to support DJGPP compilation.
*
* 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"
#if defined(TERMIOS)
#include <termios.h>
#elif defined(USG)
#include <termio.h>
#else
#ifndef __DJGPP__
#include <sgtty.h>
#endif
#endif
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
void ierase P ((void));
void imove P ((int row, int col));
void inverse P ((void));
void normal P ((void));
void backup P ((void));
static int iputch 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 */
static int termchanged = 0;
#ifdef __DJGPP__
#include "pc/djterm.c"
#endif
void ierase ()
{
if (cl)
tputs (cl, li, iputch);
else
{
if (ho)
tputs (ho, 100, iputch);
else if (cm)
tputs (tgoto (cm, 0, 0), 100, iputch);
tputs (cd, li, iputch);
}
}
void imove (row, col)
int row;
int col;
{
tputs (tgoto (cm, col, row), 100, iputch);
}
void inverse ()
{
tputs (so, 10, iputch);
}
void normal ()
{
tputs (se, 10, iputch);
}
void backup ()
{
if (BC)
tputs (BC, 1, iputch);
else
(void) putchar ('\b');
}
static int iputch (c)
int c;
{
return putchar (c);
}
#if defined(TERMIOS)
static struct termios sbuf;
static struct termios osbuf;
#elif defined(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 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 */
#if defined(TERMIOS)
if (!isatty (0))
{
(void) fprintf (stderr, TERM_C_NO_BATCH);
exit (1);
}
(void) tcgetattr (0, &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) tcsetattr (0, TCSADRAIN, &sbuf);
uerasechar = osbuf.c_cc[VERASE];
ukillchar = osbuf.c_cc[VKILL];
#elif defined(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
#if !defined(TERMIOS) && !defined(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 *) <c);
#endif
#endif /* TERMIOS && 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, iputch);
}
/* ARGSUSED */
SIGNAL_TYPE done (signo)
int signo;
{
if (tempfile[0] != '\0')
(void) unlink (tempfile);
if (termchanged)
{
if (te)
tputs (te, 1, iputch);
#if defined(TERMIOS)
(void) tcsetattr (0, TCSADRAIN, &osbuf);
#elif defined(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;
{
if (termchanged)
{
imove (li - 1, 0);
if (te)
tputs (te, 1, iputch);
#if defined(TERMIOS)
(void) tcsetattr (0, TCSADRAIN, &osbuf);
#elif defined(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) fflush (stdout);
(void) signal (signo, SIG_DFL);
#ifndef USG
(void) sigsetmask (sigblock (0) & ~(1 << (signo - 1)));
#endif
(void) kill (0, SIGSTOP);
/* stop here until continued */
(void) signal (signo, onstop);
if (termchanged)
{
#if defined(TERMIOS)
(void) tcsetattr (0, TCSADRAIN, &sbuf);
#elif defined(USG)
(void) ioctl (0, TCSETAW, (char *) &sbuf);
#else
(void) ioctl (0, TIOCSETP, (char *) &sbuf);
#ifdef TIOCSLTC
(void) ioctl (0, TIOCSLTC, (char *) <c);
#endif
#endif
if (ti)
tputs (ti, 1, iputch);
}
}
#endif
#ifndef USESH
#define NEED_SHELLESCAPE
#endif /* USESH */
#ifndef REGEX_LOOKUP
#define NEED_SHELLESCAPE
#endif /* REGEX_LOOKUP */
void stop ()
{
#ifdef SIGTSTP
onstop (SIGTSTP);
#else
/* for System V and MSDOS */
imove (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
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;
#if defined(TERMIOS)
(void) tcsetattr (0, TCSADRAIN, &osbuf);
#elif defined(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, MAYBE_CR (stderr));
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
#if defined(TERMIOS)
(void) tcsetattr (0, TCSADRAIN, &sbuf);
#elif defined(USG)
(void) ioctl (0, TCSETAW, (char *) &sbuf);
#else
(void) ioctl (0, TIOCSETP, (char *) &sbuf);
#ifdef TIOCSLTC
(void) ioctl (0, TIOCSLTC, (char *) <c);
#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 __DJGPP__
char curdir[MAXPATHLEN];
#endif
#if defined(TERMIOS)
(void) tcsetattr (0, TCSADRAIN, &osbuf);
#elif defined(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__
/* Don't erase the screen if they want to run a single command,
* otherwise they will be unable to see its output.
*/
if (buf[0] == '\0')
djgpp_restore_screen ();
/* Change and restore the current directory, because it's a global
* notion on MS-DOS/MS-Windows.
*/
getcwd (curdir, MAXPATHLEN);
#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 __DJGPP__
if (buf[0] == '\0')
djgpp_ispell_screen ();
chdir (curdir);
#endif
#if defined(TERMIOS)
(void) tcsetattr (0, TCSADRAIN, &sbuf);
#elif defined(USG)
(void) ioctl (0, TCSETAW, (char *) &sbuf);
#else
(void) ioctl (0, TIOCSETP, (char *) &sbuf);
#ifdef TIOCSLTC
(void) ioctl (0, TIOCSLTC, (char *) <c);
#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