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
ttopen(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
ttraw(void)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
ttclose(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
ttcooked(void)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
ttputc(int c)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
ttflush(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
ttgetc(void)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
charswaiting(void)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
panic(char * s)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
ttwait(int msec)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