DIRECTORY Atom, BasicTime, Buttons, Carets, CodeControl, Commander, CommanderOps, Convert, EchoStream, FS, HostAndTerminalOps, Imager, ImagerBackdoor, ImagerBitmapContext, ImagerColor, ImagerForkContext, ImagerPath, ImagerSample, ImagerSender, IO, IOClasses, IOErrorFormatting, KeyMapping, KeySyms1, KeySymsCedar, KeySymsKB, KeySymsPrincOpsConvention, KeyTypes, MessageWindow, NetAddressing, NetworkStream, Process, Real, RealFns, RefTab, RemoteEventTime, RemoteImagerDataTypes, RemoteViewersHost, RemoteViewersHostBackdoor, Rope, SF, SimpleFeedback, TerminalLocation, TerminalReceiver, Termination, TIPKeyboards, UserInput, UserInputGetActions, UserInputOps, ViewerClasses, ViewerLocks, ViewerOps, ViewerPrivate, ViewerSpecs, ViewersWorld, ViewersWorldClasses, ViewersWorldInitializations, WindowManager; RemoteViewersHostImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, Buttons, Carets, CodeControl, Commander, CommanderOps, Convert, EchoStream, FS, HostAndTerminalOps, Imager, ImagerBackdoor, ImagerBitmapContext, ImagerColor, ImagerForkContext, ImagerSample, ImagerSender, IO, IOClasses, IOErrorFormatting, KeyMapping, MessageWindow, NetAddressing, NetworkStream, Process, Real, RealFns, RefTab, RemoteEventTime, Rope, SimpleFeedback, TerminalLocation, TerminalReceiver, Termination, TIPKeyboards, UserInputGetActions, UserInputOps, ViewerLocks, ViewerOps, ViewerPrivate, ViewerSpecs, ViewersWorld, ViewersWorldClasses, ViewersWorldInitializations EXPORTS RemoteViewersHost, RemoteViewersHostBackdoor = BEGIN OPEN RemoteImagerDataTypes, HAT:HostAndTerminalOps, NA:NetAddressing, RET:RemoteEventTime, TL:TerminalLocation, VWC:ViewersWorldClasses; ROPE: TYPE ~ Rope.ROPE; LocPair: TYPE ~ RECORD [cancl, pretty: REF TL.RemoteLocation]; PushData: TYPE ~ REF PushDataPrivate; PushDataPrivate: TYPE ~ RECORD [ rloc: LocPair, coding, sendTiming, loggingNet, loggingPlain, begun: BOOL ¬ FALSE, netInStream: IO.STREAM ¬ NIL, netOutStream: IO.STREAM ¬ NIL, --the raw seething network stream netLogStream: IO.STREAM ¬ NIL, --log of what goes to netOutStream codedStream: IO.STREAM ¬ NIL, --forks to netOutStream and netLogStream codingStream: IO.STREAM ¬ NIL, --compresses to codedStream plainLogStream: IO.STREAM ¬ NIL, --log of plaintext output outStream: IO.STREAM ¬ NIL, --forks to plainLogStream and codingStream ish: ImagerSender.Handle ¬ NIL, logFlushClock: NATURAL ¬ 0, version: BYTE ¬ 0, mp: MousePosition ¬ [0, FALSE, 0], cutBuffers: CutBufferList ¬ NIL, cutBufferChange: CONDITION ]; CutBufferList: TYPE ~ LIST OF RECORD [buffer: ATOM, key: CARD, data: ROPE]; myVersion: BYTE ¬ 17; myOldestCompatibleVersion: BYTE ¬ 10; codingMethod: ROPE ¬ "B4KS"; compressable: BOOL ¬ TRUE; reject, sendTiming: BOOL ¬ FALSE; debug: BOOL ¬ TRUE; debugInput: BOOL ¬ FALSE; logInput: BOOL ¬ FALSE; logNet, logPlain: BOOL ¬ FALSE; sizeChangeable: BOOL ¬ TRUE; adjustTime: BOOL ¬ FALSE; replyToTimeProbes: BOOL ¬ TRUE; beingAdvised: BOOL ¬ FALSE; --advice temporarily decomissioned July 15, 1991 cc: BOOL ¬ FALSE; --cc temporarily decommissioned well before July 16, 1991 inputLogFmt: ROPE ¬ "-vux:/tmp/RemoteViewersInput/%g.log"; netLogFmt: ROPE ¬ "-vux:/tmp/ViewersTap/%g.netBytes"; plainLogFmt: ROPE ¬ "-vux:/tmp/ViewersTap/%g.plainBytes"; undefinedOnly: TL.LocState ¬ TL.CreateSingleLocState[[undefined[]], [undefined[]]]; localOnly: TL.LocState ¬ TL.CreateSingleLocState[[local[]], [local[]]]; greeting: ROPE ¬ "Radically new RemoteViewersHost of Groundhog Day!"; change: CONDITION; started: BOOL ¬ FALSE; locState: TL.LocState ¬ undefinedOnly; comingState: TL.LocState ¬ locState; primaryPD: PushData ¬ NIL; allPD: TL.LocSet--canonical loc -> PushData-- ¬ TL.CreateLocSet[FALSE]; screenSettingses: ScreenSettingses ¬ MakeInitialScreenSettingses[]; backingSm: Imager.SampleMap ¬ NIL; curHot: VECI ¬ [0, 0]; curCursorPattern: CursorArray ¬ ALL[0]; curCursorPatternName: ATOM ¬ NIL; inputReceiver: PROCESS ¬ NIL; connClose, connBreak: TL.Why ¬ [BasicTime.Now[], NIL]; Spy: PROC [IO.STREAM, NA.Address] ¬ NIL; vw: ViewersWorld.Ref ¬ NIL; userInput: UserInput.Handle ¬ NIL; lcAdded: BOOL ¬ FALSE; fileStream: IO.STREAM ¬ NIL; gmt1: BasicTime.GMT ¬ BasicTime.Now[].Update[-3600] --hope clocks disagree by less than 1 hr--; et1: RET.EventTime ¬ RET.FromEGMT[[gmt1, 0]]; et0: RET.EventTime ¬ et1.Sub[RET.SmallConsCC[0, 1]]; MakeInitialScreenSettingses: PROC RETURNS [sss: ScreenSettingses] ~ { sss ¬ NEW [ScreenSettingsesPrivate[1]]; sss[0] ¬ [methods: LIST["black and white"], sizes: LIST[[1000, 1000]]]; RETURN}; keyMapping: KeyMapping.Mapping ¬ CreateKeyTable[]; CreateKeyTable: PROC RETURNS [KeyMapping.Mapping] ~ { kt: KeyMapping.KeyTable ¬ NEW [KeyMapping.KeyTableRep ¬ ALL[NIL]]; FOR kn: PrincOpsKeyName IN PrincOpsKeyName DO ks0, ks1: KeyTypes.KeySym; kd: REF KeyMapping.KeySymsRep; SELECT kn FROM STUFF => {ks0 ¬ KeySymsKB.Next; ks1 ¬ KeySymsKB.R12}; USERABORT => {ks0 ¬ KeySymsCedar.Swat; ks1 ¬ KeySymsKB.R15}; Arrow => {ks0 ¬ KeySymsKB.LeftArrow; ks1 ¬ KeySymsKB.UpArrow}; Dash => {ks0 ¬ KeySyms1.Hyphen; ks1 ¬ KeySyms1.LowLine}; EXPAND => {ks0 ¬ KeySymsKB.RightMeta; ks1 ¬ KeySymsPrincOpsConvention.Expand}; A11 => {ks0 ¬ KeySymsKB.LeftAlt; ks1 ¬ KeySymsPrincOpsConvention.A11}; A8 => {ks0 ¬ ks1 ¬ KeySymsPrincOpsConvention.Paste}; --TIPKeyboards already says PASTE is used for LineFeed! ENDCASE => [ks0, ks1] ¬ TIPKeyboards.KeySymsFromKeyCode[VAL[kn.ORD]]; kd ¬ NEW [KeyMapping.KeySymsRep[IF ks0#ks1 THEN 2 ELSE 1]]; kd[0] ¬ ks0; IF ks0#ks1 THEN kd[1] ¬ ks1; kt[VAL[kn.ORD]] ¬ kd; ENDLOOP; RETURN [KeyMapping.NewMapping[kt, VAL[PrincOpsKeyName.LAST.ORD]]]}; lc: TL.Client ~ NEW [TL.ClientPrivate ¬ [ NoteChange: NoteChange, data: NIL]]; NoteChange: PROC [client: TL.Client, x: TL.LocState] ~ {[] ¬ SetLoc[x, FALSE]}; SetLoc: PROC [to: TL.LocState, asAdvice: BOOL] ~ { WithLock: ENTRY PROC ~ { ENABLE UNWIND => NULL; TRUSTED {comingState ¬ to}; RETURN}; WithLock[]; IF debug THEN SimpleFeedback.PutFL[$RemoteViewersHost, oneLiner, $FYI, "At %g, SetLoc[to: %g, asAdvice: %g].", LIST[[time[BasicTime.Now[]]], [rope[TL.FormatLocState[to]]], [boolean[asAdvice]] ]]; {old: Process.Priority ~ Process.GetPriority[]; Process.SetPriority[Process.priorityClient3]; TRUSTED {Process.Detach[FORK EffectChange[]]}; Process.SetPriority[old]; RETURN}}; OpenOrder: TYPE ~ RECORD [primary: BOOL, rloc: LocPair]; EffectChange: PROC ~ { opens: LIST OF OpenOrder ¬ NIL; InnerEffect: ENTRY PROC ~ { ENABLE UNWIND => NULL; to: TL.LocState ~ comingState; wasLocal, isLocal: BOOL ¬ TRUE; CloseOld: INTERNAL PROC [key, val: REF ANY] RETURNS [quit: BOOL ¬ FALSE] --RefTab.EachPairAction-- ~ { rl: REF TL.Location ~ NARROW[key]; IF to.allCancl.Fetch[key].found THEN RETURN; WITH rl SELECT FROM x: REF undefined TL.Location => wasLocal ¬ wasLocal; x: REF local TL.Location => wasLocal ¬ wasLocal; x: REF TL.RemoteLocation => { curPD: PushData ~ NARROW[val]; CloseStream[curPD]; IF NOT allPD.Delete[key] THEN ERROR; wasLocal ¬ FALSE}; ENDCASE => ERROR; }; AddNew: INTERNAL PROC [key, val: REF ANY] RETURNS [quit: BOOL ¬ FALSE] --RefTab.EachPairAction-- ~ { rl: REF TL.Location ~ NARROW[key]; cancl: TL.Location ~ rl­.Canonicalize[]; k2: REF TL.Location ~ NEW [TL.Location ¬ cancl]; found: BOOL; pda: REF ANY; [found, pda] ¬ allPD.Fetch[k2]; IF found THEN { IF cancl.LocsEqual[locState.primaryCancl] AND pda#NIL --pda=NIL means OpenStream is already coming, and that will set primaryPD-- THEN primaryPD ¬ NARROW[pda]; RETURN}; WITH rl SELECT FROM x: REF undefined TL.Location => BROADCAST change; x: REF local TL.Location => BROADCAST change; x: REF TL.RemoteLocation => { IF NOT allPD.Insert[k2, NIL] THEN ERROR; opens ¬ CONS[[primary: locState.primaryCancl.LocsEqual[cancl], rloc: [cancl: NARROW[k2], pretty: NARROW[rl]]], opens]; isLocal ¬ FALSE}; ENDCASE => ERROR; }; IF to.LocStateEqual[locState] AND started THEN RETURN; started ¬ TRUE; IF allPD.Pairs[CloseOld] THEN ERROR; TRUSTED {locState ¬ to}; IF locState.allPretty.Pairs[AddNew] THEN ERROR; RETURN}; Carets.SuspendCarets[]; ViewerLocks.CallUnderViewerTreeLock[InnerEffect]; --AMN ViewerPrivate.WaitForPaintingToFinish[]; Carets.ResumeCarets[]; FOR ol: LIST OF OpenOrder ¬ opens, ol.rest WHILE ol#NIL DO OpenStream[ol.first.rloc, ol.first.primary, ol.rest=NIL, [BasicTime.Now[], "change"], NIL]; ENDLOOP; RETURN}; SetSizes: PROC [] ~ { InnerPaint: PROC = { FOR screen: NAT IN [0 .. 1--A1S--) DO size: VECI ~ screenSettingses[screen].sizes.first; ViewersWorld.SetSize[vw, size.x, size.y, IF screen=0 THEN NIL ELSE ERROR]; --calls ViewerPrivate.SetCreator and ViewerOps.PaintEverything ENDLOOP; RETURN}; Carets.SuspendCarets[]; ViewerLocks.CallUnderViewerTreeLock[InnerPaint]; ViewerPrivate.WaitForPaintingToFinish[]; Carets.ResumeCarets[]; RETURN}; SetSpy: PUBLIC ENTRY PROC [spy: PROC [IO.STREAM, NA.Address]] ~ { ENABLE UNWIND => NULL; Spy ¬ spy; <> RETURN}; MaybeSpy: ENTRY PROC [pd: PushData] ~ { ENABLE UNWIND => NULL; pd.begun ¬ TRUE; MaybeSpyInternal[pd]; RETURN}; MaybeSpyInternal: INTERNAL PROC [pd: PushData] ~ { IF Spy#NIL AND pd#NIL AND pd.begun THEN TRUSTED {Process.Detach[FORK Spy[pd.netInStream, pd.rloc.pretty.addr]]}; RETURN}; AdviceFrom: PROC [name: ROPE] RETURNS [problem: ROPE] ~ { addr: NA.Address; {ENABLE NA.Error => {problem ¬ NA.FormatError[codes, msg]; GOTO Skipit}; addr ¬ NA.ParseAddress[name]; [] ¬ NA.ToNnAddress[addr]}; {pl: TL.Location ~ [remote[addr]]; cl: TL.Location ~ pl.Canonicalize[]; ls1: TL.LocState ¬ TL.GetLocState[].CopyLocState[]; ls1.LocStateIncrement[cl, pl]; {ls2: TL.LocState ¬ [primaryCancl: cl, primaryPretty: pl, allCancl: ls1.allCancl, allPretty: ls1.allPretty]; SetLoc[ls2, TRUE]; RETURN [NIL]}}; EXITS Skipit => name ¬ name}; StopAdvice: PROC RETURNS [problem: ROPE] ~ {SetLoc[localOnly, FALSE]; RETURN [NIL]}; ISNoteBreak: PROC [data: REF ANY, consumer: IO.STREAM, why: ROPE] ~ { pd: PushData ~ NARROW[data]; now: BasicTime.GMT ~ BasicTime.Now[]; TRUSTED {Process.Detach[FORK NoteBreakAt[pd, now, why]]}; RETURN}; NoteBreak: PROC [curPD: PushData, why: ROPE] ~ { now: BasicTime.GMT ~ BasicTime.Now[]; NoteBreakAt[curPD, now, why]; RETURN}; NoteBreakAt: PROC [curPD: PushData, now: BasicTime.GMT, why: ROPE] ~ { SetBreak: ENTRY PROC ~ { ENABLE UNWIND => NULL; connBreak ¬ [now, why]; RETURN}; MaybeReopen: ENTRY PROC ~ { ENABLE UNWIND => NULL; IF allPD.Fetch[curPD.rloc.cancl].val = curPD THEN TRUSTED { Process.Detach[FORK SimpleFeedback.PutFL[$RemoteViewersHost, oneLiner, $FYI, "Re-opening connection to %g after %g-second delay.", LIST[ [rope[TL.FormatLoc[curPD.rloc.pretty­]]], [real[retryPause/1000.0]] ]]]; Process.Detach[FORK OpenStream[curPD.rloc, curPD.rloc.cancl­.LocsEqual[locState.primaryCancl], TRUE, [now, why], curPD]]}; RETURN}; SetBreak[]; TRUSTED {Process.Detach[FORK SimpleFeedback.PutFL[$RemoteViewersHost, oneLiner, $FYI, "Connection to %g broken at %g (%g).", LIST[ [rope[TL.FormatLoc[curPD.rloc.pretty­]]], [time[now]], [rope[why]] ]]]}; Process.PauseMsec[retryPause]; MaybeReopen[]; RETURN}; retryPause: INT ¬ 5000; CheckIn: ENTRY PROC [rloc: LocPair, primary: BOOL, why: TL.Why, dedPD: PushData] RETURNS [PushData] ~ { ENABLE UNWIND => NULL; curPD: PushData ¬ NARROW[allPD.Fetch[rloc.cancl].val]; IF curPD#NIL THEN CloseStream[curPD]; IF rloc.cancl.kind # remote THEN ERROR; IF NOT locState.allCancl.Fetch[rloc.cancl].found THEN RETURN [NIL]; curPD ¬ NEW [PushDataPrivate ¬ [rloc]]; TRUSTED {Process.InitializeCondition[@curPD.cutBufferChange, Process.SecondsToTicks[5]]}; IF primary THEN primaryPD ¬ curPD; [] ¬ allPD.Store[rloc.cancl, curPD]; RETURN [curPD]}; CheckOut: ENTRY PROC [pd: PushData, sss: ScreenSettingses] RETURNS [ok: BOOL] ~ { ENABLE UNWIND => NULL; BROADCAST change; IF NOT allPD.Fetch[pd.rloc.cancl].found THEN {CloseStream[pd]; RETURN [FALSE]}; IF locState.primaryCancl.LocsEqual[pd.rloc.cancl­] THEN screenSettingses ¬ sss; RETURN [TRUE]}; OpenStream: PROC [rloc: LocPair, primary, final: BOOL, why: TL.Why, dedPD: PushData] ~ { rejection: ROPE; sss: ScreenSettingses; {pd: PushData ~ CheckIn[rloc, primary, why, dedPD]; IF debug THEN SimpleFeedback.PutFL[$RemoteViewersHost, oneLiner, $FYI, "At %g, OpenStream[rloc: %g, primary: %g].", LIST[ [time[BasicTime.Now[]]], [rope[IF pd#NIL THEN TL.FormatLoc[rloc.pretty­] ELSE "NIL"]], [boolean[primary]] ]]; IF pd=NIL THEN RETURN; {forFile: ROPE ~ IO.PutFR["%g-%g", [rope[NA.FormatAddress[rloc.pretty.addr, FALSE]]], [rope[rloc.pretty.addr.socket]] ]; nwsAddr: ROPE ~ pd.rloc.cancl.addr.ToNnAddress[!NA.Error => { connClose ¬ [BasicTime.Now[], NA.FormatError[codes, msg]]; TRUSTED {Process.Detach[FORK NoteReject[beingAdvised, rloc.pretty­, connClose, FALSE]]}; GOTO escape}].addr; IF logNet THEN pd.netLogStream ¬ FS.StreamOpen[fileName: IO.PutFR1[netLogFmt, [rope[forFile]] ], accessOptions: create, keep: 10 !FS.Error => CONTINUE]; IF logPlain THEN pd.plainLogStream ¬ FS.StreamOpen[fileName: IO.PutFR1[plainLogFmt, [rope[forFile]] ], accessOptions: create, keep: 10 !FS.Error => CONTINUE]; pd.loggingNet ¬ pd.netLogStream#NIL; pd.loggingPlain ¬ pd.plainLogStream#NIL; [pd.netInStream, pd.netOutStream] ¬ NetworkStream.CreateStreams[protocolFamily: pd.rloc.cancl.addr.protocolFamily, remote: nwsAddr, transportClass: $basicStream !NetworkStream.Error => { connClose ¬ [BasicTime.Now[], FmtNSErr[codes, msg]]; TRUSTED {Process.Detach[FORK NoteReject[beingAdvised, pd.rloc.pretty­, connClose, FALSE]]}; GOTO escape}]; pd.outStream ¬ pd.codingStream ¬ pd.codedStream ¬ IF pd.loggingNet THEN IOClasses.CreateDribbleOutputStream[output1: pd.netOutStream, output2: pd.netLogStream] ELSE pd.netOutStream; {ENABLE { IO.Error => {NoteBreak[pd, IOErrorFormatting.FormatError[ec, details, msg]]; GOTO escape}; IO.EndOfStream => {NoteBreak[pd, "IO.EndOfStream"]; GOTO escape}; CodeControl.BadCodingMethod => { NoteBreak[pd, IO.PutFR["Lookup of coding method %g raises BadCodingMethod[%g]", [rope[codingMethod]], [rope[errorMsg]] ]]; compressable ¬ FALSE; GOTO escape}; }; [rejection, pd.coding, pd.sendTiming, pd.version] ¬ Good[pd.netInStream, pd.netOutStream, compressable, sendTiming]; IF rejection=NIL THEN sss ¬ ImagerSender.Prelude[pd.netInStream]; }; TRUSTED {Process.Detach[FORK NoteReject[beingAdvised, pd.rloc.pretty­, [BasicTime.Now[], rejection], pd.coding]]}; IF rejection#NIL THEN { connClose ¬ [BasicTime.Now[], rejection]; pd.netInStream.Close[!IO.Error => CONTINUE]; pd.netOutStream.Close[!IO.Error => CONTINUE]; IF pd.loggingPlain THEN pd.plainLogStream.Close[!IO.Error => CONTINUE]; IF pd.loggingNet THEN pd.netLogStream.Close[!IO.Error => CONTINUE]; GOTO escape}; IF pd.coding THEN pd.codingStream ¬ CodeControl.CreateEncodingStream[pd.codedStream, codingMethod]; IF pd.loggingPlain THEN pd.outStream ¬ IOClasses.CreateDribbleOutputStream[output1: pd.codingStream, output2: pd.plainLogStream] ELSE pd.outStream ¬ pd.codingStream; IF pd.loggingPlain THEN pd.plainLogStream.PutChar[VAL[pd.version]]; MaybeSpy[pd]; pd.ish ¬ ImagerSender.Begin[ pd.outStream, pd.netOutStream, IF pd.netLogStream#NIL THEN pd.netLogStream ELSE pd.netOutStream, IF pd.plainLogStream#NIL THEN pd.plainLogStream ELSE pd.netOutStream, pd.version, pd.sendTiming, Push, ISNoteBreak, SampleScreen, 1, NIL, pd]; IF CheckOut[pd, sss] THEN { TRUSTED {Process.Detach[inputReceiver ¬ FORK Decode[pd, forFile]]}; IF final THEN SetSizes[]; } ELSE rejection ¬ rejection; EXITS escape => NULL; }}}; CloseStream: INTERNAL PROC [pd: PushData] ~ { ENABLE IO.Error => CONTINUE; IF pd.codingStream=NIL THEN RETURN; IF pd.loggingPlain THEN IO.Close[pd.plainLogStream, FALSE]; IF pd.coding THEN IO.Close[pd.codedStream, FALSE]; IF pd.loggingNet THEN IO.Close[pd.netLogStream, FALSE]; IO.Close[pd.netOutStream, TRUE]; --can't close a coding stream asynchronously with writing RETURN}; SampleScreen: PROC [data: REF ANY, screen: NATURAL] RETURNS [ScreenSetting] ~ { SELECT screen FROM 0 => RETURN [["BlackAndWhite", [ViewerSpecs.bwScreenWidth, ViewerSpecs.bwScreenHeight], left]]; ENDCASE => ERROR}; NoteReject: PROC [beingAdvised: BOOL, opLoc: TL.Location, break: TL.Why, coding: BOOL] ~ { SimpleFeedback.PutFL[$RemoteViewersHost, oneLiner, $Error, IF break.explanation=NIL THEN IF beingAdvised THEN "%g Accepting advice from %g.%g" ELSE IF coding THEN "%g Accepting %g as coding terminal.%g" ELSE "%g Accepting %g as plaintext terminal.%g" ELSE IF beingAdvised THEN "%g Giving up on advice from %g 'cause %g." ELSE "%g Giving up on %g as terminal 'cause %g.", LIST[ [rope[ShortFmtTime[break.time]]], [rope[TL.FormatLoc[opLoc]]], [rope[break.explanation]] ]]; IF break.explanation#NIL THEN SELECT beingAdvised FROM FALSE => TL.Abandon[opLoc, break]; TRUE => [] ¬ SetLoc[localOnly, FALSE]; ENDCASE => ERROR; }; Good: PROC [inStream, outStream: IO.STREAM, willingToCompress, maySendTiming: BOOL] RETURNS [rejection: ROPE ¬ "bug", doCompress, sendTiming: BOOL ¬ TRUE, version: BYTE ¬ 0] ~ { ENABLE IO.Error, IO.EndOfStream => {rejection ¬ "IO.Error during initial negotiations"; CONTINUE}; hisPassword, hisCode, hisReject: CHAR; hisVersion, hisOldestCompatibleVersion: BYTE; ctlVersion: NAT; [ctlVersion, rejection] ¬ TL.StartCommand[inStream, outStream, 'V, TRUE]; IF rejection#NIL THEN RETURN; outStream.PutChar[CHAR.LAST]; outStream.PutChar[VAL[myVersion]]; outStream.PutChar[VAL[myOldestCompatibleVersion]]; outStream.Flush[]; hisPassword ¬ inStream.GetChar[]; IF hisPassword#CHAR.LAST THEN RETURN ["terminal didn't properly open initial negotiations"]; hisVersion ¬ inStream.GetChar[].ORD; hisOldestCompatibleVersion ¬ inStream.GetChar[].ORD; IF hisVersion < myOldestCompatibleVersion OR myVersion < hisOldestCompatibleVersion THEN RETURN [IO.PutFLR[ "[%g..%g] <> [%g..%g] (terminal's version range vs. this host's)", LIST[ [cardinal[hisOldestCompatibleVersion]], [cardinal[hisVersion]], [cardinal[myOldestCompatibleVersion]], [cardinal[myVersion]]]]]; version ¬ MIN[hisVersion, myVersion]; outStream.PutChar[IF willingToCompress THEN 'C ELSE 'P]; outStream.PutChar[IF reject THEN 'R ELSE 'A]; IF hisVersion >= 12 THEN outStream.PutChar[IF maySendTiming THEN 'T ELSE 'N]; outStream.Flush[]; hisCode ¬ inStream.GetChar[]; hisReject ¬ inStream.GetChar[]; IF reject OR (SELECT hisReject FROM 'R => TRUE, 'A => FALSE, ENDCASE => ERROR) THEN RETURN ["terminal or host is rejecting"]; rejection ¬ NIL; doCompress ¬ willingToCompress AND hisCode='C; sendTiming ¬ hisVersion >= 12 AND maySendTiming; RETURN}; Decode: PROC [pd: PushData, forFile: ROPE] ~ { in: IO.STREAM ¬ pd.netInStream; inputLog: IO.STREAM ¬ NIL; et: EventTime ¬ RET.noEventTime; Repaint: PROC ~ TRUSTED {Process.Detach[FORK ViewerOps.PaintEverything[]]}; TakeTimeReply: PROC [org, mid: EventTime, descToo: BOOL, desc: TerminalReceiver.EventDesc] ~ {NULL}; TakeCutBuffer: ENTRY PROC [buffer: ATOM, key: CARD, data: ROPE] ~ { ENABLE UNWIND => NULL; pd.cutBuffers ¬ CONS[[buffer, key, data], pd.cutBuffers]; NOTIFY pd.cutBufferChange; RETURN}; InsertAction: PROC [ab: TerminalReceiver.ActionBody] = TRUSTED { IF debugInput THEN { SimpleFeedback.PutFL[$RemoteViewersHost, begin, $Input, "%g[t:%g, dt:%g, dv:%g, dy:%g", LIST[ [atom[ab.kind]], [cardinal[ab.eventTime]], [integer[ab.deltaTime]], [atom[IF ab.device#NIL THEN NARROW[ab.device] ELSE $NIL]], [atom[IF ab.display#NIL THEN NARROW[ab.display] ELSE $NIL]] ]]; SELECT ab.kind FROM $Key => SimpleFeedback.PutFL[$RemoteViewersHost, end, $Input, ", down:%g, kc:%g, ks:%g]", LIST[ [boolean[ab.down]], [cardinal[ab.keyCode.ORD]], [cardinal[ab.preferredSym]] ]]; $IntegerPosition, $Position => SimpleFeedback.PutFL[$RemoteViewersHost, end, $Input, ", x:%g, y:%g, rx:%g, ry:%g]", LIST[ [integer[ab.x]], [integer[ab.y]], [real[ab.rx]], [real[ab.ry]] ]]; ENDCASE => SimpleFeedback.Append[$RemoteViewersHost, end, $Input, "]"]; }; et ¬ et0.Add[RET.SmallConsCC[0, ab.eventTime]]; SELECT ab.kind FROM $IntegerPosition => pd.mp ¬ [mouseX: ab.x, mouseY: ab.y, color: DisplayToScreen[NARROW[ab.display]]#0]; $Position => pd.mp ¬ [mouseX: Real.Round[ab.rx], mouseY: Real.Round[ab.ry], color: DisplayToScreen[NARROW[ab.display]]#0]; $Key => IF ab.down AND pd.version > 10 THEN ImagerSender.SendTimeReply[pd.ish, et, RET.ReadEventTime[], [pd.mp, [contents: keyDown[value: VAL[ab.keyCode.ORD]]] ]]; ENDCASE => NULL; IF pd=primaryPD THEN UserInputGetActions.InsertInputActionBody[userInput, ab]; }; IF logInput THEN { inputLog ¬ FS.StreamOpen[IO.PutFR1[inputLogFmt, [rope[forFile]] ], create !FS.Error => CONTINUE]; IF inputLog#NIL THEN in ¬ EchoStream.CreateEchoStream[in: pd.netInStream, out: inputLog]; }; TerminalReceiver.Decode[in, inputLog, et1, pd.version, InsertAction, Repaint, TakeTimeReply, TakeCutBuffer, adjustTime !UNWIND => IF inputLog#NIL THEN inputLog.Close[!IO.Error => CONTINUE] ]; IF inputLog#NIL THEN inputLog.Close[!IO.Error => CONTINUE]; RETURN}; GetStats: PROC [flush: ImagerSender.Handle] RETURNS [deltaBits, totalBits, deltaSeconds: CARD, avgBitRate: REAL] = { newTime: BasicTime.GMT; totalBits ¬ ImagerSender.GetTotal[flush: flush]; newTime ¬ BasicTime.Now[]; deltaBits ¬ totalBits - lastTotal; lastTotal ¬ totalBits; deltaSeconds ¬ BasicTime.Period[lastTime, newTime]; lastTime ¬ newTime; avgBitRate ¬ IF deltaSeconds # 0 THEN REAL[deltaBits]/deltaSeconds ELSE 0; }; lastTime: BasicTime.GMT ¬ BasicTime.Now[]; lastTotal: CARD ¬ 0; MyCreate: PROC [screenServerData: REF ANY, screen: ViewerPrivate.Screen] RETURNS [c: Imager.Context] --ViewerPrivate.ContextCreatorProc-- = { size: SF.Vec ~ [s: ViewerSpecs.bwScreenHeight, f: ViewerSpecs.bwScreenWidth]; sm: Imager.SampleMap ¬ backingSm; IF sm=NIL OR ImagerSample.GetSize[sm] # size THEN backingSm ¬ sm ¬ ImagerSample.NewSampleMap[box: [[0, 0], size], bitsPerSample: 1]; {smc: Imager.Context ~ ImagerBitmapContext.Create[deviceSpaceSize: size, scanMode: [slow: down, fast: right], surfaceUnitsPerInch: [72.0, 72.0], pixelUnits: TRUE, fontCacheName: $Bitmap]; rcs: LIST OF Imager.Context ¬ NIL; i: INT ¬ 0; BuildSender: PROC [key, val: REF ANY] RETURNS [quit: BOOL ¬ FALSE] --RefTab.EachPairAction-- ~ { rl: REF TL.Location ~ NARROW[key]; pd: PushData ~ NARROW[val]; ci: Imager.Context; SELECT rl.kind FROM local, undefined => RETURN; remote => IF pd=NIL OR pd.ish=NIL --haven't opened yet, this context will be discarded after open-- THEN RETURN; ENDCASE => ERROR; ci ¬ ImagerSender.Create[pd.ish, screen.ORD]; IF ci=NIL THEN RETURN; {j: INT ¬ i ¬ i+1; rcs ¬ CONS[ci, rcs]; WHILE j MOD 2 = 0 DO c1: Imager.Context ~ rcs.first; c2: Imager.Context ~ rcs.rest.first; rcs ¬ rcs.rest; rcs.first ¬ ImagerForkContext.Create[c1, c2]; j ¬ j/2; ENDLOOP; RETURN}}; ImagerBitmapContext.SetBitmap[smc, sm]; IF allPD.Pairs[BuildSender] THEN ERROR; IF rcs=NIL THEN RETURN [smc]; WHILE rcs.rest#NIL DO c1: Imager.Context ~ rcs.first; c2: Imager.Context ~ rcs.rest.first; rcs ¬ rcs.rest; rcs.first ¬ ImagerForkContext.Create[c1, c2]; ENDLOOP; c ¬ ImagerForkContext.Create[smc, rcs.first]; IF NOT (cc OR beingAdvised) THEN ImagerForkContext.FakeANoImage[c, TRUE]; RETURN}}; SetCursorPattern: ENTRY PROC [screenServerData: REF, deltaX, deltaY: INTEGER, cursorPattern: CursorArray, patternName: ATOM ¬ $Unnamed, cursor: REF ¬ NIL] --VWC.SetCursorPatternProc-- ~ { ENABLE UNWIND => NULL; curHot ¬ [deltaX, deltaY]; curCursorPattern ¬ cursorPattern; curCursorPatternName ¬ patternName; IF primaryPD=NIL THEN RETURN; ImagerSender.SendInterminalSetting[primaryPD.ish, [CursorPattern[cursorPattern]]]; ImagerSender.SendInterminalSetting[primaryPD.ish, [CursorOffset[deltaX, deltaY]]]; RETURN}; GetCursorPattern: ENTRY PROC [screenServerData: REF, cursor: REF ¬ NIL] RETURNS [deltaX, deltaY: INTEGER, cursorPattern: CursorArray, patternName: ATOM] ~ { ENABLE UNWIND => NULL; RETURN [curHot.x, curHot.y, curCursorPattern, curCursorPatternName]}; SetBigCursorPattern: VWC.SetBigCursorPatternProc ~ {ERROR}; GetBigCursorPattern: VWC.GetBigCursorPatternProc ~ {ERROR}; IsBigCursorPattern: VWC.IsBigCursorPatternProc ~ {RETURN [FALSE]}; BigCursorsSupported: VWC.BigCursorsSupportedProc ~ {RETURN [FALSE]}; SetCursorColor: VWC.SetCursorColorProc ~ {RETURN}; GetCursorColor: VWC.GetCursorColorProc ~ {RETURN [Imager.black]}; SetMousePosition: PROC [screenServerData: REF, x, y: INTEGER, display: REF ¬ NIL, device: REF ¬ NIL] --VWC.SetMousePositionProc-- ~ { pd: PushData ~ primaryPD; IF pd#NIL AND pd.version > 15 THEN ImagerSender.SendInterminalSetting[pd.ish, [MousePosition[[mouseX: MAX[MIN[x, INT16.LAST], INT16.FIRST], mouseY: MAX[MIN[y, 16383], -16383], color: FALSE<>]]]] ELSE device ¬ device}; GetMousePosition: PROC [screenServerData: REF, device: REF ¬ NIL] RETURNS [x, y: INTEGER, display: REF ¬ NIL] --VWC.GetMousePositionProc-- ~ { pd: PushData ~ primaryPD; IF pd#NIL THEN RETURN [pd.mp.mouseX, pd.mp.mouseY, NIL<>]; RETURN [0, 0, NIL]}; SetCutBuffer: PROC [screenServerData: REF, buffer: ATOM, data: ROPE] --VWC.SetCutBufferProc-- ~ { pd: PushData ~ primaryPD; IF pd#NIL AND pd.version>=17 THEN ImagerSender.SendCutBuffer[pd.ish, buffer, data]; RETURN}; GetCutBuffer: ENTRY PROC [screenServerData: REF, buffer: ATOM] RETURNS [data: ROPE] --VWC.GetCutBufferProc-- ~ { ENABLE UNWIND => NULL; pd: PushData ~ primaryPD; IF pd#NIL AND pd.version>=17 THEN { bkey: CARD ~ cutBufferKey ¬ cutBufferKey+1; t1, t2: BasicTime.GMT; ImagerSender.RequestCutBuffer[pd.ish, buffer, cutBufferKey]; t1 ¬ BasicTime.Now[]; DO prev: CutBufferList ¬ NIL; cur: CutBufferList ¬ pd.cutBuffers; WHILE cur#NIL DO IF cur.first.buffer=buffer AND cur.first.key=bkey THEN { IF prev#NIL THEN prev.rest ¬ cur.rest ELSE pd.cutBuffers ¬ cur.rest; RETURN [cur.first.data]}; prev ¬ cur; cur ¬ cur.rest; ENDLOOP; t2 ¬ BasicTime.Now[]; IF BasicTime.Period[from: t1, to: t2] >= cutBufferTimeout THEN RETURN [NIL]; WAIT pd.cutBufferChange; ENDLOOP; }; RETURN [NIL]}; cutBufferKey: CARD ¬ 0; cutBufferTimeout: INT ¬ 10; Blink: PROC [screenServerData: REF, display: REF ¬ NIL, frequency: CARDINAL ¬ 750, duration: CARDINAL ¬ 500] --VWC.BlinkProc-- ~ { BlinkPD: PROC [key, val: REF ANY] RETURNS [quit: BOOL ¬ FALSE] ~ { pd: PushData ~ NARROW[val]; ImagerSender.BlinkBWDisplay[pd.ish]; RETURN}; IF allPD.Pairs[BlinkPD] THEN ERROR; RETURN}; GetDeviceSize: ENTRY PROC [screenServerData: REF, display: REF ¬ NIL] RETURNS [w, h: NAT] --VWC.GetDeviceSizeProc-- ~ { ENABLE UNWIND => NULL; sss: ScreenSettingses ~ screenSettingses; RETURN [sss[0].sizes.first.x, sss[0].sizes.first.y]}; AllocateColorMapIndex: PROC [screenServerData: REF, display: REF ¬ NIL, revokeIndex: VWC.RevokeColorMapIndexProc, clientData: REF ¬ NIL] RETURNS [index: CARDINAL] --VWC.AllocateColorMapIndexProc-- ~ { ERROR ViewersWorld.outOfColormapEntries--I can't allocate any color map indices--}; FreeColorMapIndex: PROC [screenServerData: REF, index: CARDINAL, display: REF ¬ NIL] ~ {RETURN}; SetColorMapEntry: PROC [screenServerData: REF, index: CARDINAL, display: REF ¬ NIL, red, green, blue: INTEGER] ~ {RETURN}; GetColorMapEntry: PROC [screenServerData: REF, index: CARDINAL, display: REF ¬ NIL] RETURNS [red, green, blue: INTEGER] ~ {ERROR}; Push: PROC [data: REF ANY] = { pd: PushData ~ NARROW[data]; IF pd.coding THEN IO.Flush[pd.codingStream]; IF (pd.loggingNet OR pd.loggingPlain) AND (pd.logFlushClock ¬ pd.logFlushClock+1)=logFlushPeriod THEN { IF pd.loggingNet THEN IO.Flush[pd.netLogStream]; IF pd.loggingPlain THEN IO.Flush[pd.plainLogStream]; pd.logFlushClock ¬ 0}; NetworkStream.SendSoon[pd.netOutStream, soon]; RETURN}; logFlushPeriod: NATURAL ¬ 25; soon: CARDINAL--milliseconds-- ¬ 0; ShortFmtTime: PROC [time: BasicTime.GMT] RETURNS [ROPE] ~ { up: BasicTime.Unpacked ~ BasicTime.Unpack[time]; 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]] ]]]}; DescribeStream: PROC [s: IO.STREAM] RETURNS [ROPE] ~ { IF s=NIL THEN RETURN ["NIL"]; WITH s.streamData SELECT FROM x: NetworkStream.NetworkStreamData => { pf, tc: ATOM; local, remote: ROPE; [pf, local, remote, tc] ¬ NetworkStream.GetStreamInfo[s]; RETURN IO.PutFLR["NetworkStream[rem=%g, loc=%g, pf=%g, tc=%g]", LIST[ [rope[remote]], [rope[local]], [atom[pf]], [atom[tc]] ]]}; ENDCASE => { class: ATOM ~ s.GetInfo[].class; RETURN IO.PutFR["%g[%bB]", [atom[class]], [integer[LOOPHOLE[s]]] ]}}; FmtNSErr: PROC [codes: LIST OF ATOM, msg: ROPE] RETURNS [ROPE] ~ { out: IO.STREAM ~ IO.ROS[]; out.PutRope["NetworkStream.Error["]; FOR codes ¬ codes, codes.rest WHILE codes#NIL DO out.PutF1["$%g, ", [atom[codes.first]]]; ENDLOOP; out.PutF1["\"%q\"]", [rope[msg]]]; RETURN [out.RopeFromROS[]]}; displayPrefix: ROPE ~ "Display"; DisplayToScreen: PROC [display: ATOM] RETURNS [screen: NAT] ~ { SELECT display FROM $Display0, $Main => RETURN [0]; $Display1, $Color => RETURN [1]; ENDCASE => { r: ROPE ~ Atom.GetPName[display]; IF displayPrefix.IsPrefix[r] THEN { nr: ROPE ~ r.Substr[start: displayPrefix.Length[]]; i: INT ~ Convert.IntFromRope[nr !Convert.Error => GOTO No]; IF i IN [0..NAT.LAST] THEN RETURN [i]; EXITS No => screen ¬ 0}; ERROR}}; Start: PUBLIC PROC ~ { bwSize: VECI ~ screenSettingses[0].sizes.first; class: VWC.ViewersWorldClass ~ NEW [VWC.ViewersWorldClassObj ¬ [ setCursorPattern: SetCursorPattern, getCursorPattern: GetCursorPattern, setBigCursorPattern: SetBigCursorPattern, getBigCursorPattern: GetBigCursorPattern, isBigCursorPattern: IsBigCursorPattern, bigCursorsSupported: BigCursorsSupported, setCursorColor: SetCursorColor, getCursorColor: GetCursorColor, setMousePosition: SetMousePosition, getMousePosition: GetMousePosition, setCutBuffer: SetCutBuffer, getCutBuffer: GetCutBuffer, blink: Blink, getDeviceSize: GetDeviceSize, creator: MyCreate, allocateColorMapIndex: AllocateColorMapIndex, freeColorMapIndex: FreeColorMapIndex, setColorMapEntry: SetColorMapEntry, getColorMapEntry: GetColorMapEntry]]; vw ¬ VWC.CreateViewersWorld[class, keyMapping]; ViewersWorld.SetSize[vw, bwSize.x, bwSize.y]; userInput ¬ ViewersWorld.GetInputHandle[vw]; UserInputOps.SetAbsoluteTime[handle: userInput, epochTimeStamp: [1], epochGMT: gmt1]; IF NOT lcAdded THEN {lcAdded ¬ TRUE; lc.AddClient[]}; ViewersWorldInitializations.StartInstallation[vw]; MessageWindow.Append[greeting, TRUE]; [] ¬ Buttons.Create[info: [name: "Exit"], proc: ExitClick, documentation: exitDoc]; [] ¬ Buttons.Create[info: [name: "Disco"], proc: DiscoClick]; [] ¬ Buttons.Create[info: [name: "Repaint"], proc: RepaintClick]; ViewerPrivate.CheckForEmergencySaveAllEdits[]; ViewersWorldInitializations.FinishInstallation[vw]; RETURN}; exitDoc: ROPE ~ "Exit Cedar"; ExitClick: ViewerClasses.ClickProc ~ { Process.SetPriority[Process.priorityClient3]; TRUSTED {Process.Detach[FORK AnimateShutdown[]]}; Process.PauseMsec[shutDownMsec]; Termination.QuitWorld[userMsg: "Cedar terminated because user clicked `Exit'", interceptable: FALSE]; }; shutDownMsec: NAT ¬ 5000; nSteps: NAT ¬ 6; AnimateShutdown: PROC ~ { ctx: Imager.Context ~ MyCreate[NIL, ViewerPrivate.Screen.FIRST]; cx: REAL ~ ViewerSpecs.bwScreenWidth/2.0; cy: REAL ~ ViewerSpecs.bwScreenHeight/2.0; r: REAL ¬ RealFns.SqRt[cx*cx + cy*cy]; ShutItDown: PROC ~ { r ¬ r; FOR i: NAT DECREASING IN [0..nSteps) DO StepPath: ImagerPath.PathProc ~ { Rect: PROC [f: REAL] ~ { rf: REAL ~ r*f; moveTo[[cx-rf, cy-rf]]; lineTo[[cx+rf, cy-rf]]; lineTo[[cx+rf, cy+rf]]; lineTo[[cx-rf, cy+rf]]; lineTo[[cx-rf, cy-rf]]; }; Circ: PROC [f: REAL] ~ { rf: REAL ~ r*f; moveTo[[cx-rf, cy]]; arcTo[[cx+rf, cy], [cx-rf, cy]]; RETURN}; Circ[REAL[i+1]/nSteps]; IF i>0 THEN Circ[REAL[i]/nSteps]; RETURN}; ctx.SetColor[ImagerColor.ColorFromHSV[REAL[i]/nSteps, 1.0, 1.0]]; ctx.MaskFill[StepPath, TRUE]; ENDLOOP; Process.PauseMsec[shutDownMsec]; RETURN}; ImagerBackdoor.ViewReset[ctx]; ViewerLocks.CallUnderViewerTreeLock[ShutItDown]; RETURN}; DiscoClick: ViewerClasses.ClickProc ~ { locState: TL.LocState ~ TL.GetLocState[]; SimpleFeedback.PutFL[$RemoteViewersHost, oneLiner, $FYI, "At %g, disconnecting from %g.", LIST[[time[BasicTime.Now[]]], [rope[TL.FormatLocState[locState]]] ]]; Process.PauseMsec[1000]; TL.SetState[undefinedOnly]; RETURN}; RepaintClick: ViewerClasses.ClickProc ~ { ViewerOps.PaintEverything[]; RETURN}; optionDesc: ROPE ¬ "((+|-)(logInput|logPlain|logNet|code|reject|sendTiming) | -codeMethod )* --- set flg(s)"; optionUsage: ROPE ¬ Rope.Concat["RemoteViewersHostOption ", 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["logPlain", FALSE] => logPlain ¬ sense; name.Equal["logNet", FALSE] => logNet ¬ sense; name.Equal["code", FALSE] => compressable ¬ sense; name.Equal["reject", FALSE] => reject ¬ sense; name.Equal["sendTiming", FALSE] => sendTiming ¬ 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["RemoteViewersHost options are: logInput=%g, logPlain=%g, logNet=%g, code=%g, codeMethod=%g, reject=%g, sendTiming=%g.\n", LIST[ [boolean[logInput]], [boolean[logPlain]], [boolean[logNet]], [boolean[compressable]], [rope[codingMethod]], [boolean[reject]], [boolean[sendTiming]] ]]; RETURN}; TRUSTED { ProcessSize: NAT = BYTES[PROCESS]; pb: PACKED ARRAY [0..ProcessSize) OF BYTE ¬ LOOPHOLE[Process.GetCurrent[]]; msg: IO.STREAM ¬ IO.ROS[]; FOR i: NAT IN [0..ProcessSize) DO msg.PutF1["%02x", [cardinal[pb[i]]] ] ENDLOOP; IF debug THEN SimpleFeedback.PutF[$RemoteViewersHost, oneLiner, $FYI, "RemoteViewersHost starting in process %g.", [rope[msg.RopeFromROS[]]] ]; Process.InitializeCondition[@change, Process.SecondsToTicks[120]]; Process.EnableAborts[@change]; }; HAT.SetProtocolVersionRangeForSide[Host, "Viewers", [myOldestCompatibleVersion, myVersion]]; Commander.Register["RemoteViewersHostOption", OptionCmd, optionDesc]; Start[]; END. TRemoteViewersHostImpl.mesa Copyright Ó 1990, 1992 by Xerox Corporation. All rights reserved. Last tweaked by Mike Spreitzer on July 6, 1992 3:23 pm PDT Swinehart, April 11, 1989 11:03:13 am PDT Michael Plass, September 14, 1989 3:57:37 pm PDT Christian Jacobi, July 1, 1992 12:05 pm PDT A1S: For now, make a ViewersWorld that has exactly one screen, 'cause the ViewersWorld package is buggy with respect to other numbers of screens. AMN: this monitor is nested "inside" every Viewers monitor: Viewers monitors should not be entered (unless already inside [sic]) from inside our monitor. Ê&–(cedarcode) style•NewlineDelimiter ˜code™Kšœ Ïeœ7™BK™:K™)K™0K™+—K˜K™‘K™K™™K™KšÏk œ^žœ‹žœžœ˜¥K˜šÏnœžœž˜$Kšžœ^žœžœô˜ÜKšžœ-˜4K˜—K˜Kšžœžœžœžœžœžœžœ˜ŽK˜Kšžœžœžœ˜K˜Kš œ žœžœžœžœ˜>K˜Kšœ žœžœ˜%šœžœžœ˜ K˜Kšœ5žœžœ˜BKšœ žœžœžœ˜KšœžœžœžœÏc!˜@Kšœžœžœžœ "˜AKšœ žœžœžœ (˜FKšœžœžœžœ ˜:Kšœžœžœžœ ˜:Kšœ žœžœžœ *˜FKšœžœ˜Kšœžœ˜Kšœ žœ˜Kšœžœ˜"Kšœžœ˜ Kšœž ˜K˜—K˜Kšœžœžœžœžœ žœžœžœ˜KK˜Kšœ žœ˜Kšœžœ˜%Kšœžœ ˜Kšœžœžœ˜Kšœžœžœ˜!Kšœžœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœžœžœ 0˜LKšœžœžœ 9˜KKšœ žœ)˜:Kšœ žœ&˜5Kšœ žœ(˜9Kšœžœ žœ4˜SKšœ žœ žœ,˜GKšœ žœ7˜EK˜Kšœž œ˜Kšœ žœžœ˜Kšœ žœ˜&Kšœ žœ˜$Kšœžœ˜Kš œžœ œžœžœ˜GK˜CKšœžœ˜"Kšœžœ ˜Kšœ žœ˜'Kšœžœžœ˜!Kšœžœžœ˜Kšœžœžœ˜6Kš Ÿœžœžœžœžœ žœ˜(Kšœžœ˜Kšœžœ˜"Kšœ žœžœ˜Kšœ žœžœžœ˜Kšœžœ! *œ˜_Kšœžœ žœ˜-Kšœžœžœ˜4K˜šŸœžœžœ˜EKšœžœ˜'Kšœžœžœ˜GKšžœ˜—K˜Kšœ2˜2K˜šŸœžœžœ˜5Kšœžœžœžœ˜Bšžœžœž˜-K˜Kšœžœ˜šžœž˜Kšžœ+žœ˜5Kšž œ.žœ˜˜>K˜8KšžœH˜NKšžœ>žœ˜FKšžœ3 7˜lKšžœ1žœžœ˜E—Kš œžœžœ žœžœ˜;K˜ Kšžœ žœ ˜Kšœžœžœ˜Kšžœ˜—Kšžœžœžœžœ˜C—K˜šœžœ žœžœ˜)KšŸ œ ˜Kšœžœ˜ —K˜šŸ œžœ žœ žœ ˜4Kšœžœ˜—K˜šŸœžœžœžœ˜2šŸœžœžœ˜Kšžœžœžœ˜Kšžœ˜Kšžœ˜—K˜ Kšžœžœbžœ žœ.˜ÃKšœ/˜/Kšœ-˜-Kšžœžœ˜.Kšœ˜Kšžœ˜ —K˜Kšœ žœžœ žœ˜8K˜šŸ œžœ˜Kšœžœžœ žœ˜šŸ œžœžœ˜Kšžœžœžœ˜Kšœžœ˜Kšœžœžœ˜šŸœžœžœ žœžœžœžœžœ œ˜fKšœžœžœ žœ˜"Kšžœžœžœ˜,šžœžœž˜Kšœžœ žœ!˜4Kšœžœžœ!˜0šœžœžœ˜Kšœžœ˜Kšœ˜Kšžœžœžœžœ˜$Kšœ žœ˜—Kšžœžœ˜—K˜—šŸœžœžœ žœžœžœžœžœ œ˜dKšœžœžœ žœ˜"Kšœžœ˜(Kš œžœžœ žœžœ˜0Kšœžœ˜ Kšœžœžœ˜ K˜šžœžœ˜Kš žœ(žœžœ Kœžœ žœ˜ŸKšžœ˜—šžœžœž˜Kšœžœ žœ ž œ˜1Kšœžœžœ ž œ˜-šœžœžœ˜Kš žœžœžœžœžœ˜(KšœžœAžœžœ˜vKšœ žœ˜—Kšžœžœ˜—K˜—Kšžœžœ žœžœ˜6Kšœ žœ˜Kšžœžœžœ˜$Kšžœ˜Kšžœ"žœžœ˜/Kšžœ˜—K˜Kšœ2 ˜7K˜(K˜š žœžœžœžœžœž˜:Kšœ4žœžœ˜[Kšžœ˜—Kšžœ˜—K˜šŸœžœ˜šŸ œžœ˜š žœ žœžœ œž˜%Kšœžœ(˜2Kš œ)žœ žœžœžœžœ >˜‰Kšžœ˜—Kšžœ˜—K˜Kšœ0˜0K˜(K˜Kšžœ˜—K˜šŸœžœžœžœžœžœžœžœ˜AKšžœžœžœ˜K˜ K˜Kšžœ˜—K˜šŸœžœžœ˜'Kšžœžœžœ˜Kšœ žœ˜K˜Kšžœ˜—K˜šŸœžœžœ˜2Kšžœžœžœžœžœ žœžœžœ,˜pKšžœ˜—K˜š Ÿ œžœžœžœ žœ˜9Kšœžœ ˜Kš œžœžœžœžœ ˜HKšœžœ˜Kšœžœ˜Kšœžœ˜"Kšœžœ˜$Kšœžœ žœ˜3Kšœ˜Kšœžœd˜lKšœ žœ˜Kšžœžœ˜Kšžœ˜—K˜šŸ œžœžœ žœ˜(Kšœžœžœžœ˜+—K˜šŸ œžœžœžœ žœžœžœ˜EKšœžœ˜Kšœžœ˜%Kšžœžœ˜9Kšžœ˜—K˜šŸ œžœžœ˜0Kšœžœ˜%K˜Kšžœ˜—K˜šŸ œžœ"žœžœ˜FšŸœžœžœ˜Kšžœžœžœ˜K˜Kšžœ˜—šŸ œžœžœ˜Kšžœžœžœ˜šžœ+žœžœ˜;Kšœžœpžœžœ@˜ÑKšœžœLžœ˜z—Kšžœ˜—K˜ Kšžœžœažœžœ@˜ËK˜Kšœ˜Kšžœ˜Kšœ žœ˜—K˜š Ÿœžœžœžœžœžœ˜gKšžœžœžœ˜Kšœžœ˜6Kšžœžœžœ˜%Kšžœžœžœ˜'Kš žœžœ+žœžœžœ˜CKšœžœ˜'KšžœR˜YKšžœ žœ˜"K˜$Kšžœ ˜—K˜š Ÿœžœžœ'žœžœ˜QKšžœžœžœ˜Kšž œ˜Kš žœžœ"žœžœžœ˜OKšžœ1žœ˜OKšžœžœ˜—K˜šŸ œžœ!žœžœ˜XKšœ žœ˜Kšœ˜Kšœ3˜3Kšžœžœgžœ!žœžœžœžœžœ ˜çKšžœžœžœžœ˜Kš œ žœžœžœ!žœ'˜xšœ žœ#žœ ˜=Kšœžœ˜:Kšžœžœ3žœ˜XKšžœ˜—Kš žœžœžœžœGžœ žœ˜˜Kš žœ žœžœžœIžœ žœ˜žKšœ žœ˜$Kšœ$žœ˜(˜ºK˜4Kšžœžœ6žœ˜[Kšžœ ˜—Kšœ2žœžœYžœ˜µšœžœ˜ KšžœKžœ ˜ZKšžœ2žœ ˜Ašœ ˜ Kšœžœj˜zKšœžœ˜Kšžœ ˜ —K˜—K˜tKšžœ žœžœ,˜AK˜KšžœžœV˜ršžœ žœžœ˜K˜)Kšœžœ žœ˜,Kšœžœ žœ˜-Kšžœžœžœ žœ˜GKšžœžœžœ žœ˜CKšžœ ˜ —Kšžœ žœR˜cšžœ˜Kšžœi˜mKšžœ ˜$—Kšžœžœžœ˜CKšœ ˜ ˜Kšœ˜Kšžœžœžœžœ˜AKšžœžœžœžœ˜EKšœ˜Kšœ#˜#Kšžœ˜ —šžœžœ˜Kšžœ!žœ˜CKšžœžœ ˜K˜—Kšžœ˜Kšžœ žœ˜K˜—K˜šŸ œžœžœ˜-Kšžœžœ žœ˜Kšžœžœžœžœ˜#Kšžœžœžœžœ˜;Kšžœ žœžœžœ˜2Kšžœžœžœžœ˜7Kšžœžœ 9˜ZKšžœ˜—K˜š Ÿ œžœžœžœ žœžœ˜Ošžœž˜KšœžœT˜_Kšžœžœ˜——K˜š Ÿ œžœžœ žœžœžœ˜Z˜:šžœž˜šžœžœ ˜Kšžœ!˜%šžœžœ˜Kšžœ(˜,Kšžœ+˜/——šžœžœ ˜Kšžœ,˜0Kšžœ-˜1——šžœ˜K˜!Kšœžœ˜K˜——š žœžœžœžœž˜6Kšžœžœ˜"Kšžœžœ˜&Kšžœžœ˜—K˜—K˜šŸœžœžœžœ$žœžœ žœ"žœžœ žœ ˜±KšžœžœžœEžœ˜bKšœ!žœ˜&Kšœ(žœ˜-Kšœ žœ˜Kšœžœ'žœ˜IKšžœ žœžœžœ˜Kšœžœžœ˜Kšœžœ ˜"Kšœžœ˜2Kšœ˜K˜!Kš žœ žœžœžœžœ8˜\Kšœ žœ˜$Kšœ0žœ˜4š žœ(žœ(žœžœžœ˜kKšœCžœ˜HKšœ?˜?K˜@—Kšœ žœ˜%Kšœžœžœžœ˜8Kšœžœžœžœ˜-Kš žœžœžœžœžœ˜MKšœ˜K˜K˜Kšžœžœžœ žœžœžœžœžœžœžœ#˜}Kšœ žœ˜Kšœžœ ˜.Kšœžœ˜0Kšžœ˜—K˜šŸœžœžœ˜.Kšœžœžœ˜Kšœ žœžœžœ˜Kšœžœ ˜ šŸœžœ˜Kšžœžœ˜;—KšŸ œžœ žœ'žœ˜dš Ÿ œžœžœ žœžœžœ˜CKšžœžœžœ˜Kšœžœ%˜9Kšžœ˜Kšžœ˜—šŸ œžœ%žœ˜@šžœ žœ˜KšœXžœLžœ žœžœžœ žœžœ žœžœžœ žœ ˜œšžœ ž˜KšœZžœ+žœ#˜¯KšœtžœD˜¼Kšžœ@˜G—K˜—Kšœ žœ˜/šžœ ž˜KšœPžœ˜gKšœcžœ˜zKš œžœ žœžœ(žœ4žœ žœ˜£Kšžœžœ˜—Kšžœžœ:˜NK˜—šžœ žœ˜Kš œ žœ žœ0žœ žœ˜aKšžœ žœžœE˜YK˜—Kš œxžœžœ žœžœžœ žœ˜¿Kš žœ žœžœžœ žœ˜;Kšžœ˜—K˜š Ÿœžœžœ&žœžœ˜tKšœžœ˜K˜0K˜K˜"K˜K˜3K˜Kš œ žœžœžœžœ˜JK˜Kšœžœ˜*Kšœ žœ˜—K˜š Ÿœžœžœžœ žœ $œ˜KšœžœE˜MK˜!šžœžœžœ ˜,KšžœS˜W—Kšœžœ˜»Kšœžœžœžœ˜"Kšœžœ˜ šŸ œžœ žœžœžœžœžœ œ˜`Kšœžœžœ žœ˜"Kšœžœ˜Kšœ˜šžœ ž˜Kšœžœ˜Kšœ žœžœžœžœ Aœžœžœ˜pKšžœžœ˜—Kšœ(žœ˜-Kšžœžœžœžœ˜Kšœžœ ˜Kšœžœ ˜šžœžœž˜K˜K˜$K˜K˜-K˜Kšžœ˜—Kšžœ˜ —Kšœ'˜'Kšžœžœžœ˜'Kšžœžœžœžœ˜šžœ žœž˜K˜K˜$K˜K˜-Kšžœ˜—K˜-Kš žœžœžœžœ#žœ˜IKšžœ˜ —K˜šŸœžœžœžœžœ+žœžœžœ œ˜»Kšžœžœžœ˜K˜K˜!K˜#Kšžœ žœžœžœ˜KšœR˜RKšœR˜RKšžœ˜—K˜šŸœžœžœžœ žœžœžœžœ+žœ˜œKšžœžœžœ˜Kšžœ?˜E—K˜KšŸœžœžœ˜;KšŸœžœžœ˜;K˜KšŸœžœžœžœ˜BKšŸœžœžœžœ˜DK˜KšŸœžœžœ˜2KšŸœžœžœ˜AK˜šŸœžœžœžœ žœžœ žœžœ œ˜…Kšœ˜KšžœžœžœžœDžœžœžœžœžœžœ žœžœžœ˜ÌKšžœ˜—K˜šŸœžœžœ žœžœžœžœ žœžœ œ˜ŽKšœ˜Kš žœžœžœžœžœ"˜XKšžœžœ˜—K˜š Ÿ œžœžœ žœžœ œ˜aK˜Kšžœžœžœžœ2˜SKšžœ˜—K˜šŸ œžœžœžœ žœžœžœ œ˜pKšžœžœžœ˜K˜šžœžœžœžœ˜#Kšœžœ!˜+Kšœžœ˜K˜