INIT.c

/*------------------------------------------------------------------------------
*
*   Macintosh Developer Technical Support
*
*   Sample Control Panel Device and INIT Combination
*
*   Program:    INIT - CDEV
*   File:       INIT.c  -   C Source
*
*   Copyright © 1990 Apple Computer, Inc.
*   All rights reserved.
*
*-----------------------------------------------------------------------------*/
 
#include <Common.h>
#include <SAGlobals.h>
#include <Processes.h>
 
 
/*------------------------------------------------------------------------------
    Global variables
------------------------------------------------------------------------------*/
 
CommonGlobalsRec    gCommonGlobals;     /* Globals common to INIT and CDEV  */
SessionRecord       gSessionRecord;     /* Data for PPC communications      */
long                gTimeLastBeeped;    /* Tickcount when we last beeped    */
 
 
/*------------------------------------------------------------------------------
    Procedure Prototypes and External Routines
------------------------------------------------------------------------------*/
 
        OSErr   c_Install(void);
        OSErr   DoPPCInit(void);
        OSErr   ReadPreferences(void);
        void    c_SystemTask(void);
        OSErr   OpenAPort(void);
 
pascal  void    InformCompProc(PPCParamBlockPtr ppb);
pascal  void    WriteCompProc(PPCParamBlockPtr ppb);
pascal  void    EndCompProc(PPCParamBlockPtr ppb);
 
extern  void        a_SetA5Ref(A5RefType);
extern  A5RefType   a_GetA5Ref(void);
 
 
/*------------------------------------------------------------------------------
 
    OSErr c_Install(void)
 
    This is the C portion of the installation process. It is called from the
    assembly installation code at INIT time.
 
    We're going to be making all of our memory allocation from the System
    zone. There's really no other choice. We're an INIT; we don't have our own
    zone to get memory from. So we save off whatever the current zone is
    (which very well may be the System Zone -- what do I know?), and set the
    zone to the System Zone.
 
    Next, we get the memory for our globals. We call MakeA5World to do this,
    which is a modification of the routine described in Technote #256. If we
    don't get our memory, restore the zone and return to our caller with an
    "out of memory" error. If we do get the memory, then save off the old A5
    and set A5 to be our A5. Also, save our A5 reference so that when our
    SystemTask patch runs, it knows what value to use for A5.
 
    After that, we do some more initialization specific to the function of
    this INIT (which is to beep, annoying the **** out of the user). We call
    two subroutines for this, which do PPCToolbox setup, and read our
    preferences file.
 
    Finally, we clean up and leave. If any errors occured, we gett rid of the
    buffer for our globals. We then restore A5 and the zone, and return any
    error numbers.
 
------------------------------------------------------------------------------*/
 
OSErr c_Install(void)
{
    OSErr       err;                /* For any routines that return errors */
    THz         oldZone;            /* Holds the current zone while we switch
                                       over to the System zone for our memory
                                       allocation. */
    A5RefType   A5Ref;              /* The reference to our globals. This is
                                       created by the SAGlobals package. */
    long        oldA5;              /* Holds the current A5 when we switch
                                       over to the A5 for our own globals. */
 
    err = noErr;
    oldZone = GetZone();
    SetZone(SystemZone());
 
    MakeA5World(&A5Ref);
    if (A5Ref == nil) {
        SetZone(oldZone);
        return(memFullErr);
    }
    a_SetA5Ref(A5Ref);
    oldA5 = SetA5World(A5Ref);
 
    (void) DoPPCInit();
    (void) ReadPreferences();
 
    (void) SetA5(oldA5);
    SetZone(oldZone);
    return (err);
}
 
 
/*------------------------------------------------------------------------------
 
    OSErr   DoPPCInit(void)
 
    Check to see if we have PPCToolbox facilities. If so, initialize the
    PPCToolbox, open a port, and perform a PPCInform call on the port (this is
    done with the EndCompProc procedure down below).
 
    If there are any errors, we set "err" to the result code. However, we have
    to distinguish between hard and soft errors. If the PPCToolbox is not
    around, that is a soft error, so we make sure to clear "err" if Gestalt
    says PPCToolbox is not there. Likewise, if the PPCInform call reports a
    result of "1", that means the asynchronous call is pending, and is also a
    soft error, so we also clear "err" in that case. Otherwise, any errors are
    hard errors, and indicate some serious problems. In that case, we report
    the error to our caller so that it can abort the installation of the INIT.
 
------------------------------------------------------------------------------*/
 
