-- File: GateWatcherTool.mesa, Last Edit: HGM January 30, 1981 3:19 PM -- Please don't forget to update the herald.... DIRECTORY InlineDefs USING [BcplLongNumber, BcplToMesaLongNumber], Process USING [Detach, Yield], Storage USING [Node, String, Free, FreeNodeNil, FreeString], String USING [ SubStringDescriptor, AppendString, AppendChar, AppendNumber, AppendLongNumber], Time USING [AppendCurrent, Current], BitBlt USING [], -- Needed by DisplaySubstring Event USING [Item, Reason, AddNotifier], FormSW USING [ AllocateItemDescriptor, ClientItemsProcType, CommandItem, Display, FindItem, newLine, ProcType, StringItem], MsgSW USING [Post], Tool USING [Create, MakeSWsProc, MakeMsgSW, MakeFormSW, AddThisSW], ToolWindow USING [TransitionProcType, DisplayProcType, CreateSubwindow], Window USING [Box, DisplaySubstring, DisplayWhite, Handle, Place], WindowFont USING [FontHeight], BootServerDefs USING [bootStatsRequest, bootStatsReply, BootStatsEntry], EchoServerDefs USING [echoStatsRequest, echoStatsReply, EchoStatsEntry], ForwarderDefs USING [ forwarderStatsRequest, forwarderStatsReply, ForwardStatsEntry, TransitMatrixEntry], GateControlDefs USING [ gateControlStatsSend, gateControlStatsAck, GateControlStatsEntry], NameServerDefs USING [nameStatsRequest, nameStatsReply, NameStatsEntry], TimeServerDefs USING [timeStatsRequest, timeStatsReply, TimeStatsEntry], PupDefs USING [ PupPackageMake, PupPackageDestroy, AppendErrorPup, GetFreePupBuffer, ReturnFreePupBuffer, PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake, PupSocketKick, SecondsToTocks, SetPupContentsWords, GetPupContentsBytes, AppendPupAddress, GetPupAddress, PupNameTrouble], PupTypes USING [ PupAddress, fillInSocketID, fillInPupAddress, gatewaySoc, miscSrvSoc, echoSoc]; GateWatcherTool: MONITOR IMPORTS InlineDefs, Process, Storage, String, Time, Event, FormSW, MsgSW, Tool, ToolWindow, Window, WindowFont, PupDefs = BEGIN OPEN PupDefs, PupTypes; msg, form, info: Window.Handle ← NIL; eventItem: Event.Item ← [eventMask: 177777B, eventProc: Broom]; -- Be sure to initialize it when it is allocated!!!! data: POINTER TO Data ← NIL; maxLines: CARDINAL = 25; Data: TYPE = RECORD [ pleaseStop: BOOLEAN ← FALSE, alighTheSillyFink: WORD ← 0, running: BOOLEAN ← FALSE, him: PupAddress ← PupTypes.fillInPupAddress, target: STRING ← NIL, fastBoot, slowBoot, newBoot, echo, name, dirsSent, route, time: LONG CARDINAL ← 0, bootSoc, echoSoc, gcSoc, nameSoc, routeSoc, timeSoc: PupSocket ← NIL, activeProcesses: CARDINAL ← 0, height: CARDINAL ← 20, line: CARDINAL ← 0, lines: ARRAY [0..maxLines) OF STRING ← ALL[NIL]]; Init: PROCEDURE = BEGIN [] ← Tool.Create[ name: "Gate Watcher of November 13, 1980"L, makeSWsProc: MakeSWs, clientTransition: ClientTransition]; Event.AddNotifier[@eventItem]; END; GateWatcherOn: PROCEDURE = BEGIN IF data.running THEN RETURN; ZapCounters[]; ClearThings[]; data.pleaseStop ← FALSE; IF ~FindPath[] THEN RETURN; data.activeProcesses ← 6; Process.Detach[FORK Boot[]]; Process.Detach[FORK Echo[]]; Process.Detach[FORK Name[]]; Process.Detach[FORK Route[]]; Process.Detach[FORK Timer[]]; Process.Detach[FORK GateControl[]]; data.running ← TRUE; UpdatePicture[]; END; GateWatcherOff: PROCEDURE = BEGIN IF data = NIL THEN RETURN; IF ~data.running THEN RETURN; WaitUntilStopped[]; data.running ← FALSE; UpdatePicture[]; END; UpdatePicture: PROCEDURE = BEGIN IF form = NIL THEN RETURN; FormSW.FindItem[form, startIX].flags.invisible ← data.running; FormSW.FindItem[form, stopIX].flags.invisible ← ~data.running; FormSW.Display[form]; END; WaitUntilStopped: PROCEDURE = BEGIN IF data = NIL THEN RETURN; data.pleaseStop ← TRUE; PupSocketKick[data.bootSoc]; PupSocketKick[data.echoSoc]; PupSocketKick[data.gcSoc]; PupSocketKick[data.nameSoc]; PupSocketKick[data.routeSoc]; PupSocketKick[data.timeSoc]; WHILE data.activeProcesses # 0 DO Process.Yield[]; ENDLOOP; END; Boot: PROCEDURE = BEGIN OPEN data; b: PupBuffer; packetNumber: CARDINAL ← LAST[CARDINAL]; where: PupAddress ← him; where.socket ← PupTypes.miscSrvSoc; bootSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]]; UNTIL pleaseStop DO b ← GetFreePupBuffer[]; b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1); b.pupType ← BootServerDefs.bootStatsRequest; SetPupContentsWords[b, 0]; bootSoc.put[b]; UNTIL (b ← bootSoc.get[]) = NIL DO -- Until timeout, or we find the expected one SELECT TRUE FROM (b.pupType = error) => ShowErrorPup[b]; (b.pupType = BootServerDefs.bootStatsReply) AND (b.pupID.a = packetNumber AND b.pupID.b = packetNumber) => BEGIN bse: LONG POINTER TO BootServerDefs.BootStatsEntry = LOOPHOLE[@b.pupBody]; data.fastBoot ← InlineDefs.BcplToMesaLongNumber[bse.fastSends]; data.slowBoot ← InlineDefs.BcplToMesaLongNumber[bse.slowSends]; data.newBoot ← InlineDefs.BcplToMesaLongNumber[bse.filesRecv]; END; ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END; ReturnFreePupBuffer[b]; ENDLOOP; IF b # NIL THEN ReturnFreePupBuffer[b] ENDLOOP; PupSocketDestroy[bootSoc]; data.activeProcesses ← data.activeProcesses - 1; END; Echo: PROCEDURE = BEGIN OPEN data; b: PupBuffer; packetNumber: CARDINAL ← LAST[CARDINAL]; where: PupAddress ← him; where.socket ← PupTypes.echoSoc; echoSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]]; UNTIL pleaseStop DO b ← GetFreePupBuffer[]; b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1); b.pupType ← EchoServerDefs.echoStatsRequest; SetPupContentsWords[b, 0]; echoSoc.put[b]; UNTIL (b ← echoSoc.get[]) = NIL DO -- Until timeout, or we find the expected one SELECT TRUE FROM (b.pupType = error) => ShowErrorPup[b]; (b.pupType = EchoServerDefs.echoStatsReply) AND (b.pupID.a = packetNumber AND b.pupID.b = packetNumber) => BEGIN ese: LONG POINTER TO EchoServerDefs.EchoStatsEntry = LOOPHOLE[@b.pupBody]; data.echo ← InlineDefs.BcplToMesaLongNumber[ese.pupsEchoed]; END; ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END; ReturnFreePupBuffer[b]; ENDLOOP; IF b # NIL THEN ReturnFreePupBuffer[b] ENDLOOP; PupSocketDestroy[echoSoc]; data.activeProcesses ← data.activeProcesses - 1; END; Name: PROCEDURE = BEGIN OPEN data; b: PupBuffer; packetNumber: CARDINAL ← LAST[CARDINAL]; where: PupAddress ← him; where.socket ← PupTypes.miscSrvSoc; nameSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]]; UNTIL pleaseStop DO b ← GetFreePupBuffer[]; b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1); b.pupType ← NameServerDefs.nameStatsRequest; SetPupContentsWords[b, 0]; nameSoc.put[b]; UNTIL (b ← nameSoc.get[]) = NIL DO -- Until nameout, or we find the expected one SELECT TRUE FROM (b.pupType = error) => ShowErrorPup[b]; (b.pupType = NameServerDefs.nameStatsReply) AND (b.pupID.a = packetNumber AND b.pupID.b = packetNumber) => BEGIN nse: LONG POINTER TO NameServerDefs.NameStatsEntry = LOOPHOLE[@b.pupBody]; data.name ← InlineDefs.BcplToMesaLongNumber[nse.nameRequests]; data.dirsSent ← InlineDefs.BcplToMesaLongNumber[nse.directoriesSend]; END; ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END; ReturnFreePupBuffer[b]; ENDLOOP; IF b # NIL THEN ReturnFreePupBuffer[b] ENDLOOP; PupSocketDestroy[nameSoc]; data.activeProcesses ← data.activeProcesses - 1; END; Timer: PROCEDURE = BEGIN OPEN data; b: PupBuffer; packetNumber: CARDINAL ← LAST[CARDINAL]; where: PupAddress ← him; where.socket ← PupTypes.miscSrvSoc; timeSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]]; UNTIL pleaseStop DO b ← GetFreePupBuffer[]; b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1); b.pupType ← TimeServerDefs.timeStatsRequest; SetPupContentsWords[b, 0]; timeSoc.put[b]; UNTIL (b ← timeSoc.get[]) = NIL DO -- Until timeout, or we find the expected one SELECT TRUE FROM (b.pupType = error) => ShowErrorPup[b]; (b.pupType = TimeServerDefs.timeStatsReply) AND (b.pupID.a = packetNumber AND b.pupID.b = packetNumber) => BEGIN tse: LONG POINTER TO TimeServerDefs.TimeStatsEntry = LOOPHOLE[@b.pupBody]; data.time ← InlineDefs.BcplToMesaLongNumber[tse.tenexRequests] + InlineDefs.BcplToMesaLongNumber[tse.stringRequests] + InlineDefs.BcplToMesaLongNumber[tse.altoRequests]; END; ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END; ReturnFreePupBuffer[b]; ENDLOOP; IF b # NIL THEN ReturnFreePupBuffer[b] ENDLOOP; PupSocketDestroy[timeSoc]; data.activeProcesses ← data.activeProcesses - 1; END; Route: PROCEDURE = BEGIN OPEN data; b: PupBuffer; packetNumber: CARDINAL ← LAST[CARDINAL]; where: PupAddress ← him; where.socket ← PupTypes.gatewaySoc; routeSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[5]]; UNTIL pleaseStop DO b ← GetFreePupBuffer[]; b.pupID.a ← b.pupID.b ← (packetNumber ← packetNumber + 1); b.pupType ← ForwarderDefs.forwarderStatsRequest; SetPupContentsWords[b, 0]; routeSoc.put[b]; UNTIL (b ← routeSoc.get[]) = NIL DO -- Until timeout, or we find the expected one SELECT TRUE FROM (b.pupType = error) => ShowErrorPup[b]; (b.pupType = ForwarderDefs.forwarderStatsReply) AND (b.pupID.a = packetNumber AND b.pupID.b = packetNumber) => BEGIN OPEN ForwarderDefs; fse: LONG POINTER TO ForwardStatsEntry = LOOPHOLE[@b.pupBody]; l: CARDINAL ← PupDefs.GetPupContentsBytes[b]/2; n: CARDINAL ← fse.numberOfNetworks; p: CARDINAL ← (l - n - SIZE[ForwardStatsEntry])/SIZE[ForwardStatsEntry]; data.route ← InlineDefs.BcplToMesaLongNumber[fse.routingInfoRequests]; PrintForwardingStats[ nets: DESCRIPTOR[@b.pupBody + SIZE[ForwardStatsEntry], n], pupStatsTable: DESCRIPTOR[ @b.pupBody + SIZE[ForwardStatsEntry] + n, p]]; END; ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END; ReturnFreePupBuffer[b]; ENDLOOP; IF b # NIL THEN ReturnFreePupBuffer[b] ENDLOOP; PupSocketDestroy[routeSoc]; data.activeProcesses ← data.activeProcesses - 1; END; PrintForwardingStats: ENTRY PROCEDURE [ nets: LONG DESCRIPTOR FOR ARRAY OF CARDINAL, pupStatsTable: LONG DESCRIPTOR FOR ARRAY OF ForwarderDefs.TransitMatrixEntry] = BEGIN FindFirst: INTERNAL PROCEDURE [from, to: CARDINAL] = BEGIN tme: LONG POINTER TO ForwarderDefs.TransitMatrixEntry; FOR i: CARDINAL IN [0..LENGTH[pupStatsTable]) DO tme ← @pupStatsTable[i]; IF from # tme.sourceNet OR to # tme.destNet THEN LOOP; LD10Dash[InlineDefs.BcplToMesaLongNumber[tme.count]]; EXIT; REPEAT FINISHED => LD10Dash[0]; ENDLOOP; END; FindSecond: INTERNAL PROCEDURE [from, to: CARDINAL] = BEGIN i: CARDINAL; tme: LONG POINTER TO ForwarderDefs.TransitMatrixEntry; FOR i ← 0, i + 1 UNTIL i = LENGTH[pupStatsTable] DO tme ← @pupStatsTable[i]; IF from # tme.sourceNet OR to # tme.destNet THEN LOOP; EXIT; -- Skip first matching entry REPEAT FINISHED => BEGIN LD10Dash[0]; RETURN; END; ENDLOOP; FOR i ← i + 1, i + 1 UNTIL i = LENGTH[pupStatsTable] DO tme ← @pupStatsTable[i]; IF from # tme.sourceNet OR to # tme.destNet THEN LOOP; LD10Dash[InlineDefs.BcplToMesaLongNumber[tme.count]/1000]; EXIT; REPEAT FINISHED => LD10Dash[0]; ENDLOOP; END; SetLinePointer[6]; WriteLine["Packets forwarded:"L]; WriteLine["from to"L]; WriteString[" Discard"L]; FOR to: CARDINAL IN [0..LENGTH[nets]) DO WriteString[" "L]; O4[nets[to]]; ENDLOOP; WriteCR[]; FOR from: CARDINAL IN [0..LENGTH[nets]) DO O4[nets[from]]; FindFirst[nets[from], 0]; FOR to: CARDINAL IN [0..LENGTH[nets]) DO FindFirst[nets[from], nets[to]]; ENDLOOP; WriteCR[]; ENDLOOP; WriteCR[]; WriteLine["KBytes forwarded:"L]; WriteLine["from to"L]; WriteString[" Discard"L]; FOR to: CARDINAL IN [0..LENGTH[nets]) DO WriteString[" "L]; O4[nets[to]]; ENDLOOP; WriteCR[]; FOR from: CARDINAL IN [0..LENGTH[nets]) DO O4[nets[from]]; FindSecond[nets[from], 0]; FOR to: CARDINAL IN [0..LENGTH[nets]) DO FindSecond[nets[from], nets[to]]; ENDLOOP; WriteCR[]; ENDLOOP; END; GateControl: PROCEDURE = BEGIN OPEN data; b: PupBuffer; packetNumber: CARDINAL ← LAST[CARDINAL]; where: PupAddress ← him; text: STRING = [50]; String.AppendString[text, target]; String.AppendString[text, " ["L]; AppendPupAddress[text, him]; String.AppendString[text, "] "L]; where.socket ← [31415, 9265]; gcSoc ← PupSocketMake[fillInSocketID, where, SecondsToTocks[10]]; UNTIL pleaseStop DO b ← GetFreePupBuffer[]; b.pupID.a ← 27182; b.pupID.b ← (packetNumber ← packetNumber + 1); b.pupType ← GateControlDefs.gateControlStatsSend; SetPupContentsWords[b, 0]; gcSoc.put[b]; UNTIL (b ← gcSoc.get[]) = NIL DO -- Until timeout, or we find the expected one SELECT TRUE FROM (b.pupType = error) => ShowErrorPup[b]; (b.pupType = GateControlDefs.gateControlStatsAck) AND (b.pupID.b = packetNumber) => PrintFirstHalf[text, b]; ENDCASE => BEGIN ReturnFreePupBuffer[b]; LOOP; END; ReturnFreePupBuffer[b]; ENDLOOP; IF b # NIL THEN ReturnFreePupBuffer[b] ENDLOOP; PupSocketDestroy[gcSoc]; data.activeProcesses ← data.activeProcesses - 1; END; PrintFirstHalf: ENTRY PROCEDURE [text: STRING, b: PupBuffer] = BEGIN gse: LONG POINTER TO GateControlDefs.GateControlStatsEntry; gse ← LOOPHOLE[@b.pupBody]; SetLinePointer[0]; WriteString[text]; FOR i: CARDINAL IN [0..gse.versionText.length) DO WriteChar[gse.versionText.char[i]]; ENDLOOP; PrintUpTime[gse.startTime]; WriteCR[]; WriteCR[]; PrintItem[data.fastBoot, "Boot: "L]; PrintItem[data.echo, "Echo: "L]; PrintItem[data.name, "Name: "L]; PrintItem[data.route, "Route: "L]; PrintItem[data.time, "Time: "L]; WriteCR[]; IF (data.slowBoot + data.newBoot + data.dirsSent) # 0 THEN BEGIN PrintItem[data.slowBoot, "SlowBoot: "L]; PrintItem[data.newBoot, "NewBoot: "L]; PrintItem[data.dirsSent, "NetDirsSent: "L]; WriteCR[]; END; PrintItem[gse.freeBuffers, "Buffers: "L]; PrintItem[gse.freeDiskPages, "Disk pages: "L]; WriteCR[]; END; PrintUpTime: INTERNAL PROCEDURE [startTime: InlineDefs.BcplLongNumber] = BEGIN now, then: LONG INTEGER; sec: LONG INTEGER; min: LONG INTEGER; hours: LONG INTEGER; WriteString[" up "L]; then ← InlineDefs.BcplToMesaLongNumber[startTime]; now ← Time.Current[]; sec ← now - then; hours ← sec/3600; sec ← sec - hours*3600; min ← sec/60; sec ← sec - min*60; WriteLongDecimal[hours]; WriteChar[':]; IF min < 10 THEN WriteChar['0]; WriteLongDecimal[min]; WriteChar[':]; IF sec < 10 THEN WriteChar['0]; WriteLongDecimal[sec]; END; PrintItem: INTERNAL PROCEDURE [d: LONG CARDINAL, s: STRING] = BEGIN IF d = 0 THEN RETURN; WriteString[s]; WriteLongDecimal[d]; WriteString[" "L]; END; FindPath: PROCEDURE RETURNS [BOOLEAN] = BEGIN text: STRING = [100]; String.AppendString[text, "Looking at "L]; String.AppendString[text, data.target]; String.AppendString[text, "="L]; GetPupAddress[ @data.him, data.target ! PupNameTrouble => BEGIN String.AppendString[text, e]; MsgSW.Post[msg, text]; GOTO Trouble; END]; AppendPupAddress[text, data.him]; MsgSW.Post[msg, text]; RETURN[TRUE]; EXITS Trouble => RETURN[FALSE]; END; -- IO things SetLinePointer: INTERNAL PROCEDURE [n: [0..maxLines)] = INLINE BEGIN data.line ← n; data.lines[data.line].length ← 0; END; WriteChar: INTERNAL PROCEDURE [c: CHARACTER] = BEGIN String.AppendChar[data.lines[data.line], c]; END; WriteCR: INTERNAL PROCEDURE = BEGIN string: STRING = data.lines[data.line]; ss: String.SubStringDescriptor ← [string, 0, string.length]; tail: Window.Place; tail ← Window.DisplaySubstring[ window: info, ss: @ss, place: [indentation, data.line*data.height], bbop: replace]; Window.DisplayWhite[info, [tail, [rightEdge - tail.x, data.height]]]; data.line ← data.line + 1; data.lines[data.line].length ← 0; END; WriteString: INTERNAL PROCEDURE [s: STRING] = BEGIN i: CARDINAL; FOR i IN [0..s.length) DO WriteChar[s[i]]; ENDLOOP; END; WriteLine: INTERNAL PROCEDURE [s: STRING] = BEGIN WriteString[s]; WriteCR[]; END; WriteLongDecimal: INTERNAL PROCEDURE [n: LONG CARDINAL] = BEGIN s: STRING = [20]; String.AppendLongNumber[s, n, 10]; WriteString[s]; END; LD10: INTERNAL PROCEDURE [n: LONG INTEGER] = BEGIN s: STRING = [20]; String.AppendLongNumber[s, n, 10]; THROUGH [s.length..10) DO WriteChar[' ]; ENDLOOP; WriteString[s]; END; LD10Dash: INTERNAL PROCEDURE [n: LONG INTEGER] = BEGIN IF n = 0 THEN WriteString[" - "L] ELSE LD10[n]; END; WriteDecimal: INTERNAL PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 10, 0]; END; WriteOctal: INTERNAL PROCEDURE [n: CARDINAL] = INLINE BEGIN WriteNumber[n, 8, 0]; END; WriteNumber: INTERNAL PROCEDURE [n, radix, width: CARDINAL] = INLINE BEGIN temp: STRING = [25]; String.AppendNumber[temp, n, radix]; THROUGH [temp.length..width) DO WriteChar[' ]; ENDLOOP; WriteString[temp]; END; D8: INTERNAL PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 10, 8]; END; O3: INTERNAL PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END; O4: INTERNAL PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 4]; END; O6: INTERNAL PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END; O9: INTERNAL PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 9]; END; WriteCurrentDateAndTime: INTERNAL PROCEDURE = BEGIN text: STRING = [20]; Time.AppendCurrent[text]; WriteString[text]; END; ShowErrorPup: PUBLIC PROCEDURE [b: PupDefs.PupBuffer] = BEGIN text: STRING = [100]; PupDefs.AppendErrorPup[text, b]; MsgSW.Post[msg, text]; END; ClearThings: PROCEDURE = BEGIN FOR i: CARDINAL IN [0..maxLines) DO data.lines[i].length ← 0; ENDLOOP; RepaintThings[]; END; indentation: CARDINAL = 10; rightEdge: CARDINAL = 500; RepaintThings: PROCEDURE = BEGIN FOR i: CARDINAL IN [0..maxLines) DO string: STRING = data.lines[i]; ss: String.SubStringDescriptor ← [string, 0, string.length]; tail: Window.Place; tail ← Window.DisplaySubstring[ window: info, ss: @ss, place: [indentation, i*data.height], bbop: replace]; Window.DisplayWhite[info, [tail, [rightEdge - tail.x, data.height]]]; ENDLOOP; END; DisplayInfo: ToolWindow.DisplayProcType = BEGIN RepaintThings[]; END; ZapCounters: PROCEDURE = BEGIN data.fastBoot ← data.slowBoot ← data.newBoot ← data.echo ← data.name ← data.dirsSent ← data.route ← data.time ← 0; END; Start: FormSW.ProcType = BEGIN GateWatcherOn[]; END; Stop: FormSW.ProcType = BEGIN GateWatcherOff[]; END; MakeSWs: Tool.MakeSWsProc = BEGIN msg ← Tool.MakeMsgSW[window: window, lines: 5]; form ← Tool.MakeFormSW[window: window, formProc: MakeForm]; info ← ToolWindow.CreateSubwindow[parent: window, display: DisplayInfo]; info.box.dims.h ← 20*36; Tool.AddThisSW[window: window, sw: info, swType: vanilla]; END; startIX: CARDINAL = 0; stopIX: CARDINAL = 1; MakeForm: FormSW.ClientItemsProcType = BEGIN nParams: CARDINAL = 3; items ← FormSW.AllocateItemDescriptor[nParams]; items[0] ← FormSW.CommandItem[ tag: "Start"L, proc: Start, place: FormSW.newLine]; items[1] ← FormSW.CommandItem[ tag: "Stop"L, proc: Stop, place: FormSW.newLine, invisible: TRUE]; items[2] ← FormSW.StringItem[tag: "Target"L, string: @data.target]; RETURN[items, TRUE]; END; AlreadyActive: ERROR = CODE; NotActive: ERROR = CODE; ClientTransition: ToolWindow.TransitionProcType = BEGIN SELECT TRUE FROM old = inactive => BEGIN IF data # NIL THEN ERROR AlreadyActive; data ← Storage.Node[SIZE[Data]]; data↑ ← []; data.height ← WindowFont.FontHeight[]; data.target ← Storage.String[20]; String.AppendString[data.target, "ME"L]; FOR i: CARDINAL IN [0..maxLines) DO data.lines[i] ← Storage.String[150]; ENDLOOP; PupDefs.PupPackageMake[]; END; new = inactive => BEGIN IF data = NIL THEN ERROR NotActive; msg ← form ← info ← NIL; IF data.running THEN GateWatcherOff[]; PupDefs.PupPackageDestroy[]; FOR i: CARDINAL IN [0..maxLines) DO Storage.FreeString[data.lines[i]]; ENDLOOP; Storage.FreeString[data.target]; data ← Storage.FreeNodeNil[data]; END; ENDCASE; END; Broom: PROCEDURE [why: Event.Reason] = BEGIN SELECT why FROM makeImage, makeCheck => BEGIN IF data = NIL THEN RETURN; IF data.running THEN GateWatcherOff[]; PupDefs.PupPackageDestroy[]; END; startImage, restartCheck, continueCheck => BEGIN IF data = NIL THEN RETURN; PupDefs.PupPackageMake[]; END; ENDCASE => NULL; END; -- Main Body Init[]; END.