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