Metropoli BBS
VIEWER: mterm.c MODE: TEXT (ASCII)
/*
 * 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 <stdio.h>			/* Standard I/O definitions */
#include <comm.h>			/* Comm     I/O definitions */
#include <video.h>			/* Video    I/O definitions */
#include <file.h>			/* File		I/O definitions */
#include <tsr.h>			/* 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();
}
[ RETURN TO DIRECTORY ]