IPCLister.main.c

/*
    File:       IPCLister.main.c
 
    Contains:   This shell can be very handy for debugging and testing things   
                Add menu items, add dialogs, add controls, or whatever else you need    
                This sample contains basic application startup and event loop handling,     
                add more features as your needs increase.   
                This sample is High Level Event aware, so you can send and receive AppleEvent   
                from this application   
    
    Written by: C.K. Haun   
 
    Copyright:  Copyright © 1992-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                7/21/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
#include "IPCListCode.h"
#include <TextUtils.h>
#include <Types.h>
#include <memory.h>
#include <Packages.h>
#include <Errors.h>
#include <quickdraw.h>
#include <fonts.h>
#include <dialogs.h>
#include <windows.h>
#include <menus.h>
#include <events.h>
#include <OSEvents.h>
#include <Desk.h>
#include <diskinit.h>
#include <OSUtils.h>
#include <resources.h>
#include <toolutils.h>
#include <AppleEvents.h>
#include <EPPC.h>
#include <GestaltEqu.h>
#include <PPCToolbox.h> 
#include <Processes.h>
#include <Balloons.h>
#include <ALiases.h>
#include <Processes.h>
#include <StandardFile.h>
#include <Folders.h>
#include <AppleTalk.h> 
#include <Lists.h>
/* prototypes */
void InitalizeApp(void);
void DoDiskEvents(long dinfo);                              /* hi word is error code, lo word is drive number */
void DrawMain(WindowPtr drawIt);
Boolean DoSelected(long val);
void InitAEStuff(void);
void DoHighLevel(EventRecord *AERecord);
void DoDaCall(MenuHandle themenu, long theit);
pascal OSErr AEOpenHandler(AppleEvent *messagein, AppleEvent *reply, long refIn);
pascal OSErr AEOpenDocHandler(AppleEvent *messagein, AppleEvent *reply, long refIn);
pascal OSErr AEPrintHandler(AppleEvent *messagein, AppleEvent *reply, long refIn);
pascal OSErr AEQuitHandler(AppleEvent *messagein, AppleEvent *reply, long refIn);
void SampleHelpDialog(void);
void RefreshMacList(ListHandle theList);
void DoIPCListAndFillList(Cell *theCell, ListHandle theList);
void SetContextSwitch(Boolean how);
OSErr myGetMyZone(char *nameBack);
 
 
/*  external */
extern void _DataInit();                                    /* this is the C initialization code */
extern OSErr myIPCListPorts(short theStartIndex, short theRequestCount, short *theActualCount, Str32 theObjStr, Str32 theZoneStr,
                            PortInfoArrayPtr thePortInfoBufferPtr);
extern OSErr GetAllPPCAbleMachines(MPPParamBlock *paramblk);
extern OSErr GetPortsOnMachine();
extern MPPParamBlock paramblk;
 
 
 
enum  {
    kMBarID = 128
};
enum  {
    kAppleMenu = 128, kFileMenu, kEditMenu, kToolsMenu
};
enum  {
    kResumeMask = 1,  /* bit of message field for resume vs. suspend */
    kSampHelp = 129, kAboutBox = 128, kHelpString = 128, kNewItem = 1, kOpenItem, kCloseItem,
    kSaveItem, kSaveAsItem, kFileBlank1,
        kPageSetupItem, kPrintItem, kFileBlank2, kQuitItem, 
        kBadSystem = 130, kGenStrings = 128, kMenuToggleStrings = 129,
        kMyModalKind = 1000, kMyDocumentKind
};
enum  {
    kItem1 = 1, kWaitDialog = 131
};
enum  {
    kCloseIt = 1, kOpenIt
};
/* string IDs for window */
enum  {
    kSayMacs = 2, kSayPorts = 3
};
 
/* some globals */
MenuHandle gAppleMenuHandle, gFileMenuHandle, gEditMenuHandle, gToolMenuHandle;
Boolean gQuit, gInBackground;
EventRecord gERecord;
AEDesc gTheAddress;
ProcessSerialNumber gOurSN;
short gHelpItem;
/* this is our global to tell us if a movable modal is up*/
unsigned long gMySleep = 30;
ListHandle gMacList, gPortsList;
 
#ifdef powerc
   QDGlobals    qd;
#endif
 
