DIRECTORY KeyNames, KeyTypes, IO, MouseTrap, Process, Real, RealFns, UserInput, UserInputOps, ViewerClasses, ViewersWorld, ViewersWorldRefType, ViewerOps, ViewersWorldInstance; MouseTrapImpl: CEDAR MONITOR IMPORTS KeyNames, Process, Real, RealFns, UserInputOps, ViewersWorld, ViewerOps, ViewersWorldInstance EXPORTS MouseTrap ~ BEGIN round: MouseTrap.RoundTrapData ¬ NEW[MouseTrap.RoundTrapDataRep]; roundWrap: MouseTrap.RoundTrapData ¬ NEW[MouseTrap.RoundTrapDataRep ¬ [yes]]; box: MouseTrap.BoxTrapData ¬ NEW[MouseTrap.BoxTrapDataRep]; boxWrap: MouseTrap.BoxTrapData ¬ NEW[MouseTrap.BoxTrapDataRep ¬ [yes]]; TrapTillMouseUp: PUBLIC PROC [v: Viewer, circle, wrapAround: BOOL] ~ { IF circle THEN { trapData: MouseTrap.RoundTrapData ¬ IF wrapAround THEN roundWrap ELSE round; radius: REAL ¬ (v.cw-1)/2.0-1.5; trapData.radiusSquared ¬ radius*radius; trapData.center ¬ UserToMouseCoords[v, v.cx+(v.cw-2)/2, v.cy+(v.ch-2)/2]; [] ¬ SetTrap[[TRUE, RoundTrap, trapData]]; } ELSE { trapData: MouseTrap.BoxTrapData ¬ IF wrapAround THEN boxWrap ELSE box; trapData.minCorner ¬ UserToMouseCoords[v, v.cx, v.cy]; trapData.maxCorner ¬ UserToMouseCoords[v, v.cx+v.cw, v.cy+v.ch]; [] ¬ SetTrap[[TRUE, BoxTrap, trapData]]; }; }; Position: TYPE ~ MouseTrap.Position; MouseTrapSpecs: TYPE ~ MouseTrap.MouseTrapSpecs; Viewer: TYPE ~ ViewerClasses.Viewer; watch: BOOL ¬ TRUE; trapSpecs: MouseTrapSpecs ¬ [enabled: FALSE]; terminalProcess: PROCESS; terminalHandlerCurrent: INTEGER ¬ 0; terminalHandlerEnabled: BOOL ¬ FALSE; enableTerminalHandler: CONDITION; viewersWorld: ViewersWorldRefType.Ref ¬ ViewersWorldInstance.GetWorld[]; inputHandle: UserInput.Handle ¬ ViewersWorld.GetInputHandle[viewersWorld]; leftMouse: KeyTypes.KeySym ¬ KeyNames.KeySymFromName["LeftMouse"]; middleMouse: KeyTypes.KeySym ¬ KeyNames.KeySymFromName["MiddleMouse"]; rightMouse: KeyTypes.KeySym ¬ KeyNames.KeySymFromName["RightMouse"]; debug: IO.STREAM ¬ NIL; UserToMouseCoords: PUBLIC PROC [self: Viewer, vx, vy: INTEGER ¬ 0] RETURNS [p: Position] ~ { [p.x, p.y] ¬ ViewerOps.UserToScreenCoords[self, vx, vy]; }; UserFromMouseCoords: PUBLIC PROC [self: Viewer, mx, my: INTEGER ¬ 0] RETURNS [p: Position] ~ { tx, ty: INTEGER; -- translation from viewer to screen [tx, ty] ¬ ViewerOps.UserToScreenCoords[self, 0, 0]; p ¬ [mx-tx, my-ty]; }; SetTrap: PUBLIC PROC [newSpecs: MouseTrapSpecs] RETURNS [oldSpecs: MouseTrapSpecs] ~ { oldSpecs ¬ trapSpecs; trapSpecs ¬ newSpecs; }; GetTrap: PUBLIC PROC RETURNS [MouseTrapSpecs] ~ {RETURN[trapSpecs]}; UnsetTrap: PUBLIC PROC RETURNS [MouseTrapSpecs] ~ { specs: MouseTrapSpecs ¬ trapSpecs; specs.enabled ¬ FALSE; RETURN[SetTrap[specs]]; }; TrapMouse: PROC [mouse: Position] ~ { IF trapSpecs.enabled AND trapSpecs.proc # NIL THEN { mouseTo: Position; changed: BOOL ¬ FALSE; [changed, mouseTo] ¬ trapSpecs.proc[mouse, trapSpecs.data]; IF changed THEN ViewersWorld.SetMousePosition[viewersWorld, mouseTo.x, mouseTo.y]; }; }; WaitForTick: PROC ~ { WaitForEnabled: ENTRY PROC ~ INLINE { UNTIL terminalHandlerEnabled DO WAIT enableTerminalHandler; ENDLOOP; }; WaitForEnabled[]; Process.Pause[1]; -- can't get vert. retrace; also, scheduler operates only every 1/10 sec }; MouseTrapWatcher: PROC ~ { x, y: INTEGER; WHILE watch DO Process.CheckForAbort[! ABORTED => EXIT]; WaitForTick[]; -- run once per tick [x: x, y: y] ¬ ViewersWorld.GetMousePosition[viewersWorld]; TrapMouse[[x, y]]; ENDLOOP; }; StartWatchingMouseTrap: PUBLIC PROC ~ { KeyboardPriority: Process.Priority ~ 6; save: Process.Priority ~ Process.GetPriority[]; watch ¬ TRUE; Process.SetPriority[KeyboardPriority]; TRUSTED {Process.Detach[terminalProcess ¬ FORK MouseTrapWatcher[]]}; Process.SetPriority[save]; EnableTerminalHandler[]; -- we're on. }; StopWatchingMouseTrap: PUBLIC PROC ~ {watch ¬ FALSE}; EnableTerminalHandler: ENTRY PROC ~ { ENABLE UNWIND => NULL; terminalHandlerEnabled ¬ TRUE; NOTIFY enableTerminalHandler; }; DisableTerminalHandler: ENTRY PROC ~ {terminalHandlerEnabled ¬ FALSE}; Debug: PUBLIC PROC [out: IO.STREAM] ~ {debug ¬ out}; MouseUpEscape: PUBLIC PROC RETURNS [allUp: BOOL] ~ { allUp ¬ UserInputOps.GetLatestKeySymState[inputHandle, leftMouse] = up AND UserInputOps.GetLatestKeySymState[inputHandle, middleMouse] = up AND UserInputOps.GetLatestKeySymState[inputHandle, rightMouse] = up; IF allUp THEN [] ¬ UnsetTrap[]; }; BoxTrap: PUBLIC MouseTrap.MouseTrapProc ~ { d: MouseTrap.BoxTrapData ¬ NARROW[data]; IF MouseUpEscape[] THEN RETURN[changed: FALSE, mouseTo: mouse]; mouseTo.x ¬ SELECT mouse.x FROM < d.minCorner.x => (SELECT d.wrap FROM x, yes => mouse.x-d.minCorner.x+d.maxCorner.x, ENDCASE => d.minCorner.x), > d.maxCorner.x => (SELECT d.wrap FROM x, yes => mouse.x+d.minCorner.x-d.maxCorner.x, ENDCASE => d.maxCorner.x), ENDCASE => mouse.x; mouseTo.y ¬ SELECT mouse.y FROM < d.minCorner.y => (SELECT d.wrap FROM y, yes => mouse.y-d.minCorner.y+d.maxCorner.y, ENDCASE => d.minCorner.y), > d.maxCorner.y => (SELECT d.wrap FROM y, yes => mouse.y+d.minCorner.y-d.maxCorner.y, ENDCASE => d.maxCorner.y), ENDCASE => mouse.y; changed ¬ NOT (mouseTo.x = mouse.x AND mouseTo.y = mouse.y); }; RoundTrap: PUBLIC MouseTrap.MouseTrapProc ~ { d: MouseTrap.RoundTrapData ¬ NARROW[data]; xDelta: INTEGER ~ mouse.x-d.center.x; yDelta: INTEGER ~ mouse.y-d.center.y; dist: REAL ~ xDelta*xDelta+yDelta*yDelta; IF MouseUpEscape[] THEN RETURN[changed: FALSE, mouseTo: mouse]; IF dist > d.radiusSquared THEN { scale: REAL ¬ RealFns.SqRt[d.radiusSquared/dist]; IF d.wrap = no THEN mouseTo ¬ [d.center.x+Real.Fix[xDelta*scale], d.center.y+Real.Fix[yDelta*scale]] ELSE { scale ¬ Real.FScale[scale, 1]; mouseTo ¬ [mouse.x-Real.Fix[xDelta*scale], mouse.y-Real.Fix[yDelta*scale]]; }; changed ¬ TRUE; } ELSE changed ¬ FALSE; }; StartWatchingMouseTrap[]; END. Ύ MouseTrapImpl.mesa Copyright Σ 1989, 1992 by Xerox Corporation. All rights reserved. Ken Shoemake, May 16, 1989 3:10:50 am PDT Jules Bloomenthal July 20, 1992 4:42 pm PDT For performance and stability reasons, this module is not expected to work in the X Window environment (setting mouse position apparently would be too slow and KeyCode-KeySym mapping isn't guarranteed stable) Should a fast mouse become available, then code (presently commented) can be used so that, when a mouse trap is enabled, the fast mouse feature is disabled to avoid fighting over where the mouse should go. When the trap is disabled, the fast mouse feature is restored to its original condition. If you can't live without a fast mouse while trapping, you'll have to write your own trap that implements fast mousing along with the trapping. Simple Client Access Type Declarations Mouse Trap Data fmps: InterminalExtras.FastMouseParms _ InterminalExtras.GetFastMouseParms[]; Wizard Control Procs Convert coordinates in viewer to hardware mouse coordinates (upper left is [0, 0]). Convert hardware mouse coordinates (upper left is [0, 0]) to coordinates for viewer. Note that mouse may be outside this viewer. Not recommended for use within MouseTrapProc, where it's better to use only mouse coords to avoid overhead. Establish new mouse trap, not necessarily enabled. Returns old trap specifications. IF oldSpecs.enabled # newSpecs.enabled THEN { IF newSpecs.enabled THEN { fast: InterminalExtras.FastMouseParms _ InterminalExtras.GetFastMouseParms[]; fmps _ fast ; fast.enabled _ FALSE; InterminalExtras.SetFastMouseParms[fast]; } ELSE InterminalExtras.SetFastMouseParms[fmps]; }; Get the specifications of the current mouse trap. Disable current mouse trap, without changing it otherwise. Return old trap specifications. IF debug # NIL THEN IO.PutF[debug, "entering TrapMouse\n"]; IF debug # NIL THEN IO.PutF[debug, "leaving TrapMouse\n"]; Terminal.Current[].WaitForBWVerticalRetrace[]; Runs as detached process, traps mouse in viewer. This is ordinarily called just once, when the Impl is run, to start background process. Terminal.RegisterNotifier[Terminal.Current[], MouseTrapController]; Kill the background process, after which no mice will be trapped. MouseTrapController: Terminal.SwapNotifier ~ TRUSTED { [vt: Virtual, action: SwapAction, clientData: REF ANY] SELECT action FROM coming => { IF terminalHandlerCurrent = 0 THEN EnableTerminalHandler[]; terminalHandlerCurrent _ terminalHandlerCurrent + 1; }; going => { terminalHandlerCurrent _ terminalHandlerCurrent - 1; IF terminalHandlerCurrent = 0 THEN DisableTerminalHandler[]; }; here, gone => NULL; ENDCASE => ERROR; }; Internal Procs Unsets trap and returns TRUE if all mouse buttons are up. Expect the unexpected. Together with BoxTrapData will keep mouse in rectangular box. Uses MouseUpEscape. IF debug # NIL THEN IO.PutF[debug, "entering BoxTrap\n"]; { Together with RoundTrapData will keep mouse in circle. Uses MouseUpEscape. IF debug # NIL THEN IO.PutF[debug, "entering RoundTrap\n"]; { Start Code Could use PBasics.IsBound to see if X Windows is loaded; if it is, we don't want to execute: Loader.MakeProcedureResident[MouseTrapWatcher]; Loader.MakeGlobalFrameResident[MouseTrapWatcher]; Κ ‹•NewlineDelimiter –"cedarcode" style™šœ™Jšœ Οeœ7™BJ™)J™+J˜—šΟk œ§˜°J˜—šΠbl œžœž˜Jšžœ^˜eJšžœ ˜J˜—šœž˜J˜Iblock–5 pt bigger leadingšŸΠ™ΠK™Έ—headšΟl™Jšœ"žœ˜BJšœ%žœ%˜MJšœžœ˜=šœ"žœ#˜HJ˜—šΟnœžœžœ!žœ˜Fšžœ˜ šžœ˜Jšœ$žœ žœ žœ˜LJšœžœ˜ J˜'J˜IJšœžœ˜+J˜—šžœ˜Jšœ"žœ žœ žœ˜FJ˜6J˜@Jšœžœ˜)J˜——J˜——š ™Jšœ žœ˜'Jšœžœ˜1Jšœžœ˜'—š ™Jšœžœžœ˜Jšœ)žœ˜0šœQ™QJ˜—Jšœž œ˜Jšœžœ˜'Jšœžœžœ˜%šœž œ˜"J˜—J˜I˜LJ˜—J˜DJ˜G˜FJ˜—Jšœ žœžœžœ˜—š ™š‘œžœžœžœ˜BJšžœ˜Jšœ˜J™SJ˜8J˜J˜—š‘œΟsž’ž’œ’œ’œ’œ’žœ˜DJšžœ˜J™TJ™KJ™LJšœ˜JšœžœΟc$˜5J˜4J˜J˜J˜—š‘œžœžœžœ˜VJ™TJ˜šžœ$™&šžœ™šžœ™šžœ™JšœM™MJšœ ™ Jšœžœ™Jšœ)™)J™—Jšžœ*™.—J™——J˜J˜J˜—š ‘œžœžœžœžœ ˜DJ™1J˜—š‘ œžœžœžœ˜3J™[J˜"Jšœžœ˜Jšžœ˜Jšœ˜J˜—š‘ œžœ˜%šžœžœžœ˜4Jšœ˜Jšœ žœžœ˜Jšžœ žœžœžœ%™;J˜;JšžœžœC˜RJšžœ žœžœžœ$™:J˜—J˜J˜—š‘ œžœ˜š‘œžœžœžœ˜%Jšžœžœžœ˜DJšœ˜—J˜Jšœ£H˜ZJ™.J˜J˜—š‘œžœ˜Jšœ0™0Jšœžœ˜šžœž˜Jšœžœžœ˜)Jšœ£˜#J˜;J˜Jšžœ˜—Jšœ˜J˜—š‘œžœžœ˜'J™WJšœ'˜'Jšœ/˜/Jšœžœ˜ Jšœ&˜&Jšžœ#žœ˜DJšœ˜J™CJšœ£ ˜%J˜J˜—š‘œžœžœ žœ˜5J™AJ˜—š‘œžœ™6Jšœ.žœžœ™6šžœž™™ Jšžœžœ™;J™4J™—™ J™4Jšžœžœ™