OSErr   DoPPCInit(void)
{
    OSErr       err;                /* For any routines that return errors */
    long        gestaltResult;      /* Gestalt result. duh! */
 
    err = Gestalt(gestaltPPCToolboxAttr, &gestaltResult);
    if (!err) {
        err = PPCInit();
        if (!err) {
            err = OpenAPort();
            if (!err) {
                EndCompProc(&gSessionRecord.pb);
                err = gSessionRecord.pb.informParam.ioResult;
                if (err > 0) {
                    err = noErr;                /* cover up for soft errors */
                }
            }
        }
    } else
        err = noErr;                            /* cover up for soft errors */
 
    return (err);
}
 
 
/*------------------------------------------------------------------------------
 
    OSErr   ReadPreferences(void)
 
    See if we can read the preferences file. First, we see if the FindFolder
    routine is present with a call to Gestalt. If so, we look for the
    preferences file. If one exists, we open it up, and read the values into
    our public globals buffer (the preferences file is just an image of that
    buffer, so the values read right in).
 
    If the FindFolder routine doesn't exist on this machine, or the
    preferences file doesn't exists, or there was an error when reading the
    file, we go right ahead and use a set of hard-wired values. All errors  at
    this stage of the INIT are considered soft errors, so we clear "err".
 
    Finally, we set "gTimeLastBeeped" to be the current tickcount.
 
------------------------------------------------------------------------------*/
 
OSErr   ReadPreferences(void)
{
    OSErr       err;                /* For any routines that return errors */
    long        gestaltResult;      /* Gestalt result. duh! */
    Boolean     useDefaults;        /* Boolean that we use to determine if we
                                       were able to read the preferences file.
                                       If not, this is TRUE, and we use default
                                       values instead. */
    FSSpec      spec;               /* FSSpec for reading our preferences */
    short       refNum;             /* Refnum for our preferences file */
    long        amountToRead;       /* Number of bytes to read from pref. */
 
    useDefaults = true;
    err = Gestalt(gestaltFindFolderAttr, &gestaltResult);
    if ((err == noErr) && (gestaltResult != 0)) {
        err = FindFolder(kOnSystemDisk, kPreferencesFolderType,
                        kCreateFolder, &spec.vRefNum, &spec.parID);
        if (!err) {
            (void) PLstrcpy(spec.name, kPrefsFileName);
            err = FSpOpenDF(&spec, fsRdPerm, &refNum);
            if (!err) {
                amountToRead = sizeof(gCommonGlobals);
                err = FSRead(refNum, &amountToRead, (Ptr) &gCommonGlobals);
                if (!err) {
                    err = FSClose(refNum);
                    useDefaults = false;
                }
            }
        }
    }
 
    err = noErr;
 
    if (useDefaults) {
        gCommonGlobals.timesToBeep = 3;
        gCommonGlobals.beepInterval = 60*60;    /* start off once a min. */
    }
    gTimeLastBeeped = TickCount();
 
    return(err);
}
 
 
/*------------------------------------------------------------------------------
 
    void c_SystemTask(void)
 
    This routine is the meat of the SystemTask() patch. It is called from
    a_SystemTask, an assembly routine that saves the registers, calls us,
    restores the registers, and then calls the real SystemTask() in a way that
    doesn't constitute a tail patch.
 
    All we do here is see if an appropriate amount of time has passed. If so,
    we beep a certain number of times. Otherwise, we do nothing.
 
------------------------------------------------------------------------------*/
 
void c_SystemTask(void)
{
    long                oldA5;
    short               loopy;
 
    oldA5 = SetA5World(a_GetA5Ref());
 
    if (TickCount() >= gTimeLastBeeped + gCommonGlobals.beepInterval) {
        for (loopy = 0; loopy < gCommonGlobals.timesToBeep; ++loopy) {
            SysBeep(5);
        }
        gTimeLastBeeped = TickCount();
    }
 
    (void) SetA5(oldA5);
}
 
 
/*------------------------------------------------------------------------------
 
    OSErr OpenAPort(void)
 
    Used to open a PPC port. We identify ourselves using the portName record.
    First, we give ourselves a name that will show up in the PPCBrowser
    (portName.name). We then give ourselves an identify to other processes. We
    can do this either by name or by creator/type signatures. In our case, we
    choose creator/type ('INCD'/'INIT').
 
    We then set up for a PPCOpen call. We are making a synchronous call, so we
    don't need a completion routine. ServiceType and resFlag are set to
    required values (per Inside Mac). We make the INIT not visible over the
    network. However, if we did, it's possible to talk to it from another
    Macintosh. If the other Mac know our communication protocol, it could
    change our beep parameters out from under us. Next, we point to the name
    record we want to use for our port, and use the default location name
    (which identifies our computer to other computers on the network).
 
    Finally, we make the PPCOpen call synchronously, and return any errors.
 
------------------------------------------------------------------------------*/
 
