PatchPowerOff.c

/*
**  Apple Macintosh Developer Technical Support
**
**  Sample code demonstrating how to patch PowerOff key
**
**  by Brian Bechtel, Apple Developer Technical Support
**
**  File:       PatchPowerOff.c
**
**  Copyright © 1995 Apple Computer, Inc.
**  All rights reserved.
**
**  You may incorporate this sample code into your applications without
**  restriction, though the sample code has been provided "AS IS" and the
**  responsibility for its operation is 100% yours.  However, what you are
**  not permitted to do is to redistribute the source as "DTS Sample 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 Code, but that you've made changes.
*/
#include <Gestalt.h>
#include <Resources.h>
#include <Errors.h>
#include <OSUtils.h>
#include <Traps.h>
#include <SetupA4.h>
#include <A4Stuff.h>
 
/* from technote 1017, "System 7.5.3"  Thanks to John Montbriand for pointing
 * out that I'd used a seed release of the technote, which didn't include in
 * the UPP definition any space for the result of the call.
 */
#ifdef PowerPC
enum {
    uppPowerOffProcInfo = kPascalStackBased
        | RESULT_SIZE(SIZE_CODE(sizeof(OSErr))) /* space for result */
        | STACK_ROUTINE_PARAMETER(1, kTwoByteCode)
        | STACK_ROUTINE_PARAMETER(2, kTwoByteCode)
};
#endif
 
/* constants used as parameters to the power off key routine */
enum {
    kPowerKey  = 0x7F,
    kDissablePwKy = 0x6B,
    kEnablePwKy = 0x00,
 
    kShutdownDlog = 0x7E,
    kNoDismiss = 0x00,
    kOneSecond = 0x70,
    kTenSeconds = 0x400
};
 
/* declaration for the power off key routine */
typedef pascal OSErr (*PwrKeyProc)(short item, short action);
 
/* for debugging */
#define assert(i) if ((i) DebugStr("\p;printf \"assertion failed file %s line %d\" __FILE__, __LINE__")
 
#define kINITid         0   /* matches the resID in the 68K Project preferences */
#define kICONid         128
 
pascal void ShowIcon7(short iconId, Boolean advance);
 
// local function declarations
void InitAlertPatch(void);
void ClearAlertPatch(void);
void asm AlertPatch(void);
OSErr DisablePowerOffKey(void);
void main(void);
 
static long     gOriginalTrapPtr = 0;
static Boolean  gAlertPatchIsInstalled = false;
 
/*
 * InstallAlertPatch
 * This routine patches the Alert() trap so that we can check for
 * the PowerOff alert and dismiss it before it comes up.
 */
void InitAlertPatch(void)
{
    if (gAlertPatchIsInstalled == false)
    {
        gOriginalTrapPtr = (long) NGetTrapAddress( _Alert, ToolTrap);
        NSetTrapAddress( (UniversalProcPtr)(AlertPatch), _Alert, ToolTrap);
        gAlertPatchIsInstalled = true;
    }
}
 
/*
 * ClearAlertPatch
 * This routine clears out the patch we installed in InstallAlertPatch.
 * Note: Never call this routine from within an extension; you don't know
 * what other patches may have been put on Alert() after you installed
 * your patch, and bad things would happen if you eliminate yourself from
 * the chain of patches...
 */
void ClearAlertPatch(void)
{
    if (gAlertPatchIsInstalled == true)
    {
        NSetTrapAddress( (UniversalProcPtr)(gOriginalTrapPtr), _Alert, ToolTrap);
        gAlertPatchIsInstalled = false;
    }
}
 
/*
 * AlertPatch
 * This patch checks to see if we are about to display alert -16500.  This
 * is the ALRT id used by the dialog which asks if you want to shutdown, etc.
 * If we are about to show that dialog, fake things up as if we have already
 * shown the dialog, and the user has pressed the Cancel button.  The result
 * is that the dialog is never shown, and no action is taken.  Just as if
 * the PowerOff code was never added to the system...
 * Call this routine only if the programmatic method (using the 'pwky' Gestalt
 * selector) fails.
 * This patch trashes register a0 and possibly register d0, but
 * these registers are trashed by Alert anyway.
 */
void asm AlertPatch(void)
{
// Check for proper alert.  If it's the trap we want to avoid,
// return to caller without actually executing the Alert trap.
// Set up the stack so it looks as if the user hit okay.
 
PatchPowerOff:
// test for specific PowerOff dialog id.  If we find it, return as if we hit cancel.
    cmpi.w  #-16500,8(sp)   // we want to avoid -16500
    bne     exitPatchPowerOff
// if it's the alert we want, return to caller without 
// executing the alert.  
// FUNCTION Alert ( alertID: INTEGER; filterProc: ProcPtr) : INTEGER;
// means that the stack looks like this:
//        sp + 0 -> return address (4 bytes)
//           + 4 -> filterProc (4 bytes)
//           + 8 -> alertID (2 bytes)
    movea.l (sp)+, a0   // the caller's return address
    lea     6(sp), sp   // clear off the parms put on by caller
    move.w  #2, (sp)    // tell the system we hit cancel. (2nd button = cancel)
    jmp     (a0)        // return to caller without actually calling Alert
    
exitPatchPowerOff:
    // call the original trap.
//First, set up a4 to access the old trap address
    jsr     SetUpA4     // puts old a4 into register d0
 
    lea     gOriginalTrapPtr, a0
 
    exg     d0, a4      // restore old value of a4
    
    movea.l (a0), a0
    jmp     (a0)    
}
 
/*
 * DisablePowerOffKey
 * This routine will either call the routine pointed to by the Gestalt
 * selector 'pwky', telling it to disable the power off key, or this
 * routine will return an error (usually telling you the Gestalt selector
 * is not installed.)
 */
OSErr DisablePowerOffKey(void) 
{
    OSErr err;
    PwrKeyProc pPwrKey;
    err = Gestalt('pwky', (long*) &pPwrKey);
    if ( (long) pPwrKey == nil )
        err = gestaltUndefSelectorErr;  // no proc ptr means no selector
    if ( err == noErr )
#ifdef PowerPC
        err = CallUniversalProc((UniversalProcPtr) pPwrKey,
            uppPowerOffProcInfo, kPowerKey, kDissablePwKy);
#else
        err = pPwrKey(kPowerKey, kDissablePwKy);
#endif
    return err;
}
 
 
/* main */
 
void main(void)
{
    long    oldA4;
    Handle  h;
    OSErr   err = noErr;
    
    #ifdef USE_DEBUGGER_CALLS
        Debugger();
    #endif
 
    /* set up our A4 context for _this file_ */
    oldA4 = SetCurrentA4();
    RememberA4();
        
    /* First try to disable the power key programmatically.
     * If that doesn't work, do a skanky hack.
     */
    if (DisablePowerOffKey() != noErr)
    {
        /* detach ourselves */
        h = Get1Resource('INIT', kINITid);
        if (h) DetachResource(h);
            
        InitAlertPatch();
    }
    ShowIcon7(kICONid, true);
    
    /* restore the a4 world */
    SetA4(oldA4);
}