Location Manager Pref Treatment
Volume Number: 13 (1997)
Issue Number: 9
Column Tag: develop
Preferential Treatment Under Apple Location Manager
by Erik Sea
Apple Location Manager (ALM) is a suite of system software enhancements that shipped in January, 1997, but unless you're a PowerBook user, you may not have seen ALM in action. Later this year, however, you'll see a completely new version that works on any Mac OS 8 computer, be it large or small, battery-powered or tethered to the electrical grid. With increased availability, it's become even more practical to add ALM-savviness to those services, tools, and applications that need it, and over the next few pages we'll discuss some ways to do just that.
At the heart of ALM is the idea that many Mac OS users repeatedly fiddle with various control panels and change preferences. They make these changes for a number of reasons: a mobile user who travels from place to place needs to adjust to those different environments, while a home user might have different settings for telecommuting versus playing games. With ALM, the chore of going through and repetitively changing a large number of settings is reduced to a single Control Strip menu choice, once the collection of settings has been given a name. With this named collection of settings and actions, known as a "location" in ALM, lengthy lists of reconfiguration steps (some users even have these taped to their monitors or stuck to their keyboards) should become a thing of the past -- provided the software they need to configure is ALM-savvy.
Should you make your software ALM-savvy? Well, if you have a product that has preferences that the user can change, directly or indirectly, the answer is yes! You might think that your software's preferences are simple -- maybe not much more than a simple "on-off" or two that the user can change with a click, but, in the larger context, the user might want to turn your preferences on or off at the same time as changing something else -- batch reconfiguration is a surprisingly common activity. Consider turning file sharing on or off: file sharing is only useful when there is a network around, and users might forget to turn it off at 37,000 feet, but if they've defined a location called "Air Travel" that turns it off for them (and perhaps turns off AppleTalk and dims the screen for better power conservation), they might just get a few precious minutes of additional battery life over just dimming the screen -- but will they remember to do it? With ALM, they only need to remember the details when they create their locations -- from then on, they need only use those locations.
You might also think that your users generally don't change their preferences, and it's true that many don't. However, you may want to consider whether they would if it were easier and could be done in concert with other changes to their environment. Some applications nest their preferences so deep in subpanels that the utility of making the change is offset by the cost of making it -- but what if it were easier? I'm sure you're starting to see what I'm getting at.
There are many ways for your software to interact with ALM, including some I probably haven't thought of. I'll present some in my allotted space, but please do get in, explore, and create things that just make sense -- the ideas are simple, but the uses are numerous!
Ways and Means
A developer once commented to me that ALM is one of the simplest, yet most complex pieces of system software to come along in a while. While you may shy from such paradoxical remarks, they correctly reflect the continuum of development options at your disposal -- ALM-savviness can take many forms, ranging from tight integration with your software to standalone ALM modules written by third parties for your products.
One of the first ALM modules written outside Apple was an ALM module for Apple's OT/PPP developed, as shareware, by Prime Computing Systems and available on the web at http://www.primecs.com.
ALM modules are generally self-contained preference-swappers that cooperate to varying degrees with the software that owns the preference in question. The tighter the integration, the better the user experience, but you can certainly start out with looser coupling and improve it as revisions to your product permit. ALM modules implement some or all of the ALM module API, and are placed in the Location Manager Modules folder, inside the Extensions folder. I'll discuss module development shortly.
There are other types of ALM modules, called action modules, that, rather than swapping preferences for a piece of software, perform an activity that may or may not be associated with a specific piece of code, but just automate the process of doing some sequence of steps during a location switch: opening a file, for example. For more on action modules, and the slight differences between their API and the API of standard modules (for example, one of the "optional" calls for a standard module becomes "required" for an action module), see the ALM SDK.
To find the latest ALM SDK, go to our web site at http://devworld.apple.com/dev/alm/, or see the Mac OS SDK on CD. To take advantage of the newer features of ALM 2.0, be sure you're using the SDK's interfaces and libraries -- older versions of these components may have come with your development environment, and don't have all of the new features. See "Whatcha Got?" for information on testing for the availability of features.
The ALM engine -- that is, the part that provides the core services related to switching -- has a public API that developers can use to enumerate the user's locations, create new locations, or switch locations. We'll cover the ALM API later.
Finally, although ALM modules can provide a great deal of flexibility to a developer who wishes to become ALM-savvy, but there are cases where a piece of software might benefit from a deeper knowledge of what the engine is doing. There is the API, as mentioned earlier, but you should know that it can be used in clever ways that might be appropriate for some software that would benefit from even tighter integration with ALM. A hypothetical example of this kind of location-awareness, going beyond mere ALM-savviness, is discussed in the latter part of this article.
WHATCHA GOT?
As with any system software from Apple, there are means for testing what features are present in ALM, since the features have changed from version 1.0 to 2.0.
- gestaltALMVers
- This selector returns the version of the ALM engine that is installed on the machine.
- gestaltALMAttr
- This selector returns a bitfield representing engine functionality with the following bit meanings:
- gestaltALMPresent is on if ALM is installed; it's possible, on some hardware, for the selector to be defined but for this bit to be off, so check!
- gestaltALMHasSFLocations is on if the Get, Put, and Merge location calls are implemented.
- gestaltALMHasCFMSupport is on if the engine recognizes CFM-based modules, otherwise only Component Manager modules are directly supported.
- gestaltALMHasRescanNotifiers is on if notifications for changes in the names or number of locations will be sent via callbacks and Apple events; otherwise, only notifiers for switches are generated.
With so many options to choose from, it might take a while to figure out how your software and ALM might collaborate. So, as you dive into the rest of this article, keep in mind that there is often more than one path to ALM-nirvana, er, savviness, but the sooner you start, the sooner you'll arrive. Setting aside philosophical opinion, let's look at the path most traveled first.
Magnificent Modules
For most situations, the ALM module provides the easiest and best-encapsulated way to get your software plugged into the ALM engine. The calls that an ALM module supports are very general: a module (or any software it interoperates with) doesn't need to know anything about locations or how the ALM control panel works, just how to interact with the software it represents.
Just What Is A Module Anyway?
It is difficult for me to describe what an ALM module is or does without showing how the user interacts with them. To define a location, the user runs the ALM control panel, and picks "New Location" from the File menu. All location editing is done from the main window, seen in Figure 1. In the top part of the window, we see the current location (that is, the location a user switched to in the past or the location the user is "in" right now) specified by a pop-up. The user can switch from this pop-up or switch from a similar menu in the control strip.
Figure 1. Apple Location Manager main window, in expanded mode.
The disclosure triangle toggles display of the bottom half, where a second pop-up shows the name of the location to be edited. As you can see, it's possible to edit locations that are not the current location, which creates some special issues you might have to deal with. More on that later.
At the left side of the window, we see a list of modules that ALM knows about -- they are located in the Location Manager Modules folder. Each of these modules has a value associated with the edit location, and a description of that value is displayed at the right when the associated module is selected at the left.
Modules are ordinarily listed in alphabetical order, and the list order is the order that the engine applies changes when switching. The user can, using menu options, move modules around in the list to achieve different results (it's often handy, for example, to have modules that might require a restart, such as Extension Set, first, so that other modules, such as Auto-Open Items, could decide to defer execution until after the restart).
Modules could also be in the list several times if the user has chosen to duplicate them (another menu option). For example, a user might have several instances of Auto-Open Items sprinkled throughout a location definition, doing everything from mounting servers to executing AppleScript scripts.
The value for a module within a location is saved whether the module is on or off -- this allows the user to temporarily alter the location by excluding a module's setting without losing the settings established for it.
Only a few calls are necessary to support everything seen in the control panel.
If you're concerned about supporting ALM 1.0, rest assured that, despite the vast difference in appearance of the control panels, ALM 2.0 calls modules with the same expectations as its predecessor, but if you want to work under both versions, you'll have to take care not to use any 2.0 features. The bottom half of the new control panel corresponds to the separate Edit Location window in the old control panel, and the new "on-off" checkbox corresponds to "adding" a module to a location in 1.0. There's some information displayed in the old interface that isn't anywhere in the new one for simplicity and performance reasons.
What A Module Needs To Know To Do
Essentially, an ALM module only needs to know how to interpret the settings of the software it supports, how to change those settings, and how to get those changes recognized. We'll discuss nuances in a moment, but here are the calls a module absolutely must handle (for both ALM 1.0 or 2.0).
pascal OSErr GetCurrent (Handle setting);
When a module receives this request, it resizes the handle to whatever size is necessary to store the current setting. It is generally a good idea to "version" this information, in case you decide to change it later. In addition to the version you should also store in the handle enough information to restore the setting when ALM calls your module with the handle at a later time. This handle is stored on disk in a location file, so be sure you've "flattened" it out (that is, there are no pointers to things in memory that might not be in the same place when your module is next called).
pascal OSErr SetCurrent (Handle setting, ALMRebootFlags* flags);
Modules receive this call during a switch. The setting will be one which your module has previously returned from a GetCurrent call, though, obviously, it may have been stored some time ago. Be aware that, because ALM allows a user to switch location very early in the boot process, you need to check for things that may not be around yet (such as the Process Manager). If the environment is too hostile for your tastes, your module should return kALMDeferSwitchErr in response to this call, and ALM will try again later in the boot process.
The flags parameter is used to communicate to the engine what the impact of the setting change was, so that the user can be offered the opportunity to restart, if necessary. Possible responses include kALMNoChange if the settings did not actually need to be changed, kALMAvailableNow if they can be used immediately, or kALMExtensions if a restart is required (in which case the user will be given the option to reboot their machine) -- see the SDK for additional values and their meanings. Note that the input value of the flags parameter is the current escalation of the switch process, as set by previous modules. If the reboot level is already too high, you can set the flags to whatever value is appropriate for what you actually did, and return kALMRebootFlagsLevelErr as the function result.
The input value of flags in ALM 1.0 is always kALMNoChange, and there is no way to determine what the reboot escalation level is when you are called. You shouldn't return kALMRebootFlagsLevelErr if the input escalation level is kALMNoChange unless you know you're under 2.0.
You should avoid forcing the user to reboot when they switch your settings -- your customers will thank you for saving them time and trouble. Possible strategies include defining an Apple event or other signal that your software will recognize as "read your preferences now", then send that signal from your module's SetCurrent function.
pascal OSErr CompareSetting (Handle setting1, Handle setting2,
Boolean* equal);
This call is made to determine whether two settings represent the same values from the point of view of the module. This is used at different times and in different ways, and has a special interaction with the optional EditSetting call, discussed later.
You'll find that this call is made less often under ALM 2.0 than it was under the older version, because of UI changes, but ALM 2.0 still requires it.
pascal OSErr DescribeSetting (Handle setting, CharsHandle text);
Generate a textual description for one of your settings, resizing the CharsHandle as appropriate to fit the text. Don't go overboard in your description -- the window containing the description is resizeable, but try to fit your description in the minimum window size for best results. The script system used will be that returned by the GetScriptInfo call, described shortly.
pascal OSErr DescribeError (OSErr lastErr, Str255 errStr);
You are encouraged to return your own (positive) error response values from a module API call, and you'll get first crack at telling the user what they mean. If you don't wish to describe an error, return paramErr in response to this function, and the control panel will present it to the user as a number (boo, hiss).
pascal OSErr GetScriptInfo (ALMScriptManagerInfoPtr info);
This call, though technically optional, is pretty easy to implement, and is recommended if you expect your module to be used worldwide. The format of info is as follows:
typedef struct {
SInt16 version;
SInt16 scriptCode;
SInt16 regionCode;
SInt16 langCode;
SInt16 fontNum;
SInt16 fontSize;
} ALMScriptManagerInfo;
You should set the version to kALMScriptInfoVersion, and fill in the font and script manager information in accordance with how you wish your text to be displayed. If you don't do this, you might not play well in other languages, because the system will fill out the script information on your behalf, and your module might end up getting displayed in the wrong script -- certain characters do not translate well, to say the least. It is actually possible for modules to be of different script systems yet be displayed correctly. If you plan to localize your text, you'll probably want to put script information in a resource that gets localized at the same time. See the sample code in the SDK for a good strategy to do exactly that.
pascal OSErr GetInfo (CharsHandle* text, STHandle* style,
ModalFilterUPP filter);
The GetInfo call is made to tell the user what your module is all about, such as what other software it interacts with. There are essentially two flavors to this call: in the first, you simply return styled text using the first two parameters, and it gets displayed, as shown in Figure 2. The text can be any length, but don't write a book -- say what your module does, what software it works with, and where to go for more information.
In the second flavor of GetInfo, the module itself takes care of displaying whatever dialog it chooses, or perhaps opens an AppleGuide window, and returns NULL in the first two parameters to signal that it did the job on its own.
Figure 2. Control Panel Displays Results of GetInfo Call.
MODULES OR APPLESCRIPT?
At first glance, it may seem that ALM modules and AppleScript are two technologies vying for the same role, for if every preference-generating piece of software were scriptable, an intelligent user could just have a collection of scripts to effect a location switch.
If you look deeper, though, you'll see that these technologies are complementary: a totally scriptable control panel, for example, even if it were recordable, would benefit from the centralized, simplified process of location definition and aggregation with other settings. At the same time, it is much easier to support ALM in a control panel that has already been made scriptable because the requisite code factoring makes ALM support a breeze.
Still, you do not need to be scriptable to support ALM, and, though I would prefer that you did make your software scriptable, you don't need to do it for ALM.
The filter parameter to the GetInfo call is a legacy parameter, used only in ALM 1.0. ALM 2.0 does not require you to call filter explicitly, but you may do so.
Those are the basics; the SDK provides a good sample implementation you can start from, in the form of the "Generic" module, which is particularly handy for prototyping.
In fact the Generic module could theoretically be used with only a few changes in resources, and, for some limited-use applications, or for prototyping, that might be enough, but it's probably not up to the quality required for commercial or even shareware release.
Though these calls are enough for ALM to work correctly and predictably, there has yet to be an API devised whereby you can become "savvy" by supporting just the lowest level, so let's go up a step.
DUALING SETTINGS
Over the last little while, people have begun to recognize the value of storing named configurations, or sets of preferences, for easier retrieval by users. This is a good thing, and is a move in the direction of ALM. An ALM module that deals with a piece of software that already supports named configurations probably just needs to track the name, and that makes life much easier, because you basically are dealing with just a reference to other data, rather than the other data directly.
There are, however, complications, such as the need to define dual formats for your settings: normal (in which case a reference to other data on the current machine is adequate) and import-export (in which case you need to recreate the entire setting for use on another machine).
Another question is what happens if the user changes the named configuration -- will your module know how to track the change and update its reference?
And how will your module help Bob import "My Reactor Settings" from Anne's computer, without wiping out his existing (and potentially different) "My Reactor Settings"? Maybe he really wants to replace them, but maybe he wants to rename the import as "Anne's Reactor Settings".
You'll have to solve the reference-tracking problem for yourself, but ALM does provide a call to handle import name collisions, called ALMConfirmName, discussed in the API section.
Of course, if your setting is very simple, such as on or off, both formats of
your setting can be the same, and your implementation of ImportExport becomes very simple -- just return noErr.
What A Module Should Know To Do
With some additional work, you can provide the user with additional ALM-supported functionality and achieve ALM-savviness. The capabilities you'll need to tackle are editing of inactive settings, and import-export. If you don't implement these functions to at least some extent, not only will you fail to enter the blissful state of ALM-savviness, but your customers may become confused and you'll have a recurring dream in which you read Inside Macintosh: AOCE Application Interfaces word for word, from cover to cover, until your conscience makes you support these added functions.
This setting must change - As mentioned earlier, it's possible for a user to edit the settings from one location while being in another. Through use of the "Apply" button (refer to Figure 1), the user can change a setting in a location by capturing the current value of the setting on the computer. This is done through the GetSetting call described previously. In user studies, we found that it's highly desirable for the user to be able to change settings in an inactive location without changing the live system settings (we do provide a way to reassert the current location's values). Unto this desire, the EditSetting call was born.
pascal OSErr EditSetting (Handle setting);
The EditSetting call is optional, but very strongly recommended. The input setting parameter is one that will have come from an earlier call to GetCurrent. Do whatever you need to do to edit the setting, and return either the same one (if the user cancels the edit) or a different one (if the user accepts the edit). When I say different, I mean different enough that your CompareSetting call will say, "Yes, they're different" -- if there was no real change, your module might just flip a bit in the setting handle that it would ignore everywhere but on a CompareSetting call. The reason for requiring these settings to seem different even if they aren't is a subtle bit of UI trickery in the ALM control panel: the control panel will automatically turn a setting on if, as a result of an EditSetting call, it can be ascertained that the settings were accepted by the user. The control panel makes the determination of acceptance based on the input and output setting handles being different.
Implementing the EditSetting call can be done in a number of ways. Ideally, an EditSetting call would bring up the same interface you use in your software to edit your live preferences, but without affecting those live preferences -- a sort of "preference UI factoring", if you will. Alternatively (but less desirably), you can take some sort of middle road, ranging from explaining to the user where they should go to edit their live system settings (and reminding them to put those settings back the way they were), to providing a little "platform" from which to launch your software, perhaps under AppleScript control.
The EditSetting call was supported under ALM 1.0, and, in fact, it's required for action modules. There is, alas, no support for EditSetting of standard modules in the old control panel, so be aware of that limitation if you plan to support both versions of ALM.
If you don't implement EditSetting, the ALM control panel has no way of knowing where your setting comes from, so it displays the message shown in Figure 3 when the user tries to edit your setting.
Figure 3. Developer Raspberry: Module does not support EditSetting.
I can't encourage you strongly enough to implement EditSetting to some extent in your initial release, and then refine it over time (if you don't go all the way up front). I've seen hours of videotaped user experiences, and the EditSetting capability more closely reflects how users expect the machine to behave, so don't let them see this brick wall of an alert when they use your module.
Duties Paid: Importing and Exporting
It would be really nifty for a user if, once they've honed a location to near perfection, they could share it with others. It would also be nice if, walking into a new environment, a user could receive a group of settings that other people in that environment use. That, in a nutshell, is what the ImportExport capability is all about.
pascal OSErr ImportExport (Boolean import, Handle setting,
SInt16 resRefNum);
The settings dealt with by most of the API can be small -- they may just be references to other data structures (I would encourage this design decision, though it introduces some new issues as discussed in "Dualing Settings"), and that will work fine on one user's computer. If you were to export just the name, however, the importing machine might not be able to interpret it correctly.
So, when you export, you might want to augment the setting handle with additional information that would allow you to replicate the setting on another machine (short of actually installing software on that machine!), and, on import, you can decode that information and shrink the handle back down to size. If that technique isn't enough, or you'd rather keep your setting the same, you can add resources to the exported location file, and read them in on import (after you read your resources in, you should delete them, since you're actually working on a copy of the import file, in the Locations folder, rather than the actual import file). You should use a unique resource type, such as your creator, for the resources you use.
If you don't implement ImportExport, be sure to document this in your GetInfo window information.
Now that you know how modules work and how ALM calls them, let's go to the other side of the fence and see how to call ALM.
Start Your Engines!
The ALM API contains a series of calls for the developer to find out about a user's locations, a call to initiate switches, calls to create or let the user merge a setting into a location, plus events and notifications to determine when a switch has occurred or a location has been created. As mentioned previously, there is also a call to handle import collisions in a uniform way.
The API
The ALM API is accessible in C, Pascal, or Assembly, from PowerPC or 68K, in classic or CFM-68K runtime models.
Since CFM-68K calling isn't available under ALM 1.0 I recommend weak-linking with the CFM-68K stub library and then testing against API symbols at launch.
What place is this?
If you want, you can take a gander at what the user has set up. Perhaps you want to build your own pop-up menu to provide the user the ability to switch locations from within your application, rather than the control strip or control panel. Here are the calls, and a very simple program to use them can be found in Listing 1.
Listing 1 Printing the User's Defined Locations
ALMToken curLocToken, locToken;
ALMLocationName curLocName, locName;
SInt16 numLocs, locIndex;
printf ("User Locations on this Machine:\n");
err = ALMCountLocations (&numLocs);
printf ("There are %d locations in total\n", numLocs);
err = ALMGetCurrentLocation (NULL, &curLocToken, curLocName);
printf ("The active location is %s\n",
p2cstr (curLocName));
for (locIndex = 0; locIndex < numLocs; locIndex += 1) {
err = ALMGetIndLocation (locIndex, &locToken, locName);
if (locToken != curLocToken) // special treatment!
printf ("Location %d is %s\n", locIndex,
p2cstr (locName));
else
printf ("Location %d is the current location\n", locIndex);
} // for
pascal OSErr ALMGetCurrentLocation (SInt16* index,
ALMToken* token,
ALMLocationName name);
pascal OSErr ALMCountLocations (SInt16* locationCount);
pascal OSErr ALMGetIndLocation (SInt16 index,
ALMToken* token,
ALMLocationName name);
The indices in these calls are zero-based, so the maximum will be one less than the locationCount. The special index value kALMNoLocationIndex denotes the situation where the user has chosen the special location "None (Off)". ALMTokens represent references to locations for use in other calls (and the special value kALMNoLocationToken corresponds to kALMNoLocationIndex). If you only want certain parameters to be returned, it's legal to pass NULL for any pointer.
Snapshots -- as Pretty as a Picture
In ALM 2.0, there are functions that you can use to interact with the user much like the control panel does. You should gestalt these functions (see "Whatcha Got?") before using them. The user experience is very much like the Standard File package, only simpler, but the calls themselves take care of a lot of additional work for you.
Sometimes, you'll want the user to simply choose a location from a list without need for iterating across all installed locations and building your own UI. To ask the user for a location, use the ALMGetLocation call, which presents a dialog as shown in Figure 4.
Figure 4. Asking the user for a location with ALMGetLocation.
pascal OSErr ALMGetLocation (ConstStr255Param prompt,
Str31 locationName,
ModalFilterYDUPP filter,
void* yourDataPtr);
The prompt is displayed above the list of locations, as in Standard File. The locationName is both an input and an output parameter -- if the name matches a location, that location will be selected in the list. The filter and data pointer are for your own use, again as in Standard File. If the user clicks cancel, the function result is userCanceledErr; otherwise, it should be noErr.
The next two calls, ALMPutLocation and ALMMergeLocation are intended to allow an application, such as a setup program, to capture the current system settings for a number of modules as a location, or merge them into an existing location. Detailed information on module signatures, or types, is beyond the scope of this article (see the SDK), but the use of some special values for these parameters is shown in Listing 2.
pascal OSErr ALMPutLocation (ConstStr255Param prompt,
Str31 locationName,
SInt16 numTypes,
ConstALMModuleTypeListPtr typeList
ModalFilterYDUPP filter, void* yourDataPtr);
pascal OSErr ALMMergeLocation (ConstStr255Param prompt,
Str31 locationName,
SInt16 numTypes,
ConstALMModuleTypeListPtr typeList,
ModalFilterYPUPP filter, void* yourDataPtr)
Listing 2. Put Up or Merge Up
// create a new, empty location
err = ALMPutLocation ("\pName your place",
"\pEmpty Location", kALMAddAllOff,
NULL, NULL, NULL);
// add to a location as a snapshot, using all currently-installed
// non-action modules, replacing current definitions
err = ALMMergeLocation ("\pRedefine your place",
"\pSnapshot Location",
kALMAddAllOnSimple, NULL, NULL, NULL);
Switching
There would be very little use to ALM without the switch engine. This is the call that the control strip and control panel use to effect a change in location:
pascal OSErr ALMSwitchToLocation (ALMToken newLocation,
ALMSwitchActionFlags switchFlags);
Most of the time, you can pass kALMDefaultSwitchFlags for the second parameter. For a quiet switch (that is, one in which the switching dialog does not come up), pass the mask kALMDontShowStatusWindow. If you're making this call from an unusual place such as inside a dialog filter or some other weird place, use the mask kALMSignalViaAE so that the switch will be deferred until the ALM control panel (or the Finder, if the control panel isn't open) is in a better synchronized state.
If you wish to switch locations from a background application you should use either kALMDontShowStatusWindow or kALMSignalViaAE or both. If you do not, ALM 2.0 will return an error. ALM 1.0 is less forgiving, and will crash if the host application hasn't initiallized the window manager. Moral: don't use default flags except in direct response to a user action in your foreground application!
Noticing Switches and Other Goings On
One of the more interesting things about ALM is that it actually tells all running processes when the user has changed locations. Under ALM 2.0, it'll also advise when the user has created, renamed, or deleted a location, so that applications that want to know can rebuild any list of locations they might have internally, or reorganize any data they might have been shadowing on a location-by-location basis by rescanning the user's installed locations using a technique like that of Listing 1.
You should check gestalt for the availability of the rescan notifiers if you're going to rely on them, as described in "Whatcha Got?". If they are not available, you can employ alternate strategies, such as periodically iterating through the installed locations. In any case, it's good practice to recheck the installed locations at start-up if you save your own list somewhere.
Listing 3. Observing Switches and Handling
Rescan Notifiers
pascal OSErr MySystemConfigNoticeHandler
(const AppleEvent* inEvent,
AppleEvent* reply, SInt32 refCon)
{
OSErr err = errAEDescNotFound;
ALMToken locToken;
DescType actualType;
Size actualSize;
// Handle any other cases you like...
[...]
// Did we get a switch notification?
if (err != noErr) {
err = AEGetKeyPtr (inEvent,
kAELocationChangedNoticeKey,
typeInteger, &actualType,
&locToken, sizeof (locToken),
&actualSize);
if (err == noErr) MyDoReactToSwitch (locToken);
} // if
// Did we get a rescan notification?
if (err != noErr) {
err = AEGetKeyPtr (inEvent,
kAELocationRescanNoticeKey,
typeInteger, &actualType,
&locToken, sizeof (locToken),
&actualSize);
if (err == noErr) MyDoReactToRescanNotice (locToken);
} // if
return err;
} // SystemConfigNoticeHandler
The class of the Apple event is kAECoreSuite, and the event ID is kAESystemConfigNotice, just as with the display manager's notifications that the screen size or resolution has changed. If your event handler receives such an event, and there is a parameter keyed under kAELocationChangedNoticeKey, then a switch has occurred, and that parameter's value is the ALMToken of the location to which the user switched. If there is a parameter under kAELocationRescanNoticeKey, then the user has made changes to the installed locations, and you can't rely on any of your previously stored data about locations being valid. The value of the parameter is the ALMToken of the current location. For a skeletal handler, see Listing 3.
Event Me Not
Of course, if you're a code resource, you won't be getting many Apple events, so you can provide a function such as this as an alternative:
pascal void MyALMNotificationRoutine (AppleEvent* theEvent);
Your implementation of this routine ought to be similar to the Apple event handler in Listing 3. Create a UPP by passing your routine to NewALMNotificationProc, and then register and deregister your routine using these calls:
pascal OSErr ALMRegisterNotifyProc
(ALMNotificationUPP notifyProc,
const ProcessSerialNumber* whichPSN);
pascal OSErr ALMRemoveNotifyProc
(ALMNotificationUPP notifyProc,
const ProcessSerialNumber* whichPSN);
The ProcessSerialNumber parameter is used by ALM to ensure that notifications aren't sent to code whose process no longer exists (but for which the notifier was not removed). You should pass the serial number you're associated with.
Import Collisions
Particularly if you implement named configurations, as discussed in "Dualing Settings", you could have situations, when importing, where a setting you're importing conflicts with a setting already on the machine. To address this problem, ALM provides a routine that displays two dialogs in succession. The first asks the user to choose either to rename or to replace the named configuration, and the second, shown in case of a rename, presents a box in which to rename the configuration. The call is as follows:
pascal OSErr ALMConfirmName (ConstStr255Param msg,
Str255 configName,
ALMConfirmChoice* choice,
ModalFilterUPP filter);
If the user cancels the operation, the function returns userCanceledErr; otherwise, the value of choice is kALMConfirmRename or kALMConfirmReplace, depending on the user's choice. You may provide a filter function, if you wish access to events in the dialog. The refcons of the dialog boxes disclose which is which, and the individual element numbers are provided as constants in the ALM 2.0 interfaces, should you wish to do things like constrain the allowed characters in your configuration name (rather than validating the name after the call returns). For details on the refcons and item numbers, see the SDK.
Due to a bug in ALM 1.0 the filter function was not called in both dialogs, but just the rename dialog. If your filter function sees a refcon of zero, you can assume you are in the 1.0 rename dialog box.
AppleScript Support
Alternatively, you can access much of the API by sending AppleScript commands to the control panel. A simple switch through AppleScript is shown in Listing 4. You can, of course, also list locations from AppleScript using the Standard Suite.
Listing 4. Driving a Switch from AppleScript
set someLocationName to "Home Office"
tell application "Finder"
set cpFolder to (control panels folder as string)
open file (cpFolder & "Location Manager")
tell application "Location Manager"
set current location to location someLocationName
end tell
end tell
Though this API is small, these same bricks will build a house or a castle, depending on how you choose to use them. Although the water may look deep, location-awareness could represent the ultimate offering to the user, giving them much more than ALM modules, alone, are capable of.
Where Am I?
Thus far, we've talked about ALM-savviness and driving the engine, but now let's switch gears a little and talk about the greater generalities of location-awareness.
How a piece of software becomes location-aware is as unique as the piece of software itself -- it requires planning and thought beyond how to simply automate the chore of switching preferences. Instead, a location-aware piece of software has new functionality that its location-oblivious sibling does not.
As an example, let's take the Apple Menu Items control panel, which has been an indispensable part of Mac OS from System 7.5 to the present. What form could location-awareness take in this control panel (see Figure 5)? Suppose that, in each location (whether a "location" is a physical location or merely a state in which they perform different tasks), the user accesses different documents, applications, and servers that are specific to that location, but, in moving from one location to the next, they constantly wipe out recent items from one place before they get back to it. Typically, users faced with this problem combat it by increasing the recent item allocation numbers, but while searching through a big list works, it's far from ideal.
Figure 5. Mock-Up: Apple Menu Items with Location-Awareness.
We could just write a module for this that, through clever folder manipulation, swapped the recent items around, but what about the occasion when the user actually wants to access a recent item from another location without switching to that location first? Perhaps the submenus could present recent items in different ways -- the ordinary way most of the time -- or sorted by location if the user holds down the option key when choosing the Apple menu.
I am not suggesting that the hypothetical example I present above is the best possible answer, but I am hoping that software developers will consider using location-awareness as a new way to solve problems that their users might be facing.
Give location-awareness some thought -- it might be your best bet.
There's No Location Like Home
Now that you've been through savviness and awareness, modules and APIs, I hope you can see the opportunities ALM presents for you to make life easier for your users, both mobile and landlubber.
So take a few moments to install and play with ALM, and then play with the sample code. I think you'll be surprised at how much mileage you can get out of a very small amount of labor!
Thanks to my reviewers Mark Cookson, Dave Ferguson, Scott Johnson, Susan Michalak, Kent Miller, and Eric Slosser. Special thanks to everyone on the ALM team, present and past, for your contributions to shaping and sculpting a truly unique and innovative piece of software.
Related Reading
- The ALM web page, located at http://devworld.apple.com/dev/alm/.
- ALM SDK, available through the web site or on Mac OS SDK.
Erik Sea (sea@apple.com) Erik arrived at Apple about a year ago, with the sole objective of inundating the PowerBook Division with slinkys -- so far, they remain confined to his office, except on weekends when they explore the building and feed on weak or infirm paper clips. When not tweaking Location Manager code or the ALM web site, he can be found volunteering his time to rearrange sets of encyclopedias so that the volumes run from right to left on the shelf (because that's the way the pages are aligned), or writing games in COBOL on his Apple ///+.