TweetFollow Us on Twitter

Dispatch Aliasing
Volume Number:5
Issue Number:11
Column Tag:Pascal Workshop

Related Info: File Manager (PBxxx) Finder Interface

Dispatch Aliasing

By Roger Horton, Winston-Salem, NC

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

Dispatcher - An Application Aliasing and Launching Utility

[Roger Horton is a Research Adjunct Assistant Professor at the Medical School of Wake Forest University, and a private consultant. He is currently in a masters degree program in computer science at the University of North Carolina, and has been programming in the Macintosh environment since 1985.]

Recently, I completed a prototype of a database application for a dental office specializing in the treatment of facial pain. I choose 4th Dimension as the development environment due to its flexibility in reporting and for the ease with which external code routines can be incorporated. However, 4D has the habit of creating a large number of database files (for indexes, resources, data definitions, etc.), which can be very unsettling to the typical office user who happens to take a glimpse inside a 4D application folder. When the users reviewed the prototype they pointed this out, and requested that they be spared the intimidation of wondering what all the files were for. At this point I began to consider the options for simplifying the process of installing and starting the application.

Avoiding Confusion

A frequent point of confusion for users of custom database applications is setting up and running the application. Something as simple as starting an application can be difficult for novice Mac users, if they have to traverse too many volumes and folders. In other text oriented operating systems, developers could rely on shell scripts to simplify a user’s interaction with the file system. On the Mac, macro programs have sprung up to fill this void. These can help, but often add more complexity than they eliminate.

If the designer can personally walk each new user through the process of installation and startup, all is well. But all too often, the system is shipped to distant users who rely primarily on the documentation. As most of us have discovered, the knowledge level of the user varies widely, and the phone time spent in supporting these users, especially in the first weeks of use, can be considerable.

My goal was to keep the user out of the 4D application folder, with its many files, and allow a launch of the program from the Finder, using a single, easily identifiable icon. Finder alternatives were not considered, due to the user’s familiarity with the Finder and our desire to use Multifinder for coordination of other programs with the database application. I reviewed some macro processors that could potentially automate the launch process, but generally found that they were sensitive to the initial state of the desktop and were likely to be confusing to the average user. Since I hoped to simplify, instead of complicate, the training process, I considered a custom solution. My initial thoughts centered around writing a small utility program that would take care of locating and launching the database application. I had previously written code to launch one application from another and so I set about considering how to use this code as a basis for a custom launcher application. I should note that normally I would have developed this type of utility program in C, but found that MPW version 2.0.2 C did not support the in-line launch code (but does in version 3.0). This caused me to turn to MPW Pascal, although I tried to develop the code for easy translation to C (avoiding booleans, ‘with’ statements, etc.).

Help from the Finder

My first intention was to build a launcher utility program that could be placed outside the 4D database folder. It would know (or could be shown) the location of the database folder, and could set up the Finder information record such that 4D would open the database after it was launched. This required storing the location of the 4D folder in the launcher program’s data or resource fork. This appeared to me to be a workable, but inflexible approach to the problem. There was also the problem of storing data in a resource fork, which Apple now discourages due to lack of access control to resource forks on networked systems.

A second approach was to rely on some help from the Finder. Using information from the Desktop file, the Finder can launch an application when the user opens (or prints) one of its documents. By taking advantage of this ability, I could create a dummy document and set its filetype to the filetype of the launcher program. It could then be placed anywhere in the file system (directly on the desktop, for example) and still initiate the launch program without knowing where the program file was. No file system paths need be remembered, but the launch program would always have to reside in the same folder as the application to be launched, and information about the target application would have to be imbedded in the program code.

Generalizing the Dispatcher

Any good computer scientist would realize that up to this point my approach was a very narrow solution to the problem, and lacked the generality we all strive for. So, I chose a solution that allowed for greater flexibility in setting the launch parameters. To do this, I relied on the dummy launch document to store the launch parameters, rather than embedding them in the program code or in the resource fork of the launch program. By doing so, the dummy documents (hereafter called launch settings files) become a sort of alias for the program they cause to be launched. They also store additional information needed for setting up the launch of the target application. Using this solution, many settings files could be created, each capable of launching a different application. These settings files could then reside anywhere in the file system, with a single program acting as the dispatcher of applications, according to the data contained in each separate launch settings file. Hence the name “dispatcher”.

Remembering the Launch Parameters

To launch a target application, and tie in an associated launch document, the dispatcher program stores two data records in each launch settings file. The two records are the same, each containing the name of the file, the directory ID of its parent folder, and the name of the volume containing the directory (if the file is at the volume root level, the directory number is 2 and the directory name is the same as the volume name). Some additional information is also stored with each of the two records to help with the launch. The filetype is remembered, and can be used when it’s necessary to ask the user to help find a file that has changed location. Also as part of the data, a flag is stored that determines whether or not the target application will be told to open the document after it is launched.

Given that multiple dispatcher settings files can be present, it has to be easy to initialize and update the data in each one. To permit this, the dispatcher program tests the command key before launching the target application. If the key is down, a dialog window is presented that allows the user to alter the previous settings directly, or to change the settings by searching for the application and its associated document using the standard file dialog. It is also possible to set or unset the flag that determines whether the application should be instructed to open the associated document.

About Launching

Apple Technical Support has provided detailed code examples illustrating how to launch one program from another (in tech notes 52 & 126). I’ve based my code on these notes, including the recommendation not to use sublaunches. Avoiding sublaunches was not a significant limitation in my particular application, since there was no need to regain control after the launched application finished. In the event that you do use the sublaunch feature, its interesting to note that the Launch routine is now a function that returns a result when run under Multifinder. You can test the result of the launch and inform the user of errors. However, it turns out that a sublaunch, rather than a launch, must be performed in order to have Multifinder return a result code that can be processed by the sublaunching program.

The overall process of launching an application with the dispatcher requires a number of steps (Figure 1). The process begins with the user opening (by double-click or menu) the dispatcher settings file. This causes the Finder to load the data document’s working directory reference number (as well as other information) into the Finder information record referenced by the AppParmHandle global, and then launch the dispatcher. When the dispatcher gets control, it uses GetAppFiles to obtain the reference number of the settings file. This number is then used to locate and open the file for reading/writing. The two data records, one for the target application and one for the associated document, are read in. If the dispatcher can open the file, but can’t read any records, it assumes the data has not been initialized, and sets up some initial dummy data. Normally this is not necessary, since new data documents can be created by duplicating existing documents and then editing the existing settings. However, when setting up the dispatcher for the first time, a file utility needs to be used to create the first data document. Remember to set the filetype to ‘LCHD’, the dispatcher’s settings file filetype (the dispatcher’s filetype is LCHA).

Having read in the necessary launch parameters, the dispatcher must now take care of some housekeeping related to the Finder information that will be passed on to the target application. First the AppParmHandle is obtained from the system global variable at memory location $AEC. The current heap zone is set to the system heap, and the existing handle is disposed. A new handle is allocated in the system heap allowing sufficient space for the information related to a single target application document. Then the current heap pointer is set back to the application’s heap. I can recall the admonition in one of Apple’s early Macintosh training courses to avoid allocating anything in the cramped system heap of the 128K and 512K Mac’s. Fortunately, Apple now provides a larger, variable size system heap and looks more favorably (I think) on developers allocating structures there.

