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.