Please consider a donation to the Higher Intellect project. See https://preterhuman.net/donate.php or the Donate to Higher Intellect page for more info.

KERMIT FOR OS/2

From Higher Intellect Vintage Wiki
_KERMIT FOR OS/2_
by Brian R. Anderson

[LISTING ONE]


MODULE PCKermit;
(**************************************************************************)
(*                                                                        *)
(*                  PCKermit  --  by Brian R. Anderson                    *)
(*                         Copyright (c) 1990                             *)
(*                                                                        *)
(*  PCKermit is an implementation of the Kermit file transfer protocol    *)
(*  developed at Columbia University.  This (OS/2 PM) version is a        *) 
(*  port from the DOS version of Kermit that I wrote two years ago.       *)
(*  My original DOS version appeared in the May 1989 issue of DDJ.        *)
(*                                                                        *)
(*  The current version includes emulation of the TVI950 Video Display    *)
(*  Terminal for interaction with IBM mainframes (through the IBM 7171).  *)
(*                                                                        *)
(**************************************************************************)

   FROM SYSTEM IMPORT
      ADR;
    
   FROM OS2DEF IMPORT
      HAB, HWND, HPS, NULL, ULONG;

   FROM PMWIN IMPORT
      MPFROM2SHORT, HMQ, QMSG, CS_SIZEREDRAW,  WS_VISIBLE, FS_ICON,      
      FCF_TITLEBAR, FCF_SYSMENU, FCF_SIZEBORDER, FCF_MINMAX, FCF_ACCELTABLE,
      FCF_SHELLPOSITION, FCF_TASKLIST, FCF_MENU, FCF_ICON, 
      SWP_MOVE, SWP_SIZE, SWP_MAXIMIZE, 
      HWND_DESKTOP, FID_SYSMENU, SC_CLOSE, MIA_DISABLED, MM_SETITEMATTR,
      WinInitialize, WinCreateMsgQueue, WinGetMsg, WinDispatchMsg, WinSendMsg,
      WinRegisterClass, WinCreateStdWindow, WinDestroyWindow, WinWindowFromID,
      WinDestroyMsgQueue, WinTerminate, WinSetWindowText, 
      WinSetWindowPos, WinQueryWindowPos;

   FROM KH IMPORT
      IDM_KERMIT;

   FROM Shell IMPORT
      Class, Title, Child, WindowProc, ChildWindowProc, 
      FrameWindow, ClientWindow, SetPort, Pos;

   
   CONST
      QUEUE_SIZE = 1024;   (* Large message queue for async events *)

   VAR
      AnchorBlock : HAB;
      MessageQueue : HMQ;
      Message : QMSG;
      FrameFlags : ULONG;
      hsys : HWND;
   

BEGIN   (* main *)
   AnchorBlock := WinInitialize(0);
    
   IF AnchorBlock # 0 THEN
      MessageQueue := WinCreateMsgQueue (AnchorBlock, QUEUE_SIZE);
    
      IF MessageQueue # 0 THEN
         (* Register the parent window class *)
         WinRegisterClass (
             AnchorBlock,
             ADR (Class),
             WindowProc,
             CS_SIZEREDRAW, 0);
         
         (* Register a child window class *)
         WinRegisterClass (
             AnchorBlock,
             ADR (Child),
             ChildWindowProc,
             CS_SIZEREDRAW, 0);
         
         (* Create a standard window *)
         FrameFlags := FCF_TITLEBAR + FCF_MENU + FCF_MINMAX + 
                       FCF_SYSMENU + FCF_SIZEBORDER + FCF_TASKLIST + 
                       FCF_ICON + FCF_SHELLPOSITION + FCF_ACCELTABLE;
         
         FrameWindow := WinCreateStdWindow (
                  HWND_DESKTOP,           (* handle of the parent window *)
                  WS_VISIBLE + FS_ICON,   (* the window style *)
                  FrameFlags,             (* the window flags *)
                  ADR(Class),             (* the window class *)
                  NULL,                   (* the title bar text *)
                  WS_VISIBLE,             (* client window style *)
                  NULL,                   (* handle of resource module *)
                  IDM_KERMIT,             (* resource id *)
                  ClientWindow            (* returned client window handle *)
         );
          
         IF FrameWindow # 0 THEN
            (* Disable the CLOSE item on the system menu *)
            hsys := WinWindowFromID (FrameWindow, FID_SYSMENU);
            WinSendMsg (hsys, MM_SETITEMATTR,
               MPFROM2SHORT (SC_CLOSE, 1),
               MPFROM2SHORT (MIA_DISABLED, MIA_DISABLED));

            (* Expand Window to Nearly Full Size, And Display the Title *)
            WinQueryWindowPos (HWND_DESKTOP, ADR (Pos));
            WinSetWindowPos (FrameWindow, 0, 
               Pos.x + 3, Pos.y + 3, Pos.cx - 6, Pos.cy - 6, 
               SWP_MOVE + SWP_SIZE);
            WinSetWindowText (FrameWindow, ADR (Title));
            
            SetPort;   (* Try to initialize communications port *)
         
            WHILE WinGetMsg(AnchorBlock, Message, NULL, 0, 0) # 0 DO
               WinDispatchMsg(AnchorBlock, Message);
            END;
          
            WinDestroyWindow(FrameWindow);
         END;
         WinDestroyMsgQueue(MessageQueue);
      END;
      WinTerminate(AnchorBlock);
   END;
END PCKermit.

[LISTING TWO]

DEFINITION MODULE Shell;

   FROM OS2DEF IMPORT
      USHORT, HWND;

   FROM PMWIN IMPORT
      MPARAM, MRESULT, SWP;

   EXPORT QUALIFIED
      Class, Child, Title, FrameWindow, ClientWindow,
      ChildFrameWindow, ChildClientWindow, Pos, SetPort, 
      WindowProc, ChildWindowProc;
         
   CONST
      Class = "PCKermit";
      Child ="Child";
      Title = "PCKermit -- Microcomputer to Mainframe Communications";

   
   VAR
      FrameWindow : HWND;
      ClientWindow : HWND;   
      ChildFrameWindow : HWND;
      ChildClientWindow : HWND;
      Pos : SWP;   (* Screen Dimensions: position & size *)
      comport : CARDINAL;


   PROCEDURE SetPort;
   
   PROCEDURE WindowProc ['WindowProc'] (
      hwnd : HWND;
      msg  : USHORT;   
      mp1  : MPARAM; 
      mp2  : MPARAM) : MRESULT [LONG, LOADDS];

   PROCEDURE ChildWindowProc ['ChildWindowProc'] (
      hwnd : HWND;
      msg  : USHORT;   
      mp1  : MPARAM; 
      mp2  : MPARAM) : MRESULT [LONG, LOADDS];

END Shell.


[LISTING THREE]

DEFINITION MODULE Term;   (* TVI950 Terminal Emulation For Kermit *)

   EXPORT QUALIFIED
      WM_TERM, WM_TERMQUIT, 
      Dir, TermThrProc, InitTerm, PutKbdChar, PutPortChar;

   CONST
      WM_TERM = 4000H;
      WM_TERMQUIT = 4001H;
   
      
   PROCEDURE Dir (path : ARRAY OF CHAR);
   (* Displays a directory *)
   
   PROCEDURE TermThrProc;
   (* Thread to get characters from port, put into buffer, send message *)
   
   PROCEDURE InitTerm;
   (* Clear Screen, Home Cursor, Get Ready For Terminal Emulation *)
   
   PROCEDURE PutKbdChar (ch1, ch2 : CHAR);
   (* Process a character received from the keyboard *)

   PROCEDURE PutPortChar (ch : CHAR);
   (* Process a character received from the port *)
   
END Term.



[LISTING FOUR]

DEFINITION MODULE Screen;
(* Module to perform "low level" screen functions (via AVIO) *)

   FROM PMAVIO IMPORT
      HVPS;

   EXPORT QUALIFIED
      NORMAL, HIGHLIGHT, REVERSE, attribute, ColorSet, hvps,
      White, Green, Amber, Color1, Color2,
      ClrScr, ClrEol, GotoXY, GetXY,	
      Right, Left, Up, Down, Write, WriteLn, WriteString,
      WriteInt, WriteHex, WriteAtt;

   
   VAR	  
      NORMAL : CARDINAL;
      HIGHLIGHT : CARDINAL;	
      REVERSE : CARDINAL;
      attribute : CARDINAL;	
      ColorSet : CARDINAL;
      hvps : HVPS;   (* presentation space used by screen module *)
         

   PROCEDURE White;
   (* Sets up colors: Monochrome White *)
      
   PROCEDURE Green;
   (* Sets up colors: Monochrome Green *)
      
   PROCEDURE Amber;
   (* Sets up colors: Monochrome Amber *)
      
   PROCEDURE Color1;
   (* Sets up colors: Blue, Red, Green *)
      
   PROCEDURE Color2;
   (* Sets up colors: Green, Magenta, Cyan *)
   
   PROCEDURE ClrScr;	  
   (* Clear the screen, and home the cursor *)	 
   
   PROCEDURE ClrEol;	  
   (* clear from the current cursor position to the end of the line *)	 
   
   PROCEDURE Right;	 
   (* move cursor to the right *)	
   
   PROCEDURE Left;	
   (* move cursor to the left *)	  
   
   PROCEDURE Up;	 
   (* move cursor up *)	  
   
   PROCEDURE Down;	
   (* move cursor down *)	 
   
   PROCEDURE GotoXY (col, row : CARDINAL);	
   (* position cursor at column, row *)	
   
   PROCEDURE GetXY (VAR col, row : CARDINAL);	
   (* determine current cursor position *)	

   PROCEDURE Write (c : CHAR);
   (* Write a Character, Teletype Mode *)

   PROCEDURE WriteString (str : ARRAY OF CHAR);
   (* Write String, Teletype Mode *)

   PROCEDURE WriteInt (n : INTEGER; s : CARDINAL);
   (* Write Integer, Teletype Mode *)
   
   PROCEDURE WriteHex (n, s : CARDINAL);
   (* Write a Hexadecimal Number, Teletype Mode *)
   
   PROCEDURE WriteLn;
   (* Write <cr> <lf>, Teletype Mode *)
   
   PROCEDURE WriteAtt (c : CHAR);	
   (* write character and attribute at cursor position *)	
   
END Screen.

[LISTING FIVE]

DEFINITION MODULE PAD;   (* Packet Assembler/Disassembler for Kermit *)

   FROM PMWIN IMPORT
      MPARAM;
      
   EXPORT QUALIFIED
      WM_PAD, PAD_Quit, PAD_Error, PacketType, yourNPAD, yourPADC, yourEOL, 
      Aborted, sFname, Send, Receive, DoPADMsg;

   CONST
      WM_PAD = 5000H;
      PAD_Quit = 0;
      PAD_Error = 20;
              
   TYPE
      (* PacketType used in both PAD and DataLink modules *)
      PacketType = ARRAY [1..100] OF CHAR;
      
   VAR
      (* yourNPAD, yourPADC, and yourEOL used in both PAD and DataLink *)
      yourNPAD : CARDINAL;   (* number of padding characters *)
      yourPADC : CHAR;       (* padding characters *)
      yourEOL  : CHAR;       (* End Of Line -- terminator *)
      sFname : ARRAY [0..20] OF CHAR;
      Aborted : BOOLEAN;

   PROCEDURE Send;
   (* Sends a file after prompting for filename *)
   
   PROCEDURE Receive;
   (* Receives a file (or files) *)

   PROCEDURE DoPADMsg (mp1, mp2 : MPARAM);
   (* Output messages for Packet Assembler/Disassembler *)
            
END PAD.


[LISTING SIX]

DEFINITION MODULE DataLink;   (* Sends and Receives Packets for PCKermit *)

   FROM PMWIN IMPORT
      MPARAM;
      
   FROM PAD IMPORT
      PacketType;
      
   EXPORT QUALIFIED
      WM_DL, FlushUART, SendPacket, ReceivePacket, DoDLMsg;

   CONST
      WM_DL = 6000H;
      
   PROCEDURE FlushUART;
   (* ensure no characters left in UART holding registers *)
    
   PROCEDURE SendPacket (s : PacketType);
   (* Adds SOH and CheckSum to packet *)
   
   PROCEDURE ReceivePacket (VAR r : PacketType) : BOOLEAN;
   (* strips SOH and checksum -- returns status: TRUE= good packet       *)
   (* received;  FALSE = timed out waiting for packet or checksum error  *)
   
   PROCEDURE DoDLMsg (mp1, mp2 : MPARAM);
   (* Process DataLink Messages *)
   
END DataLink.


[LISTING SEVEN]

(*************************************************************)
(*                                                           *)
(*                Copyright (C) 1988, 1989                   *)
(*                 by Stony Brook Software                   *)
(*                                                           *)
(*                   All rights reserved.                    *)
(*                                                           *)
(*************************************************************)

DEFINITION MODULE CommPort;

   TYPE
      CommStatus = (                
               Success,   
               InvalidPort,  
               InvalidParameter,    
               AlreadyReceiving,    
               NotReceiving,  
               NoCharacter,  
               FramingError,  
               OverrunError,  
               ParityError,  
               BufferOverflow,  
               TimeOut   
      );   

      BaudRate = (  
               Baud110,   
               Baud150,   
               Baud300,   
               Baud600,   
               Baud1200,  
               Baud2400,  
               Baud4800,  
               Baud9600,  
               Baud19200  
      );   
      
      DataBits = [7..8];  
      StopBits = [1..2];  
      Parity = (Even, Odd, None);  


   PROCEDURE InitPort(port : CARDINAL; speed : BaudRate; data : DataBits;
                          stop : StopBits; check : Parity) : CommStatus;

   PROCEDURE StartReceiving(port, bufsize : CARDINAL) : CommStatus;

   PROCEDURE StopReceiving(port : CARDINAL) : CommStatus;

   PROCEDURE GetChar(port : CARDINAL; VAR ch : CHAR) : CommStatus;

   PROCEDURE SendChar(port : CARDINAL; ch : CHAR; modem : BOOLEAN) : CommStatus;

END CommPort.


[LISTING EIGHT]

DEFINITION MODULE Files;   (* File I/O for Kermit *)

   FROM FileSystem IMPORT
      File;
      
   EXPORT QUALIFIED
      Status, FileType, Open, Create, CloseFile, Get, Put, DoWrite;
         
   TYPE
      Status = (Done, Error, EOF);
      FileType = (Input, Output);
   
   PROCEDURE Open (VAR f : File; name : ARRAY OF CHAR) : Status;
   (* opens an existing file for reading, returns status *)
   
   PROCEDURE Create (VAR f : File; name : ARRAY OF CHAR) : Status;
   (* creates a new file for writing, returns status *)
   
   PROCEDURE CloseFile (VAR f : File; Which : FileType) : Status;
   (* closes a file after reading or writing *)
   
   PROCEDURE Get (VAR f : File; VAR ch : CHAR) : Status;
   (* Reads one character from the file, returns status *)
   
   PROCEDURE Put (ch : CHAR);
   (* Writes one character to the file buffer *)
   
   PROCEDURE DoWrite (VAR f : File) : Status;
   (* Writes buffer to disk only if nearly full *)
   
END Files.



[LISTING NINE]

