DIRECTORY BasicTime, CedarProcess, CodeControl, Commander, CommanderOps, Convert, Cursors, EchoStream, FS, Histograms, HistogramsViewing, HostAndTerminalOps, Imager, ImagerReceiver, IO, IOClasses, Labels, Menus, MessageWindow, MultiCursors, NetAddressing, NetworkStream, PFS, PopUpButtons, Process, RefTab, RefText, RelativeTimes, RemoteEventTime, RemoteImagerDataTypes, RemoteViewersTerminalsKernel, Rope, SimpleFeedback, TerminalMultiServing, TerminalSender, TerminalSpy, ThisMachine, UserInput, UserInputGetActions, UserInputLookahead, UserInputOps, UserInputTypes, UserProfile, ViewerClasses, ViewerContexts, ViewerForkers, ViewerIO, ViewerLocks, ViewerOps, ViewersWorld, ViewersWorldInstance, ViewerTools; RemoteViewersTerminalByViewer: CEDAR MONITOR IMPORTS BasicTime, CedarProcess, CodeControl, Commander, CommanderOps, Convert, EchoStream, FS, Histograms, HistogramsViewing, ImagerReceiver, IO, IOClasses, Labels, Menus, MessageWindow, MultiCursors, NetAddressing, NetworkStream, PFS, PopUpButtons, Process, RefTab, RefText, RemoteEventTime, RemoteViewersTerminalsKernel, Rope, SimpleFeedback, TerminalMultiServing, TerminalSender, TerminalSpy, ThisMachine, UserInputGetActions, UserInputLookahead, UserInputOps, UserProfile, ViewerContexts, ViewerForkers, ViewerIO, ViewerLocks, ViewersWorld, ViewersWorldInstance, ViewerTools = BEGIN OPEN HAT:HostAndTerminalOps, RIDT:RemoteImagerDataTypes, NA:NetAddressing, Nws:NetworkStream, PUB:PopUpButtons, RET:RemoteEventTime, RVTK:RemoteViewersTerminalsKernel, TMS:TerminalMultiServing; CursorHandle: TYPE ~ REF CursorRec; -- used to be in Cursors CursorRec: TYPE ~ RECORD [ info: Cursors.CursorInfo, bits: Cursors.CursorArray ]; ROPE: TYPE ~ Rope.ROPE; LORA: TYPE = LIST OF REF ANY; LOR: TYPE ~ LIST OF ROPE; LON: TYPE ~ LIST OF NAT; Viewer: TYPE ~ ViewerClasses.Viewer; EventTime: TYPE ~ RET.EventTime; VEC: TYPE ~ Imager.VEC; KeyName: TYPE ~ [0..127]; KeyBits: TYPE ~ PACKED ARRAY KeyName OF DownUp; DownUp: TYPE ~ MACHINE DEPENDENT {down(0), up(1)} ¬ up; KeyState: TYPE ~ RECORD[ SELECT OVERLAID * FROM bits => [bits: KeyBits], words => [words: ARRAY [0..WORDS[KeyBits]) OF WORD], ENDCASE ]; TimingHistory: TYPE ~ REF TimingHistoryPrivate; TimingHistoryPrivate: TYPE ~ RECORD [ i, shownI: HistoryIndex ¬ 0, j, shownJ: INT ¬ 0, hist: ARRAY HistoryIndex OF Hist ¬ ALL[[]]]; NHistoryIndices: CARDINAL ~ 256; HistoryIndex: TYPE ~ CARDINAL[0 .. NHistoryIndices); Hist: TYPE ~ RECORD [ org, mid, fin: EventTime ¬ [0, 0], desc: RIDT.TimeEventDesc ¬ [mousePosition: [0, FALSE, 0], action: [contents: timedOut[]] ]]; BoxI: TYPE ~ RECORD [xmin, ymin, xmax, ymax: INTEGER]; LBRT: TYPE ~ RECORD [left, bottom, right, top: NAT]; PktStats: TYPE ~ RECORD [total, bad: CARD ¬ 0]; RtStats: TYPE ~ RECORD [ min: REAL ¬ 99.99, max: REAL ¬ -50.0, sum: REAL ¬ 0.0, count: NAT ¬ 0]; Session: TYPE ~ REF SessionPrivate; SessionPrivate: TYPE ~ RECORD [ sst: ServerState, myIncreek: UserInput.Handle ]; minPassTime: UserInputGetActions.DeltaTime ¬ 50; maxPassTime: UserInputGetActions.DeltaTime ¬ 30D3; ServerState: TYPE ~ REF ServerStateRecord; ServerStateRecord: TYPE ~ RECORD [ host: TMS.Host ¬ TMS.nullHost, upTime: Histograms.Histogram ¬ NIL, downTime: Histograms.Histogram ¬ NIL, roundTime: Histograms.Histogram ¬ NIL, upTimeView, downTimeView, roundTimeView: Viewer ¬ NIL, th: TimingHistory ¬ NIL, nextCursorPattern: Cursors.CursorArray ¬ ALL[5A5AH], thv: Viewer ¬ NIL, tho: IO.STREAM ¬ NIL, theSize: ViewerContexts.VECI, curVer: BYTE ¬ 0, client: NA.Address ¬ NA.nullAddress, sender: TerminalSender.Sender ¬ NIL, contextViewer: ViewerContexts.ContextViewer ¬ NIL, cvv: Viewer ¬ NIL, --contextViewer as a Viewer statLabel: Viewer ¬ NIL, showButton: Viewer ¬ NIL, connButton: Viewer ¬ NIL, discoButton: Viewer ¬ NIL, outStream, inStream: IO.STREAM ¬ NIL, rtStats: RtStats ¬ [], i2s: ViewerContexts.VECI ¬ [0, 0], iClip: BoxI ¬ [0, 0, 100, 100], vw: ViewersWorld.Ref ¬ ViewersWorldInstance.GetWorld[], increek: UserInput.Handle ¬ NIL, creekEnd: BOOL ¬ FALSE, xlateKC: TerminalSender.KeyCodeTranslation ¬ NIL, inside: BOOL ¬ FALSE, --cursor is inside the terminal passTime: UserInputGetActions.DeltaTime ¬ minPassTime, --don't let this much time pass without reporting it curTimeStamp: UserInputGetActions.TimeStamp ¬ [0], --time at head of increek lastTimeStamp: UserInputGetActions.TimeStamp ¬ [0], needRepaint: BOOL ¬ FALSE ]; bot: EventTime ~ [0, 0]; installDir: PFS.PATH ~ PFS.GetWDir[]; stdIncreek: UserInput.Handle ~ ViewersWorld.GetInputHandle[ViewersWorldInstance.GetWorld[]]; codingMethod: ROPE ¬ "B4KS"; reject: BOOL ¬ FALSE; willingToCode: BOOL ¬ TRUE; logInput, logPlainOutput, logCodedOutput, viewInput, viewDetails: BOOL ¬ FALSE; debugMonitor: BOOL ¬ TRUE; headTime: NAT ¬ 15; decayRate: REAL ¬ 0.95; theServer: RVTK.ViewersServer ¬ NIL; theSST: ServerState ¬ NIL; hbo: LBRT ~ [0, 1, 0, 0]; hbi: LBRT ~ [1, 2, 1, 1]; hboHeight: NAT ~ hbo.bottom + hbo.top; hboWidth: NAT ~ hbo.left + hbo.right; hbiHeight: NAT ~ hbi.bottom + hbi.top; hbiWidth: NAT ~ hbi.left + hbi.right; inputLogName: ROPE ~ "-vux:/tmp/RemoteInput.log"; plainOutputLogName: ROPE ~ "-vux:/tmp/RemoteOutput-plain.log"; codedOutputLogName: ROPE ~ "-vux:/tmp/RemoteOutput-coded.log"; fileTapLogName: ROPE ~ "-vux:/tmp/FileTap.log"; minTime: REAL ~ -4.0; maxTime: REAL ~ 30.0; dTime: REAL ~ 0.01; cursTab: RefTab.Ref--CursorHandle -> CursorHandle-- ~ RefTab.Create[hash: HashCursorHandle, equal: EqualCursorHandle]; lossyBitmap: BOOL ¬ FALSE; showClass: PUB.Class ~ PUB.MakeClass[[ proc: MyPop, choices: LIST[[$ShowTimes, "Show round trip time histograms"]] ]]; connClass: PUB.Class ~ PUB.MakeClass[[ proc: MyPop, choices: LIST[[$Connect, "...to host named in Viewers selection."], [$Observe, "Observe selected host's output"]] ]]; discoClass: PUB.Class ~ PUB.MakeClass[[ proc: MyPop, choices: LIST[[$Disconnect, "...from this host."]] ]]; GetCreek: PROC RETURNS [UserInput.Handle] ~ { uih: UserInput.Handle ~ UserInputOps.Create[]; stdin: UserInput.Handle ~ ViewersWorld.GetInputHandle[ViewersWorldInstance.GetWorld[]]; UserInputLookahead.SaveState[uih, stdin]; RETURN [uih]}; StopServing: ENTRY PROC [server: RVTK.ViewersServer] ~ { ENABLE UNWIND => NULL; sst: ServerState ~ NARROW[server.data]; IF sst.client = NA.nullAddress THEN RETURN; InnerStop[sst]; IF sst.contextViewer#NIL THEN sst.contextViewer.Destroy[]; TRUSTED {sst.client ¬ NA.nullAddress}; RETURN}; StartServing: ENTRY PROC [server: RVTK.ViewersServer, host: TMS.Host] ~ { ENABLE UNWIND => NULL; sst: ServerState ~ NARROW[server.data]; IF TMS.EqualHosts[host, sst.client] THEN RETURN; TRUSTED {sst.client ¬ host}; RETURN}; Work: PROC [server: RVTK.ViewersServer, in, out: IO.STREAM, host: TMS.Host, sessionDescr: ROPE, version: HAT.ProtocolVersion, Push: RVTK.PushProc] = { sst: ServerState ~ NARROW[server.data]; et1: EventTime; myOutStream: IO.STREAM ¬ NIL; mySender: TerminalSender.Sender ¬ NIL; decodingStream: IO.STREAM ¬ in; inputLog, plainOutputLog, codedOutputLog, push, pull, myRcvStream: IO.STREAM ¬ NIL; myContextViewer: ViewerContexts.ContextViewer ¬ NIL; initialState: TerminalSender.ActionBodyList ¬ NIL; sess: Session ¬ NIL; Messup: ENTRY PROC ~ { ENABLE UNWIND => IF sess#NIL AND sess.myIncreek#NIL THEN UserInputOps.Close[sess.myIncreek]; InnerStop[sst]; IF logInput THEN inputLog ¬ FS.StreamOpen[fileName: inputLogName, accessOptions: create, keep: 10 !FS.Error => CONTINUE]; IF logPlainOutput THEN plainOutputLog ¬ FS.StreamOpen[fileName: plainOutputLogName, accessOptions: create, keep: 10 !FS.Error => CONTINUE]; IF viewInput THEN { [push, pull] ¬ IOClasses.CreatePipe[5000]; spyer ¬ CedarProcess.Fork[Spy, pull]}; sst.curVer ¬ version; myOutStream ¬ out; sst.inStream ¬ in; sst.host _ host; myContextViewer ¬ InnerEnsureViewer[sst, sessionDescr]; sst.needRepaint ¬ FALSE; IF inputLog#NIL THEN myOutStream ¬ IOClasses.CreateDribbleOutputStream[myOutStream, inputLog]; IF push#NIL THEN myOutStream ¬ IOClasses.CreateDribbleOutputStream[myOutStream, push]; sess ¬ NEW [SessionPrivate ¬ [sst, sst.increek ¬ GetCreek[] ]]; sst.creekEnd ¬ FALSE; UserInputOps.SetAtLatest[sess.myIncreek]; [initialState, sst.lastTimeStamp] ¬ TerminalSender.StateOfIncreek[sess.myIncreek]; et1 ¬ TimeStampToEventTime[sess.myIncreek, sst.lastTimeStamp] .Sub[RET.SmallConsCC[0, sst.lastTimeStamp]] .Add[RET.SmallConsCC[0, 1]]; sst.curTimeStamp ¬ sst.lastTimeStamp; mySender ¬ sst.sender ¬ TerminalSender.StartSending[et1, sst.xlateKC, sessionDescr, sess, FilterGetAction, initialState, myOutStream, FinishSending, Push, out, SendOther]; sst.outStream ¬ myOutStream; RETURN}; Cleanup: PROC = { IF debugMonitor THEN SimpleFeedback.PutFL[$RemoteViewersTerminalByViewer, oneLiner, $Debug, "%g Trying to enter monitor to report end of Viewers service for %g", LIST[[rope[Now[]]], [rope[sessionDescr]]] ]; EnterAndCleanup[]; RETURN}; EnterAndCleanup: ENTRY PROC = { ENABLE UNWIND => NULL; Process.SetPriority[Process.priorityForeground]; myContextViewer.Destroy[]; TerminalSender.StopSending[mySender]; IF plainOutputLog#NIL THEN plainOutputLog.Close[!IO.Error => CONTINUE]; IF codedOutputLog#NIL THEN codedOutputLog.Close[!IO.Error => CONTINUE]; IF (sst.sender=mySender) # (sst.outStream=myOutStream) THEN ERROR--something's screwed up: sender and outStream are supposed to change together--; IF sst.sender=mySender THEN { sst.sender ¬ NIL; sst.outStream ¬ NIL; }; InnerStopReceiving[sst]; SimpleFeedback.PutFL[$RemoteViewersTerminalByViewer, oneLiner, $Event, "%g Done serving as Viewers terminal for %g", LIST[[rope[Now[]]], [rope[sessionDescr]]] ]; RETURN}; rejection: ROPE; expand, rcvTiming: BOOL; [rejection, expand, rcvTiming] ¬ Good[in, out, version]; SimpleFeedback.PutFL[$RemoteViewersTerminalByViewer, oneLiner, $Event, IF rejection=NIL THEN IF expand THEN "%g Serving as coding Viewers terminal for %g.%g" ELSE "%g Serving as plaintext Viewers terminal for %g.%g" ELSE "%g Not serving %g 'cause %g.", LIST[[rope[Now[]]], [rope[sessionDescr]], [rope[rejection]]] ]; IF rejection=NIL THEN { screenSettingses: RIDT.ScreenSettingses ~ NEW [RIDT.ScreenSettingsesPrivate[1]]; screenSettingses[0] ¬ [LIST["BlackAndWhite"], LIST[[sst.theSize.x, sst.theSize.y]]]; IF logCodedOutput THEN codedOutputLog ¬ FS.StreamOpen[fileName: codedOutputLogName, accessOptions: create, keep: 10 !FS.Error => CONTINUE]; IF codedOutputLog#NIL THEN in ¬ EchoStream.CreateEchoStream[in: in, out: codedOutputLog]; IF expand THEN decodingStream ¬ CodeControl.CreateDecodingStream[in, codingMethod !CodeControl.BadCodingMethod => { SimpleFeedback.PutFL[$RemoteViewersTerminalByViewer, oneLiner, $Error, "%g Abandoning %g because of error %g creating decoding stream, method %g", LIST[[rope[Now[]]], [rope[sessionDescr]], [rope[errorMsg]], [rope[codingMethod]]] ]; TMS.DontServeHost[host]; GOTO Abandoned}] ELSE decodingStream ¬ in; ImagerReceiver.SendSettingses[out, screenSettingses]; out.Flush[]; Messup[]; {ENABLE UNWIND => Cleanup[]; IF plainOutputLog#NIL THEN myRcvStream ¬ EchoStream.CreateEchoStream[in: decodingStream, out: plainOutputLog] ELSE myRcvStream ¬ decodingStream; TRUSTED {Process.Detach[FORK Head[sst, myOutStream, ThisMachine.Name[]]]}; Process.SetPriority[Process.priorityNormal]; ImagerReceiver.DoSession[sst, myRcvStream, IF plainOutputLog#NIL THEN plainOutputLog ELSE in--decoding streams can't GetIndex (on October 29, 1990)--, version, rcvTiming, CreateContext, SetInterminalVariable, SetScreen, ImagerReceiver.NullBlink, ImagerReceiver.NullBeep, ImagerReceiver.IgnoreTime, PerTimeProbe, PerTimeReply, ImagerReceiver.IgnoreAbsoluteTime, SetCutBuffer, GetCutBuffer]; }; Cleanup[]; EXITS Abandoned => rejection ¬ rejection}; RETURN}; inputSender, spyer: CedarProcess.Process ¬ NIL; FinishSending: PROC [sender: TerminalSender.Sender, sourceData: REF ANY, consumer, pushStream: IO.STREAM] ~ { sess: Session ~ NARROW[sourceData]; UserInputOps.Close[sess.myIncreek]; RETURN}; Good: PROC [in, out: IO.STREAM, version: BYTE] RETURNS [rejection: ROPE, expand, rcvTiming: BOOL ¬ TRUE] ~ { ENABLE IO.Error => {rejection ¬ "IO.Error or stream close during initial negotiation"; CONTINUE}; codeChar: CHAR; out.PutChar[IF willingToCode THEN 'C ELSE 'P]; out.PutChar[IF reject THEN 'R ELSE 'A]; out.Flush[]; codeChar ¬ in.GetChar[]; SELECT codeChar FROM 'C => expand ¬ willingToCode; 'P => expand ¬ FALSE; ENDCASE => RETURN ["host didn't properly conduct initial negotiations"]; codeChar ¬ in.GetChar[]; IF codeChar='R OR reject THEN RETURN ["terminal or host is rejecting"]; IF codeChar#'A THEN RETURN ["host didn't properly conduct initial negotiations"]; IF version >= 12 THEN { codeChar ¬ in.GetChar[]; SELECT codeChar FROM 'T => rcvTiming ¬ TRUE; 'N => rcvTiming ¬ FALSE; ENDCASE => RETURN ["host didn't properly conduct initial negotiations"]} ELSE rcvTiming ¬ FALSE; rejection ¬ NIL; RETURN}; InnerEnsureViewer: INTERNAL PROC [sst: ServerState, topName: ROPE] RETURNS [myContextViewer: ViewerContexts.ContextViewer] ~ { vcMenuPane: Viewer ¬ NIL; IF sst.cvv=NIL OR sst.cvv.destroyed THEN { sst.contextViewer ¬ myContextViewer ¬ ViewerContexts.Create[ viewerInit: [ scrollable: UserProfile.Boolean["RemoteTerminal.Scrollable", TRUE], name: topName, hscrollable: TRUE, iconic: TRUE], size: sst.theSize, NoteClear: NoteClear, NoteChange: NoteCvChange, NoteDestruction: NoteDestruction, clientData: sst]; sst.cvv ¬ sst.contextViewer.QuaViewer[outer]; vcMenuPane ¬ myContextViewer.GetMenuPane[]; sst.showButton ¬ showClass.Instantiate[viewerInfo: [name: "ShowTimes", parent: vcMenuPane, border: FALSE], instanceData: sst, paint: FALSE]; myContextViewer.AddMenuElt[sst.showButton, FALSE]; sst.connButton ¬ connClass.Instantiate[viewerInfo: [name: "Connect", parent: vcMenuPane, border: FALSE], instanceData: sst, paint: FALSE]; myContextViewer.AddMenuElt[sst.connButton, FALSE]; sst.discoButton ¬ discoClass.Instantiate[viewerInfo: [name: "Disconnect", parent: vcMenuPane, border: FALSE], instanceData: sst, paint: FALSE]; myContextViewer.AddMenuElt[sst.discoButton, FALSE]; sst.statLabel ¬ Labels.Create[info: [name: "round trip stats go here", parent: sst.cvv, border: FALSE], paint: FALSE]; ViewerContexts.AddPane[sst.contextViewer, sst.statLabel, FALSE, FALSE]; } ELSE { myContextViewer ¬ sst.contextViewer; sst.cvv.name ¬ topName}; ViewerForkers.ForkPaint[sst.cvv, all]; RETURN}; FilterGetAction: PROC [sourceData: REF ANY, waitMode: UserInputTypes.WaitMode, waitInterval: INT, acceptance: UserInputTypes.Acceptance] RETURNS [UserInputGetActions.InputActionBody] --TerminalSender.GetActionProc-- ~ TRUSTED { sess: Session ~ NARROW[sourceData]; sst: ServerState ~ sess.sst; creekWait: INT ¬ 0; IF acceptance#all THEN ERROR--not implemented--; DO ab: UserInputGetActions.InputActionBody; IF sst.creekEnd OR sst.outStream=NIL THEN RETURN [[$End, sst.lastTimeStamp, 0]]; {IF waitMode=timed THEN { creekWait ¬ waitInterval - INT[sst.curTimeStamp - sst.lastTimeStamp]; IF creekWait<0 THEN RETURN [[$TimeOut, sst.lastTimeStamp, 0]]}; ab ¬ UserInputGetActions.GetInputActionBody[sst.increek, waitMode, creekWait, all]; IF ab.kind=$TimeOut THEN RETURN [[$TimeOut, sst.lastTimeStamp, 0]]; ab.deltaTime ¬ (sst.curTimeStamp ¬ ab.eventTime) - sst.lastTimeStamp; IF sst.cvv.iconic THEN GOTO Clip; IF ab.kind = $IntegerPosition THEN {ab.rx ¬ ab.x; ab.ry ¬ ab.y; ab.kind ¬ $Position}; IF ab.kind = $Position THEN { IF DisplayToColor[ab.display] THEN GOTO Clip; ab.rx ¬ ab.rx - sst.i2s.x; ab.ry ¬ ab.ry - sst.i2s.y; sst.inside ¬ ab.rx >= sst.iClip.xmin AND ab.rx < sst.iClip.xmax AND ab.ry >= sst.iClip.ymin AND ab.ry < sst.iClip.ymax; IF NOT sst.inside THEN GOTO Clip; sst.lastTimeStamp ¬ ab.eventTime; sst.passTime ¬ minPassTime; RETURN [ab]}; SELECT ab.kind FROM $End => {sst.creekEnd ¬ TRUE; sst.lastTimeStamp ¬ ab.eventTime; RETURN [ab]}; $EventTime, $TimeIsPassing => GOTO Clip; $FakePosition, $Enter, $Exit => IF DisplayToColor[ab.display] THEN GOTO Clip; ENDCASE => NULL; IF NOT sst.inside THEN GOTO Clip; sst.lastTimeStamp ¬ ab.eventTime; sst.passTime ¬ minPassTime; RETURN [ab]; EXITS Clip => sourceData ¬ sourceData; }; IF ab.deltaTime >= sst.passTime THEN { sst.passTime ¬ MIN[sst.passTime + sst.passTime/2, maxPassTime]; RETURN [[$EventTime, sst.lastTimeStamp ¬ sst.curTimeStamp, ab.deltaTime]]}; IF ab.deltaTime>0 AND ((waitMode#forever) OR sst.needRepaint) THEN RETURN [[$EventTime, sst.lastTimeStamp ¬ sst.curTimeStamp, ab.deltaTime]]; ENDLOOP; }; Head: PROC [sst: ServerState, stream: IO.STREAM, termAddr: ROPE] ~ { b: REAL ~ decayRate; a: REAL ~ 1.0 - b; rtShow: RtStats; rtWt, badWt: REAL ¬ 1.0E-20; rtRunAvg, runBad: REAL ¬ 0.0; rtAvg, badPct: REAL ¬ 0.0; Init: ENTRY PROC ~ { ENABLE UNWIND => NULL; rtShow ¬ sst.rtStats ¬ []; RETURN}; Update: ENTRY PROC ~ { ENABLE UNWIND => NULL; IF stream#sst.outStream THEN RETURN; IF sst.rtStats.count>0 THEN { rtShow ¬ sst.rtStats; sst.rtStats ¬ []; rtAvg ¬ rtShow.sum/rtShow.count; rtRunAvg ¬ b*rtRunAvg + a*rtAvg; rtWt ¬ b*rtWt + a}; {msg: ROPE ~ IO.PutFLR[" %g Term=%g; rt min=%4.2fs max=%05.2fs avg=%4.2fs run avg=%4.2fs", LIST[ [rope[Convert.RopeFromTime[from: BasicTime.Now[], start: hours, end: seconds, useAMPM: FALSE, includeZone: FALSE]]], [rope[termAddr]], [real[rtShow.min]], [real[rtShow.max]], [real[rtAvg]], [real[rtRunAvg/rtWt]] ]]; Labels.Set[sst.statLabel, msg]; RETURN}}; Init[]; Process.SetPriority[Process.priorityForeground]; WHILE stream=sst.outStream DO sl: Viewer; Process.Pause[Process.SecondsToTicks[headTime]]; sl ¬ sst.statLabel; IF sl#NIL AND NOT sl.destroyed THEN ViewerLocks.CallUnderWriteLock[Update, sl]; ENDLOOP; stream ¬ stream; RETURN}; SendOther: PROC [sender: TerminalSender.Sender] ~ { sst: ServerState ~ theSST; IF sst.needRepaint THEN { PutRepaint: PROC [out: IO.STREAM] ~ {out.PutChar[VAL[16]]}; sst.needRepaint ¬ FALSE; sender.WithSender[PutRepaint]; }; RETURN}; PerTimeProbe: PROC [org: EventTime, desc: RIDT.TimeEventDesc] ~ { sst: ServerState ~ theSST; s: TerminalSender.Sender ~ sst.sender; ver: BYTE ~ sst.curVer; mid: EventTime ~ RET.ReadEventTime[]; Reply: PROC [out: IO.STREAM] ~ { ENABLE { IO.Error => IF (ec=StreamClosed OR ec=Failure) AND stream = out THEN CONTINUE; Nws.Timeout => RESUME; }; out.PutChar[VAL[17]]; TerminalSender.SendTimeReply[s, org, mid, ver>=13, [desc.mousePosition, desc.action]]; RETURN}; IF s#NIL THEN s.WithSender[Reply]; RETURN}; PerTimeReply: PROC [org, mid: EventTime, desc: RIDT.TimeEventDesc] ~ { fin: EventTime ~ RET.ReadEventTime[]; ut: REAL ~ RET.Sub[mid, org].ToSmall[]/1000.0; dt: REAL ~ RET.Sub[fin, mid].ToSmall[]/1000.0; rt: REAL ~ RET.Sub[fin, org].ToSmall[]/1000.0; sst: ServerState ~ theSST; sst.upTime.IncrementTransformed[minTime, maxTime, ut]; sst.downTime.IncrementTransformed[minTime, maxTime, dt]; sst.roundTime.IncrementTransformed[-1.0, maxTime, rt]; IF rt < 0 THEN lastBogon ¬ [org, mid, fin]; IF rt >= maxTime THEN lastBigon ¬ [org, mid, fin]; AddHist[sst, [org, mid, fin, desc]]; sst.rtStats.min ¬ MIN[sst.rtStats.min, rt]; sst.rtStats.max ¬ MAX[sst.rtStats.max, rt]; sst.rtStats.sum ¬ sst.rtStats.sum + rt; sst.rtStats.count ¬ sst.rtStats.count + 1; RETURN}; Bogon: TYPE ~ RECORD [org, mid, fin: EventTime ¬ bot]; lastBogon, lastBigon: Bogon ¬ []; AddHist: ENTRY PROC [sst: ServerState, h: Hist] ~ { ENABLE UNWIND => NULL; sst.th.hist[sst.th.i] ¬ h; IF sst.th.i = HistoryIndex.LAST THEN {sst.th.i ¬ 0; sst.th.j ¬ sst.th.j+1} ELSE sst.th.i ¬ sst.th.i + 1; RETURN}; SetInterminalVariable: PROC [clientData: REF ANY, setting: RIDT.InterminalSetting] ~ { sst: ServerState ~ NARROW[clientData]; WITH setting SELECT FROM CursorOffset => { ch: CursorHandle ~ NEW [CursorRec ¬ [ info: [type: last, hotX: hotX, hotY: hotY, inverted: FALSE], bits: sst.nextCursorPattern]]; cch: CursorHandle ¬ NARROW[cursTab.Fetch[ch].val]; IF cch=NIL THEN { ch.info.type ¬ MultiCursors.NewCursor[ch.bits, hotX, hotY]; [] ¬ cursTab.Store[ch, ch]; cch ¬ ch}; sst.contextViewer.SetCursor[cch.info.type]; IF sst.inside THEN MultiCursors.SetACursor[cch.info.type, NIL]; -- ??? RETURN}; MouseGrain => NULL; MousePosition => {--given positive (0 at bottom) Y coords IF sst.inside THEN { ViewersWorld.SetMousePosition[sst.vw, pos.mouseX + sst.i2s.x, pos.mouseY + sst.i2s.y <<, IF pos.color THEN 1 ELSE 0>> ]; }; }; FastMouseParms => NULL; Escapes => NULL; CursorPattern => sst.nextCursorPattern ¬ pattern; ENDCASE => ERROR; RETURN}; SetScreen: PROC [clientData: REF ANY, screen: NATURAL, setting: RIDT.ScreenSetting] ~ { RETURN}; SetCutBuffer: PROC [clientData: REF ANY, buffer: ATOM, data: ROPE] ~ { sst: ServerState ~ NARROW[clientData]; ViewersWorld.SetCutBuffer[sst.vw, buffer, data]; RETURN}; GetCutBuffer: PROC [clientData: REF ANY, buffer: ATOM, key: CARD] ~ { sst: ServerState ~ NARROW[clientData]; s: TerminalSender.Sender ~ sst.sender; data: ROPE ~ ViewersWorld.GetCutBuffer[sst.vw, buffer]; Reply: PROC [out: IO.STREAM] ~ { ENABLE { IO.Error => IF (ec=StreamClosed OR ec=Failure) AND stream = out THEN CONTINUE; Nws.Timeout => RESUME; }; out.PutChar[VAL[18]]; TerminalSender.SendCutBuffer[s, buffer, key, data]; RETURN}; IF s#NIL THEN s.WithSender[Reply]; RETURN}; CreateContext: PROC [clientData: REF ANY, screen: NATURAL] RETURNS [c: Imager.Context] --ImagerReceiver.ContextCreator-- = { sst: ServerState ~ NARROW[clientData]; cv: ViewerContexts.ContextViewer ~ sst.contextViewer; RETURN [cv.CreateContext[]]}; Spy: PROC [data: REF ANY] RETURNS [results: REF ¬ NIL] = { vStream: IO.STREAM = ViewerIO.CreateViewerStreams["Send Script"].out; pull: IO.STREAM = NARROW[data]; TerminalSpy.SpyOnStream[vStream, pull, viewDetails, viewDetails]; RETURN}; TapFile: PROC [fileName: ROPE, codingMethod: ROPE ¬ NIL, version: BYTE ¬ 0, timing: BOOL ¬ TRUE] = { fileStream: IO.STREAM ~ FS.StreamOpen[fileName]; log: IO.STREAM ~ FS.StreamOpen[fileTapLogName, create]; plainStream: IO.STREAM ¬ fileStream; inStream: IO.STREAM; Closem: PROC ~ { log.Close[!IO.Error => CONTINUE]; fileStream.Close[!IO.Error => CONTINUE]; RETURN}; IF codingMethod=NIL THEN {IF version=0 THEN version ¬ fileStream.GetChar[].ORD} ELSE plainStream ¬ CodeControl.CreateDecodingStream[fileStream, codingMethod]; inStream ¬ EchoStream.CreateEchoStream[in: plainStream, out: log]; ImagerReceiver.DoSession[theSST, inStream, log, version, timing, CreateContext, SetInterminalVariable, SetScreen, ImagerReceiver.NullBlink, ImagerReceiver.NullBeep, ImagerReceiver.IgnoreTime, ImagerReceiver.IgnoreTimeProbe, ImagerReceiver.IgnoreTimeReply, ImagerReceiver.IgnoreAbsoluteTime, ImagerReceiver.IgnoreCutBuffer, ImagerReceiver.DontGetCutBuffer ! UNWIND => Closem[] ]; Closem[]; RETURN}; NoteClear: PROC [clientData: REF ANY] ~ { sst: ServerState ~ NARROW[clientData]; sst.needRepaint ¬ TRUE}; NoteCvChange: PROC [clientData: REF ANY, idealToViewer, viewerToScreen, viewerSize, idealSize: ViewerContexts.VECI] ~ { sst: ServerState ~ NARROW[clientData]; sst.i2s ¬ [idealToViewer.x+viewerToScreen.x, idealToViewer.y+viewerToScreen.y]; IF sst.theSize # idealSize THEN { sst.theSize ¬ idealSize; MessageWindow.Append["Host won't notice new size until next connection.", TRUE]; }; sst.iClip ¬ [ MAX[0, -idealToViewer.x], MAX[0, -idealToViewer.y], MIN[sst.theSize.x, viewerSize.x-idealToViewer.x], MIN[sst.theSize.y, viewerSize.y-idealToViewer.y] ]; RETURN}; NoteDestruction: PROC [clientData: REF ANY] ~ { sst: ServerState ~ NARROW[clientData]; TRUSTED {Process.Detach[FORK EnterAndStop[sst, sst.outStream]]}; RETURN}; EnterAndStop: ENTRY PROC [sst: ServerState, outStream: IO.STREAM] ~ { ENABLE UNWIND => NULL; IF sst.outStream = outStream THEN InnerStop[sst]; RETURN}; InnerStop: INTERNAL PROC [sst: ServerState] ~ { IF sst.sender#NIL THEN TRUSTED { Process.Detach[FORK TerminalSender.Close[sst.sender]]; sst.sender ¬ NIL; sst.outStream ¬ NIL}; InnerStopReceiving[sst]; RETURN}; InnerStopReceiving: INTERNAL PROC [sst: ServerState] ~ { IF sst.inStream#NIL THEN { it: IO.STREAM ~ sst.inStream; sst.inStream ¬ NIL; IF avoidNwsBug THEN TRUSTED {Process.Detach[FORK CloseStream[it]]} ELSE CloseStream[it]; }; RETURN}; avoidNwsBug: BOOL ¬ TRUE; --TRUE if Demers still needs to remove the deadlock that occurs if you close a NetworkStream during a read. CloseStream: PROCEDURE [it: IO.STREAM] ~ {IO.Close[it, TRUE !IO.Error => CONTINUE]}; RestoreViewer: ENTRY PROC [sst: ServerState] ~ { ENABLE UNWIND => NULL; wait: CONDITION; TRUSTED {Process.InitializeCondition[@wait, Process.MsecToTicks[500]]}; UNTIL sst.cvv=NIL OR sst.cvv.destroyed DO WAIT wait ENDLOOP; [] ¬ InnerEnsureViewer[sst, ">unconnected<"]; RETURN}; Wake: ENTRY PROC [server: RVTK.ViewersServer] RETURNS [wasAwake: BOOL] ~ { ENABLE UNWIND => NULL; sst: ServerState ~ NARROW[server.data]; IF sst.cvv=NIL OR sst.cvv.destroyed THEN { [] ¬ InnerEnsureViewer[sst, ">unconnected<"]; RETURN [FALSE]} ELSE RETURN [TRUE]}; MyPop: PROC [view, instanceData, classData, key: REF ANY] --PUB.PopUpButtonProc-- ~ { sst: ServerState ~ NARROW[instanceData]; SELECT key FROM $ShowTimes => ShowTimes[sst]; $Connect, $Observe => {sel: ROPE ~ ViewerTools.GetSelectionContents[]; IF sel.Length[] = 0 THEN SimpleFeedback.Append[$RemoteTerminal, oneLiner, $Error, "Select a host first"] ELSE ServeRope[sel, IF key=$Observe THEN secondary ELSE primary]}; $Disconnect => TMS.DontServeHost[sst.host]; ENDCASE => MessageWindow.Append["Can't happen!", TRUE]; RETURN}; ServeRope: PROC [rope: ROPE, role: TMS.Role] ~ { host: TMS.Host ¬ NA.ParseAddress[rope]; err: ROPE; IF host.socket.Length[] = 0 THEN host.socket ¬ "58812"; host ¬ NA.Canonicalize[host]; err ¬ TMS.ServeHost[host, role]; IF err#NIL THEN SimpleFeedback.PutFL[$RemoteTerminal, oneLiner, $Error, "Failed to connect to %g: %g", LIST[ [rope[rope]], [rope[err]] ]]; RETURN}; Now: PROC RETURNS [ROPE] ~ { up: BasicTime.Unpacked ~ BasicTime.Unpack[BasicTime.Now[]]; RETURN [IO.PutFLR["%g/%g %g:%02g:%02g", LIST[[cardinal[up.month.ORD+1]], [cardinal[up.day]], [cardinal[up.hour]], [cardinal[up.minute]], [cardinal[up.second]] ] ]]}; ViewHistory: ENTRY PROC [sst: ServerState] ~ { ENABLE UNWIND => NULL; IF sst.thv=NIL OR sst.thv.destroyed THEN Menus.AppendMenuEntry[(sst.thv ¬ ViewerIO.GetViewerFromStream[sst.tho ¬ ViewerIO.CreateViewerStreams["Detailed timing history"].out]).menu, Menus.CreateEntry["History", HistoryClick, sst]]; RETURN}; HistoryClick: ENTRY PROC [parent: Viewer, clientData: REF ANY ¬ NIL, mouseButton: ViewerClasses.MouseButton ¬ red, shift, control: BOOL ¬ FALSE] ~ { ENABLE UNWIND => NULL; sst: ServerState ~ NARROW[clientData]; th: TimingHistory ~ sst.th; i1: HistoryIndex ¬ th.i; j1: INT ¬ 0; intro: ROPE ¬ ""; IF th.j=0 THEN i1 ¬ 0 ELSE j1 ¬ th.j-1; IF mouseButton=blue THEN intro ¬ "\n\n" ELSE IF j1>th.shownJ OR j1=th.shownJ AND i1>th.shownI THEN intro ¬ "...\n" ELSE {j1 ¬ th.shownJ; i1 ¬ th.shownI}; sst.tho.PutRope[intro]; WHILE j1 < th.j OR i1 < th.i DO ij: INT ~ j1*NHistoryIndices + i1; sst.tho.PutF1["%04g: ", [integer[ij]]]; TerminalSpy.PrintEventTime[sst.tho, th.hist[i1].org, FALSE]; sst.tho.PutChar[' ]; TerminalSpy.PrintEventTime[sst.tho, th.hist[i1].mid, FALSE]; sst.tho.PutChar[' ]; TerminalSpy.PrintEventTime[sst.tho, th.hist[i1].fin, FALSE]; sst.tho.PutF[" %g[%g, %g] ", [rope[IF th.hist[i1].desc.mousePosition.color THEN "color" ELSE "bw"]], [integer[th.hist[i1].desc.mousePosition.mouseX]], [integer[th.hist[i1].desc.mousePosition.mouseY]]]; <> sst.tho.PutRope["\n"]; IF i1=HistoryIndex.LAST THEN {j1 ¬ j1+1; i1 ¬ 0} ELSE i1 ¬ i1+1; ENDLOOP; th.shownI ¬ th.i; th.shownJ ¬ th.j; RETURN}; ShowTimes: PROC [sst: ServerState] ~ { sst.roundTimeView ¬ HistogramsViewing.Show[h: sst.roundTime, format: "%5.2f", width: 5, viewerInit: [name: "Term -> host -> Term seconds"], base: 2.0, updatePeriod: 60]; sst.downTimeView ¬ HistogramsViewing.Show[h: sst.downTime, format: "%5.2f", width: 5, viewerInit: [name: "host -> Term seconds"], base: 2.0, updatePeriod: 60]; sst.upTimeView ¬ HistogramsViewing.Show[h: sst.upTime, format: "%5.2f", width: 5, viewerInit: [name: "Term -> host seconds"], base: 2.0, updatePeriod: 60]; RETURN}; TimeStampToEventTime: PROC [uih: UserInput.Handle, ts: RelativeTimes.TimeStamp] RETURNS [et: EventTime] ~ { gmt: BasicTime.GMT; s, ms: INT; [gmt, ms] ¬ UserInputOps.GetAbsoluteTime[uih, ts]; IF debugTime THEN SimpleFeedback.PutFL[$RemoteViewersTerminalByViewer, oneLiner, $Debug, "TimeStamp %g = %g + %gms.", LIST[[cardinal[ts]], [time[gmt]], [integer[ms]]] ]; s ¬ ms/1000; ms ¬ ms - s*1000; IF ms<0 THEN {s ¬ s - 1; ms ¬ ms + 1000}; et ¬ RET.FromEGMT[[gmt.Update[s], ms*1000]]; IF debugTime THEN SimpleFeedback.PutFL[$RemoteViewersTerminalByViewer, oneLiner, $Debug, "... which = %g + %gs + %gms = %g*10000H+%g ms.", LIST[[time[gmt]], [integer[s]], [integer[ms]], [integer[et.hi]], [cardinal[et.lo]]] ]; RETURN [et]}; debugTime: BOOL ¬ TRUE; DisplayToColor: PROC [ra: REF ANY] RETURNS [BOOL] ~ { SELECT ra FROM $Main, $Display0, NIL => RETURN [FALSE]; ENDCASE => RETURN [TRUE]}; EqualCursorHandle: PROC [key1, key2: REF ANY] RETURNS [BOOL] --RefTab.EqualProc-- ~ { ch1: CursorHandle ~ NARROW[key1]; ch2: CursorHandle ~ NARROW[key2]; RETURN [ch1.bits=ch2.bits AND ch1.info.hotX=ch2.info.hotX AND ch1.info.hotY=ch2.info.hotY]}; HashCursorHandle: PROC [key: REF ANY] RETURNS [CARDINAL] --RefTab.HashProc-- ~ { ch: CursorHandle ~ NARROW[key]; hotHash: INT ~ INT[ch.info.hotX + 5] * ch.info.hotY; ans: CARDINAL ¬ (hotHash MOD 37) + 37; FOR i: NAT IN [0..16) DO ans ¬ ans*9 + ch.bits[i] ENDLOOP; RETURN [ans]}; getDecodingCharUsage: ROPE ¬ "GetDecodingChar "; GetDecodingCharCmd: PROC [cmd: Commander.Handle] RETURNS [result: REF ANY ¬ NIL, msg: ROPE ¬ NIL] ~ { argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd]; buffer: REF TEXT ~ RefText.New[256]; plainName: ROPE; otherBytes, singleBytes: INT; codedIn, plainIn, plainOut: IO.STREAM ¬ NIL; testChar: CHAR; IF argv.argc#5 THEN RETURN [$Failure, getDecodingCharUsage]; otherBytes ¬ Convert.IntFromRope[argv[3]]; singleBytes ¬ Convert.IntFromRope[argv[4]]; plainName ¬ argv[1].Concat[".plain"]; codedIn ¬ FS.StreamOpen[argv[1]]; plainIn ¬ CodeControl.CreateDecodingStream[codedIn, argv[2]]; plainOut ¬ FS.StreamOpen[plainName, create]; cmd.out.PutF1["Skipping first %g bytes.\n", [integer[otherBytes]] ]; WHILE otherBytes>0 DO ask: INT ¬ MIN[otherBytes, 256]; got: INT ¬ plainIn.GetBlock[block: buffer, count: ask]; IF got#ask THEN RETURN [$Failure, IO.PutFR["Got %g instead of %g at %g", [integer[got]], [integer[ask]], [integer[otherBytes]] ]]; plainOut.PutBlock[block: buffer, count: got]; otherBytes ¬ otherBytes - got; ENDLOOP; cmd.out.PutF1["Next %g bytes are:", [integer[singleBytes]] ]; FOR i: INT IN [0..singleBytes) DO testChar ¬ plainIn.GetChar[ !IO.EndOfStream => {cmd.out.PutRope[" --EndOfStream"]; EXIT}]; IF (i MOD 4) = 0 THEN cmd.out.PutChar[' ]; cmd.out.PutF1["%02x", [integer[testChar.ORD]]]; plainOut.PutChar[testChar]; ENDLOOP; cmd.out.PutChar['\n]; plainIn.Close[]; plainOut.Close[]; RETURN}; tapFileUsage: ROPE ¬ "TapFile ( ((+|-)timing) | -codeMethod | -version )* "; TapFileCmd: PROC [cmd: Commander.Handle] RETURNS [result: REF ANY ¬ NIL, msg: ROPE ¬ NIL] ~ { argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd]; fileName: ROPE ¬ NIL; codingMethod: ROPE ¬ NIL; version: BYTE ¬ 16; timing: BOOL ¬ TRUE; i: NAT ¬ 1; IF argv.argc<2 THEN RETURN [$Null, tapFileUsage]; WHILE i < argv.argc DO SELECT TRUE FROM argv[i].Equal["-codeMethod", FALSE] => IF (i ¬ i.SUCC) < argv.argc THEN codingMethod ¬ argv[i] ELSE RETURN [$Failure, tapFileUsage]; argv[i].Equal["-version", FALSE] => IF (i ¬ i.SUCC) < argv.argc THEN version ¬ Convert.IntFromRope[argv[i]] ELSE RETURN [$Failure, tapFileUsage]; argv[i].Equal["+timing"] => timing ¬ TRUE; argv[i].Equal["-timing"] => timing ¬ FALSE; ENDCASE => { IF i.SUCC#argv.argc THEN RETURN [$Failure, tapFileUsage]; fileName ¬ argv[i]; EXIT}; i ¬ i.SUCC; ENDLOOP; cmd.out.PutFL["Tapping %g (codeMethod=\"%q\", version=%g, timing=%g).\n", LIST[[rope[fileName]], [rope[codingMethod]], [integer[version]], [boolean[timing]]] ]; TapFile[fileName, codingMethod, version, timing]; cmd.out.PutF1["Done tapping %g.\n", [rope[fileName]] ]; RETURN}; optionDesc: ROPE ¬ "((+|-)(logInput|logPlainOutput|logCodedOutput|viewInput|viewDetails|code) | -codeMethod )* --- set flg(s)"; optionUsage: ROPE ¬ Rope.Concat["RemoteViewersTerminalByViewerOption ", optionDesc]; OptionCmd: PROC [cmd: Commander.Handle] RETURNS [result: REF ANY ¬ NIL, msg: ROPE ¬ NIL] ~ { argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd]; Set: PROC [name: ROPE, sense: BOOL] RETURNS [BOOL] ~ { SELECT TRUE FROM name.Equal["logInput", FALSE] => logInput ¬ sense; name.Equal["logPlainOutput", FALSE] => logPlainOutput ¬ sense; name.Equal["logCodedOutput", FALSE] => logCodedOutput ¬ sense; name.Equal["viewInput", FALSE] => viewInput ¬ sense; name.Equal["viewDetails", FALSE] => viewDetails ¬ sense; name.Equal["code", FALSE] => willingToCode ¬ sense; ENDCASE => RETURN [TRUE]; RETURN [FALSE]}; i: NAT ¬ 1; IF argv.argc<1 THEN RETURN [$Null, optionUsage]; WHILE i < argv.argc DO SELECT TRUE FROM argv[i].Length = 0 => RETURN [$Failure, optionUsage]; argv[i].Equal["-codeMethod", FALSE] => IF (i ¬ i.SUCC) < argv.argc THEN codingMethod ¬ argv[i] ELSE RETURN [$Failure, optionUsage]; argv[i].Fetch[0] = '+ => IF Set[argv[i].Substr[1], TRUE] THEN RETURN [$Failure, optionUsage]; argv[i].Fetch[0] = '- => IF Set[argv[i].Substr[1], FALSE] THEN RETURN [$Failure, optionUsage]; ENDCASE => RETURN [$Failure, optionUsage]; i ¬ i.SUCC; ENDLOOP; cmd.out.PutFL["RemoteViewersTerminalByViewers options are: logInput=%g, logPlainOutput=%g, logCodedOutput=%g, viewInput=%g, viewDetails=%g, code=%g, codeMethod=%g.\n", LIST[ [boolean[logInput]], [boolean[logPlainOutput]], [boolean[logCodedOutput]], [boolean[viewInput]], [boolean[viewDetails]], [boolean[willingToCode]], [rope[codingMethod]] ]]; RETURN}; MakeAnother: PROC ~ { PFS.DoInWDir[installDir, TryForAnother]; RETURN}; TryForAnother: PROC ~ { [] ¬ CommanderOps.DoCommand["Run -a RemoteViewersTerminalByViewer", NIL]; RETURN}; Start: PROC ~ { style: ROPE ~ "Viewer"; sst: ServerState ~ NEW [ServerStateRecord ¬ [ theSize: [ UserProfile.Number["RemoteTerminal.DefaultWidth", 700], UserProfile.Number["RemoteTerminal.DefaultHeight", 750]] ]]; server: RVTK.ViewersServer ~ NEW [RVTK.ViewersServerPrivate ¬ [style, StartServing, Work, StopServing, Wake, sst]]; Commander.Register["RemoteViewersTerminalByViewerOption", OptionCmd, optionDesc]; Commander.Register["TapFile", TapFileCmd, tapFileUsage]; Commander.Register["GetDecodingChar", GetDecodingCharCmd, getDecodingCharUsage]; sst.upTime ¬ Histograms.Create1D[factor: dTime, offset: minTime]; sst.downTime ¬ Histograms.Create1D[factor: dTime, offset: minTime]; sst.roundTime ¬ Histograms.Create1D[factor: dTime, offset: -1.0]; sst.th ¬ NEW [TimingHistoryPrivate ¬ []]; sst.xlateKC ¬ TerminalSender.FromUihToDcedar[stdIncreek]; theServer ¬ server; theSST ¬ sst; server.AddViewersServer[]; RVTK.SetViewersImpl[[min: 10, max: 17], style, MakeAnother]; RETURN}; Start[]; END. € RemoteViewersTerminalByViewer.mesa Copyright Ó 1990, 1992 by Xerox Corporation. All rights reserved. Last tweaked by Mike Spreitzer on July 21, 1992 1:45 pm PDT Cedardummy, June 17, 1988 10:24:12 am PDT Willie-s, April 22, 1992 11:49 am PDT Christian Jacobi, April 23, 1992 5:32 pm PDT Keys and mouse buttons are normally up. The state last reported through FilterGetAction : Ê$ï–(cedarcode) style•NewlineDelimiter ™code™"Kšœ Ïeœ7™BK™;K™)K™%K™,—K˜KšÏk œœ]žœœ œœœœžœœ œœ3œžœœœ œœ œœœœœãœR˜ÆK˜šÏnœžœž˜,KšžœœTžœœ œœžœœ œœœœ,žœœœ’œ¶˜ÃK˜—K˜KšžœžœžœžœžœŸœžœžœžœžœ˜ÇK˜Kšœžœžœ Ïc˜Kšœžœ&˜>Kšœžœ˜/Kšœ žœ˜Kšœ žœ˜Kšœžœ˜Kšœ  œC˜vK˜Kšœ žœžœ˜K˜šœ žœ žœ ˜&K˜ Kšœ žœ1˜>K˜—šœ žœ žœ ˜&K˜ šœ žœ6˜CK˜-—K˜—šœ œžœ žœ ˜'K˜ Kšœ žœ%˜2K˜—K˜šŸœžœžœ˜-K˜.K˜WK˜)Kšžœ˜—K˜šŸ œžœžœ žœ˜8Kšžœžœžœ˜Kšœžœ˜'Kšžœžœ žœžœ˜+Kšœ˜Kšžœžœžœ˜:Kšžœžœ˜&Kšžœ˜—K˜š Ÿ œžœžœ žœžœ ˜IKšžœžœžœ˜Kšœžœ˜'Kšžœžœžœžœ˜0Kšžœ˜Kšžœ˜—K˜šŸœžœ žœžœžœžœžœ žœŸœžœ˜–Kšœžœ˜'K˜Kšœ žœžœžœ˜Kšœ"žœ˜&Kšœžœžœ˜KšœCžœžœžœ˜SKšœ0žœ˜4Kšœ.žœ˜2Kšœžœ˜šŸœžœžœ˜Kšžœžœžœžœžœžœžœ$˜\Kšœ˜Kš žœ žœ žœEžœ žœ˜yKš žœžœžœKžœ žœ˜‹šžœ žœ˜K˜*K˜&—K˜K˜K˜K˜K˜7Kšœžœ˜Kšžœ žœžœJ˜^KšžœžœžœF˜VKšœžœ5˜?Kšœžœ˜K˜)K˜R˜=Kšœžœ#˜+Kšœžœ˜—K˜%K˜«K˜Kšžœ˜—šŸœžœ˜KšžœžœŽžœ(˜ÎKšœ˜Kšžœ˜—šŸœžœžœ˜Kšžœžœžœ˜Kšœ0˜0Kšœ˜Kšœ%˜%Kš žœžœžœžœ žœ˜GKš žœžœžœžœ žœ˜GKšžœ5žœž Pœ˜’šžœžœ˜Kšœ žœžœ˜&Kšœ˜—Kšœ˜Kšœužœ(˜¡Kšžœ˜—Kšœ žœ˜Kšœžœ˜K˜8˜Fšžœ ž˜šžœžœ˜Kšžœ2˜6Kšžœ5˜9—Kšžœ ˜$—Kšžœ;˜?—šžœ žœžœ˜Kšœžœžœžœ˜PKšœžœžœ"˜TKš žœžœžœKžœ žœ˜‹Kšžœžœžœ?˜Yšžœžœe˜sKšœ“žœP˜çKšžœ˜Kšžœ ˜—Kšžœ˜K˜5K˜ Kšœ ˜ Kšœžœžœ˜KšžœžœžœTžœ˜Kšžœžœ.˜JKšœ,˜,Kš œ+žœžœžœžœ 9œð˜…K˜K˜ Kšžœ%˜*—K˜Kšžœ˜Kšœ+žœ˜/—K˜š Ÿ œžœ-žœžœžœžœ˜mKšœžœ ˜#K˜#Kšžœ˜—K˜šŸœžœ žœžœ žœžœ žœžœžœ˜lKšžœžœNžœ˜aKšœ žœ˜Kšœ žœžœžœ˜.Kšœ žœžœžœ˜'K˜ K˜šžœ ž˜K˜Kšœžœ˜Kšžœžœ7˜H—K˜Kšžœ žœžœžœ#˜GKšžœ žœžœ7˜Qšžœžœ˜K˜šžœ ž˜Kšœžœ˜Kšœžœ˜Kšžœžœ7˜H——Kšžœ žœ˜Kšœ žœ˜Kšžœ˜—K˜š Ÿœžœžœžœžœ4˜~Kšœžœ˜šžœ žœžœžœ˜*˜<šœ ˜ Kšœ=žœ˜CKšœžœ žœ˜0—KšœŸ œ Ÿ œ˜BKšŸœ$˜3—K˜-K˜+Kšœcžœžœ˜ŒKšœ+žœ˜2Kšœažœžœ˜ŠKšœ+žœ˜2Kšœfžœžœ˜Kšœ,žœ˜3Kšœ`žœ žœ˜vKšœ9žœžœ˜GK˜—šžœ˜K˜$K˜—K˜&Kšžœ˜—K˜šŸœžœžœžœ3žœ)žœ'  œžœ˜ãKšœžœ ˜#Kšœ˜Kšœ žœ˜Kšžœžœž œ˜0šž˜Kšœ(˜(Kš žœžœžœžœžœ ˜Pšœžœžœ˜Kšœžœ'˜EKšžœ žœžœ%˜?—K˜SKšžœžœžœ$˜CK˜EKšžœžœžœ˜!Kšžœžœ3˜Ušžœžœ˜Kšžœžœžœ˜-K˜K˜šœ%žœ˜?Kšžœžœ˜7—Kšžœžœ žœžœ˜!K˜!K˜Kšžœ˜ —šžœ ž˜Kšœžœ$žœ˜MKšœžœ˜(Kšœ žœžœžœ˜MKšžœžœ˜—Kšžœžœ žœžœ˜!K˜!K˜Kšžœ˜ Kšžœ!˜&Kšœ˜šžœžœ˜&Kšœžœ-˜?KšžœE˜K—šžœžœžœ˜=KšžœžœD˜O—Kšžœ˜—K˜—K˜š Ÿœžœžœžœ žœ˜DKšÏgœžœ ˜Kš¡œžœ ¡œ˜K˜Kšœ žœ ˜Kšœžœ˜Kšœžœ˜šŸœžœžœ˜Kšžœžœžœ˜K˜Kšžœ˜—šŸœžœžœ˜Kšžœžœžœ˜Kšžœžœžœ˜$šžœžœ˜K˜K˜K˜ Kš œ ¡œ ¡œ¡œ¡œ˜4—Kš œžœžœLžœYžœžœg˜¸Kšœ˜Kšžœ˜ —K˜K˜0šžœž˜Kšœ ˜ Kšœ0˜0K˜Kš žœžœžœžœžœ,˜OKšžœ˜—K˜Kšžœ˜—K˜šŸ œžœ$˜3K˜šžœžœ˜Kš Ÿ œžœžœžœžœ˜;Kšœžœ˜Kšœ˜K˜—Kšžœ˜—K˜šŸ œžœžœ˜AK˜Kšœ&˜&Kšœžœ˜Kšœžœ˜%šŸœžœžœžœ˜ šžœ˜Kš žœ žœžœ žœžœžœ˜NKšœžœ˜K˜—Kšœ žœ˜KšœV˜VKšžœ˜—Kšžœžœžœ˜"Kšžœ˜—K˜šŸ œžœžœ˜FKšœžœ˜%Kšœžœžœ ˜.Kšœžœžœ ˜.Kšœžœžœ ˜.K˜Kšœ6˜6Kšœ8˜8Kšœ6˜6Kšžœžœ˜+Kšžœžœ˜2Kšœ$˜$Kšœžœ˜+Kšœžœ˜+K˜'K˜*Kšžœ˜Kšœžœžœ"˜6K˜!—K˜šŸœžœžœ ˜3Kšžœžœžœ˜K˜Kšžœžœžœ'žœ˜hKšžœ˜—K˜š Ÿœžœžœžœ žœ˜VKšœžœ ˜&šžœ žœž˜šœ˜šœžœ˜%Kšœ5žœ˜˜>Kšœ˜Kšžœžœžœžœ ˜@Kšžœ˜—K˜K˜Kšžœ˜—K˜šŸ œžœ˜&K˜©K˜ŸK˜›Kšžœ˜—K˜šŸœžœ6žœ˜kKšœžœ˜Kšœžœ˜ K˜2Kšžœ žœežœ/˜©K˜ K˜Kšžœžœ˜)Kšœžœ$˜,Kšžœ žœzžœR˜áKšžœ˜ Kšœ žœžœ˜—K˜š Ÿœžœžœžœžœžœ˜5šžœž˜Kšœžœžœžœ˜(Kšžœžœžœ˜——K˜šŸœžœžœžœžœžœ œ˜UKšœžœ˜!Kšœžœ˜!Kšžœžœžœ˜\—K˜šŸœžœžœžœžœžœ œ˜PKšœžœ˜Kšœ žœžœ"˜4Kšœžœ žœ ˜&Kš žœžœžœ žœžœ˜:Kšžœ˜—K˜KšœžœH˜bK˜šŸœžœžœ žœžœžœžœžœ˜eK˜Kšœžœ˜>Kšœžœ˜4Kšœžœ˜8Kšœžœ˜3Kšžœžœžœ˜—Kšžœžœ˜—Kšœžœ˜ Kšžœ žœžœ˜0šžœž˜šžœžœž˜Kšœžœ˜5šœžœžœžœ ˜BKšžœ˜Kšžœžœ˜$—Kš œžœžœžœžœ˜]Kš œžœžœžœžœ˜^Kšžœžœ˜*—Kšœžœ˜ Kšžœ˜—Kšœ¨žœ­˜ÙKšžœ˜—K˜šŸ œžœ˜Kšžœ%˜(Kšžœ˜—K˜šŸ œžœ˜KšœDžœ˜IKšžœ˜—K˜šŸœžœ˜Kšœžœ ˜šœžœ˜-˜ K˜7K˜8—K˜—KšœžœžœžœM˜sK˜QKšœ8˜8KšœP˜PK˜AK˜CK˜AKšœ žœ˜)K˜9K˜K˜ Kšœ˜Kšžœ8˜