DIRECTORY Basics, CardTab, Xl, XlDetails, XlDispatch, XlExtensions, XlRecycleMotionEvents, XlPrivate, XlPrivateResources; XlDispatchImpl: CEDAR MONITOR IMPORTS Basics, CardTab, Xl, XlDetails, XlPrivate, XlPrivateResources EXPORTS Xl, XlDispatch, XlRecycleMotionEvents SHARES XlPrivate, XlPrivateResources ~ BEGIN OPEN Xl, XlPrivate; DispatchHandle: TYPE = REF DispatchHandleRec; DispatchHandleRec: <>PUBLIC TYPE = RECORD [ wDataTab: CardTab.Ref ¬ NIL, priviledgeds: DispatchList ¬ NIL, junkies: REF DispatchList ¬ NIL, --REF to make type intersting only... cache: REF XlRecycleMotionEvents.EventCache ]; DispatchStuffRep: <>PUBLIC TYPE = DispatchHandleRec; DispatchList: TYPE = REF DispatchListRep; DispatchListRep: TYPE = RECORD [ currLength: NAT ¬ 0, s: SEQUENCE maxLength: NAT OF DispatchRec ]; DispatchRec: TYPE = RECORD [activate: EventCodes, tq: TQ, proc: EventProcType, data: REF, activateExtensions: LIST OF REF ¬ NIL, events: SetOfEvent ¬ unspecifiedEvents]; WindowData: TYPE = REF WindowDataRec; WindowDataRec: PUBLIC TYPE = RECORD [ dl: DispatchList ¬ NIL, externalEvents: SetOfEvent ¬ unspecifiedEvents, --best guess on what external clients want actualEvents: SetOfEvent ¬ unspecifiedEvents --believed server settings ]; NotYetDefined: EventProcType = {--prevent trapping if called before initialized--}; ProcRemoved: EventProcType = {--prevent trapping if called after removal--}; dummy: REF XlRecycleMotionEvents.EventCache ¬ NEW[XlRecycleMotionEvents.EventCache]; GetCache: <>PUBLIC PROC [c: Xl.Connection, w: Xl.Window] RETURNS [cache: REF XlRecycleMotionEvents.EventCache ¬ dummy] = { h: DispatchHandle ¬ GetDispatchHandle[c]; IF Xl.WindowId[w]=CARD.LAST THEN RETURN [h.cache]; --sentinell for allocator WITH CardTab.Fetch[h.wDataTab, w.id].val SELECT FROM wd: WindowData => { dl: DispatchList ¬ wd.dl; IF dl#NIL THEN { count: INT ¬ 0; FOR i: NAT IN [0..dl.currLength) DO IF dl[i].activate[motionNotify] THEN count ¬ count+1 ENDLOOP; IF count=1 THEN cache ¬ h.cache }; }; ENDCASE => {}; }; GetDispatchHandle: PUBLIC PROC [c: Connection] RETURNS [DispatchHandle] = { RETURN [c.dispatchStuff] }; InlineDispatch: PROC [handle: DispatchHandle, event: Event] = INLINE { WITH CardTab.Fetch[handle.wDataTab, event.dispatchDrawable.id].val SELECT FROM wd: WindowData => DispatchOneList[event, wd.dl]; ENDCASE => DispatchOneList[event, handle.junkies­]; IF handle.priviledgeds#NIL THEN DispatchOneList[event, handle.priviledgeds]; }; FindAndDispatch: PUBLIC PROC [event: Event] = { InlineDispatch[event.connection.dispatchStuff, event]; }; Dispatch: PUBLIC PROC [handle: DispatchHandle, event: Event] = { InlineDispatch[handle, event]; }; DispatchExplicite: PUBLIC PROC [wd: WindowData, event: Event] = { DispatchOneList[event, wd.dl]; }; DispatchOneList: PROC [e: Event, dl: DispatchList] = { IF dl=NIL THEN RETURN; IF Basics.BITAND[e.originalCodeByte, 127]>=XlExtensions.firstExtension THEN { WITH e SELECT FROM xEvent: ExtensionEvent => { FOR i: NAT IN [0..dl.currLength) DO FOR xl: LIST OF REF ANY ¬ dl[i].activateExtensions, xl.rest WHILE xl#NIL DO IF xl.first=xEvent.match THEN { p: EventProcType ~ dl[i].proc; --order! Enqueue[dl[i].tq, p, dl[i].data, e]; EXIT --innermost loop } ENDLOOP ENDLOOP }; ENDCASE => {} } ELSE { FOR i: NAT IN [0..dl.currLength) DO IF dl[i].activate[e.type] THEN { p: EventProcType ~ dl[i].proc; --order! Enqueue[dl[i].tq, p, dl[i].data, e]; }; ENDLOOP; }; }; RemoveWindowData: <> PROC [c: Connection, w: Window] = { h: DispatchHandle ~ c.dispatchStuff; [] ¬ CardTab.Delete[h.wDataTab, w.id]; }; GetWindowData: PUBLIC PROC [c: Connection, w: Window] RETURNS [WindowData] = { h: DispatchHandle ~ c.dispatchStuff; RETURN [NARROW[CardTab.Fetch[h.wDataTab, w.id].val]]; }; GetOrCreateWindowData: <> PROC [c: Connection, w: Window] RETURNS [wd: WindowData] = { h: DispatchHandle ~ c.dispatchStuff; wd ¬ NARROW[CardTab.Fetch[h.wDataTab, w.id].val]; IF wd=NIL THEN { wd ¬ NEW[WindowDataRec]; IF CardTab.Insert[h.wDataTab, w.id, wd] THEN RETURN [wd]; wd ¬ GetOrCreateWindowData[c, w]; }; }; UsedLength: PROC [dl: DispatchList] RETURNS [n: CARD ¬ 0] = { IF dl#NIL THEN FOR i: NAT IN [0..dl.currLength) DO IF dl[i].proc#ProcRemoved THEN n ¬ n+1; ENDLOOP; }; SetDispatchList: INTERNAL PROC [container: REF, dl: DispatchList] = { WITH container SELECT FROM wd: WindowData => wd.dl ¬ dl; rdl: REF DispatchList => rdl­ ¬ dl; h: REF DispatchHandleRec => h.priviledgeds ¬ dl; ENDCASE => ERROR; }; GetDispatchList: INTERNAL PROC [container: REF] RETURNS [dl: DispatchList] = { WITH container SELECT FROM wd: WindowData => dl ¬ wd.dl; rdl: REF DispatchList => dl ¬ rdl­; h: REF DispatchHandleRec => dl ¬ h.priviledgeds; ENDCASE => ERROR; }; EnsureEntry: INTERNAL PROC [container: REF] RETURNS [i: NAT, dl: DispatchList] = { dl ¬ GetDispatchList[container]; IF dl=NIL THEN {dl ¬ NEW[DispatchListRep[4]]; SetDispatchList[container, dl]} ELSE IF dl.currLength >= dl.maxLength THEN { use: NAT ¬ UsedLength[dl]; IF use>=NAT.LAST-4 THEN { dl.currLength ¬ dl.currLength-1; } ELSE { new: NAT ¬ use+4; dl2: DispatchList ¬ NEW[DispatchListRep[new]]; dl2.currLength ¬ 0; IF dl#NIL THEN { FOR i: NAT IN [0..dl.currLength) DO IF dl[i].proc#ProcRemoved THEN { dl2[dl2.currLength] ¬ dl[i]; dl2.currLength ¬ dl2.currLength + 1; }; ENDLOOP; }; dl ¬ dl2; SetDispatchList[container, dl]; }; }; i ¬ dl.currLength; dl[i].activate ¬ ALL[FALSE]; dl[i].activateExtensions ¬ NIL; dl[i].proc ¬ NotYetDefined; dl.currLength ¬ dl.currLength+1; }; AddMatchToList: ENTRY PROC [container: REF, match: Match, events: SetOfEvent ¬ unspecifiedEvents] = { IF match#NIL THEN { activate: EventCodes; tq: TQ ¬ match.tq; handles: EventFilter ¬ match.handles; proc: EventProcType ¬ match.proc; IF tq=NIL THEN tq ¬ CreateTQ[]; IF handles=NIL OR proc=NIL OR (handles.activate=ALL[FALSE] AND handles.activateExtensions=NIL) THEN RETURN; BEGIN n: NAT; dl: DispatchList; [n, dl] ¬ EnsureEntry[container]; dl[n].data ¬ match.data; dl[n].tq ¬ tq; dl[n].events ¬ events; dl[n].proc ¬ proc; dl[n].activateExtensions ¬ handles.activateExtensions; dl[n].activate ¬ handles.activate; END; }; }; RemoveMatchFromList: ENTRY PROC [container: REF, proc: EventProcType, tq: Xl.TQ, data: REF] = { dl: DispatchList ¬ GetDispatchList[container]; IF dl#NIL THEN FOR i: NAT IN [0..dl.currLength) DO IF dl[i].proc = proc AND dl[i].tq=tq AND dl[i].data=data THEN { dl[i].activate ¬ ALL[FALSE]; dl[i].activateExtensions ¬ NIL; dl[i].proc ¬ ProcRemoved; }; ENDLOOP }; GetInternalEvents: PROC [dl: DispatchList] RETURNS [events: SetOfEvent ¬ unspecifiedEvents] = { IF dl#NIL THEN FOR i: NAT IN [0..dl.currLength) DO IF dl[i].proc#ProcRemoved THEN events ¬ Xl.ORSetOfEvents[events, dl[i].events]; ENDLOOP; }; PrivateChangeEvents: <> PROC [c: Connection, w: Window, e: SetOfEvent, details: Details ¬ NIL] = { IF w=Xl.nullWindow THEN ERROR; BInit[c, 2, 0, 3+1]; --ChangeWindowAttributes BPutDrawable[c, w]; BPut32[c, 0800H]; --use event-mask field BPut32[c, LOOPHOLE[e]]; FinishWithDetails[c, details]; IF details=NIL THEN { XlPrivate.HardFlushBuffer[c: c, delay: XlPrivateResources.ValidID[c, w]]; }; }; InternalAddMatch: PUBLIC <> PROC [c: Connection, w: Window, match: Match, generate: SetOfEvent] = { wd: WindowData ¬ GetOrCreateWindowData[c, w]; generate.structureNotify ¬ TRUE; wd.actualEvents ¬ Xl.ORSetOfEvents[generate, wd.actualEvents]; --trust the caller to actually set it AddMatchToList[wd, match, generate]; }; AddMatches: PUBLIC PROC [c: Connection, w: Window, matchList: MatchList, generate: SetOfEvent, details: Details ¬ NIL] = { FOR l: MatchList ¬ matchList, l.rest WHILE l#NIL DO AddMatch[c, w, l.first, generate, details]; ENDLOOP }; AddMatch: PUBLIC PROC [c: Connection, w: Window, match: Match, generate: SetOfEvent, details: Details] = { action: PROC [c: Connection] = { wd: WindowData ¬ GetOrCreateWindowData[c, w]; newEvents: Xl.SetOfEvent ¬ Xl.ORSetOfEvents[wd.externalEvents, wd.actualEvents]; newEvents ¬ Xl.ORSetOfEvents[newEvents, generate]; newEvents.structureNotify ¬ TRUE; AddMatchToList[wd, match, generate]; IF newEvents#wd.actualEvents THEN { wd.actualEvents ¬ newEvents; IF w#Xl.nullWindow THEN PrivateChangeEvents[c, w, wd.actualEvents, details]; }; }; generate.structureNotify ¬ TRUE; DoWithLocks[c, action, details]; }; RemoveMatch: PUBLIC PROC [c: Connection, w: Window, match: Match, details: Details] = { action: PROC [c: Connection] = { --Need C-Lock to protect wd.dl and wd.externalEvents RemoveMatchFromList[wd, proc, match.tq, match.data]; IF ~XlPrivateResources.ValidID[c, w] THEN {--other connection owns this w n: NAT ¬ UsedLength[wd.dl]; IF n=0 THEN RemoveWindowData[c, w]; }; BEGIN internal: SetOfEvent ¬ GetInternalEvents[wd.dl]; --necessary structureNotifies are embedded new: SetOfEvent ¬ ORSetOfEvents[internal, wd.externalEvents]; IF new#wd.actualEvents THEN { wd.actualEvents ¬ new; IF w#Xl.nullWindow THEN PrivateChangeEvents[c, w, new, details]; }; END; }; proc: EventProcType ¬ match.proc; wd: WindowData = GetWindowData[c, w]; IF wd#NIL AND match#NIL THEN DoWithLocks[c, action, details]; }; AddMatchForUnregistered: PUBLIC PROC [c: Connection, match: Match] = { action: PROC [c: Connection] = { h: DispatchHandle ~ c.dispatchStuff; AddMatchToList[h.junkies, match]; }; DoWithLocks[c, action, XlDetails.ignoreErrors]; }; EnforcedSetOfEvent: PUBLIC <> PROC [c: Connection, w: Window, external: SetOfEvent] RETURNS [SetOfEvent ¬ unspecifiedEvents] = { IF external#unspecifiedEvents THEN { wd: WindowData = GetOrCreateWindowData[c, w]; internal: SetOfEvent ¬ GetInternalEvents[wd.dl]; wd.externalEvents ¬ external; external ¬ Xl.ORSetOfEvents[internal, external]; external.structureNotify ¬ TRUE; --enforce future removal of WindowData RETURN [external]; }; }; AddPriviledgedMatch: PUBLIC PROC [c: Connection, match: Match] = { action: PROC [c: Connection] = { h: DispatchHandle ~ c.dispatchStuff; AddMatchToList[h, match]; }; DoWithLocks[c, action, XlDetails.ignoreErrors]; }; RemovePriviledgedMatch: PUBLIC PROC [c: Connection, match: Match] = { action: PROC [c: Connection] = { CheckForEmpty: ENTRY PROC [h: DispatchHandle] = { IF UsedLength[h.priviledgeds]=0 THEN h.priviledgeds ¬ NIL }; h: DispatchHandle ~ c.dispatchStuff; RemoveMatchFromList[h, proc, match.tq, match.data]; CheckForEmpty[h]; }; proc: EventProcType ¬ match.proc; DoWithLocks[c, action, XlDetails.ignoreErrors]; }; InitWindow: PUBLIC PROC [c: Connection, w: Window] = { h: DispatchHandle ~ c.dispatchStuff; [] ¬ CardTab.Delete[h.wDataTab, w.id]; }; RemoveWindow: PUBLIC PROC [c: Connection, w: Window] = { h: DispatchHandle ~ c.dispatchStuff; IF w#nullWindow THEN { deleted: BOOL ¬ CardTab.Delete[h.wDataTab, w.id]; IF deleted AND XlPrivateResources.ValidID[c, w.id] THEN XlPrivateResources.EntryFreeResourceID[c, w.id]; } }; InitConnection: PUBLIC PROC [c: Connection] = { c.dispatchStuff ¬ NEW[DispatchHandleRec ¬ [ wDataTab: CardTab.Create[], junkies: NEW[DispatchList], cache: NEW[XlRecycleMotionEvents.EventCache] ]]; [] ¬ GetOrCreateWindowData[c, Xl.nullWindow]; FOR i: NAT IN [0..Xl.ScreenCount[c]) DO [] ¬ GetOrCreateWindowData[c, Xl.NthScreen[c, i].root]; ENDLOOP }; END. Z XlDispatchImpl.mesa Copyright Ó 1990, 1991, 1992, 1993 by Xerox Corporation. All rights reserved. Christian Jacobi, November 12, 1990 5:42 pm PST Christian Jacobi, September 14, 1993 4:16 pm PDT All fields protected with C-Lock internalEvents: SetOfEvent _ unspecifiedEvents, --All events required by matchlists (removed because computed) checks event against dispatch list and queues calls on match --extension event --normal case: standard event C-Lock to prevent interference of removing and creating WindowData Returns number of used entries in DispatchList Convention container: REF DispatchList => use DispatchList (for junkies) container: WindowData => use wd.dl (for normal events) container: REF DispatchHandleRec => use h.priviledged (for priviledged events) In languages with VAR parameters this would be easy. With trace and sweep garbage collection using the ADDRESS would work. Using this convention allows reference counted runtime of Dorado. Returns index in DispatchList ready to be filled in (fields reasonably initialized) Also returns dl; but only reason is to be optimizer proof --silly error too long dispatch list, --wedge or crash the connection but not inside global monitor please --order of (dl.currLength) ! --get order right as dispatching is not monitored --makes entry a noop; still needs external removing --entry and indirection to safe against allocating a new list order optional ! Thoughts about damage if destroy happens in small not yet active time: If w is owned by this connection no immediate flush is required as the worst case behaviour is either only a memory leak, or a very fast re-creation of the window. Re-creation will however remove the dispatchlist first so no old events are dispatched. If the window was owned by a different connection we can not do this test. We hope that clients requesting events on different connections do an extra test themselves using details#NIL. XError ought to be caught. --You wouldn't believe it, but the nullWindow is ok (used for mapping changes) --Make sure the right events are requested. --Can ignore internalEvents as (except for generate) they are included in actualEvents. --local copy prevents troubles inside monitor --speed up normal case of no priviledgeds; normally there aren't any Paranoia: remove previous dispatchlist just in case due to errors it would be still around --Check for deleted; "openwin-3.0-beta" used to send multiple DestroyNotify events! --A window id leak for windows without not beeing included in the table is benign enough to accept for the benefit of not debugging openwin-3.0-beta again and again. ÊC–(cedarcode) style•NewlineDelimiter ™šœ™Icodešœ ÏeœC™NK™/K™0K™—šÏk œ˜ Kšœ˜Kšœ˜Kšœ˜K˜ Kšœ ˜ K˜ Kšœ˜Kšœ ˜ Kšœ˜K˜—šÏnœžœž˜Kšžœ?˜FKšžœ&˜-Kšžœ ˜&—Kšžœžœ˜K˜Kšœžœžœ˜-šœ!žœžœžœ˜7Kšœžœ˜Kšœžœ˜"Kšœ žœžœÏc%˜FKšœžœ!˜+Kšœ˜—Kšœžœžœ˜8K˜Kšœžœžœ˜)šœžœžœ˜ Kšœ žœ˜Kšœžœ žœžœ ˜)Kšœ˜—K˜Kšœ žœžœžœžœžœžœžœžœ*˜©K˜Kšœ žœžœ˜%šœžœžœžœ˜%Kšœ ™ Kšœžœ˜Kšœ0 *˜ZKšœ0 >™nKšœ- ˜GKšœ˜—K˜KšŸ œ 1œ˜SKšŸ œ ,œ˜LK˜Kšœžœ$žœ#˜TK˜š Ÿœžœžœ"žœ žœ.˜‘Kšœ)˜)Kš žœžœžœžœžœ  ˜Lšžœ%žœž˜4šœ˜Kšœ˜šžœžœžœ˜Kšœžœ˜šžœžœžœž˜#Kšžœžœ˜4Kšžœ˜—Kšžœ žœ˜K˜—Kšœ˜—Kšžœ˜—K˜—K˜šŸœžœžœžœ˜KKšžœ˜K˜K˜—šŸœžœ*žœ˜Fšžœ?žœž˜NKšœ0˜0Kšžœ,˜3—Kšžœžœžœ-˜LK˜K˜—šŸœžœžœ˜/Kšœ6˜6K˜K˜—šŸœžœžœ+˜@Kšœ˜Kšœ˜K™—šŸœžœžœ$˜BKšœ˜K˜K™—šŸœžœ!˜6Kšœ=™=Kšžœžœžœžœ˜šžœžœ7˜Gšžœ˜K™šžœžœž˜šœ˜šžœžœžœž˜#šžœžœžœžœžœ%žœžœž˜Kšžœžœ˜Kšœ ˜'Kšœ%žœ ˜:K˜—Kšž˜—Kšž˜—K˜—Kšžœ˜ —K˜—šžœ˜Kšœ™šžœžœžœž˜#šžœžœ˜ Kšœ ˜'Kšœ$˜$K˜—Kšžœ˜—K˜——K˜—K˜šŸœ žœ˜@Kšœ$˜$Kšœ&˜&K˜K™—šŸ œžœžœžœ˜NKšœ$˜$Kšžœžœ'˜5K˜K™—šŸœ žœžœ˜^KšœB™BKšœ$˜$Kšœžœ&˜1šžœžœžœ˜Kšœžœ˜Kšžœ&žœžœ˜9Kšœ!˜!Kšœ˜—K˜—K˜šŸ œžœžœžœ ˜=Kšœ.™.šžœžœžœ˜šžœžœžœž˜#Kšžœžœ ˜'Kšžœ˜——K˜K˜—šœ ™ Kšœ=™=Kšœ6™6Kšœ žœ@™NK™4K™EK™AK™—šŸœžœžœ žœ˜Ešžœ žœž˜Kšœ˜Kšœžœ˜#Kšœžœ*˜0Kšžœžœ˜—K˜—K˜š Ÿœžœžœ žœžœ˜Nšžœ žœž˜Kšœ˜Kšœžœ˜#Kšœžœ*˜0Kšžœžœ˜—K˜—K˜š Ÿ œžœžœ žœžœžœ˜RKšœS™SK™9Kšœ ˜ šžœžœ˜ Kšžœžœ5˜Cšžœžœžœ˜-Kšœžœ˜šžœžœžœ˜šžœ˜Kšœ&™&KšœD™DKšœ ˜ K˜—šžœ˜Kšœžœ ˜Kšœžœ˜.Kšœ˜šžœžœžœ˜šžœžœžœžœ˜$šžœžœ˜ KšœA˜AK˜—Kšžœ˜—K˜—Kšœ ˜ Kšœ˜K˜——Kšœ˜——Kšœ™Kšœ˜Kšœžœžœ˜Kšœžœ˜K˜Kšœ ˜ Kšœ˜—K˜šŸœžœžœ žœ;˜ešžœžœžœ˜Kšœ˜Kšœžœ ˜Kšœ%˜%Kšœ!˜!Kšžœžœžœ˜Kšžœ žœžœžœžœžœžœžœžœžœžœ˜lšž˜Kšœžœ˜Kšœ!˜!Kšœ1™1Kšœ˜Kšœ˜Kšœ˜KšœÐbfœ ˜Kšœ¡œ˜7Kšœ¡œ˜#Kšžœ˜—K˜—Kšœ˜—K˜š Ÿœžœžœ žœžœžœ˜_Jšœ3™3Jšœ=™=Kšœ.˜.šžœžœž˜šžœžœžœž˜#šžœžœ žœžœ˜?Jšœ™Kšœžœžœ˜Kšœžœ˜Kšœ˜Kšœ˜—Kšž˜——K˜K˜—šŸœžœžœ-˜_šžœžœž˜šžœžœžœžœ˜$šžœžœ˜Kšœ0˜0—Kšžœ˜——Kšœ˜K™—šŸœ žœ>žœ˜jKšžœžœžœ˜ Kšœ ˜-Kšœ˜Kšœ ˜)Kšœ žœ˜Kšœ˜šžœ žœžœ˜K˜IKšœF™FKšœÿ™ÿKšœº™ºKšœ™K˜—K˜K˜—šŸœžœ žœC˜kKšœ-˜-Kšœžœ˜ Kšœ? %˜dKšœ$˜$Kšœ˜K™—šŸ œžœžœ[žœ˜zšžœ"žœžœž˜3Kšœ+˜+Kšž˜—K˜K˜—šŸœžœžœU˜jKšœN™Nšœžœ˜ Kšœ-˜-KšœP˜PKšœ2˜2Kšœžœ˜!Kšœ$˜$Kš +™+Kš  œ /œ ™Wšžœžœ˜#Kšœ˜Kšžœžœ5˜LK˜—K˜—Kšœžœ˜ Kšœ ˜ Kšœ˜K™—šŸ œžœžœ?˜Wšœžœ 4˜UKšœ4˜4šžœ#žœ ˜IKšœžœ˜Kšžœžœ˜#K˜—šž˜Kšœ1 *˜[Kšœ=˜=šžœžœ˜Kšœ˜Kšžœžœ)˜@K˜—Kšžœ˜—K˜—Kšœ-™-Kšœ!˜!Kšœ%˜%Kš žœžœžœžœžœ!˜=K˜K™—šŸœžœžœ"˜Fšœžœ˜ Kšœ$˜$Kšœ!˜!K˜—K˜/K˜K™—šŸœžœ žœ2žœ%˜ˆšžœžœ˜$KšœÏbœ˜-Kšœ0˜0Kšœ˜Kšœ0˜0Kšœžœ &˜GKšžœ ˜K˜—K˜—K˜šŸœžœžœ#˜Cšœžœ˜ Kšœ$˜$Kšœ˜K˜—K˜/Kšœ˜K™—šŸœžœžœ"˜Ešœžœ˜ šŸ œžœžœ˜1KšœD™DKšžœžœž˜9Kšœ˜—Kšœ$˜$Kšœ3˜3Kšœ˜K˜—Kšœ!˜!K˜/˜K˜——šŸ œžœžœ˜6K™ZKšœ$˜$Kšœ&˜&K˜K™—šŸ œžœžœ˜8Kšœ$˜$šžœžœ˜Kšœ žœ%˜2KšœS™SKšœ¦™¦Kšžœ žœ%žœ1˜hKšœ˜—K˜K˜—šŸœžœžœ˜/šœžœ˜+Kšœ˜Kšœ žœ˜Kšœžœ"˜,Kšœ˜—Kšœ-˜-šžœžœžœž˜'Kšœ7˜7Kšž˜—K˜K˜—Kšžœ˜—…—+ÄEa