#pragma segment Main
void main()
{
    WindowPtr twindow;
    //UnloadSeg((Ptr)_DataInit);                              /* throw out setup code */
    InitalizeApp();
    //UnloadSeg((Ptr)InitalizeApp);   /* get rid of my initialization code */
    do {
        
        WaitNextEvent(everyEvent, &gERecord, gMySleep, nil);
        switch (gERecord.what) {
            ProcPtr drawProc;
            short tempKind;
            case nullEvent:
                /* no nul processing in this sample */
                break;
                
            case updateEvt:
                tempKind = ((WindowPeek)gERecord.message)->windowKind;
                /* Mkae sure it's my window before I jump through the refCon */
                /* Why, since DA's have they're own layer in 7.0? */
                /* BECAUSE there are other people in the universe who will */
                /* add things to your windowList.BalloonWriter, for example, */
                /* so you still need to be careful */
                if (tempKind == kMyModalKind || tempKind == kMyDocumentKind) {
                    /* get the drawing proc from the refCon */
                    drawProc = (ProcPtr)GetWRefCon((WindowPtr)gERecord.message);
                    /* jump to it */
                    drawProc((WindowPtr)gERecord.message);
                }
                break;
                
            case mouseDown:
                /* first see where the hit was */
                
                switch (FindWindow(gERecord.where, &twindow)) {
                    
                    case inDesk:                            /* if they hit in desk, then the process manager */
                        break;                              /* will switch us out, we don't need to do anything */
                        
                    case inMenuBar:
                        /* let Help and Application menus happen always */
                        DoSelected(MenuSelect(gERecord.where));
                        break;
                        
                    case inSysWindow:
                        /* pass to the system */
                        SystemClick(&gERecord, twindow);
                        break;
                        
                    case inContent:
                        /* Handle content and control clicks here */
                        /* If they clicked in the content region of a back window, */
                        /* bring that window forward */
                        if (twindow != FrontWindow()) {                            
                            SelectWindow(twindow);          /* select the window */
                            SetPort(twindow);                            
                        } else {
                            /* control tracking or whatever. */
                            /* Here we have a couple of lists, so we'll track */
                            /* them */
                            Rect boundRect;
                            Point locPoint = gERecord.where;
                             GlobalToLocal(&locPoint);
                            /* first see if it's in eiher of our Lists */
                            boundRect = (*gMacList)->rView;
                            /* expand to include the scroll bear */
                            boundRect.right += 15;
                            if (PtInRect(locPoint, &boundRect)) {
                                Cell currentCell =  {
                                    0, 0
                                };
                                Cell theCell =  {
                                    0, 0
                                };
                                /* first, get the current cell */
                                LGetSelect(true, &currentCell, gMacList);
                                if (LClick(locPoint, nil, gMacList)) {                                    
                                    /* if it's in this list, switch port list */
                                    if (LGetSelect(true, &theCell, gMacList)) {
                                        /* this is where you'd do something special for a double-click */
                                        /* in this example I just fill the other list in agin */
                                        DoIPCListAndFillList(&theCell, gPortsList);
                                    }
                                } else {
                                    /* single click, but maybe they changed things */
                                    LGetSelect(true, &theCell, gMacList);
                                    if (theCell.v != currentCell.v)
                                        DoIPCListAndFillList(&theCell, gPortsList);
                                }
                            } else {
                                boundRect = (*gPortsList)->rView;
                                /* expand to include the scroll bear */
                                boundRect.right += 15;
                                
                                if (PtInRect(locPoint, &boundRect)) {
                                    LClick(locPoint, nil, gPortsList);
                                    /* if it's in this list, I just don't care atall */
                                } else {
                                    /* normal control tracking goes here, commented out */
                                    /* since I'm not using it today */
                                    /*
                                    if (FindControl(locPoint, twindow, &returnedControl)) {
                                    if (TrackControl(returnedControl, locPoint, nil)) {
                                    
                                    }
                                    } */
                                }
                            }
                        }
                        break;
                        
                    case inDrag:
                        DragWindow(twindow, gERecord.where, &qd.screenBits.bounds);
                        break;
                        
                    case inGrow:
                        /* Call GrowWindow here if you have a grow box */
                        break;
                        
                    case inGoAway:
                        /* Click in Close box */
                        break;
                }
            case mouseUp:
                /* don't care */
                break;
                /* same action for key or auto key */
            case keyDown:
            case autoKey:
                if (gERecord.modifiers & cmdKey)
                    DoSelected(MenuKey(gERecord.message & charCodeMask));
                break;
                
            case keyUp:
                /* don't care */
                break;
                
            case diskEvt:
                /* I don't do anything special for disk events, this just passes them */
                /* to a function that checks for an error on the mount */
                DoDiskEvents(gERecord.message);
                break;
                
            case activateEvt:
                /* not doing anything on activates */
                break;
                
            case networkEvt:
                /* don't care */
                break;
                
            case driverEvt:
                /* don't care */
                break;
                
            case app4Evt:
                /* If we're switching layers and I have my MModal up, I want to */
                /* change the state of the control */
                switch ((gERecord.message >> 24) & 0x0FF) {     /* high byte of message */
                    
                    case suspendResumeMessage:              /* suspend/resume is also an activate/deactivate */
                        gInBackground = (gERecord.message & kResumeMask) == 0;
                        /* set dim/enable on the control */
                        /* make sure hiliting is right */
                        SetContextSwitch(gInBackground);
                        break;
                        
                }
                break;
                
                /* This dispatches high level events (AppleEvents, for example) */
                /* to our dispatch routine.This is NEW in the event loop for */
                /* System 7 */
            case kHighLevelEvent:
                DoHighLevel(&gERecord);
                break;
            default:
                break;
                
        }
    }
            while (gQuit != true);
}
 
