| 1 | /* |
| 2 | * |
| 3 | * Another test harness for the readline callback interface. |
| 4 | * |
| 5 | * Author: Bob Rossi <bob@brasko.net> |
| 6 | */ |
| 7 | |
| 8 | #if defined (HAVE_CONFIG_H) |
| 9 | #include <config.h> |
| 10 | #endif |
| 11 | |
| 12 | #include <stdio.h> |
| 13 | #include <sys/types.h> |
| 14 | #include <errno.h> |
| 15 | #include <curses.h> |
| 16 | |
| 17 | #include <stdlib.h> |
| 18 | #include <unistd.h> |
| 19 | |
| 20 | #include <signal.h> |
| 21 | |
| 22 | #if 0 /* LINUX */ |
| 23 | #include <pty.h> |
| 24 | #else |
| 25 | #include <util.h> |
| 26 | #endif |
| 27 | |
| 28 | #ifdef READLINE_LIBRARY |
| 29 | # include "readline.h" |
| 30 | #else |
| 31 | # include <readline/readline.h> |
| 32 | #endif |
| 33 | |
| 34 | /** |
| 35 | * Master/Slave PTY used to keep readline off of stdin/stdout. |
| 36 | */ |
| 37 | static int masterfd = -1; |
| 38 | static int slavefd; |
| 39 | |
| 40 | void |
| 41 | sigint (s) |
| 42 | int s; |
| 43 | { |
| 44 | tty_reset (STDIN_FILENO); |
| 45 | close (masterfd); |
| 46 | close (slavefd); |
| 47 | printf ("\n"); |
| 48 | exit (0); |
| 49 | } |
| 50 | |
| 51 | static int |
| 52 | user_input() |
| 53 | { |
| 54 | int size; |
| 55 | const int MAX = 1024; |
| 56 | char *buf = (char *)malloc(MAX+1); |
| 57 | |
| 58 | size = read (STDIN_FILENO, buf, MAX); |
| 59 | if (size == -1) |
| 60 | return -1; |
| 61 | |
| 62 | size = write (masterfd, buf, size); |
| 63 | if (size == -1) |
| 64 | return -1; |
| 65 | |
| 66 | return 0; |
| 67 | } |
| 68 | |
| 69 | static int |
| 70 | readline_input() |
| 71 | { |
| 72 | const int MAX = 1024; |
| 73 | char *buf = (char *)malloc(MAX+1); |
| 74 | int size; |
| 75 | |
| 76 | size = read (masterfd, buf, MAX); |
| 77 | if (size == -1) |
| 78 | { |
| 79 | free( buf ); |
| 80 | buf = NULL; |
| 81 | return -1; |
| 82 | } |
| 83 | |
| 84 | buf[size] = 0; |
| 85 | |
| 86 | /* Display output from readline */ |
| 87 | if ( size > 0 ) |
| 88 | fprintf(stderr, "%s", buf); |
| 89 | |
| 90 | free( buf ); |
| 91 | buf = NULL; |
| 92 | return 0; |
| 93 | } |
| 94 | |
| 95 | static void |
| 96 | rlctx_send_user_command(char *line) |
| 97 | { |
| 98 | /* This happens when rl_callback_read_char gets EOF */ |
| 99 | if ( line == NULL ) |
| 100 | return; |
| 101 | |
| 102 | if (strcmp (line, "exit") == 0) { |
| 103 | tty_reset (STDIN_FILENO); |
| 104 | close (masterfd); |
| 105 | close (slavefd); |
| 106 | printf ("\n"); |
| 107 | exit (0); |
| 108 | } |
| 109 | |
| 110 | /* Don't add the enter command */ |
| 111 | if ( line && *line != '\0' ) |
| 112 | add_history(line); |
| 113 | } |
| 114 | |
| 115 | static void |
| 116 | custom_deprep_term_function () |
| 117 | { |
| 118 | } |
| 119 | |
| 120 | static int |
| 121 | init_readline (int inputfd, int outputfd) |
| 122 | { |
| 123 | FILE *inputFILE, *outputFILE; |
| 124 | |
| 125 | inputFILE = fdopen (inputfd, "r"); |
| 126 | if (!inputFILE) |
| 127 | return -1; |
| 128 | |
| 129 | outputFILE = fdopen (outputfd, "w"); |
| 130 | if (!outputFILE) |
| 131 | return -1; |
| 132 | |
| 133 | rl_instream = inputFILE; |
| 134 | rl_outstream = outputFILE; |
| 135 | |
| 136 | /* Tell readline what the prompt is if it needs to put it back */ |
| 137 | rl_callback_handler_install("(rltest): ", rlctx_send_user_command); |
| 138 | |
| 139 | /* Set the terminal type to dumb so the output of readline can be |
| 140 | * understood by tgdb */ |
| 141 | if ( rl_reset_terminal("dumb") == -1 ) |
| 142 | return -1; |
| 143 | |
| 144 | /* For some reason, readline can not deprep the terminal. |
| 145 | * However, it doesn't matter because no other application is working on |
| 146 | * the terminal besides readline */ |
| 147 | rl_deprep_term_function = custom_deprep_term_function; |
| 148 | |
| 149 | using_history(); |
| 150 | read_history(".history"); |
| 151 | |
| 152 | return 0; |
| 153 | } |
| 154 | |
| 155 | static int |
| 156 | main_loop(void) |
| 157 | { |
| 158 | fd_set rset; |
| 159 | int max; |
| 160 | |
| 161 | max = (masterfd > STDIN_FILENO) ? masterfd : STDIN_FILENO; |
| 162 | max = (max > slavefd) ? max : slavefd; |
| 163 | |
| 164 | for (;;) |
| 165 | { |
| 166 | /* Reset the fd_set, and watch for input from GDB or stdin */ |
| 167 | FD_ZERO(&rset); |
| 168 | |
| 169 | FD_SET(STDIN_FILENO, &rset); |
| 170 | FD_SET(slavefd, &rset); |
| 171 | FD_SET(masterfd, &rset); |
| 172 | |
| 173 | /* Wait for input */ |
| 174 | if (select(max + 1, &rset, NULL, NULL, NULL) == -1) |
| 175 | { |
| 176 | if (errno == EINTR) |
| 177 | continue; |
| 178 | else |
| 179 | return -1; |
| 180 | } |
| 181 | |
| 182 | /* Input received through the pty: Handle it |
| 183 | * Wrote to masterfd, slave fd has that input, alert readline to read it. |
| 184 | */ |
| 185 | if (FD_ISSET(slavefd, &rset)) |
| 186 | rl_callback_read_char(); |
| 187 | |
| 188 | /* Input received through the pty. |
| 189 | * Readline read from slavefd, and it wrote to the masterfd. |
| 190 | */ |
| 191 | if (FD_ISSET(masterfd, &rset)) |
| 192 | if ( readline_input() == -1 ) |
| 193 | return -1; |
| 194 | |
| 195 | /* Input received: Handle it, write to masterfd (input to readline) */ |
| 196 | if (FD_ISSET(STDIN_FILENO, &rset)) |
| 197 | if ( user_input() == -1 ) |
| 198 | return -1; |
| 199 | } |
| 200 | |
| 201 | return 0; |
| 202 | } |
| 203 | |
| 204 | /* The terminal attributes before calling tty_cbreak */ |
| 205 | static struct termios save_termios; |
| 206 | static struct winsize size; |
| 207 | static enum { RESET, TCBREAK } ttystate = RESET; |
| 208 | |
| 209 | /* tty_cbreak: Sets terminal to cbreak mode. Also known as noncanonical mode. |
| 210 | * 1. Signal handling is still turned on, so the user can still type those. |
| 211 | * 2. echo is off |
| 212 | * 3. Read in one char at a time. |
| 213 | * |
| 214 | * fd - The file descriptor of the terminal |
| 215 | * |
| 216 | * Returns: 0 on sucess, -1 on error |
| 217 | */ |
| 218 | int tty_cbreak(int fd){ |
| 219 | struct termios buf; |
| 220 | int ttysavefd = -1; |
| 221 | |
| 222 | if(tcgetattr(fd, &save_termios) < 0) |
| 223 | return -1; |
| 224 | |
| 225 | buf = save_termios; |
| 226 | buf.c_lflag &= ~(ECHO | ICANON); |
| 227 | buf.c_iflag &= ~(ICRNL | INLCR); |
| 228 | buf.c_cc[VMIN] = 1; |
| 229 | buf.c_cc[VTIME] = 0; |
| 230 | |
| 231 | #if defined (VLNEXT) && defined (_POSIX_VDISABLE) |
| 232 | buf.c_cc[VLNEXT] = _POSIX_VDISABLE; |
| 233 | #endif |
| 234 | |
| 235 | #if defined (VDSUSP) && defined (_POSIX_VDISABLE) |
| 236 | buf.c_cc[VDSUSP] = _POSIX_VDISABLE; |
| 237 | #endif |
| 238 | |
| 239 | /* enable flow control; only stty start char can restart output */ |
| 240 | #if 0 |
| 241 | buf.c_iflag |= (IXON|IXOFF); |
| 242 | #ifdef IXANY |
| 243 | buf.c_iflag &= ~IXANY; |
| 244 | #endif |
| 245 | #endif |
| 246 | |
| 247 | /* disable flow control; let ^S and ^Q through to pty */ |
| 248 | buf.c_iflag &= ~(IXON|IXOFF); |
| 249 | #ifdef IXANY |
| 250 | buf.c_iflag &= ~IXANY; |
| 251 | #endif |
| 252 | |
| 253 | if(tcsetattr(fd, TCSAFLUSH, &buf) < 0) |
| 254 | return -1; |
| 255 | |
| 256 | ttystate = TCBREAK; |
| 257 | ttysavefd = fd; |
| 258 | |
| 259 | /* set size */ |
| 260 | if(ioctl(fd, TIOCGWINSZ, (char *)&size) < 0) |
| 261 | return -1; |
| 262 | |
| 263 | #ifdef DEBUG |
| 264 | err_msg("%d rows and %d cols\n", size.ws_row, size.ws_col); |
| 265 | #endif |
| 266 | |
| 267 | return (0); |
| 268 | } |
| 269 | |
| 270 | int |
| 271 | tty_off_xon_xoff (int fd) |
| 272 | { |
| 273 | struct termios buf; |
| 274 | int ttysavefd = -1; |
| 275 | |
| 276 | if(tcgetattr(fd, &buf) < 0) |
| 277 | return -1; |
| 278 | |
| 279 | buf.c_iflag &= ~(IXON|IXOFF); |
| 280 | |
| 281 | if(tcsetattr(fd, TCSAFLUSH, &buf) < 0) |
| 282 | return -1; |
| 283 | |
| 284 | return 0; |
| 285 | } |
| 286 | |
| 287 | /* tty_reset: Sets the terminal attributes back to their previous state. |
| 288 | * PRE: tty_cbreak must have already been called. |
| 289 | * |
| 290 | * fd - The file descrioptor of the terminal to reset. |
| 291 | * |
| 292 | * Returns: 0 on success, -1 on error |
| 293 | */ |
| 294 | int tty_reset(int fd) |
| 295 | { |
| 296 | if(ttystate != TCBREAK) |
| 297 | return (0); |
| 298 | |
| 299 | if(tcsetattr(fd, TCSAFLUSH, &save_termios) < 0) |
| 300 | return (-1); |
| 301 | |
| 302 | ttystate = RESET; |
| 303 | |
| 304 | return 0; |
| 305 | } |
| 306 | |
| 307 | int |
| 308 | main() |
| 309 | { |
| 310 | int val; |
| 311 | val = openpty (&masterfd, &slavefd, NULL, NULL, NULL); |
| 312 | if (val == -1) |
| 313 | return -1; |
| 314 | |
| 315 | val = tty_off_xon_xoff (masterfd); |
| 316 | if (val == -1) |
| 317 | return -1; |
| 318 | |
| 319 | val = init_readline (slavefd, slavefd); |
| 320 | if (val == -1) |
| 321 | return -1; |
| 322 | |
| 323 | val = tty_cbreak (STDIN_FILENO); |
| 324 | if (val == -1) |
| 325 | return -1; |
| 326 | |
| 327 | signal (SIGINT, sigint); |
| 328 | |
| 329 | val = main_loop (); |
| 330 | |
| 331 | tty_reset (STDIN_FILENO); |
| 332 | |
| 333 | if (val == -1) |
| 334 | return -1; |
| 335 | |
| 336 | return 0; |
| 337 | } |