OSErr OpenAPort(void)
{
    gSessionRecord.portName.nameScript = GetScriptManagerVariable(smSysScript);
    (void) PLstrcpy(gSessionRecord.portName.name, "\pBG Beeper");
    gSessionRecord.portName.portKindSelector = ppcByCreatorAndType;
#if GENERATING68K
    // Universal Interfaces 2.0
    gSessionRecord.portName.u.port.portCreator = kCreator;
    gSessionRecord.portName.u.port.portType = 'INIT';
#else
    gSessionRecord.portName.u.port.creator = kCreator;
    gSessionRecord.portName.u.port.type = 'INIT';
#endif
 
    gSessionRecord.pb.openParam.ioCompletion = nil;
    gSessionRecord.pb.openParam.serviceType = ppcServiceRealTime;
    gSessionRecord.pb.openParam.resFlag = 0;
    gSessionRecord.pb.openParam.networkVisible = false;
    gSessionRecord.pb.openParam.portName = &gSessionRecord.portName;
    gSessionRecord.pb.openParam.locationName = nil; // use the default location
 
    return(PPCOpen(&gSessionRecord.pb.openParam, false));
}
 
 
/*------------------------------------------------------------------------------
 
    pascal void InformCompProc(PPCParamBlockPtr ppb)
 
    After we open a PPC port, we make an asynchronous PPCInform call on it.
    When someone wants to talk to us, the completion routine for the PPCInform
    call gets called. That's this routine. When we get called, it means the
    CDEV wants to talk to us. We find out what it's asking by looking at the
    "userData" field, in which the CDEV has placed a message number. We only
    know kGetCommonGlobalsPtr, so we respond to that by returning the pointer
    to our public globals. After filling out the parameter block and i/o
    buffer, we make a PPCWrite call. We are called at interrupt time (we are a
    completion routine, remember), so we make the PPCWrite call
    asynchronously. We provide a completion routine that gets executed when
    the call completes.
 
    Because this is a completion routine, it is declared as using the Pascal
    calling convention.
 
------------------------------------------------------------------------------*/
 
pascal void InformCompProc(PPCParamBlockPtr ppb)
{
    OSErr               err;
    GetCommonGlobalsPtr myBuffer;
 
    myBuffer = (GetCommonGlobalsPtr) ((SessionPtr)ppb)->buffer;
 
    ppb->writeParam.ioCompletion    = (PPCCompProcPtr) WriteCompProc;
    ppb->writeParam.bufferLength    = 0;
    ppb->writeParam.bufferPtr       = (Ptr) myBuffer;
    ppb->writeParam.more            = false;
 
    switch (ppb->informParam.userData) {
        case kGetCommonGlobalsPtr:
            ppb->writeParam.bufferLength    = sizeof(GetCommonGlobalsRecord);
            myBuffer->commonGlobalsAddress  = &gCommonGlobals;
            break;
    }
 
    err = PPCWriteAsync(&ppb->writeParam);
}
 
 
/*------------------------------------------------------------------------------
 
    pascal void WriteCompProc(PPCParamBlockPtr ppb)
 
    When our PPCWrite routine completes, the PPCToolbox calls this completion
    routine. All this is responsible for it closing the session on this end by
    calling PPCEnd.
 
    Because this is a completion routine, it is declared as using the Pascal
    calling convention.
 
------------------------------------------------------------------------------*/
 
pascal void WriteCompProc(PPCParamBlockPtr ppb)
{
    OSErr           err;
 
    ppb->endParam.ioCompletion = (PPCCompProcPtr) EndCompProc;
 
    err = PPCEndAsync(&ppb->endParam);
}
 
 
/*------------------------------------------------------------------------------
 
    pascal void EndCompProc(PPCParamBlockPtr ppb)
 
    After the PPCEnd call completes, the PPCToolbox calls this completion
    routine. This routine is responsible for setting us up for receiving more
    PPC communications requestions. We also call this routine when we want to
    set ourselves for the very first PPCInform call.
 
    Because this is a completion routine, it is declared as using the Pascal
    calling convention.
 
------------------------------------------------------------------------------*/
 
pascal void EndCompProc(PPCParamBlockPtr ppb)
{
    OSErr           err;
 
    ppb->informParam.ioCompletion   = (PPCCompProcPtr) InformCompProc;
    ppb->informParam.autoAccept     = true;
    ppb->informParam.portName       = &((SessionPtr)ppb)->portName;
    ppb->informParam.locationName   = &((SessionPtr)ppb)->locationName;
    ppb->informParam.userName       = &((SessionPtr)ppb)->userName;
 
    err = PPCInformAsync(&ppb->informParam);
}