#include "ctlspriv.h"

///////////////////////////////////////////////////////////////////////////////
// SUBCLASS.C -- subclassing helper functions
//
//      SetWindowSubclass
//      GetWindowSubclass
//      RemoveWindowSubclass
//      DefSubclassProc
//
//  This module defines helper functions that make subclassing windows safe(er)
// and easy(er).  The code maintains a single property on the subclassed window
// and dispatches various "subclass callbacks" to its clients a required.  The
// client is provided reference data and a simple "default processing" API.
//
// Semantics:
//  A "subclass callback" is identified by a unique pairing of a callback
// function pointer and an unsigned ID value.  Each callback can also store a
// single DWORD of reference data, which is passed to the callback function
// when it is called to filter messages.  No reference counting is performed
// for the callback, it may repeatedly call the SetWindowSubclass API to alter
// the value of its reference data element as desired.
//
// History:
//  26-April-96  francish        Created.
//
///////////////////////////////////////////////////////////////////////////////
//
// NOTE: Although a linked list would have made the code slightly simpler, this
// module uses a packed callback array to avoid unneccessary fragmentation.  fh
//
struct _SUBCLASS_HEADER;

typedef struct
{
    SUBCLASSPROC    pfnSubclass;        // subclass procedure
    UINT            uIdSubclass;        // unique subclass identifier
    DWORD           dwRefData;          // optional ref data

} SUBCLASS_CALL;

typedef struct _SUBCLASS_FRAME
{
    UINT uCallIndex;                    // index of next callback to call
    UINT uDeepestCall;                  // deepest uCallIndex on stack
    struct _SUBCLASS_FRAME *pFramePrev; // previous subclass frame pointer
    struct _SUBCLASS_HEADER *pHeader;   // header associated with this frame

} SUBCLASS_FRAME;

typedef struct _SUBCLASS_HEADER
{
    UINT uRefs;                         // subclass count
    UINT uAlloc;                        // allocated subclass call nodes
    UINT uCleanup;                      // index of call node to clean up
    DWORD dwThreadId;                   // thread id of window we are hooking
    SUBCLASS_FRAME *pFrameCur;          // current subclass frame pointer
    SUBCLASS_CALL CallArray[1];         // base of packed call node array

} SUBCLASS_HEADER;

#define CALLBACK_ALLOC_GRAIN (3)        // 1 defproc, 1 subclass, 1 spare

///////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK MasterSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam,
    LPARAM lParam);
LRESULT CallNextSubclassProc(SUBCLASS_HEADER *pHeader, HWND hWnd, UINT uMsg,
    WPARAM wParam, LPARAM lParam);

//-----------------------------------------------------------------------------
// RETAIL_ZOMBIE_MESSAGE_WNDPROC
//
// this macro controls the generation of diagnostic code for an error condition
// in the subclass code (see the SubclassDeath function below).
//
// commenting out this macro will zombie windows using DefWindowProc instead.
//
//-----------------------------------------------------------------------------
//#define RETAIL_ZOMBIE_MESSAGE_WNDPROC

#if defined(RETAIL_ZOMBIE_MESSAGE_WNDPROC) || defined(DEBUG)
#ifndef DEBUG
#pragma message("\r\nWARNING: disable retail ZombieWndProc before final release\r\n")
#endif
LRESULT ZombieWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
#else
#define ZombieWndProc DefWindowProc
#endif

