Writing DAs, Inits, and CDevs David A. Lyons, Apple II Developer Technical Support Matt Deatherage, Apple II Developer Technical Support Don Brady, Green System Software (all of Apple Computer) July 18, 1990 There are two kinds of desk accessories on the Apple IIgs: Classic Desk Accessories (which show up when you hit Apple-Ctrl-ESC) and New Desk Accessories (which you select from the Apple menu in a desktop program). Classic Desk Accessories (CDAs) Reference material: the Desk Manager chapters of Apple IIgs Toolbox Reference volumes 1 and 3, Apple IIgs Technical Note #71. CDAs are executed in the text environment, and the stack is in page 1 of bank 0 ($00/01xx). This means you only have 256 bytes of stack space to work with, which isn't very much if you're making toolbox calls or writing in a high-level language like C or Pascal. Apple IIgs Technical Note #71 shows you how to attempt to get more memory for a stack, but the memory is not always available (under ProDOS 8, all the bank 0 memory is already allocated for use by the application, so you're stuck with just one page). Reading the keyboard is tricky, becuase keyboard interrupts may or may not be started up (the Event Manager is one thing that uses keyboard interrupts to read the keyboard). You can use IntSource in the miscellaneous tools to temporarily disable keyboard interrupts, so the Event Manager won't steal keystrokes away from you, or you can call GetNextEvent to read keys if EMStatus indicates the Event Manager is active. CDAs get a chance to do special things when the system is booted, and other times when applications call DeskShutDown. Every CDA's "shutdown" entry point gets called whenever DeskShutDown is called. CDAs are not notified when DeskStartUp is called. If you make GS/OS calls, be sure to check the OS_KIND byte at $E1/00BC. You'll find $00 in the 8-bit world and $01 in the 16-bit world. The user can enter your CDA under ProDOS 8. With a third-party product, a user can even load your CDA without booting GS/OS at all--it's polite not to crash in these circumstances, so check OS_KIND even when your "shutdown" routine is called for the first time. (There is no completely reliable way to make ProDOS 8 calls from a CDA.) There's a RemoveCDA toolbox call in the Desk Manager in System Software 5.0 and later. This removes a CDA from the list, but it doesn't dispose of the CDA's memory, because there's no way to be sure that the CDA hasn't installed hooks into the system (heatbeat tasks, interrupt handlers, etc). If you write an application that calls RemoveCDA and then disposes of the CDA's memory, the responsibility is yours to determine that it's safe. CDAs can use the Message Center to communicate information to other pieces of code that know how to look for specific messages you create. See MessageCenter in the Tool Locator chapter of Apple IIgs Toolbox Reference volume 1, and MessageByName in volume 3. When you try to debug a CDA with GSBug, you crash in short order because GSBug is not designed to trace code while the stack is in page 1. To trace your CDA code, locate its entry point, break into GSBug from a convenient place in an application, set the Direct page register to 0, and start Stepping from your CDA's entry point. You'll probably have to reboot rather than resume the application, but at least you have a way to step through the CDA code. New Desk Accessories (NDAs) References: Desk Manager chapter of Apple IIgs Toolbox Reference volume 1, Apple IIgs Technical Notes #12, 53, 71, 76, 83, and probably others. (Don't wait! Memorize all the Technical Notes today.) Notes on GS Technical Note #71: ResourceShutDown does not have any parameters, in spite of the note's instruction to "Call ResourceShutDown with your NDA's user ID." Oops. Also, it is advisable to call GetSysPrefs and SetSysPrefs (GS/OS calls) around OpenResourceFile to ensure that the user gets prompted to insert the correct disk if it happens to be offline (be sure to put the preferences back how they were!). It's also a good idea to call GetLevel and SetLevel around the OpenResourceFile to minimize the chance of an application accidentally closing your NDA's resource fork (get the level, set it to 0, open your file, and put the level back how it was). Using tool sets that are not already started up is a complicated issue. See Apple IIgs Technical Note #53 for lots of information. This note will be revised further in the future. See Apple IIgs Technical Note #84 for information on using the System Software 5.0 TaskMasterDA call to greatly simplify event handling for your NDA. NDAs have four entry points: Init, Open, Close, and Action. The Open routine creates a window, calls SetSysWindow on it, and returns the pointer. Starting with System Software 4.0, the Open routine is allowed to return 0 if it doesn't want to open a window or for some reason fails to open its window (out of memory, for example). The Init entry point is notified at DeskShutDown and DeskStartUp so that the DA can take any special actions needed at boot time or when switching between applications. (DeskShutDown happens once at boot time before any application is executed.) Note that you may get more than one consecutive DeskShutDown call while switching between applications. (For most NDAs this is no problem.) Use your NDA's existing memory ID, which you can get by calling MMStartUp. There is rarely a need to get another one with GetNewID. NDAs should not call TLStartUp or TLShutDown! (Those are for applications.) Where should an NDA put configuration files on disk? Hard-coding the pathname */System/Desk.Accs/my.settings is not a very great idea. It's better to call LGetPathname or LGetPathname2 (in the System Loader tool set, documented with GS/OS) to find your NDA's actual pathname. Then you can put your files in the same directory as your NDA. (Utilities exist that load NDAs from directories other than */System/Desk.Accs!) NDAs that want to be friendly to users who boot their Apple IIgs's over a network from an AppleShare file server have a little more work cut out for them. You can do a GetFileInfo GS/OS call on the pathname "*/" and examine the fileSysID result field to determine whether the user booted from an AppleShare volume. If so, you can make the AppleShare FST-specific call GetUserPath to find the pathname of the user's private folder on the server. NDAs can communicate with other pieces of code by using MessageByName. See Apple IIgs Toolbox Reference volume 3. Apple does not currently guarantee that future system software will allow NDAs to receive keypresses with the Apple key down. In current system software, NDAs do receive Apple keys, but there are advantages to having those keys always handled by the current application. If you have an opinion on this, let's hear it! It's not too late. [Opinion at the session was pretty clearly "don't change it--my NDAs want Apple keys. Discussion of alternatives is still needed. --DAL] RemoveNDA was added to the Desk Manager new for System Software 5.0. (Be careful removing DAs, though. If you actually UserShutDown them and they're still trapping vectors or toolbox calls or have heartbeat or run-queue tasks installed, etc, you'll crash the machine.) Writing a NDA to call install another NDA is tricky--you need to use SchAddTask to make it happen after your DA code exits back to the Desk Manager. See Apple IIgs Technical Note #71. Debugging NDAs with GSBug used to be nearly impossible if the NDA accepted keyDown events. With GSBug 1.5, you can put the CapsLock key down to cause GSBug to receive keystrokes instead of letting the NDA munch them. Here are a few of things applications should do for maximum compatibility with NDAs: --Call DeskStartUp after all the other toolbox startups, and call DeskShutDown first, before shutting down other tools. --Enable the Undo/Cut/Copy/Paste/Clear items in the Edit menu when an NDA is the front window (see GetSysWFlag or GetWKind in the Window Manager chapter of volume 2). --Support the Scrap Manager, and have the application's main loop watch for GetScrapCount to change. This way an NDA can change the contents of the clipboard and the application can invalidate its Show Clipboard window. (A CDA could change the scrap, too, so don't avoid keeping a "private scrap" and converting it to the public formats only when an NDA comes to the front or goes away!) Control Panel Devices (CDevs) The only place CDevs are documented is File Type note $C7. DTS Sample code available in several languages. Be sure to read Apple IIgs Toolbox Reference volume 3 for information about extended controls and resources. These make CDevs pretty easy to write. Apple IIgs Technical Note #81 contains more information on extended controls. It's difficult to have any of your CDev's code stay resident in memory while your CDev is not the one currently selected in the control panel. If you need to keep code around all the time, consider writing an Initialization file or an NDA instead, or having an init file communicate with your CDev through the Message Center. Initialization Files (Inits) Inits are executed by the system at boot time, after the OS is present but before the first application is launched. There are two kinds of Initializaton files, Permanent Init Files (PIFs, with file type $B6) and Temporary Init Files (TIFs, with file type $B7). Watch for File Type Notes to be released for these soon. [This probably means the mid-September batch.] Inits are documented poorly (apparently not at all, except for page 266 of Programmer's Introduction to the Apple IIgs. Permanent inits generally stay in memory until the system is shut down, but temporary inits execute and are then immediately removed from memory. The environment when an Init gets control is this: --Processor is in 16-bit native mode. --There is a 4K stack/direct-page space. D points to the bottom, and S points near the top. --The A register contains the init's memory ID --The Bank register is not defined. If you use absolute addressing, start with PHB PHK PLB and end with PLB. --You can't use the desktop tools. User interaction is not generally a good idea at boot time (because the desktop environment may or may not be there--we don't guarantee it one way or the other; and because users with long boot times, for whatever reason, want to be able to turn on the machine and go get a cool drink while it boots, without finding the thing only part-way booted when they come back, waiting for them to hit OK). If you need user interaction, consider using a CDev or an NDA and, if necessary, having it communicate with your init through the Message Center (MessageByName, volume 3). The init must execute an RTL when it's finished. A permanent init can store a $0001 in the word just above the RTL address (LDA #1, STA 4,S) to ask the system to unload it (as if it were a temporary init instead). A PIF that is useless without certain nonstandard hardware might want to do this, for example. PIFs may want to install HeartBeat tasks (using SetHeartBeat and IntSource in the Miscellaneous tools), or they may wanto to install RunQueue tasks (AddToRunQ in the Desk Manager).