| /* |
| * |
| * Another test harness for the readline callback interface. |
| * |
| * Author: Bob Rossi <bob@brasko.net> |
| */ |
| |
| #if defined (HAVE_CONFIG_H) |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <errno.h> |
| #include <curses.h> |
| |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| #include <signal.h> |
| |
| #if 0 /* LINUX */ |
| #include <pty.h> |
| #else |
| #include <util.h> |
| #endif |
| |
| #ifdef READLINE_LIBRARY |
| # include "readline.h" |
| #else |
| # include <readline/readline.h> |
| #endif |
| |
| /** |
| * Master/Slave PTY used to keep readline off of stdin/stdout. |
| */ |
| static int masterfd = -1; |
| static int slavefd; |
| |
| void |
| sigint (s) |
| int s; |
| { |
| tty_reset (STDIN_FILENO); |
| close (masterfd); |
| close (slavefd); |
| printf ("\n"); |
| exit (0); |
| } |
| |
| static int |
| user_input() |
| { |
| int size; |
| const int MAX = 1024; |
| char *buf = (char *)malloc(MAX+1); |
| |
| size = read (STDIN_FILENO, buf, MAX); |
| if (size == -1) |
| return -1; |
| |
| size = write (masterfd, buf, size); |
| if (size == -1) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int |
| readline_input() |
| { |
| const int MAX = 1024; |
| char *buf = (char *)malloc(MAX+1); |
| int size; |
| |
| size = read (masterfd, buf, MAX); |
| if (size == -1) |
| { |
| free( buf ); |
| buf = NULL; |
| return -1; |
| } |
| |
| buf[size] = 0; |
| |
| /* Display output from readline */ |
| if ( size > 0 ) |
| fprintf(stderr, "%s", buf); |
| |
| free( buf ); |
| buf = NULL; |
| return 0; |
| } |
| |
| static void |
| rlctx_send_user_command(char *line) |
| { |
| /* This happens when rl_callback_read_char gets EOF */ |
| if ( line == NULL ) |
| return; |
| |
| if (strcmp (line, "exit") == 0) { |
| tty_reset (STDIN_FILENO); |
| close (masterfd); |
| close (slavefd); |
| printf ("\n"); |
| exit (0); |
| } |
| |
| /* Don't add the enter command */ |
| if ( line && *line != '\0' ) |
| add_history(line); |
| } |
| |
| static void |
| custom_deprep_term_function () |
| { |
| } |
| |
| static int |
| init_readline (int inputfd, int outputfd) |
| { |
| FILE *inputFILE, *outputFILE; |
| |
| inputFILE = fdopen (inputfd, "r"); |
| if (!inputFILE) |
| return -1; |
| |
| outputFILE = fdopen (outputfd, "w"); |
| if (!outputFILE) |
| return -1; |
| |
| rl_instream = inputFILE; |
| rl_outstream = outputFILE; |
| |
| /* Tell readline what the prompt is if it needs to put it back */ |
| rl_callback_handler_install("(rltest): ", rlctx_send_user_command); |
| |
| /* Set the terminal type to dumb so the output of readline can be |
| * understood by tgdb */ |
| if ( rl_reset_terminal("dumb") == -1 ) |
| return -1; |
| |
| /* For some reason, readline can not deprep the terminal. |
| * However, it doesn't matter because no other application is working on |
| * the terminal besides readline */ |
| rl_deprep_term_function = custom_deprep_term_function; |
| |
| using_history(); |
| read_history(".history"); |
| |
| return 0; |
| } |
| |
| static int |
| main_loop(void) |
| { |
| fd_set rset; |
| int max; |
| |
| max = (masterfd > STDIN_FILENO) ? masterfd : STDIN_FILENO; |
| max = (max > slavefd) ? max : slavefd; |
| |
| for (;;) |
| { |
| /* Reset the fd_set, and watch for input from GDB or stdin */ |
| FD_ZERO(&rset); |
| |
| FD_SET(STDIN_FILENO, &rset); |
| FD_SET(slavefd, &rset); |
| FD_SET(masterfd, &rset); |
| |
| /* Wait for input */ |
| if (select(max + 1, &rset, NULL, NULL, NULL) == -1) |
| { |
| if (errno == EINTR) |
| continue; |
| else |
| return -1; |
| } |
| |
| /* Input received through the pty: Handle it |
| * Wrote to masterfd, slave fd has that input, alert readline to read it. |
| */ |
| if (FD_ISSET(slavefd, &rset)) |
| rl_callback_read_char(); |
| |
| /* Input received through the pty. |
| * Readline read from slavefd, and it wrote to the masterfd. |
| */ |
| if (FD_ISSET(masterfd, &rset)) |
| if ( readline_input() == -1 ) |
| return -1; |
| |
| /* Input received: Handle it, write to masterfd (input to readline) */ |
| if (FD_ISSET(STDIN_FILENO, &rset)) |
| if ( user_input() == -1 ) |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /* The terminal attributes before calling tty_cbreak */ |
| static struct termios save_termios; |
| static struct winsize size; |
| static enum { RESET, TCBREAK } ttystate = RESET; |
| |
| /* tty_cbreak: Sets terminal to cbreak mode. Also known as noncanonical mode. |
| * 1. Signal handling is still turned on, so the user can still type those. |
| * 2. echo is off |
| * 3. Read in one char at a time. |
| * |
| * fd - The file descriptor of the terminal |
| * |
| * Returns: 0 on sucess, -1 on error |
| */ |
| int tty_cbreak(int fd){ |
| struct termios buf; |
| int ttysavefd = -1; |
| |
| if(tcgetattr(fd, &save_termios) < 0) |
| return -1; |
| |
| buf = save_termios; |
| buf.c_lflag &= ~(ECHO | ICANON); |
| buf.c_iflag &= ~(ICRNL | INLCR); |
| buf.c_cc[VMIN] = 1; |
| buf.c_cc[VTIME] = 0; |
| |
| #if defined (VLNEXT) && defined (_POSIX_VDISABLE) |
| buf.c_cc[VLNEXT] = _POSIX_VDISABLE; |
| #endif |
| |
| #if defined (VDSUSP) && defined (_POSIX_VDISABLE) |
| buf.c_cc[VDSUSP] = _POSIX_VDISABLE; |
| #endif |
| |
| /* enable flow control; only stty start char can restart output */ |
| #if 0 |
| buf.c_iflag |= (IXON|IXOFF); |
| #ifdef IXANY |
| buf.c_iflag &= ~IXANY; |
| #endif |
| #endif |
| |
| /* disable flow control; let ^S and ^Q through to pty */ |
| buf.c_iflag &= ~(IXON|IXOFF); |
| #ifdef IXANY |
| buf.c_iflag &= ~IXANY; |
| #endif |
| |
| if(tcsetattr(fd, TCSAFLUSH, &buf) < 0) |
| return -1; |
| |
| ttystate = TCBREAK; |
| ttysavefd = fd; |
| |
| /* set size */ |
| if(ioctl(fd, TIOCGWINSZ, (char *)&size) < 0) |
| return -1; |
| |
| #ifdef DEBUG |
| err_msg("%d rows and %d cols\n", size.ws_row, size.ws_col); |
| #endif |
| |
| return (0); |
| } |
| |
| int |
| tty_off_xon_xoff (int fd) |
| { |
| struct termios buf; |
| int ttysavefd = -1; |
| |
| if(tcgetattr(fd, &buf) < 0) |
| return -1; |
| |
| buf.c_iflag &= ~(IXON|IXOFF); |
| |
| if(tcsetattr(fd, TCSAFLUSH, &buf) < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| /* tty_reset: Sets the terminal attributes back to their previous state. |
| * PRE: tty_cbreak must have already been called. |
| * |
| * fd - The file descrioptor of the terminal to reset. |
| * |
| * Returns: 0 on success, -1 on error |
| */ |
| int tty_reset(int fd) |
| { |
| if(ttystate != TCBREAK) |
| return (0); |
| |
| if(tcsetattr(fd, TCSAFLUSH, &save_termios) < 0) |
| return (-1); |
| |
| ttystate = RESET; |
| |
| return 0; |
| } |
| |
| int |
| main() |
| { |
| int val; |
| val = openpty (&masterfd, &slavefd, NULL, NULL, NULL); |
| if (val == -1) |
| return -1; |
| |
| val = tty_off_xon_xoff (masterfd); |
| if (val == -1) |
| return -1; |
| |
| val = init_readline (slavefd, slavefd); |
| if (val == -1) |
| return -1; |
| |
| val = tty_cbreak (STDIN_FILENO); |
| if (val == -1) |
| return -1; |
| |
| signal (SIGINT, sigint); |
| |
| val = main_loop (); |
| |
| tty_reset (STDIN_FILENO); |
| |
| if (val == -1) |
| return -1; |
| |
| return 0; |
| } |