//-----------------------------------------------------------------------------
// SubclassDeath
//
// this function is called if we ever enter one of our subclassing procedures
// without our reference data (and hence without the previous wndproc).
//
// hitting this represents a catastrophic failure in the subclass code.
//
// the function resets the wndproc of the window to a 'zombie' window
// procedure to avoid faulting.  the RETAIL_ZOMBIE_MESSAGE_WNDPROC macro above
// controls the generation of diagnostic code for this wndproc.
//
//-----------------------------------------------------------------------------
LRESULT SubclassDeath(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    //
    // WE SHOULD NEVER EVER GET HERE
    // if we do please find francish to debug it immediately
    //
    DebugMsg(TF_ALWAYS, TEXT("fatal: SubclassDeath in window %08X"), hWnd);

    //
    // if we are in a debugger, stop now regardless of break flags
    //
    __try { DebugBreak(); } __except(EXCEPTION_EXECUTE_HANDLER) {;}

    //
    // we call the outside world so prepare to deadlock if we have the critsec
    //
    ASSERTNONCRITICAL;

    //
    // in theory we could save the original wndproc in a separate property
    // but that just wastes memory for something that should never happen
    //
    // convert this window to a zombie in hopes that it will get debugged
    //
    InvalidateRect(hWnd, NULL, TRUE);
    SubclassWindow(hWnd, ZombieWndProc);
    return ZombieWndProc(hWnd, uMsg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// GetWindowProc
//
// this inline function returns the current wndproc for the specified window.
//
//-----------------------------------------------------------------------------
__inline WNDPROC GetWindowProc(HWND hWnd)
{
    return (WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC);
}

//-----------------------------------------------------------------------------
// g_aCC32Subclass
//
// This is the global ATOM we use to store our SUBCLASS_HEADER property on
// random windows that come our way.
//
//  HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
//
//  Win95's property code is BROKEN.  If you SetProp using a text string, USER
// adds and removes atoms for the property symmetrically, including when the
// window is destroyed with properties lying around (good).  Unfortunately, if
// you SetProp using a global atom, USER doesn't do things quite right in the
// window cleanup case.  It uses the atom without adding references in SetProp
// calls and without deleting them in RemoveProp calls (good so far).  However,
// when a window with one of these properties lying around is cleaned up, USER
// will delete the atom on you.  This tends to break apps that do the
// following:
//
//  - MyAtom = GlobalAddAtom("foo");            // at app startup
//  - SetProp(SomeWindow, MyAtom, MyData);
//  - <window gets destroyed, USER deletes atom>
//  - <time passes>
//  - SetProp(SomeOtherWindow, MyAtom, MyData); // fails or uses random atom
//  - GlobalDeleteAtom(MyAtom);                 // fails or deletes random atom
//
//  One might be tempted to ask why this file uses atom properties if they are
// so broken.  Put simply, it is the only way to defend yourself against other
// apps that use atom properties (like the one described above).  Imagine that
// we call SetProp(OurWindow, "bar", OurData) in some other app at about the
// <time passes> point in the sequence above.  USER has just nuked some poor
// app's atom, and we wander into SetProp, which calls GlobalAddAtom, which
// just happens to give us the free slot created by USER's window cleanup code.
// Now we have a real problem because the very same atom is sitting in some
// global variable in the other app, just waiting to be deleted when that app
// exits (Peachtree Accounting tends to be very good at this...)  Of course the
// ultimate outcome of this is that we will call GetProp in some critical
// routine and our data will have vanished (it's actually still in the window's
// property table but GetProp("bar") calls GlobalFindAtom("bar") to get the
// atom to scan the property table for; and that call will fail so the property
// will be missed and we'll get back NULL).
//
//  Basically, we create an atom and aggressively increment its reference count
// so that it can withstand a few GlobalDeleteAtom calls every now and then.
// Since we are using an atom property, we need to worry about USER's cleanup
// code nuking us too.  Thus we just keep incrementing the reference count
// until it pegs.
//
//  HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
//
//-----------------------------------------------------------------------------
extern ATOM g_aCC32Subclass;

//-----------------------------------------------------------------------------
// FastGetSubclassHeader
//
// this inline function returns the subclass header for the specified window.
// if the window has no subclass header the return value is NULL.
//
//-----------------------------------------------------------------------------
__inline SUBCLASS_HEADER *FastGetSubclassHeader(HWND hWnd)
{
    return  (g_aCC32Subclass ?
            ((SUBCLASS_HEADER *)GetProp(hWnd, MAKEINTATOM(g_aCC32Subclass))) :
            NULL);
}

//-----------------------------------------------------------------------------
// GetSubclassHeader
//
// this function returns the subclass header for the specified window.  it
// fails if the caller is on the wrong process, but will allow the caller to
// get the header from a thread other than the specified window's thread.
//
//-----------------------------------------------------------------------------
SUBCLASS_HEADER *GetSubclassHeader(HWND hWnd)
{
    DWORD dwProcessId;

    //
    // only return the header if we are in the right process
    //
    if (!GetWindowThreadProcessId(hWnd, &dwProcessId))
        dwProcessId = 0;

    if (dwProcessId != GetCurrentProcessId())
    {
        if (dwProcessId)
            DebugMsg(TF_ALWAYS, TEXT("error: XxxWindowSubclass - wrong process for window %08X"), hWnd);

        Assert(FALSE);
        return NULL;
    }

    //
    // return the header
    //
    return FastGetSubclassHeader(hWnd);
}

//-----------------------------------------------------------------------------
// SetSubclassHeader
//
// this function sets the subclass header for the specified window.
//
//-----------------------------------------------------------------------------
BOOL SetSubclassHeader(HWND hWnd, SUBCLASS_HEADER *pHeader,
    SUBCLASS_FRAME *pFrameFixup)
{
    ATOM a;
    BOOL fResult = TRUE;    // assume success

    ASSERTCRITICAL;         // we are partying on the header and frame list


    if (g_aCC32Subclass == 0) {
        //
        // HACK: we are intentionally incrementing the refcount on this atom
        // WE DO NOT WANT IT TO GO BACK DOWN so we will not delete it in process
        // detach (see comments for g_aCC32Subclass in subclass.c for more info)
        //
        if ((a = GlobalAddAtom(c_szCC32Subclass)) != 0)
            g_aCC32Subclass = a;    // in case the old atom got nuked
    }


    //
    // update the frame list if required
    //
    while (pFrameFixup)
    {
        pFrameFixup->pHeader = pHeader;
        pFrameFixup = pFrameFixup->pFramePrev;
    }

    //
    // do we have a window to update?
    //
    if (hWnd)
    {
        //
        // update/remove the property as required
        //
        if (!pHeader)
        {
            //
            // HACK: we remove with an ATOM so the refcount won't drop
            //          (see comments for g_aCC32Subclass above)
            //
            RemoveProp(hWnd, MAKEINTATOM(g_aCC32Subclass));
        }
        else
        {
            //
            // HACK: we add using a STRING so the refcount will go up
            //          (see comments for g_aCC32Subclass above)
            //
            if (!SetProp(hWnd, c_szCC32Subclass, (HANDLE)pHeader))
            {
                DebugMsg(TF_ALWAYS, TEXT("wn: SetWindowSubclass - couldn't subclass window %08X"), hWnd);
                fResult = FALSE;
            }
        }
    }

    return fResult;
}

//-----------------------------------------------------------------------------
// FreeSubclassHeader
//
// this function frees the subclass header for the specified window.
//
//-----------------------------------------------------------------------------
void FreeSubclassHeader(HWND hWnd, SUBCLASS_HEADER *pHeader)
{
    ASSERTCRITICAL;                 // we will be removing the subclass header

    //
    // sanity
    //
    if (!pHeader)
    {
        Assert(FALSE);
        return;
    }

    //
    // clean up the header
    //
    SetSubclassHeader(hWnd, NULL, pHeader->pFrameCur);
    LocalFree((HANDLE)pHeader);
}

//-----------------------------------------------------------------------------
// ReAllocSubclassHeader
//
// this function allocates/reallocates a subclass header for the specified
// window.
//
//-----------------------------------------------------------------------------
SUBCLASS_HEADER *ReAllocSubclassHeader(HWND hWnd, SUBCLASS_HEADER *pHeader,
    UINT uCallbacks)
{
    UINT uAlloc;

    ASSERTCRITICAL;     // we will be replacing the subclass header

    //
    // granularize the allocation
    //
    uAlloc = CALLBACK_ALLOC_GRAIN *
        ((uCallbacks + CALLBACK_ALLOC_GRAIN - 1) / CALLBACK_ALLOC_GRAIN);

    //
    // do we need to change the allocation?
    //
    if (!pHeader || (uAlloc != pHeader->uAlloc))
    {
        //
        // compute bytes required
        //
        uCallbacks = uAlloc * sizeof(SUBCLASS_CALL) + sizeof(SUBCLASS_HEADER);

        //
        // and try to alloc
        //
        if (pHeader)
        {
            pHeader = LocalReAlloc((HANDLE)pHeader, uCallbacks,
                LPTR | LMEM_MOVEABLE);          // allow move during realloc
        }
        else
            pHeader = LocalAlloc(LPTR, uCallbacks);

        //
        // did it work?
        //
        if (pHeader)
        {
            //
            // yup, update info
            //
            pHeader->uAlloc = uAlloc;

            if (!SetSubclassHeader(hWnd, pHeader, pHeader->pFrameCur))
            {
                FreeSubclassHeader(hWnd, pHeader);
                pHeader = NULL;
            }
        }
    }

    Assert(pHeader);
    return pHeader;
}

//-----------------------------------------------------------------------------
// CallOriginalWndProc
//
// this procedure is the default SUBCLASSPROC which is always installed when we
// subclass a window.  the original window procedure is installed as the
// reference data for this callback.  it simply calls the original wndproc and
// returns its result.
//
//-----------------------------------------------------------------------------
LRESULT CALLBACK CallOriginalWndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
    LPARAM lParam, UINT uIdSubclass, DWORD dwRefData)
{
    //
    // dwRefData should be the original window procedure
    //
    Assert(dwRefData);

    //
    // and call it
    //
    return CallWindowProc((WNDPROC)dwRefData, hWnd, uMsg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// AttachSubclassHeader
//
// this procedure makes sure that a given window is subclassed by us.  it
// maintains a reference count on the data structures associated with our
// subclass.  if the window is not yet subclassed by us then this procedure
// installs our subclass procedure and associated data structures.
//
//-----------------------------------------------------------------------------
SUBCLASS_HEADER *AttachSubclassHeader(HWND hWnd)
{
    SUBCLASS_HEADER *pHeader;
    DWORD dwThreadId;

    //
    // we party on the subclass call chain here
    //
    ASSERTCRITICAL;

    //
    // we only call SetWindowLong for the first caller, which would cause this
    // operation to work out of context sometimes and fail others...
    // artifically prevent people from subclassing from the wrong thread
    //  
    if ((dwThreadId = GetWindowThreadProcessId(hWnd, NULL)) !=
        GetCurrentThreadId())
    {
        AssertMsg(FALSE, TEXT("error: SetWindowSubclass - wrong thread for window %08X"), hWnd);
        return NULL;
    }

    //
    // if haven't already subclassed the window then do it now
    //
    if ((pHeader = GetSubclassHeader(hWnd)) == NULL)
    {
        WNDPROC pfnOldWndProc;
        SUBCLASS_CALL *pCall;

        //
        // attach our header data to the window
        // we need space for two callbacks; the subclass and the original proc
        //
        if ((pHeader = ReAllocSubclassHeader(hWnd, NULL, 2)) == NULL)
            return NULL;

        pHeader->dwThreadId = dwThreadId;

        //
        // actually subclass the window
        //
        if ((pfnOldWndProc = SubclassWindow(hWnd, MasterSubclassProc)) == NULL)
        {
            // clean up and get out
            FreeSubclassHeader(hWnd, pHeader);
            return NULL;
        }

        //
        // set up the first node in the array to call the original wndproc
        //
        Assert(pHeader->uAlloc);

        pCall = pHeader->CallArray;
        pCall->pfnSubclass = CallOriginalWndProc;
        pCall->uIdSubclass = 0;
        pCall->dwRefData   = (DWORD)pfnOldWndProc;

        //
        // init our subclass refcount...
        //
        pHeader->uRefs = 1;
    }

    return pHeader;
}

//-----------------------------------------------------------------------------
// DetachSubclassHeader
//
// this procedure attempts to detach the subclass header from the specified
// window
//
//-----------------------------------------------------------------------------
void DetachSubclassHeader(HWND hWnd, SUBCLASS_HEADER *pHeader, BOOL fForce)
{
    WNDPROC pfnOldWndProc;
#ifdef DEBUG
    SUBCLASS_CALL *pCall;
    UINT uCur;
#endif

    ASSERTCRITICAL;         // we party on the subclass call chain here
    Assert(pHeader);        // fear

    //
    // if we are not being forced to remove and the window is still valid then
    // sniff around a little and decide if it's a good idea to detach now
    //
    if (!fForce && hWnd)
    {
        Assert(pHeader == FastGetSubclassHeader(hWnd)); // paranoia

        //
        // do we still have active clients?
        //
        if (pHeader->uRefs > 1)
            return;

        Assert(pHeader->uRefs); // should always have the "call original" node

        //
        // are people on our stack?
        //
        if (pHeader->pFrameCur)
            return;

        //
        // if we are out of context then we should try again later
        //
        if (pHeader->dwThreadId != GetCurrentThreadId())
        {
            SendNotifyMessage(hWnd, WM_NULL, 0, 0L);
            return;
        }

        //
        // we keep the original window procedure as refdata for our
        // CallOriginalWndProc subclass callback
        //
        pfnOldWndProc = (WNDPROC)pHeader->CallArray[0].dwRefData;
        Assert(pfnOldWndProc);

        //
        // if somebody else is subclassed after us then we can't detach now
        //
        if (GetWindowProc(hWnd) != MasterSubclassProc)
            return;

        //
        // go ahead and try to detach
        //
        if (!SubclassWindow(hWnd, pfnOldWndProc))
        {
            Assert(FALSE);      // just plain shouldn't happen
            return;
        }
    }

    //
    // warn about anybody who hasn't unhooked yet
    //
#ifdef DEBUG
    uCur = pHeader->uRefs;
    pCall = pHeader->CallArray + uCur;
    while (--uCur)          // don't complain about our 'call original' node
    {
        pCall--;
        if (pCall->pfnSubclass)
        {
            //
            // always warn about these they could be leaks
            //
            DebugMsg(TF_ALWAYS, TEXT("warning: orphan subclass: fn %08X, id %08X, dw %08X"),
                pCall->pfnSubclass, pCall->uIdSubclass, pCall->dwRefData);
        }
    }
#endif

    //
    // free the header now
    //
    FreeSubclassHeader(hWnd, pHeader);
}

//-----------------------------------------------------------------------------
// PurgeSingleCallNode
//
// this procedure purges a single dead node in the call array
//
//-----------------------------------------------------------------------------
void PurgeSingleCallNode(HWND hWnd, SUBCLASS_HEADER *pHeader)
{
    UINT uRemain;

    ASSERTCRITICAL;         // we will try to re-arrange the call array
    
    if (!pHeader->uCleanup) // a little sanity
    {
        Assert(FALSE);      // nothing to do!
        return;
    }

    //
    // and a little paranoia
    //
    Assert(!pHeader->pFrameCur ||
        (pHeader->uCleanup < pHeader->pFrameCur->uDeepestCall));

    //
    // are there any call nodes above the one we're about to remove?
    //
    if ((uRemain = (pHeader->uRefs - pHeader->uCleanup)) > 0)
    {
        //
        // yup, need to fix up the array the hard way
        //
        SUBCLASS_CALL *pCall;
        SUBCLASS_FRAME *pFrame;
        UINT uCur, uMax;

        //
        // move the remaining nodes down into the empty space
        //
        pCall = pHeader->CallArray + pHeader->uCleanup;
        MoveMemory(pCall, pCall + 1, uRemain * sizeof(SUBCLASS_CALL));

        //
        // update the call indices of any active frames
        //
        uCur = pHeader->uCleanup;
        pFrame = pHeader->pFrameCur;
        while (pFrame)
        {
            if (pFrame->uCallIndex >= uCur)
            {
                pFrame->uCallIndex--;

                if (pFrame->uDeepestCall >= uCur)
                    pFrame->uDeepestCall--;
            }

            pFrame = pFrame->pFramePrev;
        }

        //
        // now search for any other dead call nodes in the reamining area
        //
        uMax = pHeader->uRefs - 1;  // we haven't decremented uRefs yet
        while (uCur < uMax)
        {
            if (!pCall->pfnSubclass)
                break;

            pCall++;
            uCur++;
        }
        pHeader->uCleanup = (uCur < uMax)? uCur : 0;
    }
    else
    {
        //
        // nope, this case is easy
        //
        pHeader->uCleanup = 0;
    }

    //
    // finally, decrement the client count
    //
    pHeader->uRefs--;
}

//-----------------------------------------------------------------------------
// CompactSubclassHeader
//
// this procedure attempts to compact the subclass call array, freeing the
// subclass header if the array is empty
//
//-----------------------------------------------------------------------------
void CompactSubclassHeader(HWND hWnd, SUBCLASS_HEADER *pHeader)
{
    ASSERTCRITICAL;         // we will try to re-arrange the call array

    //
    // we must handle the "window destroyed unexpectedly during callback" case
    //
    if (hWnd)
    {
        //
        // clean out as many dead callbacks as possible
        //
        while (pHeader->uCleanup && (!pHeader->pFrameCur ||
            (pHeader->uCleanup < pHeader->pFrameCur->uDeepestCall)))
        {
            PurgeSingleCallNode(hWnd, pHeader);
        }

        //
        // do we still have clients?
        //
        if (pHeader->uRefs > 1)
        {
            //
            // yes, shrink our allocation, leaving room for at least one client
            //
            ReAllocSubclassHeader(hWnd, pHeader, pHeader->uRefs + 1);
            return;
        }
    }

    //
    // try to detach and free
    //
    DetachSubclassHeader(hWnd, pHeader, FALSE);
}

//-----------------------------------------------------------------------------
// FindCallRecord
//
// this procedure searches for a call record with the specified subclass proc
// and id, and returns its address.  if no such call record is found then NULL
// is returned.
//
//-----------------------------------------------------------------------------
SUBCLASS_CALL *FindCallRecord(SUBCLASS_HEADER *pHeader,
    SUBCLASSPROC pfnSubclass, UINT uIdSubclass)
{
    SUBCLASS_CALL *pCall;
    UINT uCallIndex;

    ASSERTCRITICAL;         // we'll be scanning the call array

    //
    // scan the call array.  note that we assume there is always at least
    // one member in the table (our CallOriginalWndProc record)
    //
    pCall = pHeader->CallArray + (uCallIndex = pHeader->uRefs);
    do
    {
        uCallIndex--;
        pCall--;
        if ((pCall->pfnSubclass == pfnSubclass) &&
            (pCall->uIdSubclass == uIdSubclass))
        {
            return pCall;
        }
    }
    while (uCallIndex != (UINT)-1);

    return NULL;
}

//-----------------------------------------------------------------------------
// GetWindowSubclass
//
// this procedure retrieves the reference data for the specified window
// subclass callback
//
//-----------------------------------------------------------------------------
BOOL GetWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass, UINT uIdSubclass,
    DWORD *pdwRefData)
{
    SUBCLASS_HEADER *pHeader;
    SUBCLASS_CALL *pCall;
    BOOL fResult = FALSE;
    DWORD dwRefData = 0;

    //
    // sanity
    //
    if (!IsWindow(hWnd))
    {
        AssertMsg(FALSE, TEXT("error: GetWindowSubclass - %08X not a window"), hWnd);
        goto ReturnResult;
    }

    //
    // more sanity
    //
    if (!pfnSubclass
#ifdef DEBUG
        || IsBadCodePtr(pfnSubclass)
#endif
        )
    {
        AssertMsg(FALSE, TEXT("error: GetWindowSubclass - invalid callback %08X"), pfnSubclass);
        goto ReturnResult;
    }

    ENTERCRITICAL;

    //
    // if we've subclassed it and they are a client then get the refdata
    //
    if (((pHeader = GetSubclassHeader(hWnd)) != NULL) &&
        ((pCall = FindCallRecord(pHeader, pfnSubclass, uIdSubclass)) != NULL))
    {
        //
        // fetch the refdata and note success
        //
        dwRefData = pCall->dwRefData;
        fResult = TRUE;
    }

    LEAVECRITICAL;

    //
    // we always fill in/zero pdwRefData regradless of result
    //
ReturnResult:
    if (pdwRefData)
        *pdwRefData = dwRefData;

    return fResult;
}

//-----------------------------------------------------------------------------
// SetWindowSubclass
//
// this procedure installs/updates a window subclass callback.  subclass
// callbacks are identified by their callback address and id pair.  if the
// specified callback/id pair is not yet installed then the procedure installs
// the pair.  if the callback/id pair is already installed then this procedure
// changes the refernce data for the pair.
//
//-----------------------------------------------------------------------------
BOOL SetWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass, UINT uIdSubclass,
    DWORD dwRefData)
{
    SUBCLASS_HEADER *pHeader;
    SUBCLASS_CALL *pCall;
    BOOL bResult;

    //
    // some sanity
    //
    if (!IsWindow(hWnd))
    {
        AssertMsg(FALSE, TEXT("error: SetWindowSubclass - %08X not a window"), hWnd);
        return FALSE;
    }

    //
    // more sanity
    //
    if (!pfnSubclass
#ifdef DEBUG
        || IsBadCodePtr(pfnSubclass)
#endif
        )
    {
        AssertMsg(FALSE, TEXT("error: SetWindowSubclass - invalid callback %08X"), pfnSubclass);
        return FALSE;
    }

    bResult = FALSE;    // assume failure

    //
    // we party on the subclass call chain here
    //
    ENTERCRITICAL;

    //
    // actually subclass the window
    //
    if ((pHeader = AttachSubclassHeader(hWnd)) == NULL)
        goto bail;

    //
    // find a call node for this caller
    //
    if ((pCall = FindCallRecord(pHeader, pfnSubclass, uIdSubclass)) == NULL)
    {
        //
        // not found, alloc a new one
        //
        SUBCLASS_HEADER *pHeaderT =
            ReAllocSubclassHeader(hWnd, pHeader, pHeader->uRefs + 1);

        if (!pHeaderT)
        {
            //
            // re-query in case it is already gone
            //
            if ((pHeader = FastGetSubclassHeader(hWnd)) != NULL)
                CompactSubclassHeader(hWnd, pHeader);

            goto bail;
        }

        pHeader = pHeaderT;
        pCall = pHeader->CallArray + pHeader->uRefs;
        pHeader->uRefs++;
    }

    //
    // fill in the subclass call data
    //
    pCall->pfnSubclass = pfnSubclass;
    pCall->uIdSubclass = uIdSubclass;
    pCall->dwRefData   = dwRefData;

    bResult = TRUE;

bail:
    //
    // release the critical section and return the result
    //
    LEAVECRITICAL;
    return bResult;
}

//-----------------------------------------------------------------------------
// RemoveWindowSubclass
//
// this procedure removes a subclass callback from a window.  subclass
// callbacks are identified by their callback address and id pair.
//
//-----------------------------------------------------------------------------
BOOL RemoveWindowSubclass(HWND hWnd, SUBCLASSPROC pfnSubclass,
    UINT uIdSubclass)
{
    SUBCLASS_HEADER *pHeader;
    SUBCLASS_CALL *pCall;
    BOOL bResult;
    UINT uCall;

    //
    // some sanity
    //
    if (!IsWindow(hWnd))
    {
        AssertMsg(FALSE, TEXT("error: RemoveWindowSubclass - %08X not a window"), hWnd);
        return FALSE;
    }

    //
    // more sanity
    //
    if (!pfnSubclass
#ifdef DEBUG
        || IsBadCodePtr(pfnSubclass)
#endif
        )
    {
        AssertMsg(FALSE, TEXT("error: RemoveWindowSubclass - invalid callback %08X"), pfnSubclass);
        return FALSE;
    }

    bResult = FALSE;    // assume failure

    //
    // we party on the subclass call chain here
    //
    ENTERCRITICAL;

    //
    // obtain our subclass data
    //
    if ((pHeader = GetSubclassHeader(hWnd)) == NULL)
        goto bail;

    //
    // find the callback to remove
    //
    if ((pCall = FindCallRecord(pHeader, pfnSubclass, uIdSubclass)) == NULL)
        goto bail;

    //
    // disable this node and remember that we have something to clean up
    //
    pCall->pfnSubclass = NULL;

    uCall = pCall - pHeader->CallArray;

    if (!pHeader->uCleanup || (uCall < pHeader->uCleanup))
        pHeader->uCleanup = uCall;

    //
    // now try to clean up any unused nodes
    //
    CompactSubclassHeader(hWnd, pHeader);
#ifdef DEBUG
    // the call above can realloc or free the subclass header for this window
    pHeader = NULL;
#endif

    bResult = TRUE;     // it worked

bail:
    //
    // release the critical section and return the result
    //
    LEAVECRITICAL;
    return bResult;
}

//-----------------------------------------------------------------------------
// DefSubclassProc
//
// this procedure calls the next handler in the window's subclass chain.  the
// last handler in the subclass chain is installed by us, and calls the
// original window procedure for the window.
//
//-----------------------------------------------------------------------------
LRESULT DefSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    SUBCLASS_HEADER *pHeader;
    LRESULT lResult = 0L;

    //
    // make sure the window is still valid
    //
    if (!IsWindow(hWnd))
    {
        AssertMsg(FALSE, TEXT("warning: DefSubclassProc - %08X not a window"), hWnd);
        goto BailNonCritical;
    }

    //
    // take the critical section while we figure out who to call next
    //
    ASSERTNONCRITICAL;
    ENTERCRITICAL;

    //
    // complain if we are being called improperly
    //
    if ((pHeader = FastGetSubclassHeader(hWnd)) == NULL)
    {
        AssertMsg(FALSE, TEXT("error: DefSubclassProc - window %08X not subclassed"), hWnd);
        goto BailCritical;
    }
    else if (GetCurrentThreadId() != pHeader->dwThreadId)
    {
        AssertMsg(FALSE, TEXT("error: DefSubclassProc - wrong thread for window %08X"), hWnd);
        goto BailCritical;
    }
    else if (!pHeader->pFrameCur)
    {
        AssertMsg(FALSE, TEXT("error: DefSubclassProc - window %08X not in callback"), hWnd);
        goto BailCritical;
    }

    //
    // call the next proc in the subclass chain
    //
    // WARNING: this call temporarily releases the critical section
    // WARNING: pHeader is invalid when this call returns
    //
    lResult = CallNextSubclassProc(pHeader, hWnd, uMsg, wParam, lParam);
#ifdef DEBUG
    pHeader = NULL;
#endif

    //
    // return the result
    //
BailCritical:
    LEAVECRITICAL;

BailNonCritical:
    return lResult;
}

