home *** CD-ROM | disk | FTP | other *** search
/ World of Shareware - Software Farm 2 / wosw_2.zip / wosw_2 / CPROG / FC20C.ZIP / MTERM.C < prev    next >
C/C++ Source or Header  |  1990-08-20  |  16KB  |  657 lines

  1. /*
  2.  * MICRO-Terminal:
  3.  *
  4.  * This is a very simple communications program, which provides
  5.  * a subset ANSI (VT100) terminal emulation, and basic XMODEM
  6.  * (with checksum) file transfer.
  7.  *
  8.  * If the '-t' option is given, MICRO-Terminal will install
  9.  * itself as a TSR (Ram-Resident) program, which can be invoked
  10.  * at any time by pressing both SHIFT keys.
  11.  *
  12.  * This demonstrates the use of the MICRO-C video interface
  13.  * and communications library functions for the IBM/PC, as well
  14.  * as the "SAVE_VIDEO", "RESTORE_VIDEO" and "TSR" functions.
  15.  *
  16.  * Copyright 1990 Dave Dunfield
  17.  * All rights reserved.
  18.  */
  19. #include \mc\stdio.h        /* Standard I/O definitions */
  20. #include \mc\comm.h            /* Comm     I/O definitions */
  21. #include \mc\tsr.h            /* Tsr function definitions */
  22. #include \mc\video.h        /* Video    I/O definitions */
  23.  
  24. /* Screen output positions */
  25. #define    SETROW        3        /* Screen row for settings display */
  26. #define MSGROW        20        /* Screen row for messages */
  27. #define    MENROW        7        /* Screen row for menu items */
  28. #define    MAICOL        0        /* Screen column for main menu */
  29. #define    SUBCOL        20        /* Screen column for sub menu */
  30. #define    FILCOL        5        /* Screen column for file prompt */
  31. #define    FILSIZ        50        /* Maximum size of file name */
  32.  
  33. /* XMODEM parameters */
  34. #define BLOCK_SIZE    128        /* size of transmit blocks */
  35. #define RETRYS        10        /* maximum number of retrys */
  36. #define    SOH_TIMEOUT    10        /* How long to wait for start of packet */
  37. #define    RX_TIMEOUT    2        /* How long in wait for chars in packet */
  38. #define    ACK_TIMEOUT    15        /* How long to wait for acknowlege */
  39.  
  40. /* Line control codes */
  41. #define SOH            0x01    /* start of header */
  42. #define ACK            0x06    /* Acknowledge */
  43. #define NAK            0x15    /* Negative acknowledge */
  44. #define CAN            0x18    /* Cancel */
  45. #define EOT            0x04    /* end of text */
  46.  
  47. /* Menu text tables (Used by 'vmenu') */
  48.     char *main_menu[] = {
  49.         "Terminal Emulation",
  50.         "XMODEM Download",
  51.         "XMODEM Upload",
  52.         "Serial port config",
  53.         "Exit to DOS",
  54.         0 };
  55.  
  56.     char *setup_menu[] = {
  57.         "Comm port",
  58.         "Baudrate",
  59.         "Data bits",
  60.         "Parity",
  61.         "Stop bits",
  62.         "Xon/Xoff",
  63.         0 };
  64.  
  65. /* Uart configuration static data tables */
  66.     unsigned baudvalue[] =
  67.         { _110,  _300,  _1200,  _2400,  _4800,  _9600,  _19200,  _38400 };
  68.     char *baudtext[] =
  69.         { "110", "300", "1200", "2400", "4800", "9600", "19200", "38400", 0 };
  70.     char *databits[] = { "Five", "Six", "Seven", "Eight", 0 };
  71.     char *parity[] = { "Odd", "Even", "Mark", "Space", "None", 0 };
  72.     char *onetwo[] = { "One", "Two", 0 };
  73.     char *flowctrl[] = { "Disabled", "Enabled", 0 };
  74.  
  75. /* Communications configuration parameters */
  76.     int comm = 0, baud = 5, data = 3, par = 4, stop = 0, flow = 1;
  77.  
  78. /* Misc global variables */
  79.     setup_selection = 0, transfer_selection = 0;
  80.     char dfile[FILSIZ+1] = "", ufile[FILSIZ+1] = "";
  81.  
  82. /* Saved video screens, attributes & cursor position */
  83.     char sav_buffer[(25*80)*2], sav_attr;
  84.     int sav_xy;
  85.     char video_save_area[SCR_BUF];
  86.  
  87. /*
  88.  * Main terminal program menu
  89.  */
  90. tty_main()
  91. {
  92.     int i;
  93.  
  94.     i = 0;            /* Default to top of menu */
  95.     save_video(video_save_area);
  96.  
  97. redraw:
  98.     draw_title();
  99.     vdraw_box(0, SETROW, 79, 2);
  100.     show_settings();
  101.  
  102.     for(;;) {
  103.         message("Select function and press ENTER");
  104.         if(vmenu(MAICOL, MENROW, main_menu, 0, &i))
  105.             continue;
  106.         switch(i) {
  107.             case 0 :        /* Terminal Emulation */
  108.                 if(!open_comm(flow))
  109.                     break;
  110.                 vcursor_line();
  111.                 restore_screen();
  112.                 ansi_term();
  113.                 save_screen();
  114.                 vcursor_off();
  115.                 goto redraw;
  116.             case 1 :        /* Download a file */
  117.                 if(open_comm(0))
  118.                     download(dfile);
  119.                 break;
  120.             case 2 :        /* Upload a file */
  121.                 if(open_comm(0))
  122.                     upload(dfile);
  123.                 break;
  124.             case 3 :        /* Setup serial port */
  125.                 setup();
  126.                 break;
  127.             case 4 :        /* Exit to DOS */
  128.                 Cclose();
  129.                 restore_video(video_save_area);
  130.                 return; } }
  131. }
  132.  
  133. /*
  134.  * Open a file for read or write (with overwrite prompt)
  135.  */
  136. FILE *openf(fname, rw)
  137.     char *fname, rw;
  138. {
  139.     char c, omsg[80], *mode;
  140.     FILE *fp;
  141.  
  142.     mode = "read";
  143.     fp = fopen(fname, "r");        /* First try and read the file */
  144.     if(rw) {                    /* If writing the file */
  145.         mode = "write";
  146.         if(fp) {
  147.             fclose(fp);
  148.             sprintf(omsg, "Overwrite existing %s (Y/N) ?", fname);
  149.             message(omsg);
  150.             do {
  151.                 c = toupper(vgetc());
  152.                 if((c == 0x1B) || (c == 'N'))
  153.                     return 0; }
  154.             while(c != 'Y'); }
  155.         fp = fopen(fname,"w"); }
  156.     if(!fp) {
  157.         sprintf(omsg,"Cannot %s %s (Press ENTER)", mode, fname);
  158.         message(omsg);
  159.         while(vgetc() != '\n'); }
  160.     return fp;
  161. }
  162.  
  163. /*
  164.  * Open comm port with correct settings
  165.  */
  166. open_comm(flow)
  167.     char flow;
  168. {
  169.     int mode;
  170.  
  171.     /* Calculate the communications parameter value */
  172.     mode =    ((par << 4) & 0x30) |    /* parity type */
  173.             (data & 0x03) |            /* # data bits */
  174.             ((stop << 2) & 0x04) |    /* # stop bits */
  175.             ((par < 4) << 3);        /* parity enable */
  176.  
  177.     /* Open the communications port */
  178.     if(Copen(comm+1, baudvalue[baud], mode, SET_DTR|SET_RTS|OUTPUT_2)) {
  179.         message("Cannot open COM port (Press ENTER)");
  180.         while(vgetc() != '\n');
  181.         return 0; }
  182.  
  183.     /* Remove transparency if XON/XOFF flow control */
  184.     disable();
  185.     Cflags = (flow) ? Cflags & ~TRANSPARENT : Cflags | TRANSPARENT;
  186.     enable();
  187.  
  188.     return -1;
  189. }
  190.  
  191. /*
  192.  * Draw the title  header
  193.  */
  194. draw_title()
  195. {
  196.     vopen();
  197.     V_ATTR = REVERSE;
  198.     vdraw_box(0, 0, 79, 2);
  199.     vgotoxy(1, 1);
  200.     vputf("", 26);
  201.     vputf("MICRO-Terminal Version 1.2", 52);
  202.     V_ATTR = NORMAL;
  203.     vcursor_off();
  204. }
  205.  
  206. /*
  207.  * Draw the file transfer information box
  208.  */
  209. info_box(mode, filename)
  210.     char *mode, *filename;
  211. {
  212.     vdraw_box(SUBCOL, MENROW+1, 50, 8);
  213.     vgotoxy(SUBCOL+2, MENROW+3);
  214.     vprintf("%-19s: %s", mode, filename);
  215.     vgotoxy(SUBCOL+2, MENROW+5);
  216.     vputs("Blocks transferred : 0");
  217.     vgotoxy(SUBCOL+2, MENROW+7);
  218.     vputs("Transfer status    : ");
  219.     message("File transfer in progress (ESCAPE to abort)");
  220. }
  221.  
  222. /*
  223.  * Update the transfer status field
  224.  */
  225. transfer_status(text)
  226.     char *text
  227. {
  228.     vgotoxy(SUBCOL+23, MENROW+7);
  229.     vputf(text,10);
  230. }
  231.  
  232. /*
  233.  * Show the current COM port settings
  234.  */
  235. show_settings()
  236. {
  237.     vgotoxy(18, SETROW+1);
  238.     vprintf("COM%u: %5s,%2d,%5s,%2d  Xon/Xoff %-8s",
  239.         comm+1, baudtext[baud], data+5, parity[par], stop+1, flowctrl[flow]);
  240. }
  241.  
  242. /*
  243.  * Display a message
  244.  */
  245. message(ptr)
  246.     char *ptr;
  247. {
  248.     vgotoxy(0, MSGROW);
  249.     vcleos();
  250.     vmessage(38 - strlen(ptr)/2, MSGROW, ptr);
  251. }
  252.  
  253. /*
  254.  * Save the MICRO-TERMINAL video screen.
  255.  */
  256. save_screen()
  257. {
  258.     sav_xy = V_XY;
  259.     sav_attr = V_ATTR;
  260.     copy_seg(get_ds(), sav_buffer, V_BASE, 0, (25*80)*2);
  261. }
  262.  
  263. /*
  264.  * Restore the MICRO-TERMINAL video screen
  265.  */
  266. restore_screen()
  267. {
  268.     copy_seg(V_BASE, 0, get_ds(), sav_buffer, (25*80)*2);
  269.     V_ATTR = sav_attr;
  270.     V_XY = sav_xy;
  271.     vupdatexy();
  272. }
  273.  
  274. /*
  275.  * Comm port setup menu handler
  276.  */
  277. setup()
  278. {
  279.     message("Select setting (ESCAPE to cancel)");
  280.     for(;;) {
  281.         show_settings();
  282.         if(vmenu(SUBCOL, MENROW+1, setup_menu, 0, &setup_selection))
  283.             return;
  284.         switch(setup_selection) {
  285.             case 0 :    /* Comm port */
  286.                 vmenu(SUBCOL+11,MENROW+2,onetwo,-1,&comm);
  287.                 break;
  288.             case 1 :    /* baudrate */
  289.                 vmenu(SUBCOL+11,MENROW+2,baudtext,-1,&baud);
  290.                 break;
  291.             case 2 :    /* Data bits */
  292.                 vmenu(SUBCOL+11,MENROW+2,databits,-1,&data);
  293.                 break;
  294.             case 3 :    /* Parity */
  295.                 vmenu(SUBCOL+11,MENROW+2,parity,-1,&par);
  296.                 break;
  297.             case 4 :    /* Stop bits */
  298.                 vmenu(SUBCOL+11,MENROW+2,onetwo,-1,&stop);
  299.                 break;
  300.             case 5 :    /* Flow control */
  301.                 vmenu(SUBCOL+11,MENROW+2,flowctrl,-1,&flow); } }
  302. }
  303.  
  304. /*
  305.  * ANSI (VT100) Function key translation table
  306.  */
  307.     char *ansi_keys[] = {
  308.         "\x1B[A", "\x1B[B", "\x1B[D", "\x1B[C",    /* Arrow keys */
  309.         "\x1BOR", "\x1BOS", "\x1BOP", "\x1BOQ",    /* PgUp, Pgdn, Home, End */
  310.         "\x1BOM", "\x1BOm", "\x1BOp",            /* Keypad '+','-' Insert */
  311.         "\x7F",   "\x08",                        /* Delete & Backspace */
  312.         "\x1BOq", "\x1BOr", "\x1BOs", "\x1BOt",    /* F1, F2, F3 & F4 */
  313.         "\x1BOu", "\x1BOv", "\x1BOw", "\x1BOx",    /* F5, F6, F7 & F8 */
  314.         "\x1BOy", "\x1BOp",                        /* F9 & F10 */
  315.         "\x1BOl", "\X1BOn", 0, 0 };        /* Control: Pgup, Pgdn, Home, End */
  316.  
  317. /*
  318.  * Terminal mode using ANSI (VT100) emulation
  319.  */
  320. ansi_term()
  321. {
  322.     char c, xy_flag, *ptr;
  323.     unsigned x, y, state, value, parm, parms[5];
  324.  
  325.     xy_flag = -1;        /* Force initial cursor update */
  326.     state = 0;            /* Not receiving a control sequence */
  327.     for(;;) {
  328.         /* Process any input from the comm port */
  329.         if((c = Ctestc()) != -1) {
  330.             xy_flag = -1;
  331.             if(c == 0x1B) {                /* Begin escape sequence */
  332.                 state = 1;
  333.                 parms[0] = parms[1] = value = parm = 0; }
  334.             else switch(state) {
  335.                 case 1 :                /* Escape already received */
  336.                     if(c == '[') {
  337.                         state = 2;
  338.                         break; }
  339.                     state = 0;
  340.                 case 0 :                /* No special processing */
  341.                     vputc(c);
  342.                     break;
  343.                 case 2 :                /* Waiting for numeric parms */
  344.                     if(isdigit(c)) {
  345.                         value = (value * 10) + (c - '0');
  346.                         break; }
  347.                     parms[parm++] = value;    /* More to come */
  348.                     if(c == ';') {
  349.                         value = 0;
  350.                         break; }
  351.                     state = 0;
  352.                     switch(c) {
  353.                         case 'H' :        /* Cursor position (1) */
  354.                         case 'f' :        /* Cursor position (2) */
  355.                             if(y = parms[0])
  356.                                 --y;
  357.                             if(x = parms[1])
  358.                                 --x;
  359.                             vgotoxy(x, y);
  360.                             break;
  361.                         case 'J' :        /* Erase in display */
  362.                             x = V_XY;
  363.                             value ? vclscr() : vcleos();
  364.                             V_XY = x;
  365.                             break;
  366.                         case 'K' :        /* Erase in line */
  367.                             x = V_XY;
  368.                             if(value)
  369.                                 V_XY &= 0xff00;
  370.                             vcleol();
  371.                             V_XY = x;
  372.                             break;
  373.                         case 'm' :        /* Select attributes */
  374.                             x = 0;
  375.                             do {
  376.                                 V_ATTR  = (y = parms[x]) ? (y == 4) ?
  377.                                     UNDERLINE : REVERSE : NORMAL; }
  378.                             while(++x < parm); } } }
  379.         else if(xy_flag) {                /* Cursor has moved */
  380.             vupdatexy();
  381.             xy_flag = 0; }
  382.  
  383.         /* Process any input from the keyboard */
  384.         if(c = vtstc()) {
  385.             if(c & 0x80) {                /* Special function key */
  386.                 if(!(ptr = ansi_keys[c & 0x7f]))
  387.                     return;
  388.                 while(*ptr)
  389.                     Cputc(*ptr++); }
  390.             else
  391.                 Cputc((c == '\n') ? '\r' : c); } }
  392. }
  393.  
  394. /*
  395.  * Receive a file in XMODEM protocol
  396.  */
  397. download()
  398. {
  399.     char rbuffer[BLOCK_SIZE], *error_text, *old_error, ochr;
  400.     int r, rx_block_num, error_count;
  401.     FILE *fp;
  402.  
  403.     if(vgets(FILCOL,MSGROW,"Write to file? ",dfile,FILSIZ) || !*dfile)
  404.         return;
  405.  
  406.     if(!(fp = openf(dfile, -1)))
  407.         return;
  408.  
  409.     info_box("Download to file", dfile);
  410.  
  411.     error_text = old_error = rx_block_num = 1;
  412.     error_count = RETRYS;
  413.     do {
  414.         if(vtstc() == 0x1b) {        /* Console escape */
  415.             ochr = CAN;
  416.             error_text = "CANCELED";
  417.             error_count = 0; }
  418.         else if((r = get_record(rbuffer)) == (rx_block_num & 255)) {
  419.             error_count = RETRYS;
  420.             fput(rbuffer, BLOCK_SIZE, fp);
  421.             vgotoxy(SUBCOL+23, MENROW+5);
  422.             vprintf("%u", rx_block_num++);
  423.             error_text = "RX PACKET";
  424.             ochr = ACK; }
  425.         else {
  426.             switch(r) {
  427.                 case -1 :        /* Timeout */
  428.                     error_text = "TIMEOUT";
  429.                     ochr = NAK;
  430.                     break;
  431.                 case -2 :        /* Bad block */
  432.                     error_text = "BAD BLOCK#";
  433.                     while(Cgett(RX_TIMEOUT) != -1);
  434.                     ochr = NAK;
  435.                     break;
  436.                 case -3 :        /* Bad checksum */
  437.                     error_text = "BAD CHKSUM";
  438.                     ochr = NAK;
  439.                     break;
  440.                 case -4 :        /* End of file */
  441.                     error_text = "DONE";
  442.                     ochr = ACK;
  443.                     break;
  444.                 case -5 :        /* Cancel */
  445.                     error_text = "ABORTED";
  446.                     ochr = ACK;
  447.                     break;
  448.                 default:        /* Block out of sequence */
  449.                     error_text = "WRONG BLK";
  450.                     ochr = NAK; }
  451.                 --error_count; }
  452.             Cputc(ochr);
  453.             /* Update status message */
  454.             if(error_text != old_error)
  455.                 transfer_status(old_error = error_text); }
  456.         while((r > -3) && error_count);
  457.  
  458.     message("Download ended (Press ENTER)");
  459.     fclose(fp);
  460.     while(vgetc() != '\n');
  461.     vclear_box(SUBCOL, MENROW+1, 50, 8);
  462. }
  463.  
  464. /*
  465.  * Read a record in the XMODEM protocol, return the block number
  466.  * (0-255) if successful, or one of the following return codes:
  467.  *    -1 = Timeout
  468.  *    -2 = Bad block number
  469.  *    -3 = Bad block checksum
  470.  *    -4 = End of file
  471.  *    -5 = Canceled by remote
  472.  */
  473. get_record(rbuffer)
  474.     char rbuffer[];
  475. {
  476.     int c, i, block_num, check_sum;
  477.  
  478.     check_sum = 0;
  479.     i = -2;
  480.     switch(Cgett(SOH_TIMEOUT)) {
  481.         case SOH :        /* Receive packet */
  482.             for(;;) {
  483.                 if((c = Cgett(RX_TIMEOUT)) == -1)    /* receive timeout */
  484.                     break;
  485.                 if(i == -2)                            /* first block number */
  486.                     block_num = c;
  487.                 else if(i == -1) {                    /* second block number */
  488.                     if((255 & ~c) != block_num)
  489.                         return -2; }
  490.                 else if(i == BLOCK_SIZE)            /* checksum at end */
  491.                     return (check_sum & 0xff) == c ? block_num : -3;
  492.                 else                                /* data character */
  493.                     check_sum += (rbuffer[i] = c);
  494.                 ++i; }
  495.         case -1 :        /* timeout on waiting for packet */
  496.             return -1;
  497.         case EOT :        /* end of file encountered */
  498.             return -4;
  499.         case CAN :        /* cancel protocol */
  500.             return -5; }
  501. }
  502.  
  503. /*
  504.  * Transmit a file in XMODEM protocol
  505.  */
  506. upload()
  507. {
  508.     int i, c, tx_block_num, error_count;
  509.     char buffer[BLOCK_SIZE], *error_text, *old_error;
  510.     FILE *fp;
  511.  
  512.     if(vgets(FILCOL,MSGROW,"Read from file? ",ufile,FILSIZ) || !*ufile)
  513.         return;
  514.  
  515.     if(!(fp = openf(ufile, 0)))
  516.         return;
  517.  
  518.     info_box("Upload from file", ufile);
  519.  
  520.     tx_block_num = old_error = error_text = 1;
  521.     error_count = RETRYS;
  522.  
  523.     /* Transmit the file data */
  524.     while(i = fget(buffer, BLOCK_SIZE, fp)) {
  525.         while(i < 128)
  526.                 buffer[i++] = -1;
  527.         error_text = "TX PACKET";
  528.         while(i = send_record(buffer, tx_block_num)) {
  529.             switch(i) {
  530.                 case -1 :
  531.                     error_text = "TIMEOUT";
  532.                     break;
  533.                 case -3 :
  534.                     error_text = "RECV NAK";
  535.                     break;
  536.                 case -5 :
  537.                     error_text = "ABORTED"; }
  538.                 transfer_status(old_error = error_text);
  539.                 if((i < -3) || !error_count--)
  540.                     break; }
  541.         if(vtstc() == 0x1b) {        /* Console escape */
  542.             i = -5;
  543.             error_text = "CANCELED"; }
  544.         if(i) {                        /* Error exit */
  545.             Cputc(CAN);
  546.             break; }
  547.         error_count = RETRYS;
  548.         vgotoxy(SUBCOL+23, MENROW+5);
  549.         vprintf("%u", tx_block_num++);
  550.         if(error_text != old_error)
  551.             transfer_status(old_error = error_text); }
  552.  
  553.     /* Send the end of file indicator */
  554.     error_count = RETRYS;
  555.     if(!i) for(;;) {
  556.         Cputc(EOT);
  557.         if((c = Cgett(ACK_TIMEOUT)) == ACK) {
  558.             error_text = "DONE";
  559.             break; }
  560.         if(c == CAN) {
  561.             error_text = "ABORTED";
  562.             break; }
  563.         if(((c == -1) || (c == NAK)) && !error_count--) {
  564.             error_text = "TIMEOUT";
  565.             break; } }
  566.  
  567.     transfer_status(error_text);
  568.     message("Upload ended (Press ENTER)");
  569.     fclose(fp);
  570.     while(vgetc() != '\n');
  571.     vclear_box(SUBCOL, MENROW+1, 50, 8);
  572. }
  573.  
  574. /*
  575.  * Send an record in XMODEM protocol, return 0 if successful
  576.  * Otherwise, return one of the following:
  577.  *    -1 = Timeout
  578.  *    -2 = Bad block number    (N/A)
  579.  *    -3 = Bad block            (NAK RECEIVED)
  580.  *    -4 = End of file        (N/A)
  581.  *    -5 = Canceled by remote
  582.  */
  583. send_record(buffer, block_num)
  584.     char *buffer;
  585.     int block_num;
  586. {
  587.     int i, check_sum;
  588.     char *ptr;
  589.  
  590.     check_sum = 0;
  591.     ptr = buffer;
  592.     while(Ctestc() != -1);        /* purge any received data */
  593.     Cputc(SOH);
  594.     Cputc(block_num);
  595.     Cputc(~block_num);
  596.     for(i=0; i < BLOCK_SIZE; ++i) {
  597.         Cputc(*buffer);
  598.         check_sum += *buffer++; }
  599.     Cputc(check_sum);
  600.  
  601.     for(;;) switch(Cgett(ACK_TIMEOUT)) {
  602.         case ACK :            /* Packet received ok */
  603.             return 0;
  604.         case NAK :            /* Rejected */
  605.             return -3;
  606.         case CAN :            /* Remote cancel */
  607.             return -5;
  608.         case -1 :            /* Timeout */
  609.             return -1; }
  610. }
  611.  
  612. /*
  613.  * Wait for a character from the modem (with timeout)
  614.  */
  615. Cgett(timeout)
  616.     unsigned timeout;
  617. {
  618.     int h, m, s, old_s;
  619.  
  620.     if((h = Ctestc()) != -1)    /* Very fast if characters buffered */
  621.         return h;
  622.  
  623.     get_time(&h, &m, &old_s);
  624.     do {
  625.         do {
  626.             if((h = Ctestc()) != -1)
  627.                 return h;
  628.             get_time(&h,&m,&s); }
  629.         while(s == old_s);
  630.         old_s = s; }
  631.     while(--timeout);
  632.     return -1;
  633. }
  634.  
  635. /*
  636.  * Main program, either TSR or execute main tty program menu
  637.  */
  638. main(argc, argv)
  639.     int argc;
  640.     int *argv[];
  641. {
  642.     vopen();
  643.     vputs("MICRO-Terminal: (Press CTRL-HOME or CTRL-END to exit):\n");
  644.     save_screen();
  645.  
  646. /* If RAM-resident, print startup message & TSR */
  647.     if((argc > 1) && (*argv[1] == 't-')) {
  648.         draw_title();
  649.         printf("\n\n\nRam-Resident MICRO-Terminal installed\nPress BOTH SHIFTS to POP UP\n");
  650.         vcursor_line();
  651.         tsr(&tty_main, L_SHIFT+R_SHIFT, 2000); }
  652.  
  653. /* Not RAM-resident, execute the program */
  654.     vclscr();        /* Return to blank screen */
  655.     tty_main();
  656. }
  657.