/* DoDaCall opens the requested DA.It's here as a seperate routine if you'd */
/* like to perform some action or just know when a DA is opened in your */
/* layer.Can be handy to track memory problems when a DA is opened */
/* with an Option-open */
void DoDaCall(MenuHandle themenu, long theit)
{
    long qq;
    Str255 DAname;
    GetMenuItemText(themenu, theit, DAname);
    qq = OpenDeskAcc(DAname);
}
 
/* end DoDaCall */
 
/* DoDiskEvents just checks the error code from the disk mount, */
/* and puts up the 'Format' dialog (through DIBadMount) if need be */
/* You can do much more here if you care about what disks are */
/* in the drive */
void DoDiskEvents(long dinfo)                               /* hi word is error code, lo word is drive number */
{
    short hival, loval, tommy;
    Point fredpoint =  {
        40, 40
    };
    hival = HiWord(dinfo);
    loval = LoWord(dinfo);
    if (hival != noErr)                                     /* something happened */ {
        tommy = DIBadMount(fredpoint, dinfo);
    }
}
 
/* draws my window.Pretty simple */
void DrawMain(WindowPtr drawIt)
{
    WindowPtr tempWP;
    Str255 words;
    Rect tempRect;
    GetPort(&tempWP);
    BeginUpdate(drawIt);
    SetPort(drawIt);
    EraseRect(&drawIt->portRect);
    LUpdate(drawIt->visRgn, gMacList);
    tempRect = (*gMacList)->rView;
    InsetRect(&tempRect, -2, -1);
    FrameRect(&tempRect);
    MoveTo(tempRect.left, tempRect.top - 2);
    GetIndString(words, kGenStrings, kSayMacs);
    DrawString(words);
    /* other list */
    LUpdate(drawIt->visRgn, gPortsList);
    tempRect = (*gPortsList)->rView;
    InsetRect(&tempRect, -2, -1);
    FrameRect(&tempRect);
    MoveTo(tempRect.left, tempRect.top - 2);
    GetIndString(words, kGenStrings, kSayPorts);
    DrawString(words);
    
    EndUpdate(drawIt);
    SetPort(tempWP);
}
 
