DIRECTORY BasicTime, Commander, Convert, Devices, DeviceTypes, IO, KeyMapping, KeyStateTypes, KeyTypes, PreDebug, Process, RelativeTimes, Rope, ScreenCoordsTypes, SimpleFeedback, SpecialKeySyms, UserInput, UserInputDiscrimination, UserInputGetActions, UserInputInsertActions, UserInputLookahead, UserInputOps, UserInputOpsExtras, UserInputOpsExtras2, UserInputOpsExtras3, UserInputPrivate, UserInputTypes; UserInputImpl: CEDAR MONITOR IMPORTS BasicTime, Commander, Convert, Devices, IO, KeyMapping, PreDebug, Process, RelativeTimes, Rope, SimpleFeedback EXPORTS UserInput, UserInputDiscrimination, UserInputGetActions, UserInputInsertActions, UserInputLookahead, UserInputOps, UserInputOpsExtras, UserInputOpsExtras2, UserInputOpsExtras3, UserInputPrivate ~ BEGIN HandleRep: PUBLIC TYPE = UserInputPrivate.Rep; Rep: TYPE = UserInputPrivate.Rep; Handle: TYPE = REF Rep; NarrowHandle: PUBLIC <> PROC [x: REF ANY] RETURNS [UserInput.Handle] = { RETURN [NARROW[x, Handle]] }; IsHandle: PUBLIC <> PROC [x: REF ANY] RETURNS [BOOL] = { RETURN [ISTYPE[x, Handle]]; }; ActionKind: TYPE ~ { deltaEventTime, eventTime, mousePosition, fakeMouseMotion, penPosition, keyDown, keyUp, keyStillDown, allUp, end, timedOut -- never stored -- }; TimeStamp: TYPE = RelativeTimes.TimeStamp; Acceptance: TYPE = UserInputTypes.Acceptance; DeltaTime: TYPE = RelativeTimes.DeltaTime; KeyCode: TYPE = KeyTypes.KeyCode; KeyCodes: TYPE = KeyMapping.KeyCodes; KeyTable: TYPE = KeyMapping.KeyTable; MousePosition: TYPE = ScreenCoordsTypes.TIPScreenCoordsRec; PenPosition: TYPE = ScreenCoordsTypes.TIPScreenCoordsRec; KeySym: TYPE = KeyTypes.KeySym; UpDown: TYPE = KeyStateTypes.UpDown; WaitMode: TYPE = UserInputTypes.WaitMode; PrivateData: TYPE ~ REF PrivateDataRep; PrivateDataRep: PUBLIC TYPE ~ RECORD [ next: REF EventRecord, -- the event that will next be read from the queue shared: REF SharedDataRep, number: CARD, -- a number that uniquely identifies this handle for debugging name: ATOM, -- a unique name for this handle, also for debugging newLogger: NewLoggingProc ฌ NIL ]; NewLoggingProc: TYPE = UserInputGetActions.NewLoggingProc; SharedDataRep: TYPE ~ RECORD [ latest: REF EventRecord, -- The most recent event for this collection of handles epochGMT: BasicTime.GMT, -- best guess for absolute time associated with epochTimeStamp epochTimeStamp: TimeStamp, isMouse: PACKED ARRAY KeyCode OF BOOL ฌ ALL[FALSE] ]; EventRecord: TYPE ~ RECORD [ refCount: INT, -- counts references from other EventRecords and from private.next fields kind: ActionKind, type: ATOM, -- the new scheme, more extensible than the "kind" field eventTime: TimeStamp, keyCode: KeyCode, -- valid for kind one of {keyDown, keyStillDown, keyUp} preferredSym: INTEGER, -- valid for kind one of {keyDown, keyStillDown, keyUp} newPosition: MousePosition, -- valid for kind one of {mousePosition, fakeMouseMotion, penPosition} device: REF, -- the particular mouse, keyboard, voice recognizer, etc., from which this event came display: REF, user: REF, -- the user who produced this event (if known) data: REF, -- private information associated with this event by the queueing process ref: REF, eventSource: REF READONLY ANY, acceptance: Acceptance ฌ clicksAndMotion, next: REF EventRecord, onFreeList: BOOL ฌ FALSE -- for debugging, Bier, Jacobi September 30, 1991 ]; avail: REF EventRecord ฌ NIL; -- "free" list of EventRecords availMax: INT ฌ 1200; -- the free list may not grow larger than this availCount: INT ฌ 0; -- the number of elements on the free list allocCount: CARD ฌ 0; -- number of EventRecords allocated via NEW (for statistics only). Alloc: PROC [er: EventRecord] RETURNS [e: REF EventRecord] ~ INLINE { msg: Rope.ROPE; [e, msg] ฌ EntryAlloc[]; IF msg#NIL THEN Warn[oneLiner, msg]; eญ ฌ er; }; EntryAlloc: ENTRY PROC [] RETURNS [e: REF EventRecord, msg: Rope.ROPE ฌ NIL] ~ INLINE { [e, msg] ฌ AllocInternal[] }; AllocInternal: INTERNAL PROC RETURNS [e: REF EventRecord, msg: Rope.ROPE ฌ NIL] ~ { IF avail = NIL THEN { -- allocate an EventRecord from the heap e ฌ NEW[EventRecord]; e.onFreeList ฌ FALSE; allocCount ฌ allocCount + 1; IF allocCount >= 100 AND allocCount MOD 50 = 0 THEN msg ฌ IO.PutFR1["TIP has allocated %g EventRecords so far", [integer[allocCount]]] } ELSE { -- take one from the free list e ฌ avail; avail ฌ avail.next; availCount ฌ availCount-1; IF ~e.onFreeList THEN { msg ฌ "TIP problem: attempt to allocate event that wasn't really free"; e ฌ NEW[EventRecord]; }; e.onFreeList ฌ FALSE; }; }; Warn: PROC [msgType: SimpleFeedback.MsgType, r: Rope.ROPE] = { ENABLE ANY => GOTO oops; IF msgType=oneLiner OR msgType=begin THEN r ฌ Rope.Concat["UserInputImpl: ", r]; SimpleFeedback.Append[$TIP, msgType, $Warning, r]; EXITS oops => {} }; NillHandle: ERROR ~ CODE; Bug: SIGNAL [msg: REF READONLY TEXT] ~ CODE; SetUpEventRecord: INTERNAL PROC [handle: Handle, deltaTime: DeltaTime] RETURNS [new: REF EventRecord ฌ NIL, shared: REF SharedDataRep ฌ NIL, error: REF READONLY TEXT ฌ NIL] = { private: PrivateData; warning: Rope.ROPE; IF handle = NIL THEN RETURN [NIL, NIL, "NIL-handle"]; private ฌ handle.private; IF private = NIL THEN RETURN [NIL, NIL, "NIL-private"]; shared ฌ private.shared; [new, warning] ฌ AllocInternal[]; IF warning#NIL THEN Warn[oneLiner, warning]; new.next ฌ NIL; IF deltaTime < 0 THEN RETURN [NIL, NIL, "deltaTime < 0"]; IF shared = NIL THEN RETURN [NIL, NIL, "SetUpEventRecord shared=NIL"]; IF shared.latest = NIL THEN RETURN [NIL, NIL, "SetUpEventRecord shared.latest=NIL"]; IF shared.latest.next # NIL THEN { IF shared.latest.onFreeList THEN RETURN [NIL, NIL, "SetUpEventRecord shared.latest.next#NIL, latest is on free list"]; RETURN [NIL, NIL, "SetUpEventRecord shared.latest.next#NIL, latest is active"]; }; new.eventTime.t ฌ shared.latest.eventTime.t + LOOPHOLE[deltaTime, CARD32]; new.newPosition ฌ shared.latest.newPosition; -- if there is no new mouse position, assume we are still at the last one }; PutEventRecordOnQueue: INTERNAL PROC [new: REF EventRecord, shared: REF SharedDataRep] = { ReportTimeUpdateFailed: PROC [] = { epochAsRope: Rope.ROPE ฌ NIL; epochAsRope ฌ Convert.RopeFromTime[from: shared.epochGMT, end: seconds !BasicTime.OutOfRange, BasicTime.TimeNotKnown, BasicTime.TimeParametersNotKnown, Convert.Error => CONTINUE]; IF epochAsRope#NIL THEN Warn[oneLiner, IO.PutFR1["PutEventRecordOnQueue got an event dated after %g.", [rope[epochAsRope]]] ] ELSE Warn[oneLiner, IO.PutFR1["PutEventRecordOnQueue got an event when shared.epochGMT was broken (%08x).", [cardinal[LOOPHOLE[shared.epochGMT]]]] ]; }; shared.latest.next ฌ new; -- these three lines put the action on the queue new.refCount ฌ 1; shared.latest ฌ new; BEGIN WHILE RelativeTimes.InlinePeriod[from: shared.epochTimeStamp, to: new.eventTime] > 100000 DO shared.epochGMT ฌ BasicTime.Update[shared.epochGMT, 100 ! BasicTime.OutOfRange => GOTO TimeUpdateFailed]; shared.epochTimeStamp.t ฌ shared.epochTimeStamp.t + 100000; ENDLOOP; EXITS TimeUpdateFailed => { -- shared.epochGMT was about to go beyond year 2036 ReportTimeUpdateFailed[ ! ANY => CONTINUE]; shared.epochGMT ฌ BasicTime.Now[]; -- set it back to now, Bier, January 16, 1991 shared.epochTimeStamp ฌ new.eventTime; }; END; BROADCAST newEventCame; }; InsertInputAction: PUBLIC PROC [handle: Handle, a: InputAction] = { InsertInputActionBody[handle, aญ]; }; InsertInputActionBody: PUBLIC ENTRY PROC [handle: Handle, a: InputActionBody] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, a.deltaTime]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; new.eventSource ฌ a.eventSource; new.type ฌ a.kind; SELECT a.kind FROM $TimeIsPassing => new.kind ฌ deltaEventTime; $Key => IF a.down THEN { new.kind ฌ keyDown; handle.rawKeyboardState[a.keyCode] ฌ down; } ELSE { new.kind ฌ keyUp; handle.rawKeyboardState[a.keyCode] ฌ up; }; $IntegerPosition => { new.kind ฌ mousePosition; new.newPosition ฌ [a.x, a.y, ColorDisplayFromBool[a.display = $Color OR a.display = $Display1]]; }; $FakePosition => { new.kind ฌ fakeMouseMotion; new.newPosition ฌ shared.latest.newPosition; }; $Enter => new.kind ฌ fakeMouseMotion; -- old clients will ignore this event $Exit => new.kind ฌ fakeMouseMotion; -- old clients will ignore this event $Ref => { new.kind ฌ fakeMouseMotion; -- old clients will ignore this event new.ref ฌ a.ref; new.acceptance ฌ all; -- there is no a.acceptance so re-inserting these events doesn't work well }; $AllUp => { new.kind ฌ allUp; handle.rawKeyboardState ฌ ALL[up]; }; $KeyStillDown => { new.kind ฌ keyStillDown; handle.rawKeyboardState[a.keyCode] ฌ down; }; $EventTime => { new.kind ฌ eventTime; IF shared.epochGMT = BasicTime.nullGMT THEN { shared.epochGMT ฌ BasicTime.Now[]; shared.epochTimeStamp ฌ new.eventTime; }; }; $End => new.kind ฌ end; ENDCASE => { Warn[oneLiner, IO.PutFR1["Unexpected event %g in InsertInputActionBody", [atom[a.kind]]]] }; new.eventTime ฌ a.eventTime; new.keyCode ฌ a.keyCode; new.preferredSym ฌ a.preferredSym; new.device ฌ a.device; new.display ฌ a.display; new.user ฌ a.user; new.data ฌ a.data; PutEventRecordOnQueue[new, shared]; }; InsertTimeIsPassing: PUBLIC ENTRY PROC [handle: Handle, deltaTime: DeltaTime, data: REF ฌ NIL, eventSource: REF READONLY ANY ฌ NIL] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, deltaTime]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; new.kind ฌ deltaEventTime; -- for backwards compatibility new.type ฌ $TimeIsPassing; new.data ฌ data; new.eventSource ฌ eventSource; PutEventRecordOnQueue[new, shared]; }; InsertKey: PUBLIC ENTRY PROC [handle: Handle, deltaTime: DeltaTime, down: BOOL, keyCode: KeyCode ฌ NULL, preferredSym: KeySym ฌ [0], device: REF ฌ NIL, user: REF ฌ NIL, data: REF ฌ NIL, eventSource: REF READONLY ANY ฌ NIL] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, deltaTime]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; IF down THEN { new.kind ฌ keyDown; handle.rawKeyboardState[keyCode] ฌ down; } ELSE { new.kind ฌ keyUp; handle.rawKeyboardState[keyCode] ฌ up; }; new.type ฌ $Key; new.keyCode ฌ keyCode; new.preferredSym ฌ preferredSym; new.device ฌ device; new.user ฌ user; new.data ฌ data; new.eventSource ฌ eventSource; PutEventRecordOnQueue[new, shared]; }; InsertIntegerPosition: PUBLIC ENTRY PROC [handle: Handle, deltaTime: DeltaTime, x, y: INTEGER, device: REF ฌ NIL, user: REF ฌ NIL, display: REF ฌ NIL, data: REF ฌ NIL, eventSource: REF READONLY ANY ฌ NIL] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, deltaTime]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; new.kind ฌ mousePosition; -- for backwards compatibility new.type ฌ $IntegerPosition; new.newPosition ฌ [x, y, ColorDisplayFromBool[display = $Color OR display = $Display1]]; new.device ฌ device; new.user ฌ user; new.display ฌ display; new.data ฌ data; new.eventSource ฌ eventSource; PutEventRecordOnQueue[new, shared]; }; ColorDisplayFromBool: PROC [b: BOOL] RETURNS [BOOL] = INLINE { RETURN [b]; }; InsertFakePosition: PUBLIC ENTRY PROC [handle: Handle, deltaTime: DeltaTime, device: REF ฌ NIL, user: REF ฌ NIL, display: REF ฌ NIL, data: REF ฌ NIL, eventSource: REF READONLY ANY ฌ NIL] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, deltaTime]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; new.kind ฌ fakeMouseMotion; -- for backwards compatibility new.type ฌ $FakePosition; new.newPosition ฌ shared.latest.newPosition; new.device ฌ device; new.user ฌ user; new.display ฌ display; new.eventSource ฌ eventSource; new.data ฌ data; PutEventRecordOnQueue[new, shared]; }; InsertEnter: PUBLIC ENTRY PROC [handle: Handle, deltaTime: DeltaTime, device: REF ฌ NIL, user: REF ฌ NIL, display: REF ฌ NIL, data: REF ฌ NIL, eventSource: REF READONLY ANY ฌ NIL] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, deltaTime]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; new.kind ฌ allUp; -- so that this event will be ignored by old clients new.type ฌ $Enter; new.device ฌ device; new.user ฌ user; new.display ฌ display; new.eventSource ฌ eventSource; new.data ฌ data; PutEventRecordOnQueue[new, shared]; }; InsertExit: PUBLIC ENTRY PROC [handle: Handle, deltaTime: DeltaTime, device: REF ฌ NIL, user: REF ฌ NIL, display: REF ฌ NIL, data: REF ฌ NIL, eventSource: REF READONLY ANY ฌ NIL] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, deltaTime]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; new.kind ฌ allUp; -- so that this event will be ignored by old clients new.type ฌ $Exit; new.device ฌ device; new.user ฌ user; new.display ฌ display; new.eventSource ฌ eventSource; new.data ฌ data; PutEventRecordOnQueue[new, shared]; }; InsertRef: PUBLIC ENTRY PROC [handle: Handle, deltaTime: DeltaTime, ref: REF, acceptance: Acceptance, device: REF ฌ NIL, user: REF ฌ NIL, data: REF ฌ NIL, eventSource: REF READONLY ANY ฌ NIL] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, deltaTime]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; new.kind ฌ fakeMouseMotion; -- there is no kind=ref, so we'll be something innocuous new.type ฌ $Ref; new.ref ฌ ref; new.acceptance ฌ acceptance; new.device ฌ device; new.user ฌ user; new.eventSource ฌ eventSource; new.data ฌ data; PutEventRecordOnQueue[new, shared]; }; InsertAllUp: PUBLIC ENTRY PROC [handle: Handle, deltaTime: DeltaTime, device: REF ฌ NIL, user: REF ฌ NIL, data: REF ฌ NIL, eventSource: REF READONLY ANY ฌ NIL] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, deltaTime]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; new.kind ฌ allUp; new.type ฌ $AllUp; new.device ฌ device; new.user ฌ user; new.data ฌ data; new.eventSource ฌ eventSource; handle.rawKeyboardState ฌ ALL[up]; PutEventRecordOnQueue[new, shared]; }; InsertKeyStillDown: PUBLIC ENTRY PROC [handle: Handle, deltaTime: DeltaTime, keyCode: KeyCode ฌ NULL, preferredSym: KeySym ฌ [0], device: REF ฌ NIL, user: REF ฌ NIL, data: REF ฌ NIL, eventSource: REF READONLY ANY ฌ NIL] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, deltaTime]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; new.kind ฌ keyStillDown; new.type ฌ $KeyStillDown; new.keyCode ฌ keyCode; new.preferredSym ฌ preferredSym; new.device ฌ device; new.user ฌ user; new.data ฌ data; new.eventSource ฌ eventSource; handle.rawKeyboardState[keyCode] ฌ down; PutEventRecordOnQueue[new, shared]; }; InsertEventTime: PUBLIC ENTRY PROC [handle: Handle, eventTime: TimeStamp, data: REF ฌ NIL, eventSource: REF READONLY ANY ฌ NIL] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, 0]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; IF RelativeTimes.InlineIsLaterTime[t1: eventTime, t2: shared.latest.eventTime] THEN Warn[oneLiner, "The event clock was just set backwards"]; new.kind ฌ eventTime; new.type ฌ $EventTime; new.eventTime ฌ eventTime; new.data ฌ data; new.eventSource ฌ eventSource; IF shared.epochGMT = BasicTime.nullGMT THEN { shared.epochGMT ฌ BasicTime.Now[]; shared.epochTimeStamp ฌ new.eventTime; }; PutEventRecordOnQueue[new, shared]; }; InsertEnd: PUBLIC ENTRY PROC [handle: Handle, deltaTime: DeltaTime, data: REF ฌ NIL, eventSource: REF READONLY ANY ฌ NIL] = { new: REF EventRecord; shared: REF SharedDataRep; errorMessage: REF READONLY TEXT; [new, shared, errorMessage] ฌ SetUpEventRecord[handle, deltaTime]; IF errorMessage # NIL THEN RETURN WITH ERROR Bug[errorMessage]; new.kind ฌ end; new.type ฌ $End; new.data ฌ data; new.eventSource ฌ eventSource; PutEventRecordOnQueue[new, shared]; }; handles, handlesTailPointer: LIST OF Handle; handleCount: NAT ฌ 0; RegisterHandle: PROC [handle: Handle] = { handleCount ฌ handleCount + 1; handle.private.number ฌ handleCount; IF handles = NIL THEN { handles ฌ handlesTailPointer ฌ LIST[handle]; } ELSE { handlesTailPointer.rest ฌ LIST[handle]; handlesTailPointer ฌ handlesTailPointer.rest; }; }; ThisHandleProc: TYPE = PROC [handle: Handle] RETURNS [done: BOOL ฌ FALSE]; WalkHandles: PROC [thisHandleProc: ThisHandleProc] RETURNS [aborted: BOOL ฌ FALSE] = { FOR list: LIST OF Handle ฌ handles, list.rest UNTIL list = NIL DO aborted ฌ thisHandleProc[list.first]; IF aborted THEN RETURN; ENDLOOP; }; ListHandles: Commander.CommandProc = { PrintHandleInfo: ThisHandleProc = { IF handle.private.next = NIL THEN cmd.out.PutF["(%g) %g: CLOSED\n", [integer[handle.private.number]], [atom[handle.private.name]] ] ELSE cmd.out.PutF["(%g) %g: at event %g\n", [integer[handle.private.number]], [atom[handle.private.name]], [integer[handle.private.next.eventTime]] ]; IF handle.private.next # NIL AND handle.private.next.eventTime # 0 AND handle.private.next.eventTime < earliestTime THEN { earliestTime ฌ handle.private.next.eventTime; earliestHandle ฌ handle; }; }; PrintFinalHandle: PROC [handle: Handle] = { IF handle.private.next = NIL THEN cmd.out.PutF["(%g) %g: now CLOSED\n", [integer[handle.private.number]], [atom[handle.private.name]] ] ELSE cmd.out.PutF["(%g) %g: now at event %g\n", [integer[handle.private.number]], [atom[handle.private.name]], [integer[handle.private.next.eventTime]] ]; }; thisTime: RelativeTimes.TimeStamp ฌ RelativeTimes.nullTimeStamp; earliestTime: RelativeTimes.TimeStamp ฌ [CARD.LAST]; earliestHandle: Handle ฌ NIL; [] ฌ WalkHandles[PrintHandleInfo]; IF earliestHandle = NIL THEN cmd.out.PutRope["No earliest handle"] ELSE { cmd.out.PutRope["Earliest handle = "]; [] ฌ PrintFinalHandle[earliestHandle]; }; }; DummyEvent: PROC [] RETURNS [e: REF EventRecord] = { e ฌ Alloc[[refCount: 1, kind: eventTime, eventTime: RelativeTimes.nullTimeStamp, keyCode: KeyCode.FIRST, preferredSym: 0, newPosition: [0, 0, ], next: NIL]]; }; Create: PUBLIC PROC [source: PROC [handle: UserInput.Handle] ฌ NIL, sourceData: REF ANY ฌ NIL, name: ATOM ฌ $UnNamed] RETURNS [Handle] ~ { handle: Handle ~ NEW[Rep]; private: PrivateData ~ NEW[PrivateDataRep]; handle.private ฌ private; private.shared ฌ NEW[SharedDataRep]; handle.mouseGrainTime ฌ 1; handle.mouseGrainDots ฌ 1; handle.source ฌ source; handle.sourceData ฌ sourceData; private.next ฌ private.shared.latest ฌ DummyEvent[]; private.shared.epochGMT ฌ BasicTime.nullGMT; private.shared.epochTimeStamp ฌ RelativeTimes.nullTimeStamp; private.name ฌ name; RegisterHandle[handle]; RETURN [handle]; }; SetAbsoluteTime: PUBLIC ENTRY PROC [handle: Handle, epochGMT: BasicTime.GMT ฌ BasicTime.nullGMT, epochTimeStamp: TimeStamp ฌ RelativeTimes.nullTimeStamp] ~ { private: PrivateData; shared: REF SharedDataRep; IF handle=NIL THEN RETURN WITH ERROR NillHandle; private ฌ handle.private; IF private=NIL THEN RETURN WITH ERROR Bug[NIL]; shared ฌ private.shared; IF shared=NIL THEN RETURN WITH ERROR Bug[NIL]; shared.epochGMT ฌ epochGMT; shared.epochTimeStamp ฌ epochTimeStamp; }; SetMapping: PUBLIC PROC [handle: Handle, mapping: KeyMapping.Mapping] = { private: PrivateData ~ handle.private; handle.mapping ฌ mapping; FOR k: KeyCode IN KeyCode DO SELECT KeyMapping.GetKeySym[mapping, k, 0] FROM SpecialKeySyms.Button1, SpecialKeySyms.Button2, SpecialKeySyms.Button3, SpecialKeySyms.Button4, SpecialKeySyms.Button5 => private.shared.isMouse[k] ฌ TRUE; ENDCASE => private.shared.isMouse[k] ฌ FALSE; ENDLOOP; }; GetMapping: PUBLIC PROC [handle: Handle] RETURNS [mapping: KeyMapping.Mapping] = { RETURN [handle.mapping]; }; Close: PUBLIC ENTRY PROC [handle: Handle] ~ { CloseInternal[handle]; }; CloseInternal: INTERNAL PROC [handle: Handle] ~ { private: PrivateData ~ handle.private; IF private.next # NIL THEN { DecrementRefCountInternal[private.next]; private.next ฌ NIL }; }; SaveState: PUBLIC ENTRY PROC [saved: Handle, handle: Handle] ~ { savedPrivate: PrivateData ~ saved.private; handlePrivate: PrivateData ~ handle.private; IF saved = handle THEN ERROR Bug["SaveState args are EQ"]; CloseInternal[saved]; -- in the common case, this CloseInternal should leave savedPrivate.next.refCount = 0 saved.timeStamp ฌ handle.timeStamp; saved.mousePosition ฌ handle.mousePosition; saved.penPosition ฌ handle.penPosition; saved.keyboardState ฌ handle.keyboardState; saved.mapping ฌ handle.mapping; savedPrivate.next ฌ handlePrivate.next; -- set the next action to read from the stream IncrementRefCountInternal[savedPrivate.next]; IF handlePrivate.shared = NIL THEN SIGNAL Bug[msg: "SaveState: Tail pointer becoming NIL"]; savedPrivate.shared ฌ handlePrivate.shared; }; RestoreState: PUBLIC ENTRY PROC [saved: Handle, handle: Handle] ~ { savedPrivate: PrivateData ~ saved.private; handlePrivate: PrivateData ~ handle.private; IF saved = handle THEN ERROR Bug["RestoreState args are EQ"]; CloseInternal[handle]; handle.timeStamp ฌ saved.timeStamp; handle.mousePosition ฌ saved.mousePosition; handle.penPosition ฌ saved.penPosition; handle.keyboardState ฌ saved.keyboardState; handlePrivate.next ฌ savedPrivate.next; -- set the next action to read from the stream IncrementRefCountInternal[handlePrivate.next]; IF savedPrivate.shared = NIL THEN SIGNAL Bug[msg: "RestoreState: Tail pointer becoming NIL"]; handlePrivate.shared ฌ savedPrivate.shared; }; IncrementRefCountInternal: INTERNAL PROC [e: REF EventRecord] ~ { IF e # NIL THEN e.refCount ฌ e.refCount + 1; }; DecrementRefCountInternal: INTERNAL PROC [e: REF EventRecord] ~ { WHILE e # NIL AND (e.refCount ฌ e.refCount - 1) = 0 DO next: REF EventRecord ฌ e.next; AddToFreeList[e]; e ฌ next; ENDLOOP; }; AddToFreeList: INTERNAL PROC [e: REF EventRecord] ~ INLINE { IF e.onFreeList THEN { Warn[oneLiner, "attempt to free event that wasn't really allocated"]; RETURN; }; e.onFreeList ฌ TRUE; IF availCount >= availMax THEN { e.next ฌ NIL -- let garbage collector have it -- } ELSE { e.next ฌ avail; avail ฌ e; availCount ฌ availCount + 1 }; }; newEventCame: CONDITION; InitializeCONDITION: PROC = TRUSTED { -- called when this module is started Process.EnableAborts[@newEventCame]; }; GetEventRecord: ENTRY PROC [handle: Handle, waitMode: WaitMode ฌ forever, finalTime: TimeStamp, acceptance: Acceptance ฌ clicks] RETURNS [e: REF EventRecord] ~ { ENABLE UNWIND => NULL; --because external process can be calling Process.Abort while we wait on condition newEventCame. private: PrivateData ~ handle.private; success: BOOL ฌ FALSE; [success, e] ฌ GetEventRecordInternal[handle, private, waitMode, finalTime, acceptance]; IF NOT success THEN RETURN [NIL]; IncrementRefCountInternal[e]; DecrementRefCountInternal[private.next]; private.next ฌ e; -- so, private.next is the most recent event that GetEventRecord returned }; GetEventRecordInternal: INTERNAL PROC [handle: Handle, private: PrivateData, waitMode: WaitMode, finalTime: TimeStamp, acceptance: Acceptance] RETURNS [success: BOOL ฌ TRUE, e: REF EventRecord ฌ NIL] = { SELECT waitMode FROM forever, timed => { CheckInvariants1[private]; WHILE private.next.next = NIL DO IF handle.source # NIL THEN { handle.source[handle]; -- if we have a built-in source, try to use it to get an event. IF private.next.next = NIL THEN WAIT newEventCame; } ELSE WAIT newEventCame; CheckInvariants2[private]; ENDLOOP; e ฌ private.next.next; }; dontWait => { IF private.next.next = NIL THEN RETURN [FALSE, NIL] ELSE RETURN [TRUE, private.next.next]; }; ENDCASE => ERROR; }; CheckInvariants1: PROC [private: PrivateData] = INLINE { IF private.next = NIL THEN { Warn[begin, "attempt to get an event from a closed handle"]; IF private.shared.latest = NIL THEN { Warn[end, " but latest event exists."]; private.next ฌ private.shared.latest ฌ DummyEvent[] } ELSE { Warn[end, " and no latest event!"]; private.next ฌ private.shared.latest; }; }; }; CheckInvariants2: PROC [private: PrivateData] = INLINE { IF private.next = NIL THEN { Warn[oneLiner, "handle became closed during GetEventRecordInternal"]; IF private.shared.latest = NIL THEN private.next ฌ private.shared.latest ฌ DummyEvent[] ELSE private.next ฌ private.shared.latest; }; }; InputAction: TYPE ~ UserInputGetActions.InputAction; InputActionBody: TYPE ~ UserInputGetActions.InputActionBody; GetInputAction: PUBLIC PROC [handle: Handle, waitMode: WaitMode ฌ forever, waitInterval: DeltaTime ฌ 100, acceptance: Acceptance ฌ all] RETURNS [a: InputAction] = { a ฌ NEW[InputActionBody]; a^ ฌ GetInputActionBody[handle, waitMode, waitInterval, acceptance]; }; GetInputActionBody: PUBLIC PROC [handle: Handle, waitMode: WaitMode ฌ forever, waitInterval: DeltaTime ฌ 100, acceptance: Acceptance ฌ all] RETURNS [a: InputActionBody] = { private: PrivateData ~ handle.private; startTime: TimeStamp ฌ handle.timeStamp; -- time of the last successful GetAction finalTime: TimeStamp ฌ [startTime.t + LOOPHOLE[waitInterval, CARD32]]; -- this arithmetic is (implictly) performed modulo 2ญ32. deltaTime: DeltaTime; DO -- Exit by RETURN. Keep trying until your time runs out and GetEventRecord = NIL event: REF EventRecord ~ GetEventRecord[handle, waitMode, finalTime, acceptance]; IF event = NIL THEN GOTO Timeout; -- can only happen if waitMode = dontWait deltaTime ฌ RelativeTimes.InlinePeriod[from: handle.timeStamp, to: event.eventTime]; handle.timeStamp ฌ event.eventTime; a.kind ฌ event.type; a.eventSource ฌ event.eventSource; a.deltaTime ฌ deltaTime; a.eventTime ฌ handle.timeStamp; -- The comment in UserInputGetActions now lies. eventTime is now valid for all event types IF waitMode = timed THEN { IF RelativeTimes.InlineIsLaterTime[finalTime, event.eventTime] THEN GOTO Timeout; }; SELECT event.type FROM $EventTime => IF acceptance = all THEN { a.data ฌ event.data; RETURN; }; $TimeIsPassing => { IF acceptance = all THEN { a.data ฌ event.data; RETURN; }; }; $End => { a.data ฌ event.data; RETURN; -- regardless of acceptance }; $AllUp => { handle.keyboardState ฌ ALL[up]; IF acceptance = all THEN { a.device ฌ event.device; a.data ฌ event.data; RETURN; }; }; $KeyStillDown => { handle.keyboardState[event.keyCode] ฌ down; IF acceptance = all THEN { a.keyCode ฌ event.keyCode; a.device ฌ event.device; a.data ฌ event.data; RETURN; }; }; $Key => { a.down ฌ event.kind = keyDown; handle.keyboardState[event.keyCode] ฌ IF a.down THEN down ELSE up; a.keyCode ฌ event.keyCode; a.preferredSym ฌ [event.preferredSym]; a.user ฌ event.user; a.device ฌ event.device; a.data ฌ event.data; RETURN; -- regardless of acceptance }; $IntegerPosition => { handle.mousePosition ฌ event.newPosition; IF acceptance # clicks THEN { a.device ฌ event.device; a.user ฌ event.user; a.data ฌ event.data; a.x ฌ event.newPosition.mouseX; a.y ฌ event.newPosition.mouseY; a.display ฌ event.display; RETURN; }; }; $Position => { handle.mousePosition ฌ event.newPosition; IF acceptance # clicks THEN { a.device ฌ event.device; a.user ฌ event.user; a.data ฌ event.data; a.rx ฌ event.newPosition.mouseX; -- for now, Bier, July 24, 1990 a.ry ฌ event.newPosition.mouseY; -- for now, Bier, July 24, 1990 a.display ฌ event.display; RETURN; }; }; $FakePosition => { handle.mousePosition ฌ event.newPosition; IF acceptance # clicks THEN { a.device ฌ event.device; a.user ฌ event.user; a.data ฌ event.data; a.display ฌ event.display; RETURN; }; }; $Enter, $Exit => { handle.mousePosition ฌ event.newPosition; IF acceptance # clicks THEN { a.device ฌ event.device; a.user ฌ event.user; a.data ฌ event.data; a.display ฌ event.display; RETURN; }; }; $Ref => { handle.mousePosition ฌ event.newPosition; IF acceptance = all OR (acceptance = clicksAndMotion AND event.acceptance # all) OR (acceptance = clicks AND event.acceptance = clicks) THEN { WITH event.ref SELECT FROM change: DeviceTypes.DeviceStateChange => { deviceClassName: ATOM ฌ Devices.NameOfClass[change.class]; IF deviceClassName = $FastTRAP THEN handle.fastTrapState _ event.data; }; ENDCASE; a.device ฌ event.device; a.user ฌ event.user; a.data ฌ event.data; a.ref ฌ event.ref; RETURN; }; }; ENDCASE => ERROR Bug["Unexpected event type"]; ENDLOOP; EXITS Timeout => { a.deltaTime ฌ 0; a.kind ฌ $TimeOut; RETURN; }; }; LogNewActions: PUBLIC PROC [handle: Handle, logger: NewLoggingProc ฌ NIL] = { handle.private.newLogger ฌ logger; }; GetKeyState: PUBLIC PROC [handle: Handle, keyCode: KeyCode] RETURNS [state: UpDown] ~ { RETURN [handle.keyboardState[keyCode]] }; GetKeySymState: PUBLIC PROC [handle: Handle, keySym: KeySym] RETURNS [state: UpDown ฌ up] = { keyCodes: KeyCodes ฌ KeyMapping.KeyCodesFromKeySym[handle.mapping, keySym]; FOR n: NAT IN [0..keyCodes.n) DO IF handle.keyboardState[keyCodes[n].keyCode] = down THEN RETURN[down]; ENDLOOP }; GetLatestKeyState: PUBLIC PROC [handle: Handle, keyCode: KeyCode] RETURNS [state: UpDown] = { RETURN [handle.rawKeyboardState[keyCode]]; }; GetLatestKeySymState: PUBLIC PROC [handle: Handle, keySym: KeySym] RETURNS [state: UpDown ฌ up] = { keyCodes: KeyCodes ฌ KeyMapping.KeyCodesFromKeySym[handle.mapping, keySym]; FOR n: NAT IN [0..keyCodes.n) DO IF GetLatestKeyState[handle, keyCodes[n].keyCode] = down THEN RETURN[down]; ENDLOOP }; GetTime: PUBLIC PROC [handle: Handle] RETURNS [TimeStamp] ~ { RETURN [handle.timeStamp] }; GetLatestTime: PUBLIC PROC [handle: Handle] RETURNS [TimeStamp] ~ { private: PrivateData ~ handle.private; RETURN[private.shared.latest.eventTime] }; GetLatestPosition: PUBLIC PROC [handle: Handle] RETURNS [ScreenCoordsTypes.TIPScreenCoordsRec] = { private: PrivateData ~ handle.private; RETURN[private.shared.latest.newPosition]; }; UpdateGMT: PROC [base: BasicTime.GMT, period: INT] RETURNS [BasicTime.GMT] = INLINE { RETURN [LOOPHOLE[LOOPHOLE[base, INT32]+period]] }; GetAbsoluteTime: PUBLIC ENTRY PROC [handle: Handle, timeStamp: TimeStamp] RETURNS [gmt: BasicTime.GMT, milliseconds: INT] ~ { delta: DeltaTime; private: PrivateData; shared: REF SharedDataRep; IF handle=NIL THEN RETURN WITH ERROR NillHandle; private ฌ handle.private; IF private=NIL THEN RETURN WITH ERROR Bug[NIL]; shared ฌ private.shared; IF shared=NIL THEN RETURN WITH ERROR Bug[NIL]; delta ฌ RelativeTimes.InlinePeriod[from: shared.epochTimeStamp, to: timeStamp]; gmt ฌ UpdateGMT[base: shared.epochGMT, period: delta/1000]; milliseconds ฌ delta MOD 1000; }; GetEpochTimes: PUBLIC ENTRY PROC [handle: Handle] RETURNS [epochGMT: BasicTime.GMT, epochTimeStamp: TimeStamp] ~ { private: PrivateData; shared: REF SharedDataRep; IF handle=NIL THEN RETURN WITH ERROR NillHandle; private ฌ handle.private; IF private=NIL THEN RETURN WITH ERROR Bug[NIL]; shared ฌ private.shared; IF shared=NIL THEN RETURN WITH ERROR Bug[NIL]; RETURN [shared.epochGMT, shared.epochTimeStamp] }; SetAtLatest: PUBLIC PROC [handle: Handle] ~ { a: UserInputGetActions.InputAction; DO a ฌ GetInputAction[handle: handle, waitMode: dontWait]; IF a=NIL OR a.kind=$TimeOut THEN RETURN ENDLOOP; }; IsMouseButton: PUBLIC PROC [handle: Handle, keyCode: KeyCode] RETURNS [BOOL] ~ { private: PrivateData ~ handle.private; RETURN [private.shared.isMouse[keyCode]]; }; ExplainBug: PROC [signalOrError: PreDebug.SIGANY, args: POINTER, registerData: REF] RETURNS [msg: Rope.ROPE ฌ NIL] ~ { txt: REF READONLY TEXT ฌ NIL; PreDebug.Raise[signalOrError, args ! Bug => {txt ฌ msg; CONTINUE}]; IF txt#NIL THEN msg ฌ Rope.Concat["problem in UserInputImpl: ", Rope.FromRefText[txt]]; }; GetPosition: PUBLIC PROC [handle: Handle] RETURNS [ScreenCoordsTypes.TIPScreenCoordsRec] = { RETURN [handle.mousePosition] }; InitializeCONDITION[]; PreDebug.RegisterSignalExplainer[Bug, ExplainBug, NIL]; Commander.Register["TIPListHandles", ListHandles, "Lists the numbers of the UserInput handles that are currently reading input and the time in milliseconds of the event that each handle is about to read"]; END. N UserInputImpl.mesa Copyright ำ 1988, 1992 by Xerox Corporation. All rights reserved. Michael Plass, November 26, 1991 1:37 pm PST Bier, September 30, 1993 11:02 am PDT Pier, August 4, 1992 6:20 pm PDT Christian Jacobi, August 25, 1992 2:13 pm PDT Spreitze, March 14, 1991 9:50 am PST Willie-s, October 29, 1991 3:57 pm PST Information about the queue that is independent of where in the queue you are reading Update new Update new Update new Update new Update new Update new Update new Update new Update new Update new Update new PROC [handle: Handle] RETURNS [done: BOOL _ FALSE]; This handle no longer points to the queue at position private.next. Decrement the ref count and set the pointer to NIL. The chief purpose of this copy operation is to allow the TIPMatcher to remember a position in the queue, continue to parse, and return to that position again later. The state that needs to be copied, then, includes the queue itself (handlePrivate.next) and the data associated with the next event (e.g., timeStamp, mousePosition, penPosition, and keyboardState). It is not necessary to copy the rawKeyboardState, mouseGrainTime, mouseGrainDots, source, or sourceData, as these are independent of our position in the queue, and need only belong to the master queue (i.e., the queue that is passed to InsertAction). The (nearly) inverse of SaveState. --Don't restore the mapping? Process.SetTimeout[@newEventOrTimeout, Process.MsecToTicks[100]]; Note that the record returned by GetEventRecord will have a refCount of 1 if this is the only handle with a pointer to the queue, or a refCount of 2, if one or more other handles have saved a pointer to the queue at some earlier event. The next three lines redirect handle from the last event (private.next) to the new event (e) If waitMode = forever, then we wait until we can return an event. Otherwise, we wait for waitInterval (as judged by TimeIsPassing events) or until a non-TimeIsPassing event arrives, whichever comes first. << At this point, waitMode = timed. WHILE private.next.next = NIL DO -- wait in 100 millisecond intervals IF handle.source # NIL THEN handle.source[handle]; IF private.next.next = NIL THEN WAIT newEventCame; ENDLOOP; At last, we have received an event. Does its timestamp indicate that it came in time? success ฌ RelativeTimes.InlineIsLaterTime[private.next.next.eventTime, finalTime]; IF success THEN e ฌ private.next.next; >> -- Bier, September 24, 1993 2:49:56 pm PDT Keep getting events until we get one that is OK to return. IF waitMode = timed, then we may return $TimeOut if nothing comes in the required time. Get the next event. IF this is a FastTRAP event then ... Associates a logging proc with the handle. This proc will be called whenever a new action is received. Useful for debugging. A KeySym is considered down if any KeyCode having that KeySym is down. If this keySym does not exist on this keyboard, assume the key is up. A KeySym is considered down if any KeyCode having that KeySym is down. If this keySym does not exist on this keyboard, assume the key is up. own copy of BasicTime(Impl).Update so we won't raise errors ส#%•NewlineDelimiter –(cedarcode) style™™Icodešœ ฯeœ7™BKšœ,™,K™%K™ K™-K™$K™&K˜—šฯk œ˜ Jšœ5žœิ˜‹K˜—šฯn œžœž˜Jšžœ)žœD˜vJšžœฤž˜า—K˜Kšœ žœžœ˜.Kšœžœ˜!Kšœžœžœ˜K˜š Ÿ œžœžœžœžœžœ˜aKšžœžœ ˜˜K˜——šŸœžœžœžœžœžœžœ˜QKšžœžœ ˜K˜K˜—šœ žœ˜Kšœ˜Kšœ,˜,Kšœ)˜)Kšœ ฯc˜Kšœ˜K˜—Kšœ žœ˜*Kšœ žœ˜-Kšœ žœ˜*Kšœ žœ˜!Kšœ žœ˜%Kšœ žœ˜%Kšœžœ(˜;Kšœ žœ(˜9Kšœžœ˜Kšœžœ˜$Kšœ žœ˜)K˜Kšœ žœžœ˜'šœžœžœžœ˜&Kšœžœ 2˜IKšœžœ˜Kšœžœ >˜LKšœžœ 4˜@Kšœž˜Kšœ˜K˜—šœžœ&˜:K˜—šœžœžœ˜Kš U™UKšœžœ 7˜PKšœžœ >˜WKšœ˜Kš œ žœžœ žœžœžœžœ˜2Kšœ˜K˜—šœ žœžœ˜Kšœ žœ I˜XKšœ˜Kšœžœ 8˜DKšœ˜Kšœ 7˜IKšœžœ 7˜NKšœ F˜bKšœžœ U˜bKšœ žœ˜ Kšœžœ .˜9Kšœžœ I˜TKšœžœ˜ Kšœ žœžœžœ˜K˜)Kšœžœ ˜Kšœ žœžœ 1˜Jšœ˜K˜——Kšœžœžœ ˜Kšžœžœžœ˜Kšžœžœžœ'˜PK˜2Kšžœ ˜K˜K˜—KšŸ œžœžœ˜Kš Ÿœžœžœž œžœ˜,K˜šŸœžœžœ(žœžœžœ žœžœ žœžœžœžœ˜ฐKšœ˜Kšœžœ˜Kš žœ žœžœžœžœžœ˜5K˜Kš žœ žœžœžœžœžœ˜7K˜K˜!Kšžœ žœžœ˜,Kšœ žœ˜šžœžœ˜Kšžœžœžœ˜#—šžœ žœžœ˜Kšžœžœžœ!˜1—šžœžœžœ˜Kšžœžœžœ(˜8—šžœžœžœ˜"šžœžœ˜!KšžœžœžœE˜U—Kšžœžœžœ?˜OK˜—Kšœ.žœ žœ˜JKšœ- I˜vK˜K˜—š Ÿœžœžœžœžœ˜ZšŸœžœ˜#Kšœžœžœ˜Kšœฉžœ˜ณšžœ ž˜KšžœžœT˜jKšžœžœ`žœ˜•—K˜—Kšœ 0˜JKšฯb œก˜K˜šž˜šžœUž˜\˜7Kšœžœ˜1—K˜;Kšžœ˜—šž˜šœ 3˜IKšœžœžœ˜+Kšœ# -˜PK˜&Kšœ˜——Kšžœ˜—Kšž œ˜K˜K˜—šŸœžœžœ%˜CK˜"K˜K™—šŸœžœž œ)˜QKšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜DKš žœžœžœžœžœžœ˜?K˜ K˜šžœž˜K˜,šœžœžœ˜K˜K˜*K˜—šžœ˜K˜K˜(K˜—šœ˜K˜KšœEžœ˜`K˜—šœ˜K˜K˜,K˜—Kšœ& %˜KKšœ% %˜Jšœ ˜ Kšœ %˜AK˜Kšœ J˜`K˜—šœ ˜ K˜Kšœžœ˜"K˜—šœ˜K˜K˜*K˜—šœ˜K˜šžœ%žœ˜-K˜"K˜&Kšœ˜—K˜—K˜šžœ˜ KšœžœH˜YK˜——K˜K˜K˜"K˜K˜K˜K˜K˜#K˜K™—šŸœžœž œ.žœžœ žœžœžœžœ˜‡Kšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜BKš žœžœžœžœžœžœ˜?™ Kšœ ˜9K˜K˜Kšœ˜—Kšœ#˜#K˜K˜—šŸ œžœž œ.žœžœ&žœžœžœžœžœžœžœžœžœžœ˜โKšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜BKš žœžœžœžœžœžœ˜?™ šžœžœ˜K˜K˜(K˜—šžœ˜K˜K˜&K˜—K˜K˜K˜ K˜K˜K˜Kšœ˜—Kšœ#˜#K˜K™—š Ÿœžœž œ.žœ žœžœžœžœ žœžœžœžœžœžœžœžœ˜ะKšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜BKš žœžœžœžœžœžœ˜?™ Kšœ ˜8K˜Kšœ?žœ˜XK˜K˜K˜K˜Kšœ˜—Kšœ#˜#K˜K™—š Ÿœžœžœžœžœžœ˜>Kšžœ˜ K˜—K˜šŸœžœž œ0žœžœžœžœ žœžœžœžœžœžœžœžœ˜พKšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜K˜BKš žœžœžœžœžœžœ˜?™ Kšœ ˜;K˜K˜,K˜K˜K˜Kšœ˜K˜—Kšœ#˜#K˜K™—šŸ œžœž œ0žœžœžœžœ žœžœžœžœžœžœžœžœ˜ทKšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜BKš žœžœžœžœžœžœ˜?™ Kšœ 4˜FK˜K˜K˜K˜Kšœ˜K˜—Kšœ#˜#K˜K™—šŸ œžœž œ0žœžœžœžœ žœžœžœžœžœžœžœžœ˜ถKšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜BKš žœžœžœžœžœžœ˜?™ Kšœ 4˜FK˜K˜K˜K˜Kšœ˜K˜—Kšœ#˜#K˜K™—šŸ œžœžœžœ-žœ"žœžœžœžœžœžœžœžœžœžœ˜รKšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜BKš žœžœžœžœžœžœ˜?™ Kšœ 8˜TK˜K˜K˜K˜K˜Kšœ˜K˜—Kšœ#˜#K˜K˜—šŸ œžœ0žœžœžœžœžœžœ žœžœžœžœ˜ฃKšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜BKš žœžœžœžœžœžœ˜?™ K˜K˜K˜K˜K˜Kšœ˜Kšœžœ˜"—Kšœ#˜#K˜K˜—šŸœžœž œ;žœ&žœžœžœžœžœžœžœžœžœžœ˜฿Kšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜BKš žœžœžœžœžœžœ˜?™ K˜K˜K˜K˜ K˜K˜K˜Kšœ˜K˜(—Kšœ#˜#K˜K˜—šŸœžœž œ.žœžœžœžœžœžœ˜ƒKšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜:Kš žœžœžœžœžœžœ˜?šžœL˜NKš žœœœœœœœ œœ˜>—™ K˜K˜K˜K˜Kšœ˜šžœ%žœ˜-K˜"K˜&Kšœ˜——K˜#K˜—K˜šŸ œžœž œ.žœžœžœžœžœžœ˜}Kšœžœ ˜Kšœžœ˜Kšœžœžœžœ˜ K˜BKš žœžœžœžœžœžœ˜?™ K˜K˜K˜Kšœ˜—Kšœ#˜#K˜—K˜Kšœžœžœ˜,Kšœ žœ˜šŸœžœ˜)K˜K˜$šžœ žœžœ˜Kšœžœ ˜,K˜—šžœ˜Kšœžœ ˜'K˜-K˜—K˜K˜—Kš œžœžœžœžœžœ˜Jš Ÿ œžœ"žœ žœžœ˜Vš žœžœžœžœžœž˜AK˜%Kšžœ žœžœ˜Kšžœ˜—K˜K˜—šŸ œ˜&šŸœ˜#Kšžœžœžœžœ™3šžœžœžœb˜ƒKšžœ’˜–—š žœžœžœ#žœ.žœ˜zK˜-K˜K˜—K˜—šŸœžœ˜+šžœžœžœf˜‡Kšžœ–˜š—K˜—K˜@Kšœ)žœžœ˜4Kšœžœ˜K˜"šžœž˜Kšžœ&˜*šžœ˜Kšœ&˜&K˜&K˜——K˜K˜—šŸ œžœžœžœ˜4Kšœbžœ0žœ˜K˜K˜—šŸœžœžœ žœžœžœžœžœžœ žœ ˜ŠKšœžœ˜Kšœžœ˜+K˜Kšœžœ˜$K˜K˜K˜K˜K˜K˜4K˜,K˜