//-----------------------------------------------------------------------------
// UpdateDeepestCall
//
// this procedure updates the deepest call index for the specified frame
//
//-----------------------------------------------------------------------------
void UpdateDeepestCall(SUBCLASS_FRAME *pFrame)
{
    ASSERTCRITICAL;     // we are partying on the frame list

    if (pFrame->pFramePrev &&
        (pFrame->pFramePrev->uDeepestCall < pFrame->uCallIndex))
    {
        pFrame->uDeepestCall = pFrame->pFramePrev->uDeepestCall;
    }
    else
        pFrame->uDeepestCall = pFrame->uCallIndex;
}

//-----------------------------------------------------------------------------
// EnterSubclassFrame
//
// this procedure sets up a new subclass frame for the specified header, saving
// away the previous one
//
//-----------------------------------------------------------------------------
__inline void EnterSubclassFrame(SUBCLASS_HEADER *pHeader,
    SUBCLASS_FRAME *pFrame)
{
    ASSERTCRITICAL;     // we are partying on the header and frame list

    //
    // fill in the frame and link it into the header
    //
    pFrame->uCallIndex   = pHeader->uRefs + 1;
    pFrame->pFramePrev   = pHeader->pFrameCur;
    pFrame->pHeader      = pHeader;
    pHeader->pFrameCur   = pFrame;

    //
    // initialize the deepest call index for this frame
    //
    UpdateDeepestCall(pFrame);
}

