Last month we added functions to our library of C tools to support serial ports and modems and explained --ever so briefly --the basics of serial communications. To illustrate the use of those tools and principles, we built a communications program called TINYCOMM. That program uses none of the window, menu, and help functions from our ongoing library collection. Its presentation was focused instead on a terse demonstration of the use of the serial and modem functions. This month the TINYCOMM program spawns an offspring that is named SMALLCOM and that uses the window, menu, and data entry tools from earlier columns to support the user interface. SMALLCOM has more of the features found in a commercial communications program --features, such as uploading and downloading files, a serial port configuration file that can be changed from the program, an editor, hooks for a phone directory and scripts, hooks for file transfer protocols and automatic recognition of, and reaction to, the Hayes modem result codes.
The listings for SMALLCOM are Listing One, smallcom.c, page 131, Listing Two, smallcom.prj, page 136, Listing Three, smallcom.mak, page 136, and Listing Four, smallcom.lnk, page 136. Smallcom.c is the source code for the program. In addition, you will need most of the library source programs published in this column since September when the project began. Smallcom.prj is the Turbo C project make file for building the program from the Turbo C environment. Set the compact memory model and define these global macros either as #define statements in window.h or within the Compiler Defines option (Alt-O/C/D) of the Turbo C environment as shown here:
Smallcom.mak and smallcom.lnk are the Microsoft C make file and linker command file to build the program. They assume that Microsoft C is in the DOS execution path, that the MSC libraries are in the \LIB subdirectory, and that the LIB and INCLUDE environment variables are properly set.
Earlier I mentioned hooks in the program. These hooks will be used in the coming months to add a phone directory, on-line service scripts, and XModem and Kermit file transfer protocols. The hooks are function pointers that initially have NULL values. As we add features, we will initialize the function pointers with the addresses of the functions for the features we want to add. This hooking technique allows us to plan for expansion while preserving most of the existing code.
The phone directory hook is executed by the directory menu selection. The script processor hook is executed when an originating call makes connection with the remote processor. We will decide later what a script process really is. For now it is enough to know that when we need one, it will be there when the call goes through. Scripts are typically related to specific online services, so SMALLCOM will associate scripts with phone numbers in the directory. The file transfer protocol hooks occur at two levels. The higher hook is a call to a function that will allow the user to select a protocol for an upload or download. The address of the function will be in the select_transfer_protocol function pointer hook. That function must return a subscript into an array of function pointers and will be provided later. There is an array for uploads called up_protocol and one for downloads called down_protocol. These two arrays will contain the addresses of the functions that implement the various protocols. The first entries in both arrays are the addresses of the ASCII protocol functions, which are included in this first edition of SMALLCOM. Others will be added later.
To use the modem in ways needed for SMALLCOM, we must change its initialization string from the value used last month. Modify the INITMODEM definition in modem.h to this value:
Look now at the end of Listing One, smallcom.c. There are some additional functions for managing the modem and serial port. These functions were not needed in last month's TINYCOMM program. You might want to move them into serial.c and modem.c as appropriate. I will explain those functions here.
The testcarrier function uses the carrier macro to see if the modem has lost the carrier detect signal. By testing this signal, the program can determine that the remote processor has disconnected. First, though, you must configure your modem for normal operation of the carrier detect signal. Many modems will optionally assert this signal at all times regardless of the connection. See if your modem has a dip switch to turn the signal off and use that as the default option. Remember, not all modems are alike, and not everyone will have their modems set up the same.
The waitforconnect function is used when the program is waiting for an incoming call or waiting for an originating call to be answered. When an incoming call occurs, the function can sense the baud rate of the caller and adjust the local-baud rate accordingly. This process is made possible by the result codes returned by the modem when it makes the connection.
The waitforconnect function calls the waitforresult function, which waits for and returns the modem's result code. The modem returns CONNECT, CONNECT 1200, or CONNECT 2400 strings depending on the caller's baud rate. For an originated call, the modem will return NO CARRIER if the called party answers without a carrier detect signal or NO ANSWER if the called party does not answer. The waitforresult function translates these strings into integer result codes.
The waitforresult function calls the general-purpose waitforstring function. This function will be important to us later when we get into script processing. You pass this function the address of an array of characters, each of which points to a string. The function watches the serial input stream to see if the stream matches any of the strings, and returns the offset of the matching string or -1 if the TIMEOUT value elapses before any match occurs. This use of the waitforstring function uses an array of pointers to the modem's result codes. waitforstring allows you to use the backslash as a wild card character in the string arguments.
In December we introduced help windows into our tool collection and illustrated their use by including the feature in the TWRP tiny word processor. Help windows are recorded in a file named by the load_help function and identified by help window mnemonics found in the data-entry screen FIELD structures --the MENU structures --and the calls to the set_help function. SMALLCOM includes these mnemonics, but I am not publishing the text for the help windows. There is nothing more to be learned from more help text, so they would only serve to use up valuable space in the magazine. You can customize new help windows to your preference, or you can omit them. SMALLCOM includes most of TWRP as its integrated editor, so you can copy December's twrp.hlp to a file you will name smallcom.hlp and add SMALLCOM-specific help windows to it. The mnemonics are found in the source code in smallcom.c.
When SMALLCOM is run, it looks for a file named smallcom.cfg. If that file exists, SMALLCOM reads its contents into the variables that specify the default serial port and modem parameters -- which port is being used, the parity, the number of stop bits, the word length, the baud rate, whether pulse or tone dialing is to be used, and what the default phone number is for calls originated by SMALLCOM. If the file does not exist, the program uses the values coded into those variables when SMALLCOM was compiled. The SMALLCOM menu bar includes a pop-down menu that allows you to change all but the phone number and to write everything including the phone number to a new copy of the configuration file. As things stand right now, that allows you to configure everything except the phone number.
SMALLCOM provides a large window for showing the text passed between processors and uses the menu manager software from our window library. The menu bar at the top of the screen has selections named "File," "Connect," "Parameters," "Editor," and "Directory." You can get to one of these selections by pressing F10 and using the right and left arrow keys to move back and forth among the pop-down menus, or you can press the Alt key along with the first letter of the selection you want. The "File," "Connect," and "Parameters" selections pop-down menus for further selections. The "Editor" and "Directory" selections light up the selection on the menu bar and let you press Enter to execute the text editor or phone directory. The bottom of the screen has a status bar that shows what is going on. The "On/Off Line" message tells if you are connected to a remote computer. The "Direct" message says you have selected a direct, null-modem connection. If you are logging text, the "Logging" message appears. If you are in the modem's answer mode waiting for a call, the "Answering" message appears. While you are uploading or downloading a file the "Uploading" or "Downloading" message appears with as much of the file's path and name as can fit on the status bar. The current phone number -- the one that will be dialed by the "Place Call" selection on the "Connect" menu -- is shown.
The "Parameters" pop-down menu allows you to set and change the serial port and modem parameters. Their initial values are recorded in the smallcom.cfg file. You use the Enter key to step through the valid settings for each parameter. Any changes you make on the menu are in effect for as long as the program is running. If you use the "Write Parameters" selection, the current settings are written to the smallcom.cfg file and will be the default values the next time you run the program.
You cannot communicate through SMALLCOM until you have connected with another computer. The other computer can be running SMALLCOM, it can be an online service or bulletin board system, or it can be a different communications program such as Procomm. The "Connect" pop-down menu has selections for making and breaking connections. When you select "Place Call," SMALLCOM dials the current phone number (shown in the status line at the bottom of the screen on the right side) and waits for the remote computer to answer. The "Answer Call" selection puts the modem into answer mode. When a call comes in, the modem will answer the phone and return a status message that tells the baud rate of the caller. The "Hang Up" selection breaks the connection. The "Direct Connection" command assumes that the connection is direct with a null modem cable and no modem commands are involved.
Once one of these connections has been made, the cursor is in the data window, and you can type messages and read the messages that arrive from the remote computer. You can use the "File" pop-down menu to upload and download files and to turn the system log on and off. The system log is a file named smallcom.log that records everything sent and received by the program. If smallcom.log exists when you turn the option on, new text is appended to the file.
When two computers converse across phone lines, one of them has placed a call and the other has answered. The two roles are somewhat different. The caller will expect the called system to echo any characters that the caller sends and will not display characters locally as they are being typed. The called system does not expect the caller to echo and thus displays its own characters as they are typed. Therefore, SMALLCOM must remember whether it originated or answered the call and not echo or echo accordingly. (An echo is the return of the character just received. I derived this behavior empirically by observing the behavior of other communications programs.) If you call a computer and upload a text file, the answering computer will echo each character because it does not know that you are not typing. If, however, the called computer is told to download the file, it does not echo the characters you sent, because it assumes that a file transfer is underway. These are the rules that SMALLCOM obeys. Therefore, if the called computer is downloading and the caller is typing, the caller will not display the characters on its screen because no echo is being sent. Conversely, if the caller is uploading and the called system is not downloading, the text is displayed at both locations during the transfer. If the caller is uploading and the called system downloading, neither computer displays the text. When you select the "Direct Connection" mode, each computer displays its own keystrokes and does not echo anything back to the other. Confused? So was I when I worked all this out.
In communication jargon these procedures are called half and full duplex transmissions, and some communications programs let you configure for one or the other. My objective was to let the program determine the proper mode based on its recognition of the circumstances at hand.
These echo problems pertain to typing, and they pertain as well to the ASCII file-transfer protocol because that protocol looks to the receiver just like typing. If you don't tell the other computer that you are sending a file, it doesn't see any difference. The ASCII transfer protocol is usually used to send text messages that were prepared off line, but it can also be used to upload files that do not have critical content. When you do that, the receiving program knows a file transfer is in progress because you tell it to download a file. When we get into the binary file-transfer protocols, no such echo concerns will bother us because both computers must be fully aware of what is going on.
The editor selection on the menu bar calls the SMALLCOM text editor, which is an integrated version of the TWRP tiny word processor from December. All the TWRP commands are available, and you can edit a text file of up to 800 lines. You can call this editor while you are on or off line. It can be used to browse messages that you downloaded or to compose answers. Usually you will use it while you are off line to save connect charges.
If you select the directory entry on the SMALLCOM menu bar, nothing happens because that feature is stubbed out for now with a NULL in its hook function pointer. Next month we will add the phone directory. It will allow to add, change, and delete entries and select an entry as the current one to be dialed. Until we have that feature, you must hard-code the phone number into the PHONENO string in February's modem.c.
Some readers find time to write me at the magazine or leave messages for me on CompuServe. (My CIS ID is 71101,1262.) When a reader's question or comment raises an issue or provokes a thought that might interest others, I will address it here.
A reader asked why I was reinventing the wheel. Why another window package, another help package, another menu manager, another editor, another communications program? Why any of this indeed? My answer to him was that the point of the "C Programming" column project is first to bring to you, the readers-at-large, a collection of C language tools that you can use in your applications, and second show by example how these tools are programmed in C. I am not trying to replace any programs that you might already be using, programs that do all and more of what this software does. If all you want is what Procomm does, you should get Procomm. It's a good program, and it costs much less than the time you will devote working with and learning the software tools in this column. If, on the other hand, you want to learn how programs like Procomm are developed and, at the same time, collect the tools that go into such developments, then you are in the right place. Such lessons and software tool collections are the backbone of this column and are consistent with the 12-year legacy of DDJ.
One reader cleverly matched one of my crotchets with one of my programs and suggested I practice what I preach. Being neither preacher nor teacher, I practice what I practice and offer those practices as examples of things that might benefit programmers. Sometimes my programming practices stray from the disciplines held by the distant gurus as proper programming habits. Sometimes I violate my own rules to get the job done. I do not attach as well to dogmatism as I do to pragmatism.
_C PROGRAMMING COLUMN_
by Al Stevens
[LISTING ONE]
Copyright © 1989, Dr. Dobb's JournalSMALLCOM Source Code
TURBOC=1;MSOFT=2;COMPILER=TURBOC
Hooks
More Communications Processes
AT&C1E-0M1S7=60S11=55V1X3S0=0\r
Help Windows
Configuration File
The SMALLCOM Screen Format
Echoes
Editor
Phone Directory
Discussions with Readers
/* ------ smallcom.c ---------- */
#include <conio.h>
#include <stdio.h>
#include <mem.h>
#include <string.h>
#include <ctype.h>
#include <dos.h>
#include <stdlib.h>
#include "window.h"
#include "editor.h"
#include "menu.h"
#include "entry.h"
#include "serial.h"
#include "modem.h"
#include "help.h"
#define ANSWERTIMEOUT 60
#define MAXSTRINGS 15
#define carrier() (inp(MODEMSTATUS) & 0x80)
#define LOGFILE "smallcom.log"
#define HELPFILE "smallcom.hlp"
#define CFGFILE "smallcom.cfg"
#define ALT_P 153
#define ALT_C 174
#define CTRL_C 3
#define WILDCARD '?'
static union REGS rg;
static FILE *logfp, *uploadfp, *downloadfp, *cfg;
static int running=1,connected,answering,savebaud;
int filecount;
extern int direct_connection, TIMEOUT, inserting;
extern char spaces[];
extern struct wn wkw;
extern MENU *mn;
/* ---------- prototypes ----------- */
void fileedit(char *);
static void displaycount(void);
static void smallmenu(int);
static void logserial(int);
static int upload(int, int);
static int download(int, int);
static int call(int, int);
static int directory(int, int);
static int comeditor(int, int);
static answer(int, int);
static directcon(int, int);
static int loginput(int, int);
static int hangup(int, int);
static int quit(int, int);
static int prm(int, int);
static void loadp(void);
static int savep(int, int);
static void set_parameters(void);
static int get_filename(char *);
static void notice(char *);
static void statusline(void);
static void putch_window(int);
void upload_ASCII(FILE *);
void download_ASCII(FILE *);
int keyhit(void);
char *prompt_line(char *, int, char *);
void reset_prompt(char *, int);
static int testcarrier(void);
static int waitforconnect(void);
static void initcom(void);
int waitforresult(void);
int waitforstring(char **, int, int);
static void waitforcall(void);
static void resetline(void);
/* ------- the hook to the phone directory ---------- */
static void (*phone_directory)(void) = NULL;
/* ------- the hook to script processors ---------- */
void (*script_processor)(void); /* filled in by directory */
/* ------- hooks to file transfer protocols --------- */
static int (*select_transfer_protocol)(void) = NULL;
/* ----- up to five upload function pointers ----- */
static void (*up_protocol[5])(FILE *file_pointer) = {
upload_ASCII, NULL, NULL, NULL, NULL
};
/* ----- up to five download function pointers ----- */
static void (*down_protocol[5])(FILE *file_pointer) = {
download_ASCII, NULL, NULL, NULL, NULL
};
/* --------- Files menu ------------ */
static char *fselcs[] = {
"Log Input On/Off",
"Upload a File",
"Download a File",
"Quit",
NULL
};
static char *fhelps[] = {"log","upload","download","quitcom"};
/* ----------- Connect menu -------------- */
static char *cselcs[] = {
"Place Call",
"Answer Call",
"Hang up",
"Direct Connection",
NULL
};
static char *chelps[] = {"call","answer","hangup","direct"};
/* ---------- Parameters menu --------------- */
static char *pselcs[] = {
"Com Port: ",
"Baud Rate: ",
"Data Bits: ",
"Stop Bit(s): ",
"Parity: ",
"Mode of Dialing: ",
"Write Parameters",
NULL
};
static char *phelps[] = {"port","baud","wordlen","stopbits",
"parity","dialmode","writecfg"};
/* ---------- menu selection function tables ----------- */
static int (*ffuncs[])() = {loginput,upload,download,quit};
static int (*cfuncs[])() = {call,answer,hangup,directcon};
static int (*pfuncs[])() = {prm,prm,prm,prm,prm,prm,savep};
static int (*efuncs[])() = {comeditor};
static int (*dfuncs[])() = {directory};
/* ------ horizontal prompt messages ---------- */
char fdesc[]="Message File Operations";
char cdesc[]="Connections to Remote Processor";
char pdesc[]="Set Communications Parameters for Program Start";
char edesc[]="Edit a Text File";
char ddesc[]="The SMALLCOM Telephone Directory";
/* ------- horizontal menu bar ----------- */
static MENU cmn [] = {
{"File", fdesc, fselcs, fhelps, "ludq", ffuncs, 0},
{"Connect", cdesc, cselcs, chelps, "pahd", cfuncs, 0},
{"Parameters", pdesc, pselcs, phelps, "cbdspmw", pfuncs, 0},
{"Editor", edesc, NULL, NULL, "e", efuncs, 0},
{"Directory", ddesc, NULL, NULL, "d", dfuncs, 0},
{NULL}
};
/* ------ filename data entry template and buffer ------- */
static char filename[65], filemask[65];
static FIELD fn_template[] = {
{2,14,1,filename,filemask,NULL},
{0}
};
/* ------ modem result codes ------- */
static char *results[] = {
"\r\nOK\r\n",
"\r\nCONNECT\r\n",
"\r\nRING\r\n",
"\r\nNO CARRIER\r\n",
"\r\nERROR\r\n",
"\r\nCONNECT 1200\r\n",
"\r\nNO DIALTONE\r\n",
"\r\nBUSY\r\n",
"\r\nNO ANSWER\r\n",
"\r\n\r\n",
"\r\nCONNECT 2400\r\n",
NULL
};
extern int COMPORT,PARITY,STOPBITS,WORDLEN,BAUD;
extern char DIAL[], PHONENO[];
/* ================ MAIN ================== */
void main(void)
{
int c;
char *mb;
inserting = FALSE;
load_help(HELPFILE);
loadp();
savebaud = BAUD;
set_parameters();
clear_screen();
mb = display_menubar(cmn);
establish_window(1,2,80,24,TEXTFG,TEXTBG,TRUE);
statusline();
initcom();
gotoxy(2,2);
while (running) {
set_help("smallcom");
testcarrier();
if (keyhit()) {
switch (c = getkey()) {
case F10: smallmenu(0); break;
case ALT_F: smallmenu(1); break;
case ALT_C: smallmenu(2); break;
case ALT_P: smallmenu(3); break;
case ALT_E: smallmenu(4); break;
case ALT_D: smallmenu(5); break;
case CTRL_C:clear_window();
wkw.wx = wkw.wy = 0;
gotoxy(2,2);
break;
case ESC: quit(1,1);
break;
default: if (!(c & 0x80) && connected) {
if (answering
|| direct_connection)
logserial(c=='\r'?'\n':c);
writecomm(c);
if (c == '\r')
writecomm('\n');
}
break;
}
}
if (input_char_ready()) {
logserial(c = readcomm());
if (answering)
writecomm(c);
}
}
if (connected)
hangup(1,1);
release_modem();
restore_menubar(mb);
delete_window();
clear_screen();
}
/* ---------- execute the SMALLCOM menu --------- */
static void smallmenu(int n)
{
window(1,25,80,25);
gotoxy(1,1);
cprintf(spaces);
putch(' ');
current_window();
menu_select(cmn, n);
set_parameters();
statusline();
gotoxy(wkw.wx+2, wkw.wy+2);
}
/* ------ Call menu command ------ */
static int call(hs, vs)
{
if (!connected) {
notice("Dialing");
placecall();
sleep(4);
delete_window();
if ((connected = waitforconnect()) == FALSE) {
statusline();
initmodem();
}
else if (script_processor)
(*script_processor)();
}
return TRUE;
}
/* --------- Direct Connection menu command --------- */
static int directcon(hs, vs)
{
direct_connection ^= 1;
connected |= direct_connection;
return TRUE;
}
/* ------- Hangup menu command ------- */
static int hangup(hs, vs)
{
if (connected) {
notice("Hanging up");
resetline();
delete_window();
}
return TRUE;
}
/* --------- Quit menu command --------- */
static int quit(hs, vs)
{
int c = 0;
notice("Exit to DOS? ");
c = getkey();
delete_window();
running = (tolower(c) != 'y');
return TRUE;
}
/* -------- Log Input menu command -------- */
static int loginput(hs, vs)
{
if (logfp == NULL)
logfp = fopen(LOGFILE, "ab");
else {
fclose(logfp);
logfp = NULL;
}
return TRUE;
}
/* ---------- Upload file menu command ---------- */
static int upload(hs, vs)
{
int pr = 0;
if (!connected) {
error_message("Not connected");
return FALSE;
}
if (uploadfp == NULL) {
setmem(filename, sizeof filename - 1, ' ');
setmem(filemask, sizeof filemask - 1, '_');
if (get_filename(" Upload what file? ") != ESC) {
if ((uploadfp = fopen(filename, "rb")) == NULL)
error_message("Cannot open file");
else {
statusline();
if (select_transfer_protocol)
pr = (*select_transfer_protocol)();
(*up_protocol[pr])(uploadfp);
fclose(uploadfp);
uploadfp = NULL;
}
}
}
return TRUE;
}
/* ------ upload a file with ASCII transfer protocol ----- */
void upload_ASCII(FILE *fp)
{
int c;
while ((c = fgetc(fp)) != EOF) {
writecomm(c);
displaycount();
if (input_char_ready())
logserial(readcomm());
if (keyhit())
if (getch() == ESC)
break;
if (!testcarrier())
break;
}
filecount = 0;
if (connected)
writecomm(EOF);
}
/* ---------- Download file menu command ---------- */
static int download(hs, vs)
{
int pr = 0, save_timeout;
if (!connected) {
error_message("Not connected");
return FALSE;
}
setmem(filename, sizeof filename - 1, ' ');
setmem(filemask, sizeof filemask - 1, '_');
if (get_filename(" Download what file? ") != ESC) {
downloadfp = fopen(filename, "wb");
statusline();
if (select_transfer_protocol)
pr = (*select_transfer_protocol)();
save_timeout = TIMEOUT;
TIMEOUT = 60;
(*down_protocol[pr])(downloadfp);
TIMEOUT = save_timeout;
fclose(downloadfp);
downloadfp = NULL;
}
return TRUE;
}
/* ----- download a file with ASCII transfer protocol ----- */
void download_ASCII(FILE *fp)
{
int c = 0;
while (TRUE) {
if (keyhit()) {
if ((c = getkey()) == ESC)
break;
writecomm(c);
if (!answering)
logserial(readcomm());
}
c = readcomm() & 127;
if (c == 0 || c == 0x7f)
break;
fputc(c, fp);
displaycount();
if (!testcarrier())
break;
}
}
/* --- echo modem input and write to the log if selected --- */
static void logserial(int c)
{
putch_window(c);
if (logfp)
fputc(c, logfp);
}
/* -------- read a file name ------------- */
static int get_filename(char *ttl)
{
int rtn;
establish_window(1,23,80,25,ENTRYFG,ENTRYBG,TRUE);
window_title(ttl);
gotoxy(3,2);
cputs("File name:");
rtn = data_entry(fn_template, TRUE, 1);
delete_window();
return rtn;
}
/* -------- small message ------------ */
static void notice(char *s)
{
int lf = (80-strlen(s))/2-1;
int rt = lf+strlen(s)+2;
establish_window(lf,11,rt,13,HELPFG,HELPBG,TRUE);
gotoxy(2,2);
cputs(s);
}
/* ---- comm and modem parameter menu commands ----- */
static int prm(hs, vs)
{
switch (vs) {
case 1: COMPORT ^= 3; /* flip between 1 and 2 */
break;
case 2: BAUD *= 2; /* 110,150,300, */
if (BAUD == 220) /* 600,1200,2400 */
BAUD = 150;
if (BAUD == 4800)
BAUD = 110;
break;
case 3: WORDLEN ^= 0xf; /* flip between 7 and 8 */
break;
case 4: STOPBITS ^= 3; /* flip between 1 and 2 */
break;
case 5: if (++PARITY == 3) /* 0, 1, 2 */
PARITY = 0;
break;
case 6: DIAL[3] = DIAL[3] == 'T' ? 'P' : 'T';
break;
default:
break;
}
set_parameters();
return FALSE;
}
/* ------ post the parameters into the menu display ------- */
static void set_parameters(void)
{
static char *pars[] = {"None", " Odd", "Even"};
static char *mode[] = {"Pulse", " Tone"};
pselcs[0][strlen(pselcs[0])-1] = '0' + COMPORT;
sprintf(&pselcs[1][strlen(pselcs[1])-4],"%4d",BAUD);
pselcs[2][strlen(pselcs[2])-1] = '0' + WORDLEN;
pselcs[3][strlen(pselcs[3])-1] = '0' + STOPBITS;
sprintf(&pselcs[4][strlen(pselcs[4])-4],"%s",pars[PARITY]);
sprintf(&pselcs[5][strlen(pselcs[5])-5],"%s",
mode[DIAL[3]=='T']);
}
/* ------- load the configuration file ---------- */
static void loadp(void)
{
if ((cfg = fopen(CFGFILE, "r")) != NULL) {
fscanf(cfg,"%d %d %d %d %d %c %s",
&COMPORT,&PARITY,&STOPBITS,&WORDLEN,&BAUD,&DIAL[3],
&PHONENO[0]);
fclose(cfg);
}
}
/* ---------- Write Parameters menu command ---------- */
static int savep(hs, vs)
{
cfg = fopen(CFGFILE, "w");
fprintf(cfg, "%d %d %d %d %d %c %s",
COMPORT,PARITY,STOPBITS,WORDLEN,BAUD,DIAL[3],PHONENO);
fclose(cfg);
initcom();
return FALSE;
}
/* --------- Editor menu command --------------- */
static int comeditor(hs, vs)
{
extern int MAXLINES, inserting;
MAXLINES = 800;
mn = NULL;
fileedit("");
inserting = FALSE;
insert_line();
return TRUE;
}
/* --------- Directory menu command --------------- */
static int directory(hs, vs)
{
if (phone_directory) {
mn = NULL;
(*phone_directory)();
}
return TRUE;
}
/* ----------- display a status line ----------- */
static void statusline(void)
{
char stat[81];
static char *st = NULL;
sprintf(stat,
" %s Line %s %s %s %-12.12s %-14.14s F10:Menu",
(connected ? " On" : "Off"),
(direct_connection ? "Direct" : " "),
(logfp ? "Logging" : " "),
((answering & !connected)
? "Answering " :
uploadfp ? "Uploading " :
downloadfp ? "Downloading" : " "),
(uploadfp||downloadfp ? filename : " "),
*PHONENO ? PHONENO : "No Phone #");
st = prompt_line(stat, 25, st);
}
/* ------- write the file count into the status line ------- */
static void displaycount(void)
{
filecount++;
if ((filecount % 10) == 0) {
window(1,25,80,25);
textcolor(MENUFG);
textbackground(MENUBG);
gotoxy(50,1);
cprintf("%5d", filecount);
current_window();
gotoxy(wkw.wx+2, wkw.wy+2);
}
}
/* ----- write a one-liner prompt saving video memory ----- */
char *prompt_line(char *s, int y, char *t)
{
if (t == NULL)
if ((t = malloc(160)) != NULL)
gettext(1,y,80,y,t);
window(1,y,80,y);
textcolor(MENUFG);
textbackground(MENUBG);
gotoxy(1,1);
cprintf(spaces);
putch(' ');
gotoxy(1,1);
cprintf(s);
current_window();
return t;
}
/* ------- reset the one-liner prompt line --------- */
void reset_prompt(char *s, int y)
{
puttext(1,y,80,y,s);
free(s);
}
/* -------- write a character to the user's window -------- */
static void putch_window(int c)
{
gotoxy(wkw.wx+2, wkw.wy+2);
switch (c) {
case '\t': while (wkw.wx % 4)
putch_window(' ');
break;
case '\b': if (wkw.wx)
--wkw.wx;
break;
default: putch(c);
wkw.wx++;
if (wkw.wx < wkw.wd-2)
break;
case '\n': if (wkw.wy < wkw.ht-1)
wkw.wy++;
else {
scroll_window(1);
writeline(2, wkw.wy+2, spaces+1);
}
case '\r': wkw.wx = 0;
break;
}
gotoxy(wkw.wx+2, wkw.wy+2);
}
/* ------------ wait for a call ------------ */
static void waitforcall(void)
{
answercall();
if ((connected = answering = waitforconnect()) == FALSE) {
statusline();
initmodem();
}
}
/* ---- wait for a line connection, reset baud rate ---- */
static int waitforconnect(void)
{
extern int BAUD;
int baud = 0;
while (baud == 0)
switch (waitforresult()) {
case 1: baud = 300; break; /* CONNECT */
case 5: baud = 1200; break; /* CONNECT 1200 */
case 10: baud = 2400; break; /* CONNECT 2400 */
case 0: /* OK */
case 2: break; /* RING */
case 3: /* NO CARRIER */
case 4: /* ERROR */
case 7: /* BUSY */
case 8: /* NO ANSWER */
case -1: baud = -1; break; /* time-out */
default: break; /* anything else */
}
if (baud != -1 && baud != BAUD) {
savebaud = BAUD;
BAUD = baud;
initcomport();
}
return (baud != -1);
}
/* ---- wait for a modem result (0-10). -1 if timed out ---- */
int waitforresult(void)
{
return waitforstring(results, ANSWERTIMEOUT, 0);
}
/* --------- wait for a string from the serial port -------- */
int waitforstring(char *tbl[], int wait, int wildcard)
{
int c, i, done = FALSE;
char *sr[MAXSTRINGS];
for (i = 0; tbl[i] != NULL; i++)
sr[i] = tbl[i];
while (!done) {
set_timer(wait);
while (!input_char_ready()) {
if (timed_out())
return -1;
if (keyhit())
if ((c = getkey()) == ESC)
return -1;
}
logserial(c = readcomm());
for (i = 0; tbl[i] != NULL; i++) {
if (c==*(sr[i]) ||
(wildcard && *(sr[i])==wildcard)) {
if (*(++(sr[i])) == '\0') {
done = TRUE;
break;
}
}
else
sr[i] = tbl[i];
}
}
return i;
}
/* ----- initialize from serial and modem parameters ----- */
static void initcom(void)
{
notice("Initializing Modem");
initmodem();
delete_window();
}
/* ----- test carrier detect -------- */
static int testcarrier(void)
{
if (!direct_connection && connected && carrier() == FALSE)
resetline();
return connected;
}
/* ------ disconnect and reestablish the serial port ------ */
static void resetline(void)
{
answering = connected = FALSE;
statusline();
disconnect();
BAUD = savebaud;
initcomport();
}
/* --------- answer a call ----------- */
static int answer(hs, vs)
{
answering = 1;
statusline();
gotoxy(wkw.wx+2, wkw.wy+2);
waitforcall();
return TRUE;
}
#if COMPILER==TURBOC
/* --------- use bios to test for a keystroke -------- */
int keyhit(void)
{
rg.h.ah = 1;
int86(0x16, &rg, &rg);
return ((rg.x.flags & 0x40) == 0);
}
#endif
[LISTING TWO]
smallcom (serial.h,modem.h,editor.h,window.h,menu.h,entry.h,help.h)
editshel (editor.h, menu.h, entry.h, help.h, window.h)
editor (editor.h, window.h)
help (help.h, window.h)
modem (serial.h, modem.h)
serial (serial.h)
entry (entry.h, window.h)
menu (menu.h, window.h)
window (window.h)
[LISTING THREE]
#
# SMALLCOM.MAK: make file for SMALLCOM.EXE with Microsoft C/MASM
#
.c.obj:
cl /DMSOFT=1 /DTURBOC=2 /DCOMPILER=MSOFT -c -W3 -Gs -AC $*.c
smallcom.obj : smallcom.c serial.h modem.h menu.h entry.h \
help.h window.h
modem.obj : modem.c serial.h modem.h
serial.obj : serial.c serial.h
entry.obj : entry.c entry.h window.h
menu.obj : menu.c menu.h window.h
help.obj : help.c help.h window.h
editshel.obj : editshel.c editor.h menu.h entry.h help.h \
window.h
editor.obj : editor.c editor.h window.h
window.obj : window.c window.h
microsft.obj : microsft.c
vpeek.obj : vpeek.asm
masm /MX vpeek;
keyhit.obj : keyhit.asm
masm /MX keyhit;
smallcom.exe : smallcom.obj modem.obj serial.obj editor.obj \
editshel.obj entry.obj menu.obj help.obj \
window.obj keyhit.obj vpeek.obj microsft.obj
link @smallcom.lnk
[LISTING FOUR]
smallcom+
modem+
serial+
entry+
menu+
editor+
editshel+
help+
window+
vpeek+
keyhit+
microsft
smallcom
nul
\lib\clibce