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