//-----------------------------------------------------------------------------
// LeaveSubclassFrame
//
// this procedure cleans up the current subclass frame for the specified
// header, restoring the previous one
//
//-----------------------------------------------------------------------------
__inline SUBCLASS_HEADER *LeaveSubclassFrame(SUBCLASS_FRAME *pFrame)
{
    SUBCLASS_HEADER *pHeader;

    ASSERTCRITICAL;     // we are partying on the header

    //
    // unlink the frame from its header (if it still exists)
    //
    if ((pHeader = pFrame->pHeader) != NULL)
        pHeader->pFrameCur = pFrame->pFramePrev;

    return pHeader;
}

//-----------------------------------------------------------------------------
// SubclassFrameException
//
// this procedure cleans up when an exception is thrown from a subclass frame
//
//-----------------------------------------------------------------------------
void SubclassFrameException(SUBCLASS_FRAME *pFrame)
{
    //
    // clean up the current subclass frame
    //
    ASSERTNONCRITICAL;
    ENTERCRITICAL;
    DebugMsg(TF_ALWAYS, TEXT("warning: cleaning up subclass frame after exception"));
    LeaveSubclassFrame(pFrame);
    LEAVECRITICAL;
}

//-----------------------------------------------------------------------------
// MasterSubclassProc
//
// this is the window procedure we install to dispatch subclass callbacks.
// it maintains a linked list of 'frames' through the stack which allow
// DefSubclassProc to call the right subclass procedure in multiple-message
// scenarios.
//
//-----------------------------------------------------------------------------
LRESULT CALLBACK MasterSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam,
    LPARAM lParam)
{
    SUBCLASS_FRAME Frame;
    SUBCLASS_HEADER *pHeader;
    LRESULT lResult = 0;

    //
    // prevent people from partying on the callback chain while we look at it
    //
    ASSERTNONCRITICAL;
    ENTERCRITICAL;

    //
    // freak out if we got here and we don't have our data
    //
    if ((pHeader = FastGetSubclassHeader(hWnd)) == NULL)
    {
        LEAVECRITICAL;
        return SubclassDeath(hWnd, uMsg, wParam, lParam);
    }

    //
    // set up a new subclass frame and save away the previous one
    //
    EnterSubclassFrame(pHeader, &Frame);

    __try   // protect our state information from exceptions
    {
        //
        // go ahead and call the subclass chain on this frame
        //
        // WARNING: this call temporarily releases the critical section
        // WARNING: pHeader is invalid when this call returns
        //
        lResult =
            CallNextSubclassProc(pHeader, hWnd, uMsg, wParam, lParam);
#ifdef DEBUG
        pHeader = NULL;
#endif
    }
    __except (SubclassFrameException(&Frame), EXCEPTION_CONTINUE_SEARCH)
    {
        Assert(FALSE);
    }

    ASSERTCRITICAL;

    //
    // restore the previous subclass frame
    //
    pHeader = LeaveSubclassFrame(&Frame);

    //
    // if the header is gone we have already cleaned up in a nested frame
    //
    if (!pHeader)
        goto BailOut;

    //
    // was the window nuked (somehow) without us seeing the WM_NCDESTROY?
    //
    if (!IsWindow(hWnd))
    {
        //
        // EVIL! somebody subclassed after us and didn't pass on WM_NCDESTROY
        //
        AssertMsg(FALSE, TEXT("unknown subclass proc swallowed a WM_NCDESTROY"));

        // go ahead and clean up now
        hWnd = NULL;
        uMsg = WM_NCDESTROY;
    }

    //
    // if we are returning from a WM_NCDESTROY then we need to clean up
    //
    if (uMsg == WM_NCDESTROY)
    {
        DetachSubclassHeader(hWnd, pHeader, TRUE);
        goto BailOut;
    }

    //
    // is there any pending cleanup, or are all our clients gone?
    //
    if (pHeader->uCleanup || (!pHeader->pFrameCur && (pHeader->uRefs <= 1)))
    {
        CompactSubclassHeader(hWnd, pHeader);
#ifdef DEBUG
        pHeader = NULL;
#endif
    }

    //
    // all done
    //
BailOut:
    LEAVECRITICAL;
    ASSERTNONCRITICAL;
    return lResult;
}

