/* * MICRO-Terminal: * * This is a very simple communications program, which provides * a subset ANSI (VT100) terminal emulation, and basic XMODEM * (with checksum) file transfer. * * If HOTKEYS are specified on the command line, MTERM will install * itself as a TSR (Ram-Resident) program, which can be invoked at * any time by pressing the HOTKEYS. Available HOTKEYS are: * L - Left SHIFT * R - Right SHIFT * A - ALT * C - CONTROL * S - SysRq (Caution: some systems may not like this one) * * EG: mterm LR (Install with LEFT+RIGHT SHIFT for hotkeys) * * This demonstrates the use of the MICRO-C video interface * and communications library functions for the IBM/PC, as well * as the "SAVE_VIDEO", "RESTORE_VIDEO" and "TSR" functions. * * Copyright 1990-1995 Dave Dunfield * All rights reserved. * * Permission granted for personal (non-commercial) use only. * * Compile command: cc mterm -fop */ #include /* Standard I/O definitions */ #include /* Comm I/O definitions */ #include /* Video I/O definitions */ #include /* File I/O definitions */ #include /* Tsr function definitions */ /* Screen output positions */ #define SETROW 3 /* Screen row for settings display */ #define MSGROW 20 /* Screen row for messages */ #define MENROW 7 /* Screen row for menu items */ #define MAICOL 0 /* Screen column for main menu */ #define SUBCOL 20 /* Screen column for sub menu */ #define FILCOL 5 /* Screen column for file prompt */ #define FILSIZ 50 /* Maximum size of file name */ /* XMODEM parameters */ #define BLOCK_SIZE 128 /* size of transmit blocks */ #define RETRYS 10 /* maximum number of retrys */ #define SOH_TIMEOUT 5 /* How long to wait for start of packet */ #define RX_TIMEOUT 2 /* How long in wait for chars in packet */ #define ACK_TIMEOUT 5 /* How long to wait for acknowlege */ /* Line control codes */ #define SOH 0x01 /* start of header */ #define ACK 0x06 /* Acknowledge */ #define NAK 0x15 /* Negative acknowledge */ #define CAN 0x18 /* Cancel */ #define EOT 0x04 /* end of text */ /* Menu text tables (Used by 'vmenu') */ char *main_menu[] = { "Terminal Emulation", "XMODEM Download", "XMODEM Upload", "Serial port config", "Exit to DOS", 0 }; char *setup_menu[] = { "Comm port", "Baudrate", "Data bits", "Parity", "Stop bits", "Xon/Xoff", 0 }; /* Uart configuration data tables */ unsigned baudvalue[] = { _110, _300, _1200, _2400, _4800, _9600, _19200, _38400 }; char *baudtext[] = { "110", "300", "1200", "2400", "4800", "9600", "19200", "38400", 0 }; char *databits[] = { "Five", "Six", "Seven", "Eight", 0 }; char *parity[] = { "Odd", "Even", "Mark", "Space", "None", 0 }; char *onetwo[] = { "One", "Two", 0 }; char *onefour[] = { "One", "Two", "Three", "Four", 0 }; char *flowctrl[] = { "Disabled", "Enabled", 0 }; /* Communications configuration parameters */ int comm = 0, baud = 5, data = 3, par = 4, stop = 0, flow = 1; /* Misc global variables */ setup_selection = 0, transfer_selection = 0; char dfile[FILSIZ+1] = "", ufile[FILSIZ+1] = ""; /* Saved video screens, attributes & cursor position */ char sav_buffer[(25*80)*2], sav_attr; int sav_xy; char video_save_area[SCR_BUF]; /* * Main terminal program menu */ tty_main() { int i; i = 0; /* Default to top of menu */ save_video(video_save_area); redraw: draw_title(); vdraw_box(0, SETROW, 79, 2); show_settings(); for(;;) { message("Select function and press ENTER"); if(vmenu(MAICOL, MENROW, main_menu, 0, &i)) continue; switch(i) { case 0 : /* Terminal Emulation */ if(!open_comm(flow)) break; vcursor_line(); restore_screen(); ansi_term(); save_screen(); vcursor_off(); goto redraw; case 1 : /* Download a file */ if(open_comm(0)) download(dfile); break; case 2 : /* Upload a file */ if(open_comm(0)) upload(dfile); break; case 3 : /* Setup serial port */ setup(); break; case 4 : /* Exit to DOS */ Cclose(); restore_video(video_save_area); return; } } } /* * Open a file for read or write (with overwrite prompt) */ HANDLE openf(char *fname, char rw) { char c, omsg[80], *mode; HANDLE fh; mode = "read"; fh = open(fname, F_READ); /* First try and read the file */ if(rw) { /* If writing the file */ mode = "write"; if(fh) { close(fh); sprintf(omsg, "Overwrite existing %s (Y/N) ?", fname); message(omsg); do { c = toupper(vgetc()); if((c == 0x1B) || (c == 'N')) return 0; } while(c != 'Y'); } fh = open(fname, F_WRITE); } if(!fh) { sprintf(omsg,"Cannot %s %s (Press ENTER)", mode, fname); message(omsg); while(vgetc() != '\n'); } return fh; } /* * Open comm port with correct settings */ open_comm(char flow) { int mode; /* Calculate the communications parameter value */ mode = ((par << 4) & 0x30) | /* parity type */ (data & 0x03) | /* # data bits */ ((stop << 2) & 0x04) | /* # stop bits */ ((par < 4) << 3); /* parity enable */ /* Open the communications port */ if(Copen(comm+1, baudvalue[baud], mode, SET_DTR|SET_RTS|OUTPUT_2)) { message("Cannot open COM port (Press ENTER)"); while(vgetc() != '\n'); return 0; } /* Remove transparency if XON/XOFF flow control */ disable(); Cflags = (flow) ? Cflags & ~TRANSPARENT : Cflags | TRANSPARENT; enable(); return -1; } /* * Draw the title header */ draw_title() { vopen(); V_ATTR = REVERSE; vdraw_box(0, 0, 79, 2); vgotoxy(1, 1); vputf("", 26); vputf("MICRO-Terminal Version 1.3", 52); V_ATTR = NORMAL; vcursor_off(); } /* * Draw the file transfer information box */ info_box(char *mode, char *filename) { vdraw_box(SUBCOL, MENROW+1, 50, 8); vgotoxy(SUBCOL+2, MENROW+3); vprintf("%-19s: %s", mode, filename); vgotoxy(SUBCOL+2, MENROW+5); vputs("Blocks transferred : 0"); vgotoxy(SUBCOL+2, MENROW+7); vputs("Transfer status : "); message("File transfer in progress (ESCAPE to abort)"); } /* * Update the transfer status field */ transfer_status(char *text) { vgotoxy(SUBCOL+23, MENROW+7); vputf(text,10); } /* * Show the current COM port settings */ show_settings() { vgotoxy(18, SETROW+1); vprintf("COM%u: %5s,%2d,%5s,%2d Xon/Xoff %-8s", comm+1, baudtext[baud], data+5, parity[par], stop+1, flowctrl[flow]); } /* * Display a message */ message(char *ptr) { vgotoxy(0, MSGROW); vcleos(); vmessage(38 - strlen(ptr)/2, MSGROW, ptr); } /* * Save the MICRO-TERMINAL video screen. */ save_screen() { sav_xy = V_XY; sav_attr = V_ATTR; copy_seg(get_ds(), sav_buffer, V_BASE, 0, (25*80)*2); } /* * Restore the MICRO-TERMINAL video screen */ restore_screen() { copy_seg(V_BASE, 0, get_ds(), sav_buffer, (25*80)*2); V_ATTR = sav_attr; V_XY = sav_xy; vupdatexy(); } /* * Comm port setup menu handler */ setup() { message("Select setting (ESCAPE to cancel)"); for(;;) { show_settings(); if(vmenu(SUBCOL, MENROW+1, setup_menu, 0, &setup_selection)) return; switch(setup_selection) { case 0 : /* Comm port */ vmenu(SUBCOL+11,MENROW+2,onefour,-1,&comm); break; case 1 : /* baudrate */ vmenu(SUBCOL+11,MENROW+2,baudtext,-1,&baud); break; case 2 : /* Data bits */ vmenu(SUBCOL+11,MENROW+2,databits,-1,&data); break; case 3 : /* Parity */ vmenu(SUBCOL+11,MENROW+2,parity,-1,&par); break; case 4 : /* Stop bits */ vmenu(SUBCOL+11,MENROW+2,onetwo,-1,&stop); break; case 5 : /* Flow control */ vmenu(SUBCOL+11,MENROW+2,flowctrl,-1,&flow); } } } /* * Terminal mode using ANSI (VT100) emulation */ ansi_term() { char c, xy_flag, *ptr; unsigned x, y, state, value, parm, parms[5]; /* ANSI (VT100) Function key translation table */ static char *ansi_keys[] = { "\x1B[A", "\x1B[B", "\x1B[D", "\x1B[C", /* Arrow keys */ "\x1BOR", "\x1BOS", "\x1BOP", "\x1BOQ", /* PgUp, Pgdn, Home, End */ "\x1BOM", "\x1BOm", "\x1BOp", /* Keypad '+','-' Insert */ "\x7F", "\x08", /* Delete & Backspace */ "\x1BOq", "\x1BOr", "\x1BOs", "\x1BOt", /* F1, F2, F3 & F4 */ "\x1BOu", "\x1BOv", "\x1BOw", "\x1BOx", /* F5, F6, F7 & F8 */ "\x1BOy", "\x1BOz", /* F9 & F10 */ "\x1BOl", "\x1BOn", 0, 0 }; /* Ctl: Pu, Pd, Home, End */ xy_flag = -1; /* Force initial cursor update */ state = 0; /* Not receiving a control sequence */ for(;;) { /* Process any input from the comm port */ if((c = Ctestc()) != -1) { xy_flag = -1; if(c == 0x1B) { /* Begin escape sequence */ state = 1; parms[0] = parms[1] = value = parm = 0; } else switch(state) { case 1 : /* Escape already received */ if(c == '[') { state = 2; break; } state = 0; case 0 : /* No special processing */ vputc(c); break; case 2 : /* Waiting for numeric parms */ if(isdigit(c)) { value = (value * 10) + (c - '0'); break; } parms[parm++] = value; /* More to come */ if(c == ';') { value = 0; break; } state = 0; switch(c) { case 'H' : /* Cursor position (1) */ case 'f' : /* Cursor position (2) */ if(y = parms[0]) --y; if(x = parms[1]) --x; vgotoxy(x, y); break; case 'J' : /* Erase in display */ x = V_XY; value ? vclscr() : vcleos(); V_XY = x; break; case 'K' : /* Erase in line */ x = V_XY; if(value) V_XY &= 0xff00; vcleol(); V_XY = x; break; case 'm' : /* Select attributes */ x = 0; do { V_ATTR = (y = parms[x]) ? (y == 4) ? UNDERLINE : REVERSE : NORMAL; } while(++x < parm); } } } else if(xy_flag) { /* Cursor has moved */ vupdatexy(); xy_flag = 0; } /* Process any input from the keyboard */ if(c = vtstc()) { if(c & 0x80) { /* Special function key */ if(!(ptr = ansi_keys[c & 0x7f])) return; while(*ptr) Cputc(*ptr++); } else Cputc((c == '\n') ? '\r' : c); } } } /* * Receive a file in XMODEM protocol */ download() { char rbuffer[BLOCK_SIZE], *error_text, *old_error, ochr; int r, rx_block_num, error_count; HANDLE fh; if(vgets(FILCOL,MSGROW,"Write to file? ",dfile,FILSIZ) || !*dfile) return; if(!(fh = openf(dfile, -1))) return; info_box("Download to file", dfile); error_text = old_error = rx_block_num = 1; error_count = RETRYS; do { if(vtstc() == 0x1b) { /* Console escape */ ochr = CAN; error_text = "CANCELED"; error_count = 0; } else if((r = get_record(rbuffer)) == (rx_block_num & 255)) { error_count = RETRYS; write(rbuffer, BLOCK_SIZE, fh); vgotoxy(SUBCOL+23, MENROW+5); vprintf("%u", rx_block_num++); error_text = "RX PACKET"; ochr = ACK; } else { switch(r) { case -1 : /* Timeout */ error_text = "TIMEOUT"; ochr = NAK; break; case -2 : /* Bad block */ error_text = "BAD BLOCK#"; while(Cgett(RX_TIMEOUT) != -1); ochr = NAK; break; case -3 : /* Bad checksum */ error_text = "BAD CHKSUM"; ochr = NAK; break; case -4 : /* End of file */ error_text = "DONE"; ochr = ACK; break; case -5 : /* Cancel */ error_text = "ABORTED"; ochr = ACK; break; default: /* Block out of sequence */ error_text = "WRONG BLK"; ochr = NAK; } --error_count; } Cputc(ochr); /* Update status message */ if(error_text != old_error) transfer_status(old_error = error_text); } while((r > -3) && error_count); message("Download ended (Press ENTER)"); close(fh); while(vgetc() != '\n'); vclear_box(SUBCOL, MENROW+1, 50, 8); } /* * Read a record in the XMODEM protocol, return the block number * (0-255) if successful, or one of the following return codes: * -1 = Timeout * -2 = Bad block number * -3 = Bad block checksum * -4 = End of file * -5 = Canceled by remote */ get_record(char rbuffer[]) { int c, i, block_num, check_sum; check_sum = 0; i = -2; switch(Cgett(SOH_TIMEOUT)) { case SOH : /* Receive packet */ for(;;) { if((c = Cgett(RX_TIMEOUT)) == -1) /* receive timeout */ break; if(i == -2) /* first block number */ block_num = c; else if(i == -1) { /* second block number */ if((255 & ~c) != block_num) return -2; } else if(i == BLOCK_SIZE) /* checksum at end */ return (check_sum & 0xff) == c ? block_num : -3; else /* data character */ check_sum += (rbuffer[i] = c); ++i; } case -1 : /* timeout on waiting for packet */ return -1; case EOT : /* end of file encountered */ return -4; case CAN : /* cancel protocol */ return -5; } } /* * Transmit a file in XMODEM protocol */ upload() { int i, c, tx_block_num, error_count; char buffer[BLOCK_SIZE], *error_text, *old_error; HANDLE fh; if(vgets(FILCOL,MSGROW,"Read from file? ",ufile,FILSIZ) || !*ufile) return; if(!(fh = openf(ufile, 0))) return; info_box("Upload from file", ufile); tx_block_num = old_error = error_text = 1; error_count = RETRYS; /* Transmit the file data */ while(i = read(buffer, BLOCK_SIZE, fh)) { while(i < 128) buffer[i++] = -1; error_text = "TX PACKET"; while(i = send_record(buffer, tx_block_num)) { switch(i) { case -1 : error_text = "TIMEOUT"; break; case -3 : error_text = "RECV NAK"; break; case -5 : error_text = "ABORTED"; } transfer_status(old_error = error_text); if((i < -3) || !error_count--) break; } if(vtstc() == 0x1b) { /* Console escape */ i = -5; error_text = "CANCELED"; } if(i) { /* Error exit */ Cputc(CAN); break; } error_count = RETRYS; vgotoxy(SUBCOL+23, MENROW+5); vprintf("%u", tx_block_num++); if(error_text != old_error) transfer_status(old_error = error_text); } /* Send the end of file indicator */ error_count = RETRYS; if(!i) for(;;) { Cputc(EOT); if((c = Cgett(ACK_TIMEOUT)) == ACK) { error_text = "DONE"; break; } if(c == CAN) { error_text = "ABORTED"; break; } if(((c == -1) || (c == NAK)) && !error_count--) { error_text = "TIMEOUT"; break; } } transfer_status(error_text); message("Upload ended (Press ENTER)"); close(fh); while(vgetc() != '\n'); vclear_box(SUBCOL, MENROW+1, 50, 8); } /* * Send an record in XMODEM protocol, return 0 if successful * Otherwise, return one of the following: * -1 = Timeout * -2 = Bad block number (N/A) * -3 = Bad block (NAK RECEIVED) * -4 = End of file (N/A) * -5 = Canceled by remote */ send_record(char *buffer, int block_num) { int i, check_sum; char *ptr; check_sum = 0; ptr = buffer; while(Ctestc() != -1); /* purge any received data */ Cputc(SOH); Cputc(block_num); Cputc(~block_num); for(i=0; i < BLOCK_SIZE; ++i) { Cputc(*buffer); check_sum += *buffer++; } Cputc(check_sum); for(;;) switch(Cgett(ACK_TIMEOUT)) { case ACK : /* Packet received ok */ return 0; case NAK : /* Rejected */ return -3; case CAN : /* Remote cancel */ return -5; case -1 : /* Timeout */ return -1; } } /* * Wait for a character from the modem (with timeout) */ Cgett(unsigned timeout) { int h, m, s, old_s; if((h = Ctestc()) != -1) /* Very fast if characters buffered */ return h; get_time(&h, &m, &old_s); do { do { if((h = Ctestc()) != -1) return h; get_time(&h,&m,&s); } while(s == old_s); old_s = s; } while(--timeout); return -1; } /* * Main program, either TSR or execute main tty program menu */ main(int argc, char *argv[]) { int hot_keys; char *ptr; vopen(); vputs("MICRO-Terminal: (Press CTRL-HOME or CTRL-END to exit):\n"); save_screen(); /* If RAM-resident, print startup message & TSR */ if(argc > 1) { draw_title(); printf("\n\n\nCopyright 1990-1995 Dave Dunfield\nAll rights reserved.\n"); vcursor_line(); hot_keys = 0; ptr = argv[1]; while(*ptr) switch(toupper(*ptr++)) { case 'A' : hot_keys |= ALT; break; case 'C' : hot_keys |= CONTROL; break; case 'L' : hot_keys |= L_SHIFT; break; case 'R' : hot_keys |= R_SHIFT; break; case 'S' : hot_keys |= SYS_REQ; break; default: abort("\nInvalid HOTKEY"); } tsr(&tty_main, hot_keys, 2000); } /* Not RAM-resident, execute the program */ vclscr(); /* Return to blank screen */ tty_main(); }