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 Macintoshs 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. Im sure they probably say that INITs are much easier to write in assembly. Well, its probably true, if you know assembly, but, if you dont know assembly, I would say that it isnt 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 applications 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;
Interface
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 systems 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 ptrs 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
<Option> key is held down. It checks the low level interupt mouse 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 didnt quite follow all of the rules or perhaps its 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.