//-----------------------------------------------------------------------------
// EnterSubclassCallback
//
// this procedure finds the next callback in the subclass chain and updates
// pFrame to indicate that we are calling it
//
//-----------------------------------------------------------------------------
UINT EnterSubclassCallback(SUBCLASS_HEADER *pHeader, SUBCLASS_FRAME *pFrame,
    SUBCLASS_CALL *pCallChosen)
{
    SUBCLASS_CALL *pCall;
    UINT uDepth;

    //
    // we will be scanning the subclass chain and updating frame data
    //
    ASSERTCRITICAL;

    //
    // scan the subclass chain for the next callable subclass callback
    //
    pCall = pHeader->CallArray + pFrame->uCallIndex;
    uDepth = 0;
    do
    {
        uDepth++;
        pCall--;

    } while (!pCall->pfnSubclass);

    //
    // copy the callback information for the caller
    //
    pCallChosen->pfnSubclass = pCall->pfnSubclass;
    pCallChosen->uIdSubclass = pCall->uIdSubclass;
    pCallChosen->dwRefData   = pCall->dwRefData;

    //
    // adjust the frame's call index by the depth we entered
    //
    pFrame->uCallIndex -= uDepth;

    //
    // keep the deepest call index up to date
    //
    UpdateDeepestCall(pFrame);

    return uDepth;
}

