Cursor Wrap
Volume Number: 5
Issue Number: 0
Column Tag: Pascal Procedures
Related Info: Control Panel OS Utilities
Writing INITs in Pascal
By Steve Kiene, Lincoln, NE
Why Inits: Trap Patching, etc
INITs are pretty hot items these days. I have at least six in my System Folder and
I know people that have fifteen or more. INITs do anything from making it easier to
move through SFGetFiles (SFScrollINIT by Andy Hertzfeld) to creating large virtual
desktops (SteppingOUT II). INITs are used to customize the Macintosh. They are very
simple to install and remove (dragging them into or out of the System Folder).
The majority of INITs trap patches. Patch trapping is simply the rewriting of
the Macintosh’s internal routines or the modification of these routines for special
situations. This is done by using the traps GetTrapAddress,SetTrapAddress,
NSetTrapAddress, and NGetTrapAddress. Usually the programmer calls GetTrapAddress
and saves the address for later use; the programmer then puts this address
somewhere,usually in his/her code so that his/her code can check for the special
situation, do whatever-taken that it is the special situation, and then call the original
trap address. All trap patches should call the original trap address; this allows traps
to be patched more than one time and is the best bet for compatibility insurance.
I would guess that 90% of all INITs are written in assembly. I’m sure they
probably say that INITs are much easier to write in assembly. Well, it’s probably
true, if you know assembly, but, if you don’t know assembly, I would say that it isn’t
all that easy. This article, since it is in the Pascal section, demonstrates how to write
an INIT in Pascal, includes full source to an INIT that allows the cursor to wrap around
the screen, and gives a couple of hints on writing INITs in general.
Having the INIT install our patch.
INITs must load their code into the system heap so that the code will stay around
as application’s are launched (the application heap is flushed every launch or return
to the Finder). The INIT part of the code should put the trap patch or whatever code
into the system heap. In CursorWrap, the included INIT, we create a pointer in the
system heap that is four bytes greater than the size of our VBL task code; we then
BlockMove the code into the pointer starting at the location of the pointer plus four.
We store the pointer to our VBL task in the four bytes before our code so that we have
easy access to it. The code is written in MPW Pascal, but should be easy to convert to
any other development system.
{1}
Unit CursorWrap;
Uses
{$LOAD MAC.Dump}
MemTypes,QuickDraw,OSIntf,Toolintf,PackIntf,Script;
Procedure SetUpVBL;
Procedure Wrap;
Implementation
Procedure SetUpVBL;
var
theVBL : VBLTask;
myQElem : QElemPtr;
myErr : OSErr;
SaveZone : THz;
SizeNeeded : Longint;
PatchPtr : Ptr;
theCode : Handle;
thePtr : ^LongInt;
dummyErr : OSErr;
Begin
{ * Get handle to our code *}
theCode:=Get1Resource(‘INIT’,1);
{ * Use the system’s Heap * }
SaveZone:=GetZone;
SetZone(SystemZone);
{ * Get size of our patch code and our QElem ptr * }
SizeNeeded:=SizeResource(theCode)-(LongInt(@SetUpVBL)-LongInt(
theCode^))+sizeof(QElem);
ResrvMem(SizeNeeded);
If MemError<>NoErr then
begin
{ * If not enough room in system heap then get out * }
SysBeep(1);
SetZone(SaveZone);
exit(SetUpVBL);
end;
{ * Get new ptr for our code * }
PatchPtr:=NewPtr(SizeNeeded+4);
{ * blockmove our code in * }
BlockMove(@Wrap,Pointer(Ord(PatchPtr)+4),SizeNeeded);
{ * get new ptr for our VBL task * }
myQElem:=QElemPtr(NewPtr(sizeof(QElem)));
{ * restore zone * }
SetZone(SaveZone);
{ * put our vbl task ptr’s address into the ptr where our patch
code will be * }
thePtr:=Pointer(PatchPtr);
thePtr^:=LongInt(myQElem);
{ * set up VBL and install * }
with theVBL do
begin
qType:=Ord(vType);
vblAddr:=Pointer(Ord(PatchPtr)+4);
vblCount:=6;
vblPhase:=0;
end;
myQElem^.vblQElem:=theVBL;
dummyErr:=VInstall(myQElem);
End;
{------------------------------------------------------}
Procedure Wrap;
{**
This code allows the cursor to seemly wrap around the screen when the
location (mTemp) and if it is on one edge of the screen it moves it
to the the other edge. It puts the new cursor cooridinate into mTemp
and then puts $FF into cursorNew--this tells the cursor routines that
the cursor has moved
**}
const
CurrentA5 = $904;
var
myQElem : QElemPtr;
thePtr : ^LongInt;
CursorCoordPtr : ^Point;
ChangedPtr : ^Byte;
changed : Boolean;
theMap : KeyMap;
currGDevice : GDHandle;
mouseRect : Rect;
myRectPtr : ^Rect;
myPtr,myPtr2 : ^longint;
myRect : Rect;
Begin
{ * Get the keymap and check if option down; if is not then do not
allow wrap * }
GetKeys(theMap);
If theMap[58] then
Begin
{ * set up ptr to low memory global of cursor location * }
CursorCoordPtr:=Pointer($828);
changed:=false;
{ * get rectangle of screen * }
currGDevice:=GetGDevice;
If currGDevice<>Nil then
mouseRect:=currGDevice^^.gdRect
else
begin
{ * Use currentA5 to get offset to screenBits.bounds * }
myPtr:=pointer(CurrentA5);
myPtr2:=pointer(myPtr^);
myRectPtr:=Pointer(myPtr2^-116);
mouseRect:=myRectPtr^;
end;
InsetRect(mouseRect,1,1);
{ * check cursor location and change it if wrapping * }
With CursorCoordPtr^,mouseRect do
Begin
if v<=top then
Begin
v:=bottom-1;
changed:=true;
End
else if v>=bottom then
Begin
v:=top+1;
changed:=true;
End;
if h<=left then
Begin
h:=right-1;
changed:=true;
End
else if h>=right then
Begin
h:=left+1;
changed:=true;
End;
End;
{ * if we changed the cursor location then set the low memory
global cursorNew * }
If changed then
Begin
changedPtr:=Pointer($8CE);
{ * this tells the cursor drawing routines that the cursor
is moved * }
changedPtr^:=$FF;
End;
End;
{ * get ptr to VBL taks from where we originally put it * }
thePtr:=Pointer(Ord(@Wrap)-4);
{ * reset the vblcount so that we are executed again * }
myQElem:=QElemPtr(thePtr^);
myQElem^.vblQElem.vblCount:=6;
End;
End.
Another Way
There is another way to store variables. In our example INIT this would be the
VBL pointer address. This way involves using a header file that is written in
assembler. The header file does nothing but branch over instructions that take up
space. The header would look something like the following in MPW assembly:
Header MAIN EXPORT
Header1
BRA.S @1
DC.L 0
DC.L 0
DC.L 0
@1
END
This header would leave room for twelve bytes, three handles, or whatever. To
use this you would have to link the Header.a.o in first and reference it in you pascal
code. A little help:
{3}
Procedure Header; EXTERNAL;{must be after Implementation }
myPtr:=Ptr(Ord(@Header)+2); { myPtr points to the first byte of
storage }
The above statement returns a pointer to the address of the header plus two bytes,
so that we skip over the branch statement.
You would use this type of code when you are writing a WDEF, CDEF, etc. and you
want to have some type of communication between your patches and your custom
definition. We used this type of code when we wrote Tear-Off Menus and wanted our
custom WDEF and MDEF to be able to access globals that our Menu and Window Manager
traps used.
Added tips for coping with problems.
When writing INITs that are a little heavier than a ‘one trap patch’ INIT or a
simple VBL installation, then you are most likely going to run into problems with
certain programs. Maybe it will be because the guys at Microsoft didn’t quite follow
all of the rules or perhaps it’s some other reason. Regardless the problem, the answer
is to not install when you know you are not compatible. The easiest way to do this is to
try and load the resource type of the creator type (ie. MSWD for Microsoft Word)
when the application is launched.
{4}
theType:=Get1Resource(‘MSWD’,0);
If theType<>Nil then { the application exists }
Since some users can and do rename their applications, one can not just check for
application names. Creator types should be unique (if they are registered with Apple)
and no problems should be encountered this way.
Good luck with your INITs.