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: BOOLFALSE;
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[]];
};
StartSerialDevices[];
END.