/* my menu action taker.It returns a Boolean which I usually ignore, but it */
/* mught be handy someday */
Boolean DoSelected(long val)
{
    short loval, hival;
    Boolean returnVal = false;
    loval = LoWord(val);
    hival = HiWord(val);
    
    switch (hival) {                                        /* switch off the menu number selected */
        case kAppleMenu:                                    /* Apple menu */
            if (loval != 1) {                               /* if this was not About, it's a DA */
                DoDaCall(gAppleMenuHandle, loval);
            } else {
                Alert(kAboutBox, nil);                      /* do about box */
            }
            returnVal = true;
            break;
        case kFileMenu:                                     /* File menu */
            switch (loval) {
                case kQuitItem:
                    gQuit = true;                           /* onlyitem */
                    returnVal = true;
                    break;
                default:
                    break;
            }
            break;
            
        case kEditMenu:
            /* edit menu junk */
            /* don't care */
            break;
            
        case kToolsMenu:
            /* add all your test stuff here */
            RefreshMacList(gMacList);
            break;
            
        case kHMHelpMenuID:                                 /* Defined in Balloons.h */
            /* I only care about this item.If anything else is returned here, I don't know what */
            /* it is, so I leave it alone.Remember, the Help Manager chapter says that */
            /* Apple reserves the right to add and change things in the Help menu */
            if (loval == gHelpItem)
                SampleHelpDialog();
            break;
            
    }
    HiliteMenu(0);
    return(returnVal);
}
 
/* InitAEStuff installs my appleevent handlers */
void InitAEStuff(void)
{    
    OSErr aevtErr = noErr;
    long aLong = 0;
    Boolean gHasAppleEvents = false;
    /* Check this machine for AppleEvents.  If they are not here (ie not 7.0)
    *   then we exit */
    gHasAppleEvents = (Gestalt(gestaltAppleEventsAttr, &aLong) == noErr);
    /* The following series of calls installs all our AppleEvent Handlers.
    *   These handlers are added to the application event handler list that 
    *   the AppleEvent manager maintains.  So, whenever an AppleEvent happens
    *   and we call AEProcessEvent, the AppleEvent manager will check our
    *   list of handlers and dispatch to it if there is one.
    */
    if (gHasAppleEvents) {
         aevtErr = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, 
             NewAEEventHandlerProc(AEOpenHandler),0, false);
         aevtErr = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, 
             NewAEEventHandlerProc(AEOpenDocHandler),0, false);
         aevtErr = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, 
             NewAEEventHandlerProc(AEQuitHandler), 0, false);
         aevtErr = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, 
             NewAEEventHandlerProc(AEPrintHandler),0, false);
             
         if (aevtErr)  ExitToShell();
         
 
       } 
    else ExitToShell();
    
}
/* end InitAEStuff */
 
 
/* I'm not doing error handling in this sample for clarities sake, you should. Hah, */
/* easy for me to say, huh? */
void DoHighLevel(EventRecord *AERecord)
{
    
    AEProcessAppleEvent(AERecord);
    
}
 
/* end DoHighLevel */
 
/* This is the standard Open Application event.*/
pascal OSErr AEOpenHandler(AppleEvent *messagein, AppleEvent *reply, long refIn)
{
#pragma unused (messagein,reply,refIn)
 
    Rect lRect;
    Rect lRect2 =  {
        0, 0, 0, 1
    };
    Point cp =  {
        0, 0
    };
    
    short theSize;
    FontInfo theFont;
    typedef unsigned char Tuple[104];
    extern Tuple myRetBuff[100];
    WindowPtr myWindow;
    /* we of course don't do anything here in this simple app */
    /* except open our window */
    myWindow = GetNewWindow(128, nil, (WindowPtr)-1);
    ((WindowPeek)myWindow)->windowKind = kMyDocumentKind;
    /* install drawing proc */
    SetWRefCon(myWindow, (long)DrawMain);
    
    /* add two lists */
    SelectWindow(myWindow);
    SetPort(myWindow);
    /* call the draw proc oncet */
    GetFontInfo(&theFont);
    theSize = (theFont.ascent + theFont.descent + theFont.leading) * 10;
    SetRect(&lRect, 10, 30, (myWindow->portRect.right / 2) - 15, 30 + theSize);
    gMacList = LNew(&lRect, &lRect2, cp, nil, myWindow, true, false, false, true);
    LActivate(true, gMacList);
    LSetDrawingMode(false, gMacList);
    (*gMacList)->listFlags = lDoVAutoscroll;
    SetRect(&lRect, (myWindow->portRect.right / 2) + 15, 30, (myWindow->portRect.right) - 15, 30 + theSize);
    
    gPortsList = LNew(&lRect, &lRect2, cp, nil, myWindow, true, false, false, true);
    LActivate(true, gPortsList);
    LSetDrawingMode(false, gPortsList);
    (*gPortsList)->listFlags = lDoVAutoscroll;
    /* While we're here we may as well find and add all the PPC capable macs in my zone */
    DrawMain(myWindow);
    
    RefreshMacList(gMacList);
    
    return(noErr);
    
}
 
