(*----------------------------------------------------------------------------
 :Program.     Termite.mod
 :Contents.    A little Terminal Proggy (OS 2.04 or higher)
 :Author.      Christian Stiens
 :Address.     Snail-Mail:           E-Mail:
 :Address.     Heustiege 2           FIDO: 2:245/5802.25
 :Address.     W-4710 Lüdinghausen   UUCP: Christian_Stiens@ouzonix.bo.open.de
 :Copyright.   SHAREWARE (DM 10)
 :Language.    Oberon-2
 :Translator.  Amiga Oberon 2.45
 :History.     V1.0, 25-Jan-93: first release
 :Imports.     CBText37, DeviceSupport37, FileReq37, OwnDevUnit,
 :Imports.     ReqTools, XPR
 :Support.     Thanks to Nico Francois for his ReqTools Library
 :Support.     Thanks to Christopher A. Wichura for his OwnDevUnit Library
 :Support.     Thanks to Olaf `Olsen' Barthel for releasing the source of term
----------------------------------------------------------------------------*)


(* $Debug- *)

MODULE Termite;

  IMPORT
           Arguments,  (* To set the correct CurrentDir if WB-started *)
    cbt := CBText37,
    con := Console,
    cu  := ConUnit,
    d   := Dos,
    df  := DiskFont,
    ds  := DeviceSupport37,
    e   := Exec,
    fr  := FileReq37,
    fs  := FileSystem, (* modified: BufSize = 8192 *)
    g   := Graphics,
    gt  := GadTools,
    hw  := Hardware,
    I   := Intuition,
    ie  := InputEvent,
    in  := Input,
    k   := KeyMap,
         (* $IF debug *)
           NoGuru,
         (* $END *)
    ol  := OberonLib,
    odu := OwnDevUnit,
    rt  := ReqTools,
    se  := Serial,
    SYS := SYSTEM,
    str := Strings,
    ti  := Timer,
    u   := Utility,
           XPR;


  TYPE
    LongArray12=ARRAY 12 OF LONGINT;
    LongArray8 =ARRAY  8 OF LONGINT;

  CONST
    version = "\o$VER: termite 1.0 (25.1.93)";

    baudRates = LongArray12(112,300,1200,2400,4800,9600,19200,31250,38400,57600,76800,115200);
    bufSizes  = LongArray8 (512,1024,2048,4096,8192,16384,32768,65536);

    topaz80attr = g.TextAttr(SYS.ADR("topaz.font"),8,SHORTSET{},SHORTSET{});

    A5 = 13;


  TYPE

    Config = STRUCT
      device        : e.STRING;
      unit          : INTEGER;
      rBufLen       : LONGINT;
      baud          : LONGINT;
      breakTime     : LONGINT;
      bitsPerChar   : SHORTINT;
      stopBits      : SHORTINT;
      parity        : SHORTINT;
      handShaking   : SHORTINT;
      duplex        : SHORTINT;
      radBoogie     : BOOLEAN;
      initString    : e.STRING;
      hangupString  : e.STRING;
      time4connect  : LONGINT;
      capture       : e.STRING;
      protocol      : e.STRING;
      uploadDir     : e.STRING;
      downloadDir   : e.STRING;
      pasteDir      : e.STRING;
      pasteDelay    : LONGINT;
      screenDepth   : INTEGER;
      font          : e.STRING;
      keyMap        : e.STRING;
      priority      : SHORTINT;
    END;


  CONST
    none       = 0;

    xonxoff    = 1;
    ctsrts     = 2;

    even       = 1;
    odd        = 2;
    mark       = 3;
    space      = 4;

    halfduplex = 0;
    fullduplex = 1;


  CONST
    writeBufLen = 256;
    readBufLen = 1024;
    maxDialEntries = 40;
    items = 77;


  TYPE
    NewMenuType     = ARRAY items                OF gt.NewMenu;
    FullNewMenuType = ARRAY items+maxDialEntries OF gt.NewMenu;


  VAR
    oldMemReqs    : LONGSET;
    resident      : BOOLEAN;
    serWriteReq   : se.IOExtSerPtr;
    serReadReq    : se.IOExtSerPtr;
    serReadPort   : e.MsgPortPtr;
    serReq        : se.IOExtSerPtr;
    writeBuf      : UNTRACED POINTER TO ARRAY writeBufLen OF CHAR;
    readBuf       : UNTRACED POINTER TO ARRAY readBufLen OF CHAR;
    conWriteReq   : e.IOStdReqPtr;
    conReadReq    : e.IOStdReqPtr;
    conReadPort   : e.MsgPortPtr;
    timeReq       : ti.TimeRequestPtr;
    inputReq      : e.IOStdReqPtr;
    xprio         : XPR.IOPtr;
    me            : d.ProcessPtr;
    scr           : I.ScreenPtr;
    height        : INTEGER;
    args          : STRUCT config,phonebook: e.STRPTR END;
    rd            : d.RDArgsPtr;
    pub           : I.ScreenPtr;
    attr          : g.TextAttrPtr;
    font          : g.TextFontPtr;
    rect          : g.Rectangle;
    menu          : I.MenuPtr;
    newMenu       : FullNewMenuType;
    config        : Config;
    win           : I.WindowPtr;
    blockCnt      : INTEGER;
    transferWin   : I.WindowPtr;
    topaz80       : g.TextFontPtr;
    statusWin     : I.WindowPtr;
    statusTask    : e.TaskPtr;
    statusStack   : UNTRACED POINTER TO ARRAY 4008 OF SYS.BYTE;
    statusRunning : BOOLEAN;
    colorMap      : ARRAY 32 OF INTEGER;
    colorCnt      : INTEGER;
    mySig         : SHORTINT;
    dialArray     : ARRAY maxDialEntries OF e.STRPTR;
    lastDialNr    : INTEGER;
    afpt          : ARRAY 2 OF INTEGER;
    bbox          : I.IBox;
    lock          : d.FileLockPtr;
    seg           : e.BPTR;
    oldPri        : LONGINT;
    keyMap        : k.KeyMapPtr;
    oldWP         : I.WindowPtr;
    xprFlags      : LONGSET;
    serLocked     : BOOLEAN;
    reading       : BOOLEAN;
    online        : BOOLEAN;
    protocolOpts  : e.STRING;
    vi            : e.APTR;
    displayID     : LONGINT;
    capture       : fs.File;
    cap           : BOOLEAN;
    sigRec        : LONGSET;
    sigMask       : LONGSET;
    msg           : I.IntuiMessagePtr;
    ySize         : INTEGER;
    ioErr         : INTEGER;
    i             : INTEGER;
    n             : LONGINT;
    name          : e.STRING;
    (* $IF unreg *)
    factors       : STRUCT a,b: INTEGER END;
    string        : e.STRING;
    result        : LONGINT;
    (* $END *)


  PROCEDURE SerInit(ser: e.MessagePtr);
  BEGIN
    INCL(ser(se.IOExtSer).serFlags,se.shared);
  END SerInit;


  PROCEDURE ConInit(con: e.MessagePtr);
  BEGIN
    con(e.IOStdReq).data := win;
    con(e.IOStdReq).length := SIZE(I.Window);
  END ConInit;


  PROCEDURE Min(x,y: LONGINT): LONGINT;
  BEGIN
    IF x < y THEN RETURN x ELSE RETURN y END;
  END Min;


  PROCEDURE StringToInt(st: ARRAY OF CHAR; base: INTEGER): LONGINT; (* $CopyArrays- *)
    VAR i,sum,n: LONGINT;
        ch: CHAR;
  BEGIN
    sum := 0;
    IF st[0]="$" THEN base := 16 END;
    FOR i := 0 TO str.Length(st)-1 DO
      ch := CAP(st[i]);
      CASE ch OF
      | "0".."9": n := ORD(ch)-ORD("0")
      | "A".."Z": IF base=16 THEN n := ORD(ch)-ORD("A")+10 ELSE n := -1 END;
      ELSE
        n := -1;
      END;
      IF (n>=0) & (sum<100000000) THEN sum := sum*base+n END;
    END;
    RETURN sum;
  END StringToInt;


  PROCEDURE LoadKeyMap(name: ARRAY OF CHAR): k.KeyMapPtr; (* $CopyArrays- *)
    VAR keyMap: k.KeyMapPtr;
        newSeg: e.BPTR;
        fileName: e.STRING;
  BEGIN
    keyMap := NIL;
    IF name[0]#0X THEN
      IF (str.Occurs(name,":") >= 0) OR (str.Occurs(name,"/") >= 0) THEN
        COPY(name,fileName);
      ELSE
        fileName := "DEVS:keymaps/";
        IF d.AddPart(fileName,name,LEN(fileName)) THEN END;
      END;
      newSeg := d.LoadSeg(fileName);
      IF newSeg # NIL THEN
        IF seg # NIL THEN d.UnLoadSeg(seg) END;
        seg := newSeg;
        keyMap := SYS.VAL(e.APTR,SYS.VAL(LONGINT,newSeg)*4+4+SIZE(e.Node));
      END;
    END;
    RETURN keyMap;
  END LoadKeyMap;


  PROCEDURE BlockWindow;
  BEGIN
    IF win#NIL THEN
      IF blockCnt=0 THEN
        I.ClearMenuStrip(win);
        rt.SetWaitPointer(win);
      END;
      INC(blockCnt);
    END;
  END BlockWindow;


  PROCEDURE UnBlockWindow;
  BEGIN
    IF win#NIL THEN
      DEC(blockCnt);
      IF blockCnt=0 THEN
        I.ClearPointer(win);
        IF I.ResetMenuStrip(win,menu^) THEN END;
      END;
    END;
  END UnBlockWindow;


  PROCEDURE RemSpaces(VAR s: ARRAY OF CHAR): BOOLEAN;
    VAR i: LONGINT;
        quote: BOOLEAN;
  BEGIN
    quote := FALSE;
    FOR i := 0 TO str.Length(s)-1 DO
      IF  s[i]='"' THEN quote := ~quote END;
      IF (s[i]=";") & ~quote THEN s[i] := 0X END;
    END;
    WHILE (str.Length(s)>0) & ((s[0]=" ") OR (s[0]="\t")) DO str.Delete(s,0,1) END;
    i := str.Length(s)-1;
    WHILE (i>=0) & ((s[i]=" ") OR (s[i]="\t")) DO
      s[i] := 0X;
      DEC(i);
    END;
    RETURN s[0]#0X
  END RemSpaces;


  PROCEDURE SendReadReq;
  BEGIN
    IF ~reading THEN
      serReadReq.ioSer.data    := readBuf;
      serReadReq.ioSer.length  := 1;
      serReadReq.ioSer.command := e.read;
      SYS.SETREG(0,e.SetSignal(LONGSET{},LONGSET{serReadPort.sigBit}));
      e.SendIO(serReadReq);
      reading := TRUE;
    END;
  END SendReadReq;


  PROCEDURE AbortReadReq;
  BEGIN
    IF reading THEN
      IF e.CheckIO(serReadReq)=NIL THEN
        e.AbortIO(serReadReq);
      END;
      e.OldWaitIO(serReadReq);
      reading := FALSE
    END;
  END AbortReadReq;


  PROCEDURE Capture(block: e.APTR; size: LONGINT);
  BEGIN
    IF cap & (capture.status=fs.ok) THEN
      SYS.SETREG(0,fs.WriteBlock(capture,block,size));
    END;
  END Capture;


  PROCEDURE ConfigToSer;
    VAR wasReading: BOOLEAN;
  BEGIN
    wasReading := reading;
    AbortReadReq;
    serWriteReq.serFlags := SHORTSET{se.xDisabled,se.partyOn};
    serWriteReq.extFlags := LONGSET{};
    serWriteReq.baud     := config.baud;
    serWriteReq.rBufLen  := config.rBufLen;
    serWriteReq.writeLen := config.bitsPerChar;
    serWriteReq.readLen  := config.bitsPerChar;
    serWriteReq.stopBits := config.stopBits;
    serWriteReq.brkTime  := config.breakTime;
    CASE config.handShaking OF
    | xonxoff: EXCL(serWriteReq.serFlags,se.xDisabled);
    | ctsrts:  INCL(serWriteReq.serFlags,se.sevenWire);
    ELSE
    END;
    CASE config.parity OF
    | none:  EXCL(serWriteReq.serFlags,se.partyOn)
    | even:
    | odd:   INCL(serWriteReq.serFlags,se.partyOdd);
    | mark:  INCL(serWriteReq.extFlags,se.mSpOn);   INCL(serWriteReq.extFlags,se.mark);
    | space: INCL(serWriteReq.extFlags,se.mSpOn);
    ELSE
    END;
    IF config.radBoogie THEN INCL(serWriteReq.serFlags,se.radBoogie) END;
    e.CopyMem(serWriteReq.ctlChar,serReadReq.ctlChar,SIZE(serReadReq^)-SIZE(e.IOStdReq));
    serWriteReq.ioSer.command := se.setparams;
    ioErr := e.DoIO(serWriteReq);
    IF wasReading THEN
      SendReadReq;
    END;
  END ConfigToSer;


  PROCEDURE SerWriteString(txt: ARRAY OF CHAR); (* $CopyArrays- *)
    CONST
      setCtrlLines = e.nonstd + 7;
      rtsF = 1;
      dtrF = 2;
    VAR
      i,l: LONGINT;
      s: ARRAY 8 OF CHAR;
      ctrl: BOOLEAN;
  BEGIN
    ctrl := FALSE;
    FOR i := 0 TO str.Length(txt)-1 DO
      s[0] := txt[i]; s[1] := 0X;
      IF ctrl THEN
        CASE CAP(s[0]) OF
          "R" : s[0] := "\r" |
          "N" : s[0] := "\n" |
          "E" : s[0] := "\e" |
          "^","\\","~":
        ELSE
          s[0] := 0X
        END;
        ctrl := FALSE;
      ELSE
        CASE s[0] OF
        | "\\": ctrl := TRUE; s[0] := 0X;
        | "~" : d.Delay(d.ticksPerSecond DIV 2); s[0] := 0X;
        | "^" : serWriteReq.ioSer.command := setCtrlLines;
                serWriteReq.ioSer.offset := dtrF;
                serWriteReq.ioSer.length := 0;
                IF e.DoIO(serWriteReq)#0 THEN
                  INCL(hw.ciab.pra,hw.comDTR);
                END;
                d.Delay(d.ticksPerSecond DIV 8);
                serWriteReq.ioSer.command := setCtrlLines;
                serWriteReq.ioSer.offset := dtrF;
                serWriteReq.ioSer.length := dtrF;
                IF e.DoIO(serWriteReq)#0 THEN
                  EXCL(hw.ciab.pra,hw.comDTR);
                END;
                s[0] := 0X;
        ELSE
        END;
      END;
      l := str.Length(s);
      IF l>0 THEN
        serWriteReq.ioSer.data := SYS.ADR(s);
        serWriteReq.ioSer.length := l;
        serWriteReq.ioSer.command := e.write;
        ioErr := e.DoIO(serWriteReq);
        IF config.duplex=halfduplex THEN
          conWriteReq.length  := l;
          conWriteReq.data    := SYS.ADR(s);
          conWriteReq.command := e.write;
          e.OldDoIO(conWriteReq);
          Capture(SYS.ADR(s),l);
        END;
      END;
    END;
  END SerWriteString;


  PROCEDURE MyRequest(title,textFormat,gadgetFormat: ARRAY OF CHAR;
                      args: e.APTR): LONGINT; (* $CopyArrays- *)
    VAR res: LONGINT;
  BEGIN
    BlockWindow;
    res := rt.EZRequestTags(textFormat,gadgetFormat,NIL,args,rt.ezReqTitle,SYS.ADR(title),u.end);
    UnBlockWindow;
    RETURN res;
  END MyRequest;


  PROCEDURE Assert(cc: BOOLEAN; txt: ARRAY OF CHAR); (* $CopyArrays- *)
  BEGIN
    IF ~cc THEN
      IF MyRequest("Information",txt,"Cancel",NIL)=0 THEN END;
      HALT(d.fail);
    END;
  END Assert;


  PROCEDURE RFProc; (* $EntryExitCode- *)
  BEGIN
    SYS.INLINE(016C0U,04E75U);
  END RFProc;


  PROCEDURE SplitLine(VAR line,first,rest: ARRAY OF CHAR);
    VAR i,j,k,l: LONGINT;
        quote,take,gotFirst: BOOLEAN;
        ch,lastCh: CHAR;
  BEGIN
    quote := FALSE; gotFirst := FALSE;
    i := 0; j := 0; k := 0; ch := 0X;
    l := str.Length(line);
    WHILE i<l DO
      take := TRUE; lastCh := ch; ch := line[i];
      CASE ch OF
      | '"'      : quote := ~quote; IF ~quote OR (lastCh#'"') THEN take := FALSE END;
      | " ","\t" : IF ~quote & ~gotFirst THEN
                     gotFirst := TRUE;
                     WHILE (i<l-1) & ((line[i+1]=" ")OR(line[i+1]="\t")) DO INC(i) END;
                     take := FALSE;
                   END;
      ELSE
      END;
      IF take THEN
        IF ~gotFirst THEN
          IF j<LEN(first) THEN first[j] := ch; INC(j) END;
        ELSE
          IF k<LEN(rest)  THEN rest [k] := ch; INC(k) END;
        END;
      END;
      INC(i);
    END;
    IF j<LEN(first) THEN first[j] := 0X END;
    IF k<LEN(rest)  THEN rest [k] := 0X END;
  END SplitLine;


  PROCEDURE LoadConfig;
    VAR file: fs.File;
        string,keyword,valueStr: e.STRING;
        value,dummy,i: LONGINT;
  BEGIN
    config.device        := "serial.device";
    config.unit          := 0;
    config.initString    := "ATZ\\r~~";
    config.hangupString  := "^~~~+++~~~ATH0\\r";
    config.rBufLen       := 8192;
    config.baud          := 2400;
    config.breakTime     := 250000;
    config.bitsPerChar   := 8;
    config.stopBits      := 1;
    config.parity        := none;
    config.handShaking   := ctsrts;
    config.radBoogie     := FALSE;
    config.duplex        := fullduplex;
    config.keyMap        := "";
    config.font          := "";
    config.protocol      := "";
    config.downloadDir   := "";
    config.uploadDir     := "";
    config.pasteDir      := "";
    config.capture       := "T:Capture_";
    config.screenDepth   := 2;
    config.time4connect  := 0;
    config.pasteDelay    := 10;
    config.priority      := 0;

    IF args.config # NIL THEN
      COPY(args.config^,string);
    ELSE
      string := "Termite.config";
      IF ~fs.Exists(string) THEN
        string := "PROGDIR:Termite.config";
        IF resident OR ~fs.Exists(string) THEN
          string := "ENV:Termite.config";
          IF ~fs.Exists(string) THEN
            string := "s:Termite.config";
            IF ~fs.Exists(string) THEN
              string := ""
            END;
          END;
        END;
      END;
    END;
    IF fs.Open(file,string,FALSE) THEN
      WHILE fs.ReadString(file,string) DO
        IF RemSpaces(string) THEN
          SplitLine(string,keyword,valueStr);
          str.Upper(keyword);
          value := StringToInt(valueStr,10);

          IF    keyword = "DEVICE"      THEN config.device := valueStr
          ELSIF keyword = "UNIT"        THEN config.unit := SHORT(value)
          ELSIF keyword = "HANDSHAKING" THEN str.Upper(valueStr); IF valueStr="XONXOFF" THEN config.handShaking:=xonxoff ELSIF valueStr="7WIRE" THEN config.handShaking:=ctsrts ELSE config.handShaking:=none END;
          ELSIF keyword = "BAUDRATE"    THEN config.baud         := value
          ELSIF keyword = "BREAKTIME"   THEN config.breakTime    := value
          ELSIF keyword = "SERIALBUFFER"THEN config.rBufLen      := value DIV 512 * 512;
          ELSIF keyword = "PARITY"      THEN str.Upper(valueStr); IF    valueStr="EVEN" THEN config.parity:=even  ELSIF valueStr="ODD"THEN config.parity:=odd ELSIF valueStr="MARK" THEN config.parity:=mark
                                                                  ELSIF valueStr="SPACE"THEN config.parity:=space ELSE config.parity:=none END;
          ELSIF keyword = "DUPLEX"      THEN str.Upper(valueStr); IF valueStr="FULL" THEN config.duplex:=fullduplex ELSE config.duplex:=halfduplex END;
          ELSIF keyword = "RADBOOGIE"   THEN str.Upper(valueStr); config.radBoogie := valueStr#"FALSE"
          ELSIF keyword = "TIMEFORCONNECT" THEN config.time4connect := value
          ELSIF keyword = "PASTEDELAY"  THEN config.pasteDelay   := value
          ELSIF keyword = "INITSTRING"  THEN config.initString   := valueStr
          ELSIF keyword = "HANGUPSTRING"THEN config.hangupString := valueStr
          ELSIF keyword = "STOPBITS"    THEN config.stopBits     := SHORT(SHORT(value))
          ELSIF keyword = "BITSPERCHAR" THEN config.bitsPerChar  := SHORT(SHORT(value))
          ELSIF keyword = "KEYMAP"      THEN config.keyMap       := valueStr
          ELSIF keyword = "SCREENDEPTH" THEN config.screenDepth  := SHORT(value)
          ELSIF keyword = "PRIORITY"    THEN config.priority     := SHORT(SHORT(value))
          ELSIF keyword = "PROTOCOL"    THEN config.protocol     := valueStr
          ELSIF keyword = "CAPTURE"     THEN config.capture      := valueStr
          ELSIF keyword = "UPLOADDIR"   THEN config.uploadDir    := valueStr
          ELSIF keyword = "DOWNLOADDIR" THEN config.downloadDir  := valueStr
          ELSIF keyword = "PASTEDIR"    THEN config.pasteDir     := valueStr
          ELSIF(keyword = "FONT") & (config.font="") THEN
            config.font := valueStr;
            i := d.SplitName(valueStr,"/",string,0,LEN(string));
            IF (i # -1) & (string # "") THEN
              str.Append(string,".font");
              NEW(attr);
              NEW(attr.name);
              COPY(string,attr.name^);
              e.CopyMem(valueStr[i],string,LEN(string));
              IF string # "" THEN
                attr.ySize := SHORT(StringToInt(string,10));
                IF attr.ySize <  5 THEN attr.ySize :=  5 END;
                IF attr.ySize > 50 THEN attr.ySize := 50 END;
                font := df.OpenDiskFont(attr^);
                IF font # NIL THEN
                  attr.ySize := font.ySize;
                ELSE
                  attr := NIL;
                END;
              ELSE
                attr := NIL;
              END;
            END;
          ELSIF keyword = "PALETTE"     THEN
            colorCnt := 0;
            WHILE str.Length(valueStr)>0 DO
              i := 0;
              WHILE (valueStr[0]#",") & (valueStr[0]#0X) DO
                IF i<LEN(string) THEN
                  string[i] := valueStr[0]; INC(i);
                END;
                str.Delete(valueStr,0,1);
              END;
              IF i<LEN(string) THEN string[i] := 0X END;
              IF valueStr[0]="," THEN str.Delete(valueStr,0,1) END;
              IF colorCnt<LEN(colorMap) THEN
                colorMap[colorCnt] := SHORT(StringToInt(string,16));
                INC(colorCnt)
              END;
            END;
          END;
        END;
      END;
      IF fs.Close(file) THEN END;
    END;
    IF config.screenDepth<  1 THEN config.screenDepth:=  1 ELSIF config.screenDepth>      4 THEN config.screenDepth:=      4 END;
    IF config.breakTime  <  1 THEN config.breakTime  :=  1 ELSIF config.breakTime  >5000000 THEN config.breakTime  :=5000000 END;
    IF config.rBufLen    <512 THEN config.rBufLen    :=512 ELSIF config.rBufLen    >  65536 THEN config.rBufLen    :=  65536 END;
    IF config.bitsPerChar<  7 THEN config.bitsPerChar:=  7 ELSIF config.bitsPerChar>      8 THEN config.bitsPerChar:=      8 END;
    IF config.stopBits   <  1 THEN config.stopBits   :=  1 ELSIF config.stopBits   >      2 THEN config.stopBits   :=      2 END;
    IF config.baud       <112 THEN config.baud       :=112 ELSIF config.baud       > 115200 THEN config.baud       := 115200 END;
    IF config.time4connect< 0 THEN config.time4connect:= 0 ELSIF config.time4connect> 10000 THEN config.time4connect:= 10000 END;
    IF config.pasteDelay <  0 THEN config.pasteDelay :=  0 ELSIF config.pasteDelay >   1000 THEN config.pasteDelay :=   1000 END;
  END LoadConfig;


  PROCEDURE PutText(win: I.WindowPtr; x,y: INTEGER; txt: ARRAY OF CHAR; n: LONGINT); (* $CopyArrays- *)
    VAR
      l: LONGINT;
      rp: g.RastPortPtr;
      text: ARRAY 50 OF CHAR;
  BEGIN
    rp := win.rPort;
    g.Move(rp,win.borderLeft+(x-1)*rp.txWidth,win.borderTop+(y-1)*rp.txHeight+rp.txBaseline);
    IF n<=0 THEN
      g.Text(rp,txt,str.Length(txt));
    ELSE
      text := "                                                  ";
      l := str.Length(txt);
      IF l>50 THEN l := 50 END;
      IF l>0 THEN e.CopyMem(txt,text,l) END;
      IF n>50 THEN n := 50 END;
      g.Text(rp,text,n);
    END;
  END PutText;


  PROCEDURE DrawBox(rp: g.RastPortPtr; left,top,width,height: INTEGER);
  BEGIN
    IF (width > 0) & (height > 0) THEN
      g.RectFill(rp,left,top,left+width-1,top+height-1);
    END;
  END DrawBox;


  PROCEDURE OpenTransferWindow(title: ARRAY OF CHAR): BOOLEAN; (* $CopyArrays- *)
    VAR
      rp: g.RastPortPtr;
      box: I.IBox;
      try: INTEGER;
      oldPt: e.APTR;
      oldPtSz: SHORTINT;
  BEGIN
    IF transferWin = NIL THEN

      box.width  := scr.rastPort.txWidth  * 63 + 10;
      box.height := scr.rastPort.txHeight * 16 + 16;

      try := 0;
      LOOP
        INC(try);

        box.top  := (scr.height-(box.height+scr.barHeight)) DIV 2;
        box.left := (scr.width -box.width)  DIV 2;

        transferWin := I.OpenWindowTagsA(NIL,
                            I.waTitle,       SYS.ADR(title),
                            I.waLeft,        box.left,
                            I.waTop,         box.top,
                            I.waInnerHeight, box.height,
                            I.waInnerWidth,  box.width,
                            I.waAutoAdjust,  I.LFALSE,
                            I.waIDCMP,       LONGSET{I.closeWindow},
                            I.waFlags,       LONGSET{I.activate,I.noCareRefresh,I.windowDrag,I.windowDepth,I.windowClose,I.rmbTrap},
                            I.waCustomScreen,scr,
                            u.done);
        IF transferWin#NIL THEN
          IF try=2 THEN g.SetFont(transferWin.rPort,topaz80) END;
          EXIT
        END;

        IF (try=2) OR (topaz80=NIL) THEN RETURN FALSE END;

        box.width  := 8 * 63 + 10;
        box.height := 8 * 16 + 16;
      END;

      BlockWindow;
  
      rp := transferWin.rPort;

      g.SetDrMd(rp,g.jam2);
      g.SetAPen(rp,1);
  
      PutText(transferWin, 2,2,"Protocol:",0);
      PutText(transferWin, 2,3,"File Name:",0);
  
      PutText(transferWin, 2,5,"Status:",0);
      PutText(transferWin, 2,6,"Error Msg:",0);
  
      PutText(transferWin, 2, 8,"File Size:",0);      PutText(transferWin,32, 8,"Timeouts:",0);
      PutText(transferWin, 2, 9,"Bytes transf.:",0);  PutText(transferWin,32, 9,"Errors:",0);
      PutText(transferWin, 2,10,"Blocks transf.:",0); PutText(transferWin,32,10,"Char. Delay:",0);
      PutText(transferWin, 2,11,"Block Size:",0);     PutText(transferWin,32,11,"Packet Delay:",0);
      PutText(transferWin, 2,12,"Block Check:",0);    PutText(transferWin,32,12,"Expected Time:",0);
      PutText(transferWin, 2,13,"Chars/Sec:",0);      PutText(transferWin,32,13,"Elapsed Time:",0);
  
      PutText(transferWin, 25,15,"Data transferred",0);
  
      bbox.width  := transferWin.width * 3 DIV 4;
      bbox.height := rp.txHeight+4;
      bbox.left   := (transferWin.width-bbox.width) DIV 2;
      bbox.top    := transferWin.borderTop+(16-1)*rp.txHeight+4;

      gt.DrawBevelBox(rp,bbox.left-2,bbox.top-1,bbox.width+4,bbox.height+2, gt.visualInfo,vi,gt.bbRecessed,I.LTRUE,u.end);

      g.SetDrMd(rp,g.jam1);
      afpt[0] := 05555U; afpt[1] := 0AAAAU;
      oldPt := rp.areaPtrn; oldPtSz := rp.areaPtSz;
      g.SetAfPt(rp,SYS.ADR(afpt),1);
      DrawBox(rp,bbox.left,bbox.top,bbox.width,bbox.height);
      g.SetAfPt(rp,oldPt,oldPtSz);
      g.SetDrMd(rp,g.jam2);
    END;
    RETURN TRUE;
  END OpenTransferWindow;


  PROCEDURE CloseTransferWindow;
  BEGIN
    IF transferWin # NIL THEN
      d.Delay(d.ticksPerSecond);
      UnBlockWindow;
      I.CloseWindow(transferWin);
      transferWin := NIL;
    END;
  END CloseTransferWindow;


  PROCEDURE Transfer(upload: BOOLEAN);
    VAR ok: BOOLEAN;
  BEGIN
    IF XPR.base=NIL THEN RETURN END;
    IF upload THEN ok := OpenTransferWindow("Upload")
              ELSE ok := OpenTransferWindow("Download") END;
    IF ok THEN
      AbortReadReq;
      IF upload THEN ok := XPR.XProtocolSend(xprio^);
                ELSE ok := XPR.XProtocolReceive(xprio^); END;
      SendReadReq;
      CloseTransferWindow;
    END;
  END Transfer;


  PROCEDURE CloseProtocol;
    VAR item: I.MenuItemPtr;
  BEGIN
    IF XPR.base # NIL THEN
      XPR.XProtocolCleanup(xprio^);
      e.CloseLibrary(XPR.base);
      XPR.base := NIL;
      xprio.data := NIL;
      xprio.filename := NIL;
      config.protocol := "";
      IF menu#NIL THEN
        IF (win#NIL) & (win.menuStrip#NIL) THEN
          I.OffMenu(win,I.UIntToLong(I.FullMenuNum(5,4,I.noSub)));
        ELSE
          item := I.ItemAddress(menu^,I.UIntToLong(I.FullMenuNum(5,4,I.noSub)));
          EXCL(item.flags,I.itemEnabled);
        END;
      END;
    END;
  END CloseProtocol;


  CONST
    cantLoadProtocol  = 1;
    cantSetupProtocol = 2;


  PROCEDURE OpenProtocol(name: ARRAY OF CHAR): INTEGER; (* $CopyArrays- *)
    VAR libName: ARRAY 80 OF CHAR;
        xprname: ARRAY 80 OF CHAR;
        base: e.LibraryPtr;
        item: I.MenuItemPtr;
  BEGIN
    COPY(name,xprname);
    str.Insert(xprname,0,"xpr");
    COPY(xprname,libName);
    str.Append(libName,".library");
    base := e.OpenLibrary(libName,0);
    IF base # NIL THEN
      CloseProtocol;
      XPR.base := base;
      IF d.GetVar(xprname,protocolOpts,LEN(protocolOpts),LONGSET{}) > 0 THEN
        xprio.filename := SYS.ADR(protocolOpts);
      END;
      xprFlags := XPR.XProtocolSetup(xprio^);
      (* $IF debug *)
      d.PrintF("xprflags=%lx\n",SYS.VAL(LONGINT,xprFlags));
      (* $END *)
      IF ~(XPR.success IN xprFlags) THEN
        CloseProtocol;
        RETURN cantSetupProtocol;
      ELSE
        IF menu#NIL THEN
          IF (win#NIL) & (win.menuStrip#NIL) THEN
            I.OnMenu(win,I.UIntToLong(I.FullMenuNum(5,4,I.noSub)));
          ELSE
            item := I.ItemAddress(menu^,I.UIntToLong(I.FullMenuNum(5,4,I.noSub)));
            INCL(item.flags,I.itemEnabled);
          END;
        END;
      END;
    ELSE
      RETURN cantLoadProtocol
    END;
    RETURN 0
  END OpenProtocol;


(*------------------- XPR Callback Functions ----------------------*)

  (* $StackChk- *)

  (* $SaveRegs+ *)

  PROCEDURE fopen(filename{8}: e.STRPTR; accessmode{9}: e.STRPTR): d.FileHandlePtr;
    VAR file: d.FileHandlePtr;
        ok: BOOLEAN;
  BEGIN
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("fopen %s\n",filename);
    (* $END *)
    file := NIL;
    CASE CAP(accessmode^[0]) OF
      "R": file := d.Open(filename^,d.oldFile);
    | "W": file := d.Open(filename^,d.newFile);
    | "A": file := d.Open(filename^,d.oldFile);
           IF file#NIL THEN
             IF d.Seek(file,0,d.end)=0 THEN END;
           ELSE
             file := d.Open(filename^,d.newFile);
           END;
    ELSE
    END;
    RETURN file;
  END fopen;


  (* $SaveRegs+ *)

  PROCEDURE fclose(fileptr{8}: d.FileHandlePtr): LONGINT;
  BEGIN
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("fclose\n");
    (* $END *)
    d.OldClose(fileptr);
    RETURN 0;
  END fclose;


  (* $SaveRegs+ *)

  PROCEDURE fread(buffer{8}: e.STRPTR; size{0},count{1}: LONGINT; fileptr{9}: d.FileHandlePtr): LONGINT;
    VAR n,siz,cnt: LONGINT;
  BEGIN
    siz := size;
    cnt := count;
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("fread %ld,%ld\n",siz,cnt);
    (* $END *)
    IF (siz>0) & (cnt>0) THEN
      n := d.FRead(fileptr,buffer^,siz,cnt);
      IF n > 0 THEN
        RETURN n
      END;
    END;
    RETURN 0;
  END fread;


  (* $SaveRegs+ *)

  PROCEDURE fwrite(buffer{8}: e.STRPTR; size{0},count{1}: LONGINT; fileptr{9}: d.FileHandlePtr): LONGINT;
    VAR n,siz,cnt: LONGINT;
  BEGIN
    siz := size;
    cnt := count;
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("fwrite %ld,%ld\n",siz,cnt);
    (* $END *)
    IF (siz>0) & (cnt>0) THEN
      n := d.FWrite(fileptr,buffer^,siz,cnt);
      IF n > 0 THEN
        RETURN n
      END;
    END;
    RETURN 0;
  END fwrite;


  (* $SaveRegs+ *)

  PROCEDURE finfo(filename{8}: e.STRPTR; typeofinfo{0}: LONGINT): LONGINT;
    VAR infotype: LONGINT;
        lock: d.FileLockPtr;
        fib: d.FileInfoBlockPtr;
        res: LONGINT;
  BEGIN
    infotype := typeofinfo;
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("finfo\n");
    (* $END *)
    res := 0;
    CASE infotype OF
      1: lock := d.Lock(filename^,d.sharedLock);
         IF lock#NIL THEN
           fib := d.AllocDosObjectTags(d.fib,u.done);
           IF fib # NIL THEN
             IF d.Examine(lock,fib^) THEN
               res := fib.size
             END;
             d.FreeDosObject(d.fib,fib);
           END;
           d.UnLock(lock);
         END;
    | 2: res := 1
    ELSE
    END;
    RETURN res
  END finfo;


  (* $SaveRegs+ *)

  PROCEDURE fseek(fileptr{8}: d.FileHandlePtr; offset{0},origin{1}: LONGINT): LONGINT;
    VAR
      offs,orgn: LONGINT;
  BEGIN
    offs := offset;
    orgn := origin;
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("fseek\n");
    (* $END *)
    IF d.Seek(fileptr,offs,orgn-1)=-1 THEN RETURN -1
                                      ELSE RETURN  0 END;
  END fseek;


  (* $SaveRegs+ *)

  PROCEDURE unlink(filename{8}: e.STRPTR): LONGINT;
  BEGIN
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("unlink\n");
    (* $END *)
    IF d.DeleteFile(filename^) THEN RETURN  0
                               ELSE RETURN -1 END;
  END unlink;


  (* $SaveRegs+ *)

  PROCEDURE chkabort(): LONGINT;
    VAR msg: I.IntuiMessagePtr;
        res: LONGINT;
  BEGIN
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("chkabort\n");
    (* $END *)
    IF transferWin=NIL THEN RETURN 0 END;
    res := 0;
    LOOP
      msg := e.GetMsg(transferWin.userPort);
      IF msg=NIL THEN EXIT END;
      IF I.closeWindow IN msg.class THEN
        IF (res=0) & (MyRequest("Request","Abort Transfer?","Abort|Cancel",NIL)#0) THEN
          res := -1;
        END;
      END;
      e.ReplyMsg(msg);
    END;
    RETURN res;
  END chkabort;


  (* $SaveRegs+ *)

  PROCEDURE sread(buffer{8}: e.STRPTR; size{0},timeout{1}: LONGINT): LONGINT;
    VAR siz,tmout: LONGINT;
        sigRec: LONGSET;
        sigTimer: SHORTINT;
  BEGIN
    siz := size;
    tmout := timeout;
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("sread\n");
    (* $END *)
    IF chkabort()#0 THEN RETURN -1 END;
    IF siz <= 0 THEN RETURN 0 END;
    serWriteReq.ioSer.command := se.query;
    e.OldDoIO(serWriteReq);
    IF tmout < 1 THEN
      IF serWriteReq.ioSer.actual > 0 THEN
        serReadReq.ioSer.length  := Min(serWriteReq.ioSer.actual,siz);
        serReadReq.ioSer.command := e.read;
        serReadReq.ioSer.data    := buffer;
        IF e.DoIO(serReadReq)=0 THEN RETURN serReadReq.ioSer.actual END;
      END;
    ELSE
      IF serWriteReq.ioSer.actual < siz THEN
        timeReq.node.command := ti.addRequest;
        IF tmout < 1000000 THEN
          timeReq.time.secs  := 0;
          timeReq.time.micro := tmout;
        ELSE
          timeReq.time.secs  := tmout DIV 1000000;
          timeReq.time.micro := tmout MOD 1000000;
        END;
        serReadReq.ioSer.command := e.read;
        serReadReq.ioSer.data    := buffer;
        serReadReq.ioSer.length  := siz;
        sigTimer := timeReq.node.message.replyPort.sigBit;
        SYS.SETREG(0,e.SetSignal(LONGSET{},LONGSET{serReadPort.sigBit,sigTimer}));
        e.SendIO(serReadReq);
        e.SendIO(timeReq);
        LOOP
          sigRec := e.Wait(LONGSET{serReadPort.sigBit,sigTimer});
          IF sigTimer IN sigRec THEN
            e.AbortIO(serReadReq);
            e.OldWaitIO(serReadReq);
            e.OldWaitIO(timeReq);
            IF serReadReq.ioSer.actual=0 THEN
              serWriteReq.ioSer.command := se.query;
              e.OldDoIO(serWriteReq);
              IF serWriteReq.ioSer.actual > 0 THEN
                serReadReq.ioSer.length  := Min(serWriteReq.ioSer.actual,siz);
                serReadReq.ioSer.command := e.read;
                serReadReq.ioSer.data    := buffer;
                IF e.DoIO(serReadReq)=0 THEN RETURN serReadReq.ioSer.actual
                                     ELSE RETURN 0 END;
              ELSE
                RETURN 0
              END;
            ELSE
              RETURN serReadReq.ioSer.actual
            END;
          END;
          IF serReadPort.sigBit IN sigRec THEN
            e.AbortIO(timeReq);
            e.OldWaitIO(timeReq);
            e.OldWaitIO(serReadReq);
            IF serReadReq.ioSer.error=0 THEN RETURN serReadReq.ioSer.actual
                                     ELSE RETURN 0 END;
          END;
        END; (* LOOP *)
      ELSE
        serReadReq.ioSer.command := e.read;
        serReadReq.ioSer.data    := buffer;
        serReadReq.ioSer.length  := siz;
        IF e.DoIO(serReadReq)=0 THEN RETURN siz END;
      END;
    END;
    RETURN 0;
  END sread;


  (* $SaveRegs+ *)

  PROCEDURE swrite(buffer{8}: e.STRPTR; size{0}: LONGINT): LONGINT;
    VAR siz: LONGINT;
  BEGIN
    siz := size;
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("swrite\n");
    (* $END *)
    serWriteReq.ioSer.data := buffer;
    serWriteReq.ioSer.length := siz;
    serWriteReq.ioSer.command := e.write;
    RETURN e.DoIO(serWriteReq);
  END swrite;


  (* $SaveRegs+ *)

  PROCEDURE sflush(): LONGINT;
  BEGIN
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("sflush\n");
    (* $END *)
    serWriteReq.ioSer.command := e.clear;
    RETURN e.DoIO(serWriteReq);
  END sflush;


  (* $SaveRegs+ *)

  PROCEDURE update(us{8}: XPR.UpdatePtr): LONGINT;
    VAR mask: LONGSET;
        p,w,len: INTEGER;
        txt: ARRAY 50 OF CHAR;
        rp: g.RastPortPtr;
  BEGIN
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("update\n");
    (* $END *)
    IF transferWin=NIL THEN
      IF ~OpenTransferWindow("Auto Transfer") THEN RETURN 0 END;
    END;
    rp := transferWin.rPort;
    g.SetAPen(transferWin.rPort,2);
    mask := us.updatemask;
    IF (XPR.protocol    IN mask)&(us.protocol   #NIL)THEN PutText(transferWin, 2+11,2,us.protocol^,50)    END;
    IF (XPR.filename    IN mask)&(us.filename   #NIL)THEN PutText(transferWin, 2+11,3,us.filename^,50)    END;
    IF (XPR.blockcheck  IN mask)&(us.blockcheck #NIL)THEN PutText(transferWin, 2+16,12,us.blockcheck^,14) END;
    IF (XPR.expecttime  IN mask)&(us.expecttime #NIL)THEN PutText(transferWin,32+15,12,us.expecttime^,14) END;
    IF (XPR.elapsedtime IN mask)&(us.elapsedtime#NIL)THEN PutText(transferWin,32+15,13,us.elapsedtime^,14)END;
    IF (XPR.msg         IN mask)&(us.msg        #NIL)THEN PutText(transferWin, 2+11,5,us.msg^,50);        END;
    IF (XPR.errormsg    IN mask)&(us.errormsg   #NIL)THEN PutText(transferWin, 2+11,6,us.errormsg^,50);   END;
    IF (XPR.filesize    IN mask)&(us.filesize   #-1) THEN e.RawDoFmt("%ld",SYS.ADR(us.filesize),RFProc,SYS.ADR(txt));    PutText(transferWin, 2+16, 8,txt,14) END;
    IF (XPR.blocks      IN mask)&(us.blocks     #-1) THEN e.RawDoFmt("%ld",SYS.ADR(us.blocks),RFProc,SYS.ADR(txt));      PutText(transferWin, 2+16,10,txt,14) END;
    IF (XPR.blocksize   IN mask)&(us.blocksize  #-1) THEN e.RawDoFmt("%ld",SYS.ADR(us.blocksize),RFProc,SYS.ADR(txt));   PutText(transferWin, 2+16,11,txt,14) END;
    IF (XPR.bytes       IN mask)&(us.bytes      #-1) THEN e.RawDoFmt("%ld",SYS.ADR(us.bytes),RFProc,SYS.ADR(txt));       PutText(transferWin, 2+16, 9,txt,14) END;
    IF (XPR.errors      IN mask)&(us.errors     #-1) THEN e.RawDoFmt("%ld",SYS.ADR(us.errors),RFProc,SYS.ADR(txt));      PutText(transferWin,32+15, 9,txt,14) END;
    IF (XPR.timeouts    IN mask)&(us.timeouts   #-1) THEN e.RawDoFmt("%ld",SYS.ADR(us.timeouts),RFProc,SYS.ADR(txt));    PutText(transferWin,32+15, 8,txt,14) END;
    IF (XPR.packetdelay IN mask)&(us.packetdelay#-1) THEN e.RawDoFmt("%ld",SYS.ADR(us.packetdelay),RFProc,SYS.ADR(txt)); PutText(transferWin,32+15,11,txt,14) END;
    IF (XPR.chardelay   IN mask)&(us.chardelay  #-1) THEN e.RawDoFmt("%ld",SYS.ADR(us.chardelay),RFProc,SYS.ADR(txt));   PutText(transferWin,32+15,10,txt,14) END;
    IF (XPR.datarate    IN mask)&(us.datarate   #-1) THEN e.RawDoFmt("%ld",SYS.ADR(us.datarate),RFProc,SYS.ADR(txt));    PutText(transferWin, 2+16,13,txt,14) END;
    IF (XPR.bytes       IN mask)&(us.bytes#-1) & (us.filesize>0) & (us.bytes<20000000) THEN
      p := SHORT((us.bytes * 100) DIV us.filesize);
      IF p<0 THEN p := 0 ELSIF p>100 THEN p := 100 END;
      w := SHORT((bbox.width * LONG(p)) DIV 100);
      g.SetAPen(rp,3); DrawBox(rp,bbox.left,  bbox.top,w           ,bbox.height);
      g.SetAPen(rp,0); DrawBox(rp,bbox.left+w,bbox.top,bbox.width-w,bbox.height);
      e.RawDoFmt("%d%%",SYS.ADR(p),RFProc,SYS.ADR(txt));
      g.SetAPen(rp,2);
      g.SetDrMd(rp,g.jam1);
      len := SHORT(str.Length(txt));
      g.Move(rp,bbox.left+(bbox.width-len*rp.txWidth) DIV 2,bbox.top+rp.txBaseline+2);
      g.Text(rp,txt,len);
      g.SetDrMd(rp,g.jam2);
    END;
    RETURN 0
  END update;


  (* $SaveRegs+ *)

  PROCEDURE setserial(newstatus{0}: LONGINT): LONGINT;
    TYPE
      Status=STRUCT byte3:SHORTINT; byte2:SHORTINT; byte1:SHORTSET; byte0:SHORTSET END;
    VAR
      newstat, oldstat: Status;
      i: SHORTINT; found: BOOLEAN;
  BEGIN
    newstat := SYS.VAL(Status,newstatus);
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("setserial\n");
    (* $END *)
    serWriteReq.ioSer.command := se.query;
    e.OldDoIO(serWriteReq);
    oldstat.byte0 := serWriteReq.serFlags;
    oldstat.byte1 := SHORTSET{};
    IF se.mSpOn IN serWriteReq.extFlags THEN INCL(oldstat.byte1,0) END;
    IF se.mark  IN serWriteReq.extFlags THEN INCL(oldstat.byte1,1) END;
    IF serWriteReq.stopBits = 2         THEN INCL(oldstat.byte1,2) END;
    IF serWriteReq.readLen  = 7         THEN INCL(oldstat.byte1,3) END;
    IF serWriteReq.writeLen = 7         THEN INCL(oldstat.byte1,4) END;
    i := 0; found := FALSE;
    WHILE (i < LEN(baudRates)) & ~found DO
      IF serWriteReq.baud = baudRates[i] THEN
        oldstat.byte2 := i;
        found := TRUE
      END;
      INC(i)
    END;
    IF ~found THEN RETURN -1 END;
    oldstat.byte3 := 0;
    IF SYS.VAL(LONGINT,newstat) # -1 THEN
      IF (newstat.byte2 >= 0) & (newstat.byte2 < LEN(baudRates)) THEN
        serWriteReq.baud := baudRates[newstat.byte2];
      ELSE RETURN -1
      END;
      serWriteReq.serFlags := newstat.byte0;
      serWriteReq.extFlags := LONGSET{};
      IF 0 IN newstat.byte1 THEN INCL(serWriteReq.extFlags,se.mSpOn) END;
      IF 1 IN newstat.byte1 THEN INCL(serWriteReq.extFlags,se.mark) END;
      IF 2 IN newstat.byte1 THEN serWriteReq.stopBits := 2 ELSE serWriteReq.stopBits := 1 END;
      IF 3 IN newstat.byte1 THEN serWriteReq.readLen  := 7 ELSE serWriteReq.readLen  := 8 END;
      IF 4 IN newstat.byte1 THEN serWriteReq.writeLen := 7 ELSE serWriteReq.writeLen := 8 END;
      serWriteReq.ioSer.command := se.setparams;
      IF e.DoIO(serWriteReq)#0 THEN RETURN -1 END;
    END;
    RETURN SYS.VAL(LONGINT,oldstat);
  END setserial;


  (* $SaveRegs+ *)

  PROCEDURE squery(): LONGINT;
  BEGIN
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("squery\n");
    (* $END *)
    serWriteReq.ioSer.command := se.query;
    IF e.DoIO(serWriteReq)=0 THEN RETURN serWriteReq.ioSer.actual
                          ELSE RETURN -1 END;
  END squery;


  (* $SaveRegs+ *)

  PROCEDURE gets(prompt{8},buffer{9}: e.STRPTR): LONGINT;
    VAR name: e.STRING;
        ok: BOOLEAN;
  BEGIN
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("gets\n");
    (* $END *)
    BlockWindow;
    ok := rt.GetString(buffer^,LEN(buffer^),prompt^,NIL,u.end);
    COPY(buffer^,protocolOpts);
    name := "xpr"; str.Append(name,config.protocol);
    IF d.SetVar(name,protocolOpts,-1,LONGSET{d.globalOnly}) THEN END;
    name := "ENVARC:"; str.Append(name,"xpr"); str.Append(name,config.protocol);
    IF d.SetVar(name,protocolOpts,-1,LONGSET{d.globalOnly}) THEN END;
    UnBlockWindow;
    IF ok THEN RETURN  0
          ELSE RETURN -1 END;
  END gets;


  (* $SaveRegs+ *)

  PROCEDURE getptr(typeofinfo{0}: LONGINT): e.APTR;
    VAR infotype: LONGINT;
  BEGIN
    infotype := typeofinfo;
    ol.SetA5;
    (* $IF debug *)
    d.PrintF("getptr\n");
    (* $END *)
    IF infotype = 1 THEN RETURN scr
                    ELSE RETURN  -1 END;
  END getptr;

  (* $StackChk= *)


  PROCEDURE XprSetup(VAR io: XPR.IO);
  BEGIN
    io.filename := NIL;
    io.fopen    := SYS.VAL(e.PROC,fopen);
    io.fclose   := SYS.VAL(e.PROC,fclose);
    io.fread    := SYS.VAL(e.PROC,fread);
    io.fwrite   := SYS.VAL(e.PROC,fwrite);
    io.sread    := SYS.VAL(e.PROC,sread);
    io.swrite   := SYS.VAL(e.PROC,swrite);
    io.sflush   := SYS.VAL(e.PROC,sflush);
    io.update   := SYS.VAL(e.PROC,update);
    io.chkabort := SYS.VAL(e.PROC,chkabort);
    io.chkmisc  := NIL;
    io.gets     := SYS.VAL(e.PROC,gets);
    io.setserial:= SYS.VAL(e.PROC,setserial);
    io.ffirst   := NIL;
    io.fnext    := NIL;
    io.finfo    := SYS.VAL(e.PROC,finfo);
    io.fseek    := SYS.VAL(e.PROC,fseek);
    io.extension:= 4;
    io.data     := NIL;
    io.options  := NIL;
    io.unlink   := SYS.VAL(e.PROC,unlink);
    io.squery   := SYS.VAL(e.PROC,squery);
    io.getptr   := SYS.VAL(e.PROC,getptr);
  END XprSetup;

(*----------------------- Menu Functions --------------------------*)

  PROCEDURE QuitFunc(code: INTEGER): BOOLEAN;
  BEGIN
    RETURN MyRequest("Request","Quit Termite?","Quit|Cancel",NIL) # 0;
  END QuitFunc;


  PROCEDURE AboutFunc(code: INTEGER): BOOLEAN;
  BEGIN
    (* $IF unreg *)
    IF MyRequest("Information","    Termite 1.0\n"
                               "   unregistered\n"
                               "      © 1993\n"
                               "by Christian Stiens\n"
                               "     SHAREWARE\n"
                               "All Rights Reserved"," OK ",NIL)=0 THEN END;
    (* $ELSE *)
    IF MyRequest("Information","    Termite 1.0\n"
                               " registered #00000\n"
                               "      © 1993\n"
                               "by Christian Stiens\n"
                               "     SHAREWARE\n"
                               "All Rights Reserved"," OK ",NIL)=0 THEN END;
    (* $END *)
    RETURN FALSE;
  END AboutFunc;


  PROCEDURE BaudFunc(code: INTEGER): BOOLEAN;
    VAR nr: INTEGER;
  BEGIN
    nr := I.SubNum(code);
    IF (nr<0) OR (nr >= LEN(baudRates)) THEN RETURN FALSE END;
    config.baud := baudRates[nr];
    ConfigToSer;
    RETURN FALSE;
  END BaudFunc;


  PROCEDURE BitsFunc(code: INTEGER): BOOLEAN;
  BEGIN
    CASE I.SubNum(code) OF
      0: config.bitsPerChar := 7;
    ELSE config.bitsPerChar := 8;
    END;
    ConfigToSer;
    RETURN FALSE;
  END BitsFunc;


  PROCEDURE StopBitsFunc(code: INTEGER): BOOLEAN;
  BEGIN
    CASE I.SubNum(code) OF
      1: config.stopBits := 2
    ELSE config.stopBits := 1
    END;
    ConfigToSer;
    RETURN FALSE;
  END StopBitsFunc;


  PROCEDURE DuplexFunc(code: INTEGER): BOOLEAN;
  BEGIN
    CASE I.SubNum(code) OF
      0: config.duplex := halfduplex
    | 1: config.duplex := fullduplex
    ELSE
    END;
    RETURN FALSE;
  END DuplexFunc;


  PROCEDURE InputBufFunc(code: INTEGER): BOOLEAN;
    VAR nr: INTEGER;
  BEGIN
    nr := I.SubNum(code);
    IF (nr<0) OR (nr >= LEN(bufSizes)) THEN RETURN FALSE END;
    config.rBufLen := bufSizes[nr];
    ConfigToSer;
    RETURN FALSE;
  END InputBufFunc;


  PROCEDURE HandshakeFunc(code: INTEGER): BOOLEAN;
  BEGIN
    CASE I.SubNum(code) OF
      none..ctsrts: config.handShaking := SHORT(I.SubNum(code));
    ELSE RETURN FALSE
    END;
    ConfigToSer;
    RETURN FALSE;
  END HandshakeFunc;


  PROCEDURE ParityFunc(code: INTEGER): BOOLEAN;
  BEGIN
    CASE I.SubNum(code) OF
      none..space: config.parity := SHORT(I.SubNum(code));
    ELSE RETURN FALSE
    END;
    ConfigToSer;
    RETURN FALSE;
  END ParityFunc;


  PROCEDURE RadboogieFunc(code: INTEGER): BOOLEAN;
  BEGIN
    config.radBoogie := I.checked IN SYS.VAL(I.MenuItemPtr,I.ItemAddress(menu^,code)).flags;
    ConfigToSer;
    RETURN FALSE;
  END RadboogieFunc;


  PROCEDURE HangUpFunc(code: INTEGER): BOOLEAN;
  BEGIN
    SerWriteString(config.hangupString);
    RETURN FALSE
  END HangUpFunc;


  PROCEDURE InitFunc(code: INTEGER): BOOLEAN;
  BEGIN
    SerWriteString(config.initString);
    RETURN FALSE
  END InitFunc;


  PROCEDURE InitStringFunc(code: INTEGER): BOOLEAN;
  BEGIN
    BlockWindow;
    IF rt.GetString(config.initString,LEN(config.initString),"Enter Init-String",NIL,u.end) THEN END;
    UnBlockWindow;
    RETURN FALSE
  END InitStringFunc;


  PROCEDURE HangupStringFunc(code: INTEGER): BOOLEAN;
  BEGIN
    BlockWindow;
    IF rt.GetString(config.hangupString,LEN(config.hangupString),"Enter Hangup-String",NIL,u.end) THEN END;
    UnBlockWindow;
    RETURN FALSE
  END HangupStringFunc;



  PROCEDURE WriteString(VAR file: fs.File; head,txt: ARRAY OF CHAR; quote: BOOLEAN); (* $CopyArrays- *)
    VAR string: e.STRING;
  BEGIN
    COPY(head,string);
    WHILE str.Length(string) < 16 DO str.Append(string," ") END;
    IF quote THEN str.Append(string,'"') END;
    str.Append(string,txt);
    IF quote THEN str.Append(string,'"') END;
    IF (file.status=fs.ok) & fs.WriteString(file,string) THEN END;
  END WriteString;

  PROCEDURE WriteInt(VAR file: fs.File; head: ARRAY OF CHAR; n: LONGINT); (* $CopyArrays- *)
    VAR int: ARRAY 16 OF CHAR;
  BEGIN
    e.RawDoFmt("%ld",SYS.ADR(n),RFProc,SYS.ADR(int));
    WriteString(file,head,int,FALSE);
  END WriteInt;

  PROCEDURE WriteBool(VAR file: fs.File; head: ARRAY OF CHAR; flag: BOOLEAN); (* $CopyArrays- *)
    VAR string: ARRAY 8 OF CHAR;
  BEGIN
    IF flag THEN string := "TRUE" ELSE string := "FALSE" END;
    WriteString(file,head,string,FALSE);
  END WriteBool;

  PROCEDURE WriteEnum(VAR file: fs.File; head: ARRAY OF CHAR; int: INTEGER; enum: ARRAY OF CHAR); (* $CopyArrays- *)
    VAR txt: ARRAY 16 OF CHAR;
        i,j: INTEGER;
  BEGIN
    j := 0;
    FOR i := 1 TO int DO
      WHILE (enum[j]#0X) & (enum[j]#"|") DO INC(j) END;
      IF enum[j]="|" THEN INC(j) END;
    END;
    i := 0;
    WHILE (enum[j]#0X) & (enum[j]#"|") DO
      txt[i] := enum[j];
      INC(i); INC(j);
    END;
    txt[i] := 0X;
    WriteString(file,head,txt,FALSE);
  END WriteEnum;


  PROCEDURE SaveConfigFunc(code: INTEGER): BOOLEAN;
    VAR name: e.STRING;
        file: fs.File;
        i,n: INTEGER;
  BEGIN
    BlockWindow;
    fr.okText := "Save";
    fr.pattern := "~(#?.info)";
    name := "Termite.config";
    IF fr.FileReqWinSave("Save Config",name,win) THEN
      IF fs.Open(file,name,TRUE) THEN
        IF fs.WriteString(file,"; Termite Configuration\n") THEN END;
        WriteString(file,"DEVICE"       ,config.device,TRUE);
        WriteInt   (file,"UNIT"         ,config.unit);
        WriteInt   (file,"BUFFER"       ,config.rBufLen);
        WriteInt   (file,"BAUDRATE"     ,config.baud);
        WriteInt   (file,"BREAKTIME"    ,config.breakTime);
        WriteInt   (file,"BITSPERCHAR"  ,config.bitsPerChar);
        WriteInt   (file,"STOPBITS"     ,config.stopBits);
        WriteEnum  (file,"PARITY"       ,config.parity,"NONE|EVEN|ODD|MARK|SPACE|");
        WriteEnum  (file,"HANDSHAKING"  ,config.handShaking,"NONE|XONXOFF|7WIRE|");
        WriteBool  (file,"RADBOOGIE"    ,config.radBoogie);
        WriteEnum  (file,"DUPLEX"       ,config.duplex,"HALF|FULL|");
        WriteString(file,"INITSTRING"   ,config.initString,TRUE);
        WriteString(file,"HANGUPSTRING" ,config.hangupString,TRUE);
        WriteInt   (file,"TIMEFORCONNECT",config.time4connect);
        WriteString(file,"PROTOCOL"     ,config.protocol,TRUE);
        WriteString(file,"CAPTURE"      ,config.capture,TRUE);
        WriteString(file,"DOWNLOADDIR"  ,config.downloadDir,TRUE);
        WriteString(file,"UPLOADDIR"    ,config.uploadDir,TRUE);
        WriteString(file,"PASTEDIR"     ,config.pasteDir,TRUE);
        WriteInt   (file,"PASTEDELAY"   ,config.pasteDelay);
        WriteInt   (file,"SCREENDEPTH"  ,config.screenDepth);
        FOR i := 0 TO ASH(LONG(1),scr.rastPort.bitMap.depth)-1 DO
          n := g.GetRGB4(scr.viewPort.colorMap,i);
          e.RawDoFmt("%03x,",SYS.ADR(n),RFProc,SYS.ADR(name[i*4]));
        END;
        IF i>0 THEN name[i*4-1] := 0X END;
        WriteString(file,"PALETTE"      ,name,FALSE);
        WriteString(file,"FONT"         ,config.font,TRUE);
        WriteString(file,"KEYMAP"       ,config.keyMap,TRUE);
        WriteInt   (file,"PRIORITY"     ,config.priority);

        IF fs.Close(file) THEN END;
      END;
    END;
    UnBlockWindow;
    RETURN FALSE
  END SaveConfigFunc;


  PROCEDURE ProtocolFunc(code: INTEGER): BOOLEAN;
    VAR n: LONGINT;
        name: e.STRING;
        xprname: ARRAY 80 OF CHAR;
        nochmal: BOOLEAN;
  BEGIN
    name := "LIBS:";
    BlockWindow;
    fr.pattern := "xpr#?.library";
    fr.okText := "Select";
    fr.patGad := FALSE;
    REPEAT
      nochmal := FALSE;
      IF fr.FileReqWin("Select Protocol",name,win) THEN
        COPY(d.FilePart(name)^,xprname);
        str.Delete(xprname,0,3);
        FOR n := 0 TO str.Length(xprname)-1 DO
          IF xprname[n]="." THEN xprname[n] := 0X END;
        END;
        CASE OpenProtocol(xprname) OF
          cantLoadProtocol:  nochmal := MyRequest("Information","Can't load Protocol", "Retry|Cancel",NIL)#0;
        | cantSetupProtocol: nochmal := MyRequest("Information","Can't setup Protocol","Retry|Cancel",NIL)#0;
        ELSE
          COPY(xprname,config.protocol);
        END;
      END;
    UNTIL ~nochmal;
    fr.patGad := TRUE;
    UnBlockWindow;
    RETURN FALSE
  END ProtocolFunc;


  PROCEDURE OptionsFunc(code: INTEGER): BOOLEAN;
  BEGIN
    IF XPR.base#NIL THEN
      xprio.filename := NIL;
      IF XPR.success IN XPR.XProtocolSetup(xprio^) THEN END;
    END;
    RETURN FALSE
  END OptionsFunc;


  PROCEDURE DownloadFunc(code: INTEGER): BOOLEAN;
    VAR name: e.STRING;
        lock,oldCD: d.FileLockPtr;
  BEGIN
    name := "";
    lock := NIL;
    BlockWindow;
    IF config.downloadDir # "" THEN
      lock := d.Lock(config.downloadDir,d.sharedLock);
      IF lock#NIL THEN oldCD := d.CurrentDir(lock) END;
    END;
    IF XPR.base=NIL THEN
      IF ProtocolFunc(0) THEN END;
    END;
    IF XPR.base#NIL THEN
      IF XPR.norecreq IN xprFlags THEN
        xprio.filename := NIL;
        Transfer(FALSE);
      ELSE
        fr.pattern := "~(#?.info)";
        fr.okText := "Downld";
        IF fr.FileReqWin("Download File",name,win) THEN
          xprio.filename := SYS.ADR(name);
          Transfer(FALSE);
        END;
      END;
    END;
    IF lock # NIL THEN oldCD := d.CurrentDir(oldCD); d.UnLock(lock) END;
    UnBlockWindow;
    RETURN FALSE
  END DownloadFunc;


  PROCEDURE UploadFunc(code: INTEGER): BOOLEAN;
    VAR name : e.STRING;
        lock,oldCD : d.FileLockPtr;
  BEGIN
    name := "";
    lock := NIL;
    BlockWindow;
    IF config.uploadDir # "" THEN
      lock := d.Lock(config.uploadDir,d.sharedLock);
      IF lock#NIL THEN oldCD := d.CurrentDir(lock) END;
    END;
    IF XPR.base=NIL THEN
      IF ProtocolFunc(0) THEN END;
    END;
    IF XPR.base#NIL THEN
      IF XPR.nosndreq IN xprFlags THEN
        xprio.filename := NIL;
        Transfer(TRUE);
      ELSE
        fr.pattern := "~(#?.info)";
        fr.okText := "Upload";
        IF fr.FileReqWin("Upload File",name,win) THEN
          xprio.filename := SYS.ADR(name);
          Transfer(TRUE);
        END;
      END;
    END;
    IF lock # NIL THEN oldCD := d.CurrentDir(oldCD); d.UnLock(lock) END;
    UnBlockWindow;
    RETURN FALSE
  END UploadFunc;


  PROCEDURE DialFunc(code: INTEGER): BOOLEAN;
    VAR
      item: I.MenuItemPtr;
      nr: INTEGER;
  BEGIN
    IF I.MenuNum(code)=4 THEN
      nr := lastDialNr
    ELSE
      nr := I.ItemNum(code);
      IF lastDialNr = -1 THEN
         I.OnMenu(win,I.UIntToLong(I.FullMenuNum(4,2,I.noSub)));
      END;
    END;
    IF (nr >= 0) & (nr < LEN(dialArray)) THEN
      SerWriteString(dialArray[nr]^);
      lastDialNr := nr;
    END;
    RETURN FALSE
  END DialFunc;


  PROCEDURE BreakFunc(code: INTEGER): BOOLEAN;
  BEGIN
    serWriteReq.ioSer.command := se.break;
    ioErr := e.DoIO(serWriteReq);
    RETURN FALSE
  END BreakFunc;


  PROCEDURE ClearFunc(code: INTEGER): BOOLEAN;
  BEGIN
    conWriteReq.data := SYS.ADR("\x0C");
    conWriteReq.length := -1;
    conWriteReq.command := e.write;
    e.OldDoIO(conWriteReq);
    RETURN FALSE
  END ClearFunc;


  PROCEDURE ResetFunc(code: INTEGER): BOOLEAN;
  BEGIN
    conWriteReq.data := SYS.ADR("\ec\[1}");
    conWriteReq.length := -1;
    conWriteReq.command := e.write;
    e.OldDoIO(conWriteReq);
    RETURN FALSE
  END ResetFunc;


  PROCEDURE ColorsFunc(code: INTEGER): BOOLEAN;
    VAR res: LONGINT;
  BEGIN
    BlockWindow;
    res := rt.PaletteRequest("Change Palette",NIL,u.end);
    UnBlockWindow;
    RETURN FALSE
  END ColorsFunc;


  PROCEDURE BreakTimeFunc(code: INTEGER): BOOLEAN;
  BEGIN
    BlockWindow;
    REPEAT
      IF ~rt.GetLong(config.breakTime,"Enter Break-Time (µs)",NIL,rt.glWidth,250,u.end) THEN END;
    UNTIL (config.breakTime>0)&(config.breakTime<5000000);
    ConfigToSer;
    UnBlockWindow;
    RETURN FALSE
  END BreakTimeFunc;


  PROCEDURE PasteDelayFunc(code: INTEGER): BOOLEAN;
  BEGIN
    BlockWindow;
    REPEAT
      IF ~rt.GetLong(config.pasteDelay,"Enter Paste-Delay (ms)",NIL,rt.glWidth,250,u.end) THEN END;
    UNTIL (config.pasteDelay >= 0) & (config.pasteDelay <= 1000);
    UnBlockWindow;
    RETURN FALSE
  END PasteDelayFunc;


  PROCEDURE Paste(text: e.APTR; size: LONGINT);
    VAR
      ch: CHAR;
      i,n: LONGINT;
      msg: I.IntuiMessagePtr;
      txt: UNTRACED POINTER TO ARRAY 0FFFFFFFH OF CHAR;
  BEGIN
    txt := text;
    IF size > 0 THEN
      AbortReadReq;

      FOR i := 0 TO size-1 DO
        ch := txt[i];
        IF ch = "\n" THEN ch := "\r" END;

        serWriteReq.ioSer.data := SYS.ADR(ch);
        serWriteReq.ioSer.length := 1;
        serWriteReq.ioSer.command := e.write;
        ioErr := e.DoIO(serWriteReq);

        IF config.duplex=halfduplex THEN
          conWriteReq.length  := 1;
          conWriteReq.data    := SYS.ADR(ch);
          conWriteReq.command := e.write;
          e.OldDoIO(conWriteReq);
          Capture(SYS.ADR(ch),1);
        END;

        n := sread(SYS.VAL(e.STRPTR,readBuf),readBufLen,0);
        IF n > 0 THEN
          conWriteReq.length  := n;
          conWriteReq.data    := readBuf;
          conWriteReq.command := e.write;
          e.OldDoIO(conWriteReq);
          Capture(readBuf,n);
        END;

        timeReq.time.secs  :=  config.pasteDelay DIV 1000;
        timeReq.time.micro := (config.pasteDelay * 1000) MOD 1000000;
        IF config.pasteDelay > 0 THEN
          timeReq.node.command := ti.addRequest;
          e.OldDoIO(timeReq);
        END;

        LOOP
          msg := gt.GetIMsg(win.userPort);
          IF msg=NIL THEN EXIT END;
          IF I.menuPick IN msg.class THEN
            IF MyRequest("Request","Abort Paste?","Abort|Cancel",NIL)#0 THEN
              i := size;
            END;
          END;
          gt.ReplyIMsg(msg);
        END;

      END;
      SendReadReq;
    END;
  END Paste;


  PROCEDURE PasteClipboardFunc(code: INTEGER): BOOLEAN;
    VAR name: e.STRING;
        text: e.APTR;
        size: LONGINT;
  BEGIN
    BlockWindow;
    IF cbt.Paste(size,text) THEN
      Paste(text,size);
      DISPOSE(text)
    END;
    UnBlockWindow;
    RETURN FALSE
  END PasteClipboardFunc;


  PROCEDURE PasteFileFunc(code: INTEGER): BOOLEAN;
    VAR name: e.STRING;
        text: e.APTR;
        size: LONGINT;
        file: fs.File;
        lock,oldCD: d.FileLockPtr;
  BEGIN
    name := "";
    lock := NIL;
    BlockWindow;
    IF config.pasteDir # "" THEN
      lock := d.Lock(config.pasteDir,d.sharedLock);
      IF lock#NIL THEN oldCD := d.CurrentDir(lock) END;
    END;
    fr.pattern := "~(#?.info)";
    fr.okText := "Paste";
    IF fr.FileReqWin("Paste File",name,win) THEN
      IF fs.Open(file,name,FALSE) THEN
        size := fs.Size(file);
        IF size > 0 THEN
          ol.Allocate(text,size);
          IF text#NIL THEN
            IF fs.ReadBlock(file,text,size) THEN
              Paste(text,size);
            END;
            DISPOSE(text)
          END;
        END;
        IF fs.Close(file) THEN END;
      END;
    END;
    IF lock # NIL THEN oldCD := d.CurrentDir(oldCD); d.UnLock(lock) END;
    UnBlockWindow;
    RETURN FALSE
  END PasteFileFunc;


(*------------------------- Menu Handler --------------------------*)


  PROCEDURE HandleMenuEvent (code: INTEGER): BOOLEAN;
    TYPE
      MenuFunc = PROCEDURE(code: INTEGER): BOOLEAN;
    VAR
      item: I.MenuItemPtr;
      func: MenuFunc;
      end: BOOLEAN;
  BEGIN
    end := FALSE;
    WHILE (code # I.menuNull) & ~end DO
      item := I.ItemAddress(menu^, code);
      func := SYS.VAL(MenuFunc,gt.MenuItemUserData(item));
      IF func # NIL THEN
        end := func(code)
      END;
      code := item.nextSelect;
    END;
    RETURN end;
  END HandleMenuEvent;


(*---------------------------- New Menu ---------------------------*)

  CONST
    menuBaud        = 15; itemsBaud = 12;
    menuBufLen      = 28; itemsBufLen = 8;
    menuHandShake   = 37;
    menuParity      = 41;
    menuBitsPerChar = 47;
    menuStopBits    = 50;
    menuDuplex      = 53;
    menuRadboogie   = 59;
    menuOptions     = 74;
    menuDial        = 75;

  CONST
    NewMenu = NewMenuType(
(*0*) gt.title, SYS.ADR("Project"),     NIL,         {},  LONGSET{}, NIL,
      gt.item , SYS.ADR("Save Config..."),SYS.ADR("S"),{},LONGSET{}, SYS.VAL(e.APTR,SaveConfigFunc),
      gt.item , SYS.ADR("About..."),    NIL,         {},  LONGSET{}, SYS.VAL(e.APTR,AboutFunc),
      gt.item , gt.barLabel,            NIL,         {},  LONGSET{}, NIL,
      gt.item , SYS.ADR("Quit"),        SYS.ADR("Q"),{},  LONGSET{}, SYS.VAL(e.APTR,QuitFunc),

(*5*) gt.title, SYS.ADR("Edit"),        NIL,         {},  LONGSET{}, NIL,
      gt.item , SYS.ADR("Paste Clip"),  SYS.ADR("V"),{},  LONGSET{}, SYS.VAL(e.APTR,PasteClipboardFunc),
      gt.item , SYS.ADR("Paste File..."),SYS.ADR("F"),{}, LONGSET{}, SYS.VAL(e.APTR,PasteFileFunc),
      gt.item , gt.barLabel,            NIL,         {},  LONGSET{}, NIL,
      gt.item , SYS.ADR("Delay..."),    NIL,         {},  LONGSET{}, SYS.VAL(e.APTR,PasteDelayFunc),

(*8*) gt.title, SYS.ADR("Screen"),      NIL,         {},  LONGSET{}, NIL,
      gt.item , SYS.ADR("Palette..."),  SYS.ADR("P"),{},  LONGSET{}, SYS.VAL(e.APTR,ColorsFunc),
      gt.item , SYS.ADR("Clear"),       SYS.ADR("L"),{},  LONGSET{}, SYS.VAL(e.APTR,ClearFunc),
      gt.item , SYS.ADR("Reset"),       SYS.ADR("R"),{},  LONGSET{}, SYS.VAL(e.APTR,ResetFunc),

(*14*)gt.title, SYS.ADR("Serial"),      NIL,         {},  LONGSET{}, NIL,

      gt.item , SYS.ADR("Baud Rate"),   NIL,         {},  LONGSET{}, NIL,
      gt.sub ,  SYS.ADR("110"),         NIL,         {I.checkIt},          -LONGSET{0}, SYS.VAL(e.APTR,BaudFunc),
      gt.sub ,  SYS.ADR("300"),         NIL,         {I.checkIt},          -LONGSET{1}, SYS.VAL(e.APTR,BaudFunc),
      gt.sub ,  SYS.ADR("1200"),        NIL,         {I.checkIt},          -LONGSET{2}, SYS.VAL(e.APTR,BaudFunc),
      gt.sub ,  SYS.ADR("2400"),        NIL,         {I.checkIt},          -LONGSET{3}, SYS.VAL(e.APTR,BaudFunc),
      gt.sub ,  SYS.ADR("4800"),        NIL,         {I.checkIt},          -LONGSET{4}, SYS.VAL(e.APTR,BaudFunc),
      gt.sub ,  SYS.ADR("9600"),        NIL,         {I.checkIt},          -LONGSET{5}, SYS.VAL(e.APTR,BaudFunc),
      gt.sub ,  SYS.ADR("19200"),       NIL,         {I.checkIt},          -LONGSET{6}, SYS.VAL(e.APTR,BaudFunc),
      gt.sub ,  SYS.ADR("31250"),       NIL,         {I.checkIt},          -LONGSET{7}, SYS.VAL(e.APTR,BaudFunc),
      gt.sub ,  SYS.ADR("38400"),       NIL,         {I.checkIt},          -LONGSET{8}, SYS.VAL(e.APTR,BaudFunc),
      gt.sub ,  SYS.ADR("57600"),       NIL,         {I.checkIt},          -LONGSET{9}, SYS.VAL(e.APTR,BaudFunc),
      gt.sub ,  SYS.ADR("76800"),       NIL,         {I.checkIt},          -LONGSET{10},SYS.VAL(e.APTR,BaudFunc),
      gt.sub ,  SYS.ADR("115200"),      NIL,         {I.checkIt},          -LONGSET{11},SYS.VAL(e.APTR,BaudFunc),

(*28*)gt.item , SYS.ADR("Buffer Size"), NIL,    {},                    LONGSET{},  NIL,
      gt.sub ,  SYS.ADR("512"),         NIL,         {I.checkIt},          -LONGSET{0}, SYS.VAL(e.APTR,InputBufFunc),
      gt.sub ,  SYS.ADR("1024"),        NIL,         {I.checkIt},          -LONGSET{1}, SYS.VAL(e.APTR,InputBufFunc),
      gt.sub ,  SYS.ADR("2048"),        NIL,         {I.checkIt},          -LONGSET{2}, SYS.VAL(e.APTR,InputBufFunc),
      gt.sub ,  SYS.ADR("4096"),        NIL,         {I.checkIt},          -LONGSET{3}, SYS.VAL(e.APTR,InputBufFunc),
      gt.sub ,  SYS.ADR("8192"),        NIL,         {I.checkIt},          -LONGSET{4}, SYS.VAL(e.APTR,InputBufFunc),
      gt.sub ,  SYS.ADR("16384"),       NIL,         {I.checkIt},          -LONGSET{5}, SYS.VAL(e.APTR,InputBufFunc),
      gt.sub ,  SYS.ADR("32768"),       NIL,         {I.checkIt},          -LONGSET{6}, SYS.VAL(e.APTR,InputBufFunc),
      gt.sub ,  SYS.ADR("65536"),       NIL,         {I.checkIt},          -LONGSET{7}, SYS.VAL(e.APTR,InputBufFunc),

(*37*)gt.item , SYS.ADR("Handshaking"), NIL,         {},  LONGSET{}, NIL,
      gt.sub ,  SYS.ADR("None"),        NIL,         {I.checkIt},          -LONGSET{0}, SYS.VAL(e.APTR,HandshakeFunc),
      gt.sub ,  SYS.ADR("XON/XOFF"),    NIL,         {I.checkIt},          -LONGSET{1}, SYS.VAL(e.APTR,HandshakeFunc),
      gt.sub ,  SYS.ADR("RTS/CTS"),     NIL,         {I.checkIt},          -LONGSET{2}, SYS.VAL(e.APTR,HandshakeFunc),

(*41*)gt.item , SYS.ADR("Parity"),      NIL,         {},  LONGSET{}, NIL,
      gt.sub ,  SYS.ADR("None"),        NIL,         {I.checkIt},          -LONGSET{0}, SYS.VAL(e.APTR,ParityFunc),
      gt.sub ,  SYS.ADR("Even"),        NIL,         {I.checkIt},          -LONGSET{1}, SYS.VAL(e.APTR,ParityFunc),
      gt.sub ,  SYS.ADR("Odd"),         NIL,         {I.checkIt},          -LONGSET{2}, SYS.VAL(e.APTR,ParityFunc),
      gt.sub ,  SYS.ADR("Mark"),        NIL,         {I.checkIt},          -LONGSET{3}, SYS.VAL(e.APTR,ParityFunc),
      gt.sub ,  SYS.ADR("Space"),       NIL,         {I.checkIt},          -LONGSET{4}, SYS.VAL(e.APTR,ParityFunc),

(*47*)gt.item , SYS.ADR("Bits/Char"),   NIL,         {},  LONGSET{}, NIL,
      gt.sub ,  SYS.ADR("7"),           NIL,         {I.checkIt},          -LONGSET{0}, SYS.VAL(e.APTR,BitsFunc),
      gt.sub ,  SYS.ADR("8"),           NIL,         {I.checkIt},          -LONGSET{1}, SYS.VAL(e.APTR,BitsFunc),

(*50*)gt.item , SYS.ADR("Stop Bits"),   NIL,         {},  LONGSET{}, NIL,
      gt.sub ,  SYS.ADR("1"),           NIL,         {I.checkIt},          -LONGSET{0}, SYS.VAL(e.APTR,StopBitsFunc),
      gt.sub ,  SYS.ADR("2"),           NIL,         {I.checkIt},          -LONGSET{1}, SYS.VAL(e.APTR,StopBitsFunc),

(*53*)gt.item , SYS.ADR("Duplex"),      NIL,         {},  LONGSET{}, NIL,
      gt.sub ,  SYS.ADR("Half"),        NIL,         {I.checkIt},          -LONGSET{0}, SYS.VAL(e.APTR,DuplexFunc),
      gt.sub ,  SYS.ADR("Full"),        NIL,         {I.checkIt},          -LONGSET{1}, SYS.VAL(e.APTR,DuplexFunc),

      gt.item , gt.barLabel,            NIL,         {},  LONGSET{}, NIL,

(*57*)gt.item , SYS.ADR("Break Time..."),      NIL,  {},                       LONGSET{}, SYS.VAL(e.APTR,BreakTimeFunc),
      gt.item , gt.barLabel,            NIL,         {},  LONGSET{}, NIL,
(*59*)gt.item , SYS.ADR("Rad Boogie"),  NIL,         {I.checkIt,I.menuToggle}, LONGSET{}, SYS.VAL(e.APTR,RadboogieFunc),

(*60*)gt.title, SYS.ADR("Modem"),       NIL,         {},  LONGSET{}, NIL,
      gt.item , SYS.ADR("Hang Up"),     SYS.ADR("H"),{},  LONGSET{}, SYS.VAL(e.APTR,HangUpFunc),
      gt.item , SYS.ADR("Init"),        SYS.ADR("I"),{},  LONGSET{}, SYS.VAL(e.APTR,InitFunc),
      gt.item , SYS.ADR("Redial"),      SYS.ADR("E"),{gt.itemDisabled},  LONGSET{}, SYS.VAL(e.APTR,DialFunc),
      gt.item , gt.barLabel,            NIL,         {},  LONGSET{}, NIL,
      gt.item , SYS.ADR("Send Break"),  SYS.ADR("B"),{},  LONGSET{}, SYS.VAL(e.APTR,BreakFunc),
      gt.item , gt.barLabel,            NIL,         {},  LONGSET{}, NIL,
      gt.item , SYS.ADR("Hangup String..."), NIL,    {},  LONGSET{}, SYS.VAL(e.APTR,HangupStringFunc),
      gt.item , SYS.ADR("Init String..."),   NIL,    {},  LONGSET{}, SYS.VAL(e.APTR,InitStringFunc),

(*69*)gt.title, SYS.ADR("Transfer"),    NIL,         {},  LONGSET{}, NIL,
      gt.item , SYS.ADR("Download..."), SYS.ADR("D"),{},  LONGSET{}, SYS.VAL(e.APTR,DownloadFunc),
      gt.item , SYS.ADR("Upload..."),   SYS.ADR("U"),{},  LONGSET{}, SYS.VAL(e.APTR,UploadFunc),
      gt.item , gt.barLabel,            NIL,         {},  LONGSET{}, NIL,
      gt.item , SYS.ADR("Protocol..."), NIL,         {},  LONGSET{}, SYS.VAL(e.APTR,ProtocolFunc),
      gt.item , SYS.ADR("Options..."),  SYS.ADR("O"),{},  LONGSET{}, SYS.VAL(e.APTR,OptionsFunc),

(*75*)gt.title, SYS.ADR("Dial"),        NIL,         {},  LONGSET{}, NIL,

      gt.end  , NIL,                    NIL,         {},  LONGSET{}, NIL);


  PROCEDURE LoadPhoneBook;
    CONST
       commKeys = "1\o2\o3\o4\o5\o6\o7\o8\o9\o0\o";
    VAR
      file: fs.File;
      entry: LONGINT;
      string: e.STRING;
  BEGIN
    IF args.phonebook # NIL THEN
      COPY(args.phonebook^,string);
    ELSE
      string := "Termite.phonebook";
      IF ~fs.Exists(string) THEN
        string := "PROGDIR:Termite.phonebook";
        IF resident OR ~fs.Exists(string) THEN
          string := "ENV:Termite.phonebook";
          IF ~fs.Exists(string) THEN
            string := "s:Termite.phonebook";
            IF ~fs.Exists(string) THEN
              string := ""
            END;
          END;
        END;
      END;
    END;
    entry := 0;
    IF fs.Open(file,string,FALSE) THEN
      WHILE fs.ReadString(file,string) & (entry < maxDialEntries) DO
        IF RemSpaces(string) THEN
          NEW(dialArray[entry]);
          NEW(newMenu[menuDial+1+entry].label);
          SplitLine(string,newMenu[menuDial+1+entry].label^,dialArray[entry]^);
          newMenu[menuDial+1+entry].type := gt.item;
          newMenu[menuDial+1+entry].userData := SYS.VAL(e.APTR,DialFunc);
          IF entry<10 THEN
            newMenu[menuDial+1+entry].commKey := SYS.ADR(commKeys[entry*2]);
          END;
          INC(entry);
        END;
      END;
      IF fs.Close(file) THEN END;
    END;
    IF entry=0 THEN INCL(newMenu[menuDial].flags,gt.menuDisabled) END;
  END LoadPhoneBook;


(*--------------------------- Status Task -------------------------*)

  (* $StackChk- $Debug- *)

  PROCEDURE StatusProc;
    CONST
      PC = "NEOMS";
    VAR
      status: STRUCT hour,min,sec: INTEGER; baud: LONGINT; len: INTEGER; dummy,par: CHAR; stop: INTEGER END;
      sec,mic: LONGINT;
      timeReq: ti.TimeRequestPtr;
      serPort: e.MsgPortPtr;
      rp: g.RastPortPtr;
      sec0: LONGINT;
      wasOnline: BOOLEAN;
      statusLine: ARRAY 80 OF CHAR;

  BEGIN
    (* $IF SmallData *)
    SYS.SETREG(A5,e.exec.thisTask.userData);
    (* $END *)

    rp := statusWin.rPort;

    g.SetRast(rp,scr.blockPen);
    g.SetDrMd(rp,g.jam2);
    g.SetAPen(rp,scr.detailPen);
    g.SetBPen(rp,scr.blockPen);

    timeReq := ds.OpenDev(ti.timerName,ti.vBlank,LONGSET{},SIZE(timeReq^),NIL);
    IF timeReq # NIL THEN
      serPort := e.CreateMsgPort();
      IF serPort # NIL THEN
        serReq .ioSer.message.replyPort := serPort;

        wasOnline := FALSE;
        sec := 0;
  
        REPEAT
          timeReq.time.secs  := 1;
          timeReq.time.micro := 0;
          timeReq.node.command := ti.addRequest;
          e.SendIO(timeReq);
  
          serReq.ioSer.command := se.query;
          e.OldDoIO(serReq);
          online := ~(hw.comCD IN serReq.status);

          IF online THEN
            I.CurrentTime(sec,mic);
            IF ~wasOnline THEN sec0 := sec-config.time4connect; wasOnline := TRUE END;
            DEC(sec,sec0);
          ELSE
            wasOnline := FALSE;
          END;
    
          status.hour := SHORT(sec DIV (60*60));
          status.min  := SHORT(sec DIV 60 MOD 60);
          status.sec  := SHORT(sec MOD 60);
          status.baud := config.baud;
          status.len  := config.bitsPerChar;
          status.stop := config.stopBits;
          status.par := PC[config.parity];
          IF online THEN statusLine := "Online     "
                    ELSE statusLine := "Offline    " END;
          e.RawDoFmt("%02d:%02d:%02d  %6ld   %d-%c-%d   ",SYS.ADR(status),RFProc,SYS.ADR(statusLine[10]));
          str.Append(statusLine,config.protocol);
          str.Append(statusLine,"               ");

          g.Move(rp,0,rp.txBaseline);
          g.Text(rp,statusLine,50);

          e.OldWaitIO(timeReq);
        UNTIL ol.closing;
        e.DeleteMsgPort(serPort);
      END;
      ds.CloseDev(timeReq);
    END;
    e.Signal(me,LONGSET{mySig});
    SYS.SETREG(0,e.Wait(LONGSET{}));
  END StatusProc;

  (* $StackChk= $Debug= *)


(*-----------------------------------------------------------------*)
(*------------------------------ Main -----------------------------*)
(*-----------------------------------------------------------------*)

BEGIN

  mySig := -1;
  oldPri := -999;
  me := SYS.VAL(d.ProcessPtr,ol.Me);
  oldWP := me.windowPtr;
  lastDialNr := -1;

  IF I.int.libNode.version<37 THEN
    IF I.DisplayAlert(0,"\x00\x64\x14Need OS 2.04 or higher.\o\o",50) THEN END;
    HALT(d.fail);
  END;

  (* $IF unreg *)
  IF MyRequest("Information","   You are still using\n"
                             " the unregistered version\n"
                             "        of Termite.\n"
                             "So, I must give you a task!"," OK ",NIL)=0 THEN END;
  factors.a := hw.custom.vhposr MOD 64 + 20;
  factors.b := (hw.custom.vhposr DIV 64) MOD 64 + 20;
  e.RawDoFmt("What is %d x %d?",SYS.ADR(factors),RFProc,SYS.ADR(string));
  REPEAT UNTIL rt.GetLong(result,string,NIL,rt.glWidth,250,u.end);
  Assert(result = factors.a * factors.b,"Wrong!");
  (* $END *)

  resident := d.GetProgramDir()=NIL;

  IF ~ol.wbStarted THEN
    rd := d.ReadArgs("C=CONFIG/K,P=PHONEBOOK/K",args,NIL);
    IF rd=NIL THEN
      IF d.PrintFault(d.IoErr(),NIL) THEN END;
      HALT(d.fail);
    END;
  END;

  oldMemReqs := ol.MemReqs;
  INCL(ol.MemReqs,e.public);
  NEW(writeBuf);
  NEW(readBuf);
  NEW(serReadReq);
  NEW(serReq);
  NEW(conReadReq);
  NEW(xprio);
  NEW(statusTask);
  NEW(statusStack);
  NEW(inputReq);
  ol.MemReqs := oldMemReqs;

  XprSetup(xprio^);

  mySig := e.AllocSignal(-1);
  Assert(mySig # -1,"Can't alloc Signal");

  topaz80 := g.OpenFont(topaz80attr);

(*--------------------- Open Input.Device -------------------------*)

  IF e.OpenDevice("input.device",0,inputReq,LONGSET{})=0 THEN
    in.base := inputReq.device
  ELSE
    DISPOSE(inputReq); inputReq := NIL;
    Assert(FALSE,"Can't open input.device");
  END;

(*------------------------ Open Timer -----------------------------*)

  timeReq := ds.OpenDev(ti.timerName,ti.microHz,LONGSET{},SIZE(timeReq^),NIL);
  Assert(timeReq # NIL,"Can't open timer.device");
  ti.base := timeReq.node.device;

(*------------------------ Load Config ----------------------------*)

  LoadConfig;
  keyMap := LoadKeyMap(config.keyMap);
  oldPri := e.SetTaskPri(me,config.priority);

(*------------------------ Open Serial ----------------------------*)

  IF odu.base # NIL THEN
    serLocked := odu.AttemptDevUnit(config.device,config.unit,"Termite",0)=NIL;
    Assert(serLocked,"Can't lock serial.device");
  END;

  serWriteReq := ds.OpenDev(config.device,config.unit,LONGSET{},SIZE(serWriteReq^),SerInit);
  Assert(serWriteReq # NIL,"Can't open serial.device");

  ConfigToSer;

  serReq^ := serWriteReq^;
  serReadReq^ := serWriteReq^;
  serReadPort := e.CreateMsgPort();
  Assert(serReadPort#NIL,"Can't create Port");
  serReadReq.ioSer.message.replyPort := serReadPort;

(*------------------------ Open Screen ----------------------------*)

  pub := I.LockPubScreen("Workbench");
  Assert(pub#NIL,"Can't lock Workbench");
  displayID := g.GetVPModeID(SYS.ADR(pub.viewPort));
  I.UnlockPubScreen(NIL,pub);

  height := -1;

  IF I.QueryOverscan(displayID,rect,I.oScanText) # 0 THEN
    height := rect.maxY-rect.minY+1;
  END;

  scr := I.OpenScreenTagsA(NIL,
              I.saDepth,       config.screenDepth,
              I.saHeight,      height,
              I.saDisplayID,   displayID,
              I.saPubName,     SYS.ADR("TERMITE"),
              I.saOverscan,    I.oScanText,
              I.saTitle,       SYS.ADR(version[7]),
              I.saPens,        SYS.ADR("\xff\xff"),
              I.saFullPalette, I.LTRUE,
              I.saFont,        attr,
              u.done);
  Assert(scr # NIL,"Can't open screen");

  SYS.SETREG(0,I.PubScreenStatus(scr,{}));

  IF colorCnt>0 THEN g.LoadRGB4(SYS.ADR(scr.viewPort),colorMap,colorCnt) END;

(*--------------------------- Setup Menu --------------------------*)

  e.CopyMem(NewMenu,newMenu,SIZE(NewMenu));

  FOR i := 0 TO itemsBaud-1 DO
    IF baudRates[i]=config.baud THEN INCL(newMenu[menuBaud+1+i].flags,I.checked) END;
  END;
  FOR i := 0 TO itemsBufLen-1 DO
    IF bufSizes[i]=config.rBufLen THEN INCL(newMenu[menuBufLen+1+i].flags,I.checked) END;
  END;
  INCL(newMenu[menuStopBits      + config.stopBits   ].flags,I.checked);
  INCL(newMenu[menuDuplex     +1 + config.duplex     ].flags,I.checked);
  INCL(newMenu[menuBitsPerChar-6 + config.bitsPerChar].flags,I.checked);
  INCL(newMenu[menuHandShake  +1 + config.handShaking].flags,I.checked);
  INCL(newMenu[menuParity     +1 + config.parity     ].flags,I.checked);
  IF config.radBoogie THEN INCL(newMenu[menuRadboogie].flags,I.checked) END;
  IF XPR.base=NIL     THEN INCL(newMenu[menuOptions  ].flags,gt.itemDisabled) END;

  LoadPhoneBook;

  vi   := gt.GetVisualInfo(scr,  u.done); Assert(vi # NIL,"Can't get Visual Info");
  menu := gt.CreateMenus(newMenu,u.done); Assert(menu # NIL,"Can't create Menu");
  Assert(gt.LayoutMenus(menu,vi,u.done),"Can't layout Menu");

(*--------------------------- Open Window -------------------------*)

  ySize := scr.font.ySize;

  win := I.OpenWindowTagsA(NIL,
              I.waTop,      scr.barHeight+2,
              I.waHeight,   scr.height-(scr.barHeight+2)-ySize,
              I.waIDCMP,    LONGSET{I.menuPick},
              I.waFlags,    LONGSET{I.activate,I.borderless,I.backDrop},
              I.waCustomScreen, scr,
              u.done);
  Assert(win # NIL,"Can't open window");

  me.windowPtr := win;

  Assert(I.SetMenuStrip(win,menu^),"Can't set menu");

  statusWin := I.OpenWindowTagsA(NIL,
              I.waTop,      scr.height-ySize,
              I.waHeight,   ySize,
              I.waIDCMP,    LONGSET{},
              I.waFlags,    LONGSET{I.noCareRefresh,I.borderless,I.backDrop},
              I.waCustomScreen, scr,
              u.done);
  Assert(statusWin # NIL,"Can't open status window");


(*------------------- Attach Console to Window --------------------*)

  conWriteReq := ds.OpenDev(con.consoleName,cu.standard,cu.flagDefault,0,ConInit);
  Assert(conWriteReq # NIL,"Can't open console.device");
  con.base := conWriteReq.device;
  conReadReq^ := conWriteReq^;
  conReadPort := e.CreateMsgPort();
  Assert(conReadPort # NIL,"Can't create port");
  conReadReq.message.replyPort := conReadPort;

  IF keyMap # NIL THEN
    conReadReq.data := keyMap;
    conReadReq.length := SIZE(keyMap^);
    conReadReq.command := con.setKeyMap;
    e.OldDoIO(conReadReq)
  END;

(*----------------------- Open Capture ----------------------------*)

  IF config.capture # "" THEN
    i := 1;
    LOOP
      COPY(config.capture,name);
      e.RawDoFmt("%d",SYS.ADR(i),RFProc,SYS.ADR(name[str.Length(name)]));
      IF ~fs.Exists(name) & (d.IoErr()#d.objectInUse) THEN EXIT END;
      INC(i);
    END;
    cap := fs.Open(capture,name,TRUE);
    IF ~cap & (MyRequest("Information","Can't open capture","Cancel",NIL)=0) THEN END;
  END;

(*----------------------- Setup Status Task -----------------------*)

  statusTask.spLower   := SYS.ADR(statusStack[   0]);
  statusTask.spUpper   := SYS.ADR(statusStack[4000]);
  statusTask.spReg     := statusTask.spUpper;
  statusTask.node.type := e.task;
  statusTask.node.name := SYS.ADR("termite.status");
  statusTask.node.pri  := 5;
  (* $IF SmallData *)
  statusTask.userData  := SYS.REG(A5);
  (* $END *)
  e.Forbid;
  e.AddTask(statusTask,StatusProc,NIL);
  statusRunning := TRUE;
  e.Permit;

(*------------------------ Load Protocol --------------------------*)

  IF OpenProtocol(config.protocol) # 0 THEN config.protocol := "" END;

(*-------------------------- Main Loop ----------------------------*)

  sigMask := LONGSET{win.userPort.sigBit,serReadPort.sigBit,conReadPort.sigBit};

  conReadReq.data   := writeBuf;
  conReadReq.length := writeBufLen;
  conReadReq.command := e.read;
  e.SendIO(conReadReq);

  SendReadReq;

  SerWriteString(config.initString);

  LOOP
    sigRec := e.Wait(sigMask);

    IF win.userPort.sigBit IN sigRec THEN
      LOOP
        msg := gt.GetIMsg(win.userPort);
        IF msg=NIL THEN EXIT END;

        IF I.menuPick IN msg.class THEN
          IF HandleMenuEvent(msg.code) THEN
            gt.ReplyIMsg(msg);
            AbortReadReq;
            HALT(0)
          END;
        END;

        gt.ReplyIMsg(msg);
      END; (* LOOP *)
    END;

    IF conReadPort.sigBit IN sigRec THEN
      e.OldWaitIO(conReadReq);
      n := conReadReq.actual;
      IF (XPR.base#NIL) & (XPR.usermon IN xprFlags) THEN
        n := XPR.XProtocolUserMon(xprio^,writeBuf,n,writeBufLen);
        IF transferWin # NIL THEN CloseTransferWindow END;
      END;
      IF n > 0 THEN
        serWriteReq.ioSer.data    := writeBuf;
        serWriteReq.ioSer.length  := n;
        serWriteReq.ioSer.command := e.write;
        ioErr := e.DoIO(serWriteReq);
        IF config.duplex=halfduplex THEN
          conWriteReq.length  := n;
          conWriteReq.data    := writeBuf;
          conWriteReq.command := e.write;
          e.OldDoIO(conWriteReq);
          Capture(writeBuf,n);
        END;
      END;

      conReadReq.data   := writeBuf;
      conReadReq.length := writeBufLen;
      conReadReq.command := e.read;
      e.SendIO(conReadReq);
    END;

    IF serReadPort.sigBit IN sigRec THEN
      ioErr := e.WaitIO(serReadReq);
      reading := FALSE;

      (* $IF debug *)
      IF ioErr#0 THEN d.PrintF("ioErr=%ld\n",ioErr) END;
      (* $END *)

      n := serReadReq.ioSer.actual;

      serReadReq.ioSer.command := se.query;
      ioErr := e.DoIO(serReadReq);

      IF serReadReq.ioSer.actual>0 THEN
        serReadReq.ioSer.data   := SYS.ADR(readBuf[n]);
        serReadReq.ioSer.length := Min(serReadReq.ioSer.actual,readBufLen-n);
        serReadReq.ioSer.command := e.read;
        ioErr := e.DoIO(serReadReq);
        INC(n,serReadReq.ioSer.actual);

        (* $IF debug *)
        IF ioErr#0 THEN d.PrintF("ioErr=%ld\n",ioErr) END;
        (* $END *)
      END;

      IF (XPR.base#NIL) & (XPR.hostmon IN xprFlags) THEN
        n := XPR.XProtocolHostMon(xprio^,readBuf,n,readBufLen);
        IF transferWin # NIL THEN CloseTransferWindow END;
      END;

      IF n > 0 THEN
        IF ie.lAlt IN SYS.VAL(SET,in.PeekQualifier()) THEN
          conWriteReq.length := 1;
          conWriteReq.data := SYS.ADR(".")
        ELSE
          conWriteReq.length  := n;
          conWriteReq.data    := readBuf;
        END;
        conWriteReq.command := e.write;
        e.OldDoIO(conWriteReq);
  
        Capture(readBuf,n);
      END;

      SendReadReq;

    END;
  END;

CLOSE

  IF statusRunning     THEN SYS.SETREG(0,e.Wait(LONGSET{mySig})); e.RemTask(statusTask) END;
  IF XPR.base    # NIL THEN XPR.XProtocolCleanup(xprio^); e.CloseLibrary(XPR.base) END;
  IF transferWin # NIL THEN I.CloseWindow(transferWin)    END;
  IF cap               THEN IF fs.Close(capture) THEN END END;
  IF conReadPort # NIL THEN e.DeleteMsgPort(conReadPort)  END;
  IF conWriteReq # NIL THEN ds.CloseDev(conWriteReq)      END;
  IF statusWin   # NIL THEN I.CloseWindow(statusWin)      END;
  IF win         # NIL THEN IF win.menuStrip # NIL THEN I.ClearMenuStrip(win); END; I.CloseWindow(win) END;
  IF menu        # NIL THEN gt.FreeMenus(menu)            END;
  IF vi          # NIL THEN gt.FreeVisualInfo(vi)         END;
  IF scr         # NIL THEN I.OldCloseScreen(scr)         END;
  IF font        # NIL THEN g.CloseFont(font)             END;
  IF serReadPort # NIL THEN e.DeleteMsgPort(serReadPort)  END;
  IF serWriteReq # NIL THEN ds.CloseDev(serWriteReq)      END;
  IF serLocked & (odu.base # NIL) THEN odu.FreeDevUnit(config.device,config.unit) END;
  IF seg         # NIL THEN d.UnLoadSeg(seg)              END;
  IF timeReq     # NIL THEN ds.CloseDev(timeReq)          END;
  IF inputReq    # NIL THEN e.CloseDevice(inputReq)       END;
  IF topaz80     # NIL THEN g.CloseFont(topaz80)          END;
  IF mySig       #  -1 THEN e.FreeSignal(mySig)           END;
  IF rd          # NIL THEN d.FreeArgs(rd)                END;
  IF oldPri      #-999 THEN oldPri := e.SetTaskPri(me,oldPri) END;
  me.windowPtr := oldWP;

END Termite.

