DIRECTORY
Atom USING [ DottedPairNode, GetPropFromList, PutPropOnList ],
Commander USING [ CommandProc, Register ],
IO,
Lark
-- USING [
-- CommandEvents, ConnectionSpec, Event, LarkModel, Passel, StatusEvents ]--,
LarkFeep,
LarkFeepRpcControl USING [ ExportInterface, InterfaceRecord, InterfaceRecordObject ],
LarkSmartsMonitorImpl,
MBQueue USING [ Create, QueueClientAction ],
Nice,
Process USING [ Detach, SecondsToTicks, SetTimeout ],
RefID USING [ ID, nullID, Reseal, Seal, Unseal ],
Rope USING [ ROPE ],
RPC USING [ EncryptionKey, ExportFailed ],
ThParty USING [ Alert, ConversationInfo, CreateConversation, DescribeParty, GetConversationInfo, GetKeyTable, GetParty, PartyInfo, RegisterServiceInterface, ReportAction, SmartsInterfaceRecord ],
ThPartyPrivate USING [ NewId, RegisterLocal ],
Thrush
USING [
ActionID, ActionReport, ConversationID, ConvEvent, Credentials, InterfaceSpec, NetAddress, PartyID, NB, noAddress, nullConvID, nullID, Reason, ROPE, SHHH, SmartsID, StateInConv ],
ThSmartsPrivate
USING [
ChangeState, ComputeConnection, ConvDesc, EnterLarkState, Fail, ForgetConv, GetConv, GetConvDesc, GetSIC, GetSmartsInfo, HardwareInUse, HardwareRequired, InputEventSpec, LarkInfo, LarkReportAction, LarkState, OpenConversations, QueueFeeps, SmartsInfo, SmartsInfoBody ],
ThSmartsRpcControl,
Triples USING [Make ],
TU,
VoiceUtils USING [ CmdOrToken, CurrentPasskey, MakeRName, Problem, ProblemFR, ReportFR ]
;
Events
RecordTrunkEvent handles events from the EtherPhone Trunk connection.
The only interesting events at present are "ring" (place trunk->station call) and "tones F" (station->trunk call being "answered")
The upstream code produces a single "enabled" event when the line starts ringing, and a single "disabled" event when it appears that ringing has stopped. This "disabled" event must be anticipated and ignored if the station answers (calling trunk can't hang up on completed call!)
LarkTrunkParseEvent:
ENTRY
PROC[r:
REF] = {
-- r is the EventSpec
ENABLE UNWIND=>NULL;
eventSpec: ThSmartsPrivate.InputEventSpec = NARROW[r];
smartsInfo: SmartsInfo = eventSpec.smartsInfo;
sEvent: Lark.StatusEvent ← eventSpec.sEvent;
IF smartsInfo.failed THEN RETURN;
SELECT sEvent.device
FROM
ringDetect =>
SELECT sEvent.event
FROM
Lark.enabled => CmdRinging[smartsInfo];
Lark.disabled => CmdStopRinging[smartsInfo];
ENDCASE => ERROR;
ENDCASE;
};
ringProblem: ROPE = "Problem responding to phone line ringing";
ringProblemFR: ROPE = "Problem responding to phone line ringing %g";
CmdRinging:
INTERNAL
PROC[info: SmartsInfo] = {
partyID: PartyID; -- self
calledPartyRname: ROPE;
calledPartyID: PartyID;
cDesc: ConvDesc;
convEvent: Thrush.ConvEvent;
nb: Thrush.NB; {
IF ThSmartsPrivate.GetSIC[info] # $idle
THEN
RETURN;
Not a valid time for this call. Ignore. Host system will handle.
cDesc ← ThSmartsPrivate.GetConv[info, [], TRUE];
partyID ← cDesc.situation.self.partyID; -- self
[nb, calledPartyRname] ←
ThParty.DescribeParty[partyID: partyID, nameReq: $owner];
IF nb # $success OR calledPartyRname=NIL THEN GOTO LarkFailed;
[nb, calledPartyID] ← ThParty.GetParty[partyID: partyID, rName: calledPartyRname];
SELECT nb
FROM
$success => NULL;
$noSuchParty2 => GOTO LarkFailed; -- Trunk with no Lark is a fatal error
$voiceTerminalBusy, $convStillActive => GOTO Failed; -- Can't ring in right now.
ENDCASE => GOTO LarkFailed; -- None of the remaining errors are healthy
[nb, convEvent] ← ThParty.CreateConversation[
credentials: cDesc.situation.self,
state: $initiating,
checkConflict: pd.checkConflictAtConvCreate
];
SELECT nb
FROM
$success => NULL;
$voiceTerminalBusy => GOTO Failed; -- Temporary problem
ENDCASE => GOTO LarkFailed; -- All others are fatal
NoteNewTkState[cDesc, convEvent];
nb←ThParty.Alert[credentials: cDesc.situation.self, calledPartyID: calledPartyID];
By here, we have to kill the conversation before forgetting it, unless the error is fatal
SELECT nb
FROM
$success => NULL;
$noSuchParty2, $voiceTerminalBusy => GOTO ZapAndFail; -- Bitch a bit and give up
$noSuchSmarts, $noSuchParty, $noSuchConv => GOTO LarkFailed;
ENDCASE => ERROR; -- Fatally confused.
EXITS
LarkFailed => {
ThSmartsPrivate.ForgetConv[cDesc];
ThSmartsPrivate.Fail[info.larkInfo, ringProblem, TRUE];
};
Failed => {
VoiceUtils.ReportFR[ringProblemFR, $System, NIL, atom[nb]];
ThSmartsPrivate.ForgetConv[cDesc];
};
ZapAndFail => {
VoiceUtils.ProblemFR[ringProblemFR, $System, NIL, atom[nb]];
CmdStopRinging[info];
};
}};
CmdStopRinging:
INTERNAL
PROC[info: SmartsInfo] = {
cDesc: ConvDesc=ThSmartsPrivate.GetConvDesc[info];
IF cDesc=NIL THEN RETURN;
SELECT cDesc.situation.self.state
FROM
$initiating, $ringback => NULL; -- ringing stopped before called party answered.
ENDCASE => RETURN; -- already active or something; ignore end of ringing
[] ← ThSmartsPrivate.ChangeState[cDesc, $idle, $terminating];
ChangeState will give best-efforts, complaining in the system log and/or failing the hardware when there are serious problems.
};
LarkTrunkProgress:
ENTRY
PROC[
interface: ThSmartsRpcControl.InterfaceRecord,
shh: Thrush.SHHH,
convEvent: Thrush.ConvEvent
] = {
ENABLE UNWIND => NULL; -- RestoreInvariant;
Some party has changed state in a conversation we know about.
convID: Thrush.ConversationID = convEvent.self.convID;
info: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: convEvent.self.smartsID];
cDesc: ConvDesc;
whatNeedsDoing: WhatNeedsDoing;
IF info=NIL THEN { Problem["No Smarts at TrunkProgress for SmartsID %g", NIL, card[convEvent.self.smartsID]]; RETURN; };
cDesc ← ThSmartsPrivate.GetConv[ info, convEvent.self, TRUE ];
IF convEvent.self.partyID = convEvent.other.partyID
THEN {
-- own state changed
NoteNewTkState[cDesc, convEvent];
IF ThSmartsPrivate.HardwareInUse[cDesc]
THEN
DoRejectCall[cDesc, convEvent];
We have to reject this, since we're already actively dealing with another conv. We are (still) willing to seriously consider only one conversation at a time on the trunk side.
RETURN;
};
Someone else's state changed in a conv. we're interested in. See if it means anything to us.
whatNeedsDoing ← whatNeedsDoingIf[cDesc.situation.self.state][convEvent.other.state];
SELECT whatNeedsDoing
FROM
$noop, $ntiy => NULL; -- No action is needed, or we don't know what one to take.
$imp => ERROR; -- This is supposed to be impossible!
$xrep => Problem["Didn't expect state change report", info];
$frgt => ThSmartsPrivate.ForgetConv[cDesc];
$invl => {
Problem["Invalid state transition", info];
[]←ThSmartsPrivate.ChangeState[cDesc: cDesc, state: $idle,
reason: $error, comment: "System Error: Invalid state transition"];
};
$idle => {
nb2: Thrush.NB; cInfo: ThParty.ConversationInfo;
[nb2, cInfo] ← ThParty.GetConversationInfo[convID: convEvent.self.convID];
SELECT nb2
FROM
$success => NULL;
$noSuchConv => {
Problem["Conversation disappeared, forget the whole thing", info];
ThSmartsPrivate.ForgetConv[cDesc];
};
ENDCASE => ERROR;
IF (cInfo.numParties-cInfo.numIdle) <= 1
THEN
[]←ThSmartsPrivate.ChangeState[cDesc, $idle, $terminating];
};
$actv => [] ← ThSmartsPrivate.ChangeState[cDesc: cDesc, state: $active, bilateral: TRUE];
$rbak => [] ← ThSmartsPrivate.ChangeState[cDesc: cDesc, state: $ringback];
$reac => {
-- we're active and the other party goes active
larkInfo: ThSmartsPrivate.LarkInfo ← cDesc.info.larkInfo;
SetLarkTrunkState[cDesc, larkInfo.larkState,
LIST[SetupConnection[cDesc, larkInfo]]];
};
$deac => NULL;
ENDCASE => ERROR;
};
LarkTrunkSubstitution:
ENTRY
PROC[
interface: ThSmartsRpcControl.InterfaceRecord,
shh: Thrush.SHHH,
convEvent: Thrush.ConvEvent,
oldPartyID: Thrush.PartyID,
newPartyID: Thrush.PartyID
] = {
ENABLE UNWIND => NULL; -- RestoreInvariant;
A poacher has substituted for a poachee, or the other way around. Update state so that if it's us, we know who we are. I wonder how well this works.
info: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: convEvent.self.smartsID];
cDesc: ConvDesc;
IF info=NIL THEN { Problem["No Smarts at TrunkSubstitution for SmartsID %g", NIL, card[convEvent.self.smartsID]]; RETURN; };
cDesc ← ThSmartsPrivate.GetConv[ info, convEvent.self ];
IF cDesc=NIL THEN RETURN;
NoteNewTkState[cDesc, convEvent];
};
LarkTrunkCheckIn:
ENTRY
PROC[
interface: ThSmartsRpcControl.InterfaceRecord,
shh: Thrush.SHHH,
credentials: Thrush.Credentials,
reason: Thrush.Reason,
nextScheduledCheck: INT ← LAST[INT]
] = {
ENABLE
UNWIND =>
NULL;
The corresponding LarkSmarts version will take care of everything.
};
QdNotification:
ENTRY
PROC [r:
REF] ~ {
ENABLE UNWIND => NULL; -- RestoreInvariant;
cDesc: ConvDesc = NARROW[r];
IF cDesc=NIL OR cDesc.situation.self.state # $notified THEN RETURN;
[] ← ThSmartsPrivate.ChangeState[cDesc: cDesc, state: $active, bilateral: TRUE];
};
QdIdle:
ENTRY
PROC [r:
REF] ~ {
Idle after unstable-state timeout
ENABLE UNWIND => NULL; -- RestoreInvariant;
cDesc: ConvDesc = NARROW[r];
IF cDesc=NIL OR cDesc.situation.self.state = $idle THEN RETURN;
[] ← ThSmartsPrivate.ChangeState[cDesc: cDesc, state: $idle, reason: $noAnswer];
};
DoRejectCall:
INTERNAL
PROC[cDesc: ConvDesc, convEvent: Thrush.ConvEvent] = {
[]←ThSmartsPrivate.ChangeState[cDesc, $idle, $busy];
};
LimitSignalling:
ENTRY
PROC [cDesc: ConvDesc, unstableState: StateInConv] ~
TRUSTED {
We have entered an unstable state, which should not be allowed to persist too long before we hang up and free our resources. "Too long" is determined by a pd.timeoutUnstableState.
c: CONDITION;
Process.SetTimeout[@c, Process.SecondsToTicks[pd.timeoutUnstableState]];
WAIT c;
SELECT cDesc.situation.self.state FROM
# unstableState => RETURN; -- we made it.
$initiating => NULL; -- inherently unstable
$active => {
nb: Thrush.NB; cInfo: ThParty.ConversationInfo;
[nb, cInfo] ← ThParty.GetConversationInfo[convID: cDesc.situation.self.convID];
IF nb#$success --we got problems!-- OR cInfo.numActive>1 THEN RETURN;
};
ENDCASE => RETURN;
cDesc.info.requests.QueueClientAction[QdIdle, cDesc];
};
NoteNewTkState:
INTERNAL
PROC[cDesc: ConvDesc, convEvent: Thrush.ConvEvent] ~ {
OPEN now: cDesc.situation.self;
nb: Thrush.NB;
larkStateData: LIST OF REF ANY←NIL;
state: StateInConv ← convEvent.self.state;
previousState: StateInConv = cDesc.situation.self.state;
info: ThSmartsPrivate.SmartsInfo = cDesc.info;
larkInfo: ThSmartsPrivate.LarkInfo = info.larkInfo;
larkState: ThSmartsPrivate.LarkState ← $none;
cDesc.situation ← convEvent^;
IF state = previousState THEN RETURN; -- No conceivable value in acting.
SELECT state
FROM
$notified => info.requests.QueueClientAction[QdNotification, cDesc];
$initiating => TRUSTED {Process.Detach[FORK LimitSignalling[cDesc, state]]; };
$idle => {
IF larkInfo.forwardedCall THEN larkState ← $idle;
ThSmartsPrivate.ForgetConv[cDesc];
};
$neverWas => ThSmartsPrivate.ForgetConv[cDesc];
$active => {
phoneNumber: ROPE;
connectionSpec: ThParty.PartyInfo ← SetupConnection[cDesc, larkInfo];
[nb, phoneNumber] ← ThParty.DescribeParty[partyID: now.partyID, nameReq: $address];
larkState ←
IF nb # $success OR phoneNumber=NIL OR connectionSpec.conversationInfo.originator=now.partyID THEN trunkTalking ELSE trunkSignalling;
larkStateData ←
LIST[
connectionSpec,
NEW[Atom.DottedPairNode ← [$phoneNumber, phoneNumber]]];
TRUSTED {Process.Detach[FORK LimitSignalling[cDesc, state]];};
};
ENDCASE;
IF state > $idle
AND cDesc.keyTable=
NIL
THEN {
[nb, cDesc.keyTable] ← ThParty.GetKeyTable[credentials: now];
IF nb # $success THEN cDesc.keyTable←NIL;
};
SetLarkTrunkState[cDesc, larkState, larkStateData];
};
SetLarkTrunkState:
INTERNAL
PROC[
cDesc: ConvDesc, larkState: ThSmartsPrivate.LarkState, larkStateData: LIST OF REF ANY ] = {
state: StateInConv ← cDesc.situation.self.state;
larkInfo: ThSmartsPrivate.LarkInfo ← cDesc.info.larkInfo;
IF ThSmartsPrivate.HardwareInUse[cDesc]
THEN {
IF ThSmartsPrivate.HardwareRequired[cDesc]
THEN
Problem["Conflicting use of Lark at SetLarkTrunkState: ", cDesc.info];
RETURN;
};
IF ThSmartsPrivate.HardwareRequired[cDesc]
AND cDesc.keyTable#cDesc.info.larkInfo.keyTable
AND cDesc.keyTable#
NIL
THEN
larkStateData ← CONS[cDesc.keyTable, larkStateData];
IF larkState = $none THEN larkState ← cDesc.info.larkInfo.larkState;
ThSmartsPrivate.EnterLarkState[cDesc.info.larkInfo, larkState, larkStateData];
};
SetupConnection:
INTERNAL
PROC[cDesc: ConvDesc, larkInfo: ThSmartsPrivate.LarkInfo]
RETURNS[connectionSpec: ThParty.PartyInfo] = {
forwardedCall: BOOL←FALSE;
connectionSpec ← ThSmartsPrivate.ComputeConnection[cDesc];
IF connectionSpec=NIL THEN ERROR;
forwardedCall ←
connectionSpec.conversationInfo.numParties>1 AND
connectionSpec[0].socket.host # connectionSpec[1].socket.host;
larkInfo.forwardedCall ← forwardedCall;
};
Feep:
PUBLIC
ENTRY
PROCEDURE [
-- for local instance
shhh: Thrush.SHHH, serviceID: Thrush.SmartsID, convID: Thrush.ConversationID, requestingParty: Thrush.PartyID,
number: Rope.ROPE, noisy: BOOLEAN ← FALSE, on, off: CARDINAL← 177777B]
RETURNS [nb: Thrush.NB←$success] = {
RETURN[FeepOp[NIL, shhh, serviceID, convID, requestingParty, number, noisy, on, off]];
};
FeepOp:
INTERNAL
PROCEDURE [
-- Exports to voice service interface LarkFeep
interface: LarkFeepRpcControl.InterfaceRecord,
shhh: Thrush.SHHH, serviceID: Thrush.SmartsID, convID: Thrush.ConversationID, requestingParty: Thrush.PartyID,
number: Rope.ROPE, noisy: BOOLEAN ← FALSE, on, off: CARDINAL← 177777B]
RETURNS [nb: Thrush.NB←$success] = {
info: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: serviceID];
cDesc: ConvDesc;
IF info = NIL THEN RETURN[$noSuchSmarts];
cDesc ← ThSmartsPrivate.GetConv[info, [convID: convID]];
IF cDesc = NIL THEN RETURN[$notInConv];
IF cDesc.situation.self.state # $active THEN RETURN[$convNotActive];
IF noisy THEN number ← IO.PutFR["A%g", rope[number]];
IF on#177777B THEN number ← IO.PutFR["%gO%g", char[LOOPHOLE[on/10]], rope[number]];
IF off#177777B THEN number ← IO.PutFR["%gF%g", char[LOOPHOLE[off/10]], rope[number]];
ThSmartsPrivate.QueueFeeps[info, number];
};
Feep.nb~$success
Feep.nb~$noSuchSmarts -- really no such SmartsInfo
Feep.nb~$notInConv -- really no cDesc
Feep.nb~$convNotActive
FlashInfo: TYPE = REF FlashInfoBody;
FlashInfoBody:
TYPE =
RECORD [
requestingPartyID: Thrush.PartyID,
actionID: Thrush.ActionID
];
Flash:
PUBLIC
ENTRY
PROCEDURE [
-- Exports to voice service interface LarkFeep
shhh: Thrush.SHHH, serviceID: Thrush.SmartsID, convID: Thrush.ConversationID, requestingParty: Thrush.PartyID, actionID: Thrush.ActionID,
extraTime: CARDINAL← 177777B]
RETURNS [nb: Thrush.NB←$success] = {
RETURN[FlashOp[NIL, shhh, serviceID, convID, requestingParty, extraTime]];
};
FlashOp:
INTERNAL
PROCEDURE [
-- Exports to voice service interface LarkFeep
interface: LarkFeepRpcControl.InterfaceRecord,
shhh: Thrush.SHHH, serviceID: Thrush.SmartsID, convID: Thrush.ConversationID, requestingParty: Thrush.PartyID, actionID: Thrush.ActionID,
extraTime: CARDINAL← 177777B]
RETURNS [nb: Thrush.NB←$success] = {
info: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: serviceID];
cDesc: ConvDesc;
IF info = NIL THEN RETURN[$noSuchSmarts];
cDesc ← ThSmartsPrivate.GetConv[info, [convID: convID]];
IF cDesc = NIL THEN RETURN[$notInConv];
IF cDesc.situation.self.state # $active THEN RETURN[$convNotActive];
info.larkInfo.props ← Atom.PutPropOnList[info.larkInfo.props, $FlashInfo,
NEW[FlashInfoBody ← [requestingParty, actionID]]];
SetLarkTrunkState[cDesc, $trunkFlashing, NIL];
};
Flash.nb~$success
Flash.nb~$noSuchSmarts -- really no such SmartsInfo
Flash.nb~$notInConv -- really no cDesc
Flash.nb~$convNotActive
FlashDone:
PUBLIC
ENTRY
PROC[r:
REF] = {
-- r is a LarkInfo; flashing has finished
nb: Thrush.NB;
info: ThSmartsPrivate.LarkInfo = NARROW[r];
smartsInfo: SmartsInfo = IF info=NIL THEN NIL ELSE info.larkTrunkSmartsInfo;
cDesc: ConvDesc = ThSmartsPrivate.GetConvDesc[smartsInfo];
flashInfo: FlashInfo ←
IF info=
NIL
THEN
NIL
ELSE NARROW[Atom.GetPropFromList[info.props, $FlashInfo]];
IF flashInfo=NIL OR flashInfo.actionID=0 THEN RETURN;
nb ← ThParty.ReportAction[
report: [
self: cDesc.situation.self, -- placeholder
other: cDesc.situation.self,
requestingParty: flashInfo.requestingPartyID,
actionID: flashInfo.actionID,
actionClass: $switchhookFlash,
actionType: $flashed
],
reportToAll: FALSE
].nb;
IF nb#$success
THEN
IF info#NIL AND ~info.failed THEN ThSmartsPrivate.Fail[info, IO.PutFR["Couldn't report switchhook flash completion -- %g", atom[nb]], TRUE]
ELSE VoiceUtils.ProblemFR["Couldn't report switchhook flash completion, probably due to earlier failure -- %g", $System, NIL, atom[nb]];
};
LarkTrunkSmartsInit: Commander.CommandProc = {
larkFeepSpec.interfaceName.instance ← NIL;
larkFeepSpec.hostHint ← Thrush.noAddress;
larkFeepUser ← VoiceUtils.MakeRName[style: rName, name:
VoiceUtils.CmdOrToken[cmd: cmd, key: "ThrushServerInstance", default: "Strowger.Lark"]];
larkFeepPassword ← VoiceUtils.CurrentPasskey[VoiceUtils.CmdOrToken[
cmd: cmd, key: "ThrushServerPassword", default: "MFLFLX"]];
VoiceUtils.ReportFR["Initialize[LarkTrunkSmarts.Lark, %s]", $System, NIL, rope[larkFeepUser]];
};
whatNeedsDoingIf:
ARRAY StateInConv
OF
ARRAY StateInConv
OF WhatNeedsDoing ← [
If we're in the state identified by the row, and someone else in the conversation reports a transition onto the state identified by the column, what should we do?
never idle failed resrv pars init notif rback ring canAc activ inact -- ← (other)
[ $imp, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt],
--neverWas
(Clip this table to view without these comments.)
This situation arises when we've forgotten about the conversation that somebody else is still reporting on.
[ $imp, $noop, $frgt, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $ntiy ], -- idle
[ $imp, $imp, $frgt, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- failed
[ $imp, $imp, $frgt, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ],
-- reserved
The actions of other parties are not of interest to us yet, since they're not in this conv.
[ $imp, $imp, $frgt, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ],
-- parsing
Ditto.
[ $imp,
$idle,$frgt, $invl, $invl, $invl, $xrep,
$xrep,
$rbak,$actv,
$actv,
$actv ],
-- initiating
They can either enter ringing to indicate interest, or go active without ringing
If they enter ringing, we ignore it: initiating is as good as ringback for trunks.
[ $imp,
$idle,$frgt, $invl, $invl, $invl, $xrep, $noop, $noop, $ntiy, $noop, $ntiy ],
-- notified
We don't expect to hear from others while we're deciding whether to play
[ $imp,
$idle,$frgt, $invl, $invl, $invl, $xrep, $xrep,
$noop,$actv,
$actv,
$actv ],
-- ringback
They have earlier expressed interest noopringing), and are now joining the fray
[ $imp,
$idle,$frgt, $invl, $invl, $invl, $invl, $invl, $invl, $invl, $invl, $invl ],
-- ringing
The only thing that interests us here is everybody else quitting.
[ $imp, $idle,$frgt, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $noop, $ntiy ], -- canActivate
[ $imp, $idle,$frgt, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $reac, $deac ], -- active
[ $imp, $idle,$frgt, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $ntiy, $ntiy ] -- inactive (current ^)
];
}.