/* end AEOpenHandler */
 
/* Open Doc, opens our documents.Remember, this can happen at application start AND */
/* anytime else.If your app is up and running and the user goes to the desktop, hilites one */
/* of your files, and double-clicks or selects Open from the finder File menu this event */
/* handler will get called. Which means you don't do any initialization of globals here, or */
/* anything else except open then doc.*/
/* SO-- Do NOT assume that you are at app start time in this */
/* routine, or bad things will surely happen to you. */
 
pascal OSErr AEOpenDocHandler(AppleEvent *messagein, AppleEvent *reply, long refIn)
{
#pragma unused (messagein,refIn,reply)
    /* we of course don't do anything here */
    return(errAEEventNotHandled);                           /* we have no docs, so no odoc events should come to us */
}
 
pascal OSErr AEPrintHandler(AppleEvent *messagein, AppleEvent *reply, long refIn)
{                                                           /* no printing handler in yet, so we'll ignore this */
    /* the operation is functionally identical to the ODOC event, with the additon */
    /* of calling your print routine.*/
#pragma unused (messagein,refIn,reply)
    /* we of course don't do anything here */
    return(errAEEventNotHandled);                           /* we have no docs, so no pdoc events should come to us */
}
 
/* Standard Quit event handler, to handle a Quit event from the Finder, for example.*/
/* ¥¥¥¥¥ DO NOT CALL EXITTOSHELL HERE ¥¥¥¥¥ or you will never have a happy life.*/
pascal OSErr AEQuitHandler(AppleEvent *messagein, AppleEvent *reply, long refIn)
{
#pragma unused (messagein,refIn,reply)
    
    /* prepQuit sets the Stop flag for us.It does _NOT_ quit, you */
    /* should NEVER quit from an AppleEvent handler.Calling */
    /* ExitToShell here would blow things up */
    gQuit = true;
    return(noErr);
}
 
/* This is my sample help dialog.Does not do anything, expand as you need */
void SampleHelpDialog(void)
{
    DialogPtr tdial = GetNewDialog(kSampHelp, nil, (WindowPtr)-1);
    short itemhit = 0;
    while (itemhit != 1) {
        ModalDialog(nil, &itemhit);
    }
    DisposeDialog(tdial);
}
 
 
/* myGetMyZone returns (oh, you guessed!) a zone name string */
OSErr myGetMyZone(char *nameBack)
{
    OSErr myErr = noErr;
    XPPParamBlock *theParamPtr = (XPPParamBlock *)NewPtrClear(sizeof(XPPParamBlock));
    theParamPtr->XCALL.xppTimeout = 3;
    theParamPtr->XCALL.xppRetry = 4;
    theParamPtr->XCALL.zipBuffPtr = nameBack;
    theParamPtr->XCALL.zipInfoField[1] = 0;
    theParamPtr->XCALL.zipInfoField[2] = 0;
    myErr = GetMyZone(theParamPtr, false);
    DisposePtr((Ptr)theParamPtr);
    return(myErr);
}
 
/* RefreshMacList re-scans the current zone for all macs, and */
/* adds them to the list */
void RefreshMacList(ListHandle theList)
{
    register qq;
    Cell cp =  {
        0, 0
    };
    /* This takes a while here at Apple (lotsa machines) so I'll put up a wait dialog */
    DialogPtr theDial = GetNewDialog(kWaitDialog, nil, (WindowPtr)-1);
    DrawDialog(theDial);
    /* this takes a while */
    SetCursor(*GetCursor(watchCursor));
    /* Delete all the current list entries quickly */
    LDelRow(32000, 0, theList);
    /* fill in the list of machines */
    GetAllPPCAbleMachines(&paramblk);
    /* loop through all the machines in this zone and add 'em to the list */
    for (qq = 0; qq < paramblk.NBP.parm.Lookup.numGotten; qq++) {
        EntityName myEntityName;
        AddrBlock myAddrBlock;   
        
        LAddRow(1, qq, theList);    /* a new item, add a row for it */
        cp.v = qq;
        /* get the info out of the NBP record.  It's easier than calculating the offsets */
        /* yerself */
        NBPExtract((Ptr)&myRetBuff, paramblk.NBP.parm.Lookup.numGotten, qq + 1, &myEntityName, &myAddrBlock);
        /* and add this name to the list */
        LAddToCell((&myEntityName.objStr[1]), myEntityName.objStr[0], cp, theList);
    }
    /* kill my wait dialog */
    DisposeDialog(theDial);
    InitCursor();
}
 