//-----------------------------------------------------------------------------
// LeaveSubclassCallback
//
// this procedure finds the next callback in the cal
//
//-----------------------------------------------------------------------------
__inline void LeaveSubclassCallback(SUBCLASS_FRAME *pFrame, UINT uDepth)
{
    //
    // we will be updating subclass frame data
    //
    ASSERTCRITICAL;

    //
    // adjust the frame's call index by the depth we entered and return
    //
    pFrame->uCallIndex += uDepth;

    //
    // keep the deepest call index up to date
    //
    UpdateDeepestCall(pFrame);
}

//-----------------------------------------------------------------------------
// SubclassCallbackException
//
// this procedure cleans up when a subclass callback throws an exception
//
//-----------------------------------------------------------------------------
void SubclassCallbackException(SUBCLASS_FRAME *pFrame, UINT uDepth)
{
    //
    // clean up the current subclass callback
    //
    ASSERTNONCRITICAL;
    ENTERCRITICAL;
    DebugMsg(TF_ALWAYS, TEXT("warning: cleaning up subclass callback after exception"));
    LeaveSubclassCallback(pFrame, uDepth);
    LEAVECRITICAL;
}

//-----------------------------------------------------------------------------
// CallNextSubclassProc
//
// this procedure calls the next subclass callback in the subclass chain
//
// WARNING: this call temporarily releases the critical section
// WARNING: pHeader is invalid when this call returns
//
//-----------------------------------------------------------------------------
LRESULT CallNextSubclassProc(SUBCLASS_HEADER *pHeader, HWND hWnd, UINT uMsg,
    WPARAM wParam, LPARAM lParam)
{
    SUBCLASS_CALL Call;
    SUBCLASS_FRAME *pFrame;
    LRESULT lResult;
    UINT uDepth;

    ASSERTCRITICAL;     // sanity
    ASSERT(pHeader);    // paranoia

    //
    // get the current subclass frame
    //
    pFrame = pHeader->pFrameCur;
    ASSERT(pFrame);

    //
    // get the next subclass call we need to make
    //
    uDepth = EnterSubclassCallback(pHeader, pFrame, &Call);

    //
    // leave the critical section so we don't deadlock in our callback
    //
    // WARNING: pHeader is invalid when this call returns
    //
    LEAVECRITICAL;
#ifdef DEBUG
    pHeader = NULL;
#endif

    //
    // we call the outside world so prepare to deadlock if we have the critsec
    //
    ASSERTNONCRITICAL;

    __try   // protect our state information from exceptions
    {
        //
        // call the chosen subclass proc
        //
        ASSERT(Call.pfnSubclass);

        lResult = Call.pfnSubclass(hWnd, uMsg, wParam, lParam,
            Call.uIdSubclass, Call.dwRefData);
    }
    __except (SubclassCallbackException(pFrame, uDepth),
        EXCEPTION_CONTINUE_SEARCH)
    {
        ASSERT(FALSE);
    }

    //
    // we left the critical section before calling out so re-enter it
    //
    ASSERTNONCRITICAL;
    ENTERCRITICAL;

    //
    // finally, clean up and return
    //
    LeaveSubclassCallback(pFrame, uDepth);
    return lResult;
}

