<<>> <> <> <> <<>> <> <> <<>> 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; <> 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: <> PROC [b: EventBuffer] RETURNS [SAFE PROCESS] = { ENABLE UNWIND => NULL; IF b # NIL THEN { inputProcess: SAFE PROCESS = b.inputProcess; b.inputProcess ¬ NIL; <> 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; }; <> 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 { <> 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; <> 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] = { <> ENABLE UNWIND => NULL; FOR i: NAT IN [0..h.nDevices) DO b: EventBuffer ~ h.inputs[i]; <> 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 { <> 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] = { <> 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]; <> DO [e, deviceName, dispatch] ¬ Read[50]; IF deviceName=$Closed THEN EXIT; -- no active devices IF dispatch#NIL THEN { dispatch[e, deviceName, deltaTime, GetClientData[deviceName]]; <> } <> ELSE NULL; ENDLOOP; SetHToNIL[]; StartSerialDevices[]; }; SetHToNIL: ENTRY PROC = { h ¬NIL; }; WaitForRegistration: ENTRY PROC = { vw: REF ViewersWorldObj ¬ ViewersWorldInstance.GetWorld[]; <> WAIT registerCV; Process.Detach[FORK ProcessInput[vw, NIL, NIL, NIL]]; }; StartSerialDevices: PROC = { Process.Detach[FORK WaitForRegistration[]]; }; StartSerialDevices[]; END.