xref: /openbsd/usr.bin/mg/ttyio.c (revision 097a140d)
1 /*	$OpenBSD: ttyio.c,v 1.40 2021/03/20 09:00:49 lum Exp $	*/
2 
3 /* This file is in the public domain. */
4 
5 /*
6  * POSIX terminal I/O.
7  *
8  * The functions in this file negotiate with the operating system for
9  * keyboard characters, and write characters to the display in a barely
10  * buffered fashion.
11  */
12 
13 #include <sys/ioctl.h>
14 #include <sys/queue.h>
15 #include <sys/time.h>
16 #include <sys/types.h>
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <poll.h>
20 #include <signal.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <term.h>
25 #include <termios.h>
26 #include <unistd.h>
27 
28 #include "def.h"
29 
30 #define NOBUF	512			/* Output buffer size. */
31 
32 int	ttstarted;
33 char	obuf[NOBUF];			/* Output buffer. */
34 size_t	nobuf;				/* Buffer count. */
35 struct	termios	oldtty;			/* POSIX tty settings. */
36 struct	termios	newtty;
37 int	nrow;				/* Terminal size, rows. */
38 int	ncol;				/* Terminal size, columns. */
39 
40 /*
41  * This function gets called once, to set up the terminal.
42  * On systems w/o TCSASOFT we turn off off flow control,
43  * which isn't really the right thing to do.
44  */
45 void
46 ttopen(void)
47 {
48 	if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO))
49 		panic("standard input and output must be a terminal");
50 
51 	if (ttraw() == FALSE)
52 		panic("aborting due to terminal initialize failure");
53 }
54 
55 /*
56  * This function sets the terminal to RAW mode, as defined for the current
57  * shell.  This is called both by ttopen() above and by spawncli() to
58  * get the current terminal settings and then change them to what
59  * mg expects.	Thus, tty changes done while spawncli() is in effect
60  * will be reflected in mg.
61  */
62 int
63 ttraw(void)
64 {
65 	if (tcgetattr(0, &oldtty) == -1) {
66 		dobeep();
67 		ewprintf("ttopen can't get terminal attributes");
68 		return (FALSE);
69 	}
70 	(void)memcpy(&newtty, &oldtty, sizeof(newtty));
71 	/* Set terminal to 'raw' mode and ignore a 'break' */
72 	newtty.c_cc[VMIN] = 1;
73 	newtty.c_cc[VTIME] = 0;
74 	newtty.c_iflag |= IGNBRK;
75 	newtty.c_iflag &= ~(BRKINT | PARMRK | INLCR | IGNCR | ICRNL | IXON);
76 	newtty.c_oflag &= ~OPOST;
77 	newtty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
78 
79 	if (tcsetattr(0, TCSASOFT | TCSADRAIN, &newtty) == -1) {
80 		dobeep();
81 		ewprintf("ttopen can't tcsetattr");
82 		return (FALSE);
83 	}
84 	ttstarted = 1;
85 
86 	return (TRUE);
87 }
88 
89 /*
90  * This function gets called just before we go back home to the shell.
91  * Put all of the terminal parameters back.
92  * Under UN*X this just calls ttcooked(), but the ttclose() hook is in
93  * because vttidy() in display.c expects it for portability reasons.
94  */
95 void
96 ttclose(void)
97 {
98 	if (ttstarted) {
99 		if (ttcooked() == FALSE)
100 			panic("");	/* ttcooked() already printf'd */
101 		ttstarted = 0;
102 	}
103 }
104 
105 /*
106  * This function restores all terminal settings to their default values,
107  * in anticipation of exiting or suspending the editor.
108  */
109 int
110 ttcooked(void)
111 {
112 	ttflush();
113 	if (tcsetattr(0, TCSASOFT | TCSADRAIN, &oldtty) == -1) {
114 		dobeep();
115 		ewprintf("ttclose can't tcsetattr");
116 		return (FALSE);
117 	}
118 	return (TRUE);
119 }
120 
121 /*
122  * Write character to the display.  Characters are buffered up,
123  * to make things a little bit more efficient.
124  */
125 int
126 ttputc(int c)
127 {
128 	if (nobuf >= NOBUF)
129 		ttflush();
130 	obuf[nobuf++] = c;
131 	return (c);
132 }
133 
134 /*
135  * Flush output.
136  */
137 void
138 ttflush(void)
139 {
140 	ssize_t	 written;
141 	char	*buf = obuf;
142 
143 	if (nobuf == 0 || batch == 1)
144 		return;
145 
146 	while ((written = write(fileno(stdout), buf, nobuf)) != nobuf) {
147 		if (written == -1) {
148 			if (errno == EINTR)
149 				continue;
150 			panic("ttflush write failed");
151 		}
152 		buf += written;
153 		nobuf -= written;
154 	}
155 	nobuf = 0;
156 }
157 
158 /*
159  * Read character from terminal. All 8 bits are returned, so that you
160  * can use a multi-national terminal.
161  */
162 int
163 ttgetc(void)
164 {
165 	char	c;
166 	ssize_t	ret;
167 
168 	do {
169 		ret = read(STDIN_FILENO, &c, 1);
170 		if (ret == -1 && errno == EINTR) {
171 			if (winch_flag) {
172 				redraw(0, 0);
173 				winch_flag = 0;
174 			}
175 		} else if (ret == -1 && errno == EIO)
176 			panic("lost stdin");
177 		else if (ret == 1)
178 			break;
179 	} while (1);
180 	return ((int) c) & 0xFF;
181 }
182 
183 /*
184  * Returns TRUE if there are characters waiting to be read.
185  */
186 int
187 charswaiting(void)
188 {
189 	int	x;
190 
191 	return ((ioctl(0, FIONREAD, &x) == -1) ? 0 : x);
192 }
193 
194 /*
195  * panic - just exit, as quickly as we can.
196  */
197 void
198 panic(char *s)
199 {
200 	static int panicking = 0;
201 
202 	if (panicking)
203 		return;
204 	else
205 		panicking = 1;
206 	ttclose();
207 	(void) fputs("panic: ", stderr);
208 	(void) fputs(s, stderr);
209 	(void) fputc('\n', stderr);	/* Use '\n' as no buffers now. */
210 	exit(1);
211 }
212 
213 /*
214  * This function returns FALSE if any characters have showed up on the
215  * tty before 'msec' milliseconds.
216  */
217 int
218 ttwait(int msec)
219 {
220 	struct pollfd	pfd[1];
221 
222 	pfd[0].fd = 0;
223 	pfd[0].events = POLLIN;
224 
225 	if ((poll(pfd, 1, msec)) == 0)
226 		return (TRUE);
227 	return (FALSE);
228 }
229