Figure 1.

With a new Finder information record allocated, the launch data is loaded into records corresponding byte-for-byte to the Finder information records, and the records are copied to the system heap at the location referenced by the newly allocated handle. This handle is then loaded into the system global at location $AEC. Be sure to note that if the target application is to be launched without an associated document, then the Finder information must be zeroed out correctly, since the Finder will not be intervening to perform its usual housekeeping. With the application parameters correctly loaded, all that is left to do is to set the default working directory to the target application’s directory and call the launch routine.

Navigating the HFS

Something should be said about the way the dispatcher navigates the hierarchical file system. After the target application and document are initially located, the dispatcher must assure that enough information is saved to permit them to be located on subsequent invocations.

To adequately specify a file to the file system, a number of options are available. A full pathname from the root volume is one possibility. However, tech note #238 discourages this, since the file’s directory may be moved, making the pathname invalid (also note that system 7.0 will provide unique 32 bit file ID numbers). For current system versions, the dispatcher remembers the filename, directory ID number, and the volume name, and uses them together to locate the target file. Since multiple volumes may be mounted, the program checks the stored volume name against each mounted volume for a match. It is possible that two volumes might have the same name and confuse the dispatcher, but this is uncommon and left as an enhancement to the program. Having located the volume, the program tests for the presence of the target file by using the filename and directory ID number. This process is carried out for both target files (application and associated document) and, if successful, is followed by the opening of working directories for both files.

A number of errors can occur while trying to locate the target files. Most commonly, one or both of the files might have been moved to another directory and/or volume. If this occurs, the program responds by asking the user to find the missing files, using the standard file dialog. As each file is found, the volume name, directory ID, and filename are saved so as to be available during the next use of the dispatcher. After the correct location information is obtained, it is written back to the settings file prior to launching the target application. If any unrecoverable errors occur, or the users cancels any of the dialogs, the dispatcher terminates without carrying out the launch.

One other point that should be made comes from personal experience with the dangers of using PBHSetVol. Anyone considering using the low level file manager calls should make sure they read tech note #140. Normally, most file manager calls will accept a working directory reference number in place of a volume reference number. This maintains compatibility between the flat and hierarchical file systems. Unlike other calls though, PBHSetVol has the habit of taking a working directory reference number in ioVRefNum and then setting the default directory back to the root. This can cause more than a few minutes of confusion trying to find out why files suddenly can’t be found.

Figure 2.

Using and Extending the Dispatcher

The dispatcher program has been written to require minimal user intervention. Ideally, a typical user should not even know that an intermediate program has been part of the launch of the target application. However, when initializing the file location data, or accommodating the movement of a file to a new location, the dispatcher’s settings dialog must be used (Figure 2). Its occasionally useful to view the dialog just to confirm what the settings are, especially if the settings haven’t been used in a while. The information presented in the dialog is very simple. Two edittext items are present to allow the user to enter the name of the target application and associated document. A check box item is also present that lets the user specify whether the application should be launched with or without the associated document.

A second method to specify the target file data is also provided. A “Search” button is present that, when pressed, sets the filenames to “XXXXXXXX” and goes directly to the standard file dialogs to locate the files. This method completely clears the previous filenames, and normally is used when new settings are being created.

As mentioned above, the dispatcher program and data documents can be located anywhere in the file system. This has been especially handy for me when using Multifinder. I’m sure that any Multifinder user is familiar with the desktop clutter that occurs when more than one application is open. Having a lot of Finder folders open on the desktop only adds to this clutter. My solution is to keep a set of dispatcher settings files directly on the desktop corresponding to the projects on which I’m currently working (Figure 3). All other folders are kept closed. This makes for much less clutter, and saves me the trouble of opening and closing folders as I launch each application.

Figure 3.

In my case, the original purpose of the dispatcher was to make installation and launching easier for users of 4th Dimension custom applications. My database application is supplied with the dispatcher located in a folder amongst the other 4D files. The settings file is located outside this folder. Installation simply involves copying the settings file and database folder onto the user’s hard disk. The user never has to look inside the database folder, except from the standard file dialog when first installing the program. If the volume on which the application is to be installed happens to be known ahead of time, then its possible to completely preconfigure the settings file.

Its easy to conceive of some enhancements to the dispatcher that would make it more useful. Options could easily be added to allow more than one data document to be associated with the target application. In this way, an application could be opened with a number of associated documents, similar (although much more limited) to a user startup script in the MPW environment. Provision could also be made for retention of the location of the dispatcher program in the data documents. This would make it possible to use the dispatcher with Finder substitutes. The dispatcher could also be made to create its own settings files, making initial use of the program simpler. Its very likely that future developments in the Mac operating systems (real file aliasing, etc.) will help provide for some of the features of the dispatcher program. But for the time being, it provides users with an intuitive method of launching custom database applications. Beyond that, it can help to reduce desktop clutter while providing a general method for launching any application with an associated document.

Listing:  dispatcher.make

dispatcherƒƒdispatcher.r
 Rez dispatcher.r -o dispatcher -append
 SetFile -a B dispatcher -c LCHA -t APPL
dispatcherƒƒdispatcher.p.o
 Link dispatcher.p.o 
 “{Libraries}”Interface.o 
 “{Libraries}”Runtime.o 
 “{PLibraries}”Paslib.o 
 -o dispatcher
dispatcher.p.o   ƒ dispatcher.p
 Pascal dispatcher.p
Listing:  Dispatcher.p

{
 File:      dispatcher.p
 Program:   dispatcher
 Author:    Roger A. Horton
              Verity Software Systems
              Copyright 1989, All Rights Reserved
 Creation:  6/4/89
 Purpose:   Create a small utility program to launch applications
from the Finder with associated launch documents. This
is especially useful for custom applications that require
a number of files, and that may cause a user confusion in
installing and launching the application. 
 Technique: This program performs a launch (not a sublaunch) using the 
code of Mac Tech Note #126 as a model. The utility does not directly 
initiate the launch, but relies on associated
data documents to initiate the process, and to determine
which application and documents are to be launched. The 
launch utility and document(s) can be located anywhere in 
the file system. By double clicking on a launch document, 
the user causes the Finder to locate and launch the launch
utility program. The utility then reads the launch data 
document to obtain the name and location of the application
and its associated document. The Finder application parameters
are then set up, and the application is launched. If the 
location of the application or its associated documents has
changed since the last launch, the user is prompted to find
their new location. In essence, the launch utility documents
act as aliases for target applications, and add additional
usefulness by providing for automatic control of associated
application documents.

   Program History:
}

program scan;
{$D+}               { Macsbug/TMON symbols    }
{$R-}               { Turn off range checking }
uses
 { Macintosh toolbox units } 
 PasLibIntf,Memtypes,QuickDraw,OSIntf,ToolIntf,PackIntf,
 SANE, PrintTraps, ROMDefs
 { Custom units }

const
 active = 0;    { for use in controls and menus }
 inactive = 255;    

 DfltDlogID = 200; { resource ID for config dialog }
 StopDlogID = 201; { resource ID for stop alert dialog }
  
 AppParmGVar= $AEC; { AppParmHandle sys global variable }

