Control Strip
Volume Number: 10
Issue Number: 12
Column Tag: New Apple Technology
Related Info: Help Manager
Writing Control Strip Modules
Extend the strip with your own items
By Mike Blackwell, mkb@cs.cmu.edu
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
About the Author
Mike Blackwell - Mike organizes international symposiums, travels around, and
figures out how to program things before Apple gets around to documenting them. He’s
also been known to do the occasional piece of contract work. Given that chose to go
traveling without sending us a bio to fill this space, he’s in no position to dispute our
claim that he spends most of his time working for a huge unnamed charitable
foundation, handing out large grants to starving software entrepreneurs.
When Apple introduced the PowerBook 500 and Duo 280 series computers, they
also released the latest version of System 7. Along with the usual new goodies that
accompany a system release this included the Control Strip extension. The Control
Strip is a long thin window which floats above all other applications on the desktop,
always easily available to the user. When retracted, the Control Strip tucks out of the
way in a corner of the desktop. When extended, it presents a row of separate chiclet
tiles, each displaying some aspect of system status, such as remaining battery life or
the state of file sharing. Some of the tiles allow the user to click on them to quickly
change the state of the system through a popup menu or dialog box.
Each of the status tiles in the Control Strip is controlled by a chunk of code called
a Control Strip Module (specifically, a resource of type ‘sdev’), which is generally
bundled into a corresponding module file. The user can control which modules are
available simply by adding or removing the module files from the Control Strip
Modules folder in the system folder. At boot time, the Control Strip extension loads and
initializes all of the modules found in this folder.
Control strip modules are fairly easy to write. Since the Control Strip extension
itself deals with all of the low level interface to the system, the module programmer is
freed from the usual minutiae of Macintosh programming such as toolbox
initialization, event loops, grafports, and the likes. However, there are still some
tricky areas: resource loading, global memory, loading and saving user preferences,
and balloon help, for example. In this article we will use MPW to develop a simple
module which displays a rudimentary clock, to learn just what makes a Control Strip
module tick.
Before we get started, there are a few caveats to keep in mind:
• Space in the Control Strip is a very limited resource, and should not be
needlessly cluttered with modules that might be better implemented as
applications. The Control Strip should be reserved for information that the user
may need convenient access to at any time.
• Since Control Strip modules are always “running,” they should minimize their
impact on system performance by consuming as little memory and as few
processor cycles as possible.
• Finally, the current release of the Control Strip extension only runs on the
PowerBook machines.
If you would like to play with Control Strip on a desktop Mac, you have two
options. If you have access to the Control Strip extension (on the System 7.5
installation disk, for example), Rob Mah has produced a nice little patcher program
which will modify Control Strip to run on all platforms (by deleting ‘sdev’ resource
number -4064 from the extension, which Control Strip uses as a “PowerBook-only”
flag). Also, Sigurdur Asgeirsson has written a shareware Control Strip work-alike
called Desktop Strip, which will run on all Macs and use most Control Strip modules
(some nice ones are provided). These programs are available at finer archive sites
everywhere, try:
/* 1 */
ftp://mac.archive.umich.edu/
mac/ system.extensions/cdev/controlstrippatcher2.0.sit.hqx
ftp://mac.archive.umich.edu/
mac/ system.extensions/cdev/ desktopstrip1.0.sit.hqx
They are also available on this issue’s code disk and online sites (see p. 2).
Structure of a Module
All code for the module is contained in a code resource of type ‘sdev’ in the module
file. Typically a module file has only one ‘sdev’ resource corresponding to one module.
If a module file has more than one ‘sdev’ resource, then each is loaded as a separate
module, though this gives the user less flexibility in mixing and matching modules. One
possible use for multiple modules in one file would be to have different versions of a
similar module, only one of which will load depending on the user’s hardware
configuration. As we will see shortly, during the initialization process a module can
decide for itself if it should stay loaded.
Besides the ‘sdev’ resource, a module file will typically contain a couple of string
and picture resources, plus the usual version, icon, and bundle resources to help the
user distinguish the file from a generic document. Unless otherwise required, the
programmer should number all resources in the range 256 - 32767 to avoid conflicts
with other system resources.
In our example module, SimpleCSClock.r is a Rez file which contains all of the
resources for the module except for the ‘sdev’ resource, which will be produced by the
C compiler. It starts with a version and signature resource, with information to be
displayed by the Finder’s Get Info command. The signature (‘cs!C’ in this case) is also
used as the file’s creator ID and as a Preferences key, and should be unique. If you are
planning to distribute a module widely, its signature should be registered with Apple to
prevent conflicts with other applications.
Next is a ‘PICT’ resource which is a picture of a small right-pointing arrow.
This will be drawn in the right-hand side of our tile, to indicate to the user that the
module provides a popup menu. A ‘MENU’ resource defines that menu, followed by a
couple of strings and string lists which will be described later. Finally, a ‘BNDL’,
‘FREF’, and icon family define the icons for the file that the user will see from the
Finder.
The header file SimpleCSClock.h defines the various resource and item numbers
in a common location for both the Rez and C programs.
Module Initialization and Global Memory
The Control Strip communicates to the module through a single entry point at the
beginning of the module’s ‘sdev’ resource. The prototype for this interface is
/* 2 */
pascal long ControlStripModule(long message, long params,
Rect *statusRect, GrafPtr statusPort);
message signifies which action the Control Strip wishes the module to perform,
from which the module can then dispatch the appropriate routines. These actions
include module initialization, display management, queries for module features or
display size, periodic “tickles,” and a couple of others.
parms is generally used to store a handle to a parameter or global variables
needed by the control strip. The statusRect and statusPort variables are used when
drawing the control strip tile.
In addition to this message interface, the Control Strip extension also provides
the module with a handful of utility toolbox routines which simplify many actions
common to most modules (the utility routine names all begin with the letters “SB” -
until just before its release, Control Strip was called Status Bar).
Control Strip sends sdevInitModule as the very first message a module receives.
This happens as the control strip extension loads modules from the modules folder at
boot time. sdevInitModule asks the module to determine if it can run on this particular
platform (generally using various calls to Gestalt to query for necessary features),
and then initialize its internal state. If the initialization returns a value less than zero
to the Control Strip (because it cannot run on this platform or something failed during
initialization), then the module is unloaded and not installed in the Control Strip.
In our example, the Initialize() routine is dispatched on receipt of the initialize
message (note that params will always be 0 for this message). After checking the
machine features, a chunk of memory and a handle to it is allocated to hold all of the
global variables that the module will need to maintain its state. These variables are
defined in the Globals structure at the beginning of the program. If the initialization
proceeds successfully, then the handle to the globals is returned to the Control Strip.
On all subsequent message calls to the module the Control Strip will pass this handle in
params, so the globals will always be accessible.
The module file will not be open after the first initialize message has been
processed, so any resources that the module needs must be loaded from the file during
initialization. This is accomplished with toolbox variants of GetResource. Once each
resource is loaded in to memory, its handle is stashed in the globals structure, and the
resource is detached so it will always be available. One reason why the module file is
not left open after initialization is to conserve power on PowerBooks. If the module
were to make GetResource calls on a regular basis it would keep the hard disk turned
on a lot.
Once the resources are loaded, the Initialize() routine loads and sets any
previously saved user’s preferences. This is accomplished using two of the Control
Strip’s utility routines. First, SBGetDetachedIndString() is called to get the string
item corresponding to the name of our preferences from the recently detached ‘STR#’
resource helpStrings. This name is then passed to SBLoadPreferences() to read the
module’s preferences in to the SavedSettings structure. The first field of the
preferences structure is checked against 4-byte module signature to make sure these
preferences really belong to this module. Finally, the user’s preferences are copied
into the globals structure (in this case, there is only one preference: whether to
display the time in 12- or 24-hour format).
Maintaining The Display
Once the module has been initialized and loaded, it can go about its business of
maintaining its status display in its tile. This is primarily accomplished through two
messages from the Control Strip: sdevPeriodicTickle and sdevDrawStatus. For these
two messages (and all others as well), the Control Strip passes the handle to our
globals structure in params, a pointer to the drawing rectangle in statusRect, and a
pointer to the GrafPort in statusPort (which typically you won’t need to access
directly).
Note that each time a message is processed after initialization, the first thing the
message handler does is save the state of the globals structure handle, and then lock the
structure down so it won’t move while the message is being handled. At the completion
of the message, the handle’s state is restored. We don’t simply use
HLock()/HUnlock(), because modules can be reentrant - it is possible for the Control
Strip to call the module’s message handler before a previous message has completed. If
this happens and we inadvertently unlock the handle underneath another message’s
feet, all hell could break loose. For this same reason, it’s best not to store variables
like parameter blocks in the globals structure, since two messages could be trying to
modify them at the same time. Allocate these variables on the stack as needed (by
declaring them within the scope of the handler routine), or if they must be global, add
an in-use flag.
The module performs most of its work in the sdevPeriodicTickle message. This
message is called periodically, but there is no guarantee about how often or when it
will be called. The sdevPeriodicTickle message gives the module a chance to check
whether its status display is out of date, and if so to update it. Since our clock module
updates rather slowly (once every minute), there’s no need to check if the time has
changed every single time the tickle message is called - once every couple of seconds is
adequate and saves wasting cycles needlessly. To accomplish this, we use the nextTick
global variable to signal when we need to check if the display is out of date. If the
system clock (returned by TickCount()) is less than nextTick, then we return from
the message right away without any further processing.
Otherwise, we call UpdateTime(), which checks to see if the time has changed
since it was last displayed, and stores the time to be displayed in the globals structure.
If the time has changed, the tile rectangle is erased and DrawDisplay() is called to
update the display. DrawDisplay(), in turn, converts the current time in to a string,
selects the proper font, sets the drawing point within the statusRect, and calls
DrawString() to draw the time of day in the module’s tile. DrawDisplay() then draws
the small right-pointing arrow picture just to the right of the time string, which
indicates that a popup menu is available. Finally, nextTick is set for two seconds from
the current time.
After drawing all of the information in the statusRect, the tickle message handler
compares the width of what it has just drawn (returned by DrawDisplay()) to the
width of what it had drawn the previous time through (and saved in the width field of
the globals structure). If the width has changed then the width field is updated, and
sdevResizeDisplay bit is set to be returned to the message call. This bit signals to the
Control Strip to shrink or grow the module’s tile appropriately so all of the data is
visible in the tile.
When the Control Strip is informed that the module’s tile width has changed, it
will next send the module an sdevGetDisplayWidth message request. Since the module is
maintaining that width anyway, there’s no need to recompute it, and the module simply
returns the width field from the globals structure.