/* $OpenBSD: ttyio.c,v 1.40 2021/03/20 09:00:49 lum Exp $ */
/* This file is in the public domain. */
/*
* POSIX terminal I/O.
*
* The functions in this file negotiate with the operating system for
* keyboard characters, and write characters to the display in a barely
* buffered fashion.
*/
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <term.h>
#include <termios.h>
#ifndef TCSASOFT
#define TCSASOFT 0
#endif
#include <unistd.h>
#include "def.h"
#define NOBUF 512 /* Output buffer size. */
int ttstarted;
char obuf[NOBUF]; /* Output buffer. */
size_t nobuf; /* Buffer count. */
struct termios oldtty; /* POSIX tty settings. */
struct termios newtty;
int nrow; /* Terminal size, rows. */
int ncol; /* Terminal size, columns. */
/*
* This function gets called once, to set up the terminal.
* On systems w/o TCSASOFT we turn off off flow control,
* which isn't really the right thing to do.
*/
void
ttopen(void)
{
if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO))
panic("standard input and output must be a terminal");
if (ttraw() == FALSE)
panic("aborting due to terminal initialize failure");
}
/*
* This function sets the terminal to RAW mode, as defined for the current
* shell. This is called both by ttopen() above and by spawncli() to
* get the current terminal settings and then change them to what
* mg expects. Thus, tty changes done while spawncli() is in effect
* will be reflected in mg.
*/
int
ttraw(void)
{
if (tcgetattr(0, &oldtty) == -1) {
dobeep();
ewprintf("ttopen can't get terminal attributes");
return (FALSE);
}
(void)memcpy(&newtty, &oldtty, sizeof(newtty));
/* Set terminal to 'raw' mode and ignore a 'break' */
newtty.c_cc[VMIN] = 1;
newtty.c_cc[VTIME] = 0;
newtty.c_iflag |= IGNBRK;
newtty.c_iflag &= ~(BRKINT | PARMRK | INLCR | IGNCR | ICRNL | IXON);
newtty.c_oflag &= ~OPOST;
newtty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
if (tcsetattr(0, TCSASOFT | TCSADRAIN, &newtty) == -1) {
dobeep();
ewprintf("ttopen can't tcsetattr");
return (FALSE);
}
ttstarted = 1;
return (TRUE);
}
/*
* This function gets called just before we go back home to the shell.
* Put all of the terminal parameters back.
* Under UN*X this just calls ttcooked(), but the ttclose() hook is in
* because vttidy() in display.c expects it for portability reasons.
*/
void
ttclose(void)
{
if (ttstarted) {
if (ttcooked() == FALSE)
panic(""); /* ttcooked() already printf'd */
ttstarted = 0;
}
}
/*
* This function restores all terminal settings to their default values,
* in anticipation of exiting or suspending the editor.
*/
int
ttcooked(void)
{
ttflush();
if (tcsetattr(0, TCSASOFT | TCSADRAIN, &oldtty) == -1) {
dobeep();
ewprintf("ttclose can't tcsetattr");
return (FALSE);
}
return (TRUE);
}
/*
* Write character to the display. Characters are buffered up,
* to make things a little bit more efficient.
*/
int
ttputc(int c)
{
if (nobuf >= NOBUF)
ttflush();
obuf[nobuf++] = c;
return (c);
}
/*
* Flush output.
*/
void
ttflush(void)
{
ssize_t written;
char *buf = obuf;
if (nobuf == 0 || batch == 1)
return;
while ((written = write(fileno(stdout), buf, nobuf)) != nobuf) {
if (written == -1) {
if (errno == EINTR)
continue;
panic("ttflush write failed");
}
buf += written;
nobuf -= written;
}
nobuf = 0;
}
/*
* Read character from terminal. All 8 bits are returned, so that you
* can use a multi-national terminal.
*/
int
ttgetc(void)
{
char c;
ssize_t ret;
do {
ret = read(STDIN_FILENO, &c, 1);
if (ret == -1 && errno == EINTR) {
if (winch_flag) {
redraw(0, 0);
winch_flag = 0;
}
} else if (ret == -1 && errno == EIO)
panic("lost stdin");
else if (ret == 1)
break;
} while (1);
return ((int) c) & 0xFF;
}
/*
* Returns TRUE if there are characters waiting to be read.
*/
int
charswaiting(void)
{
int x;
return ((ioctl(0, FIONREAD, &x) == -1) ? 0 : x);
}
/*
* panic - just exit, as quickly as we can.
*/
void
panic(char *s)
{
static int panicking = 0;
if (panicking)
return;
else
panicking = 1;
ttclose();
(void) fputs("panic: ", stderr);
(void) fputs(s, stderr);
(void) fputc('\n', stderr); /* Use '\n' as no buffers now. */
exit(1);
}
/*
* This function returns FALSE if any characters have showed up on the
* tty before 'msec' milliseconds.
*/
int
ttwait(int msec)
{
struct pollfd pfd[1];
pfd[0].fd = 0;
pfd[0].events = POLLIN;
if ((poll(pfd, 1, msec)) == 0)
return (TRUE);
return (FALSE);
}