type
 { general types }
 pLaunchStruct = ^LaunchStruct; { Launch record }
 LaunchStruct = record
 pfName: ^Str255;   { pointer to application name }
 param: integer;  { alternate video,audio buffers }
 LC: packed array[0..1] of char; { extended parameters }
 extBlockLen: longint; { extra block length }
 fFlags: integer;         { finder file info flags }
 launchFlags: longint;  { bits 30,31 = 1 for sublaunch }
 end;

 FileLInfo = record      { Finder files }
 vRefNum: integer;  { 2 bytes }
 fType: OSType;     { file type - 4 bytes }
 version: boolean;  { 1 byte } 
 unused: boolean;   { 1 byte }
 fName: Str255; { 1 length byte, variable length string }
 end;

 APHdl = ^APPtr;  { Finder info }
 APPtr = ^APRec;
 APRec = record
 message: integer; { 2 bytes }
 count: integer;        { 2 bytes }
 files: array[1..1] of FileLInfo;
 end;
 LaunchData = record {application & document data settings}
 vName: Str255;    { volume name }
 vRefNum: integer; { working directory reference # }
 dirID: longint;   { directory ID # }
 fName: Str255;    { file name }
 fType: OSType;    { file type }
 useIt: integer; { whether to open associated document }
 end;
var
 { file system parameter blocks }
 myPB: ParamBlockRec;
 myHPB: HParamBlockRec;
 myInfoPB: CInfoPBRec;
 myWDPB: WDPBRec;
 { launch data records }
 appLData: LaunchData;  { application }
 docLData: LaunchData;  { document }
 { miscellaneous }
 done: integer;   { done flag }
 lcnt: integer;   { loop counter }

{ ****** Utility functions and procedures ****** }
procedure AlertMsg(msgStr1, msgStr2:Str255);
 { Procedure to print alert message. 
 To print numerics, use something like this:
 testStr1, testStr2: Str255;
 NumToString(reqCnt, testStr1);
 NumToString(reqCnt, testStr2);
 AlertMsg(testStr1, testStr2); }
var
 theRect:rect;
 ignore:integer;
begin
 ParamText(msgStr1, msgStr2, ‘’, ‘’);
 ignore := StopAlert(StopDlogID,NIL);
end;   { procedure AlertMsg }

{ *********      Error Handling     *********** }

function GetErrorMsg(result:OSErr):string;
begin
 result := abs(result);
 case result of
 18: GetErrorMsg := ‘driver error during status operation’;
 19: GetErrorMsg := ‘driver error during read operation’;
 20: GetErrorMsg := ‘driver error during write operation’;
 27: GetErrorMsg := ‘driver I/O error caused abort of operation’;
 28: GetErrorMsg := ‘driver not open’;
 33: GetErrorMsg := ‘the file directory is full’;
 34: GetErrorMsg := ‘all allocation blocks on the volume are full’;
 35: GetErrorMsg := ‘the specified volume is not mounted’;
 36: GetErrorMsg := ‘there was an unspecified I/O error’;
 37: GetErrorMsg := ‘the file or volume name is bad’;
 39: GetErrorMsg := ‘logical EOF reached unexpectedly’;
 40: GetErrorMsg := ‘attempt made to position before start of file’;
 42: GetErrorMsg := ‘too many files are open’;
 43: GetErrorMsg := ‘the file could not be found’;
 44: GetErrorMsg := ‘the volume is locked by hardware setting’;
 45: GetErrorMsg := ‘the file is locked’;
 46: GetErrorMsg := ‘the volume is locked by a software flag’;
 47: GetErrorMsg := ‘the file is already in use’;
 48: GetErrorMsg := ‘a file with specified name exists’;
 49: GetErrorMsg := ‘the file is already open for read/write’;
 50: GetErrorMsg := ‘no volume specified and no default volume’;
 51: GetErrorMsg := ‘a non-existent path was specified’;
 52: GetErrorMsg := ‘the was an error finding current position in file’;
 53: GetErrorMsg := ‘the specified volume is not on-line’;
 54: GetErrorMsg := ‘attempt to open a locked file for writing’;
 55: GetErrorMsg := ‘attempt to mount an already mounted volume’;
 56: GetErrorMsg := ‘the specified drive number is not mounted’;
 57: GetErrorMsg := ‘the volume lacks a Macintosh format directory’;
 58: GetErrorMsg := ‘there was an external file system error’;
 59: GetErrorMsg := ‘there was a problem during renaming’;
 60: GetErrorMsg := ‘the master directory block is bad’;
 61: GetErrorMsg := ‘read/write permission does not allow writing’;
 108: GetErrorMsg := ‘there is insufficient application memory’;
 109: GetErrorMsg := ‘a nil master pointer has been encountered’;
 111: GetErrorMsg := ‘attempt to operate on a free block’;
 112: GetErrorMsg := ‘attempt to purge a locked block’;
 117: GetErrorMsg := ‘the block is locked’;
 120: GetErrorMsg := ‘the directory could not be found’;
 121: GetErrorMsg := ‘too many working directories are open’;
 122: GetErrorMsg := ‘a folder cannot be placed in its own subfolder’;
 123: GetErrorMsg := ‘attempt HFS operations on non-HFS volume’;
 127: GetErrorMsg := ‘there was an internal file system error’;
 128: GetErrorMsg := ‘printing aborted at user request’;
 end;
end;   { function GetErrorMsg }

procedure IOCheck(result:OSErr);
var
 ignore:integer;
 errorString:Str255;
begin
 if result <> NoErr then
 begin
 NumToString(result,errorString);
 ParamText(‘Macintosh OS Error #:’, errorString, ‘Macintosh OS Error 
Desc:’, GetErrorMsg(result));
 ignore := StopAlert(StopDlogID,NIL);
 end
end;   { procedure IOCheck }

procedure LaunchFailed(errNo:OSErr);
var
 ignore:integer;
 errorString:Str255;
begin
 NumToString(errNo,errorString);
 ParamText(‘Launch Error #:’, errorString,’’,’’);
 ignore := StopAlert(StopDlogID,NIL);
end;   { procedure LaunchFailed }

procedure NotFoundMsg(str1,str2:Str255);
var
 ignore: integer;
begin
 ParamText(‘Please help locate’, str1, str2, ‘using the following dialog...’);
 ignore := StopAlert(StopDlogID,NIL);
end;   { procedure LaunchFailed }

{ *********   Configuration routines  *********** }
function ReadDefaultData: integer;
{ Open the launch data file and read the data necessary for
locating launch application and associated document (if any). }
var
 theErr: OSErr;
 myAppFile: AppFile;
 myRefNum: integer;
 theResult: integer;
 appMsg: integer;
 appCount: integer;
begin
 theResult := 0;   { be optimistic }
 CountAppFiles(appMsg, appCount);
 if appCount < 1 then
 begin
 theResult := 1;
 AlertMsg(‘Dispatcher must be launched’,’from a data document’);
 { could create a new data document file instead }
 end
 else
 begin
 GetAppFiles(1, myAppFile);
 { open the file for reading }
 myPB.ioCompletion := nil;
 myPB.ioNamePtr := @myAppFile.fName;
 myPB.ioVRefNum := myAppFile.vRefNum;
 myPB.ioPermssn := fsRdWrPerm; { read/write permission }
 myPB.ioMisc := nil;  { use volume buffer }
 theErr := PBOpen(@myPB, false);
 if theErr <> noErr then
 begin
 IOCheck(theErr); 
 theResult := 1;
 end
 else
 begin
 myRefNum := myPB.ioRefNum;
 { read the application launch data }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 myPB.ioBuffer := @appLData;
 myPB.ioReqCount := sizeof(LaunchData);
 myPB.ioPosMode := fsAtMark;
 myPB.ioPosOffset := 0;
 theErr:= PBRead(@myPB, false);
 if ((theErr <> noErr) or (myPB.ioActCount < 1)) then
 begin
 appLData.vName := ‘Uninitialized’; 
 appLData.dirID := 0; 
 appLData.fName := ‘Uninitialized’; 
 appLData.fType := ‘APPL’; 
 appLData.useIt := 1;    { not used }
 end;

 { read the document launch data }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 myPB.ioBuffer := @docLData;
 myPB.ioReqCount := sizeof(LaunchData);
 myPB.ioPosMode := fsAtMark;
 myPB.ioPosOffset := 0;
 theErr:= PBRead(@myPB, false);
 if ((theErr <> noErr) or (myPB.ioActCount < 1)) then
 begin
 docLData.vName := ‘Uninitialized’; 
 docLData.dirID := 0; 
 docLData.fName := ‘Uninitialized’; 
 docLData.fType := ‘TEXT’; 
 docLData.useIt := 1; 
 end;
 { close the data file }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 theErr:= PBClose(@myPB, false);
 end;   { if data file opened OK }
 end;   { if app files count is > 0 }
 ReadDefaultData := theResult;
end;   { procedure ReadDefaultData }

function WriteDefaultData: integer;
{ Open the launch data file and write the data necessary for
locating launch application and associated document (if any).
 This should take place any time the data is changed. }
var
 theErr: OSErr;
 myAppFile: AppFile;
 myRefNum: integer;
 theResult: integer;
begin
 theResult := 0;   { be optimistic }
 GetAppFiles(1, myAppFile);
 { open the file for reading/writing }
 myPB.ioCompletion := nil;
 myPB.ioNamePtr := @myAppFile.fName;
 myPB.ioVRefNum := myAppFile.vRefNum;
 myPB.ioPermssn := fsRdWrPerm; { read/write permission }
 myPB.ioMisc := nil;       { use volume buffer }
 theErr:= PBOpen(@myPB, false);
 if theErr <> noErr then
 begin
 IOCheck(theErr); 
 theResult := 1;
 end
 else
 begin
 myRefNum := myPB.ioRefNum;
 { write the application launch data }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 myPB.ioBuffer := @appLData;
 myPB.ioReqCount := sizeof(LaunchData);
 myPB.ioPosMode := fsAtMark;
 myPB.ioPosOffset := 0;
 theErr:= PBWrite(@myPB, false);
 if ((theErr <> noErr) or (myPB.ioActCount < 1)) then
 begin
 IOCheck(theErr); 
 theResult := 1;
 end
 else
 begin
 { write the document launch data }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 myPB.ioBuffer := @docLData;
 myPB.ioReqCount := sizeof(LaunchData);
 myPB.ioPosMode := fsAtMark;
 myPB.ioPosOffset := 0;
 theErr:= PBWrite(@myPB, false);
 if ((theErr <> noErr) or (myPB.ioActCount < 1)) then
 begin
 IOCheck(theErr); 
 theResult := 1;
 end;   { if bad document write }
 end;   { if application write OK }
 { close the data file }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 theErr:= PBClose(@myPB, false);
 end;   { if data file opened OK }
 WriteDefaultData := theResult;
end;   { procedure WriteDefaultData }

function FindApplFile: integer;
  { Given the info in the data document, try to find the target
application’s working directory reference number. If it can’t
 be found, then prompt the user to find it. }
var
 theErr: OSErr;
 theResult: integer;
 reply: SFReply;
 topLeft: Point;
 fileFilter: SFTypeList;
 appdone, appfound: integer;
 theName: Str255;
 theIndex: integer;
 theRefNum: integer;
begin
 theResult := 0;   { be optimistic }
 { see if the volume is mounted }
 theIndex := 1;
 appdone := 0; appfound := 0; theName:= ‘’;
 repeat
 myHPB.ioVolIndex := theIndex;
 myHPB.ioCompletion := NIL;
 myHPB.ioNamePtr := @theName;
 theErr := PBHGetVInfo(@myHPB, false);
 if theErr <> noErr then
 appdone := 1;   { no more volumes to check }
 if theName = appLData.vName then
 begin
 appfound := 1; appdone := 1;
 theRefNum := myHPB.ioVRefNum;
 end;
 theIndex := theIndex + 1;
 until appdone > 0;

 if appfound = 0 then
 theResult := 1
 else
 begin
 { see if file can be found  }
 myHPB.ioCompletion := NIL;
 myHPB.ioNamePtr := @appLData.fName;
 myHPB.ioVRefNum := theRefNum;  { from call above } 
 myHPB.ioFDirIndex := 0; { use file name, not index }
 myHPB.ioDirID := appLData.dirID;  { use stored Dir ID }
 theErr := PBHGetFInfo(@myHPB,false); 
 if (theErr <> noErr) then
 begin
 { IOCheck(theErr); }
 theResult := 1;
 end
 else
 begin
 { get the current application WD Ref Num }
 myWDPB.ioCompletion := nil;
 myWDPB.ioNamePtr := nil;   { directory name }
 myWDPB.ioVRefNum := theRefNum;
 myWDPB.ioWDProcID := longint(‘ERIK’);
 myWDPB.ioWDDirID := appLData.dirID;
 theErr := PBOpenWD(@myWDPB, false);
 appLData.vRefNum := myWDPB.ioVRefNum;
 IOCheck(theErr);
 end;   { getting the current WD Ref Num }
 end;   { if volume was found OK }

 { if application couldn’t be found using default information, make the 
user find it manually. }
 if theResult <> 0 then
 begin
 NotFoundMsg(‘the program file: ‘,appLData.fName);
 topLeft.h := 80; topLeft.v := 70;          
 fileFilter[0] := appLData.fType;  
 SFGetFile(topLeft,’’,NIL,1,fileFilter,NIL,reply);
 if reply.Good then
 begin
 theResult := 0;  { reset the error flag }
 appLData.vRefNum := reply.vRefNum;  { WD RefNum }
 appLData.fName := reply.fName;
 appLData.fType := reply.fType;
 { look up new volume name }
 myHPB.ioCompletion := NIL;
 myHPB.ioNamePtr := @appLData.vName;
 myHPB.ioVRefNum := appLData.vRefNum;  
 myHPB.ioVolIndex := 0;  { use vRefNum, not name }
 theErr := PBHGetVInfo(@myHPB,false); 
 IOCheck(theErr);
 { look up new application directory ID }
 myInfoPB.ioCompletion := nil;
 myInfoPB.ioNamePtr:= @appLData.fName;
 myInfoPB.ioVRefNum := appLData.vRefNum;
 myInfoPB.ioFDirIndex := 0;
 myInfoPB.ioDirID := 0;
 theErr := PBGetCatInfo(@myInfoPB,false); 
 IOCheck(theErr);
 appLData.dirID := myInfoPB.ioFLParID;
 { update the default data }
 if WriteDefaultData <> 0 then
 theResult := 1;
 end   { if reply is good }
 else
 begin
 theResult := 1;
 end;   { user cancelled application lookup }
 end;   { if default application location not valid }
 FindApplFile := theResult;
end;   { procedure FindApplFile }

function FindDocFile: integer;
  { Given the info in the data document, try to find launch
    document’s working directory reference number. If it can’t
 be found, then prompt the user to find it. }
var
 theErr: OSErr;
 theResult: integer;
 reply: SFReply;
 topLeft: Point;
 fileFilter: SFTypeList;
 docdone, docfound: integer;
 theName: Str255;
 theIndex: integer;
 theRefNum: integer;
begin
 theResult := 0;   { be optimistic }

 { see if there is an associated document with application }
 if (docLData.useIt = 1) then   { launch with associated document }
 begin
 { see if the volume is mounted }
 theIndex := 1;
 docdone := 0; docfound := 0; theName:= ‘’;
 repeat
 myHPB.ioVolIndex := theIndex;
 myHPB.ioCompletion := NIL;
 myHPB.ioNamePtr := @theName;
 theErr := PBHGetVInfo(@myHPB, false);
 if theErr <> noErr then
 docdone := 1;   { no more volumes to check }
 if theName = docLData.vName then
 begin
 docfound := 1; docdone := 1;
 theRefNum := myHPB.ioVRefNum;
 end;
 theIndex := theIndex + 1;
 until docdone > 0;

 if docfound = 0 then
 theResult := 1
 else
 begin
 { see if the file can be found in its directory }
 myHPB.ioCompletion := NIL;
 myHPB.ioNamePtr := @docLData.fName;
 myHPB.ioVRefNum := theRefNum;   { from call above } 
 myHPB.ioFDirIndex := 0; {use file name, not index}
 myHPB.ioDirID := docLData.dirID;{use stored Dir ID}
 theErr := PBHGetFInfo(@myHPB,false); 
 if (theErr <> noErr) then
 begin
 { IOCheck(theErr); }
 theResult := 1;
 end
 else
 begin
 { get the current document WD Ref Num }
 myWDPB.ioCompletion := nil;
 myWDPB.ioNamePtr := nil;   { directory name }
 myWDPB.ioVRefNum := theRefNum;
 myWDPB.ioWDProcID := longint(‘ERIK’);
 myWDPB.ioWDDirID := docLData.dirID;
 theErr := PBOpenWD(@myWDPB, false);
 docLData.vRefNum := myWDPB.ioVRefNum;
 IOCheck(theErr);
 end;   { getting the current WD Ref Num }
 end;   { if volume was found OK }

{ if document couldn’t be found using default information, make the user 
find it manually. }
 if theResult <> 0 then
 begin
 NotFoundMsg(‘the associated document: ‘,docLData.fName); 
 topLeft.h := 80; topLeft.v := 70;          
 fileFilter[0] := docLData.fType;  
 SFGetFile(topLeft,’’,NIL,-1,fileFilter,NIL,reply);
 if reply.Good then
 begin
 theResult := 0;  { reset the error flag }
 docLData.vRefNum := reply.vRefNum;
 docLData.fName := reply.fName;
 docLData.fType := reply.fType;
 { look up new volume name }
 myHPB.ioCompletion := nil;
 docLData.vName := ‘’;
 myHPB.ioNamePtr := @docLData.vName;
 myHPB.ioVRefNum := docLData.vRefNum;  
 myHPB.ioVolIndex := 0; { use vRefNum, not name }
 theErr := PBHGetVInfo(@myHPB,false); 
 { look up new document parent directory ID }
 myInfoPB.ioCompletion := nil;
 myInfoPB.ioNamePtr:= @docLData.fName;
 myInfoPB.ioVRefNum := docLData.vRefNum;
 myInfoPB.ioFDirIndex := 0;
 myInfoPB.ioDirID := 0;
 theErr := PBGetCatInfo(@myInfoPB,false); 
 IOCheck(theErr);
 docLData.dirID := myInfoPB.ioFLParID;
 { parent directory ID }

 { update the default data }
 if WriteDefaultData <> 0 then
 theResult := 1;
 end   { if reply is good }
 else
 begin
 theResult := 1;
 end;   { user cancelled document lookup }
 end;   { if default document location not valid }
 end;   { if associated document exists }
 FindDocFile := theResult;
end;   { procedure FindDocFile }

function EditDefaults: integer;
var
 itemHit: integer;
 itemType: integer;
 itemRect: Rect;
 dfltDlg: DialogPtr;
 itemHdl1, itemHdl2: Handle;   { edittext handles }
 itemHdl3: Handle;    { check box dialog handle }
 cBoxHdl1: ControlHandle; { check box control handle }
 theResult: integer;
begin
 theResult := 0;
 { get dialog & edittext handles }
 dfltDlg := GetNewDialog(DfltDlogID, nil, Pointer(-1));
 GetDItem(dfltDlg, 4, itemType, itemHdl1, itemRect);
 GetDItem(dfltDlg, 5, itemType, itemHdl2, itemRect);
 GetDItem(dfltDlg, 6, itemType, itemHdl3, itemRect);
 cBoxHdl1 := ControlHandle(itemHdl3);
 SetIText(itemHdl1, appLData.fName);     { app filename }
 SetIText(itemHdl2, docLData.fName);     { doc filename }
 SetCtlValue(cBoxHdl1, docLData.useIt);  { use/don’t use document }
 
 { put up dialog }
 itemHit := 0;
 while ((itemHit > 3) or (itemHit < 1))  do
 begin
 ModalDialog(nil, itemHit);
 if itemHit = 6 then   { toggle the check box }
 begin
 if (GetCtlValue(cBoxHdl1) = 0) then
 SetCtlValue(cBoxHdl1, 1)
 else
 SetCtlValue(cBoxHdl1, 0);
 end;
 end;   { while }
 { get dialog results }
 case itemHit of 
 1: begin { use names entered in dialog }
 { application name }
 GetIText(itemHdl1, appLData.fName);
 { document name }
 GetIText(itemHdl2, docLData.fName);
 end;
 2: begin  { cause a search for new files }
 appLData.fName := ‘XXXXXXXX’;
 docLData.fName := ‘XXXXXXXX’;
 end;
 3: theResult := 1; { cancel the launch }
 end;
 if (theResult = 0) then
 begin
 docLData.useIt := GetCtlValue(cBoxHdl1);
 theResult := WriteDefaultData;  { update the defaults }
 end;
 DisposDialog(dfltDlg);
 EditDefaults := theResult;
end;   { EditDefaults }

function TestCommandKey: integer;
{ if command is held down while starting, the user will
    be allowed to edit the default settings. The file data
 has already been read at this point. If the user cancels,
 the data defaults are not changed. }
var
 theResult: integer;
 eventReady: boolean;
 myEvent: EventRecord;
begin
 theResult := 0;
 eventReady := GetNextEvent(everyEvent, myEvent);
 if (BAND(myEvent.modifiers,cmdkey) > 0) then
 theResult := EditDefaults;
 TestCommandKey := theResult;  
end;   { TestCommandKey }

{ *********   Transfer and launch routines  *********** }

function ResetFinderInfo: integer;
{ This is an example of how the finder parms could be changed
    without allocating a new structure in the system heap. However, the 
changes could not exceed the size of the existing heap object.  This 
routine is not used. }
var
 fInfoHdl: Handle;
 hSize: integer;
 appParmRec: APRec;        { my Finder info }
 theResult: integer;
begin
 theResult := 0;   { be optimistic }
 { get AppParmHandle, lock it, and copy parms record }
 fInfoHdl := Handle(AppParmGVar);
 fInfoHdl := Handle(fInfoHdl^);
 HLock(fInfoHdl);
 hSize := GetHandleSize(fInfoHdl);
 BlockMove(Pointer(fInfoHdl^), Pointer(@appParmRec), hSize);
 if (appParmRec.count > 0) then
 begin
 if (length(appParmRec.files[1].fName) >= length(docLData.fName)) then
 begin  
 { now change the launch parameters }
 appParmRec.files[1].fName := docLData.fName;
 appParmRec.files[1].fType := docLData.fType;
 appParmRec.files[1].vRefNum := docLData.vRefNum;
 BlockMove(Pointer(@appParmRec), Pointer(fInfoHdl^), hSize);
 end
 end
 else 
 begin
 AlertMsg(‘Unable to reset’, ‘launch file parameters’);
 theResult := 1;
 end;
 HUnlock(fInfoHdl);
 ResetFinderInfo := theResult;
end;   { ResetFinderInfo }

function ZeroFinderInfo: integer;
{ If no associated documents are needed, then the number of
    files in app parm handle block must be set to zero, and
 the file types set to zero so the Finder can clean up when
 it gets control back. The number of files will always be 1
 when this routine is called. }
var
 fInfoHdl: Handle;   { Finder info handle }
 hSize: integer;
 appParmRec: APRec; { my Finder info }
 theResult: integer;
begin
 theResult:= 0;   { be optimistic }
 { get handle, lock it, and copy parms record }
 fInfoHdl := Handle(AppParmGVar);
 fInfoHdl := Handle(fInfoHdl^);
 HLock(fInfoHdl);
 hSize := GetHandleSize(fInfoHdl);
 BlockMove(Pointer(fInfoHdl^), Pointer(@appParmRec), hSize);
 if (appParmRec.count > 0) then
 begin
 { now change the launch parameters }
 appParmRec.count := 0;
 appParmRec.files[1].fType := OSType(longint(0));
 { write it back out }
 BlockMove(Pointer(@appParmRec), Pointer(fInfoHdl^), hSize);
 end;
 HUnlock(fInfoHdl);
 ZeroFinderInfo := theResult;
end;   { ZeroFinderInfo }

function ReplaceFinderInfo: integer;
  { To prepare for the launch, the finder launch information must be 
reset to hold the name and type of the database startup file. This routine 
disposes the old AppParmHandle heap object and allocates a new one on 
the system heap. Returns 0 if successful, 1 if an error occurred. }
var
 fInfoHdl: Handle;      { handle to Finder info }
 sysZone: THz;          { system heap zone pointer }
 appZone: THz;          { application heap zone pointer }
 hSize: integer;
 appParmRec: APRec;     { my Finder info }
 theErr: OSErr;
 theResult: integer;
 saveAppParm: ^longint; { pointer used to access $AEC }
begin
 theResult := 0;   { be optimistic }
 { get handle, lock it, and copy parms record }
 fInfoHdl := Handle(appParmGVar);
 fInfoHdl := Handle(fInfoHdl^);
 HLock(fInfoHdl);
 hSize := GetHandleSize(fInfoHdl);
 BlockMove(Pointer(fInfoHdl^), Pointer(@appParmRec), hSize);
 
 { dispose the old handle in the system zone}
 HUnlock(fInfoHdl);
 appZone := GetZone;
 sysZone := SystemZone;
 SetZone(sysZone);
 DisposHandle(fInfoHdl);
 
 { allocate a new handle in the system zone }
 hSize := 13 + length(docLData.fName); { enough for one doc }
 fInfoHdl := NewHandle(hSize);
 theErr := MemError;
 SetZone(appZone);
 if theErr <> noErr then
 begin
 IOCheck(theErr);
 theResult := 1;
 end
 else
 begin
 HLock(fInfoHdl);
 { put the new handle back at $AEC }
 saveAppParm := Pointer(AppParmGVar);
 saveAppParm^ := longint(fInfoHdl);
 
 { now change the launch parameters }
 appParmRec.message:= 0;    { open it }
 appParmRec.count:= 1;      { number of documents }
 appParmRec.files[1].vRefNum := docLData.vRefNum;
 appParmRec.files[1].fType := docLData.fType;
 appParmRec.files[1].version := false;   
 appParmRec.files[1].unused := false;
 appParmRec.files[1].fName := docLData.fName;
 BlockMove(Pointer(@appParmRec), Pointer(fInfoHdl^), hSize);
 HUnlock(fInfoHdl);
 end;   { if sys heap handle allocated OK }
 ReplaceFinderInfo := theResult;
end;   { ReplaceFinderInfo }

function LaunchIt(pLnch: pLaunchStruct): OSErr; 
{ from tech note #52 & 126 }
 INLINE $205F, $A9F2, $3E80;

procedure DoLaunch;
{ Launch the application, given the information in the
  launch data records for the application & document. }
var
 fileInfo: FInfo;
 ignore: integer;
 myLaunch: LaunchStruct;
 theErr: OSErr;
begin
 { set default working directory to application’s folder }
 myWDPB.ioCompletion := NIL;
 myWDPB.ioNamePtr := NIL;
 myWDPB.ioVRefNum := appLData.vRefNum;
 theErr := PBSetVol(@myWDPB,false);  

 { get finder flags for launch record}
 myInfoPB.ioNamePtr:= @appLData.fName;
 myInfoPB.ioVRefNum := appLData.vRefNum;
 myInfoPB.ioFDirIndex := 0; { use name instead of index }
 myInfoPB.ioDirID := 0;
 theErr := PBGetCatInfo(@myInfoPB,false); 

 { now launch the application }
 myLaunch.pfName := @appLData.fName;    
 myLaunch.param := 0; 
 myLaunch.LC := ‘LC’;
 myLaunch.extBlockLen := 6;
 myLaunch.LaunchFlags:= $00000000; {$C0000000 for sublaunch}
 myLaunch.fFlags := myInfoPB.ioFlFndrInfo.fdFlags;  { from GetCatInfo 
}

 theErr := LaunchIt(@myLaunch);
 if theErr < 0 then   
 LaunchFailed(theErr);
end;   { procedure DoLaunch }

function PrepareForLaunch: integer;
{ set up the AppParmsHandle data }
var
 theResult: integer;
begin
 theResult := 0;
 if (docLData.useIt = 1) then  { application & document }
 theResult := ReplaceFinderInfo
 else       { application only }
 theResult := ZeroFinderInfo;
 PrepareForLaunch := theResult;
end;   { function PrepareForLaunch }

procedure Initialize;
{ initialize managers & globals }
begin
 { initialize the managers }
 InitGraf(@thePort);               
 InitFonts;                        
 InitWindows;                      
 InitMenus;                        
 TEInit;                           
 InitDialogs(NIL);                 
 FlushEvents(everyEvent,0);        
end; { Initialize }

{ main program }
begin 
 Initialize;
 lcnt := 1; done := 0;
 while done = 0 do
 begin
 case lcnt of
 1: done := ReadDefaultData;
 2: done := TestCommandKey;
 3: done := FindApplFile;
 4: done := FindDocFile;
 5: done := PrepareForLaunch;
 6: DoLaunch;    { returns only if sublaunched }
 end;
 lcnt := lcnt + 1;
 if lcnt > 6 then done := 1;
 end;   { while }
end. { dispatcher }
Listing:  dispatcher.r

/* dispatcher.r
 Copyright Verity Software Systems 1989
     All rights reserved.
 Resource definitions for dispatcher application
 Rez format
*/
#include “Types.r”
resource ‘BNDL’ (128) {
 ‘LCHA’,
 0,
 { /* array TypeArray: 2 elements */
 /* [1] */
 ‘ICN#’,
 { /* array IDArray: 2 elements */
 /* [1] */
 0, 128,
 /* [2] */
 1, 129
 },
 /* [2] */
 ‘FREF’,
 { /* array IDArray: 2 elements */
 /* [1] */
 0, 130,
 /* [2] */
 1, 131
 }
 }
};
resource ‘ICN#’ (128, “Dispatcher Application Icon”) {
 { /* array: 2 elements */
 /* [1] */
 $”FFFF FFFE 8000 0003 8120 0003 8120 0003"
 $”8220 0003 8440 0003 B880 07C3 8118 1823"
 $”861C 2023 B80F FF23 8006 0023 8005 0043"
 $”8004 8043 8004 4083 800C 2083 800C 0103"
 $”8014 0203 8014 0603 8024 0A03 8024 1203"
 $”8020 7203 8021 D203 801E 1203 8000 1203"
 $”8000 1203 8000 1203 8000 3303 8000 2103"
 $”8000 2103 8000 2103 FFFF FFFF 7FFF FFFF”,
 /* [2] */
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 }
};
resource ‘ICN#’ (129, “Dispatcher Document Icon”) {
 { /* array: 2 elements */
 /* [1] */
 $”FFFF FFFE 8000 0003 8000 1E03 8000 0183"
 $”8000 1C43 8000 0243 83C0 0123 8430 18A3"
 $”8408 38A3 84FF F0A3 8400 6003 8200 A003"
 $”8201 2003 8102 2003 8104 3003 8080 3003"
 $”8040 2803 8060 2803 8050 2403 8048 2403"
 $”804E 0403 804B 8403 8048 7803 8048 0003"
 $”8048 0003 8048 0003 80CC 0003 8084 0003"
 $”8084 0003 8084 0003 FFFF FFFF 7FFF FFFF”,
 /* [2] */
 $”FFFF FFFE FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF 7FFF FFFF”
 }
};
resource ‘FREF’ (130) {
 ‘APPL’,
 0,
 “Dispatcher Application”
};
resource ‘FREF’ (131) {
 ‘LCHD’,
 1,
 “Dispatcher Document”
};
data ‘LCHA’ (0) {
 $”4469 7370 6174 6368 6572 2041 7070 6C69"        
 /* Dispatcher Application */
 $”6361 7469 6F6E 202D 2043 6F70 7972 6967"        
 /* Copyright 1989 Verity Software Systems. */
 $”6874 2031 3938 3920 5665 7269 7479 2053"        
 $”6F66 7477 6172 6520 5379 7374 656D 732E”        
 $”2052 6F67 6572 2041 2E20 486F 7274 6F6E”        
 /*  Roger A. Horton */
 $”202D 2041 6C6C 2072 6967 6874 7320 5265"        
 /*  - All rights Reserved */
 $”7365 7276 6564"                                 
};
resource ‘DLOG’ (200, “Configuration Settings”) {
 {38, 116, 312, 390},
 altDBoxProc,
 visible,
 noGoAway,
 0x0,
 200,
 “Configuration Settings”
};
resource ‘DITL’ (200, “Configuration Settings”) {
 { /* array DITLarray: 13 elements */
 /* [1] */
 {184, 24, 204, 84},
 Button {
 enabled,
 “OK”
 },
 /* [2] */
 {184, 109, 204, 169},
 Button {
 enabled,
 “Search”
 },
 /* [3] */
 {184, 192, 204, 252},
 Button {
 enabled,
 “Cancel”
 },
 /* [4] */
 {74, 31, 92, 239},
 EditText {
 enabled,
 “4th Dimension® v1.0.6”
 },
 /* [5] */
 {128, 30, 146, 238},
 EditText {
 enabled,
 “FPR”
 },
 /* [6] */
 {151, 56, 171, 212},
 CheckBox {
 enabled,
 “Open App Document”
 },
 /* [7] */
 {4, 81, 24, 182},
 StaticText {
 enabled,
 “• Dispatcher •”
 },
 /* [8] */
 {25, 75, 45, 190},
 StaticText {
 enabled,
 “Current Settings”
 },
 /* [9] */
 {51, 31, 70, 239},
 StaticText {
 enabled,
 “Application Name:”
 },
 /* [10] */
 {106, 32, 125, 239},
 StaticText {
 enabled,
 “Document Name:”
 },
 /* [11] */
 {220, 30, 238, 249},
 StaticText {
 enabled,
 “© Verity Software Systems 1989”
 },
 /* [12] */
 {239, 15, 256, 128},
 StaticText {
 enabled,
 “by Roger Horton”
 },
 /* [13] */
 {239, 132, 256, 263},
 StaticText {
 enabled,
 “All Rights Reserved”
 }
 }
};
resource ‘ALRT’ (201) {
 {80, 100, 210, 400},
 201,
 { /* array: 4 elements */
 /* [1] */
 OK, visible, sound1,
 /* [2] */
 OK, visible, sound1,
 /* [3] */
 OK, visible, sound1,
 /* [4] */
 OK, visible, sound1
 }
};
resource ‘DITL’ (201) {
 { /* array DITLarray: 2 elements */
 /* [1] */
 {100, 125, 120, 175},
 Button {
 enabled,
 “OK”
 },
 /* [2] */
 {10, 70, 90, 290},
 StaticText {
 disabled,
 “^0 \n^1 \n^2 \n^3”
 }
 }
};
resource ‘SIZE’ (-1) {
 dontSaveScreen,
 ignoreSuspendResumeEvents,
 dontDoOwnActivate,
 128000,
 128000
};

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Dropbox 193.4.5594 - Cloud backup and sy...
Dropbox is a file hosting service that provides cloud storage, file synchronization, personal cloud, and client software. It is a modern workspace that allows you to get to all of your files, manage... Read more
Google Chrome 122.0.6261.57 - Modern and...
Google Chrome is a Web browser by Google, created to be a modern platform for Web pages and applications. It utilizes very fast loading of Web pages and has a V8 engine, which is a custom built... Read more
Skype 8.113.0.210 - Voice-over-internet...
Skype is a telecommunications app that provides HD video calls, instant messaging, calling to any phone number or landline, and Skype for Business for productive cooperation on the projects. This... Read more
Tor Browser 13.0.10 - Anonymize Web brow...
Using Tor Browser you can protect yourself against tracking, surveillance, and censorship. Tor was originally designed, implemented, and deployed as a third-generation onion-routing project of the U.... Read more
Deeper 3.0.4 - Enable hidden features in...
Deeper is a personalization utility for macOS which allows you to enable and disable the hidden functions of the Finder, Dock, QuickTime, Safari, iTunes, login window, Spotlight, and many of Apple's... Read more
OnyX 4.5.5 - Maintenance and optimizatio...
OnyX is a multifunction utility that you can use to verify the startup disk and the structure of its system files, to run miscellaneous maintenance and cleaning tasks, to configure parameters in the... Read more
Hopper Disassembler 5.14.1 - Binary disa...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32- and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about its... Read more

Latest Forum Discussions

See All

Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »
Live, Playdate, Live! – The TouchArcade...
In this week’s episode of The TouchArcade Show we kick things off by talking about all the games I splurged on during the recent Playdate Catalog one-year anniversary sale, including the new Lucas Pope jam Mars After Midnight. We haven’t played any... | Read more »
TouchArcade Game of the Week: ‘Vroomies’
So here’s a thing: Vroomies from developer Alex Taber aka Unordered Games is the Game of the Week! Except… Vroomies came out an entire month ago. It wasn’t on my radar until this week, which is why I included it in our weekly new games round-up, but... | Read more »
SwitchArcade Round-Up: ‘MLB The Show 24’...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for March 15th, 2024. We’re closing out the week with a bunch of new games, with Sony’s baseball franchise MLB The Show up to bat yet again. There are several other interesting games to... | Read more »
Steam Deck Weekly: WWE 2K24 and Summerho...
Welcome to this week’s edition of the Steam Deck Weekly. The busy season has begun with games we’ve been looking forward to playing including Dragon’s Dogma 2, Horizon Forbidden West Complete Edition, and also console exclusives like Rise of the... | Read more »
Steam Spring Sale 2024 – The 10 Best Ste...
The Steam Spring Sale 2024 began last night, and while it isn’t as big of a deal as say the Steam Winter Sale, you may as well take advantage of it to save money on some games you were planning to buy. I obviously recommend checking out your own... | Read more »
New ‘SaGa Emerald Beyond’ Gameplay Showc...
Last month, Square Enix posted a Let’s Play video featuring SaGa Localization Director Neil Broadley who showcased the worlds, companions, and more from the upcoming and highly-anticipated RPG SaGa Emerald Beyond. | Read more »
Choose Your Side in the Latest ‘Marvel S...
Last month, Marvel Snap (Free) held its very first “imbalance" event in honor of Valentine’s Day. For a limited time, certain well-known couples were given special boosts when conditions were right. It must have gone over well, because we’ve got a... | Read more »
Warframe welcomes the arrival of a new s...
As a Warframe player one of the best things about it launching on iOS, despite it being arguably the best way to play the game if you have a controller, is that I can now be paid to talk about it. To whit, we are gearing up to receive the first... | Read more »
Apple Arcade Weekly Round-Up: Updates an...
Following the new releases earlier in the month and April 2024’s games being revealed by Apple, this week has seen some notable game updates and events go live for Apple Arcade. What The Golf? has an April Fool’s Day celebration event going live “... | Read more »

Price Scanner via MacPrices.net

Apple Education is offering $100 discounts on...
If you’re a student, teacher, or staff member at any educational institution, you can use your .edu email address when ordering at Apple Education to take $100 off the price of a new M3 MacBook Air.... Read more
Apple Watch Ultra 2 with Blood Oxygen feature...
Best Buy is offering Apple Watch Ultra 2 models for $50 off MSRP on their online store this week. Sale prices available for online orders only, in-store prices may vary. Order online, and choose... Read more
New promo at Sams Club: Apple HomePods for $2...
Sams Club has Apple HomePods on sale for $259 through March 31, 2024. Their price is $40 off Apple’s MSRP, and both Space Gray and White colors are available. Sale price is for online orders only, in... Read more
Get Apple’s 2nd generation Apple Pencil for $...
Apple’s Pencil (2nd generation) works with the 12″ iPad Pro (3rd, 4th, 5th, and 6th generation), 11″ iPad Pro (1st, 2nd, 3rd, and 4th generation), iPad Air (4th and 5th generation), and iPad mini (... Read more
10th generation Apple iPads on sale for $100...
Best Buy has Apple’s 10th-generation WiFi iPads back on sale for $100 off MSRP on their online store, starting at only $349. With the discount, Best Buy’s prices are the lowest currently available... Read more
iPad Airs on sale again starting at $449 on B...
Best Buy has 10.9″ M1 WiFi iPad Airs on record-low sale prices again for $150 off Apple’s MSRP, starting at $449. Sale prices for online orders only, in-store price may vary. Order online, and choose... Read more
Best Buy is blowing out clearance 13-inch M1...
Best Buy is blowing out clearance Apple 13″ M1 MacBook Airs this weekend for only $649.99, or $350 off Apple’s original MSRP. Sale prices for online orders only, in-store prices may vary. Order... Read more
Low price alert! You can now get a 13-inch M1...
Walmart has, for the first time, begun offering new Apple MacBooks for sale on their online store, albeit clearance previous-generation models. They now have the 13″ M1 MacBook Air (8GB RAM, 256GB... Read more
Best Apple MacBook deal this weekend: Get the...
Apple has 13″ M2 MacBook Airs available for only $849 today in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty is included,... Read more
New 15-inch M3 MacBook Air (Midnight) on sale...
Amazon has the new 15″ M3 MacBook Air (8GB RAM/256GB SSD/Midnight) in stock and on sale today for $1249.99 including free shipping. Their price is $50 off MSRP, and it’s the lowest price currently... Read more

Jobs Board

Early Preschool Teacher - Glenda Drive/ *Appl...
Early Preschool Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Read more
Senior Software Engineer - *Apple* Fundamen...
…center of Microsoft's efforts to empower our users to do more. The Apple Fundamentals team focused on defining and improving the end-to-end developer experience in Read more
Relationship Banker *Apple* Valley Main - W...
…Alcohol Policy to learn more. **Company:** WELLS FARGO BANK **Req Number:** R-350696 **Updated:** Mon Mar 11 00:00:00 UTC 2024 **Location:** APPLE VALLEY,California Read more
Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill WellSpan Medical Group, York, PA | Nursing | Nursing Support | FTE: 1 | Regular | Tracking Code: 200555 Apply Now Read more
Early Preschool Teacher - Glenda Drive/ *Appl...
Early Preschool Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.