X11SerialDevicesImpl.mesa
Copyright Ó 1992 by Xerox Corporation. All rights reserved.
Kenneth A. Pier, October 28, 1992 2:57 pm PST
This module is used in to run serial devices in the X11ViewersWorld. Mouse and keyboard are not processed here but come from a separate stream of X events. Only serial devices such as the FastTrap trackball should use this mechanism.
Bier, September 30, 1993 10:52 am PDT
DIRECTORY
Basics, InputDevice, InputDeviceTypes, Process, RelativeTimes, UnixSysCalls, UnixTypes, UserInput, ViewersWorld, ViewersWorldInstance, ViewersWorldRefType, ViewersWorldTypes;
X11SerialDevicesImpl:
CEDAR
MONITOR
IMPORTS Basics, Process, UnixSysCalls, ViewersWorld, ViewersWorldInstance
EXPORTS InputDevice, ViewersWorldRefType ~ BEGIN
OPEN InputDevice;
Event: TYPE = InputDeviceTypes.Event;
EventBuffer: TYPE = InputDeviceTypes.EventBuffer;
EventBufferRep: TYPE = InputDeviceTypes.EventBufferRep;
HandleRep: TYPE = InputDeviceTypes.HandleRep;
Milliseconds: TYPE ~ INT;
ViewersWorldObj:
PUBLIC
TYPE = ViewersWorldTypes.ViewersWorldObj;
export to ViewersWorldRefType
DeltaTime: TYPE = RelativeTimes.DeltaTime;
h: Handle; -- the one global InputDevice.Handle in the world
registerCV: CONDITION; -- and a cv to wait on until h is non-NIL
Reopen:
PUBLIC PROC = {
eventBuffer: EventBuffer;
IF h=
NIL
THEN OpenHandle[]
-- first registration ever in this world
ELSE Close[]; -- must abort all existing reader process, then reopen
FOR i:
NAT
IN [0..h.nDevices)
DO
IF (eventBuffer ¬ h.inputs[i])#NIL THEN Register[deviceName: eventBuffer.deviceName, bufferLength: eventBuffer.length, open: eventBuffer.open, eventReader: eventBuffer.read, eventDispatch: eventBuffer.dispatch, close: eventBuffer.close, clientData: eventBuffer.clientData];
ENDLOOP;
};
OpenHandle:
SAFE
PROC =
TRUSTED {
h ¬ NEW[HandleRep];
Process.SetTimeout[@h.inputAvailable, Process.MsecToTicks[1000]];
Process.EnableAborts[@h.inputAvailable];
};
Close:
PUBLIC
ENTRY SAFE PROC [] = {
eventBuffer: EventBuffer;
IF h=NIL THEN RETURN;
FOR i:
NAT
IN [0..h.nDevices)
DO
IF (eventBuffer ¬ h.inputs[i])#
NIL
THEN {
CloseDeviceInternal[eventBuffer];
};
ENDLOOP;
};
CloseDeviceInternal:
INTERNAL PROC [b: EventBuffer] = {
inputProcess: SAFE PROCESS ¬ NIL;
IF b.active
THEN {
-- don't close it if its already closed
b.active ¬ FALSE;
IF (inputProcess ¬ GrabInputProcess[b]) #
NIL
THEN
TRUSTED {
Process.Abort[inputProcess];
JOIN inputProcess;
IF b.close#NIL THEN b.close[b.clientData]; -- call device specific close proc
};
};
};
GrabInputProcess:
<<ENTRY>>
PROC [b: EventBuffer]
RETURNS [
SAFE
PROCESS] = {
ENABLE UNWIND => NULL;
IF b #
NIL
THEN {
inputProcess: SAFE PROCESS = b.inputProcess;
b.inputProcess ¬ NIL;
BROADCAST h.inputAvailable; -- so we don't wait for a timeout on this
RETURN [inputProcess]
};
RETURN [NIL]
};
Register:
PUBLIC
ENTRY
SAFE
PROC [deviceName:
ATOM, bufferLength:
NAT ¬ 20, open: OpenProc, eventReader: EventReaderProc, eventDispatch: EventDispatchProc ¬
NIL, close: CloseProc, clientData:
REF] =
TRUSTED {
ENABLE UNWIND => NULL;
eventBuffer: EventBuffer;
notifyRegister: BOOL ← FALSE;
IF h=
NIL
THEN {
-- first registration ever in this world
OpenHandle[];
notifyRegister ← TRUE;
};
search for existing registration. If found, reregister. If not, make a new entry.
FOR i:
INT
IN [0..InputDeviceTypes.maxDevices)
DO
IF h.inputs[i] =
NIL
THEN {
-- i points to next empty array entry
eventBuffer ¬ h.inputs[i] ¬ NEW[EventBufferRep[bufferLength]];
IF i=h.nDevices THEN h.nDevices ¬ h.nDevices + 1; -- should check for overflowing maxDevices
EXIT;
};
IF h.inputs[i].deviceName=deviceName
THEN {
-- i points to existing array entry
eventBuffer ¬ h.inputs[i];
CloseDeviceInternal[eventBuffer]; -- close existing reader process
EXIT;
};
ENDLOOP;
IF eventBuffer=NIL THEN ERROR; -- sanity check
eventBuffer.active ¬ TRUE;
eventBuffer.open ¬ open;
eventBuffer.deviceName ¬ deviceName;
eventBuffer.read ¬ eventReader;
eventBuffer.dispatch ¬ eventDispatch;
eventBuffer.close ¬ close;
eventBuffer.clientData ¬ clientData;
eventBuffer.start ¬ eventBuffer.end ¬ 0;
IF open#NIL THEN eventBuffer.clientData ¬ open[clientData]; -- call the device specific open proc
Process.EnableAborts[@eventBuffer.inputWanted];
eventBuffer.inputProcess ¬ FORK InputProcess[eventBuffer, eventReader];
IF notifyRegister THEN NOTIFY registerCV;
};
UnRegister:
PUBLIC
ENTRY PROC [deviceName:
ATOM] = {
eventBuffer: EventBuffer;
IF h=NIL THEN RETURN;
FOR i:
NAT
IN [0..h.nDevices)
DO
IF (eventBuffer ¬ h.inputs[i])#
NIL
AND eventBuffer.deviceName=deviceName
THEN {
CloseDeviceInternal[eventBuffer];
h.inputs[i] ¬ NIL; -- devices that are UnRegistered will be forgotten
};
ENDLOOP;
};
GetClientData:
PUBLIC
ENTRY PROC [deviceName:
ATOM]
RETURNS [
REF] = {
eventBuffer: EventBuffer;
FOR i:
NAT
IN [0..h.nDevices)
DO
IF (eventBuffer ¬ h.inputs[i])#NIL AND eventBuffer.deviceName=deviceName THEN RETURN[eventBuffer.clientData];
ENDLOOP;
RETURN[NIL];
};
InputProcess:
SAFE
PROC [b: EventBuffer, eventReader: EventReaderProc] =
TRUSTED {
ENABLE ABORTED => CONTINUE;
Process.SetPriority[Process.priorityRealTime];
WHILE b.active
DO
NewEvents[b, eventReader[b, CheckBufferSpace[b], b.clientData]];
ENDLOOP;
};
CheckBufferSpace:
ENTRY
PROC [b: EventBuffer]
RETURNS [
NAT] =
TRUSTED {
Returns number of available slots (after maybe shifting data up)
ENABLE UNWIND => NULL;
WHILE b.active
DO
n: NAT = (b.end-b.start);
IF n = 0 THEN {b.start ¬ b.end ¬ 0; RETURN [b.length]};
IF b.start > n
OR ((b.start > 0)
AND
INT[b.length-b.end] <
INT[2])
THEN {
Basics.MoveWords[dst: LOOPHOLE[@(b[0])], src: LOOPHOLE[@(b[b.start])], count: n*WORDS[Event]];
b.start ¬ 0;
b.end ¬ n;
};
IF b.end < b.length THEN RETURN [b.length-b.end];
WAIT b.inputWanted;
ENDLOOP;
This has been closed.
b.start ¬ b.end ¬ 0;
RETURN [0];
};
NewEvents:
ENTRY
PROC [b: EventBuffer, n:
INT] = {
ENABLE UNWIND => NULL;
b.end ¬ b.end + n;
BROADCAST h.inputAvailable;
};
InOrder:
PROC [t1, t2: UnixTypes.TimeVal]
RETURNS [
BOOL] = {
RETURN [t1.sec < t2.sec OR (t1.sec = t2.sec) AND (t1.usec <= t2.usec)]
};
activeReader:
PROC [timeout: Milliseconds ¬
LAST[Milliseconds]]
RETURNS [e: Event, deviceName:
ATOM, dispatch: EventDispatchProc ¬
NIL] ¬ ThreadRead;
Read:
SAFE
PROC [timeout: Milliseconds ¬
LAST[Milliseconds]]
RETURNS [e: Event, deviceName:
ATOM, dispatch: EventDispatchProc ¬
NIL] = {
[e, deviceName, dispatch] ¬ activeReader[timeout];
};
More:
PUBLIC
ENTRY
SAFE
PROC [deviceName:
ATOM]
RETURNS [
BOOL] = {
deviceName=NIL means search every device buffer.
ENABLE UNWIND => NULL;
FOR i:
NAT
IN [0..h.nDevices)
DO
b: EventBuffer ~ h.inputs[i];
IF b=NIL THEN RETURN [FALSE]; -- this seems wrong. KAP.
IF b=NIL THEN LOOP; -- can have NIL EventBuffers due to UnRegistered devices
IF (deviceName=
NIL
OR b.deviceName=deviceName)
THEN
IF b.start < b.end AND b[b.start].time = h.lastDeliveredTime THEN RETURN [TRUE];
ENDLOOP;
RETURN [FALSE];
};
TimeoutEvent:
PROC
RETURNS [Event,
ATOM] =
TRUSTED {
now: UnixTypes.TimeVal;
tz: UnixTypes.TimeZone;
[] ¬ UnixSysCalls.GetTimeOfDay[@now, @tz];
RETURN [[id: timeoutID, pairType: none, pair: 0, value: 0, time: now], $Timeout];
};
ThreadRead:
ENTRY
PROC [timeout: Milliseconds ¬
LAST[Milliseconds]]
RETURNS [e: Event, deviceName:
ATOM, dispatch: EventDispatchProc ¬
NIL] = {
ENABLE UNWIND => NULL;
GetEvent:
INTERNAL
PROC
RETURNS [
BOOL] = {
best: EventBuffer ¬ NIL;
anyActive: BOOL ¬ FALSE;
FOR i:
NAT
IN [0..h.nDevices)
DO
b: EventBuffer ~ h.inputs[i];
IF b=NIL THEN LOOP;
anyActive ¬ anyActive OR b.active;
IF b.active
AND b.start < b.end
THEN {
IF best =
NIL
OR InOrder[b[b.start].time, e.time]
THEN {
best ¬ b;
e ¬ b[b.start];
deviceName ¬ b.deviceName;
dispatch ¬ b.dispatch;
};
};
ENDLOOP;
IF
NOT anyActive
THEN {
This handle has been closed!
e ¬ [id: timeoutID, pairType: none, pair: 0, value: 0, time: h.lastDeliveredTime];
deviceName ¬ $Closed;
RETURN [TRUE];
};
IF best = NIL THEN RETURN [FALSE];
best.start ¬ best.start + 1;
IF best.start = best.end THEN NOTIFY best.inputWanted;
RETURN [TRUE]
};
IF
NOT GetEvent[]
THEN {
IF timeout = 0
THEN [e, deviceName] ¬ TimeoutEvent[]
ELSE
TRUSTED {
IF timeout =
LAST[Milliseconds]
THEN Process.DisableTimeout[@h.inputAvailable]
ELSE Process.SetTimeout[@h.inputAvailable, Process.MsecToTicks[timeout]];
DO
WAIT h.inputAvailable;
IF GetEvent[] THEN EXIT;
IF timeout #
LAST[Milliseconds]
THEN {
[e, deviceName] ¬ TimeoutEvent[];
EXIT;
};
ENDLOOP;
};
};
h.lastDeliveredTime ¬ e.time;
};
ProcessInput:
PUBLIC
SAFE
PROC [viewersWorld: ViewersWorldRefType.Ref, goAway: GoAwayProc, updateMouse: UpdatePointerProc, updatePen: UpdatePointerProc] = {
BEGIN
ENABLE
ABORTED =>
CONTINUE;
DoProcessInput[viewersWorld, goAway, updateMouse, updatePen];
END;
Close[];
};
DoProcessInput:
SAFE
PROC [viewersWorld: ViewersWorldRefType.Ref, goAway: GoAwayProc, updateMouse: UpdatePointerProc, updatePen: UpdatePointerProc] = {
Processes input events from devices that are not yet handled by the X input extension, such as the FastTRAP trackball.
deltaTime: RelativeTimes.DeltaTime = 0; -- should really be computed from XTkTIPSourceImpl.GetDeltaTime, but that routine isn't public and I don't know how to get ahold of the XTkTIPSourceImpl.Handle (Bier, September 30, 1993)
e: Event;
deviceName: ATOM;
dispatch: EventDispatchProc ¬ NIL;
userInput: UserInput.Handle ~ ViewersWorld.GetInputHandle[viewersWorld];
Process.SetPriority[Process.priorityClient3];
The next higher priority than priorityForeground (which equals priorityClient2)
DO
[e, deviceName, dispatch] ¬ Read[50];
IF deviceName=$Closed THEN EXIT; -- no active devices
IF dispatch#
NIL
THEN {
dispatch[e, deviceName, deltaTime, GetClientData[deviceName]];
GetClientData can be optimized out by passing around clientDatas in this module
}
ELSE IF e.id=timeoutID THEN UserInputInsertActions.InsertTimeIsPassing[userInput, deltaTime]
ELSE NULL;
ENDLOOP;
SetHToNIL[];
StartSerialDevices[];
};
SetHToNIL:
ENTRY
PROC = {
h ¬NIL;
};
WaitForRegistration:
ENTRY PROC = {
vw:
REF ViewersWorldObj ¬ ViewersWorldInstance.GetWorld[];
have to wait for the first device to Register itself
WAIT registerCV;
Process.Detach[FORK ProcessInput[vw, NIL, NIL, NIL]];
};
StartSerialDevices:
PROC = {
Process.Detach[FORK WaitForRegistration[]];
};
END.