1.0a3
QISA, or the Q Internet Setup Assistant, is a sample that demonstrates the basics of writing an Internet Setup Assistant for Mac OS. The sample is a Carbon application that runs on both Mac OS 9 and Mac OS X. It uses platform-specific technologies to configure the network for the appropriate platform. On Mac OS 9, it uses the Network Setup library for this, and on Mac OS X it uses the System Configuration framework. Platform-specific code is compiled into distinct bundles, which are then loaded and called at runtime via CFBundle.
QISA makes extensive use of the MoreIsBetter DTS sample code library. The Mac OS 9 platform support bundle uses the MoreNetworkSetup module as a high-level interface to the Network Setup library. The Mac OS X platform support bundle uses MoreSCF as a high-level interface to System Configuration framework, and MoreSecurity to execute that code in a privileged helper tool.
QISA runs on Mac OS 9 and higher (excluding Mac OS X 10.0.x, which did not support a public System Configuration framework API). On traditional Mac OS, it requires CarbonLib 1.6 or higher.
The sample contains the following items.
To try out the sample for yourself, launch the QISA application. The application will create a new setup window which is made up of a number of panels. When you've finished entering information in a panel, click Next. In the "Config" panel, click the "Do It" button to apply your changes. QISA will create a new network location (on Mac OS X, or a set of configurations on traditional Mac OS) named "QISA Test" and make those settings active.
You can build the sample using Project Builder 2.1 from the December 2002 Mac OS X developer tools release. Open the project file (QISA.pbproj), make sure that the "QISA" target is selected, and choose Build from the Build menu. This will automatically build the application and the platform-support bundle.
The project also builds with CodeWarrior Pro 8.3 on Mac OS X 10.2. Open the project file (QISA.mcp), choose either the CFM or Mach-O target, and then choose Make from the Project menu. This will automatically build the application and the platform-support bundles.
The Mach-O target builds are provided to keep me honest and to allow easier debugging on Mac OS X. If you want to deploy a product, like QISA, that runs on Mac OS 9 and X, you would ship a CFM build.
QISA is a modern Carbon-based application, packaged to support both Mac OS 9 and Mac OS X. The application is modern in the sense that it's entirely Carbon-event based and creates its user interface using NIBs. The application is divided into three main layers.
These layers share one key data structure, a dictionary of global values. The set of keys for this dictionary and their semantics are defined in "QISA.h". The accessor routines for this dictionary are all implemented in "QISA.c". There is one such dictionary per setup window. The application layer initialises the dictionary to its default value based on a property list file within the application bundle ("SetupInfo.plist", read by CopyGlobalValuesFromFile) supplemented by a property list from the platform plug-in ("PlatformProperties.plist", read by QISACopyPlatformProperties which is called by CopyGlobalValuesFromFile). Each panel reads values from the dictionary (using QISAGetGlobalValue) to display the current user settings and writes values to the dictionary (using QISASetGlobalValue) when those settings change. Finally, when the "Config" panel wants to create a new network location, it makes a copy of the global values dictionary (by calling QISACopyGlobalsDict) and passes that to the platform support layer, which creates the network location based on the information in the dictionary.
The application layer is surprisingly simple. It consists of just two source files "QISA.h" and "QISA.c". The first contains definitions and entry points that are used by the other two layers, for example, the global value dictionary routines described above. The file "QISA.c" contains the implementation of these routines and some application infrastructure. This infrastructure is surprisingly small due to the miracle of Carbon events.
The highlights of "QISA.c" are:
QISAGetGlobalValue, QISASetGlobalValue, and QISACopyGlobalsDict) represent the API to the global values dictionary. The dictionary itself is stored in a mutable CFDictionary within the per-window state (see below).
QISADisplayError is used by both the application layer and the panels to display error dialogs.
QISAIsButtonEnabled and QISASetButtonEnable, allow the panels to control the state of the Next and Previous buttons. The internal routines SwitchFromPanel, SwitchToPanel, and ChangeToNewPanel implement the panel switching behaviour. SwitchToPanel and SwitchFromPanel are split from ChangeToNewPanel because they're called to switch to the initial panel when the document is created and to switch from the current panel when the document is closed. The panel architecture is described in more detail in the next section. QISACommandNew and QISACommandAbout. There is also an "open application" Apple event handler (NewAppleEventHandler), which just calls through to QISACommandNew. SetupMenus, to set up the menu bar), and then calls RunApplicationEventLoop to run the main loop. The application layer maintains per-window storage, in the form of the QISAState structure. A pointer to this state is held in the window's refCon. This state, in turn, holds a WindowRef for the window, along with all the other state needed to manage the window. This includes an array of panel state structures that holds the state for each panel within the window (the panels field), and a mutable CFDictionary that holds the global values (the globalValues field).
The interface between the application and the panel layers is declared in "QISAPanels.h" (application to panel) and "QISA.h" (panel to application). The interface is object oriented in design, although implemented in C. The application maintains a per-panel data structure, QISAPanel, for each panel in the window. Every time it calls the panel or the panel calls it, a pointer to this data structure is passed. This data structure contains the state needed by the application layer to manage the panel, including a number of panel entry point function pointers. Each pointer is initialised to point to a default implementation (within "QISA.c"). When the panel is initialised it can override one or more of these default implementations by overwriting the entry point function pointers.
The entry points currently defined are:
Initialise The application layer calls this entry point before calling any other. If this returns an error, the panel is not displayed, an error dialog is shown, and no other entry points are called. Typcially the panel uses this entry point to initialise its instance data. The default implementation does nothing and returns noErr.
Terminate The application layer calls this for each panel that has been successfully initialised when the window is closed. Typically the panel uses this opportunity to clean up its instance data. The default implementation does nothing. SwitchTo The application layer calls this entry point when the user switches to the panel. If it returns an error, the panel is not switched to (the old panel remains current) and an error dialog is shown. Typically the implementation sets up its controls to reflect the current state of the window, as stored in the global values dictionary. The default implementation does nothing. SwitchFrom This application layer calls this entry point when the user switches away from the panel to another panel in the same window (activating a different window is not considered a switch). The panel is required to return a "next panel to activate". All current panels do this by calling through to the default implementation (which returns the next panel in the sequence) but a future panel could implement some clever branching mechanism. Typically the implementation also stores the current value of its controls in the window's global values. In addition, the panel can use this as an opportunity to cancel any pending activities, interacting with the user if required. If the panel returns an error, the switch does not happen; the panel can utilitise this to present a confirmation dialog to the user and return userCanceledErr if the user hits the cancel button. The panel state also contains a refCon field that the panel can use to store a pointer to its own per-panel instance data structure.
There are three panels implemented in "QISAPanels.c".
|
Note: |
SetupPanelCommandDoIt) is the code that actually calls the platform to create the network configuration.
|
Note: |
The platform support layer consists of two files, "QISAPlatform.h" and "QISAPlatform.c". The header file contains an abstract API that is supported by both platforms (traditional Mac OS and Mac OS X). The implementation file contains three main components.
QISAPlatformInit, it loads the appropriate platform-specific bundle and extracts from it functions pointers for each of the platform functions. It stores these functions in global variables.
|
Note: |
There are two platform-specific bundles.
The platform-specific functions are listed below.
QISACreatePortArray and QISACreateCCLArray These entry points are used by the PortCCL panel to populate the serial port and CCL popup menus. For the Mach-O platform, these calls are routed directly through to the like-named routines in MoreSCF. The CFM platform uses MoreSCF to generate the CCL array but its port scanning code is completely distinct; it's implemented using Open Transport port registry calls. QISADoesNetworkConfigExist This routine isn't actually used by current implementation, although both platforms' implementations should work (but are untested).
QISAMakeNetworkConfig This routine creates a new network location based on a copy of the global value dictionary (discussed earlier) and makes it active. The Mach-O platform implements this routine by using MoreSecurity to invoke a privileged helper tool. The helper tool does the work by calling MoreSCF library routines, which in turn call System Configuration framework. The CFM platform implements this routine by calling MoreNetworkSetup, which in turn calls through to the Network Setup library.
In addition, each platform has a properties dictionary (stored in the "PlatformProperties.plist") that is merged into the global values dictionary. This dictionary currently only contains a single key.
kQISAKeyControlPanels This is an array of control panels to quit before attempting to set up the network. This is required because each platform has (broken) control panels that don't recognise changes to the underlying preferences made via the platform API, but the actual list of control panels is different on each platform ("System Preferences" on Mac OS X; "TCP/IP", "Remote Access", and "Modem" on traditional Mac OS).
As Mac OS X is multi-user system, the Mach-O platform has to be concerned about security. Specifically, it must be careful to prevent a non-privileged user from messing up the machine's network configuration. It does this by tagging any set that it creates with a custom key, and only allowing you to modify sets that contain that key. See CopyCustomDictionary and SetCustomDictionary in "QISASetupTool.c" for the details.
Ultimately I would like to be able to use Authorization Services to determine whether the user has the authority to create network locations and to switch the current location. However, limitations of the Authorization Services API prevent me from doing this at this time. Furthermore, the Apple menu (and its agent, the scselect command line tool) allows any user to switch the current location, so QISA does no worse than Mac OS X's out-of-the-box security.
QISA is still very much a work in progress. Ultimately I want it to create a temporary network configuration, dial the modem, download a configuration file, disconnect the modem, and then set up a permanent network configuration based on the configuration file. At the moment all that it does is create a basic network configuration. However, it does this with a single binary on both traditional Mac OS and Mac OS X, which makes it a valuable example already.
I would also like to support plug-in panels in QISA. I didn't implement this feature in the current release because I wanted to ship it quickly. Also, there are technical problems with using NIB-based user interfaces in a plug-in environment on traditional Mac OS. Specifically, you can't move controls from one window to another on traditional Mac OS (EmbedControl returns paramErr in this case), so there's no way to load a panel's user interface from a NIB and then embed that user interface inside the main window.
To simplify the platform plug-in API, I make use of Core Foundation types. However, the CFM platform is linked with InterfaceLib and would not normally have access to Core Foundation. To work around this, I built my own Core Foundation stub library and linked to that. This works in practice and is not particularly bad in theory. After all, the CFM platform is being hosted in a CarbonLib application, which means that Core Foundation is already instantiated within the process. I'm just simplifying the job of calling it from InterfaceLib-based code. I'm also confident that this approach will remain compatible because CarbonLib and Mac OS 9 are no longer being updated.
There's currently no support for PPPoE. This would not be hard to do for Mac OS X but can't easily be supported on traditional Mac OS.
QISADoesNetworkConfigExist is not currently used for anything. The goal is to present a user interface to confirm the overwriting of the current QISA location.
On traditional Mac OS on certain machines, the machine dials the modem as soon as QISA has finished configuring the network. I believe that this is being caused by some background-only application, not by QISA itself. Curiously, if you disconnect, then set up the network again using QISA, it doesn't happen again. I discovered this problem during testing (version 1.0a2), however, I didn't consider it significant enough to hold up the release.
There are a number of other problems that I would classify as bugs. I didn't fix them because they didn't seem important enough.
If you find any problems with this sample, mail <DTS@apple.com> and Ill try to fix them up.
1.0d1 (Nov 2002) was a pre-release version with limited functionality. It was distributed to a small number of developers at a kitchen.
1.0d2 (Nov 2002) was another pre-release version.
1.0a1 (Mar 2002) was yet another pre-release version.
1.0a2 (Apr 2003) was sent to a limited number of DTS engineers for internal testing.
1.0a3 (Apr 2003) was the first released version.
Share and Enjoy.
Apple Developer Technical Support
Networking, Communications, Hardware
25 Apr 2003