///////////////////////////////////////////////////////////////////////////////

#if defined(RETAIL_ZOMBIE_MESSAGE_WNDPROC) || defined(DEBUG)
#ifdef DEBUG
static const TCHAR c_szZombieMessage[] =                                     \
    TEXT("This window has encountered an internal error which is preventing ")    \
    TEXT("it from operating normally.\r\n\nPlease report this problem to ")       \
    TEXT("FrancisH immediately.");
#else
static const TCHAR c_szZombieMessage[] =                                     \
    TEXT("This window has encountered an internal error which is preventing ")    \
    TEXT("it from operating normally.\r\n\nPlease report this as a bug in the ")  \
    TEXT("COMCTL32 library.");
#endif

LRESULT ZombieWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_ERASEBKGND:
        {
            HDC hDC = (HDC)wParam;
            HBRUSH hBrush = CreateSolidBrush(RGB(255,255,0));

            if (hBrush)
            {
                RECT rcErase;

                switch (GetClipBox(hDC, &rcErase))
                {
                default:
                    FillRect(hDC, &rcErase, hBrush);
                    break;
                case NULLREGION:
                case ERROR:
                    break;
                }

                DeleteBrush(hBrush);
            }
        }
        return 1;

    case WM_PAINT:
        {
            RECT rcClient;
            PAINTSTRUCT ps;
            HDC hDC = BeginPaint(hWnd, &ps);

            if (hDC && GetClientRect(hWnd, &rcClient))
            {
                COLORREF clrBkSave = SetBkColor(hDC, RGB(255,255,0));
                COLORREF clrFgSave = SetTextColor(hDC, RGB(255,0,0));

                DrawText(hDC, c_szZombieMessage, -1, &rcClient,
                    DT_LEFT | DT_TOP | DT_NOPREFIX | DT_WORDBREAK |
                    DT_WORD_ELLIPSIS);

                SetTextColor(hDC, clrFgSave);
                SetBkColor(hDC, clrBkSave);
            }

            EndPaint(hWnd, &ps);
        }
        return 0;
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
#endif

///////////////////////////////////////////////////////////////////////////////