BitNapper
Volume Number: 2
Issue Number: 5
Column Tag: Graphics Lab:Asm
BitNapper DA Steals Bit Maps! 
By Chris Yerga, MacTutor Contributing Editor
Anguish and Fear
Welcome to the first installment of the Graphics Lab. This is where we really see
what the graphics capability of the Macintosh is like. This month's column will discuss
desk accessories and the techniques necessary to implement them using MDS, and in
particular, the marvelous BitNapper DA, which will become very important to us next
month. I have always had problems understanding what desk accessories really are and
why mine never worked. Consequently, when I realized that I needed to write a DA to
simplify the development of a game, I was wracked with apprehension. So my special
thanks go to BMUG's Dave Burnard, who taught me how to write desk accessories
without even realizing it. Thanks Dave.
Cutting Up
First, lets discuss what the desk accessory will do. The BitNapper DA's,
function is to allow the user to "cut" rectangular reigons from the screen image. The
DA will then create a bitMap out of the data, format it as an MDS source file, and write
it out to disk. This allows us to cut graphics from MacPaint or any other application
that supports desk accessories, and use them in our own MDS programs. More details
on this technique will be presented next month when we use our BitNapper DA with
another graphics program to create animated effects.
Since we want access to graphics anywhere on the screen, it is undesirable to
have our desk accessory based in a window which will cover up some area of the screen.
As a result, we will make our DA menu based. When the desk accessory is opened, it
will install it's own menu in the menu bar, and it will remove the menu when it's
closed.
The essence of desk accessories
Now lets take a look at the desk accessory itself. From a low-level veiwpoint, a
desk accessory is a device driver. That is, desk accessories and device drivers are
structured similarly. The structure that they share is somewhat different from that of
a standard Macintosh application, and consequently, the programming approach is a
bit different. To understand why this is, lets look at figure #1.
Figure #1 shows the structure of a DA header. The first word, drvrFlags,
specifies what types of calls are supported by the desk accessory, whether it needs to
be called periodically, etc. Our DA will set this to indicate that Control calls are
supported, and that the accessory should be locked in memory after it's loaded. The
following word is drvrDelay, which determines how often, in ticks, the desk
accessory needs to be called. Since we don't need to update an on-screen clock or
perform any similar task, we don't require periodic calls and will set this field to NUL.
The next word, drvvrEMask, is the event mask for our DA. We will set ours to
NUL, because our DA doesn't respond to event manager events. Rather, our DA will
respond to desk manager events that are sent when a menu item is selected. The ID of
the menu belonging to our DA is stored in the next field, drvrMenu.
The following 5 words are offsets to the Open, Prime, Control, Status, and
Close calls relative to the beginning of the desk accessory. The Open routine is called
when the DA is first opened, and is responsible for defining and installing the menu,
and other initialization tasks. The Prime routine is called when the accessory needs a
periodic event, our Prime routine simply returns. The Control routine is the heart of
the DA, and is responsible for handling events. Our DA has no Status routine, since it
is not appropriate. Finally, the Close routine removes the DA's menu and frees up any
storage allocated by the DA.
Following the offsets is an optional name for the desk accessory. This is not
where the name that appears in the menu bar is stored; rather, this is available for
internal use by the DA. For example, a DA could look here to find the title for it's
window, etc. We don't use this name. The remaining data is the actual code for the DA's
routines.
But wait, there's more...
There is another data structure that we must concern ourselves with. This is the
Device Control Entry. The DCE is a 40 byte relocatable block that is allocated on the
system heap when a desk accessory is opened. As illustrated in figure #2, the DCE
contains various information about the DA, and copies of some of the data in the DA
header. Although it is primarily intended for internal use by the device manager and the
desk manager, there are a couple of fields relevant to our discussion.
The DCE contains a field which the DA may use to store a handle to some private
storage which it allocates. This field, dCtlStorage, will be where we store the
menuHandle for the DA's menu.
Another important field is dCtlWindow, in which the DA may store the
windowPtr of it's main window. Its purpose is analogous to our use of dCtlStorage for
DA's which are window based. For such DA's it is important to set the windowKind field
of the window record to the DA's driver reference number, which is located in
dCtlRefNum. This marks the window as belonging to a DA, and insures that Desk
Manager and Event Manager routines work properly.
The tables are turned...
We have dealt with register-based calling conventions in the past. This is where
arguments are passed in a data structure pointed to by an address register rather than
on the stack. But this time we are on the receiving end. Instead of us having to set up
the proper fields of a parameter block, we are passed parameter blocks to use or
ignore as we wish. When a DA routine is called, A0 contains a pointer to the
IOParamBlock and A1 contains a pointer to the DA's DCE. When a DA routine is
finished, it must send an error code back in D0. If D0 is cleared, then no error has
occurred.
For openers
The first thing the Open routine does is save the registers on the stack. Next, it
copies the DCE pointer from A1 into a less volatile register. Then it makes sure this is
the first call to the Open routine by testing the dCtlStorage field of the DCE. If there
isn't a menuHandle stored here (it's NIL) then a menu is created and the menuHandle is
stored, otherwise it just returns.
The Close routine does the opposite. It removes the menu from the menu bar,
releases the memory occupied by it, and clears the dCtlStorage field.
The Prime and Status routines don't exist, so the offsets in the DA header point
to a routine called Null which simply clears D0 to signify that no error has occurred
and returns.
On with the show
The Control routine is the functional element of the DA. When it is called, it
checks the csCode field of the IOParamBlock to see if an event of interest has occurred.
There are 9 possible messages passed in csCode; they are listed on page 14 of the Desk
Manager Programmer's Guide in Inside Macintosh. However, our DA only acknowledges
1 of these messages.
This is accMenu, which has a decimal value of 67. When an accMenu message is
passed to a DA, the csParam field of the IOParamBlock contains the MenuID of the menu
and csParam+2 contains the item number. SInce we only have one menu, we don't need
to check the MenuID. Rather, we check the item number to determine which command
was selected.
One of the menu items is Word Constraint, which is toggled on and off by
repeated selctions. What this does is force the rectangular regions to have widths which
occur on word boundaries; this is a convenient format for certain graphic applications.
When this item is selected, the DA calls _GetItmMark to determine if the item is
checked. It toggles the appearance of the item with _CheckItem and returns.
The main menu item is Steal Bits. When this is called, the cursor is hidden and
is replaced by a small rectangle which inverts whatever it is currently over. The user
positions this at the upper left corner of the region he wants to steal. Then the user
drags to the lower right of the region. The routine inverts the rectangular region
selected until the mouse button is released.
When the button is released, the DA calculates the relevant information needed
to create a BitMap data structure containing the selected bits. When this is done, a
nonrelocatable block of proper size is allocated on the application heap and the screen
data is copied via _CopyBits. The user is then prompted for a filename via SFPutFile
and so on. I won't go into detail about this portion of the DA, as it is very similar to my
icon converter in Vol. 2 No. 1. of MacTutor. That issue also contains additional
information about the structure of BitMaps, if the above seems unclear.
Get it where it counts
Before we can use the DA, we must install it into the System file. A DA is a
resource of type DRVR. At the beginning of the source file we use the Resource
directive of MDS to assemble the code into a DRVR resource. After linking, the DA can
be installed into System via the font/DA mover by holding down the option key and
clicking on the open button. This allows us to open the output file which contains our
DA resource. Other more tedious options include using the resource editor, resource
mover, etc. During development of a DA, I usually just link the .REL file of the DA into
the resource fork of a shell application that does nothing else but support DA's. This
saves me from the tedious work of installing the DA after every assembly.
I hope that I have shed some light onto desk accessories. If you are interested in
this particular DA or graphic techniques in general, more specific information will be
coming next month in our special animation issue. Enjoy!
;
; BitNapper
; by Chris Yerga
;
RESOURCE 'DRVR' 27 'BitNapper'
INCLUDE MacTraps.D
.TRAP _MakeFile $A008
MACRO Center String,MidPt,Y =