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