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