/* DoIPCListAndFillList calls IPCListPorts for the machine name selected, and  */
/* fills our list with the ports on the other machine */
void DoIPCListAndFillList(Cell *theCell, ListHandle theList)
{
    PortInfoArrayPtr thePorts;
    short returnedCount;
    OSErr myErr = noErr;
    Cell cp =  {
        0, 0
    };
    EntityName myEntityName;
    AddrBlock myAddrBlock;                                  /* myAddrBlock: AddrBlock; */
    register qq;
    SetCursor(*GetCursor(watchCursor));
    /* I want to get a maximum of 30 ports, so allocate a buffer that big */    
    thePorts = (PortInfoArrayPtr)NewPtrClear(sizeof(PortInfoRec) * 30);
    /* extract the machine name and zone name based on the cell they clicked  */
    /* on and the buffer we got from GetAllPPCAbleMachines */
    NBPExtract((Ptr)&myRetBuff, paramblk.NBP.parm.Lookup.numGotten, theCell->v + 1, &myEntityName, &myAddrBlock);
    /* call our IPCList routine */
    myErr = myIPCListPorts(0, 30, &returnedCount, myEntityName.objStr, myEntityName.zoneStr, thePorts);
    if(myErr == noErr){
        /* now add the names to the list */
        LDelRow(32000, 0, theList);
        for (qq = 0; qq < returnedCount; qq++) {
            LAddRow(1, qq, theList);
            cp.v = qq;          
            LAddToCell(&(((PortInfoPtr)thePorts)->name.name[1]), thePorts->name.name[0], cp, theList);
            thePorts = thePorts + 1;            
        }
    }
    DisposePtr((Ptr)thePorts);
    InitCursor();
}
 
/* SetContextSwitch does what I need to do to go into/come out of */
/* the backgrounmd.  In this example, all I do is set the lists */
void SetContextSwitch(Boolean how)
{
how = (how ? false:true); /* don't ask, The how is kinda backwards */
    LActivate(how,gMacList);
    LActivate(how,gPortsList);
}
 
 
#pragma segment Init
void InitalizeApp(void)
{
    MenuHandle helpHandle;
    Handle myMenu;
    StringHandle helpString;
    short count;
    long vers;
    MaxApplZone();
    InitGraf((Ptr)&qd.thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(nil);
    InitCursor();
    /* Check system version */
    Gestalt(gestaltSystemVersion, &vers);
    vers = (vers >> 8) & 0xf;                               /* shift result over and mask out major version number */
    if (vers < 7) {
        StopAlert(kBadSystem, nil);
        ExitToShell();
    }
    PPCInit();
    InitAEStuff();
    
    /* set up my menu junk */
    myMenu = GetNewMBar(kMBarID);
    SetMenuBar(myMenu);
    gAppleMenuHandle = GetMenuHandle(kAppleMenu);
    gFileMenuHandle = GetMenuHandle(kFileMenu);
    gEditMenuHandle = GetMenuHandle(kEditMenu);
    gToolMenuHandle = GetMenuHandle(kToolsMenu);
    AppendResMenu(gAppleMenuHandle, 'DRVR');
    /* now install my Help menu item in the Help Manager's menu */
    HMGetHelpMenuHandle(&helpHandle);                       /* Get the Hlpe menu handle */
    count = CountMItems(helpHandle);                        /* How many items are there? */
    helpString = GetString(kHelpString);                    /* get my help string */
    DetachResource((Handle)helpString);                             /* detach it */
    HNoPurge((Handle)helpString);
    MoveHHi((Handle)helpString);
    HLock((Handle)helpString);
    InsertMenuItem(helpHandle,*helpString, count + 1);       /* insert my item in the Help menu */
    gHelpItem = CountMItems(helpHandle);                    /* The number of the item */
    
    DrawMenuBar();
    GetCurrentProcess(&gOurSN);                             /* Get our process serial number for later use, if needed */
    
}