Button DA
Volume Number: 2
Issue Number: 5
Column Tag: Assembly Language Lab
Panic Button DA Shows Screen Blanking 
By Ed Ludwig, Garfield, NJ
Panic Button DA
It is very difficult these days to find a computing magazine that isn't 98% fluff
(and meaningless advertisments) and 2% useful information. MacTutor is the only
magazine that I actually look forward to reading each month from cover to cover!
Here is a little gem, just to show my appreciation for all the hard work the
MacTutor staff puts in each month, and one I hope proves useful to our readers as well.
Here is a desk accessory (very tiny!) that I wrote using the MDS Assembler. It's
function is very simple; it's a Panic Button! There are probably others that are more
sophisticated than this one, but sometimes less is more.
Fig. 1 The Panic Button Exposed!
When you invoke it, you are given a tiny dialog box which you can move and hide
in a corner of the screen. When you are in the middle of your favorite program
(doodling in MacPaint for example...) and your boss comes along, what do you do? HIT
THE PANIC BOTTON! When you hit the button, the whole screen goes dark as if the Mac
is turned off and you are save! After the boss goes away, click the mouse, and
everything is just as you left it.
There are only two things to remember: First, move the mouse away from the
spot where you pressed the panic button when you click to restore the screen, because
if you click inside the button again, the whole screen will flash dark again. Second, the
program grabs about 22K of storage to save away the screen buffer while the screen is
dark (and then restores it again) so it may not work with large programs that don't
leave 22K of memory in the Mac to spare; however, a simple Alert box will be
triggered with the message "Not Enough Memory' to inform you of that fact if a memory
problem develops.
Program Details
Desk accessories were covered in detail last month in both C and assembly, but
this little DA is very nicely done and easy to follow. In fact, it will serve as a better
introduction than the more complicated DA published last month if you are new to DA
programming. As with all DA's, we begin with a table of word length constants that tell
the desk manager where the five entry points of the DA are. These five entry points are
standard for any type driver application, which is what a desk accessory is. The entry
points are for opening the DA, closing the DA and control of the DA. The other two entry
points are normally associated with device drivers that require a status check or a
prime routine for set-up. Immediately after this table of entry points is the string title
of our desk accessory.
The three routines for open, close and control then follow. In general, the desk
manager will pass control to either one of these three routines from the currently
running application depending on circumstances. When the user first selects the DA
from the Apple menu, the open entry will be invoked. When a control in the DA's
window is pressed, the control entry will be invoked. When the user no longer requires
the DA and presses the close box, then the close entry will be invoked. We must be
prepared for the DA to begin execution at any one of these three entry points.
Open and Close
When we open or close the DA, we first save the application's graph port. For
some reason, the desk manager does not do this for us. Care must be taken to save all the
registers and e specially to protect the contents of A0 and A1, which contain information
vital to the DA. It is very easy to forget and make a trap call and find out the DCE pointer
is messed up because the A1 register was not protected from alteration. When the DA is
called, register A0 contains a pointer to the IO Parameter Block and A1 contains the
pointer to the Device Control Entry Record. These two structures contain all the
information the DA needs to find out what to do. In the IO Parameter block is a field
called csCode which we use to branch on to handle the various events passed to the DA.
This event processing is done in the Control entry of our DA. In the Open and Close entry
portions, we make use of the Device Control Entry Record to set up any global storage
space required by the DA. The field dCtlStorage is used to hold a handle to any global
variable space required by the DA. Both the DCE and the IO Parameter block were shown
in detail in last months's issue of MacTutor, and are documented in Inside Macintosh.
Resource ID
One thing we need to do when we open the DA is to establish the base ID for our
resources. In the top of our DA source code, we tell the linker that the code that follows
is a resource of type DRVR and we give it a resource Id number as shown:
RESOURCE 'DRVR' 25 'Panic Button'
In fact, this entire source file is all resources! After the code resource, we have
our 'traditional' resources. Anyway, the number 25 indicates the Id number for this
DRVR resource, and hence for our DA. All other resources must be a function of this
number, because the Font/DA Mover will change the DA number when it inserts this DA
into the system file so that no two DA's have the same Id. The formula for calculating
the RMaker resource ID number from this DA reference number is as follows:
RMaker ID = (DArefNum X 32) + Index - 16384
The index is used for owned resources such as Alert items, where the resource
"owns" other resources. Otherwise, it is normally zero. Since our DA reference
number is 25, we calculate:
RMaker ID = 25 X 32 +0 -16384 = -15584
Hence this is the number we would use for our resource ID's if we were using the
RMaker file to compile our resources. It turns out that RMaker has a problem opening a
".REL" file created by the Consulair C compiler for a DRVR Proc type resource. As a
result of this bug, I was unable to use RMaker to complete the DA so I have translated
all the RMaker resources into assembly language and have used the Consulair linker to
create the DA. If your using the MDS assembler, simply assemble and link this file with
the MDS linker. I have to use the Consulair compiler for my MDS assembler because I
have a Mac Plus and Apple hasen't finished fixing the MDS system to run properly
under HFS. Actually, the Consulair Compiler makes a great MDS alternative. But when
the 16 bit value -15584 is used for the resource ID, the assembler converts this to a
32 bit value of FFFF C320 which does not work for a resource ID. So what we do is take
-15584, convert it to hex with a hex calculator (included on the source code disk for
this issue) to FFFF C320, take the last word, C320 and convert that back to decimal,
which gives us 49952. This is the decimal value we use for our resource ID numbers
when coding resources in assembly language.
The last thing we do in our open routine is to set up a dialog type window and to
save the window pointer back in our DCE record and mark that window as a system
window belonging to a DA by updating the windowkind field in our newly created window
record. We then restore the users graph port and exit, clearing D0 to show the DA
executed properly.
For the close routine, we do much the same thing only in reverse. This time we
dispose of our window and release any global storage we might have referenced to
dctlstorage field of our DCE record. In this DA, we don't use any storage except briefly
during our control loop when we blank the screen.
The Control Loop
That brings us to the control loop, where the events processed by this DA are
handled. The IO Parameter block entry cscode tells us which event was passed to us from
the desk manager. In our case, the only event we will see is a dialog event when the user
presses the panic button. The csparam field of the IO Parameter block contains the
pointer to our event record, so we use that along with our window pointer to call
_ dialogselect, which returns the itemhit with the control item that caused the event. If
we didn't get an event, then we take the control exit through JIODone and return to the
current application.
If we did get an event, then that means the user pressed the panic button and our
job is to clear the screen. Now the control entry code does it's work. We first get a
handle to 22K of storage space so we can copy the current screen contents into a safe
area on the heap. If this request becomes a problem for the Mac, then we check the
error code returned in D0 to see if it's a memory full error, -108. If so, then we
forget the screen blanking and exit because there is not enough memory available to
lock up the necessary storage to blank the screen. However if it's some other type of
error, the routine is executed with the assumption that the new handle was returned.
This might cause a problem but I can't think under what circumstances it might happen.
If the error is a memory full, we display an alert message using our alert resource,
and then dispose our window and exit through JIODone. Since our simple DA doesn't need
any attention, we return no error code and it's just as if we were never called.
Assuming no error was returned, we can then blank the screen. We do this by
locking our handle after saving it in the dctlstorage field of the DCE record and then
calling our blank screen subroutine. When we return, we unlock our handle and get rid
of the storage space and exit.
Screen Save
That leaves only the screen save and get id base subroutines to discuss. The
screen save subroutine moves each byte of the screen buffer to our 22K storage space
on the heap so it can be restored later, and fills the screen with $FFFF for black. A
pointer to the base of the screen buffer area is contained in the system global location
$824. We load the screen buffer pointer into A3 and the deferenced handle of our screen
storage area into A2 and do an indirect move to move each word from the screen to the
storage. Our loop counter is 10,943 words of memory or 21,886 bytes of memory.
(Note that in the old 128K Macs, after the screen buffer, you have just over 100K, and
with another 20K for various system things like heaps and stacks and such, the poor
machine had only 80K or so for programs. Since most Mac programs take at least 60K,
it's no wonder the skinny mac died of memory starvation.)
After setting the screen black, we loop on the buttonstate until it changes and we
can then restore the screen and exit. The button state is stored in a system global at
$172 and will be set when a button down event is registered by the system.
Our resources provide us with a dialog window containing a single control item, a
button. The DLOG and first DITL resources specify our window and panic button. The
next resource is an alert: a sorry button control and a string telling the user there is
not enough memory. This alert is invoked when we detect a memory full error when we
try to get a handle to 22K of heap space for saving the screen buffer. Note that the alert
resource "owns it's DITL list and so the resource ID of it's DITL list is incremented
accordingly.
That completes our simple, but fun little DA. Enjoy.
Assembly Language Source Code
;Panic Button DA
; By Ed Ludwig for MacTutor
RESOURCE 'DRVR' 25 'Panic Button'
;includes (probably not all are neccesary)
Include MacTraps.d ; sys and toolbox traps
Include ToolEqu.d ; Use toolbox equates
Include QuickEqu.d ; Use quickdraw equates
Include SysEqu.d ; Use system equates
include FSEqu.d ;file system equates
;my equates
screenbase equ $824 ;pointer
buttonstate equ $172 ;byte
; first comes the device control entry table
accentry:
dc.w $0400 ;control events only
dc.w 0 ;set only if acc. needs time
dc.w $0142 ;event mask
dc.w 0 ;menu id (none at the moment)
;offsets into routines used by desk manager