RemoteViewersHostImpl.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.
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;
<<MaybeSpyInternal[];>>
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<<screen#0>>]]]]
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<<IF pd.mp.color THEN 1 ELSE 0>>];
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 <methodName>)* --- 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.