DIRECTORY Ascii, Basics USING [BITAND], Commander USING [CommandProc, Lookup, Register], CommandExtras USING [MakeUninterpreted], CommandTool USING [ArgumentVector, Failed, Parse], EditedStream USING [SetEcho], FS USING [Error, StreamOpen], Graphics USING [Context, DrawBox, SetPaintMode], GraphicsBasic, IO, IOClasses USING [CreateDribbleOutputStream], List USING [Length, Remove], Loader USING [BCDBuildTime], Menus USING [AppendMenuEntry, CreateEntry, FindEntry, MenuEntry, MenuProc, ReplaceMenuEntry], Process USING [Detach, MsecToTicks, Pause], PupDefs USING [PupAddress, PupPackageMake, PupPackageDestroy], PupStream USING [ConsumeMark, GetPupAddress, PupByteStreamCreate, PupNameTrouble, SecondsToTocks, SendMark, StreamClosing, TimeOut], PupTypes USING [telnetSoc], Rope USING [Cat, Fetch, Find, Length, ROPE, Substr], TiogaOps USING [GetCaret, GetRope, Location], TIPUser USING [InstantiateNewTIPTable, RegisterTIPPredicate, TIPPredicate, TIPTable], TypeScript USING [BackSpace, ChangeLooks, Create, InsertCharAtFrontOfBuffer, TS], UserCredentials USING [Get], ViewerClasses, ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc], ViewerIO USING [CreateViewerStreams], ViewerOps USING [AcquireContext, AddProp, DestroyViewer, FetchProp, PaintViewer, ReleaseContext, XYWH2Box], ViewerTools USING [GetSelectedViewer, GetSelectionContents, SetSelection]; Chat: CEDAR MONITOR LOCKS h.LOCK USING h: Handle IMPORTS Basics, Commander, CommandExtras, CommandTool, EditedStream, FS, Graphics, IO, IOClasses, List, Loader, Menus, Process, PupDefs, PupStream, Rope, TiogaOps, TIPUser, TypeScript, UserCredentials, ViewerEvents, ViewerIO, ViewerOps, ViewerTools SHARES Menus, ViewerClasses, ViewerOps = BEGIN Handle: TYPE = REF ChatInstanceRecord; State: TYPE = {idle, starting, running, closing, destroy}; DisconnectChar: CHAR = 220C; AbortChar: CHAR = 221C; ConnectChar: CHAR = 222C; LoginChar: CHAR = 223C; RemoteCloseChar: CHAR = 224C; MarkByte: TYPE = [0..256); setLineWidth: MarkByte = 2; setPageLength: MarkByte = 3; timingMark: MarkByte = 5; timingMarkReply: MarkByte = 6; ChatInstanceRecord: TYPE = MONITORED RECORD [ ts: TypeScript.TS, -- the primary typescript state: State _ idle, lorc: CHAR _ 'c, logFileName: Rope.ROPE, logStream: IO.STREAM, debugging: BOOL _ FALSE, rawLogFileName: Rope.ROPE, rawLogStream: IO.STREAM, keyFile: Rope.ROPE, argv: CommandTool.ArgumentVector, pleaseStop: BOOL _ FALSE, uToSStopped: BOOL _ FALSE, sToUStopped: BOOL _ FALSE, inDestroy: BOOL _ FALSE, serverToUserProcess: PROCESS, userToServerProcess: PROCESS, serverName: Rope.ROPE _ "Ivy", useOldHost: BOOL _ FALSE, keyStream: IO.STREAM, destroyOnClose: BOOL _ FALSE, synchronous: BOOL _ FALSE, in: IO.STREAM, origOut: IO.STREAM, out: IO.STREAM, tipTable: TIPUser.TIPTable, serverStream: IO.STREAM _ NIL, oldSplit: Menus.MenuEntry _ NIL ]; logFileNumber: INT _ 0; chatInstanceList: LIST OF REF ANY _ NIL; destroyEvent: ViewerEvents.EventRegistration _ NIL; closeEvent: ViewerEvents.EventRegistration _ NIL; chatTipTable: TIPUser.TIPTable; ServerToUser: PROC [h: Handle] = BEGIN c: CHAR; mySST: MarkByte; DO ENABLE { ABORTED => GOTO Cleanup; PupStream.StreamClosing => { IF NOT h.pleaseStop THEN TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: RemoteCloseChar]; GOTO Cleanup; }; IO.Error => GOTO Cleanup; }; IF h.serverStream.EndOf[] THEN { mySST _ PupStream.ConsumeMark[h.serverStream]; IF mySST = timingMark THEN PupStream.SendMark[h.serverStream, timingMarkReply]; }; c _ h.serverStream.GetChar[! PupStream.TimeOut => { IF h.pleaseStop OR h.state # running THEN GOTO Cleanup ELSE RESUME; }; IO.EndOfStream => { LOOP; } ]; IF h.pleaseStop OR h.state # running THEN GOTO Cleanup; IF h.debugging THEN { h.rawLogStream.PutChar[c]; h.rawLogStream.Flush[]; }; c _ LOOPHOLE[Basics.BITAND[LOOPHOLE[c, CARDINAL], 177B], CHAR]; SELECT c FROM Ascii.BEL => Flash[h]; Ascii.ControlA, Ascii.BS => TypeScript.BackSpace[h.ts]; Ascii.TAB, IN[Ascii.SP..0176C] => h.out.PutChar[c]; Ascii.LF => h.out.PutChar[Ascii.CR]; ENDCASE => NULL; ENDLOOP; EXITS Cleanup => h.sToUStopped _ TRUE; END; Flash: PROC [h: Handle] = { context: Graphics.Context _ ViewerOps.AcquireContext[h.ts, IF h.ts.iconic THEN FALSE ELSE h.ts.column = color]; [] _ Graphics.SetPaintMode[context, invert]; Graphics.DrawBox[context, ViewerOps.XYWH2Box[0, 0, h.ts.ww, h.ts.wh]]; Process.Pause[Process.MsecToTicks[100]]; Graphics.DrawBox[context, ViewerOps.XYWH2Box[0, 0, h.ts.ww, h.ts.wh]]; ViewerOps.ReleaseContext[context]; }; InitialNegotiations: PROC [h: Handle] = TRUSTED { PupStream.SendMark[h.serverStream, setLineWidth]; h.serverStream.PutChar[0C]; h.serverStream.Flush[]; IF h.lorc='l OR h.lorc='x THEN { name, password: Rope.ROPE; [name: name, password: password] _ UserCredentials.Get[]; h.serverStream.PutRope["Login "]; h.serverStream.PutRope[name]; IF h.lorc='l AND Rope.Find[s1: name, s2: "."] = -1 THEN h.serverStream.PutRope[".PA"]; h.serverStream.PutRope[" "]; h.serverStream.PutRope[password]; h.serverStream.PutRope[" \n"]; h.serverStream.Flush[]; }; }; FinishStringWithErrorMsg: PROC [h: Handle, errorMsg: Rope.ROPE] = { IF errorMsg # NIL THEN h.out.PutF[": %s.\n", IO.rope[errorMsg]] ELSE h.out.PutF[".\n"]; }; FromKeys: PROC [h: Handle] = TRUSTED { count: NAT _ 0; { WHILE h.keyStream # NIL AND ~h.keyStream.EndOf[] DO IF h.pleaseStop OR h.state # starting THEN GOTO CloseKeyStream; h.serverStream.PutChar[h.keyStream.GetChar[]]; count _ count + 1; IF count >= 50 THEN { h.serverStream.Flush[]; count _ 0; }; ENDLOOP; GOTO CloseKeyStream; EXITS CloseKeyStream => { IF h.keyStream # NIL THEN{ h.keyStream.Close[]; h.keyStream _ NIL; }; IF count # 0 THEN h.serverStream.Flush[]; }; }; }; StartUp: PROC [h: Handle] = BEGIN ENABLE UNWIND => h.state _ idle; h.state _ starting; IF NOT h.useOldHost OR h.serverName.Length[] = 0 THEN h.serverName _ FindHostName[h]; h.useOldHost _ FALSE; h.pleaseStop _ FALSE; h.sToUStopped _ FALSE; h.uToSStopped _ FALSE; ViewerTools.SetSelection[h.ts, NIL]; TRUSTED { h.out.PutF["\nViewers Chat of %t.\n", IO.time[Loader.BCDBuildTime[FindHostName]]]; }; IF h.logStream # NIL THEN h.out.PutF["Log file: %g\n", IO.rope[h.logFileName]] ELSE h.out.PutF["No log file.\n"]; OpenConnection[h]; IF h.serverStream = NIL THEN { h.state _ idle; RETURN; }; SetName[h, Rope.Cat["Chat ", h.serverName]]; InitialNegotiations[h]; FromKeys[h]; h.state _ running; h.serverToUserProcess _ FORK ServerToUser[h]; END; FindHostName: PROC [h: Handle] RETURNS [host: Rope.ROPE] = { caret: TiogaOps.Location; r: Rope.ROPE; i: INT; r _ ViewerTools.GetSelectionContents[]; IF r.Length[] > 1 THEN RETURN[r]; caret _ TiogaOps.GetCaret[]; r _ TiogaOps.GetRope[caret.node]; i _ caret.where; WHILE i > 0 DO char: CHARACTER = Rope.Fetch[r, i - 1]; IF -- char # '* AND -- NOT ChatTokenProc[char] = other THEN EXIT; i _ i -1; ENDLOOP; host _ Rope.Substr[base: r, start: i, len: caret.where - i]; }; ChatTokenProc: IO.BreakProc -- [char: CHAR] RETURNS [IO.CharClass] -- = TRUSTED { IF IO.TokenProc[char] = other THEN RETURN [other]; IF char = '+ THEN RETURN [other]; RETURN [sepr]; }; OpenConnection: PROC [h: Handle] = TRUSTED { addr: PupDefs.PupAddress; PupDefs.PupPackageMake[]; { h.out.PutF["Opening connection to %g ... ", IO.rope[h.serverName]]; addr _ PupStream.GetPupAddress[PupTypes.telnetSoc, h.serverName ! PupStream.PupNameTrouble => { h.out.PutF["PUP name error"]; FinishStringWithErrorMsg[h, e]; GOTO Return; } ]; h.serverStream _ PupStream.PupByteStreamCreate[addr, PupStream.SecondsToTocks[1] ! PupStream.StreamClosing => { h.out.PutF["Can't connect"]; FinishStringWithErrorMsg[h, text]; GOTO Return; } ]; h.out.PutF["open.\n"]; EXITS Return => NULL; }; }; CloseConnection: PROC [h: Handle, print: BOOL] = TRUSTED { h.pleaseStop _ TRUE; IF h.serverStream # NIL THEN { IF print THEN h.out.PutF["\nClosing connection to %s", IO.rope[h.serverName] ! IO.Error => CONTINUE]; h.serverStream.Close[]; h.serverStream _ NIL; -- could cause pointer faults! IF print THEN h.out.PutF[" ... Closed\n" ! IO.Error => CONTINUE]; PupDefs.PupPackageDestroy[]; }; IF h.keyStream # NIL THEN { h.keyStream.Close[]; h.keyStream _ NIL; }; IF h.state = running THEN JOIN h.serverToUserProcess; IF h.logStream # NIL THEN h.logStream.Flush[]; h.state _ idle; SetName[h, "Chat"]; }; SetName: PROC [h: Handle, r: Rope.ROPE] = { InternalSetName: PROC [v: ViewerClasses.Viewer] = { v.name _ r; ViewerOps.PaintViewer[viewer: v, hint: caption]; }; EnumerateSplits[h.ts, InternalSetName ! ANY => CONTINUE]; }; ChatMain: Commander.CommandProc = TRUSTED { h: Handle _ NEW[ChatInstanceRecord _ []]; execOut: IO.STREAM _ cmd.out; switchChar: CHAR; i: NAT _ 2; h.argv _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; CONTINUE; }]; IF h.argv = NIL THEN RETURN; h.lorc _ 'l; -- default GV login WHILE i < h.argv.argc DO IF h.argv[i].Length[] > 1 THEN SELECT h.argv[i].Fetch[0] FROM '- => { switchChar _ h.argv[i].Fetch[1]; SELECT switchChar FROM 'd => h.destroyOnClose _ TRUE; 'k => { IF i+1 < h.argv.argc THEN { h.keyStream _ IO.RIS[h.argv[i+1]]; i _ i + 1; }; }; 's => h.synchronous _ TRUE; 'D => { h.debugging _ TRUE; h.rawLogFileName _ IO.PutFR["Chat%d.rawLog", IO.int[logFileNumber]]; h.rawLogStream _ FS.StreamOpen[fileName: h.rawLogFileName, accessOptions: $create ! FS.Error => IF error.group = user THEN { execOut.PutF["Chat: Cannot open raw log %s\n", IO.rope[h.rawLogFileName]]; CONTINUE; } ]; }; ENDCASE => h.lorc _ switchChar; }; '> => h.logFileName _ Rope.Substr[base: h.argv[i], start: 1]; '< => h.keyFile _ Rope.Substr[base: h.argv[i], start: 1]; ENDCASE => execOut.PutF["chat: unknown command: %s.\n", IO.rope[h.argv[i]]]; i _ i + 1; ENDLOOP; IF h.logFileName.Length[] = 0 THEN { h.logFileName _ IO.PutFR["Chat%d.log", IO.int[logFileNumber]]; logFileNumber _ logFileNumber + 1; }; IF h.keyFile.Length[] > 0 THEN h.keyStream _ FS.StreamOpen[fileName: h.keyFile ! FS.Error => IF error.group = user THEN { execOut.PutF["Chat: Cannot open %s\n", IO.rope[h.keyFile]]; CONTINUE; }]; h.ts _ TypeScript.Create[info: [name: "Chat", iconic: h.argv.argc <= 1 OR h.lorc = 'i], paint: TRUE]; TypeScript.ChangeLooks[h.ts, 'f]; h.ts.file _ h.logFileName; h.ts.icon _ typescript; h.logStream _ FS.StreamOpen[fileName: h.logFileName, accessOptions: $create ! FS.Error => IF error.group = user THEN { execOut.PutF["Chat: Cannot open %s\n", IO.rope[h.logFileName]]; CONTINUE; }]; chatTipTable.link _ h.ts.tipTable; chatTipTable.opaque _ FALSE; h.tipTable _ h.ts.tipTable _ chatTipTable; [in: h.in, out: h.origOut] _ ViewerIO.CreateViewerStreams[name: "Chat.log", viewer: h.ts, editedStream: FALSE]; h.out _ h.origOut; IF h.logStream # NIL THEN h.out _ IOClasses.CreateDribbleOutputStream[output1: h.origOut, output2: h.logStream]; IF h.debugging THEN h.out.PutF["Debugging Mode On ... raw log on %g\n", IO.rope[h.rawLogFileName]]; EditedStream.SetEcho[h.in, NIL]; IF h.argv.argc > 1 THEN { h.serverName _ h.argv[1]; h.useOldHost _ TRUE; IF h.lorc = 'i THEN h.lorc _ 'c ELSE TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: IF h.lorc = 'c THEN ConnectChar ELSE LoginChar]; }; IF List.Length[chatInstanceList] = 0 THEN { destroyEvent _ ViewerEvents.RegisterEventProc[proc: MyDestroy, event: destroy] }; chatInstanceList _ CONS[h, chatInstanceList]; Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "Disconnect", proc: MyDisconnect, clientData: h, fork: TRUE, documentation: "Close Ethernet connection"]]; Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "Login", proc: MyLogin, clientData: h, documentation: "Open Ethernet Connection to selected host and send Login sequence"]]; Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "Connect", proc: MyConnect, clientData: h, documentation: "Open Ethernet Connection to selected host"]]; Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "BreakKey", proc: MyBreakKey, clientData: h, documentation: "Transmit Ascii.NULL (DLS interprets as RS-232 Line Break)"]]; Menus.AppendMenuEntry[menu: h.ts.menu, entry: Menus.CreateEntry[name: "FlushLog", proc: MyFlushLog, clientData: h, documentation: "Flush log file to disk."]]; h.oldSplit _ Menus.FindEntry[menu: h.ts.menu, entryName: "Split"]; Menus.ReplaceMenuEntry[menu: h.ts.menu, oldEntry: h.oldSplit, newEntry: Menus.CreateEntry[name: "Split", proc: MySplit, fork: TRUE, clientData: h, documentation: "Split window"]]; ViewerOps.AddProp[h.ts, $ChatToolData, h]; ViewerOps.PaintViewer[viewer: h.ts, hint: all]; IF h.synchronous THEN UserToServer[h] ELSE { h.userToServerProcess _ FORK UserToServer[h]; Process.Detach[h.userToServerProcess]; }; }; UserToServer: PROC [h: Handle] = TRUSTED {{ char: CHAR; count: NAT _ 0; DO ENABLE { ABORTED => ERROR; -- ever happen? IO.Error => { CloseConnection[h, FALSE]; GOTO Die; }; PupStream.StreamClosing => { IF h.serverStream # NIL THEN { h.out.PutF["\nConnection being closed by %s", IO.rope[h.serverName]]; FinishStringWithErrorMsg[h, text]; CloseConnection[h, FALSE]; IF h.destroyOnClose THEN { ViewerOps.DestroyViewer[h.ts]; GOTO Die; }; }; CONTINUE; }; }; char _ h.in.GetChar[]; IF h.inDestroy THEN { CloseConnection[h, FALSE]; GOTO Die; }; SELECT char FROM AbortChar => { IF h.state = running THEN CloseConnection[h, TRUE]; }; DisconnectChar => { IF h.state = running THEN CloseConnection[h, TRUE]; }; RemoteCloseChar => { ERROR PupStream.StreamClosing[why: remoteClose, text: NIL]; }; ConnectChar => { IF h.state = idle THEN { h.lorc _ 'c; StartUp[h]; count _ 0; }; }; LoginChar => { IF h.state = idle THEN { h.lorc _ 'l; StartUp[h]; count _ 0; }; }; ENDCASE => { SELECT h.state FROM running => { h.serverStream.PutChar[char]; IF count >= 50 OR h.in.CharsAvail[] = 0 THEN { h.serverStream.Flush[]; count _ 0; } ELSE count _ count + 1; }; ENDCASE => h.out.PutChar[char]; }; ENDLOOP; EXITS Die => { IF h.logStream # NIL THEN h.logStream.Close[]; IF h.debugging THEN h.rawLogStream.Close[]; }; }; }; MyDestroy: ViewerEvents.EventProc = { h: Handle _ NARROW[ViewerOps.FetchProp[viewer, $ChatToolData]]; IF h = NIL THEN RETURN; IF NumSplit[viewer] = 1 THEN { -- last one h.inDestroy _ TRUE; chatInstanceList _ List.Remove[h, chatInstanceList]; IF List.Length[chatInstanceList] = 0 THEN { ViewerEvents.UnRegisterEventProc[proc: destroyEvent, event: destroy]; }; } ELSE IF NumSplit[viewer] > 1 THEN { IF viewer = h.ts THEN { Another: PROC [v: ViewerClasses.Viewer] = { IF v # viewer THEN h.ts _ v; }; EnumerateSplits[viewer, Another]; }; }; }; MyClose: ViewerEvents.EventProc = { h: Handle _ NARROW[ViewerOps.FetchProp[viewer, $ChatToolData]]; IF h = NIL THEN RETURN; TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: DisconnectChar]; }; MyConnect: Menus.MenuProc = { viewer: TypeScript.TS _ NARROW[parent]; h: Handle _ NARROW[clientData]; h.ts _ viewer; -- "primary" copy h.useOldHost _ mouseButton # red; TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: ConnectChar]; }; MyLogin: Menus.MenuProc = { viewer: TypeScript.TS _ NARROW[parent]; h: Handle _ NARROW[clientData]; h.ts _ viewer; -- "primary" copy h.useOldHost _ mouseButton # red; TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: LoginChar]; }; MyDisconnect: Menus.MenuProc = { h: Handle _ NARROW[clientData]; TypeScript.InsertCharAtFrontOfBuffer[ts: h.ts, char: DisconnectChar]; }; MyBreakKey: Menus.MenuProc = TRUSTED { h: Handle _ NARROW[clientData]; IF h.state # running THEN RETURN; IF h.serverStream # NIL THEN { h.serverStream.PutChar['\000]; h.serverStream.Flush[]; }; }; MyFlushLog: Menus.MenuProc = { h: Handle _ NARROW[clientData]; IF h.logStream # NIL THEN h.logStream.Flush[]; }; MySplit: Menus.MenuProc = { h: Handle _ NARROW[clientData]; CheckChatProperties: PROC [v: ViewerClasses.Viewer] = { IF ViewerOps.FetchProp[v, $ChatToolData] = NIL THEN ViewerOps.AddProp[v, $ChatToolData, h]; v.tipTable _ h.tipTable; }; h.oldSplit.proc[parent: parent, clientData: h.oldSplit.clientData, mouseButton: mouseButton, shift: shift, control: control]; EnumerateSplits[NARROW[parent, ViewerClasses.Viewer], CheckChatProperties]; }; ConnectionOpen: TIPUser.TIPPredicate --PROC RETURNS [BOOLEAN]-- = { h: Handle; viewer: ViewerClasses.Viewer _ ViewerTools.GetSelectedViewer[]; IF viewer=NIL THEN RETURN [FALSE]; -- no primary selection h _ NARROW[ViewerOps.FetchProp[viewer, $ChatToolData]]; IF h=NIL THEN RETURN [FALSE]; -- not a chat tool RETURN [h.state = running]; -- connection open? }; EnumerateSplits: PROC [v: ViewerClasses.Viewer, p: PROC [v: ViewerClasses.Viewer]] = { v2: ViewerClasses.Viewer _ v; IF v = NIL THEN RETURN; DO p[v2]; IF v2.link = NIL OR v2.link = v THEN RETURN; v2 _ v2.link; ENDLOOP; }; NumSplit: PROC [v: ViewerClasses.Viewer] RETURNS [count: INT _ 0] = { Counter: PROC [v2: ViewerClasses.Viewer] = { count _ count + 1; }; EnumerateSplits[v, Counter]; }; Init: PROC = { Commander.Register[key: "Chat", proc: ChatMain, doc: "Pup User Telnet, see ChatDoc.tioga"]; CommandExtras.MakeUninterpreted[Commander.Lookup["Chat"]]; chatTipTable _ TIPUser.InstantiateNewTIPTable["Chat.TIP"]; TIPUser.RegisterTIPPredicate[$ConnectionOpen, ConnectionOpen]; }; Init[]; END. March 31, 1982 9:27 pm, Stewart, copied from Laurel Chat April 1, 1982 4:06 pm, Stewart, own viewer class & TIP table April 4, 1982 5:18 pm, Stewart, Menu April 6, 1982 10:27 pm, Stewart, command line stuff 18-Apr-82 17:42:24, Use TypeScript again June 6, 1982 4:44 pm, Stewart, fix Menu instantiation to add Split June 6, 1982 8:08 pm, Stewart, using own Split code until viewers copies PropList September 21, 1982 4:26 pm, Stewart, Cedar 3.4 November 3, 1982 5:53 pm, Warren Teitelman January 23, 1983 10:18 pm, Stewart, Cedar 3.5, major rework January 24, 1983 5:49 pm, Stewart, Cedar 3.6 January 25, 1983 3:09 pm, Maxwell April 6, 1983 6:43 pm, Larry Stewart June 13, 1983 1:41 pm, Stewart, Cedar 4.2, added synchronous feature September 7, 1983 6:19 pm, Stewart, Cedar 5 January 6, 1984 1:13 pm, Stewart, TypeScript.BackSpace on Control-A and BS March 4, 1984 4:15:26 pm PST, Pavel, Cleanup, flash on Control-G, grab input focus on start up and connection. March 11, 1984 5:59:56 pm PST, Pavel, Map received back-quotes (140C) into normal single quotes. March 12, 1984 1:14:30 pm PST, Pavel, Gacha font changed to add a back-quote, so previous hack removed. February 26, 1985 2:50:10 pm PST, Pavel, Added -D switch for making a raw log of characters received from the server. Also changed it to make a newline on receipt of LF instead of CR. fChat.mesa Larry Stewart, January 6, 1984 1:28 pm Warren Teitelman, November 3, 1982 5:53 pm Last Edited by: Maxwell, January 25, 1983 3:09 pm Last Edited by: Paul Rovner, November 29, 1983 10:56 am Last Edited by: Pavel Curtis on February 26, 1985 2:59:48 pm PST Last Edited by: Bill Jackson on November 1, 1984 5:46:27 pm PST This procedure is FORKed by the UserToServer process at the time a connection is opened. It is JOINED whenever the connection is closed. It also goes away if the Chat viewer is destroyed. UserAbort is active because Chat.TIP is not activated until h.state = running (due to the TIP predicate), so control-DEL sets UserAbort. However, while in this routine, UserAbort is used only to stop taking characters from the keyStream, not to close the connection. If the user types control-DEL here, she will have to type it again to really close the connection. This procedure includes '+ in order to handle Pup names of the form dls+100004 iconic unless command line not empty create log file plug in Chat TIP table. IF -i then we are just initializing the host name, else really connect closeEvent _ ViewerEvents.RegisterEventProc[proc: MyClose, event: close]; last viewer destroyed ! Close connection and exit This EventProc exits only to keep h.ts pointing to a valid copy of the typescript viewer. It is only needed for the use of TypeScript.InsertCharAtFrontOfBuffer. next line is a crock to avoid signal in TypeScripts, see McGregor Process.Pause[Process.SecondsToTicks[2]]; h.out.Close[]; breaks viewers destroy!! ViewerEvents.UnRegisterEventProc[proc: closeEvent, event: close]; main program for chat ÊW˜Jšœ ™ Jšœ&™&Jšœ*™*Jšœ1™1Jšœ7™7Jšœ@™@Jšœ?™?J˜šÏk ˜ Jšœ˜Jšœœœ˜Jšœ œ!˜0Jšœœ˜(Jšœ œ!˜2Jšœ œ ˜Jšœœ˜Jšœ œ"˜0J˜Jšœ˜Jšœ œ˜,Jšœœ˜Jšœœ˜JšœœR˜]Jšœœ˜+Jšœœ1˜>Jšœ œu˜„Jšœ œ ˜Jšœœœ ˜4Jšœ œ˜-JšœœH˜UJšœ œ=œ˜QJšœœ˜J˜Jšœ œH˜ZJšœ œ˜%Jšœ œ\˜kšœ œ9˜JJ˜——Jš Ðbxœœœœœœ ˜0šœ/˜6Jšœœ œ!˜?J˜J˜Jšœ˜J˜.—Jšœ"˜(Jš˜J˜Jšœœœ˜&Jšœœ/˜:J˜Jšœœ˜Jšœ œ˜Jšœ œ˜Jšœ œ˜Jšœœ˜Jšœ œ ˜J˜J˜J˜J˜J˜šœœ œœ˜-JšœœÏc˜,J˜Jšœœ˜Jšœœ˜Jšœ œœ˜Jšœ œœ˜Jšœœ˜Jšœœœ˜Jšœœ˜Jšœ!˜!Jšœ œœ˜Jšœ œœ˜Jšœ œœ˜Jšœ œœ˜Jšœœ˜Jšœœ˜Jšœœ ˜Jšœ œœ˜Jšœ œœ˜Jšœœœ˜Jšœ œœ˜Jšœœœ˜Jšœ œœ˜Jšœœœ˜J˜Jšœœœœ˜Jšœ˜J˜J˜—Jšœœ˜Jš œœœœœœ˜(Jšœ/œ˜3Jšœ-œ˜1J˜J˜JšœB™BJšœF™FJšœ2™2J˜šÏn œœ˜ š˜Jšœœ˜J˜š˜š˜šœ˜šœ˜ Jšœ ˜ —˜˜šœœ˜JšœF˜F—Jšœ ˜ —J˜—šœ ˜ Jšœ ˜ ——J˜—šœ˜šœ˜J˜.Jšœœ5˜O—J˜—˜˜˜šœœ˜)Jšœ˜ —š˜Jšœ˜——J˜—šœ˜Jšœœ˜ —Jšœ˜—šœœ˜)Jšœ ˜ —šœ œ˜J˜J˜J˜—Jš œœœœœ œ˜?šœ˜ Jšœœ ˜Jšœœ˜7Jšœœœœ˜3Jšœœœ˜$Jšœœ˜——Jšœ˜Jš˜Jšœœ˜ —Jšœ˜—J˜š œœ˜Jš œ;œ œœœ˜oJ˜,JšœF˜FJ˜(JšœF˜FJšœ"˜"J˜—J˜š œœœ˜1J˜2J˜J˜šœ œ œ˜ Jšœœ˜J˜9J˜!J˜Jšœ œ#œ˜VJ˜J˜!J˜J˜J˜—J˜J˜—š œœœ˜CJšœ œœœ˜?Jšœ˜J˜J˜—JšœU™UJšœ2™2JšœU™UJšœT™TJšœ:™:J˜š œœœ˜&Jšœœ˜J˜šœœœ˜3Jšœœœœ˜?J˜.J˜šœ œ˜J˜J˜ J˜—Jšœ˜—Jšœ˜Jš˜˜šœœœ˜J˜Jšœœ˜J˜—Jšœ œ˜)J˜—J˜J˜J˜—š œœ˜!Jšœœ˜ J˜Jšœœœœ ˜UJšœœ˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœœ˜$šœ˜ Jšœ&œ*˜RJ˜—šœœ˜Jšœœ˜4—š˜Jšœ˜—J˜šœœœ˜J˜Jšœ˜J˜—J˜,J˜J˜ J˜Jšœœ˜-Jšœ˜J˜—š  œœ œ œ˜˜@šœœ ˜ šœœ˜Kšœ/œ˜JJšœ˜ K˜——K˜—K˜—Jšœ˜—J˜—J˜=J˜9Jšœ1œ˜L——J˜ Jšœ˜—šœœ˜$Jšœœœ˜>J˜"J˜J˜—š œœœ"œ œœ˜yJšœ'œ˜;Jšœ˜ J˜—Jšœ$™$JšœGœœ˜eJ˜!J˜J˜Jšœ™š œœ>œ œœ˜vJšœ'œ˜?Jšœ˜ J˜J˜—Jšœ™J˜"Jšœœ˜J˜*Jšœhœ˜oJ˜šœœ˜JšœV˜V—šœ œ˜J˜O—Jšœœ˜ šœœ˜J˜Jšœœ˜JšœF™FJšœ œ ˜Jšœ6œ œ œ ˜jJ˜—šœ#œ˜+JšœI™IJ˜NJ˜—Jšœœ˜-Jšœ}œ/˜°J˜ÂJ˜®J˜ÀJ˜žJ˜J˜BJšœ~œ1˜³J˜*J˜/Jšœœ˜%šœ˜Jšœœ˜-J˜&J˜—J˜J˜š  œœœ˜+Jšœœ˜ Jšœœ˜šœœ˜ JšœœŸ˜"šœ ˜ Jšœ™Jšœ™Jšœœ˜Jšœ˜ J˜—˜šœœœ˜Jšœ.œ˜EJ˜"Jšœœ˜šœœ˜J˜Jšœ˜ J˜—J˜—Jšœ˜ J˜—J˜J˜šœ œ˜Jšœœ˜Jšœ˜ J˜—šœ˜˜Jšœœœ˜3J˜—˜Jšœœœ˜3J˜—˜Jšœ1œ˜;J˜—˜šœœ˜J˜ J˜ J˜ J˜—J˜—˜šœœ˜J˜ J˜ J˜ J˜—J˜—šœ˜ šœ ˜˜ J˜šœ œœ˜.J˜J˜ J˜—Jšœ˜J˜—Jšœ˜—J˜——Jšœ˜—Jš˜˜Jšœœœ˜.Jšœ œ˜+J˜—J˜J˜J˜—JšœQ™QJšœO™OJ˜˜%Jšœ œ-˜?Jšœœœœ˜šœœŸ ˜*Jšœœ˜JšœA™AJšœ)™)Jšœ'™'J˜4šœ#œ˜+J˜EJšœA™AJ˜—J˜—šœœœ˜#šœœ˜š œœ˜+Jšœ œ ˜J˜—J˜!J˜—J˜—J˜J˜—˜#Jšœ œ-˜?Jšœœœœ˜J˜EJ˜J˜—˜Jšœœœ ˜'Jšœ œ ˜JšœŸ˜ J˜"J˜BJ˜J˜—˜Jšœœœ ˜'Jšœ œ ˜JšœŸ˜ J˜"J˜@J˜J˜—˜ Jšœ œ ˜J˜EJ˜J˜—šœœ˜&Jšœ œ ˜Jšœœœ˜!šœœœ˜J˜J˜J˜—J˜J˜—˜Jšœ œ ˜Jšœœœ˜.J˜J˜——š œ˜Jšœ œ ˜š œœ˜7Jšœ)œœ(˜[J˜J˜—J˜}Jšœœ5˜KJ˜J˜—š œŸœ˜CJ˜ J˜?Jš œœœœœŸ˜:Jšœœ-˜7Jš œœœœœŸ˜0JšœŸ˜/J˜J˜—š œœœ˜VJ˜Jšœœœœ˜š˜J˜Jš œ œœ œœ˜,J˜ Jšœ˜—J˜J˜—š œœœ œ ˜Eš œœ˜,J˜J˜—J˜J˜J˜—š œœ˜J˜[Jšœ:˜:J˜:J˜>J˜J˜—Jšœ™J˜J˜J˜Jšœ˜J˜J˜8Jšœ3œ˜