SimpleTerminalImpl.mesa
Copyright Ó 1984, 1985, 1986, 1987 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) February 20, 1985 3:38:56 pm PST
Willie-Sue, June 11, 1985 1:17:50 pm PDT
Doug Wyatt, January 17, 1987 4:50:01 pm PST
Mike Spreitzer March 4, 1987 12:05:10 pm PST
Tim Diebert: March 5, 1987 9:20:32 am PST
DIRECTORY
Ascii USING [BS, CR, SP, TAB],
BasicTime USING [FromPupTime, GMT, Now],
File USING [GetVolumeName, SystemVolume, Volume],
IO USING [card, CharsAvail, CreateStream, CreateStreamProcs, GetChar, PutChar, PutFR, PutRope, STREAM, StreamProcs, time],
Process USING [DisableTimeout, Pause, priorityRealTime, SetPriority, SetTimeout, Ticks],
Rope USING [Cat, ROPE],
SimpleTerminal USING [],
SimpleTerminalBackdoor USING [Impl, ImplRep],
SystemVersion USING [bootFileDate, release],
ThisMachine USING [Address, Name, ProcessorID],
TerminalDefs USING [KeyName, KeyState],
TTYStream USING [CreateStream];
Constraints to satisfy externally:
A statevector is needed at priority 6 for the keyboard watching process, at least until facilities exist to let it allocate one itself.
If keyboard watcher is to avoid faults, its code and global frame should be pinned.
State: TYPE = {off, turningOn, on, turningOff, joining};
Global variables protected by "simpleTerminalLock"
simpleTerminalLock: MONITORLOCK;
curImpl: Impl;
state: State ← off;
stateChange: CONDITION ← [timeout: 0];
turnOnCount: NAT ← 0;
inStream, outStream: STREAM ← NIL;
Exported to SimpleTerminal and SimpleTerminalBackdoor
SetImpl:
PUBLIC
SAFE
PROC [impl: Impl]
RETURNS [ok:
BOOL] ~
TRUSTED {
SetImplEntry:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK] ~ {
SELECT state
FROM
off => IF (ok ← curImpl=defaultImpl) THEN curImpl ← impl;
turningOn, on, turningOff, joining => ok ← FALSE;
ENDCASE => ERROR;
};
SetImplEntry[@simpleTerminalLock];
};
UnsetImpl:
PUBLIC
SAFE
PROC [impl: Impl] ~
TRUSTED {
UnsetImplEntry:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK] ~ {
UNTIL state = off DO WAIT stateChange ENDLOOP;
IF curImpl # impl THEN RETURN WITH ERROR CallerError[];
curImpl ← defaultImpl;
};
UnsetImplEntry[@simpleTerminalLock];
};
CallerError: ERROR = CODE;
TurnOn: PUBLIC SAFE PROC [nameStripe: ROPE ← NIL] RETURNS [in, out: STREAM] = CHECKED {[in, out] ← FullTurnOn[nameStripe, NIL]};
FullTurnOn:
PUBLIC
SAFE
PROC [bannerLeft, bannerRight:
ROPE ←
NIL]
RETURNS [in, out:
STREAM] =
TRUSTED {
TurnOnEntryA:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK]
RETURNS [turnOnNeeded: BOOL] = -- INLINE -- {
oldState: State = state;
DO
SELECT state
FROM
off => {
IF turnOnCount ~= 0 THEN ERROR;
curImpl.CaptureOriginalState[curImpl];
state ← turningOn;
EXIT
};
turningOff => {
Shutdown was deferred and is still pending. We simply cancel it.
state ← on;
EXIT
};
on => EXIT;
ENDCASE;
WAIT stateChange;
ENDLOOP;
curImpl.ReStart[curImpl];
turnOnCount ← turnOnCount.SUCC;
IF oldState ~= state THEN BROADCAST stateChange;
RETURN[state ~= on]
};
TurnOnEntryB:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK] =
-- INLINE -- {
state ← on;
BROADCAST stateChange;
};
IF TurnOnEntryA[@simpleTerminalLock]
THEN {
IF bannerLeft = NIL THEN bannerLeft ← DefaultBannerLeft[];
IF bannerRight = NIL THEN bannerRight ← DefaultBannerRight[];
[inStream, outStream] ← curImpl.Start[curImpl, bannerLeft, bannerRight];
TurnOnEntryB[@simpleTerminalLock];
};
RETURN [inStream, outStream]
};
DefaultBannerLeft:
PUBLIC
SAFE
PROC
RETURNS [
ROPE] ~
CHECKED {
RETURN [
IO.PutFR["Cedar %g.%g.%g of %t",
IO.card[SystemVersion.release.major],
IO.card[SystemVersion.release.minor],
IO.card[SystemVersion.release.patch],
IO.time[BasicTime.FromPupTime[SystemVersion.bootFileDate]]
]];
};
DefaultBannerRight:
PUBLIC
SAFE
PROC
RETURNS [
ROPE] ~
CHECKED {
systemVolume: File.Volume = File.SystemVolume[];
volumeLabel:
ROPE ←
IF systemVolume = NIL THEN "No System Volume"
ELSE File.GetVolumeName[systemVolume];
name: ROPE;
name ← Rope.Cat[name, ThisMachine.Name[]];
name ← Rope.Cat[name, " (", ThisMachine.Address[]];
name ← Rope.Cat[name, ", ", ThisMachine.ProcessorID[$ProductSoftware], ")"];
RETURN [volumeLabel.Cat[" on ", name]];
};
TurnOff:
PUBLIC
SAFE
PROC =
TRUSTED {
ShutdownNeeded:
SAFE
PROC
RETURNS [
BOOL] ~
TRUSTED {
TurnOffEntry:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK]
RETURNS [shutdown: BOOL] = -- INLINE -- {
DO
SELECT state
FROM
off => RETURN[FALSE];
on => EXIT;
ENDCASE => WAIT stateChange;
ENDLOOP;
IF (shutdown ← ((turnOnCount ← turnOnCount.
PRED) = 0))
THEN {
state ← turningOff;
BROADCAST stateChange;
};
};
RETURN [TurnOffEntry[@simpleTerminalLock]]};
curImpl.TurnOff[curImpl, ShutdownNeeded];
};
StartShutdown:
PUBLIC
SAFE
PROC
RETURNS [needed:
BOOL] ~
TRUSTED {
DoShutDownEntryA:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK]
RETURNS [needed: BOOL] = INLINE {
IF (needed ← state = turningOff)
THEN {
state ← joining;
BROADCAST stateChange;
};
};
needed ← DoShutDownEntryA[@simpleTerminalLock];
};
ShuttingDown:
PUBLIC
SAFE
PROC
RETURNS [
BOOL] ~
TRUSTED {
GoAway:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK]
RETURNS [
BOOLEAN] =
INLINE {
RETURN[state = joining]};
RETURN [GoAway[@simpleTerminalLock]];
};
FinishShutdown:
PUBLIC
SAFE
PROC ~
TRUSTED {
DoShutDownEntryB:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK] =
INLINE {
state ← off;
BROADCAST stateChange;
};
inStream ← outStream ← NIL;
DoShutDownEntryB[@simpleTerminalLock];
};
InputTimeout: PUBLIC SAFE SIGNAL = CODE;
SetInputTimeout:
PUBLIC
SAFE
PROC [ticks: Process.Ticks] =
CHECKED {
curImpl.SetInputTimeout[curImpl, ticks];
};
EnableCursorTracking:
PUBLIC
SAFE
PROC =
TRUSTED {
curImpl.EnableCursorTracking[curImpl];
};
DisableCursorTracking:
PUBLIC
SAFE
PROC =
TRUSTED {
curImpl.DisableCursorTracking[curImpl];
};
Default Implementation
keyboardWatcher: PROCESS;
ttyStream: IO.STREAM ← NIL;
charPos: NAT;
defaultImpl:
PUBLIC Impl ←
NEW [ImplRep ← [
CaptureOriginalState: CaptureOriginalTerminalState,
Start: TerminalStart,
ReStart: TerminalReStart,
TurnOff: TerminalTurnOff,
SetInputTimeout: SetTerminalInputTimeout,
DisableCursorTracking: DisableTerminalCursorTracking,
EnableCursorTracking: EnableTerminalCursorTracking,
data: NIL]];
CaptureOriginalTerminalState:
SAFE
PROC [impl: Impl] ~
CHECKED {
};
TerminalStart:
SAFE
PROC [impl: Impl, bannerLeft, bannerRight:
ROPE]
RETURNS [in, out:
STREAM] ~
TRUSTED {
IF ttyStream =
NIL
THEN {ttyStream ← TTYStream.CreateStream[];
IO.PutRope[ttyStream, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"];
charPos ← 1};
InitScreen[bannerLeft, bannerRight];
keyboardWatcher ← FORK ProcessKeyboard[];
in ← IO.CreateStream[inStreamProcs, NIL];
out ← IO.CreateStream[outStreamProcs, NIL];
};
TerminalReStart:
SAFE
PROC [impl: Impl] ~
CHECKED {
[] ← terminal.Select[];
};
TerminalTurnOff: SAFE PROC [impl: Impl, ShutdownNeeded: SAFE PROC RETURNS [BOOL]] ~ CHECKED { };
SetTerminalInputTimeout:
SAFE
PROC [impl: Impl, ticks: Process.Ticks] ~
TRUSTED {
SetInputTimeoutEntry:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK] =
INLINE {
IF ticks = 0 THEN Process.DisableTimeout[@charactersAvailable]
ELSE Process.SetTimeout[@charactersAvailable, ticks];
};
SetInputTimeoutEntry[@keyboardLock];
};
DisableTerminalCursorTracking: SAFE PROC [impl: Impl] ~ TRUSTED {};
EnableTerminalCursorTracking: SAFE PROC [impl: Impl] ~ TRUSTED {};
Keyboard Definitions
KeyState: TYPE = TerminalDefs.KeyState;
KeyName: TYPE ~ TerminalDefs.KeyName;
KeyItem:
TYPE ~
RECORD[normal, shift:
CHAR] ← [0C, 0C];
Keyboard Implementation
Global variables protected by "keyboardLock"
keyboardLock: MONITORLOCK;
charactersAvailable: CONDITION;
in, out: NAT;
buffer: PACKED ARRAY NAT[0..50) OF CHAR ← ALL[' ];
ProcessKeyboard:
PROC = {
GoAway:
ENTRY
PROC[m:
POINTER
TO
MONITORLOCK]
RETURNS [
BOOL] =
INLINE {
RETURN[state = joining];
};
AddToBuffer:
ENTRY
PROC[m:
POINTER
TO
MONITORLOCK, c:
CHAR] =
INLINE {
newin: NAT ← in + 1;
IF newin = buffer.LENGTH THEN newin ← 0;
IF newin ~= out THEN {buffer[in] ← c; in ← newin};
};
NotifyCharsAvailable:
ENTRY
PROC[m:
POINTER
TO
MONITORLOCK] =
INLINE {
BROADCAST charactersAvailable;
};
in ← out ← 0;
Process.SetPriority[Process.priorityRealTime];
UNTIL GoAway[@simpleTerminalLock]
DO
charsSeen: BOOL ← FALSE;
numChars: INT ← 0;
Process.Pause[1]; -- let the other guys run.
numChars ← IO.CharsAvail[ttyStream];
IF numChars # 0
THEN
BEGIN
char: CHAR ← IO.GetChar[ttyStream];
AddToBuffer[@keyboardLock, char];
NotifyCharsAvailable[@keyboardLock];
END;
ENDLOOP;
};
Display Implementation
Global variables protected by "screenLock"
screenLock: MONITORLOCK;
noTrack: CARDINAL ← 0;
InitScreen:
PROC [bannerLeft, bannerRight:
ROPE] = {
IO.PutRope[ttyStream, bannerLeft]; IO.PutChar[ttyStream, '\n]; IO.PutChar[ttyStream, '\n];
IO.PutRope[ttyStream, bannerRight]; IO.PutChar[ttyStream, '\n];
};
ClearThisChar:
INTERNAL
PROC = {};
DisplayChar:
INTERNAL
PROC [c:
CHAR] = {
SELECT c
FROM
IN [Ascii.SP..'~] => {IO.PutChar[ttyStream, c]; charPos ← charPos + 1};
Ascii.CR => {IO.PutChar[ttyStream, c]; charPos ← 0};
Ascii.
BS => {
IO.PutChar[ttyStream, c]; charPos ← charPos - 1;
IF charPos < 1 THEN charPos ← 1};
Ascii.
TAB => {
IO.PutChar[ttyStream, c]; charPos ← charPos + 1
;
UNTIL charPos MOD 8 = 0 DO
IO.PutChar[ttyStream, ' ]; charPos ← charPos + 1; ENDLOOP;
RETURN};
IN [0C..Ascii.SP) => {IO.PutChar[ttyStream, c]};
ENDCASE => RETURN;
};
IO Streams implementation
CreateStreams:
PROC = {
};
CharsAvail:
SAFE
PROC [self:
STREAM, wait:
BOOL]
RETURNS [n:
INT] =
TRUSTED {
CharsAvailEntry:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK] = {
WHILE wait AND in = out DO WAIT charactersAvailable ENDLOOP;
n ← in - out;
IF n < 0 THEN n ← n + buffer.LENGTH;
};
CharsAvailEntry[@keyboardLock];
};
EndOf:
SAFE
PROC [self:
STREAM]
RETURNS [
BOOL] =
CHECKED {
RETURN[
FALSE]};
GetChar:
SAFE
PROC [self:
STREAM]
RETURNS [c:
CHAR] =
TRUSTED {
GetEntry:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK]
RETURNS [
BOOL] =
INLINE {
ENABLE UNWIND => NULL;
WHILE in = out
DO
WAIT charactersAvailable;
IF in = out THEN RETURN[FALSE];
ENDLOOP;
c ← buffer[out];
IF (out ← out + 1) = buffer.LENGTH THEN out ← 0;
RETURN[TRUE]
};
UNTIL GetEntry[@keyboardLock] DO SIGNAL InputTimeout; ENDLOOP;
};
PutChar:
SAFE
PROC [self:
STREAM, char:
CHAR] =
TRUSTED {
PutCharEntry:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK] =
INLINE {
DisplayChar[char];
};
PutCharEntry[@screenLock];
};
EraseChar:
SAFE
PROC [self:
STREAM, char:
CHAR] =
TRUSTED {
EraseCharEntry:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK] =
INLINE {
ClearThisChar[]; -- to get rid of possible blinker
DisplayChar[Ascii.BS];
};
EraseCharEntry[@screenLock];
};
Initialization
inStreamProcs: REF IO.StreamProcs;
outStreamProcs: REF IO.StreamProcs;
Initialize:
ENTRY
PROC [m:
POINTER
TO
MONITORLOCK] = {
inStreamProcs ←
IO.CreateStreamProcs[
variety: $input, class: $SimpleTerminalTTY,
getChar: GetChar,
endOf: EndOf,
charsAvail: CharsAvail
];
outStreamProcs ←
IO.CreateStreamProcs[
variety: $output, class: $SimpleTerminalTTY,
putChar: PutChar,
eraseChar: EraseChar
];
curImpl ← defaultImpl;
};
Initialize[@simpleTerminalLock];
WSO, July 30, 1984: catch BasicTime.TimeNotKnown in call to BasicTime.Now.