/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is lineterm. * * The Initial Developer of the Original Code is * Ramalingam Saravanan. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* lterm.c: Test driver for LINETERM * CPP options: * USE_NCURSES: Enable NCURSES as a screen display option * NCURSES_MOUSE_VERSION: Enable NCURSES mouse operations * LINUX: for Linux2.0/glibc * SOLARIS: for Solaris2.6 */ #include #include #include #include #include #include #include #ifdef USE_NCURSES #include #endif #ifndef _REENTRANT #define _REENTRANT #endif /* !_REENTRANT */ #include #ifdef SOLARIS #include #include #endif #ifdef LINUX #include #include #endif #include "lineterm.h" #include "tracelog.h" #define MAXPROMPT 256 /* Maximum length of prompt regexp */ #define MAXCOL 4096 /* Maximum columns in line buffer */ /* (0,0) is upper lefthand corner of window */ /* Character attributes A_NORMAL Normal display (no highlight) A_STANDOUT Best highlighting mode of the terminal. A_UNDERLINE Underlining A_REVERSE Reverse video A_BLINK Blinking A_DIM Half bright A_BOLD Extra bright or bold A_PROTECT Protected mode A_INVIS Invisible or blank mode A_ALTCHARSET Alternate character set A_CHARTEXT Bit-mask to extract a character COLOR_PAIR(n) Color-pair number n */ /* GLOBAL VARIABLES */ static int ncursesFlag = 0; static int ptyFlag = 1; static int debugFlag = 0; static int ltermNumber = -1; static char *debugFunction; static char *ttyDevice; static char *errDevice; static int screenMode = 0; static int topScrollRow, botScrollRow; static struct termios tios; /* TERMIOS structure */ static void finish(int sig); static pthread_t output_handler_thread_ID; static void *output_handler(void *arg); static void input_handler(int *plterm); #ifdef USE_NCURSES static SCREEN *termScreen = NULL; #endif int main(int argc, char *argv[]) { FILE *inFile, *outFile; UNICHAR uregexp[MAXPROMPT+1]; int argNo, options, processType, retValue; int remaining, decoded; int messageLevel; char *promptStr; char **commandArgs; char *defaultCommand[] = {(char *)getenv("SHELL"), "-i", NULL}; /* Process command line arguments */ ncursesFlag = 0; ptyFlag = 1; debugFlag = 0; processType = LTERM_DETERMINE_PROCESS; debugFunction = NULL; ttyDevice = NULL; errDevice = NULL; promptStr = "#$%>?"; /* JUST A LIST OF DELIMITERS AT PRESENT */ argNo = 1; while (argNo < argc) { if ((strcmp(argv[argNo],"-h") == 0)||(strcmp(argv[argNo],"-help") == 0)) { fprintf(stderr, "Usage: %s [-help] [-ncurses] [-nopty] [-debug] [-tcsh / -bash] [-function debug_fun] [-tty /dev/ttyname] [-err /dev/ttyname] [-prompt ] ...\n", argv[0]); exit(0); } else if (strcmp(argv[argNo],"-ncurses") == 0) { ncursesFlag = 1; argNo++; } else if (strcmp(argv[argNo],"-nopty") == 0) { ptyFlag = 0; argNo++; } else if (strcmp(argv[argNo],"-debug") == 0) { debugFlag = 1; argNo++; } else if (strcmp(argv[argNo],"-bash") == 0) { processType = LTERM_BASH_PROCESS; argNo++; } else if (strcmp(argv[argNo],"-tcsh") == 0) { processType = LTERM_TCSH_PROCESS; argNo++; } else if (strcmp(argv[argNo],"-function") == 0) { argNo++; if (argNo < argc) { debugFunction = argv[argNo++]; } } else if (strcmp(argv[argNo],"-tty") == 0) { argNo++; if (argNo < argc) { ttyDevice = argv[argNo++]; } } else if (strcmp(argv[argNo],"-err") == 0) { argNo++; if (argNo < argc) { errDevice = argv[argNo++]; } } else if (strcmp(argv[argNo],"-prompt") == 0) { argNo++; if (argNo < argc) { promptStr = argv[argNo++]; } } else break; } if (argNo < argc) { /* Execute specified command */ commandArgs = argv + argNo; } else { /* Execute default shell */ commandArgs = defaultCommand; } /* Convert prompt string to Unicode */ retValue = utf8toucs(promptStr, strlen(promptStr), uregexp, MAXPROMPT, 0, &remaining, &decoded); if ((retValue < 0) || (remaining > 0)) { fprintf(stderr, "lterm: Error in decoding prompt string\n"); exit(1); } assert(decoded <= MAXPROMPT); uregexp[decoded] = U_NUL; if (debugFlag) { messageLevel = 98; } else { messageLevel = 1; } if (errDevice != NULL) { /* Redirect debug STDERR output to specified device */ int errfd = -1; if ( (errfd = open(errDevice, O_WRONLY)) == -1) perror("lterm"); if (dup2(errfd, 2) == -1) { fprintf(stderr, "lterm: Failed dup2 for specified stderr\n"); exit(-1); } fprintf(stderr, "\n\nlterm: Echoing %s output to %s\n", argv[0], errDevice); } signal(SIGINT, finish); /* Interrupt handler */ if (ncursesFlag) { /* NCURSES mode */ #ifdef USE_NCURSES if (ttyDevice == NULL) { /* Initialize screen on controlling TTY */ initscr(); } else { /* Initialize screen on specified TTY */ if (errDevice != NULL) fprintf(stderr, "lterm-00: Opening xterm %s\n", ttyDevice); inFile = fopen( ttyDevice, "r"); outFile = fopen( ttyDevice, "w"); termScreen = newterm("xterm", outFile, inFile); set_term(termScreen); } /* NCURSES screen settings */ cbreak(); /* set terminal to raw (non-canonical) mode */ noecho(); /* Disable terminal echo */ nonl(); /* Do not translate newline */ intrflush(stdscr, FALSE); /* Flush input on interrupt */ keypad(stdscr, TRUE); /* Enable user keypad */ #ifdef NCURSES_MOUSE_VERSION mousemask(BUTTON1_CLICKED, NULL); /* Capture Button1 click events */ #endif clear(); /* Clear screen */ #endif /* USE_NCURSES */ } else { /* XTERM mode */ /* Get terminal attributes */ if (tcgetattr(0, &tios) == -1) { fprintf(stderr, "lterm: Failed to get TTY attributes\n"); exit(-1); } /* Disable signals, canonical mode processing, and echo */ tios.c_lflag &= ~(ISIG | ICANON | ECHO ); /* set MIN=1 and TIME=0 */ tios.c_cc[VMIN] = 1; tios.c_cc[VTIME] = 0; /* Set terminal attributes */ if (tcsetattr(0, TCSAFLUSH, &tios) == -1) { fprintf(stderr, "lterm: Failed to set TTY attributes\n"); exit(-1); } } /* Initialize LTERM operations */ lterm_init(0); if (errDevice != NULL) { tlog_message("lterm-00: Testing tlog_message\n"); tlog_warning("lterm-00: Testing tlog_warning\n"); fprintf(stderr, "lterm-00: "); tlog_unichar(uregexp, ucslen(uregexp)); tlog_set_level(LTERM_TLOG_MODULE, messageLevel, debugFunction); } if (errDevice != NULL) fprintf(stderr, "lintest-00: Opening LTERM to execute %s\n", commandArgs[0]); options = 0; if (!ptyFlag) options |= LTERM_NOPTY_FLAG; ltermNumber = lterm_new(); retValue = lterm_open(ltermNumber, commandArgs, NULL, NULL, uregexp, options, processType, 24, 80, 0, 0, NULL, NULL); if (retValue < 0) { fprintf(stderr, "lterm: Error %d in opening LTERM\n", retValue); exit(1); } /* Create output handler thread */ retValue = pthread_create(&output_handler_thread_ID, NULL, output_handler, (void *) <ermNumber); if (retValue != 0) { fprintf(stderr, "lterm: Error %d in creating OUTPUT_HANDLER thread\n", retValue); finish(0); } if (errDevice != NULL) fprintf(stderr, "lterm-00: Created OUTPUT_HANDLER thread\n"); /* Process input */ input_handler(<ermNumber); /* Join output handler thread */ if (errDevice != NULL) fprintf(stderr, "lterm-00: Joining OUTPUT_HANDLER thread\n"); retValue = pthread_join(output_handler_thread_ID, NULL); if (retValue != 0) { fprintf(stderr, "lterm: Error %d in joining OUTPUT_HANDLER thread\n", retValue); finish(0); } finish(0); } void finish(int sig) { if (ncursesFlag) { #ifdef USE_NCURSES endwin(); /* Close window */ if (termScreen != NULL) delscreen(termScreen); #endif } if (ltermNumber >= 0) { /* Close and delete LTERM */ lterm_delete(ltermNumber); } if (errDevice != NULL) fprintf(stderr, "finished-00: Finished\n"); exit(0); } /** Output an Unicode message to specified file descriptor or * to NCURSES window if fd == -1; */ void writeUnicode(int fd, const UNICHAR *buf, int count) { char str[MAXCOL]; int j, k; k = 0; for (j=0; j= MAXCOL-4) { if (MAXCOL >= 4) { str[MAXCOL-4] = '.'; str[MAXCOL-3] = '.'; str[MAXCOL-2] = '.'; } k = MAXCOL-1; break; } /* TEMPORARY IMPLEMENTATION: just truncate Unicode to byte characters */ str[k++] = buf[j]; } if (k == 0) return; if (fd >= 0) { if (write(fd, str, k) != k) { fprintf(stderr, "writeUnicode: Error in writing to FD %d\n", fd); exit(-1); } } else { #ifdef USE_NCURSES addnstr(str, k); #endif } } /** Output an Unicode message to specified output stream * if NOCONTROL is true, control characters are converted to printable * characters before output */ void printUnicode(FILE *outStream, const UNICHAR *buf, int count, int noControl) { char str[MAXCOL]; int j, k; k = 0; for (j=0; j= MAXCOL-4) { if (MAXCOL >= 4) { str[MAXCOL-4] = '.'; str[MAXCOL-3] = '.'; str[MAXCOL-2] = '.'; } k = MAXCOL-1; break; } if (!noControl && ((buf[j] < U_SPACE) || (buf[j] == U_DEL)) ) { /* Control character */ str[k++] = U_CARET; str[k++] = buf[j]+U_ATSIGN; } else { /* Printable character */ /* TEMPORARY IMPLEMENTATION: just truncate Unicode to byte characters */ str[k++] = buf[j]; } } /* Insert terminating null character and display string */ str[k++] = '\0'; fprintf(outStream, "%s\n", str); } void input_handler(int *plterm) { char ch; UNICHAR uch; int n_written; for (;;) { /* Read a character from TTY (raw mode) */ if (ncursesFlag) { #ifdef USE_NCURSES ch = getch(); if (ch == '\r') ch = '\n'; #endif } else { ch = getchar(); } /* fprintf(stderr, "input_handler-00: ch=%d\n", ch); */ if (ch == 0x1D) { fprintf(stderr, "input_handler-00: C-] character read; terminating\n"); break; } uch = (UNICHAR) ch; n_written = lterm_write(*plterm, &uch, 1, LTERM_WRITE_PLAIN_INPUT); /* Exit loop if TTY has been closed */ if (n_written == -2) { if (errDevice != NULL) fprintf(stderr, "input_handler-00: pseudo-TTY has been closed\n", *plterm); break; } if (n_written < 0) { fprintf(stderr, "input_handler: Error %d return from lterm_write\n", n_written); return; } } /* Close LTERM */ if (errDevice != NULL) fprintf(stderr, "input_handler-00: Closing LTERM %d\n", *plterm); /* Close and delete LTERM */ lterm_delete(*plterm); *plterm = -1; } void *output_handler(void *arg) { int *plterm = (int *) arg; int timeout = -1; UNICHAR buf[MAXCOL]; UNISTYLE style[MAXCOL]; int n_read, opcodes, opvals, buf_row, buf_col, cursor_row, cursor_col; int xmax, ymax, x, y, c; #ifdef USE_NCURSES MEVENT mev; #endif if (errDevice != NULL) fprintf(stderr, "output_handler-00: thread ID = %d, LTERM=%d\n", pthread_self(), *plterm); /* Get screen size */ if (ncursesFlag) { #ifdef USE_NCURSES getmaxyx(stdscr, ymax, xmax); #endif } else { ymax = 24; xmax = 80; } if (errDevice != NULL) fprintf(stderr, "output_handler-00: screen xmax = %d, ymax = %d\n", xmax,ymax); for (;;) { n_read = lterm_read(*plterm, timeout, buf, MAXCOL, style, &opcodes, &opvals, &buf_row, &buf_col, &cursor_row, &cursor_col); if (n_read == -1) { fprintf(stderr, "output_handler: Error %d return from lterm_read\n", n_read); return NULL; } /* Exit loop if TTY has been closed; * leave it to input handler to close the LTERM. */ if (n_read == -2) { if (errDevice != NULL) fprintf(stderr, "output_handler: pseudo-TTY has been closed\n"); break; } if (debugFlag) { fprintf(stderr, "output_handler-00: n_read=%d, opcodes=%x, opvals=%d, buf_row/col=%d/%d, cursor_row/col=%d/%d\n", n_read, opcodes, opvals, buf_row, buf_col, cursor_row, cursor_col); fprintf(stderr, "output_handler-00: U(%d): ", n_read); printUnicode(stderr, buf, n_read, 1); fprintf(stderr, "\n"); } if (opcodes & LTERM_STREAMDATA_CODE) { /* Stream data */ if (debugFlag) fprintf(stderr, "output_handler-00: STREAMDATA\n"); } else if (opcodes & LTERM_SCREENDATA_CODE) { /* Screen data */ if (!screenMode) { screenMode = 1; topScrollRow = ymax-1; botScrollRow = 0; } if (debugFlag) fprintf(stderr, "output_handler-00: SCREENDATA, topScrollRow=%d, botScrollRow=%d\n", topScrollRow, botScrollRow); if (ncursesFlag) { /* NCURSES mode */ #ifdef USE_NCURSES if (opcodes & LTERM_CLEAR_CODE) { clear(); } else if (opcodes & LTERM_INSERT_CODE) { if ((botScrollRow > 0) && (opvals > 0)) { move(ymax-botScrollRow-opvals, 0); insdelln(-opvals); } move(ymax-1-buf_row, 0); insdelln(opvals); } else if (opcodes & LTERM_DELETE_CODE) { move(ymax-1-buf_row, 0); insdelln(-opvals); if ((botScrollRow > 0) && (opvals > 0)) { move(ymax-botScrollRow-opvals, 0); insdelln(opvals); } } else if (opcodes & LTERM_SCROLL_CODE) { topScrollRow = opvals; botScrollRow = buf_row; } else if (opcodes & LTERM_OUTPUT_CODE) { /* Data available for display */ move(ymax-1-buf_row, buf_col); clrtoeol(); if (n_read > 0) { if (style[0] != LTERM_STDOUT_STYLE) attr_on(A_REVERSE, NULL); writeUnicode(-1, buf, n_read); if (style[0] != LTERM_STDOUT_STYLE) attr_off(A_REVERSE, NULL); } } /* Position cursor */ move(ymax-1-cursor_row, cursor_col); refresh(); #endif /* USE_NCURSES */ } else { /* XTERM MODE */ char esc_seq[21]; if (opcodes & LTERM_CLEAR_CODE) { /* Clear screen */ write(1, "\033[H\033[2J", 7); } else if (opcodes & LTERM_INSERT_CODE) { /* Insert lines */ sprintf(esc_seq, "\033[%d;%dH", ymax-buf_row, buf_col+1); write(1, esc_seq, strlen(esc_seq)); sprintf(esc_seq, "\033[%dL", opvals); write(1, esc_seq, strlen(esc_seq)); } else if (opcodes & LTERM_DELETE_CODE) { /* Delete lines */ sprintf(esc_seq, "\033[%d;%dH", ymax-buf_row, buf_col+1); write(1, esc_seq, strlen(esc_seq)); sprintf(esc_seq, "\033[%dM", opvals); write(1, esc_seq, strlen(esc_seq)); } else if (opcodes & LTERM_SCROLL_CODE) { /* Set scrolling region */ sprintf(esc_seq, "\033[%d;%dr", ymax-opvals, ymax-buf_row); write(1, esc_seq, strlen(esc_seq)); } else if (opcodes & LTERM_OUTPUT_CODE) { /* Data available for display */ sprintf(esc_seq, "\033[%d;%dH", ymax-buf_row, buf_col+1); write(1, esc_seq, strlen(esc_seq)); if (n_read > 0) { if (style[0] != LTERM_STDOUT_STYLE) write(1, "\033[7m", 4); writeUnicode(1, buf, n_read); if (style[0] != LTERM_STDOUT_STYLE) write(1, "\033[27m", 5); } } sprintf(esc_seq, "\033[%d;%dH", ymax-cursor_row, cursor_col+1); write(1, esc_seq, strlen(esc_seq)); } } else if (opcodes & LTERM_LINEDATA_CODE) { /* Line data */ if (debugFlag) fprintf(stderr, "output_handler-00: LINEDATA\n"); if (screenMode) screenMode = 0; if (ncursesFlag) { /* NCURSES mode */ #ifdef USE_NCURSES /* Move cursor to bottom of screen, clear line and display line */ move(ymax-1,0); clrtoeol(); if (n_read > 0) writeUnicode(-1, buf, n_read); move(ymax-1,cursor_col); if (opcodes & LTERM_NEWLINE_CODE) { move(0,0); insdelln(-1); move(ymax-1,0); } refresh(); #endif /* USE_NCURSES */ } else { /* Screen mode */ int j; /* Clear line */ write(1, "\033[2K", 4); write(1, "\r", 1); if (opcodes & LTERM_META_CODE) write(1, "META", 4); if (n_read > 0) writeUnicode(1, buf, n_read); for (j=0; j< (n_read-cursor_col); j++) write(1, "\033[D", 3); if (opcodes & LTERM_BELL_CODE) write(1, "\007", 1); if (opcodes & LTERM_CLEAR_CODE) write(1, "\033[H\033[2J", 7); if (opcodes & LTERM_NEWLINE_CODE) write(1, "\n", 1); } } else if (opcodes != 0) { fprintf(stderr, "output_handler: invalid opcodes %x\n", opcodes); } } return NULL; }