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
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
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
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]];
};
};
Type Declarations
Position:    TYPE ~ MouseTrap.Position;
MouseTrapSpecs:  TYPE ~ MouseTrap.MouseTrapSpecs;
Viewer:    TYPE ~ ViewerClasses.Viewer;
Mouse Trap Data
watch:    BOOL ¬ TRUE;
trapSpecs:    MouseTrapSpecs ¬ [enabled: FALSE];
fmps:     InterminalExtras.FastMouseParms ← InterminalExtras.GetFastMouseParms[];
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;
Wizard Control Procs
UserToMouseCoords: PUBLIC PROC [self: Viewer, vx, vy: INTEGER ¬ 0]
RETURNS [p: Position]
~ {
Convert coordinates in viewer to hardware mouse coordinates (upper left is [0, 0]).
[p.x, p.y] ¬ ViewerOps.UserToScreenCoords[self, vx, vy];
};
UserFromMouseCoords: PUBLIC PROC [self: Viewer, mx, my: INTEGER ¬ 0]
RETURNS [p: Position]
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.
~ {
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] ~ {
Establish new mouse trap, not necessarily enabled. Returns old trap specifications.
oldSpecs ¬ trapSpecs;
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];
};
trapSpecs ¬ newSpecs;
};
GetTrap: PUBLIC PROC RETURNS [MouseTrapSpecs] ~ {RETURN[trapSpecs]};
Get the specifications of the current mouse trap.
UnsetTrap: PUBLIC PROC RETURNS [MouseTrapSpecs] ~ {
Disable current mouse trap, without changing it otherwise. Return old trap specifications.
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;
IF debug # NIL THEN IO.PutF[debug, "entering TrapMouse\n"];
[changed, mouseTo] ¬ trapSpecs.proc[mouse, trapSpecs.data];
IF changed THEN ViewersWorld.SetMousePosition[viewersWorld, mouseTo.x, mouseTo.y];
IF debug # NIL THEN IO.PutF[debug, "leaving TrapMouse\n"];
};
};
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
Terminal.Current[].WaitForBWVerticalRetrace[];
};
MouseTrapWatcher: PROC ~ {
Runs as detached process, traps mouse in viewer.
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 ~ {
This is ordinarily called just once, when the Impl is run, to start background process.
KeyboardPriority: Process.Priority ~ 6;
save: Process.Priority ~ Process.GetPriority[];
watch ¬ TRUE;
Process.SetPriority[KeyboardPriority];
TRUSTED {Process.Detach[terminalProcess ¬ FORK MouseTrapWatcher[]]};
Process.SetPriority[save];
Terminal.RegisterNotifier[Terminal.Current[], MouseTrapController];
EnableTerminalHandler[]; -- we're on.
};
StopWatchingMouseTrap: PUBLIC PROC ~ {watch ¬ FALSE};
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;
};
EnableTerminalHandler: ENTRY PROC ~ {
ENABLE UNWIND => NULL;
terminalHandlerEnabled ¬ TRUE;
NOTIFY enableTerminalHandler;
};
DisableTerminalHandler: ENTRY PROC ~ {terminalHandlerEnabled ¬ FALSE};
Debug: PUBLIC PROC [out: IO.STREAM] ~ {debug ¬ out};
Internal Procs
MouseUpEscape: PUBLIC PROC RETURNS [allUp: BOOL] ~ {
Unsets trap and returns TRUE if all mouse buttons are up. Expect the unexpected.
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 ~ {
Together with BoxTrapData will keep mouse in rectangular box. Uses MouseUpEscape.
IF debug # NIL THEN IO.PutF[debug, "entering BoxTrap\n"]; {
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 ~ {
Together with RoundTrapData will keep mouse in circle. Uses MouseUpEscape.
IF debug # NIL THEN IO.PutF[debug, "entering RoundTrap\n"]; {
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;
};
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];
StartWatchingMouseTrap[];
END.