IMPLEMENTATION MODULE Shell;

   FROM SYSTEM IMPORT
      ADDRESS, ADR;
    
   IMPORT ASCII;
   
   FROM OS2DEF IMPORT
      LOWORD, HIWORD, HWND, HDC, HPS, RECTL, USHORT, NULL, ULONG;

   FROM Term IMPORT
      WM_TERM, WM_TERMQUIT, 
      Dir, TermThrProc, InitTerm, PutKbdChar, PutPortChar;

   FROM PAD IMPORT
      WM_PAD, PAD_Quit, PAD_Error, DoPADMsg, Aborted, sFname, Send, Receive;

   FROM DataLink IMPORT
      WM_DL, DoDLMsg;
            
   FROM Screen IMPORT
      hvps, ColorSet, White, Green, Amber, Color1, Color2, ClrScr, WriteLn;
      
   FROM DosCalls IMPORT
      DosCreateThread, DosSuspendThread, DosResumeThread, DosSleep;

   FROM PMAVIO IMPORT
      VioCreatePS, VioAssociate, VioDestroyPS, VioShowPS, WinDefAVioWindowProc,
      FORMAT_CGA, HVPS;
      
   FROM PMWIN IMPORT
      MPARAM, MRESULT, SWP, PSWP, 
      WS_VISIBLE, FCF_TITLEBAR, FCF_SIZEBORDER, FCF_SHELLPOSITION,
      WM_SYSCOMMAND, WM_MINMAXFRAME, SWP_MINIMIZE, HWND_DESKTOP, 
      WM_PAINT, WM_QUIT, WM_COMMAND, WM_INITDLG, WM_CONTROL, WM_HELP,
      WM_INITMENU, WM_SIZE, WM_DESTROY, WM_CREATE, WM_CHAR, 
      BM_SETCHECK, MBID_OK, MB_OK, MB_OKCANCEL, 
      KC_CHAR, KC_CTRL, KC_VIRTUALKEY, KC_KEYUP,
      SWP_SIZE, SWP_MOVE, SWP_MAXIMIZE, SWP_RESTORE,
      MB_ICONQUESTION, MB_ICONASTERISK, MB_ICONEXCLAMATION,
      FID_MENU, MM_SETITEMATTR, MM_QUERYITEMATTR, 
      MIA_DISABLED, MIA_CHECKED, MPFROM2SHORT,
      WinCreateStdWindow, WinDestroyWindow,
      WinOpenWindowDC, WinSendMsg, WinQueryDlgItemText, WinInvalidateRect,
      WinDefWindowProc, WinBeginPaint, WinEndPaint, WinQueryWindowRect,
      WinSetWindowText, WinSetFocus, WinDlgBox, WinDefDlgProc, WinDismissDlg, 
      WinMessageBox, WinPostMsg, WinWindowFromID, WinSendDlgItemMsg,
      WinSetWindowPos, WinSetActiveWindow;

   FROM PMGPI IMPORT
      GpiErase;

   FROM KH IMPORT
      IDM_KERMIT, IDM_FILE, IDM_OPTIONS, IDM_SENDFN, ID_SENDFN,
      IDM_DIR, IDM_CONNECT, IDM_SEND, IDM_REC, IDM_DIRPATH, ID_DIRPATH, 
      IDM_DIREND, IDM_QUIT, IDM_ABOUT, IDM_HELPMENU, IDM_TERMHELP, 
      IDM_COMPORT, IDM_BAUDRATE, IDM_DATABITS, IDM_STOPBITS, IDM_PARITY, 
      COM_OFF, ID_COM1, ID_COM2, PARITY_OFF, ID_EVEN, ID_ODD, ID_NONE, 
      DATA_OFF, ID_DATA7, ID_DATA8, STOP_OFF, ID_STOP1, ID_STOP2,
      BAUD_OFF, ID_B110, ID_B150, ID_B300, ID_B600, ID_B1200, ID_B2400, 
      ID_B4800, ID_B9600, ID_B19K2,
      IDM_COLORS, IDM_WHITE, IDM_GREEN, IDM_AMBER, IDM_C1, IDM_C2;

   FROM CommPort IMPORT
      CommStatus, BaudRate, DataBits, StopBits, Parity, InitPort,
      StartReceiving, StopReceiving;
   
   FROM Strings IMPORT
      Assign, Append, AppendChar;
   
   
   CONST
      WM_SETMAX = 7000H;
      WM_SETFULL = 7001H;
      WM_SETRESTORE = 7002H;
      NONE = 0;   (* no port yet initialized *)
      STKSIZE = 4096;
      BUFSIZE = 4096;   (* Port receive buffers: room for two full screens *)
      PortError = "Port Is Already In Use -- EXIT? (Cancel Trys Another Port)";
      ESC = 33C;
      
   
   VAR
      FrameFlags : ULONG;
      TermStack : ARRAY [1..STKSIZE] OF CHAR;
      Stack : ARRAY [1..STKSIZE] OF CHAR;
      TermThr : CARDINAL;
      Thr : CARDINAL;
      hdc : HDC;
      frame_hvps, child_hvps : HVPS;
      TermMode : BOOLEAN;
      Path : ARRAY [0..60] OF CHAR;
      Banner : ARRAY [0..40] OF CHAR;
      PrevComPort : CARDINAL;
      Settings : ARRAY [0..1] OF RECORD
                                    baudrate : CARDINAL;
                                    databits : CARDINAL;
                                    parity : CARDINAL;
                                    stopbits : CARDINAL;
                                 END;    

   PROCEDURE SetFull;
   (* Changes window to full size *)
      BEGIN
         WinSetWindowPos (FrameWindow, 0,		
            Pos.x + 3, Pos.y + 3, Pos.cx - 6, Pos.cy - 6,
            SWP_MOVE + SWP_SIZE);
      END SetFull;
      
      		
   PROCEDURE SetRestore;
   (* Changes window to full size FROM maximized *)
      BEGIN
         WinSetWindowPos (FrameWindow, 0,
            Pos.x + 3, Pos.y + 3, Pos.cx - 6, Pos.cy - 6,		
            SWP_MOVE + SWP_SIZE + SWP_RESTORE);		  
      END SetRestore;
      
                        		  
   PROCEDURE SetMax;
   (* Changes window to maximized *)
      BEGIN
         WinSetWindowPos (FrameWindow, 0,                           
            Pos.x + 3, Pos.y + 3, Pos.cx - 6, Pos.cy - 6,		
            SWP_MOVE + SWP_SIZE + SWP_MAXIMIZE);	
      END SetMax;
      
                       								                  
   PROCEDURE SetBanner;
   (* Displays Abbreviated Program Title + Port Settings in Title Bar *)

      CONST
         PortName : ARRAY [0..1] OF ARRAY [0..5] OF CHAR =
            [["COM1:", 0C], ["COM2:", 0C]]; 
         BaudName : ARRAY [0..8] OF ARRAY [0..5] OF CHAR =
            [["110", 0C], ["150", 0C], ["300", 0C], 
             ["600", 0C], ["1200", 0C], ["2400", 0C], 
             ["4800", 0C], ["9600", 0C], ["19200", 0C]];  
         ParityName : ARRAY [0..2] OF CHAR = ['E', 'O', 'N'];
   
      BEGIN
         WITH Settings[comport - COM_OFF] DO
            Assign (Class, Banner);
            Append (Banner, " -- ");
            Append (Banner, PortName[comport - COM_OFF]);
            Append (Banner, BaudName[baudrate - BAUD_OFF]);
            AppendChar (Banner, ',');
            AppendChar (Banner, ParityName[parity - PARITY_OFF]);
            AppendChar (Banner, ',');
            AppendChar (Banner, CHR ((databits - DATA_OFF) + 30H));
            AppendChar (Banner, ',');
            AppendChar (Banner, CHR ((stopbits - STOP_OFF) + 30H)); 
            WinSetWindowText (FrameWindow, ADR (Banner));
         END;
      END SetBanner;
   
   
   PROCEDURE SetPort;
   (* Sets The Communications Parameters Chosen By User *)

      VAR
         status : CommStatus;
         rc : USHORT;
      
      BEGIN
         IF PrevComPort # NONE THEN
            StopReceiving (PrevComPort - COM_OFF);
         END;
         
         WITH Settings[comport - COM_OFF] DO
            status := InitPort (
               comport - COM_OFF,
               BaudRate (baudrate - BAUD_OFF),
               DataBits (databits - DATA_OFF),
               StopBits (stopbits - STOP_OFF),
               Parity (parity - PARITY_OFF),
            );
         END;
     
         IF status = Success THEN
            StartReceiving (comport - COM_OFF, BUFSIZE);
            PrevComPort := comport;
         ELSE
            rc := WinMessageBox (HWND_DESKTOP, FrameWindow, ADR (PortError),
                                 0, 0, MB_OKCANCEL + MB_ICONEXCLAMATION);
            IF rc = MBID_OK THEN
               WinPostMsg (FrameWindow, WM_QUIT, 0, 0);
            ELSE   (* try the other port *)
               IF comport = ID_COM1 THEN
                  comport := ID_COM2;
               ELSE
                  comport := ID_COM1;
               END;
               SetPort;   (* recursive call for retry *)
            END;
         END;      
         SetBanner;
      END SetPort;


   PROCEDURE MakeChild (msg : ARRAY OF CHAR);
   (* Creates a child window for use by send or receive threads *)
      
      VAR
         c_hdc : HDC;
         
      BEGIN
         WinPostMsg (FrameWindow, WM_SETFULL, 0, 0);
            
         Disable (IDM_CONNECT);
         Disable (IDM_SEND);
         Disable (IDM_REC);
         Disable (IDM_DIR);
         Disable (IDM_OPTIONS);
         Disable (IDM_COLORS);
         
         (* Create a client window *)	 
         FrameFlags := FCF_TITLEBAR + FCF_SIZEBORDER;
         
         ChildFrameWindow := WinCreateStdWindow (
				ClientWindow,        (* handle of the parent window *)
				WS_VISIBLE,          (* the window style *)
				FrameFlags,          (* the window flags *)
				ADR(Child),          (* the window class *)
				NULL,                (* the title bar text *)
				WS_VISIBLE,          (* client window style *)
				NULL,                (* handle of resource module *)
				IDM_KERMIT,          (* resource id *)
				ChildClientWindow    (* returned client window handle *)
         );
         
         WinSetWindowPos (ChildFrameWindow, 0,
            Pos.cx DIV 4, Pos.cy DIV 4, 
            Pos.cx DIV 2, Pos.cy DIV 2 - 3,
            SWP_MOVE + SWP_SIZE);
         
         WinSetWindowText (ChildFrameWindow, ADR (msg));

         WinSetActiveWindow (HWND_DESKTOP, ChildFrameWindow);
                  
         c_hdc := WinOpenWindowDC (ChildClientWindow);
         hvps := child_hvps;
         VioAssociate (c_hdc, hvps);
         ClrScr;	 (* clear the hvio window *)
      END MakeChild;
      

   PROCEDURE Disable (item : USHORT);
   (* Disables and "GREYS" a menu item *)   
   
      VAR
         h : HWND;
         
      BEGIN
         h := WinWindowFromID (FrameWindow, FID_MENU);
         WinSendMsg (h, MM_SETITEMATTR,
            MPFROM2SHORT (item, 1),
            MPFROM2SHORT (MIA_DISABLED, MIA_DISABLED));
      END Disable;
      
      
   PROCEDURE Enable (item : USHORT);
   (* Enables a menu item *)
   
      VAR
         h : HWND;
         atr : USHORT;
         
      BEGIN
         h := WinWindowFromID (FrameWindow, FID_MENU);
         atr := USHORT (WinSendMsg (h, MM_QUERYITEMATTR,
                        MPFROM2SHORT (item, 1),
                        MPFROM2SHORT (MIA_DISABLED, MIA_DISABLED)));
         atr := USHORT (BITSET (atr) * (BITSET (MIA_DISABLED) / BITSET (-1)));                  
         WinSendMsg (h, MM_SETITEMATTR,
            MPFROM2SHORT (item, 1),
            MPFROM2SHORT (MIA_DISABLED, atr));
      END Enable;
      
               
   PROCEDURE Check (item : USHORT);
   (* Checks a menu item -- indicates that it is selected *)   
   
      VAR
         h : HWND;
         
      BEGIN
         h := WinWindowFromID (FrameWindow, FID_MENU);
         WinSendMsg (h, MM_SETITEMATTR,
            MPFROM2SHORT (item, 1),
            MPFROM2SHORT (MIA_CHECKED, MIA_CHECKED));
      END Check;
      
      
   PROCEDURE UnCheck (item : USHORT);
   (* Remove check from a menu item *)
   
      VAR
         h : HWND;
         atr : USHORT;
         
      BEGIN
         h := WinWindowFromID (FrameWindow, FID_MENU);
         atr := USHORT (WinSendMsg (h, MM_QUERYITEMATTR,
                        MPFROM2SHORT (item, 1),
                        MPFROM2SHORT (MIA_CHECKED, MIA_CHECKED)));
         atr := USHORT (BITSET (atr) * (BITSET (MIA_CHECKED) / BITSET (-1)));                  
         WinSendMsg (h, MM_SETITEMATTR,
            MPFROM2SHORT (item, 1),
            MPFROM2SHORT (MIA_CHECKED, atr));
      END UnCheck;
      
               
   PROCEDURE DoMenu (hwnd : HWND; item : MPARAM);
   (* Processes Most Menu Interactions *)
   
      VAR
         rcl : RECTL;
         rc : USHORT;
         
      BEGIN
         CASE  LOWORD (item) OF
            IDM_DIR:
               SetFull;
               WinQueryWindowRect (hwnd, rcl);
               WinDlgBox (HWND_DESKTOP, hwnd, PathDlgProc, 0, IDM_DIRPATH, 0);
               hvps := frame_hvps;
               VioAssociate (hdc, hvps);
               Dir (Path);
               WinDlgBox (HWND_DESKTOP, hwnd, DirEndDlgProc, 0, IDM_DIREND, 0);
               VioAssociate (0, hvps);
               WinInvalidateRect (hwnd, ADR (rcl), 0);
         |  IDM_CONNECT:
               TermMode := TRUE;
               Disable (IDM_CONNECT);
               Disable (IDM_SEND);
               Disable (IDM_REC);
               Disable (IDM_DIR);
               Disable (IDM_OPTIONS);
               Disable (IDM_COLORS);
               (* MAXIMIZE Window -- Required for Terminal Emulation *)
               SetMax;
               hvps := frame_hvps;
               VioAssociate (hdc, hvps);
               DosResumeThread (TermThr);
               InitTerm;
         |  IDM_SEND:
               WinDlgBox (HWND_DESKTOP, hwnd, SendFNDlgProc, 0, IDM_SENDFN, 0);
               MakeChild ("Send a File");
               DosCreateThread (Send, Thr, ADR (Stack[STKSIZE]));
         |  IDM_REC:
               MakeChild ("Receive a File"); 
               DosCreateThread (Receive, Thr, ADR (Stack[STKSIZE]));
         |  IDM_QUIT:
               rc := WinMessageBox (HWND_DESKTOP, ClientWindow,
                        ADR ("Do You Really Want To EXIT PCKermit?"),
                        ADR ("End Session"), 0, MB_OKCANCEL + MB_ICONQUESTION);
               IF rc = MBID_OK THEN
                  StopReceiving (comport - COM_OFF);
                  WinPostMsg (hwnd, WM_QUIT, 0, 0);
               END;
         |  IDM_COMPORT:
               WinDlgBox (HWND_DESKTOP, hwnd, ComDlgProc, 0, IDM_COMPORT, 0);
               SetPort;
         |  IDM_BAUDRATE:
               WinDlgBox (HWND_DESKTOP, hwnd, BaudDlgProc, 0, IDM_BAUDRATE, 0);
               SetPort;
         |  IDM_DATABITS:
               WinDlgBox (HWND_DESKTOP, hwnd, DataDlgProc, 0, IDM_DATABITS, 0);
               SetPort;
         |  IDM_STOPBITS:
               WinDlgBox (HWND_DESKTOP, hwnd, StopDlgProc, 0, IDM_STOPBITS, 0);
               SetPort;
         |  IDM_PARITY:
               WinDlgBox (HWND_DESKTOP, hwnd, ParityDlgProc, 0, IDM_PARITY, 0);
               SetPort;
         |  IDM_WHITE:
               UnCheck (ColorSet);
               ColorSet := IDM_WHITE;
               Check (ColorSet);
               White;
         |  IDM_GREEN:
               UnCheck (ColorSet);
               ColorSet := IDM_GREEN;
               Check (ColorSet);
               Green;
         |  IDM_AMBER:
               UnCheck (ColorSet);
               ColorSet := IDM_AMBER;
               Check (ColorSet);
               Amber;
         |  IDM_C1:
               UnCheck (ColorSet);
               ColorSet := IDM_C1;
               Check (ColorSet);
               Color1;
         |  IDM_C2:   
               UnCheck (ColorSet);
               ColorSet := IDM_C2;
               Check (ColorSet);
               Color2;           
         |  IDM_ABOUT:
               WinDlgBox (HWND_DESKTOP, hwnd, AboutDlgProc, 0, IDM_ABOUT, 0);
         ELSE
            (* Don't do anything... *)
         END;
      END DoMenu;   


   PROCEDURE ComDlgProc ['ComDlgProc'] (
   (* Process Dialog Box for choosing COM1/COM2 *)
         hwnd  : HWND;
         msg   : USHORT;   
         mp1   : MPARAM; 
         mp2   : MPARAM) : MRESULT [LONG, LOADDS];
      BEGIN
         CASE msg OF
            WM_INITDLG:
               WinSendDlgItemMsg (hwnd, comport, BM_SETCHECK, 1, 0);
               WinSetFocus (HWND_DESKTOP, WinWindowFromID (hwnd, comport));
               RETURN 1;
         |  WM_CONTROL:
               comport := LOWORD (mp1);
               RETURN 0;
         |  WM_COMMAND:
               WinDismissDlg (hwnd, 1);
               RETURN 0;
         ELSE
            RETURN WinDefDlgProc (hwnd, msg, mp1, mp2);
         END;
      END ComDlgProc;
   
    
   PROCEDURE BaudDlgProc ['BaudDlgProc'] (
   (* Process Dialog Box for choosing Baud Rate *)
         hwnd  : HWND;
         msg   : USHORT;   
         mp1   : MPARAM; 
         mp2   : MPARAM) : MRESULT [LONG, LOADDS];
      BEGIN
         WITH Settings[comport - COM_OFF] DO
            CASE msg OF
               WM_INITDLG:
                  WinSendDlgItemMsg (hwnd, baudrate, BM_SETCHECK, 1, 0);
                  WinSetFocus (HWND_DESKTOP, WinWindowFromID (hwnd, baudrate));
                  RETURN 1;
            |  WM_CONTROL:
                  baudrate := LOWORD (mp1);
                  RETURN 0;
            |  WM_COMMAND:
                  WinDismissDlg (hwnd, 1);
                  RETURN 0;
            ELSE
               RETURN WinDefDlgProc (hwnd, msg, mp1, mp2);
            END;
         END;
      END BaudDlgProc;
   
    
   PROCEDURE DataDlgProc ['DataDlgProc'] (
   (* Process Dialog Box for choosing 7 or 8 data bits *)
         hwnd  : HWND;
         msg   : USHORT;   
         mp1   : MPARAM; 
         mp2   : MPARAM) : MRESULT [LONG, LOADDS];
      BEGIN
         WITH Settings[comport - COM_OFF] DO
            CASE msg OF
               WM_INITDLG:
                  WinSendDlgItemMsg (hwnd, databits, BM_SETCHECK, 1, 0);
                  WinSetFocus (HWND_DESKTOP, WinWindowFromID (hwnd, databits));
                  RETURN 1;
            |  WM_CONTROL:
                  databits := LOWORD (mp1);
                  RETURN 0;
            |  WM_COMMAND:
                  WinDismissDlg (hwnd, 1);
                  RETURN 0;
            ELSE
               RETURN WinDefDlgProc (hwnd, msg, mp1, mp2);
            END;
         END;
      END DataDlgProc;
   
    
   PROCEDURE StopDlgProc ['StopDlgProc'] (
   (* Process Dialog Box for choosing 1 or 2 stop bits *)
         hwnd  : HWND;
         msg   : USHORT;   
         mp1   : MPARAM; 
         mp2   : MPARAM) : MRESULT [LONG, LOADDS];
      BEGIN
         WITH Settings[comport - COM_OFF] DO
            CASE msg OF
               WM_INITDLG:
                  WinSendDlgItemMsg (hwnd, stopbits, BM_SETCHECK, 1, 0);
                  WinSetFocus (HWND_DESKTOP, WinWindowFromID (hwnd, stopbits));
                  RETURN 1;
            |  WM_CONTROL:
                  stopbits := LOWORD (mp1);
                  RETURN 0;
            |  WM_COMMAND:
                  WinDismissDlg (hwnd, 1);
                  RETURN 0;
            ELSE
               RETURN WinDefDlgProc (hwnd, msg, mp1, mp2);
            END;
         END;
      END StopDlgProc;
   
    
   PROCEDURE ParityDlgProc ['ParityDlgProc'] (
   (* Process Dialog Box for choosing odd, even, or no parity *)
         hwnd  : HWND;
         msg   : USHORT;   
         mp1   : MPARAM; 
         mp2   : MPARAM) : MRESULT [LONG, LOADDS];
      BEGIN
         WITH Settings[comport - COM_OFF] DO
            CASE msg OF
               WM_INITDLG:
                  WinSendDlgItemMsg (hwnd, parity, BM_SETCHECK, 1, 0);
                  WinSetFocus (HWND_DESKTOP, WinWindowFromID (hwnd, parity));
                  RETURN 1;
            |  WM_CONTROL:
                  parity := LOWORD (mp1);
                  RETURN 0;
            |  WM_COMMAND:
                  WinDismissDlg (hwnd, 1);
                  RETURN 0;
            ELSE
               RETURN WinDefDlgProc (hwnd, msg, mp1, mp2);
            END;
         END;
      END ParityDlgProc;
   
    
   PROCEDURE AboutDlgProc ['AboutDlgProc'] (
   (* Process "About" Dialog Box *)
         hwnd  : HWND;
         msg   : USHORT;   
         mp1   : MPARAM; 
         mp2   : MPARAM) : MRESULT [LONG, LOADDS];
      BEGIN
         IF msg = WM_COMMAND THEN
            WinDismissDlg (hwnd, 1);
            RETURN 0;
         ELSE
            RETURN WinDefDlgProc (hwnd, msg, mp1, mp2);
         END;
      END AboutDlgProc;


   PROCEDURE SendFNDlgProc ['SendFNDlgProc'] (
   (* Process Dialog Box that obtains send filename from user *)
         hwnd  : HWND;
         msg   : USHORT;   
         mp1   : MPARAM; 
         mp2   : MPARAM) : MRESULT [LONG, LOADDS];
      BEGIN
         CASE msg OF
            WM_INITDLG:
               WinSetFocus (HWND_DESKTOP, WinWindowFromID (hwnd, ID_SENDFN));
               RETURN 1;
         |  WM_COMMAND:
               WinQueryDlgItemText (hwnd, ID_SENDFN, 20, ADR (sFname));
               WinDismissDlg (hwnd, 1);
               RETURN 0;
         ELSE
            RETURN WinDefDlgProc (hwnd, msg, mp1, mp2);
         END;
      END SendFNDlgProc;
      

   PROCEDURE PathDlgProc ['PathDlgProc'] (
   (* Process Dialog Box that obtains directory path from user *)
         hwnd  : HWND;
         msg   : USHORT;   
         mp1   : MPARAM; 
         mp2   : MPARAM) : MRESULT [LONG, LOADDS];
      BEGIN
         CASE msg OF
            WM_INITDLG:
               WinSetFocus (HWND_DESKTOP, WinWindowFromID (hwnd, ID_DIRPATH));
               RETURN 1;
         |  WM_COMMAND:
               WinQueryDlgItemText (hwnd, ID_DIRPATH, 60, ADR (Path));
               WinDismissDlg (hwnd, 1);
               RETURN 0;
         ELSE
            RETURN WinDefDlgProc (hwnd, msg, mp1, mp2);
         END;
      END PathDlgProc;


   PROCEDURE DirEndDlgProc ['DirEndDlgProc'] (
   (* Process Dialog Box to allow user to cancel directory *)
         hwnd  : HWND;
         msg   : USHORT;   
         mp1   : MPARAM; 
         mp2   : MPARAM) : MRESULT [LONG, LOADDS];
      BEGIN
         IF msg = WM_COMMAND THEN
            WinDismissDlg (hwnd, 1);
            RETURN 0;
         ELSE
            RETURN WinDefDlgProc (hwnd, msg, mp1, mp2);
         END;
      END DirEndDlgProc;
      
   
   PROCEDURE HelpDlgProc ['HelpDlgProc'] (
   (* Process Dialog Boxes for the HELP *)
         hwnd  : HWND;
         msg   : USHORT;   
         mp1   : MPARAM; 
         mp2   : MPARAM) : MRESULT [LONG, LOADDS];
      BEGIN
         IF msg = WM_COMMAND THEN
            WinDismissDlg (hwnd, 1);
            RETURN 0;
         ELSE
            RETURN WinDefDlgProc (hwnd, msg, mp1, mp2);
         END;
      END HelpDlgProc;


   PROCEDURE KeyTranslate (mp1, mp2 : MPARAM; VAR c1, c2 : CHAR) : BOOLEAN;
   (* Translates WM_CHAR message into ascii keystroke *)
   
      VAR
			code : CARDINAL;	 
			fs : BITSET;	
			VK, KU, CH, CT : BOOLEAN;	 
   
      BEGIN
         fs := BITSET (LOWORD (mp1));	 (* flags *)				
         VK := (fs * BITSET (KC_VIRTUALKEY)) # {};			  
         KU := (fs * BITSET (KC_KEYUP)) # {};			
         CH := (fs * BITSET (KC_CHAR)) # {};			  
         CT := (fs * BITSET (KC_CTRL)) # {};			  
         IF (NOT KU) THEN			 
            code := LOWORD (mp2);	(* character code *)			  
            c1 := CHR (code);			  
            c2 := CHR (code DIV 256);			 
            IF ORD (c1) = 0E0H THEN	  (* function *)			 
               c1 := 0C;			   
            END;			 
            IF CT AND (NOT CH) AND (NOT VK) AND (code # 0) THEN			
               c1 := CHR (CARDINAL ((BITSET (ORD (c1)) * BITSET (1FH))));
            END;			 
            RETURN TRUE;
         ELSE
            RETURN FALSE;
         END;
      END KeyTranslate;
      
         
   PROCEDURE WindowProc ['WindowProc'] (
   (* Main Window Procedure -- Handles message from PM and elsewhere *)
         hwnd  : HWND;
         msg   : USHORT;   
         mp1   : MPARAM; 
         mp2   : MPARAM) : MRESULT [LONG, LOADDS];

      VAR
         ch : CHAR;
         hps       : HPS;
         pswp      : PSWP;
         c1, c2    : CHAR;
         
      BEGIN
         CASE msg OF 
            WM_HELP:
               IF TermMode THEN
                  WinDlgBox (HWND_DESKTOP, hwnd, HelpDlgProc, 
                             0, IDM_TERMHELP, 0);
               ELSE
                  WinDlgBox (HWND_DESKTOP, hwnd, HelpDlgProc, 
                             0, IDM_HELPMENU, 0);
               END;
               RETURN 0;
         |  WM_SETFULL:
               SetFull;
               RETURN 0;
         |  WM_SETRESTORE:
               SetRestore;
               RETURN 0;
         |  WM_SETMAX:
               SetMax;
               RETURN 0;
         |  WM_MINMAXFRAME:
               pswp := PSWP (mp1);
               IF BITSET (pswp^.fs) * BITSET (SWP_MINIMIZE) # {} THEN
                  (* Don't Display Port Settings While Minimized *)
                  WinSetWindowText (FrameWindow, ADR (Title));
               ELSE
                  WinSetWindowText (FrameWindow, ADR (Banner));
                  IF TermMode AND
                   (BITSET (pswp^.fs) * BITSET (SWP_RESTORE) # {}) THEN
                     (* Force window to be maximized in terminal mode *)
                     WinPostMsg (FrameWindow, WM_SETMAX, 0, 0);
                  ELSIF (NOT TermMode) AND
                   (BITSET (pswp^.fs) * BITSET (SWP_MAXIMIZE) # {}) THEN
                     (* Prevent maximized window EXCEPT in terminal mode *)
                     WinPostMsg (FrameWindow, WM_SETRESTORE, 0, 0);
                  ELSE
                     (* Do Nothing *)
                  END;
               END;
               RETURN WinDefWindowProc (hwnd, msg, mp1, mp2);
         |  WM_CREATE:
               hdc := WinOpenWindowDC (hwnd);
               VioCreatePS (frame_hvps, 25, 80, 0, FORMAT_CGA, 0);
               VioCreatePS (child_hvps, 16, 40, 0, FORMAT_CGA, 0);
               DosCreateThread (TermThrProc, TermThr, ADR (TermStack[STKSIZE]));
               DosSuspendThread (TermThr);
               RETURN 0;
         |  WM_INITMENU:
               Check (ColorSet);
               RETURN 0;
         |  WM_COMMAND: 
               DoMenu (hwnd, mp1);
               RETURN 0;
         |  WM_TERMQUIT:
               TermMode := FALSE;
               DosSuspendThread (TermThr);
               VioAssociate (0, hvps);
               (* Restore The Window *)
               SetRestore;
               Enable (IDM_CONNECT);
               Enable (IDM_SEND);
               Enable (IDM_REC);
               Enable (IDM_DIR);
               Enable (IDM_OPTIONS);
               Enable (IDM_COLORS);
               RETURN 0;
         |  WM_TERM:
               PutPortChar (CHR (LOWORD (mp1)));   (* To Screen *)
               RETURN 0;
         |  WM_CHAR:
               IF TermMode THEN
                  IF KeyTranslate (mp1, mp2, c1, c2) THEN
                     PutKbdChar (c1, c2);   (* To Port *)
                     RETURN 0;
                  ELSE
                     RETURN WinDefAVioWindowProc (hwnd, msg, mp1, mp2);
                  END;
               ELSE
                  RETURN WinDefWindowProc (hwnd, msg, mp1, mp2);
               END;
         |  WM_PAINT:
               hps := WinBeginPaint (hwnd, NULL, ADDRESS (NULL));
               GpiErase (hps);
               VioShowPS (25, 80, 0, hvps); 
               WinEndPaint (hps);
               RETURN 0;
         |  WM_SIZE:
               IF TermMode THEN
                  RETURN WinDefAVioWindowProc (hwnd, msg, mp1, mp2);
               ELSE
                  RETURN WinDefWindowProc (hwnd, msg, mp1, mp2);
               END;
         |  WM_DESTROY:
               VioDestroyPS (frame_hvps);
               VioDestroyPS (child_hvps);
               RETURN 0;
         ELSE
            RETURN WinDefWindowProc (hwnd, msg, mp1, mp2);
         END;
      END WindowProc;
      

   PROCEDURE ChildWindowProc ['ChildWindowProc'] (
   (* Window Procedure for Send/Receive child windows *)
      hwnd : HWND;
      msg  : USHORT;   
      mp1  : MPARAM; 
      mp2  : MPARAM) : MRESULT [LONG, LOADDS];
      
      VAR
         mp : USHORT;
         hps : HPS;
         c1, c2 : CHAR;
      
      BEGIN
         CASE msg OF
            WM_PAINT:
               hps := WinBeginPaint (hwnd, NULL, ADDRESS (NULL));
               GpiErase (hps);
               VioShowPS (16, 40, 0, hvps); 
               WinEndPaint (hps);
               RETURN 0;
         |  WM_CHAR:
               IF KeyTranslate (mp1, mp2, c1, c2) AND (c1 = ESC) THEN
                  Aborted := TRUE;
                  RETURN 0;
               ELSE
                  RETURN WinDefWindowProc (hwnd, msg, mp1, mp2);
               END;
         |  WM_PAD:
               mp := LOWORD (mp1);
               IF (mp = PAD_Error) OR (mp = PAD_Quit) THEN
                  WriteLn;
                  IF mp = PAD_Error THEN
                     WinMessageBox (HWND_DESKTOP, hwnd, 
                                    ADR ("File Transfer Aborted"),
                                    ADR (Class), 0, MB_OK + MB_ICONEXCLAMATION);
                  ELSE
                     WinMessageBox (HWND_DESKTOP, hwnd, 
                                       ADR ("File Transfer Completed"),
                                       ADR (Class), 0, MB_OK + MB_ICONASTERISK);
                  END;
                  DosSleep (2000);
                  VioAssociate (0, hvps);
                  WinDestroyWindow(ChildFrameWindow);
                  Enable (IDM_CONNECT);
                  Enable (IDM_SEND);
                  Enable (IDM_REC);
                  Enable (IDM_DIR);
                  Enable (IDM_OPTIONS);
                  Enable (IDM_COLORS);
               ELSE
                  DoPADMsg (mp1, mp2);
               END;
               RETURN 0;
         |  WM_DL:
               DoDLMsg (mp1, mp2);
               RETURN 0;
         |  WM_SIZE:
               WinSetWindowPos (ChildFrameWindow, 0,
                  Pos.cx DIV 4, Pos.cy DIV 4, 
                  Pos.cx DIV 2, Pos.cy DIV 2 - 3,
                  SWP_MOVE + SWP_SIZE);
               RETURN WinDefAVioWindowProc (hwnd, msg, mp1, mp2);
         ELSE
            RETURN WinDefWindowProc (hwnd, msg, mp1, mp2);
         END;
      END ChildWindowProc;


BEGIN   (* Module Initialization *)
    WITH Settings[ID_COM1 - COM_OFF] DO
       baudrate := ID_B1200;
       parity := ID_EVEN;
       databits := ID_DATA7;
       stopbits := ID_STOP1;
    END;
    
    WITH Settings[ID_COM2 - COM_OFF] DO
       baudrate := ID_B19K2;
       parity := ID_EVEN;
       databits := ID_DATA7;
       stopbits := ID_STOP1;
    END;
    PrevComPort := NONE;
    comport := ID_COM1;
    TermMode := FALSE;   (* Not Initially in Terminal Emulation Mode *)
END Shell.



[LISTING TEN]

IMPLEMENTATION MODULE Term;   (* TVI950 Terminal Emulation for Kermit *)

   FROM Drives IMPORT
      SetDrive;
      
   FROM Directories IMPORT
      FileAttributes, AttributeSet, DirectoryEntry, FindFirst, FindNext;
      
   FROM SYSTEM IMPORT
      ADR;

   FROM OS2DEF IMPORT
      ULONG;
            
   FROM DosCalls IMPORT
      DosChDir, DosSleep;
            
   FROM Screen IMPORT
      ClrScr, ClrEol, GotoXY, GetXY,
      Right, Left, Up, Down, WriteAtt, WriteString, WriteLn, Write,
      attribute, NORMAL, HIGHLIGHT, REVERSE;		
      
   FROM PMWIN IMPORT
      WinPostMsg, MPFROM2SHORT;

   FROM Shell IMPORT
      comport, FrameWindow;
      
   FROM KH IMPORT
      COM_OFF;
            
   FROM CommPort IMPORT
      CommStatus, GetChar, SendChar;
            
   FROM Strings IMPORT
      Length, Concat;
   
   IMPORT ASCII;


   CONST
      (* Key codes:  Note: F1 -- F12 are actually Shift-F1 -- Shift-F12 *)
      F1 = 124C;
      F2 = 125C;
      F3 = 126C;
      F4 = 127C;
      F5 = 130C;
      F6 = 131C;
      F7 = 132C;
      F8 = 133C;
      F9 = 134C;
      F10 = 135C;
      F11 = 207C;
      F12 = 210C;
      AF1 = 213C;   (* Alt-F1 *)
      AF2 = 214C;   (* Alt-F2 *)
      INS = 122C;
      DEL = 123C;
      HOME = 107C;
      PGDN = 121C;   (* synonym for PF10 *)
      PGUP = 111C;   (* synonym for PF11 *)
      ENDD = 117C;   (* synonym for PF12 *)
      UPARROW = 110C;
      DOWNARROW = 120C;
      LEFTARROW = 113C;
      RIGHTARROW = 115C;
      CtrlX = 30C;
      CtrlCaret = 36C;
      CtrlZ = 32C;
      CtrlL = 14C;
      CtrlH = 10C;
      CtrlK = 13C;
      CtrlJ = 12C;
      CtrlV = 26C;
      ESC = 33C;
      BUFSIZE = 4096;   (* character buffer used by term thread *)

   
   VAR
      commStat : CommStatus;
      echo : (Off, Local, On);      
      newline: BOOLEAN;   (* translate <cr> to <cr><lf> *)
      Insert : BOOLEAN;
                  

   PROCEDURE Dir (path : ARRAY OF CHAR);
   (* Change drive and/or directory; display a directory (in wide format) *)
   
      VAR
         gotFN : BOOLEAN;
         filename : ARRAY [0..20] OF CHAR;
         attr : AttributeSet;
         ent : DirectoryEntry;
         i, j, k : INTEGER;
         
      BEGIN
         filename := "";   (* in case no directory change *)
         i := Length (path);
         IF (i > 2) AND (path[1] = ':') THEN   (* drive specifier *)
            DEC (i, 2);
            SetDrive (ORD (CAP (path[0])) - ORD ('A')); 
            FOR j := 0 TO i DO   (* strip off the drive specifier *)
               path[j] := path[j + 2];
            END;
         END;
         IF i # 0 THEN
            gotFN := FALSE;
            WHILE (i >= 0) AND (path[i] # '\') DO
               IF path[i] = '.' THEN
                  gotFN := TRUE;
               END;
               DEC (i);
            END;
            IF gotFN THEN
               j := i + 1;
               k := 0;
               WHILE path[j] # 0C DO
                  filename[k] := path[j];
                  INC (k);       INC (j);
               END;
               filename[k] := 0C;
               IF (i = -1) OR ((i = 0) AND (path[0] = '\')) THEN
                  INC (i);
               END;
               path[i] := 0C;
            END;
         END;
         IF Length (path) # 0 THEN
            DosChDir (ADR (path), 0);
         END;
         IF Length (filename) = 0 THEN
            filename := "*.*";
         END;
         attr := AttributeSet {ReadOnly, Directory, Archive};
         i := 1;   (* keep track of position on line *)

         ClrScr;         
         gotFN := FindFirst (filename, attr, ent);
         WHILE gotFN DO
            WriteString (ent.name);
            j := Length (ent.name);
            WHILE j < 12 DO   (* 12 is maximum length for "filename.typ" *)
               Write (' ');
               INC (j);
            END;
            INC (i);   (* next position on this line *)
            IF i > 5 THEN
               i := 1;   (* start again on new line *)
               WriteLn;
            ELSE
               WriteString (" | ");
            END;
            gotFN := FindNext (ent);
         END;
         WriteLn;
      END Dir;
  

   PROCEDURE InitTerm;
   (* Clear Screen, Home Cursor, Get Ready For Terminal Emulation *)
      BEGIN
         ClrScr;
         Insert := FALSE;
         attribute := NORMAL;
      END InitTerm;   


   PROCEDURE PutKbdChar (ch1, ch2 : CHAR);
   (* Process a character received from the keyboard *)
      BEGIN
         IF ch1 = ASCII.enq THEN   (* Control-E *)
            echo := On;
         ELSIF ch1 = ASCII.ff THEN   (* Control-L *)
            echo := Local;
         ELSIF ch1 = ASCII.dc4 THEN   (* Control-T *)
            echo := Off;
         ELSIF ch1 = ASCII.so THEN   (* Control-N *)
            newline := TRUE;
         ELSIF ch1 = ASCII.si THEN   (* Control-O *)
            newline := FALSE;
         ELSIF (ch1 = ASCII.can) OR (ch1 = ESC) THEN
            attribute := NORMAL;
            WinPostMsg (FrameWindow, WM_TERMQUIT, 0, 0);
         ELSIF ch1 = 0C THEN
            Function (ch2);
         ELSE
            commStat := SendChar (comport - COM_OFF, ch1, FALSE);
            IF (echo = On) OR (echo = Local) THEN
               WriteAtt (ch1);
            END;
         END;
      END PutKbdChar;


   PROCEDURE Function (ch : CHAR);
   (* handles the function keys -- including PF1 - PF12, etc. *)
      BEGIN
         CASE ch OF
            F1 :  commStat := SendChar (comport - COM_OFF, ASCII.soh, FALSE);   
                  commStat := SendChar (comport - COM_OFF, '@', FALSE);   
                  commStat := SendChar (comport - COM_OFF, ASCII.cr, FALSE);
         |  F2 :  commStat := SendChar (comport - COM_OFF, ASCII.soh, FALSE);   
                  commStat := SendChar (comport - COM_OFF, 'A', FALSE);   
                  commStat := SendChar (comport - COM_OFF, ASCII.cr, FALSE);
         |  F3 :  commStat := SendChar (comport - COM_OFF, ASCII.soh, FALSE);   
                  commStat := SendChar (comport - COM_OFF, 'B', FALSE);   
                  commStat := SendChar (comport - COM_OFF, ASCII.cr, FALSE);
         |  F4 :  commStat := SendChar (comport - COM_OFF, ASCII.soh, FALSE);   
                  commStat := SendChar (comport - COM_OFF, 'C', FALSE);   
                  commStat := SendChar (comport - COM_OFF, ASCII.cr, FALSE);
         |  F5 :  commStat := SendChar (comport - COM_OFF, ASCII.soh, FALSE);   
                  commStat := SendChar (comport - COM_OFF, 'D', FALSE);   
                  commStat := SendChar (comport - COM_OFF, ASCII.cr, FALSE);
         |  F6 :  commStat := SendChar (comport - COM_OFF, ASCII.soh, FALSE);   
                  commStat := SendChar (comport - COM_OFF, 'E', FALSE);   
                  commStat := SendChar (comport - COM_OFF, ASCII.cr, FALSE);
         |  F7 :  commStat := SendChar (comport - COM_OFF, ASCII.soh, FALSE);   
                  commStat := SendChar (comport - COM_OFF, 'F', FALSE);   
                  commStat := SendChar (comport - COM_OFF, ASCII.cr, FALSE);
         |  F8 :  commStat := SendChar (comport - COM_OFF, ASCII.soh, FALSE);   
                  commStat := SendChar (comport - COM_OFF, 'G', FALSE);   
                  commStat := SendChar (comport - COM_OFF, ASCII.cr, FALSE);
         |  F9 :  commStat := SendChar (comport - COM_OFF, ASCII.soh, FALSE);   
                  commStat := SendChar (comport - COM_OFF, 'H', FALSE);   
                  commStat := SendChar (comport - COM_OFF, ASCII.cr, FALSE);
         |  F10, 
            PGDN: commStat := SendChar (comport - COM_OFF, ASCII.soh, FALSE);   
                  commStat := SendChar (comport - COM_OFF, 'I', FALSE);   
                  commStat := SendChar (comport - COM_OFF, ASCII.cr, FALSE);
         |  F11,
            AF1,
            PGUP: commStat := SendChar (comport - COM_OFF, ASCII.soh, FALSE);   
                  commStat := SendChar (comport - COM_OFF, 'J', FALSE);   
                  commStat := SendChar (comport - COM_OFF, ASCII.cr, FALSE);
         |  F12,
            AF2,
            ENDD: commStat := SendChar (comport - COM_OFF, ESC, FALSE);
                  commStat := SendChar (comport - COM_OFF, 'Q', FALSE);
         |  INS : IF NOT Insert THEN
                     commStat := SendChar (comport - COM_OFF, ESC, FALSE);
                     commStat := SendChar (comport - COM_OFF, 'E', FALSE);
                  END;
         |  DEL : commStat := SendChar (comport - COM_OFF, ESC, FALSE);
                  commStat := SendChar (comport - COM_OFF, 'R', FALSE);
         |  HOME       : commStat := SendChar (comport - COM_OFF, CtrlZ, FALSE);
         |  UPARROW    : commStat := SendChar (comport - COM_OFF, CtrlK, FALSE);
         |  DOWNARROW  : commStat := SendChar (comport - COM_OFF, CtrlV, FALSE);
         |  LEFTARROW  : commStat := SendChar (comport - COM_OFF, CtrlH, FALSE);
         |  RIGHTARROW : commStat := SendChar (comport - COM_OFF, CtrlL, FALSE);
         ELSE
            (* do nothing *)
         END;
      END Function;

      
   PROCEDURE TermThrProc;
   (* Thread to get characters from port, put into buffer *)
   
      VAR
         ch : CHAR;
         
      BEGIN
         LOOP
            IF GetChar (comport - COM_OFF, ch) = Success THEN
               WinPostMsg (FrameWindow, WM_TERM, MPFROM2SHORT (ORD (ch), 0), 0);
            ELSE
               DosSleep (0);
            END
         END;
      END TermThrProc;


   VAR
      EscState, CurState1, CurState2 : BOOLEAN;
      CurChar1 : CHAR;
      
   PROCEDURE PutPortChar (ch : CHAR);
   (* Process a character received from the port *)
      BEGIN
         IF EscState THEN
            EscState := FALSE;
            IF ch = '=' THEN
               CurState1 := TRUE;
            ELSE
               Escape (ch);
            END;
         ELSIF CurState1 THEN
            CurState1 := FALSE;
            CurChar1 := ch;
            CurState2 := TRUE;
         ELSIF CurState2 THEN
            CurState2 := FALSE;
            Cursor (ch);
         ELSE
            CASE ch OF
               CtrlCaret, CtrlZ : ClrScr;
            |  CtrlL : Right;
            |  CtrlH : Left;
            |  CtrlK : Up;
            |  CtrlJ : Down;
            |  ESC   : EscState := TRUE;
            ELSE
               WriteAtt (ch);
               IF newline AND (ch = ASCII.cr) THEN
                  WriteLn;
               END;
            END;
         END;
         IF echo = On THEN
            commStat := SendChar (comport - COM_OFF, ch, FALSE);
         END;
      END PutPortChar;
      
      
   PROCEDURE Escape (ch : CHAR);
   (* handles escape sequences *)
      BEGIN
         CASE ch OF
            '*' : ClrScr;
         |  'T', 'R' : ClrEol;
         |  ')' : attribute := NORMAL;
         |  '(' : attribute := HIGHLIGHT;   
         |  'f' : InsertMsg;
         |  'g' : InsertOn;
         ELSE
            (* ignore *)
         END;
      END Escape;
      
      
   PROCEDURE Cursor (ch : CHAR);
   (* handles cursor positioning *)
   
      VAR
         x, y : CARDINAL;
         
      BEGIN
         y := ORD (CurChar1) - 20H;
         x := ORD (ch) - 20H;
         GotoXY (x, y);   (* adjust for HOME = (1, 1) *)
      END Cursor;
      
      
   VAR
      cx, cy : CARDINAL;
      
   PROCEDURE InsertMsg;
   (* get ready insert mode -- place a message at the bottom of the screen *)
      BEGIN
         IF NOT Insert THEN
            GetXY (cx, cy);   (* record current position *)
            GotoXY (1, 24);
            ClrEol;
            attribute := REVERSE;
         ELSE   (* exit Insert mode *)
            GetXY (cx, cy);
            GotoXY (1, 24);
            ClrEol;
            GotoXY (cx, cy);
            Insert := FALSE;
         END;
      END InsertMsg;   
      
      
   PROCEDURE InsertOn;
   (* enter insert mode -- after INSERT MODE message is printed *)
      BEGIN
         attribute := NORMAL;
         GotoXY (cx, cy);
         Insert := TRUE;
      END InsertOn;   
      

BEGIN   (* module initialization *)
   echo := Off;
   newline := FALSE;
   Insert := FALSE;
   EscState := FALSE;
   CurState1 := FALSE;
   CurState2 := FALSE;
END Term.


[LISTING ELEVEN]

IMPLEMENTATION MODULE Screen;
(* module to perform "low level" screen functions (via AVIO) *)

   IMPORT ASCII;
   
   FROM SYSTEM IMPORT
      ADR;

   FROM Strings IMPORT
      Length;
      
   FROM Conversions IMPORT
      IntToString;

   FROM KH IMPORT
      IDM_GREEN;
                  
   FROM Vio IMPORT
      VioSetCurPos, VioGetCurPos, VioScrollUp, 
      VioWrtNCell, VioWrtTTY, VioCell;


   CONST
      GREY = 07H;
      WHITE = 0FH;
      REV_GY = 70H;
      GREEN = 02H;
      LITE_GRN = 0AH;
      REV_GRN = 20H;
      AMBER = 06H;
      LITE_AMB = 0EH;
      REV_AMB = 60H;
      RED = 0CH;
      CY_BK = 0B0H;
      CY_BL = 0B9H;
      REV_RD = 0CFH;
      REV_BL = 9FH;
      MAGENTA = 05H;
      
            
   VAR	
      (* From Definition Module
      NORMAL : CARDINAL;
      HIGHLIGHT : CARDINAL;
      REVERSE : CARDINAL;
		attribute : CARDINAL;	
      hvps : HVPS;
      *)
	   x, y : CARDINAL;	 
	   bCell : VioCell;	 
      

   PROCEDURE White;
   (* Sets up colors: Monochrome White *)
      BEGIN
         NORMAL := GREY;
         HIGHLIGHT := WHITE;
         REVERSE := REV_GY;
         attribute := NORMAL;
      END White;
      
      
   PROCEDURE Green;
   (* Sets up colors: Monochrome Green *)
      BEGIN
         NORMAL := GREEN;
         HIGHLIGHT := LITE_GRN;
         REVERSE := REV_GRN;
         attribute := NORMAL;
      END Green;
      
      
   PROCEDURE Amber;
   (* Sets up colors: Monochrome Amber *)
      BEGIN
         NORMAL := AMBER;
         HIGHLIGHT := LITE_AMB;
         REVERSE := REV_AMB;
         attribute := NORMAL;
      END Amber;
      
      
   PROCEDURE Color1;
   (* Sets up colors: Blue, Red, Green *)
      BEGIN
         NORMAL := GREEN;
         HIGHLIGHT := RED;
         REVERSE := REV_BL;
         attribute := NORMAL;
      END Color1;
      
      
   PROCEDURE Color2;
   (* Sets up colors: Cyan Background; Black, Blue, White-on-Red *)
      BEGIN
         NORMAL := CY_BK;
         HIGHLIGHT := CY_BL;
         REVERSE := REV_RD;
         attribute := NORMAL;
      END Color2;
      
      
   PROCEDURE HexToString (num : INTEGER;
                          size : CARDINAL;
                          VAR buf : ARRAY OF CHAR;
                          VAR I : CARDINAL;
                          VAR Done : BOOLEAN);
   (* Local Procedure to convert a number to a string, represented in HEX *)   
   
      CONST
         ZERO = 30H;   (* ASCII code *)
         A = 41H; 
         
      VAR
         i : CARDINAL;
         h : CARDINAL;
         t : ARRAY [0..10] OF CHAR;
                                
      BEGIN
         i := 0;
         REPEAT
            h := num MOD 16;
            IF h <= 9 THEN
               t[i] := CHR (h + ZERO);
            ELSE
               t[i] := CHR (h - 10 + A);
            END;
            INC (i);
            num := num DIV 16;
         UNTIL num = 0;
         
         IF (size > HIGH (buf)) OR (i > HIGH (buf)) THEN
            Done := FALSE;
            RETURN;
         ELSE
            Done := TRUE;
         END;
         
         WHILE size > i DO
            buf[I] := '0';   (* pad with zeros *)
            DEC (size);
            INC (I);
         END;
         
         WHILE i > 0 DO
            DEC (i);
            buf[I] := t[i];
            INC (I);
         END;
         
         buf[I] := 0C;
      END HexToString;
                                
   
   PROCEDURE ClrScr;	  
   (* Clear the screen, and home the cursor *)	 
      BEGIN	  
         bCell.ch := ' ';	 (* space = blank screen *)	
         bCell.attr := CHR (NORMAL);	(* Normal Video Attribute *)	 
         VioScrollUp (0, 0, 24, 79, 25, bCell, hvps);	  
         GotoXY (0, 0);	  
      END ClrScr;     
 
 
 
   PROCEDURE ClrEol;     
   (* clear from the current cursor position to the end of the line *)    
      BEGIN     
         GetXY (x, y);     (* current cursor position *)    
         bCell.ch := ' ';    (* space = blank *)     
         bCell.attr := CHR (NORMAL);   (* Normal Video Attribute *)    
         VioScrollUp (y, x, y, 79, 1, bCell, hvps);   
      END ClrEol;     
   
   
   PROCEDURE Right;    
   (* move cursor to the right *)   
      BEGIN     
         GetXY (x, y);    
         INC (x);     
         GotoXY (x, y);     
      END Right;    
   
   
   PROCEDURE Left;   
   (* move cursor to the left *)     
      BEGIN     
         GetXY (x, y);    
         DEC (x);     
         GotoXY (x, y);     
      END Left;   
   
   
   PROCEDURE Up;    
   (* move cursor up *)     
      BEGIN     
         GetXY (x, y);    
         DEC (y);     
         GotoXY (x, y);     
      END Up;    
   
   
   PROCEDURE Down;   
   (* move cursor down *)    
      BEGIN     
         GetXY (x, y);    
         INC (y);     
         GotoXY (x, y);     
      END Down;   
   
   
   PROCEDURE GotoXY (col, row : CARDINAL);   
   (* position cursor at column, row *)   
      BEGIN     
         IF (col <= 79) AND (row <= 24) THEN     
            VioSetCurPos (row, col, hvps);   
         END;    
      END GotoXY;     
   
   
   PROCEDURE GetXY (VAR col, row : CARDINAL);   
   (* determine current cursor position *)   
      BEGIN     
         VioGetCurPos (row, col, hvps);   
      END GetXY;    
   

   PROCEDURE Write (c : CHAR);
   (* Write a Character *)
      BEGIN
         WriteAtt (c);
      END Write;
      
      
   PROCEDURE WriteString (str : ARRAY OF CHAR);
   (* Write String *)
   
      VAR
         i : CARDINAL;
         c : CHAR;
         
      BEGIN
         i := 0;
         c := str[i];
         WHILE c # 0C DO
            Write (c);
            INC (i);
            c := str[i];
         END;
      END WriteString;

      
   PROCEDURE WriteInt (n : INTEGER; s : CARDINAL);
   (* Write Integer *)
   
      VAR
         i : CARDINAL;
         b : BOOLEAN;
         str : ARRAY [0..6] OF CHAR;
         
      BEGIN
         i := 0;
         IntToString (n, s, str, i, b);
         WriteString (str);
      END WriteInt;
      
   
   PROCEDURE WriteHex (n, s : CARDINAL);
   (* Write a Hexadecimal Number *)
   
      VAR
         i : CARDINAL;
         b : BOOLEAN;
         str : ARRAY [0..6] OF CHAR;
         
      BEGIN
         i := 0;
         HexToString (n, s, str, i, b);
         WriteString (str);
      END WriteHex;
      
   
   PROCEDURE WriteLn;
   (* Write <cr> <lf> *)
      BEGIN
         Write (ASCII.cr);   Write (ASCII.lf); 
      END WriteLn;
   
   
   PROCEDURE WriteAtt (c : CHAR);   
   (* write character and attribute at cursor position *)   
   
      VAR   
         s : ARRAY [0..1] OF CHAR;    

      BEGIN     
         GetXY (x, y);
         IF (c = ASCII.ht) THEN
            bCell.ch := ' ';
            bCell.attr := CHR (attribute);   
            REPEAT
               VioWrtNCell (bCell, 1, y, x, hvps);     
               Right;
            UNTIL (x MOD 8) = 0; 
         ELSIF (c = ASCII.cr) OR (c = ASCII.lf)
          OR (c = ASCII.bel) OR (c = ASCII.bs) THEN   
            s[0] := c;    s[1] := 0C;   
            VioWrtTTY (ADR (s), 1, hvps);     
            IF c = ASCII.lf THEN
               ClrEol;
            END;
         ELSE    
            bCell.ch := c;     
            bCell.attr := CHR (attribute);   
            VioWrtNCell (bCell, 1, y, x, hvps);     
            Right;   
         END;    
      END WriteAtt;    
   
BEGIN     (* module initialization *)     
   ColorSet := IDM_GREEN;
   NORMAL := GREEN;
   HIGHLIGHT := LITE_GRN;
   REVERSE := REV_GRN;
   attribute := NORMAL;     
END Screen.



[LISTING TWELVE]

(**************************************************************************)
(*                                                                        *)
(*                     Copyright (c) 1988, 1989                           *)
(*                      by Stony Brook Software                           *)
(*                               and                                      *)
(*                        Copyright (c) 1990                              *)
(*                       by Brian R. Anderson                             *)
(*                        All rights reserved.                            *)
(*                                                                        *)
(**************************************************************************)

IMPLEMENTATION MODULE CommPort [7];

   FROM SYSTEM IMPORT
      ADR, BYTE, WORD, ADDRESS;

   FROM Storage IMPORT
      ALLOCATE, DEALLOCATE;
      
   FROM DosCalls IMPORT
      DosOpen, AttributeSet, DosDevIOCtl, DosClose, DosRead, DosWrite;


   TYPE
      CP = POINTER TO CHAR;
      
   VAR
      pn : CARDINAL;
      Handle : ARRAY [0..3] OF CARDINAL;
      BufIn : ARRAY [0..3] OF CP;
      BufOut : ARRAY [0..3] OF CP;
      BufStart : ARRAY [0..3] OF CP;
      BufLimit : ARRAY [0..3] OF CP;
      BufSize : ARRAY [0..3] OF CARDINAL;
      Temp : ARRAY [1..1024] OF CHAR;   (* size of OS/2's serial queue *)
      

   PROCEDURE CheckPort (portnum : CARDINAL) : BOOLEAN;
   (* Check for a valid port number and open the port if it not alredy open *)
   
      CONST
         PortName : ARRAY [0..3] OF ARRAY [0..4] OF CHAR =
            [['COM1', 0C], ['COM2', 0C], ['COM3', 0C], ['COM4', 0C]];

      VAR
         Action : CARDINAL;
         
      BEGIN
         (* check the port number *)
         IF portnum > 3 THEN
            RETURN FALSE;
         END;

         (* attempt to open the port if it is not already open *)
         IF Handle[portnum] = 0 THEN
            IF DosOpen(ADR(PortName[portnum]), Handle[portnum], Action, 0,
             AttributeSet{}, 1, 12H, 0) # 0 THEN
               RETURN FALSE;
            END;
         END;
         RETURN TRUE;
      END CheckPort;


   
   PROCEDURE InitPort (portnum : CARDINAL; speed : BaudRate; data : DataBits;
                         stop : StopBits; check : Parity) : CommStatus;
   (* Initialize a port *)
      
      CONST
         Rate : ARRAY BaudRate OF CARDINAL =
                   [110, 150, 300, 600, 1200, 2400, 4800, 9600, 19200];
         TransParity : ARRAY Parity OF BYTE = [2, 1, 0];

      TYPE
         LineChar =  RECORD
                        bDataBits : BYTE;
                        bParity : BYTE;
                        bStopBits : BYTE;
                     END;

      VAR
         LC : LineChar;
               
      BEGIN
         (* Check the port number *)
         IF NOT CheckPort(portnum) THEN
            RETURN InvalidPort;
         END;

         (* Set the baud rate *)
         IF DosDevIOCtl(0, ADR(Rate[speed]), 41H, 1, Handle[portnum]) # 0 THEN
            RETURN InvalidParameter;
         END;

         (* set the characteristics *)
         LC.bDataBits := BYTE(data);
         IF stop = 1 THEN
            DEC (stop);    (* 0x00 = 1 stop bits;    0x02 = 2 stop bits *)
         END;
         LC.bStopBits := BYTE(stop);
         LC.bParity := TransParity[check];

         IF DosDevIOCtl(0, ADR(LC), 42H, 1, Handle[portnum]) # 0 THEN
            RETURN InvalidParameter;
         END;

         RETURN Success;
      END InitPort;


   PROCEDURE StartReceiving (portnum, bufsize : CARDINAL) : CommStatus;
   (* Start receiving characters on a port *)
      BEGIN
         IF NOT CheckPort(portnum) THEN
            RETURN InvalidPort;
         END;
         IF BufStart[portnum] # NIL THEN
            RETURN AlreadyReceiving;
         END;
         ALLOCATE (BufStart[portnum], bufsize);
         BufIn[portnum] := BufStart[portnum];
         BufOut[portnum] := BufStart[portnum];
         BufLimit[portnum] := BufStart[portnum];
         INC (BufLimit[portnum]:ADDRESS, bufsize - 1);
         BufSize[portnum] := bufsize;
         RETURN Success;
      END StartReceiving;


   PROCEDURE StopReceiving (portnum : CARDINAL) : CommStatus;
   (* Stop receiving characters on a port *)
      BEGIN
         IF NOT CheckPort(portnum) THEN
            RETURN InvalidPort;
         END;
         IF BufStart[portnum] # NIL THEN
            DEALLOCATE (BufStart[portnum], BufSize[portnum]);
            BufLimit[portnum] := NIL;
            BufIn[portnum] := NIL;
            BufOut[portnum] := NIL;
            BufSize[portnum] := 0;
         END;
         DosClose(Handle[portnum]);
         Handle[portnum] := 0;
         RETURN Success;
      END StopReceiving;


   PROCEDURE GetChar (portnum : CARDINAL; VAR ch : CHAR) : CommStatus;
   (* Get a character from the comm port *)
   
      VAR
         status : CARDINAL;
         read : CARDINAL;
         que : RECORD
                  ct : CARDINAL;
                  sz : CARDINAL;
               END;
         i : CARDINAL;
               
      BEGIN
         IF BufStart[portnum] = NIL THEN
            RETURN NotReceiving;
         END;
         IF NOT CheckPort(portnum) THEN
            RETURN InvalidPort;
         END;
         status := DosDevIOCtl (ADR (que), 0, 68H, 1, Handle[portnum]);
         IF (status = 0) AND (que.ct # 0) THEN
            status := DosRead (Handle[portnum], ADR (Temp), que.ct, read);
            IF (status # 0) OR (read = 0) THEN
               RETURN NotReceiving;
            END;
            FOR i := 1 TO read DO
               BufIn[portnum]^ := Temp[i];
               IF BufIn[portnum] = BufLimit[portnum] THEN
                  BufIn[portnum] := BufStart[portnum];
               ELSE
                  INC (BufIn[portnum]:ADDRESS);
               END;
               IF BufIn[portnum] = BufOut[portnum] THEN
                  RETURN BufferOverflow;
               END;
            END;
         END;
         
         IF BufIn[portnum] = BufOut[portnum] THEN
            RETURN NoCharacter;
         END;
         ch := BufOut[portnum]^;
         IF BufOut[portnum] = BufLimit[portnum] THEN
            BufOut[portnum] := BufStart[portnum];
         ELSE
            INC (BufOut[portnum]:ADDRESS);
         END;
         RETURN Success;
      END GetChar;


   PROCEDURE SendChar (portnum : CARDINAL; ch : CHAR; 
                         modem : BOOLEAN) : CommStatus;
   (* send a character to the comm port *)
      
      VAR
         wrote : CARDINAL;
         status : CARDINAL;
         commSt : CHAR;
         
      BEGIN
         IF NOT CheckPort(portnum) THEN
            RETURN InvalidPort;
         END;
         status := DosDevIOCtl (ADR (commSt), 0, 64H, 1, Handle[portnum]);
         IF (status # 0) OR (commSt # 0C) THEN
            RETURN TimeOut;
         ELSE
            status := DosWrite(Handle[portnum], ADR(ch), 1, wrote);
            IF (status # 0) OR (wrote # 1) THEN
               RETURN TimeOut;
            ELSE
               RETURN Success;
            END;
         END;
      END SendChar;


BEGIN   (* module initialization *)
   (* nothing open yet *)
   FOR pn := 0 TO 3 DO
      Handle[pn] := 0;
      BufStart[pn] := NIL;
      BufLimit[pn] := NIL;
      BufIn[pn] := NIL;
      BufOut[pn] := NIL;
      BufSize[pn] := 0;
   END;
END CommPort.


[LISTING THIRTEEN]

IMPLEMENTATION MODULE Files;   (* File I/O for Kermit *)

   FROM FileSystem IMPORT
      File, Response, Delete, Lookup, Close, ReadNBytes, WriteNBytes;

   FROM Strings IMPORT
      Append;
      
   FROM Conversions IMPORT
      CardToString;
      
   FROM SYSTEM IMPORT
      ADR, SIZE;

      
   TYPE
      buffer = ARRAY [1..512] OF CHAR;

      
   VAR
      ext : CARDINAL;  (* new file extensions to avoid name conflict *)
      inBuf, outBuf : buffer;
      inP, outP : CARDINAL;   (* buffer pointers *)
      read, written : CARDINAL;   (* number of bytes read or written *)
                                  (* by ReadNBytes or WriteNBytes    *)
       
      
   PROCEDURE Open (VAR f : File; name : ARRAY OF CHAR) : Status;
   (* opens an existing file for reading, returns status *)
      BEGIN
         Lookup (f, name, FALSE);
         IF f.res = done THEN
            inP := 0;   read := 0;
            RETURN Done;
         ELSE
            RETURN Error;
         END;
      END Open;
      
      
   PROCEDURE Create (VAR f : File; name : ARRAY OF CHAR) : Status;
   (* creates a new file for writing, returns status *)
   
      VAR
         ch : CHAR;
         str : ARRAY [0..3] OF CHAR;
         i : CARDINAL;
         b : BOOLEAN;
         
      BEGIN
         LOOP
            Lookup (f, name, FALSE);   (* check to see if file exists *)
            IF f.res = done THEN
               Close (f);
               (* Filename Clash: Change file name *)
               IF ext > 99 THEN   (* out of new names... *)
                  RETURN Error;
               END;
               i := 0;
               WHILE (name[i] # 0C) AND (name[i] # '.') DO
                  INC (i);   (* scan for end of filename *)
               END;
               name[i] := '.';   name[i + 1] := 'K';   name[i + 2] := 0C;
               i := 0;
               CardToString (ext, 1, str, i, b); 
               Append (name, str);   (* append new extension *)
               INC (ext);
            ELSE
               EXIT;
            END;
         END;
         Lookup (f, name, TRUE);
         IF f.res = done THEN
            outP := 0;
            RETURN Done;
         ELSE
            RETURN Error;
         END;
      END Create;
      
      
   PROCEDURE CloseFile (VAR f : File; Which : FileType) : Status;
   (* closes a file after reading or writing *)
      BEGIN
         written := outP;
         IF (Which = Output) AND (outP > 0) THEN
            WriteNBytes (f, ADR (outBuf), outP);
            written := f.count;
         END;
         Close (f);
         IF (written = outP) AND (f.res = done) THEN
            RETURN Done;
         ELSE
            RETURN Error;
         END;
      END CloseFile;
      
      
   PROCEDURE Get (VAR f : File; VAR ch : CHAR) : Status;
   (* Reads one character from the file, returns status *)
      BEGIN
         IF inP = read THEN
            ReadNBytes (f, ADR (inBuf), SIZE (inBuf));
            read := f.count;
            inP := 0;
         END;
         IF read = 0 THEN
            RETURN EOF;
         ELSE
            INC (inP);
            ch := inBuf[inP];
            RETURN Done;
         END;
      END Get;
      
      
   PROCEDURE Put (ch : CHAR);
   (* Writes one character to the file buffer *)
      BEGIN
         INC (outP);
         outBuf[outP] := ch;
      END Put;
      
      
   PROCEDURE DoWrite (VAR f : File) : Status;
   (* Writes buffer to disk only if nearly full *)
      BEGIN
         IF outP < 400 THEN   (* still room in buffer *)
            RETURN Done;
         ELSE
            WriteNBytes (f, ADR (outBuf), outP);
            written := f.count;
            IF (written = outP) AND (f.res = done) THEN
               outP := 0;
               RETURN Done;
            ELSE
               RETURN Error;
            END;
         END;
      END DoWrite;  
      
BEGIN (* module initialization *)
   ext := 0;
END Files.




[LISTING FOURTEEN]

DEFINITION MODULE KH;

CONST
   ID_OK        =  25;
   
   PARITY_OFF   =  150;
   ID_NONE      =  152;
   ID_ODD       =  151;
   ID_EVEN      =  150;
   
   STOP_OFF     =  140;
   ID_STOP2     =  142;
   ID_STOP1     =  141;
   
   DATA_OFF     =  130;
   ID_DATA8     =  138;
   ID_DATA7     =  137;

   BAUD_OFF     =  120;   
   ID_B19K2     =  128;
   ID_B9600     =  127;
   ID_B4800     =  126;
   ID_B2400     =  125;
   ID_B1200     =  124;
   ID_B600      =  123;
   ID_B300      =  122;
   ID_B150      =  121;
   ID_B110      =  120;
   
   COM_OFF      =  100;
   ID_COM2      =  101;
   ID_COM1      =  100;

   IDM_C2       =  24;
   IDM_C1       =  23;
   IDM_AMBER    =  22;
   IDM_GREEN    =  21;
   IDM_WHITE    =  20;
   IDM_COLORS   =  19;
   IDM_DIREND   =  18;
   ID_DIRPATH   =  17;
   ID_SENDFN    =  16;
   IDM_DIRPATH  =  15;
   IDM_SENDFN   =  14;
   IDM_TERMHELP =  13;
   IDM_HELPMENU =  12;   
   IDM_ABOUT    =  11;
   IDM_PARITY   =  10;
   IDM_STOPBITS =  9;
   IDM_DATABITS =  8;
   IDM_BAUDRATE =  7;
   IDM_COMPORT  =  6;
   IDM_QUIT     =  5;
   IDM_REC      =  4;
   IDM_SEND     =  3;
   IDM_CONNECT  =  2;
   IDM_DIR      =  1;
   IDM_OPTIONS  =  52;
   IDM_FILE     =  51;
   IDM_KERMIT   =  50;

END KH.


[LISTING FIFTEEN]

IMPLEMENTATION MODULE KH;
END KH.


[LISTING SIXTEEN]

#define IDM_KERMIT     50
#define IDM_FILE       51
#define IDM_OPTIONS    52
#define IDM_HELP       0
#define IDM_DIR        1
#define IDM_CONNECT    2
#define IDM_SEND       3
#define IDM_REC        4
#define IDM_QUIT       5
#define IDM_COMPORT    6
#define IDM_BAUDRATE   7
#define IDM_DATABITS   8
#define IDM_STOPBITS   9
#define IDM_PARITY     10
#define IDM_ABOUT      11
#define IDM_HELPMENU   12
#define IDM_TERMHELP   13
#define IDM_SENDFN     14
#define IDM_DIRPATH    15
#define ID_SENDFN      16
#define ID_DIRPATH     17
#define IDM_DIREND     18
#define IDM_COLORS     19
#define IDM_WHITE      20
#define IDM_GREEN      21
#define IDM_AMBER      22
#define IDM_C1         23
#define IDM_C2         24
#define ID_OK          25
#define ID_COM1        100
#define ID_COM2        101
#define ID_B110        120
#define ID_B150        121
#define ID_B300        122
#define ID_B600        123
#define ID_B1200       124
#define ID_B2400       125
#define ID_B4800       126
#define ID_B9600       127
#define ID_B19K2       128
#define ID_DATA7       137
#define ID_DATA8       138
#define ID_STOP1       141
#define ID_STOP2       142
#define ID_EVEN        150
#define ID_ODD         151
#define ID_NONE        152


[LISTING SEVENTEEN]

IMPLEMENTATION MODULE DataLink;  (* Sends and Receives Packets for PCKermit *)

   FROM ElapsedTime IMPORT
      StartTime, GetTime;

   FROM Screen IMPORT
      ClrScr, WriteString, WriteLn;

   FROM OS2DEF IMPORT
      HIWORD, LOWORD;

   FROM PMWIN IMPORT
      MPARAM, MPFROM2SHORT, WinPostMsg;
      
   FROM Shell IMPORT
      ChildFrameWindow, comport;
                  
   FROM CommPort IMPORT
      CommStatus, GetChar, SendChar;
      
   FROM PAD IMPORT
      PacketType, yourNPAD, yourPADC, yourEOL; 

   FROM KH IMPORT
      COM_OFF;
      
   FROM SYSTEM IMPORT
      BYTE;
      
   IMPORT ASCII;


   CONST
      MAXtime = 100;   (* hundredths of a second -- i.e., one second *)
      MAXsohtrys = 100;
      DL_BadCS = 1;
      DL_NoSOH = 2;
      

   TYPE
      SMALLSET = SET OF [0..7];   (* BYTE *)               
      
   VAR
      ch : CHAR;
      status : CommStatus;
      

   PROCEDURE Delay (t : CARDINAL);
   (* delay time in milliseconds *)
   
      VAR
         tmp : LONGINT;
         
      BEGIN
         tmp := t DIV 10;
         StartTime;
         WHILE GetTime() < tmp DO
         END;
      END Delay;
      
            
   PROCEDURE ByteAnd (a, b : BYTE) : BYTE;
      BEGIN
         RETURN BYTE (SMALLSET (a) * SMALLSET (b));
      END ByteAnd;
      
            
   PROCEDURE Char (c : INTEGER) : CHAR;
   (* converts a number 0-95 into a printable character *)
      BEGIN
         RETURN (CHR (CARDINAL (ABS (c) + 32)));
      END Char;
      
      
   PROCEDURE UnChar (c : CHAR) : INTEGER;
   (* converts a character into its corresponding number *)
      BEGIN
         RETURN (ABS (INTEGER (ORD (c)) - 32));
      END UnChar;


   PROCEDURE FlushUART;
   (* ensure no characters left in UART holding registers *)
      BEGIN
         Delay (500);
         REPEAT
            status := GetChar (comport - COM_OFF, ch); 
         UNTIL status = NoCharacter;
      END FlushUART;
        

   PROCEDURE SendPacket (s : PacketType);
   (* Adds SOH and CheckSum to packet *)
   
      VAR
         i : CARDINAL;
         checksum : INTEGER;
         
      BEGIN
         Delay (10);   (* give host a chance to catch its breath *)
         FOR i := 1 TO yourNPAD DO
            status := SendChar (comport - COM_OFF, yourPADC, FALSE);
         END;
         status := SendChar (comport - COM_OFF, ASCII.soh, FALSE);
         i := 1;
         checksum := 0;
         WHILE s[i] # 0C DO
            INC (checksum, ORD (s[i]));
            status := SendChar (comport - COM_OFF, s[i], FALSE);
            INC (i);
         END;
         checksum := checksum + (INTEGER (BITSET (checksum) * {7, 6}) DIV 64);
         checksum := INTEGER (BITSET (checksum) * {5, 4, 3, 2, 1, 0});
         status := SendChar (comport - COM_OFF, Char (checksum), FALSE);
         IF yourEOL # 0C THEN
            status := SendChar (comport - COM_OFF, yourEOL, FALSE);
         END;
      END SendPacket;
      
      
   PROCEDURE ReceivePacket (VAR r : PacketType) : BOOLEAN;
   (* strips SOH and checksum -- returns status: TRUE = good packet     *)
   (* received;  FALSE = timed out waiting for packet or checksum error *)
   
      VAR
         sohtrys : INTEGER;
         i, len : INTEGER;
         ch : CHAR;
         checksum : INTEGER;
         mycheck, yourcheck : CHAR;
         
      BEGIN
         sohtrys := MAXsohtrys;
         REPEAT
            StartTime;
            REPEAT
               status := GetChar (comport - COM_OFF, ch);
            UNTIL (status = Success) OR (GetTime() > MAXtime);
            ch := CHAR (ByteAnd (ch, 177C));   (* mask off MSB *)
            (* skip over up to MAXsohtrys padding characters, *)
            (* but allow only MAXsohtrys/10 timeouts          *)
            IF status = Success THEN
               DEC (sohtrys);
            ELSE
               DEC (sohtrys, 10);
            END;
         UNTIL (ch = ASCII.soh) OR (sohtrys <= 0);
         
         IF ch = ASCII.soh THEN
            (* receive rest of packet *)
            StartTime;
            REPEAT
               status := GetChar (comport - COM_OFF, ch);
            UNTIL (status = Success) OR (GetTime() > MAXtime);
            ch := CHAR (ByteAnd (ch, 177C));
            len := UnChar (ch);
            r[1] := ch;
            checksum := ORD (ch);
            i := 2;   (* on to second character in packet -- after LEN *)
            REPEAT
               StartTime;
               REPEAT
                  status := GetChar (comport - COM_OFF, ch);
               UNTIL (status = Success) OR (GetTime() > MAXtime);
               ch := CHAR (ByteAnd (ch, 177C));
               r[i] := ch;   INC (i);
               INC (checksum, (ORD (ch)));   
            UNTIL (i > len);
            (* get checksum character *)
            StartTime;
            REPEAT 
               status := GetChar (comport - COM_OFF, ch);
            UNTIL (status = Success) OR (GetTime() > MAXtime);
            ch := CHAR (ByteAnd (ch, 177C));
            yourcheck := ch;
            r[i] := 0C;
            checksum := checksum + 
                            (INTEGER (BITSET (checksum) * {7, 6}) DIV 64);
            checksum := INTEGER (BITSET (checksum) *  {5, 4, 3, 2, 1, 0});
            mycheck := Char (checksum);
            IF mycheck = yourcheck THEN   (* checksum OK *)
               RETURN TRUE;
            ELSE   (* ERROR!!! *)
               WinPostMsg (ChildFrameWindow, WM_DL, 
                           MPFROM2SHORT (DL_BadCS, 0), 0);
               RETURN FALSE;  
            END;
         ELSE
            WinPostMsg (ChildFrameWindow, WM_DL, 
                        MPFROM2SHORT (DL_NoSOH, 0), 0);
            RETURN FALSE;
         END;
      END ReceivePacket;
      
      
   PROCEDURE DoDLMsg (mp1, mp2 : MPARAM);
   (* Process DataLink Messages *)
      BEGIN
         CASE LOWORD (mp1) OF
            DL_BadCS:
               WriteString ("Bad Checksum");   WriteLn;
         |  DL_NoSOH:
               WriteString ("No SOH");   WriteLn;
         ELSE
            (* Do Nothing *)
         END;
      END DoDLMsg;

END DataLink.


[LISTING EIGHTEEN]

#include <os2.h>
#include "pckermit.h"

ICON IDM_KERMIT pckermit.ico

MENU IDM_KERMIT
   BEGIN
      SUBMENU "~File", IDM_FILE
         BEGIN
            MENUITEM "~Directory...",     IDM_DIR
            MENUITEM "~Connect\t^C",          IDM_CONNECT
            MENUITEM "~Send...\t^S",          IDM_SEND
            MENUITEM "~Receive...\t^R",       IDM_REC
            MENUITEM SEPARATOR
            MENUITEM "E~xit\t^X",             IDM_QUIT
            MENUITEM "A~bout PCKermit...",  IDM_ABOUT
         END
         
      SUBMENU "~Options", IDM_OPTIONS
         BEGIN
            MENUITEM "~COM port...",      IDM_COMPORT
            MENUITEM "~Baud rate...",     IDM_BAUDRATE
            MENUITEM "~Data bits...",     IDM_DATABITS
            MENUITEM "~Stop bits...",     IDM_STOPBITS
            MENUITEM "~Parity bits...",   IDM_PARITY
         END

      SUBMENU "~Colors", IDM_COLORS
         BEGIN
            MENUITEM "~White Mono",       IDM_WHITE
            MENUITEM "~Green Mono",       IDM_GREEN
            MENUITEM "~Amber Mono",       IDM_AMBER
            MENUITEM "Full Color ~1",     IDM_C1
            MENUITEM "Full Color ~2",     IDM_C2
         END
         	 
      MENUITEM "F1=Help",    IDM_HELP, MIS_HELP | MIS_BUTTONSEPARATOR
   END

ACCELTABLE IDM_KERMIT
   BEGIN
      "^C", IDM_CONNECT
      "^S", IDM_SEND
      "^R", IDM_REC
      "^X", IDM_QUIT
   END
   
DLGTEMPLATE IDM_COMPORT LOADONCALL MOVEABLE DISCARDABLE 
BEGIN
    DIALOG "", IDM_COMPORT, 129, 91, 143, 54, FS_NOBYTEALIGN | FS_DLGBORDER | 
                WS_VISIBLE | WS_CLIPSIBLINGS | WS_SAVEBITS
    BEGIN
        CONTROL "Select COM Port", IDM_COMPORT, 10, 9, 83, 38, 
                WC_STATIC, SS_GROUPBOX | WS_VISIBLE
        CONTROL "COM1", ID_COM1, 30, 25, 43, 10, WC_BUTTON, 
	        BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP | WS_VISIBLE
        CONTROL "COM2", ID_COM2, 30, 15, 39, 10, WC_BUTTON, 
	        BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "OK", ID_OK, 101, 10, 38, 12, WC_BUTTON, BS_PUSHBUTTON | 
                BS_DEFAULT | WS_GROUP | WS_TABSTOP | WS_VISIBLE
    END
END

DLGTEMPLATE IDM_BAUDRATE LOADONCALL MOVEABLE DISCARDABLE 
BEGIN
    DIALOG "", IDM_BAUDRATE, 131, 54, 142, 115, FS_NOBYTEALIGN | 
                FS_DLGBORDER | WS_VISIBLE | WS_CLIPSIBLINGS | WS_SAVEBITS
    BEGIN
        CONTROL "Select Baud Rate", IDM_BAUDRATE, 8, 6, 85, 107, 
                WC_STATIC, SS_GROUPBOX | WS_VISIBLE
        CONTROL "110 Baud", ID_B110, 20, 90, 62, 10, WC_BUTTON, 
	        BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP | WS_VISIBLE
        CONTROL "150 Baud", ID_B150, 20, 80, 57, 10, WC_BUTTON, 
	        BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "300 Baud", ID_B300, 20, 70, 58, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "600 Baud", ID_B600, 20, 60, 54, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "1200 Baud", ID_B1200, 20, 50, 59, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "2400 Baud", ID_B2400, 20, 40, 63, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "4800 Baud", ID_B4800, 20, 30, 62, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "9600 Baud", ID_B9600, 20, 20, 59, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "19,200 Baud", ID_B19K2, 20, 10, 69, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "OK", ID_OK, 100, 8, 38, 12, WC_BUTTON, BS_PUSHBUTTON | 
		BS_DEFAULT | WS_GROUP | WS_TABSTOP | WS_VISIBLE
    END
END

DLGTEMPLATE IDM_DATABITS LOADONCALL MOVEABLE DISCARDABLE 
BEGIN
    DIALOG "", IDM_DATABITS, 137, 80, 140, 56, FS_NOBYTEALIGN | 
                FS_DLGBORDER | WS_VISIBLE | WS_SAVEBITS
    BEGIN
        CONTROL "Select Data Bits", IDM_DATABITS, 8, 11, 80, 36, 
                WC_STATIC, SS_GROUPBOX | WS_VISIBLE
        CONTROL "7 Data Bits", ID_DATA7, 15, 25, 67, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP | WS_VISIBLE
        CONTROL "8 Data Bits", ID_DATA8, 15, 15, 64, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "OK", ID_OK, 96, 12, 38, 12, WC_BUTTON, BS_PUSHBUTTON | 
		BS_DEFAULT | WS_GROUP | WS_TABSTOP | WS_VISIBLE
    END
END

DLGTEMPLATE IDM_STOPBITS LOADONCALL MOVEABLE DISCARDABLE 
BEGIN
    DIALOG "", IDM_STOPBITS, 139, 92, 140, 43, FS_NOBYTEALIGN | 
                FS_DLGBORDER | WS_VISIBLE | WS_SAVEBITS
    BEGIN
        CONTROL "Select Stop Bits", IDM_STOPBITS, 9, 6, 80, 32, 
                WC_STATIC, SS_GROUPBOX | WS_VISIBLE
        CONTROL "1 Stop Bit", ID_STOP1, 20, 20, 57, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP | WS_VISIBLE
        CONTROL "2 Stop Bits", ID_STOP2, 20, 10, 60, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "OK", ID_OK, 96, 8, 38, 12, WC_BUTTON, BS_PUSHBUTTON | 
		BS_DEFAULT | WS_GROUP | WS_TABSTOP | WS_VISIBLE
    END
END

DLGTEMPLATE IDM_PARITY LOADONCALL MOVEABLE DISCARDABLE 
BEGIN
    DIALOG "", IDM_PARITY, 138, 84, 134, 57, FS_NOBYTEALIGN | FS_DLGBORDER | 
                WS_VISIBLE | WS_SAVEBITS
    BEGIN
        CONTROL "Select Parity", IDM_PARITY, 12, 6, 64, 46, WC_STATIC, 
                SS_GROUPBOX | WS_VISIBLE
        CONTROL "Even", ID_EVEN, 25, 30, 40, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP | WS_VISIBLE
        CONTROL "Odd", ID_ODD, 25, 20, 38, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "None", ID_NONE, 25, 10, 40, 10, WC_BUTTON, 
		BS_AUTORADIOBUTTON | WS_TABSTOP | WS_VISIBLE
        CONTROL "OK", ID_OK, 88, 8, 38, 12, WC_BUTTON, BS_PUSHBUTTON | 
		BS_DEFAULT | WS_GROUP | WS_TABSTOP | WS_VISIBLE
    END
END


DLGTEMPLATE IDM_ABOUT LOADONCALL MOVEABLE DISCARDABLE 
BEGIN
    DIALOG "", IDM_ABOUT, 93, 74, 229, 88, FS_NOBYTEALIGN | FS_DLGBORDER | 
                WS_VISIBLE | WS_SAVEBITS
    BEGIN
        ICON IDM_KERMIT -1, 12, 64, 22, 16
        CONTROL "PCKermit for OS/2", 256, 67, 70, 82, 8, WC_STATIC, 
		SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "Copyright (c) 1990 by Brian R. Anderson", 257, 27, 30, 172, 8, 
                WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "Microcomputer to Mainframe Communications", 259, 13, 50, 199, 8, 
                WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "  OK  ", 258, 88, 10, 38, 12, WC_BUTTON, BS_PUSHBUTTON | 
                BS_DEFAULT | WS_TABSTOP | WS_VISIBLE
    END
END

DLGTEMPLATE IDM_HELPMENU LOADONCALL MOVEABLE DISCARDABLE 
BEGIN
    DIALOG "", IDM_HELPMENU, 83, 45, 224, 125, FS_NOBYTEALIGN | FS_DLGBORDER | 
                WS_VISIBLE | WS_CLIPSIBLINGS | WS_SAVEBITS
    BEGIN
        ICON IDM_KERMIT -1, 14, 99, 21, 16
        CONTROL "PCKermit Help Menu", 256, 64, 106, 91, 8, WC_STATIC, 
                SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "set communications Options .................. Alt, O", 
                258, 10, 80, 201, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | 
                WS_GROUP | WS_VISIBLE
        CONTROL "Connect to Host ................................... Alt, F; C", 
                259, 10, 70, 204, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | 
                WS_GROUP | WS_VISIBLE
        CONTROL "Directory .............................................. Alt, F; D", 
                260, 10, 60, 207, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | 
                WS_GROUP | WS_VISIBLE
        CONTROL "Send a File .......................................... Alt, F; S", 
                261, 10, 50, 207, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | 
                WS_GROUP | WS_VISIBLE
        CONTROL "Receive a File ...................................... Alt, F; R", 
                262, 10, 40, 209, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | 
                WS_GROUP | WS_VISIBLE
        CONTROL "Exit ...................................................... Alt, F; X", 
                263, 10, 30, 205, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | 
                WS_GROUP | WS_VISIBLE
        CONTROL "OK", 264, 83, 9, 38, 12, WC_BUTTON, BS_PUSHBUTTON | 
		WS_TABSTOP | WS_VISIBLE | BS_DEFAULT
    END
END

DLGTEMPLATE IDM_TERMHELP LOADONCALL MOVEABLE DISCARDABLE 
BEGIN
    DIALOG "", IDM_TERMHELP, 81, 20, 238, 177, FS_NOBYTEALIGN | 
                FS_DLGBORDER | WS_VISIBLE | WS_CLIPSIBLINGS | WS_SAVEBITS
    BEGIN
        CONTROL "^E = Echo mode", 256, 10, 160, 72, 8, WC_STATIC, 
                SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "^L = Local echo mode", 257, 10, 150, 97, 8, WC_STATIC, 
                SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "^T = Terminal Mode (no echo)", 258, 10, 140, 131, 8, WC_STATIC, 
                SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "^N = Newline mode (<cr> --> <cr><lf>)", 259, 10, 130, 165, 8, 
                WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "^O = Newline mode OFF", 260, 10, 120, 109, 8, WC_STATIC, 
                SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "Televideo TVI950 / IBM 7171 Terminal Emulation", 261, 10, 100, 217, 8, 
                WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "Sh-F1 - Sh-F12   =   PF1 - PF12", 262, 10, 90, 135, 8, 
                WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "Home                 =  Clear", 263, 10, 80, 119, 8, WC_STATIC, 
                SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "PgDn                  =  Page  Down (as used in PROFS)", 
                264, 10, 70, 228, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | 
                WS_GROUP | WS_VISIBLE
        CONTROL "PgUp                  =  Page Up (as used in PROFS)", 
                265, 10, 60, 227, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | 
                WS_GROUP | WS_VISIBLE
        CONTROL "Insert                 =  Insert (Enter to Clear)", 266, 10, 40, 221, 8, 
                WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "Delete                =  Delete", 267, 10, 30, 199, 8, 
                WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "Control-G           =  Reset (rewrites the screen)", 268, 10, 20, 222, 8, 
                WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "Cursor Keys (i.e., Up, Down, Left, Right) all work.", 
                269, 10, 10, 229, 8, WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | 
                WS_GROUP | WS_VISIBLE
        CONTROL "OK", 270, 193, 158, 38, 12, WC_BUTTON, BS_PUSHBUTTON | 
                BS_DEFAULT | WS_TABSTOP | WS_VISIBLE
        CONTROL "End                    =  End (as used in PROFS)", 271, 10, 50, 209, 8, 
                WC_STATIC, SS_TEXT | DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
    END
END


DLGTEMPLATE IDM_SENDFN LOADONCALL MOVEABLE DISCARDABLE 
BEGIN
    DIALOG "", IDM_SENDFN, 113, 90, 202, 60, FS_NOBYTEALIGN | FS_DLGBORDER | 
                WS_VISIBLE | WS_SAVEBITS
    BEGIN
        CONTROL "Send File", 256, 4, 4, 195, 24, WC_STATIC, SS_GROUPBOX | 
                WS_GROUP | WS_VISIBLE
        CONTROL "Enter filename:", 257, 13, 11, 69, 8, WC_STATIC, SS_TEXT | 
                DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
	ICON	IDM_KERMIT -1, 15, 38, 22, 16
        CONTROL "PCKermit for OS/2", 259, 59, 45, 82, 8, WC_STATIC, SS_TEXT | 
                DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "OK", 260, 154, 36, 38, 12, WC_BUTTON, BS_PUSHBUTTON | 
                WS_TABSTOP | WS_VISIBLE | BS_DEFAULT
        CONTROL "", ID_SENDFN, 89, 10, 98, 8, WC_ENTRYFIELD, ES_LEFT | 
		ES_MARGIN | WS_TABSTOP | WS_VISIBLE
    END
END

DLGTEMPLATE IDM_DIRPATH LOADONCALL MOVEABLE DISCARDABLE 
BEGIN
    DIALOG "", IDM_DIRPATH, 83, 95, 242, 46, FS_NOBYTEALIGN | FS_DLGBORDER | 
                WS_VISIBLE | WS_SAVEBITS
    BEGIN
        CONTROL "Directory", 256, 7, 5, 227, 24, WC_STATIC, SS_GROUPBOX | 
                WS_GROUP | WS_VISIBLE
        CONTROL "Path:", 257, 28, 11, 26, 8, WC_STATIC, SS_TEXT | DT_LEFT | 
                DT_TOP | WS_GROUP | WS_VISIBLE
        CONTROL "OK", 258, 185, 31, 38, 12, WC_BUTTON, BS_PUSHBUTTON | 
                WS_TABSTOP | WS_VISIBLE | BS_DEFAULT
        CONTROL "*.*", ID_DIRPATH, 57, 11, 166, 8, WC_ENTRYFIELD, ES_LEFT | 
		ES_AUTOSCROLL | ES_MARGIN | WS_TABSTOP | WS_VISIBLE
    END
END

DLGTEMPLATE IDM_DIREND LOADONCALL MOVEABLE DISCARDABLE 
BEGIN
    DIALOG "", IDM_DIREND, 149, 18, 101, 27, FS_NOBYTEALIGN | FS_DLGBORDER | 
                WS_VISIBLE | WS_SAVEBITS
    BEGIN
        CONTROL "Cancel", 256, 30, 2, 38, 12, WC_BUTTON, BS_PUSHBUTTON | 
                BS_DEFAULT | WS_TABSTOP | WS_VISIBLE
        CONTROL "Directory Complete", 257, 9, 16, 84, 8, WC_STATIC, SS_TEXT | 
                DT_LEFT | DT_TOP | WS_GROUP | WS_VISIBLE
    END
END



[LISTING NINETEEN]

HEAPSIZE 16384
STACKSIZE 16384
EXPORTS
    WindowProc
    ChildWindowProc
    

[FILE PCKERMIT]

OS2DEF.SYM: OS2DEF.DEF
    M2 OS2DEF.DEF/OUT:OS2DEF.SYM
OS2DEF.OBJ: OS2DEF.MOD OS2DEF.SYM
    M2 OS2DEF.MOD/OUT:OS2DEF.OBJ
PMWIN.SYM: PMWIN.DEF OS2DEF.SYM
    M2 PMWIN.DEF/OUT:PMWIN.SYM
PMWIN.OBJ: PMWIN.MOD OS2DEF.SYM PMWIN.SYM
    M2 PMWIN.MOD/OUT:PMWIN.OBJ
KH.SYM: KH.DEF
    M2 KH.DEF/OUT:KH.SYM
KH.OBJ: KH.MOD KH.SYM
    M2 KH.MOD/OUT:KH.OBJ
SHELL.SYM: SHELL.DEF PMWIN.SYM OS2DEF.SYM
    M2 SHELL.DEF/OUT:SHELL.SYM
TERM.SYM: TERM.DEF
    M2 TERM.DEF/OUT:TERM.SYM
PAD.SYM: PAD.DEF PMWIN.SYM
    M2 PAD.DEF/OUT:PAD.SYM
DATALINK.SYM: DATALINK.DEF PAD.SYM PMWIN.SYM
    M2 DATALINK.DEF/OUT:DATALINK.SYM
PMAVIO.SYM: PMAVIO.DEF PMWIN.SYM OS2DEF.SYM
    M2 PMAVIO.DEF/OUT:PMAVIO.SYM
PMAVIO.OBJ: PMAVIO.MOD PMAVIO.SYM
    M2 PMAVIO.MOD/OUT:PMAVIO.OBJ
PMGPI.SYM: PMGPI.DEF OS2DEF.SYM
    M2 PMGPI.DEF/OUT:PMGPI.SYM
PMGPI.OBJ: PMGPI.MOD OS2DEF.SYM PMGPI.SYM
    M2 PMGPI.MOD/OUT:PMGPI.OBJ
COMMPORT.SYM: COMMPORT.DEF
    M2 COMMPORT.DEF/OUT:COMMPORT.SYM
COMMPORT.OBJ: COMMPORT.MOD COMMPORT.SYM
    M2 COMMPORT.MOD/OUT:COMMPORT.OBJ
FILES.SYM: FILES.DEF
    M2 FILES.DEF/OUT:FILES.SYM
PCKERMIT.OBJ: PCKERMIT.MOD SHELL.SYM KH.SYM PMWIN.SYM OS2DEF.SYM
    M2 PCKERMIT.MOD/OUT:PCKERMIT.OBJ
SCREEN.SYM: SCREEN.DEF PMAVIO.SYM
    M2 SCREEN.DEF/OUT:SCREEN.SYM
SCREEN.OBJ: SCREEN.MOD SCREEN.SYM
    M2 SCREEN.MOD/OUT:SCREEN.OBJ
FILES.OBJ: FILES.MOD FILES.SYM
    M2 FILES.MOD/OUT:FILES.OBJ
SHELL.OBJ: SHELL.MOD COMMPORT.SYM KH.SYM PMGPI.SYM PMWIN.SYM PMAVIO.SYM -
SCREEN.SYM DATALINK.SYM PAD.SYM TERM.SYM OS2DEF.SYM SHELL.SYM
    M2 SHELL.MOD/OUT:SHELL.OBJ
TERM.OBJ: TERM.MOD COMMPORT.SYM KH.SYM SHELL.SYM PMWIN.SYM SCREEN.SYM TERM.SYM
    M2 TERM.MOD/OUT:TERM.OBJ
PAD.OBJ: PAD.MOD DATALINK.SYM KH.SYM SHELL.SYM PMWIN.SYM COMMPORT.SYM -
FILES.SYM OS2DEF.SYM SCREEN.SYM PAD.SYM
    M2 PAD.MOD/OUT:PAD.OBJ
DATALINK.OBJ: DATALINK.MOD KH.SYM PAD.SYM COMMPORT.SYM SHELL.SYM PMWIN.SYM -
OS2DEF.SYM SCREEN.SYM DATALINK.SYM
    M2 DATALINK.MOD/OUT:DATALINK.OBJ
PCKERMIT.res: PCKERMIT.rc PCKERMIT.h PCKERMIT.ico
    rc -r PCKERMIT.rc
PCKERMIT.EXE: OS2DEF.OBJ PMWIN.OBJ KH.OBJ PMAVIO.OBJ PMGPI.OBJ COMMPORT.OBJ -
PCKERMIT.OBJ SCREEN.OBJ FILES.OBJ SHELL.OBJ TERM.OBJ PAD.OBJ DATALINK.OBJ 
    LINK @PCKERMIT.LNK
    rc PCKERMIT.res
PCKERMIT.exe: PCKERMIT.res
    rc PCKERMIT.res    


[FILE PCKERMIT.LNK]

KH.OBJ+
pckermit.OBJ+
SCREEN.OBJ+
COMMPORT.OBJ+
FILES.OBJ+
SHELL.OBJ+
TERM.OBJ+
PAD.OBJ+
DATALINK.OBJ
pckermit
pckermit
PM+
M2LIB+
DOSCALLS+
OS2
pckermit.edf


[FILE PAD.MOD]

IMPLEMENTATION MODULE PAD;   (* Packet Assembler/Disassembler for Kermit *)

   FROM SYSTEM IMPORT
      ADR;

   FROM Storage IMPORT
      ALLOCATE, DEALLOCATE;
      
   FROM Screen IMPORT
      ClrScr, WriteString, WriteInt, WriteHex, WriteLn;

   FROM OS2DEF IMPORT
      HIWORD, LOWORD;
            
   FROM DosCalls IMPORT
      ExitType, DosExit;
      
   FROM Strings IMPORT
      Length, Assign;
      
   FROM FileSystem IMPORT
      File;
      
   FROM Directories IMPORT
      FileAttributes, AttributeSet, DirectoryEntry, FindFirst, FindNext;
      
   FROM Files IMPORT
      Status, FileType, Open, Create, CloseFile, Get, Put, DoWrite;

   FROM PMWIN IMPORT
      MPARAM, MPFROM2SHORT, WinPostMsg;
      
   FROM Shell IMPORT
      ChildFrameWindow, comport;
      
   FROM KH IMPORT
      COM_OFF;
            
   FROM DataLink IMPORT
      FlushUART, SendPacket, ReceivePacket;

   FROM SYSTEM IMPORT
      BYTE;
                        
   IMPORT ASCII;
   

   CONST
      myMAXL = 94;
      myTIME = 10;
      myNPAD = 0;
      myPADC = 0C;
      myEOL  = 0C;
      myQCTL = '#';
      myQBIN = '&';
      myCHKT = '1';     (* one character checksum *)
      MAXtrys = 5;
      (* From DEFINITION MODULE:
      PAD_Quit = 0;  *)
      PAD_SendPacket = 1;
      PAD_ResendPacket = 2;
      PAD_NoSuchFile = 3;
      PAD_ExcessiveErrors = 4;
      PAD_ProbClSrcFile = 5;
      PAD_ReceivedPacket = 6;
      PAD_Filename = 7;
      PAD_RequestRepeat = 8;
      PAD_DuplicatePacket = 9;
      PAD_UnableToOpen = 10;
      PAD_ProbClDestFile = 11;
      PAD_ErrWrtFile = 12;
      PAD_Msg = 13;
      
      
   TYPE
      (* From Definition Module:
      PacketType = ARRAY [1..100] OF CHAR;
      *)
      SMALLSET = SET OF [0..7];   (* a byte *)
      
                        
   VAR
      yourMAXL : INTEGER;   (* maximum packet length -- up to 94 *)
      yourTIME : INTEGER;   (* time out -- seconds *) 
      (* From Definition Module
      yourNPAD : INTEGER;   (* number of padding characters *)
      yourPADC : CHAR;   (* padding characters *)
      yourEOL  : CHAR;   (* End Of Line -- terminator *)
      *)
      yourQCTL : CHAR;   (* character for quoting controls '#' *)
      yourQBIN : CHAR;   (* character for quoting binary '&' *)
      yourCHKT : CHAR;   (* check type -- 1 = checksum, etc. *)
      sF, rF : File;   (* files being sent/received *)
      InputFileOpen : BOOLEAN;
      rFname : ARRAY [0..20] OF CHAR;
      sP, rP : PacketType;   (* packets sent/received *)
      sSeq, rSeq : INTEGER;   (* sequence numbers *)
      PktNbr : INTEGER;   (* actual packet number -- no repeats up to 32,000 *)
      ErrorMsg : ARRAY [0..40] OF CHAR;
      

   PROCEDURE PtrToStr (mp : MPARAM; VAR s : ARRAY OF CHAR);
   (* Convert a pointer to a string into a string *)
      
      TYPE
         PC = POINTER TO CHAR;
      
      VAR
         p : PC;
         i : CARDINAL;
         c : CHAR;
         
      BEGIN
         i := 0;
         REPEAT
            p := PC (mp);
            c := p^;
            s[i] := c;
            INC (i);
            INC (mp);
         UNTIL c = 0C;
      END PtrToStr;


   PROCEDURE DoPADMsg (mp1, mp2 : MPARAM);
   (* Output messages for Packet Assembler/Disassembler *)
            
      VAR
         Message : ARRAY [0..40] OF CHAR;
         
      BEGIN
         CASE LOWORD (mp1) OF
            PAD_SendPacket:
               WriteString ("Sent Packet #");   
               WriteInt (LOWORD (mp2), 5);
               WriteString ("  (ID: ");   WriteHex (HIWORD (mp2), 2);   
               WriteString ("h)");
         |  PAD_ResendPacket:
               WriteString ("ERROR -- Resending:");   WriteLn;
               WriteString ("     Packet #");   
               WriteInt (LOWORD (mp2), 5);
               WriteString ("  (ID: ");   WriteHex (HIWORD (mp2), 2);   
               WriteString ("h)");
         |  PAD_NoSuchFile:
               WriteString ("No such file: ");   
               PtrToStr (mp2, Message);   WriteString (Message);
         |  PAD_ExcessiveErrors:
               WriteString ("Excessive errors ..."); 
         |  PAD_ProbClSrcFile:
               WriteString ("Problem closing source file...");  
         |  PAD_ReceivedPacket:
               WriteString ("Received Packet #");   
               WriteInt (LOWORD (mp2), 5);
               WriteString ("  (ID: ");   WriteHex (HIWORD (mp2), 2);   
               WriteString ("h)");
         |  PAD_Filename:
               WriteString ("Filename = ");   
               PtrToStr (mp2, Message);   WriteString (Message);
         |  PAD_RequestRepeat:
               WriteString ("ERROR -- Requesting Repeat:");   WriteLn;
               WriteString ("         Packet #");   
               WriteInt (LOWORD (mp2), 5);
               WriteString ("  (ID: ");   WriteHex (HIWORD (mp2), 2);   
               WriteString ("h)");
         |  PAD_DuplicatePacket:
               WriteString ("Discarding Duplicate:");   WriteLn;
               WriteString ("         Packet #");   
               WriteString ("  (ID: ");   WriteHex (HIWORD (mp2), 2);   
               WriteString ("h)");
         |  PAD_UnableToOpen:
               WriteString ("Unable to open file: ");
               PtrToStr (mp2, Message);   WriteString (Message);
         |  PAD_ProbClDestFile:
               WriteString ("Error closing file: ");   
               PtrToStr (mp2, Message);   WriteString (Message);
         |  PAD_ErrWrtFile:
               WriteString ("Error writing to file: ");   
               PtrToStr (mp2, Message);   WriteString (Message);
         |  PAD_Msg:
               PtrToStr (mp2, Message);   WriteString (Message);
         ELSE
            (* Do Nothing *)
         END;
         WriteLn; 
      END DoPADMsg;
      

   PROCEDURE CloseInput;
   (* Close the input file, if it exists.  Reset Input File Open flag *)
      BEGIN
         IF InputFileOpen THEN
            IF CloseFile (sF, Input) = Done THEN
               InputFileOpen := FALSE;
            ELSE
               WinPostMsg (ChildFrameWindow, WM_PAD,
                  MPFROM2SHORT (PAD_ProbClSrcFile, 0),
                  ADR (sFname));
            END;
         END;
      END CloseInput;
      
      
   PROCEDURE NormalQuit;
   (* Exit from Thread, Post message to Window *)
      BEGIN
         WinPostMsg (ChildFrameWindow, WM_PAD, 
            MPFROM2SHORT (PAD_Quit, 0), 0);
         DosExit (EXIT_THREAD, 0);
      END NormalQuit;
      
      
   PROCEDURE ErrorQuit;
   (* Exit from Thread, Post message to Window *)
      BEGIN
         WinPostMsg (ChildFrameWindow, WM_PAD, 
            MPFROM2SHORT (PAD_Error, 0), 0);
         DosExit (EXIT_THREAD, 0);
      END ErrorQuit;
      
      
   PROCEDURE ByteXor (a, b : BYTE) : BYTE;
      BEGIN
         RETURN BYTE (SMALLSET (a) / SMALLSET (b));
      END ByteXor;
      
      
   PROCEDURE Char (c : INTEGER) : CHAR;
   (* converts a number 0-94 into a printable character *)
      BEGIN
         RETURN (CHR (CARDINAL (ABS (c) + 32)));
      END Char;
      
      
   PROCEDURE UnChar (c : CHAR) : INTEGER;
   (* converts a character into its corresponding number *)
      BEGIN
         RETURN (ABS (INTEGER (ORD (c)) - 32));
      END UnChar;

      
   PROCEDURE TellError (Seq : INTEGER);
   (* Send error packet *)
      BEGIN
         sP[1] := Char (15);
         sP[2] := Char (Seq);
         sP[3] := 'E';   (* E-type packet *)
         sP[4] := 'R';   (* error message starts *)
         sP[5] := 'e';
         sP[6] := 'm';
         sP[7] := 'o';
         sP[8] := 't';
         sP[9] := 'e';
         sP[10] := ' ';
         sP[11] := 'A';
         sP[12] := 'b';
         sP[13] := 'o';
         sP[14] := 'r';
         sP[15] := 't';
         sP[16] := 0C;
         SendPacket (sP);
      END TellError;
      
      
   PROCEDURE ShowError (p : PacketType);
   (* Output contents of error packet to the screen *)
   
      VAR
         i : INTEGER;
         
      BEGIN
         FOR i := 4 TO UnChar (p[1]) DO
            ErrorMsg[i - 4] := p[i];
         END;
         ErrorMsg[i - 4] := 0C;
         WinPostMsg (ChildFrameWindow, WM_PAD, 
            MPFROM2SHORT (PAD_Msg, 0), ADR (ErrorMsg));
      END ShowError;
      
      
   PROCEDURE youInit (type : CHAR);   
   (* I initialization YOU for Send and Receive *)      
      BEGIN
         sP[1] := Char (11);   (* Length *)
         sP[2] := Char (0);   (* Sequence *)
         sP[3] := type;
         sP[4] := Char (myMAXL);
         sP[5] := Char (myTIME);
         sP[6] := Char (myNPAD);
         sP[7] := CHAR (ByteXor (myPADC, 100C));
         sP[8] := Char (ORD (myEOL));
         sP[9] := myQCTL;
         sP[10] := myQBIN;
         sP[11] := myCHKT;
         sP[12] := 0C;   (* terminator *)
         SendPacket (sP);
      END youInit;
      

   PROCEDURE myInit;
   (* YOU initialize ME for Send and Receive *)
   
      VAR
         len : INTEGER;
         
      BEGIN
         len := UnChar (rP[1]);
         IF len >= 4 THEN
            yourMAXL := UnChar (rP[4]);
         ELSE
            yourMAXL := 94;
         END;
         IF len >= 5 THEN
            yourTIME := UnChar (rP[5]);
         ELSE
            yourTIME := 10;
         END;
         IF len >= 6 THEN
            yourNPAD := UnChar (rP[6]);
         ELSE
            yourNPAD := 0;
         END;
         IF len >= 7 THEN
            yourPADC := CHAR (ByteXor (rP[7], 100C));
         ELSE
            yourPADC := 0C;
         END;
         IF len >= 8 THEN
            yourEOL := CHR (UnChar (rP[8]));
         ELSE
            yourEOL := 0C;
         END;
         IF len >= 9 THEN
            yourQCTL := rP[9];
         ELSE
            yourQCTL := 0C;
         END;
         IF len >= 10 THEN
            yourQBIN := rP[10];
         ELSE
            yourQBIN := 0C;
         END;
         IF len >= 11 THEN
            yourCHKT := rP[11];
            IF yourCHKT # myCHKT THEN
               yourCHKT := '1';
            END;
         ELSE
            yourCHKT := '1';
         END;
      END myInit;
      
            
   PROCEDURE SendInit;
      BEGIN
         youInit ('S');
      END SendInit;
      
      
   PROCEDURE SendFileName;
   
      VAR
         i, j : INTEGER;
         
      BEGIN
         (* send file name *)
         i := 4;   j := 0;
         WHILE sFname[j] # 0C DO
            sP[i] := sFname[j];
            INC (i);   INC (j);
         END;
         sP[1] := Char (j + 3);
         sP[2] := Char (sSeq);
         sP[3] := 'F';   (* filename packet *)
         sP[i] := 0C;
         SendPacket (sP);
      END SendFileName;
      
      
   PROCEDURE SendEOF;
      BEGIN
         sP[1] := Char (3);
         sP[2] := Char (sSeq);
         sP[3] := 'Z';   (* end of file *)
         sP[4] := 0C;
         SendPacket (sP);
      END SendEOF;
      
      
   PROCEDURE SendEOT;
      BEGIN
         sP[1] := Char (3);
         sP[2] := Char (sSeq);
         sP[3] := 'B';   (* break -- end of transmit *)
         sP[4] := 0C;
         SendPacket (sP);
      END SendEOT;
      
      
   PROCEDURE GetAck() : BOOLEAN;
   (* Look for acknowledgement -- retry on timeouts or NAKs *)
   
      VAR
         Type : CHAR;
         Seq : INTEGER;
         retrys : INTEGER;
         AckOK : BOOLEAN;
          
      BEGIN
         WinPostMsg (ChildFrameWindow, WM_PAD, 
            MPFROM2SHORT (PAD_SendPacket, 0),
            MPFROM2SHORT (PktNbr, sSeq));
      
         retrys := MAXtrys;
         LOOP
            IF Aborted THEN
               TellError (sSeq);
               CloseInput;
               ErrorQuit;
            END;
            IF ReceivePacket (rP) THEN
               Seq := UnChar (rP[2]);
               Type := rP[3];
               IF (Seq = sSeq) AND (Type = 'Y') THEN
                  AckOK := TRUE;
               ELSIF (Seq = (sSeq + 1) MOD 64) AND (Type = 'N') THEN
                  AckOK := TRUE;   (* NAK for (n + 1) taken as ACK for n *)
               ELSIF Type = 'E' THEN
                  ShowError (rP);
                  AckOK := FALSE;
                  retrys := 0;
               ELSE
                  AckOK := FALSE;
               END;
            ELSE
               AckOK := FALSE;
            END;
            IF AckOK OR (retrys = 0) THEN
               EXIT;
            ELSE
               WinPostMsg (ChildFrameWindow, WM_PAD,
                  MPFROM2SHORT (PAD_ResendPacket, 0),
                  MPFROM2SHORT (PktNbr, sSeq));
               
               DEC (retrys);
               FlushUART;
               SendPacket (sP);
            END;
         END;
      
         IF AckOK THEN
            INC (PktNbr);
            sSeq := (sSeq + 1) MOD 64;
            RETURN TRUE;
         ELSE
            RETURN FALSE;
         END;
      END GetAck;
         

   PROCEDURE GetInitAck() : BOOLEAN;
   (* configuration for remote station *)
      BEGIN
         IF GetAck() THEN
            myInit;
            RETURN TRUE;
         ELSE 
            RETURN FALSE;
         END;
      END GetInitAck;
      
      
   PROCEDURE Send;
   (* Send one or more files: sFname may be ambiguous *)
   
      TYPE
         LP = POINTER TO LIST;   (* list of filenames *)
         LIST = RECORD
                   fn : ARRAY [0..20] OF CHAR;
                   next : LP;
                END;
                
      VAR
         gotFN : BOOLEAN;
         attr : AttributeSet;
         ent : DirectoryEntry;
         front, back, t : LP;   (* add at back of queue, remove from front *)
         
      BEGIN
         Aborted := FALSE;
         InputFileOpen := FALSE;
         
         front := NIL;   back := NIL;
         attr := AttributeSet {};   (* normal files only *)
         IF Length (sFname) = 0 THEN
            WinPostMsg (ChildFrameWindow, WM_PAD,
               MPFROM2SHORT (PAD_Msg, 0), 
               ADR ("No file specified..."));
            ErrorQuit;
         ELSE
            gotFN := FindFirst (sFname, attr, ent);
            WHILE gotFN DO   (* build up a list of file names *)
               ALLOCATE (t, SIZE (LIST));
               Assign (ent.name, t^.fn);
               t^.next := NIL;
               IF front = NIL THEN
                  front := t;   (* start from empty queue *)
               ELSE
                  back^.next := t;   (* and to back of queue *)
               END;
               back := t;
               gotFN := FindNext (ent);
            END;
         END;
      
         IF front = NIL THEN   
            WinPostMsg (ChildFrameWindow, WM_PAD,
               MPFROM2SHORT (PAD_NoSuchFile, 0),
               ADR (sFname));
            ErrorQuit;
         ELSE
            sSeq := 0;   PktNbr := 0;
            FlushUART;
            SendInit;   (* my configuration information *)
            IF NOT GetInitAck() THEN     (* get your configuration information *)
               WinPostMsg (ChildFrameWindow, WM_PAD,
                  MPFROM2SHORT (PAD_ExcessiveErrors, 0),
                  MPFROM2SHORT (0, 0));
               ErrorQuit;
            END;
             
            WHILE front # NIL DO   (* send the files *)
               Assign (front^.fn, sFname);
               PktNbr := 1;
               Send1;
               t := front;
               front := front^.next;
               DEALLOCATE (t, SIZE (LIST));
            END;
         END;
      
         SendEOT;
         IF NOT GetAck() THEN
            WinPostMsg (ChildFrameWindow, WM_PAD,
               MPFROM2SHORT (PAD_ExcessiveErrors, 0),
               MPFROM2SHORT (0, 0));
            CloseInput;
            ErrorQuit;
         END;
         NormalQuit;
      END Send;
      
            
   PROCEDURE Send1;
   (* Send one file: sFname *)
   
      VAR
         ch : CHAR;
         i : INTEGER;
         
      BEGIN
         IF Open (sF, sFname) = Done THEN
            InputFileOpen := TRUE;
         ELSE;
            WinPostMsg (ChildFrameWindow, WM_PAD,
               MPFROM2SHORT (PAD_NoSuchFile, 0),
               ADR (sFname));
            ErrorQuit;
         END;
         
         WinPostMsg (ChildFrameWindow, WM_PAD,
            MPFROM2SHORT (PAD_Filename, 0), 
            ADR (sFname));
         WinPostMsg (ChildFrameWindow, WM_PAD,
            MPFROM2SHORT (PAD_Msg, 0), 
            ADR ("(<ESC> to abort file transfer.)"));
            
         SendFileName;        
         IF NOT GetAck() THEN
            WinPostMsg (ChildFrameWindow, WM_PAD,
               MPFROM2SHORT (PAD_ExcessiveErrors, 0),
               MPFROM2SHORT (0, 0));
            CloseInput;
            ErrorQuit;
         END;
         
         (* send file *)
         i := 4;
         LOOP
            IF Get (sF, ch) = EOF THEN   (* send current packet & terminate *)
               sP[1] := Char (i - 1);
               sP[2] := Char (sSeq);
               sP[3] := 'D';   (* data packet *)
               sP[i] := 0C;   (* indicate end of packet *)
               SendPacket (sP);
               IF NOT GetAck() THEN
                  WinPostMsg (ChildFrameWindow, WM_PAD,
                     MPFROM2SHORT (PAD_ExcessiveErrors, 0),
                     MPFROM2SHORT (0, 0));
                  CloseInput;
                  ErrorQuit;
               END;
               SendEOF;
               IF NOT GetAck() THEN
                  WinPostMsg (ChildFrameWindow, WM_PAD,
                     MPFROM2SHORT (PAD_ExcessiveErrors, 0),
                     MPFROM2SHORT (0, 0));
                  CloseInput;
                  ErrorQuit;
               END;
               EXIT;
            END;
                  
            IF i >= (yourMAXL - 4) THEN   (* send current packet *)
               sP[1] := Char (i - 1);
               sP[2] := Char (sSeq);
               sP[3] := 'D';
               sP[i] := 0C;
               SendPacket (sP);
               IF NOT GetAck() THEN
                  WinPostMsg (ChildFrameWindow, WM_PAD,
                     MPFROM2SHORT (PAD_ExcessiveErrors, 0),
                     MPFROM2SHORT (0, 0));
                  CloseInput;
                  ErrorQuit;
               END;
               i := 4;
            END;

            (* add character to current packet -- update count *)
            IF ch > 177C THEN   (* must be quoted (QBIN) and altered *)
               (* toggle bit 7 to turn it off *)
               ch := CHAR (ByteXor (ch, 200C));
               sP[i] := myQBIN;   INC (i);
            END;
            IF (ch < 40C) OR (ch = 177C) THEN   (* quote (QCTL) and alter *)
               (* toggle bit 6 to turn it on *)
               ch := CHAR (ByteXor (ch, 100C));
               sP[i] := myQCTL;   INC (i);
            END;
            IF (ch = myQCTL) OR (ch = myQBIN) THEN   (* must send it quoted *)
               sP[i] := myQCTL;   INC (i);
            END;
            sP[i] := ch;   INC (i);
         END;   (* loop *)
         
         CloseInput;
      END Send1;
      

   PROCEDURE ReceiveInit() : BOOLEAN;
   (* receive my initialization information from you *)
   
      VAR
         RecOK : BOOLEAN;
         trys : INTEGER;
          
      BEGIN
         trys := 1;
         LOOP
            IF Aborted THEN
               TellError (rSeq);
               ErrorQuit;
            END;
            RecOK := ReceivePacket (rP) AND (rP[3] = 'S');
            IF RecOK OR (trys = MAXtrys) THEN
               EXIT;
            ELSE
               INC (trys);
               SendNak;
            END;
         END;
         
         IF RecOK THEN
            myInit;
            RETURN TRUE;
         ELSE
            RETURN FALSE;
         END;   
      END ReceiveInit;
      
      
   PROCEDURE SendInitAck;
   (* acknowledge your initialization of ME and send mine for YOU *)
      BEGIN
         WinPostMsg (ChildFrameWindow, WM_PAD,
            MPFROM2SHORT (PAD_ReceivedPacket, 0),
            MPFROM2SHORT (PktNbr, rSeq));
         INC (PktNbr);
         rSeq := (rSeq + 1) MOD 64;
         youInit ('Y');
      END SendInitAck;
      
      
   PROCEDURE ValidFileChar (VAR ch : CHAR) : BOOLEAN;
   (* checks if character is one of 'A'..'Z', '0'..'9', makes upper case *)
      BEGIN
         ch := CAP (ch);
         RETURN ((ch >= 'A') AND (ch <= 'Z')) OR ((ch >= '0') AND (ch <= '9'));
      END ValidFileChar;


   TYPE
      HeaderType = (name, eot, fail);
      
   PROCEDURE ReceiveHeader() : HeaderType;
   (* receive the filename -- alter for local conditions, if necessary *)
   
      VAR
         i, j, k : INTEGER;
         RecOK : BOOLEAN;
         trys : INTEGER;
         
      BEGIN
         trys := 1;
         LOOP
            IF Aborted THEN
               TellError (rSeq);
               ErrorQuit;
            END;
            RecOK := ReceivePacket (rP) AND ((rP[3] = 'F') OR (rP[3] = 'B'));
            IF trys = MAXtrys THEN
               RETURN fail;
            ELSIF RecOK AND (rP[3] = 'F') THEN
               i := 4;   (* data starts here *)
               j := 0;   (* beginning of filename string *)
               WHILE (ValidFileChar (rP[i])) AND (j < 8) DO
                  rFname[j] := rP[i];
                  INC (i);   INC (j);
               END;
               REPEAT
                  INC (i);
               UNTIL (ValidFileChar (rP[i])) OR (rP[i] = 0C);
               rFname[j] := '.';   INC (j);
               k := 0;
               WHILE (ValidFileChar (rP[i])) AND (k < 3) DO
                  rFname[j + k] := rP[i];
                  INC (i);   INC (k);
               END;
               rFname[j + k] := 0C;  
               WinPostMsg (ChildFrameWindow, WM_PAD,
                  MPFROM2SHORT (PAD_Filename, 0),
                  ADR (rFname));
               RETURN name;
            ELSIF RecOK AND (rP[3] = 'B') THEN
               RETURN eot;
            ELSE
               INC (trys);
               SendNak;
            END;
         END;
      END ReceiveHeader;
      
      
   PROCEDURE SendNak;
      BEGIN
         WinPostMsg (ChildFrameWindow, WM_PAD,
            MPFROM2SHORT (PAD_RequestRepeat, 0),
            MPFROM2SHORT (PktNbr, rSeq));
         FlushUART;
         sP[1] := Char (3);   (* LEN *)
         sP[2] := Char (rSeq); 
         sP[3] := 'N';   (* negative acknowledgement *)
         sP[4] := 0C;
         SendPacket (sP);
      END SendNak;
      
      
   PROCEDURE SendAck (Seq : INTEGER);
      BEGIN
         IF Seq # rSeq THEN
            WinPostMsg (ChildFrameWindow, WM_PAD,
               MPFROM2SHORT (PAD_DuplicatePacket, 0),
               MPFROM2SHORT (0, rSeq));
         ELSE
            WinPostMsg (ChildFrameWindow, WM_PAD,
               MPFROM2SHORT (PAD_ReceivedPacket, 0),
               MPFROM2SHORT (PktNbr, rSeq));
            rSeq := (rSeq + 1) MOD 64;
            INC (PktNbr);
         END;
         
         sP[1] := Char (3);
         sP[2] := Char (Seq);
         sP[3] := 'Y';   (* acknowledgement *)
         sP[4] := 0C;
         SendPacket (sP);
      END SendAck;
      
      
   PROCEDURE Receive;
   (* Receives a file  (or files) *)
   
      VAR
         ch, Type : CHAR;
         Seq : INTEGER;
         i : INTEGER;
         EOF, EOT, QBIN : BOOLEAN;
         trys : INTEGER;
                  
      BEGIN
         Aborted := FALSE;
         
         WinPostMsg (ChildFrameWindow, WM_PAD,
            MPFROM2SHORT (PAD_Msg, 0), 
            ADR ("Ready to receive file(s)..."));
         WinPostMsg (ChildFrameWindow, WM_PAD,
            MPFROM2SHORT (PAD_Msg, 0),
            ADR ("(<ESC> to abort file transfer.)"));

         FlushUART;
         rSeq := 0;   PktNbr := 0;  
         IF NOT ReceiveInit() THEN   (* your configuration information *)
            WinPostMsg (ChildFrameWindow, WM_PAD,
               MPFROM2SHORT (PAD_ExcessiveErrors, 0),
               MPFROM2SHORT (0, 0));
            ErrorQuit;
         END;
         SendInitAck;       (* send my configuration information *)
         EOT := FALSE;
         WHILE NOT EOT DO
            CASE ReceiveHeader() OF
               eot  : EOT := TRUE;   EOF := TRUE;
            |  name : IF Create (rF, rFname) # Done THEN
                         WinPostMsg (ChildFrameWindow, WM_PAD,
                               MPFROM2SHORT (PAD_UnableToOpen, 0),
                               ADR (rFname));
                         ErrorQuit;
                      ELSE
                         PktNbr := 1;
                         EOF := FALSE;
                      END;
            |  fail : WinPostMsg (ChildFrameWindow, WM_PAD,
                            MPFROM2SHORT (PAD_ExcessiveErrors, 0),
                            MPFROM2SHORT (0, 0));
                      ErrorQuit;
            END;
            SendAck (rSeq);   (* acknowledge for name or eot *)
            trys := 1;   (* initialize *)
            WHILE NOT EOF DO
               IF Aborted THEN
                  TellError (rSeq);
                  ErrorQuit;
               END;
               IF ReceivePacket (rP) THEN
                  Seq := UnChar (rP[2]);
                  Type := rP[3];
                  IF Type = 'Z' THEN
                     EOF := TRUE;
                     IF CloseFile (rF, Output) = Done THEN
                        (* normal file termination *)
                     ELSE
                        WinPostMsg (ChildFrameWindow, WM_PAD,
                           MPFROM2SHORT (PAD_ProbClDestFile, 0),
                           ADR (rFname));
                        ErrorQuit;
                     END;
                     trys := 1;   (* good packet -- reset *)
                     SendAck (rSeq);
                  ELSIF Type = 'E' THEN
                     ShowError (rP);
                     ErrorQuit;
                  ELSIF (Type = 'D') AND ((Seq + 1) MOD 64 = rSeq) THEN
                  (* discard duplicate packet, and Ack anyway *)
                     trys := 1;
                     SendAck (Seq); 
                  ELSIF (Type = 'D') AND (Seq = rSeq) THEN
                     (* put packet into file buffer *)
                     i := 4;   (* first data in packet *)
                     WHILE rP[i] # 0C DO
                        ch := rP[i];   INC (i);
                        IF ch = yourQBIN THEN
                           ch := rP[i];   INC (i);
                           QBIN := TRUE;
                        ELSE
                           QBIN := FALSE;
                        END;
                        IF ch = yourQCTL THEN                  
                           ch := rP[i];   INC (i);
                           IF (ch # yourQCTL) AND (ch # yourQBIN) THEN
                              ch := CHAR (ByteXor (ch, 100C));
                           END;
                        END;
                        IF QBIN THEN
                           ch := CHAR (ByteXor (ch, 200C));
                        END;
                        Put (ch);
                     END;
                  
                     (* write file buffer to disk *)
                     IF DoWrite (rF) # Done THEN
                        WinPostMsg (ChildFrameWindow, WM_PAD,
                           MPFROM2SHORT (PAD_ErrWrtFile, 0),
                           ADR (rFname));
                        ErrorQuit;
                     END;
                     trys := 1;
                     SendAck (rSeq);
                  ELSE
                     INC (trys);
                     IF trys = MAXtrys THEN
                        WinPostMsg (ChildFrameWindow, WM_PAD,
                           MPFROM2SHORT (PAD_ExcessiveErrors, 0),
                           MPFROM2SHORT (0, 0));
                        ErrorQuit;
                     ELSE
                        SendNak;
                     END;
                  END;
               ELSE
                  INC (trys);
                  IF trys = MAXtrys THEN
                     WinPostMsg (ChildFrameWindow, WM_PAD,
                        MPFROM2SHORT (PAD_ExcessiveErrors, 0),
                        MPFROM2SHORT (0, 0));
                     ErrorQuit;
                  ELSE
                     SendNak;
                  END;
               END;
            END;
         END;
         NormalQuit;
      END Receive;
      
      
BEGIN   (* module initialization *)
   yourEOL := ASCII.cr;
   yourNPAD := 0;
   yourPADC := 0C;
END PAD.