/******************************Module*Header*******************************\
* Module Name: wndobj.cxx
*
* WNDOBJ support routines.
*
* Created: 22-Sep-1993 17:42:20
* Author: Wendy Wu [wendywu]
*         Hock San Lee [hockl]
*
* Copyright (c) 1993-1999 Microsoft Corporation
\**************************************************************************/

#include "precomp.hxx"

#if DBG

long glDebugLevel = 0;
#define DBGINFO(str)  if (glDebugLevel >= 2) DbgPrint("GLSRVL: " str)
#define DBGENTRY(str) if (glDebugLevel >= 8) DbgPrint("GLSRVL: " str)

#else

#define DBGINFO(str)
#define DBGENTRY(str)

#endif


// Global tracking object (TRACKOBJ) pointer.
// If this is non-null, we are tracking some WNDOBJs in the system.

PTRACKOBJ gpto = (PTRACKOBJ)NULL;

// Global that indicates whether to notify driver with the new WNDOBJ
// states following a WNDOBJ creation.  User has not changed the window
// states but this is required to initialize the driver.  The update is
// done in the parent gdi functions (e.g. SetPixelFormat) that allow
// the DDI to create a WNDOBJ.

BOOL gbWndobjUpdate;

// The following is a global uniqueness that gets bumped up anytime USER
// changes anyone's VisRgn:

ULONG giVisRgnUniqueness = 0;

// Maximum region rectangle

RECTL grclMax = {
    MIN_REGION_COORD,
    MIN_REGION_COORD,
    MAX_REGION_COORD,
    MAX_REGION_COORD
};

// Here is a brief description of the semaphore usage.
//
// There are 3 semaphores that this module uses/references.
//
// 1. User critical section.
//    The user critical section ensures that no window moves when a new
//    update occurs.  It is only relevent to the display DCs to ensure a
//    consistent window client regions state.  For example, GreSetClientRgn
//    assumes that no window can move until GreClientRgnUpdated is called.
//
// 2. Display devlock and DC/surface locks.
//    The display devlock must be entered when a WNDOBJ is being used or
//    updated.  This prevents a WNDOBJ from being modified when it is
//    being used in the DDI.  The display devlock applies to the display
//    DCs only.  For memory and printer DCs, the surface is locked when
//    a WNDOBJ is used.  This is the current GDI design to prevent a
//    different thread from deleting a surface while it is being used.
//    Note that this precludes any multi-thread access to the printer or
//    memory DCs.
//
// 3. Window object semaphore.
//    The window object semaphore is used to protect access to the window
//    object data structures.  Note that a semaphore is used instead of
//    a mutex here to allow for process cleanup of the semaphore.  The
//    process cleanup is done in the user process cleanup code.
//
// The above 3 semaphores must be entered in the given order.  Otherwise,
// a deadlock may occur.


/******************************Member*Function*****************************\
* VOID TRACKOBJ::vUpdateDrvDelta
*
* Update driver function for delta regions.  fl must have
* WOC_RGN_CLIENT_DELTA or WOC_RGN_SURFACE_DELTA set.
* Note that if the delta is empty, we do not need to call the driver.
* The compiler does not allow this function to be inline because or forward
* reference.
*
* History:
*  Thu Jan 13 09:55:23 1994     -by-    Hock San Lee    [hockl]
* Wrote it.
\**************************************************************************/

VOID TRACKOBJ::vUpdateDrvDelta(EWNDOBJ *pwo, FLONG fl)
{
    ASSERTGDI(fl & (WOC_RGN_CLIENT_DELTA|WOC_RGN_SURFACE_DELTA),
        "TRACKOBJ::vUpdateDrvDelta, Bad flags\n");

    if (!pwo->erclExclude().bEmpty())
        (*pfn)((WNDOBJ *)pwo, fl);
}

/******************************Public*Function*****************************\
* EngCreateWnd
*
* Create a WNDOBJ from a HWND.  This function should only be called
* when the calling thread has the usercrit and devlock in that order.
* GDI will ensure that the thread acquires both locks before calling the
* DDIs that allow EngCreateWnd to be called.  The driver should only call
* this function from those DDI entry points.  Currently, GreSetPixelFormat
* acquires both locks before calling DrvSetPixelFormat; GreExtEscape for
* WNDOBJ_SETUP escape also acquires both locks before calling DrvEscape.
*
* This function allows tracking multiple surfaces (screen, bitmaps and
* printers).  For each display surface being tracked, it further allows
* multiple TRACKOBJs to be created for each driver function.  The TRACKOBJs
* on a device surface are identified by unique pfn function pointers.
* This allows a live video driver and an OpenGL driver to track windows
* independently of each other.  The only restriction is that a window on
* a surface cannot be tracked by more than one TRACKOBJs on that surface.
*
* A WNDOBJ has an associated pixel format.  Once a WNDOBJ is created with
* a given pixel format, it cannot be set to a different pixel format.  If
* there is no pixel format associated with a WNDOBJ, e.g. live video,
* it should be set to zero.
*
* The WNDOBJ created in this function does not have the current states
* until the first driver update function is called.  However, the driver
* may immediately associate its own data with the WNDOBJ by calling the
* WNDOBJ_vSetConsumer function.
*
* Once a WNDOBJ is created, it cannot be deleted by the driver.  Gdi will
* will notifiy the driver of the deletion when the window goes away or
* when the associated surface is deleted.
*
* The given hwnd identifies the user window to be tracked.  It must be 0
* if the surface is a printer or memory bitmap.
*
* Returns the new WNDOBJ pointer if a WNDOBJ is created; 0 if an error
* occurs; -1 if hwnd is already being tracked by this driver function.
*
* History:
*  Thu Jan 13 09:55:23 1994     -by-    Hock San Lee    [hockl]
* Rewrote it.
*  27-Sep-1993 -by- Wendy Wu [wendywu]
* Wrote it.
\**************************************************************************/

// This is a private clean up class for this function.

class WO_CLEANUP
{
private:
    BOOL      bKeep;            // TRUE if resouces should not be freed
    PTRACKOBJ pto;
    PEWNDOBJ  pwoSurf;
    PEWNDOBJ  pwoClient;
    PREGION   prgnSurf;
    PREGION   prgnClient;
    HSEMAPHORE hsemClient;

public:
    WO_CLEANUP()
    {
        bKeep      = FALSE;
        pto        = (PTRACKOBJ)NULL;
        pwoSurf    = (PEWNDOBJ)NULL;
        pwoClient  = (PEWNDOBJ)NULL;
        prgnSurf   = (PREGION)NULL;
        prgnClient = (PREGION)NULL;
        hsemClient = NULL;
    }

    VOID vSetTrackobj(PTRACKOBJ pto1)           { pto        = pto1; }
    VOID vSetSurfWndobj(PEWNDOBJ pwo)           { pwoSurf    = pwo; }
    VOID vSetClientWndobj(PEWNDOBJ pwo)         { pwoClient  = pwo; }
    VOID vSetSurfRegion(RGNMEMOBJ &rmo)         { prgnSurf   = rmo.prgnGet();}
    VOID vSetClientRegion(RGNMEMOBJ &rmo)       { prgnClient = rmo.prgnGet();}
    VOID vSetClientSem(HSEMAPHORE hsem)         { hsemClient = hsem; }
    PTRACKOBJ ptoGet()                          { return(pto); }

    VOID vKeepAll()                             { bKeep = TRUE; }

   ~WO_CLEANUP()
    {
        if (bKeep)      return;

        DBGINFO("EngCreateWnd: no WNDOBJ created\n");
        if (pto)        { pto->ident = 0; VFREEMEM(pto); }
        if (pwoSurf)    { pwoSurf->ident = 0; VFREEMEM(pwoSurf); }
        if (pwoClient)  { pwoClient->ident = 0; VFREEMEM(pwoClient); }
        if (prgnSurf)   prgnSurf->vDeleteREGION();
        if (prgnClient) prgnClient->vDeleteREGION();
        if (hsemClient) GreDeleteSemaphore(hsemClient);
    }
};

WNDOBJ * APIENTRY EngCreateWnd
(
    SURFOBJ          *pso,
    HWND             hwnd,
    WNDOBJCHANGEPROC pfn,
    FLONG            fl,
    int              iPixelFormat
)
{
    WO_CLEANUP cleanup;         // prepare for clean up
    PEWNDOBJ   pwoClient;
    PEWNDOBJ   pwoSurf;
    PEWNDOBJ   pwo;
    PTRACKOBJ  pto;
    PEWNDOBJ   pwoGenericSibling = NULL;

    DBGENTRY("EngCreateWnd\n");

    PSURFACE   pSurf = SURFOBJ_TO_SURFACE(pso);

// Assert that we are in user critical section and also hold the devlock.
// This ensures that no one is updating the hwnd.

    if (!UserIsUserCritSecIn())
    {
        RIP("Driver may call EngCreateWnd only from WNDOBJ_SETUP escape\n"
            "(or from OpenGL MCD or ICD escapes)");
        return(NULL);
    }

    CHECKUSERCRITIN;
    if (hwnd)
    {
        CHECKDEVLOCKIN2(pSurf);
    }

// Validate flags.

#if ((WO_VALID_FLAGS & WO_INTERNAL_VALID_FLAGS) != 0)
#error "bad WO_INTERNAL_VALID_FLAGS"
#endif

    if ((fl & ~WO_VALID_FLAGS) != 0)
        return((WNDOBJ *)0);

// If this is the first time we need to track window object, create the
// semaphore for synchronization.  We use a semaphore instead of mutex
// so that we can perform process cleanup of the semaphore.

// Enter the semphore for window object.

    SEMOBJ so(ghsemWndobj);

// If the window is already being tracked by the same TRACKOBJ and the
// pixel format is the same, return -1.  If the window is being tracked
// by a different TRACKOBJ, return 0.  There may be multiple TRACKOBJs
// on the same device surface but a window can be tracked by only one
// TRACKOBJ.  In addition, pixel format in a window cannot be modified
// once it is set.

    for (pto = gpto; pto; pto = pto->ptoNext)
    {
        for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext)
        {
            if (pwo->hwnd == hwnd)
            {
                KdPrint(("Failing EngCreateWnd -- hwnd already has a WNDOBJ\n"));

                if (pto->pfn == pfn && pwo->ipfd == iPixelFormat)
                    return((WNDOBJ *)-1);   // return -1
                else
                    return((WNDOBJ *)0);    // tracked by a diff TRACKOBJ
            }
        }
    }

// If this is the first time we track a device surface for this driver
// function, we have to allocate space for the TRACKOBJ.  We determine
// if this is a new TRACKOBJ by comparing the pfn with those of the
// existing ones.  This allows the live video, installable opengl, and
// generic opengl windows to be tracked separately.

    for (pto = gpto; pto; pto = pto->ptoNext)
        if (pto->pSurface == pSurf && pto->pfn == pfn)
                break;
    if (!pto)
    {
// Allocate a new TRACKOBJ.

        if (!(pto = (PTRACKOBJ) PALLOCMEM(sizeof(TRACKOBJ), 'dnwG')))
            return((WNDOBJ *)0);
        cleanup.vSetTrackobj(pto);

        pto->ident   = TRACKOBJ_IDENTIFIER;
        // pto->ptoNext
        pto->pwoSurf  = (PEWNDOBJ)NULL;
        pto->pwo      = (PEWNDOBJ)NULL;
        pto->pSurface = pSurf;
        pto->pfn      = pfn;
        pto->fl       = fl;
        pto->erclSurf.left   = 0;
        pto->erclSurf.top    = 0;
        pto->erclSurf.right  = pSurf->sizl().cx;
        pto->erclSurf.bottom = pSurf->sizl().cy;

// Create a surface WNDOBJ for the TRACKOBJ if it requests WO_RGN_SURFACE or
// WO_RGN_SURFACE_DELTA.

        if (fl & (WO_RGN_SURFACE|WO_RGN_SURFACE_DELTA))
        {
            if (!(pwoSurf = (PEWNDOBJ) PALLOCMEM(sizeof(EWNDOBJ), 'dnwG')))
                return((WNDOBJ *)0);
            cleanup.vSetSurfWndobj(pwoSurf);

// Create a surface client region that is the entire surface.

            RGNMEMOBJ rmoSurf((BOOL)FALSE);
            if (!rmoSurf.bValid())
                return((WNDOBJ *)0);
            cleanup.vSetSurfRegion(rmoSurf);
            rmoSurf.vSet((RECTL *)&pto->erclSurf);

// Initialize the surface WNDOBJ.

            pwoSurf->pto        = pto;                  // pto used by vSetClip
            rmoSurf.prgnGet()->vStamp();                // init iUniq
            pwoSurf->vSetClip(rmoSurf.prgnGet(), pto->erclSurf);
            pwoSurf->pvConsumer = 0;
            pwoSurf->psoOwner   = pSurf->pSurfobj();
            pwoSurf->ident      = EWNDOBJ_IDENTIFIER;
            pwoSurf->pwoNext    = (PEWNDOBJ)NULL;       // no next pointer
            pwoSurf->hwnd       = 0;                    // no hwnd
            pwoSurf->fl         = fl | WO_SURFACE;
            pwoSurf->ipfd       = 0;                    // no pixel format

// Add WNDOBJ to the TRACKOBJ.

            pto->pwoSurf = pwoSurf;
        }
    }

// The tracking flags must be consistent.

    if ((pto->fl & ~WO_INTERNAL_VALID_FLAGS) != fl)
        return((WNDOBJ *)0);

// Allocate a new client WNDOBJ.

    if (!(pwoClient = (PEWNDOBJ) PALLOCMEM(sizeof(EWNDOBJ), 'dnwG')))
        return((WNDOBJ *)0);
    cleanup.vSetClientWndobj(pwoClient);

// Create an empty window client region.  The client region is still being
// created.  The driver window region update will be done in the parent gdi
// function.

    ERECTL    erclClient(0,0,0,0);
    RGNMEMOBJ rmoClient((BOOL)FALSE);
    if (!rmoClient.bValid())
        return((WNDOBJ *)0);
    cleanup.vSetClientRegion(rmoClient);
    rmoClient.vSet((RECTL *)&erclClient);

// Initialize the per-WNDOBJ semaphore once per window.

    {
        pwoClient->hsem = GreCreateSemaphore();
        if (pwoClient->hsem == NULL)
        {
            return NULL;
        }
        cleanup.vSetClientSem(pwoClient->hsem);
        fl |= WO_HSEM_OWNER;
    }

// Initialize the WNDOBJ.

    pwoClient->pto        = pto;        // pto used by vSetClip
    rmoClient.prgnGet()->vStamp();      // init iUniq
    pwoClient->vSetClip(rmoClient.prgnGet(), erclClient);
    pwoClient->pvConsumer = 0;          // to be set by the driver
    pwoClient->psoOwner   = pSurf->pSurfobj();
    pwoClient->ident      = EWNDOBJ_IDENTIFIER;
    pwoClient->hwnd       = hwnd;
    pwoClient->fl         = fl;
    pwoClient->ipfd       = iPixelFormat;

// Add WNDOBJ to TRACKOBJ.

    pwoClient->pwoNext = pto->pwo;
    pto->pwo = pwoClient;

    ASSERTGDI(offsetof(EWNDOBJ, pvConsumer) == offsetof(WNDOBJ, pvConsumer),
              "EngCreateWnd: rclClient wrong offset\n");

// Add TRACKOBJ to global linked list.

    if (cleanup.ptoGet())
    {
        pto->ptoNext = gpto;
        gpto = pto;
    }

// If hwnd is given, attach the WNDOBJ to the window in user.
// Otherwise, it is a printer surface or memory bitmap.  Attach it to
// the surface.

    if (hwnd)
    {
        UserAssociateHwnd(hwnd, (PVOID) pwoClient);
    }
    else
    {
// Only one WNDOBJ per memory bitmap or printer surface.

        ASSERTGDI(!pSurf->pwo(),
                  "EngCreateWnd: multiple WNDOBJs unexpected in memory DCs\n");
        pSurf->pwo(pwoClient);
    }

// Inform the parent gdi function that it needs to update the new WNDOBJ
// in the driver.

    pto->fl       |= WO_NEW_WNDOBJ;
    pwoClient->fl |= WO_NEW_WNDOBJ;
    gbWndobjUpdate = TRUE;

// Don't free the PDEV until the WNDOBJ is destroyed.

    PDEVOBJ po(pSurf->hdev());
    po.vReferencePdev();

// Everything is golden.  Keep the created objects and return the new WNDOBJ.

    cleanup.vKeepAll();
    return((WNDOBJ *)pwoClient);
}

/******************************Public*Function*****************************\
* GreDeleteWnd
*
* This function is called when the window that is being tracked is deleted
* in user, or when the device surface (printer or memory bitmap) that
* is begin tracked is deleted.  It deletes the WNDOBJ and notifies the
* driver that the WNDOBJ is going away.
*
* This function does not update the driver with the new client regions
* following the WNDOBJ deletion.  It assumes that if the deletion is a
* window, user will update or has updated the client regions; and if the
* deleteion is a printer or memory bitmap, the TRACKOBJ is going away
* and therefore no need to notify driver.
*
* If the deletion is a window, the calling thread must have the usercrit.
*
* History:
*  Thu Jan 13 09:55:23 1994     -by-    Hock San Lee    [hockl]
* Wrote it.
\**************************************************************************/

VOID APIENTRY GreDeleteWnd(PVOID _pwoDelete)
{
    PEWNDOBJ  pwoDelete = (PEWNDOBJ)_pwoDelete;
    PEWNDOBJ  pwo;
    PTRACKOBJ pto;

    DBGENTRY("GreDeleteWnd\n");

// Validate pwoDelete.

    if (!pwoDelete->bValid())
    {
        ASSERTGDI(FALSE, "GreDeleteWnd: Invalid pwoDelete\n");
        return;
    }

// If hwnd is non 0, the user calling thread must hold the usercrit.
// This ensures that no one is updating the hwnd.

    if (pwoDelete->hwnd)
    {
        CHECKUSERCRITIN;
        CHECKDEVLOCKIN2(pwoDelete->pto->pSurface);
    }

    pto = pwoDelete->pto;


// Acquire the device lock.  Note that this may be different from the
// device lock that USER is holding if the PDEV is marked as 'deleted':

    PDEVOBJ po(pto->pSurface->hdev());

    {
        DEVLOCKOBJ dlo(po);

// Enter the semaphore for window object.

        ASSERTGDI(ghsemWndobj, "GreDeleteWnd: bad ghsemWndobj\n");
        SEMOBJ so(ghsemWndobj);

// Notify driver that the WNDOBJ is going away.
// Hold the WNDOBJ stable while doing so by grabbing the per-WNDOBJ semaphore.

        {
            SEMOBJ soClient(pwoDelete->hsem);

            pto->vUpdateDrv(pwoDelete, WOC_DELETE);
        }

// Unlink pwoDelete from chain.

        if (pto->pwo == pwoDelete)
            pto->pwo = pwoDelete->pwoNext;
        else
            for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext)
            {
                if (pwo->pwoNext == pwoDelete)
                {
                    pwo->pwoNext = pwoDelete->pwoNext;
                    break;
                }
            }

// Free pwoDelete.

        pwoDelete->bDelete();       // delete RGNOBJ
        pwoDelete->ident = 0;
        VFREEMEM(pwoDelete);        // free memory

// Delete the tracking object if there are no more windows to track.

        if (pto->pwo == (PEWNDOBJ)NULL)
        {
// Unlink pto from chain.

            if (pto == gpto)
                gpto = pto->ptoNext;
            else
                for (PTRACKOBJ ptoTmp = gpto; ptoTmp; ptoTmp = ptoTmp->ptoNext)
                {
                    if (ptoTmp->ptoNext == pto)
                    {
                        ptoTmp->ptoNext = pto->ptoNext;
                        break;
                    }
                }

// Delete the pwoSurf if it exists.

            if (pto->pwoSurf)
            {
                ASSERTGDI(pto->fl & (WO_RGN_SURFACE|WO_RGN_SURFACE_DELTA),
                    "GreDeleteWnd: WO_RGN_SURFACE or WO_RGN_SURFACE_DELTA not set\n");

                pto->pwoSurf->bDelete();    // delete RGNOBJ
                pto->pwoSurf->ident = 0;
                VFREEMEM(pto->pwoSurf);     // free memory
            }

            pto->ident = 0;
            VFREEMEM(pto);
        }

// Inform the sprite code that a WNDOBJ has been deleted.

        vSpWndobjChange(po.hdev(), NULL);
    }

// Remove the reference to the PDEV.

    po.vUnreferencePdev();
}

/******************************Public*Function*****************************\
* EngDeleteWnd
*
* Driver-callable entry point to delete a WNDOBJ.
*
\**************************************************************************/

VOID EngDeleteWnd(WNDOBJ* _pwoDelete)
{
    EWNDOBJ* pwoDelete = (EWNDOBJ*)_pwoDelete;

    if (!UserIsUserCritSecIn())
    {
        RIP("Driver may call EngDeleteWnd only from WNDOBJ_SETUP escape\n"
            "(or from OpenGL MCD or ICD escapes)");
    }
    else
    {
// Tell USER to disassociate this WNDOBJ from its window:

        if (pwoDelete->hwnd)
        {
            UserAssociateHwnd(pwoDelete->hwnd, NULL);
        }

        GreDeleteWnd(pwoDelete);
    }
}

/******************************Public*Routine******************************\
* VOID vChangeWndObjs
*
* Transfers ownership of WNDOBJs between PDEVs.
*
* History:
*  8-Feb-1996 -by- J. Andrew Goossen [andrewgo]
* Wrote it.
\**************************************************************************/

VOID vChangeWndObjs(
SURFACE* pSurfaceOld,
HDEV     hdevOld,
SURFACE* pSurfaceNew,
HDEV     hdevNew)
{
    TRACKOBJ*   pto;
    EWNDOBJ*    pwo;
    EWNDOBJ*    pwoNext;

    CHECKUSERCRITIN;

    SEMOBJ so(ghsemWndobj);

// Note that we shouldn't use pSurfaceOld->hdev() or pSurfaceNew->hdev()
// becuase they haven't been updated yet:

    PDEVOBJ poOld(hdevOld);
    PDEVOBJ poNew(hdevNew);

// Changing drivers.  Delete all WNDOBJs belonging to the old
// surface:

    for (pto = gpto; pto != NULL; pto = pto->ptoNext)
    {
        if (pto->pSurface == pSurfaceOld)
        {
// For every WNDOBJ belonging to this TRACKOBJ, transfer
// ownership to the new PDEV:

            for (pwo = pto->pwo; pwo != NULL; pwo = pwo->pwoNext)
            {
                ASSERTGDI(pwo->psoOwner == pSurfaceOld->pSurfobj(),
                    "Old psoOwner mismatch");

                poNew.vReferencePdev();
                poOld.vUnreferencePdev();
            }
        }
        else if (pto->pSurface == pSurfaceNew)
        {
            for (pwo = pto->pwo; pwo != NULL; pwo = pwo->pwoNext)
            {
                ASSERTGDI(pwo->psoOwner == pSurfaceNew->pSurfobj(),
                    "New psoOwner mismatch");

                poOld.vReferencePdev();
                poNew.vUnreferencePdev();
            }
        }
    }
}

/******************************Public*Routine******************************\
* VOID vTransferWndObjs
*
* Transfers ownership of WNDOBJs from PDEV to other PDEV.
*
* History:
*  2-16-1999 -by- Hideyuki Nagase [hideyukn]
* Wrote it.
\**************************************************************************/

VOID vTransferWndObjs(
SURFACE* pSurface,
HDEV     hdevOld,
HDEV     hdevNew)
{
    TRACKOBJ*   pto;
    EWNDOBJ*    pwo;
    EWNDOBJ*    pwoNext;

    CHECKUSERCRITIN;

    SEMOBJ so(ghsemWndobj);

    PDEVOBJ poOld(hdevOld);
    PDEVOBJ poNew(hdevNew);

    for (pto = gpto; pto != NULL; pto = pto->ptoNext)
    {
        if (pto->pSurface == pSurface)
        {
            for (pwo = pto->pwo; pwo != NULL; pwo = pwo->pwoNext)
            {
                ASSERTGDI(pwo->psoOwner == pSurface->pSurfobj(),
                    "New psoOwner mismatch");

                KdPrint(("Transfer wndobj %x - hdevNew %x hdevOld %x \n",
                          pwo,hdevNew,hdevOld));

                poNew.vReferencePdev();
                poOld.vUnreferencePdev();
            }
        }
    }
}

/******************************Public*Function*****************************\
* vForceClientRgnUpdate
*
* This function is called by gdi to force an update of the new WNDOBJ
* that is just created.
*
* This function should only be called when the calling thread has the
* usercrit and devlock in that order.  Currently, it is called from
* GreSetPixelFormat and GreExtEscape for WNDOBJ_SETUP escape after
* they detected that the driver has created a new WNDOBJ.
*
* History:
*  Thu Jan 13 09:55:23 1994     -by-    Hock San Lee    [hockl]
* Wrote it.
\**************************************************************************/

VOID vForceClientRgnUpdate()
{
    PTRACKOBJ pto;
    PEWNDOBJ  pwo = (PEWNDOBJ)NULL;

    DBGENTRY("vForceClientRgnUpdate\n");

// Assert that we are in user critical section and also hold the devlock.
// This ensures that no one is updating the hwnd.

    CHECKUSERCRITIN;

// Update the new WNDOBJ that was just created.
// User has not changed the client regions because we are still in the
// user critical section.  We are updating the client regions ourselves.

    {
// Enter the semphore for window object.

        SEMOBJ so(ghsemWndobj);

// Find the newly created WNDOBJ.

        for (pto = gpto; pto; pto = pto->ptoNext)
        {
            if (!(pto->fl & WO_NEW_WNDOBJ))
                continue;

            pto->fl &= ~WO_NEW_WNDOBJ;
            pto->fl |= WO_NOTIFIED;

            for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext)
            {
                if (!(pwo->fl & WO_NEW_WNDOBJ))
                    continue;

                pwo->fl &= ~WO_NEW_WNDOBJ;
                pwo->fl |= WO_NOTIFIED;
                break;          // found it
            }
            break;              // found it
        }

        if (!pwo)
        {
            ASSERTGDI(FALSE, "vForceClientRgnUpdate: no new WNDOBJ found\n");
            return;
        }

// We need to ensure that the caller holds the devlock before calling this
// function.  Otherwise, some other threads may be drawing into the wrong
// client region.  We do the check here because we don't have any pSurf
// information earlier.

        if (pwo->hwnd)
        {
            CHECKDEVLOCKIN2(pto->pSurface);
        }

// If hwnd exists, get the client region from user.

        HRGN   hrgnClient;
        ERECTL erclClient;

        if (pwo->hwnd)
        {
            hrgnClient = UserGetClientRgn(pwo->hwnd,
                                          (LPRECT)&erclClient,
                                          pwo->fl & WO_RGN_WINDOW);
        }
        else
        {
// If hwnd does not exist, this is a memory bitmap or printer surface.
// The client region is the whole surface.

            erclClient = pto->erclSurf;
            hrgnClient = GreCreateRectRgnIndirect((LPRECT)&erclClient);
        }

        if (!hrgnClient)
        {
            ASSERTGDI(FALSE, "vForceClientRgnUpdate: hwnd has no rgn\n");
            return;
        }

// Update client region in the WNDOBJ.

        GreSetRegionOwner(hrgnClient, OBJECT_OWNER_PUBLIC);
        RGNOBJAPI roClient(hrgnClient,FALSE);
        ASSERTGDI(roClient.bValid(), "vForceClientRgnUpdate: invalid hrgnClient\n");

#ifdef OPENGL_MM

// Under Multi-mon we need to adjust the client region and
// client rectangle by the offset of the surface.
// Additionally we need to clip the client region to the
// surface of the TRACKOBJ.

        if (!(pwo->fl & WO_RGN_DESKTOP_COORD))
        {
            PDEVOBJ pdo(pwo->pto->pSurface->hdev());

            if (pdo.bValid())
            {
                if (pdo.bPrimary(pwo->pto->pSurface))
                {
                    POINTL ptlOrigin;

                    ptlOrigin.x = -pdo.pptlOrigin()->x;
                    ptlOrigin.y = -pdo.pptlOrigin()->y;

                    if ((ptlOrigin.x != 0) || (ptlOrigin.y != 0))
                    {
// offset the region and window rect if necessary

                        roClient.bOffset(&ptlOrigin);
                        erclClient += ptlOrigin;      /* this offsets by ptl */
                    }
                }
            }

            RGNMEMOBJTMP rmoTmp;
            RGNMEMOBJTMP rmoRcl;

            if (rmoTmp.bValid() && rmoRcl.bValid())
            {
// this clips the client region to the pto surface

                rmoRcl.vSet((RECTL *) &pto->erclSurf);
                rmoTmp.bCopy(roClient);
                roClient.iCombine(rmoTmp, rmoRcl, RGN_AND);
                if (rmoTmp.iCombine(roClient,rmoRcl,RGN_AND) != ERROR)
                {
                    roClient.bSwap(&rmoTmp);
                }
            }
        }

#endif // OPENGL_MM

// We're going to modify the WNDOBJ now.  Grab the per-WNDOBJ semaphore to
// keep it stable.  Don't release until after the driver is called.

        SEMOBJ soClient(pwo->hsem);

        roClient.bSwap(pwo);
        pwo->prgn->vStamp();                 // init iUniq
        pwo->vSetClip(pwo->prgn, erclClient);
        roClient.bDeleteRGNOBJAPI();         // delete handle too

// Call driver with the new WNDOBJ.

        if (pto->fl & WO_RGN_CLIENT_DELTA)
            pto->vUpdateDrvDelta(pwo, WOC_RGN_CLIENT_DELTA);
        if (pto->fl & WO_RGN_CLIENT)
            pto->vUpdateDrv(pwo, WOC_RGN_CLIENT);

// Let the sprite code know that there's a new WNDOBJ, now completely
// formed.        

        vSpWndobjChange(pto->pSurface->hdev(), pwo);
    }

// Update the remaining window client regions.

    GreClientRgnUpdated(GCR_WNDOBJEXISTS);
}

/******************************Member*Function*****************************\
* GreWindowInsteadOfClient
*
* Returns TRUE if the window area instead of the client area should be
* used for the WNDOBJ regions.
*
\**************************************************************************/

BOOL GreWindowInsteadOfClient(PVOID _pwo)
{
    PEWNDOBJ pwo = (PEWNDOBJ) _pwo;

    return(pwo->fl & WO_RGN_WINDOW);
}

/******************************Member*Function*****************************\
* GreClientRgnUpdated
*
* User calls this function after having updated all the vis/client
* region changes and before releasing the devlock.  We have to complete
* the remaining client region update operation.
*
* Gdi calls vForceClientRgnUpdate and this function after a new WNDOBJ
* is created to update the driver.
*
* This function should only be called when the calling thread has the
* usercrit and devlock in that order.
*
* If the GCR_DELAYFINALUPDATE flag is specified, the caller must
* subsequently call GreClientRgnDone.  If the driver specified
* the WO_DRAW_NOTIFY flag, the GCR_DELAYFINALUPDATE will suppress
* the WOC_DRAWN notification until GreClientRgnDone is called (or
* the next time GreClientRgnUpdated is called without
* GCR_DELAYFINALUPDATE).
*
* History:
*  Thu Jan 13 09:55:23 1994     -by-    Hock San Lee    [hockl]
* Rewrote it.
*  11-Nov-1993 -by- Wendy Wu [wendywu]
* Wrote it.
\**************************************************************************/

VOID APIENTRY GreClientRgnUpdated(FLONG flUpdate)
{
    PTRACKOBJ pto;
    PEWNDOBJ  pwo;

    DBGENTRY("GreClientRgnUpdated\n");

// Since we are holding the DEVLOCK to the active display, we can
// increment the VisRgn count here without doing an atomic increment.

    giVisRgnUniqueness++;

// Go any further only if some WNDOBJs exist.

    if (!(flUpdate & GCR_WNDOBJEXISTS))
    {
        return;
    }

// Assert that we are in user critical section and also hold the devlock.
// This ensures that no one is updating the hwnd.

    CHECKUSERCRITIN;

// Enter the semphore for window object.

    SEMOBJ so(ghsemWndobj);

// The surface client regions have changed.  Complete the remaining
// client region update.

    for (pto = gpto; pto; pto = pto->ptoNext)
    {
        if (!(pto->fl & WO_NOTIFIED))
            continue;

        pto->fl &= ~WO_NOTIFIED;

// We need to ensure that user holds the devlock before calling this function.
// Otherwise, some other threads may be drawing into the wrong client region.
// We do the check here because we don't have any pSurface information earlier.
//
// Also, we exclude the entire screen since we are going to touch windows all
// over the place and end with calling driver with WOC_COMPLETE which
// can have an effect anywhere on the screen.

        DEVEXCLUDEOBJ dxo;
        if (pto->pwo->hwnd)
        {
            RECTL rclSurf;
            HDEV  hdev = pto->pSurface->hdev();
            PDEVOBJ po(hdev);

            CHECKDEVLOCKIN2(pto->pSurface);

            ASSERTGDI(po.bValid(), "GreClientRgnUpdated: invalid pdevobj\n");

            if (po.bValid() && !po.bDisabled())
            {
                rclSurf.left = 0;
                rclSurf.top = 0;
                rclSurf.right = pto->pSurface->sizl().cx;
                rclSurf.bottom = pto->pSurface->sizl().cy;

                dxo.vExclude(hdev, &rclSurf, (ECLIPOBJ *) NULL);
            }
        }

// Traverse the chain and call the driver with un-changed windows if
// the WO_RGN_UPDATE_ALL and WO_RGN_CLIENT flags are set.

        if ((pto->fl & (WO_RGN_CLIENT|WO_RGN_UPDATE_ALL))
         == (WO_RGN_CLIENT|WO_RGN_UPDATE_ALL))
        {
            for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext)
                if (pwo->fl & WO_NOTIFIED)
                    pwo->fl &= ~WO_NOTIFIED;
                else
                {
                // Make WNDOBJ stable by holding the per-WNDOBJ semaphore
                // while we call the driver.

                    SEMOBJ soClient(pwo->hsem);

                    pto->vUpdateDrv(pwo, WOC_RGN_CLIENT);
                }
        }

// Update the surface WNDOBJ if requested.

        if (pto->fl & (WO_RGN_SURFACE|WO_RGN_SURFACE_DELTA))
        {
            PEWNDOBJ     pwoSurf = pto->pwoSurf;
            RGNMEMOBJTMP rmoTmp((BOOL)FALSE);
            RGNMEMOBJTMP rmoSurfNew((BOOL)FALSE);

            if (rmoTmp.bValid() && rmoSurfNew.bValid())
            {
// Construct the new surface region which is the entire surface minus
// the combined client regions.

                rmoSurfNew.vSet(&pwoSurf->rclClient);
                for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext)
                {
                    RGNOBJ  ro(pwo->prgn);
                    if (rmoTmp.iCombine(rmoSurfNew, ro, RGN_DIFF) != ERROR)
                        rmoSurfNew.bSwap(&rmoTmp);
                }

// If WO_RGN_SURFACE_DELTA is set, update the driver with the new surface delta.

                if (pto->fl & WO_RGN_SURFACE_DELTA)
                {
                    RGNOBJ  roSurf(pwoSurf->prgn);
                    if (rmoTmp.iCombine(rmoSurfNew, roSurf, RGN_DIFF) != ERROR)
                    {
                        pwoSurf->bSwap(&rmoTmp);
                        pwoSurf->prgn->vStamp();        // new iUniq
                        pwoSurf->vSetClip(pwoSurf->prgn, *(ERECTL *)&pwoSurf->rclClient);

                        pto->vUpdateDrvDelta(pwoSurf, WOC_RGN_SURFACE_DELTA);
                    }
                }

// Save the new surface region.
// The surface region may be the same as previous one here.  This code can be
// optimized a little.

                pwoSurf->bSwap(&rmoSurfNew);
                pwoSurf->prgn->vStamp();        // new iUniq
                pwoSurf->vSetClip(pwoSurf->prgn, *(ERECTL *)&pwoSurf->rclClient);

// Give the driver the new surface region.

                if (pto->fl & WO_RGN_SURFACE)
                    pto->vUpdateDrv(pwoSurf, WOC_RGN_SURFACE);
            }

        } // if (pto->fl & (WO_RGN_SURFACE|WO_RGN_SURFACE_DELTA))

// Send down the WOC_CHANGED to signify notification complete.

        pto->vUpdateDrv((PEWNDOBJ)NULL, WOC_CHANGED);

// Need WOC_DRAWN notification if requested.  Notification is delayed
// if GCR_DELAYFINALUPDATE is set.  Delayed notification is signaled
// by setting the WO_NEED_DRAW_NOTIFY flag in the trackobj and is
// handled in GreClientRgnDone.

        if (pto->fl & WO_DRAW_NOTIFY)
        {
            if (flUpdate & GCR_DELAYFINALUPDATE)
            {
                pto->fl |= WO_NEED_DRAW_NOTIFY;
            }
            else
            {
                pto->vUpdateDrv((PEWNDOBJ)NULL, WOC_DRAWN);
                pto->fl &= ~WO_NEED_DRAW_NOTIFY;
            }
        }
    } // for (pto = gpto; pto; pto = pto->ptoNext)
}

/******************************Public*Routine******************************\
* GreClientRgnDone
*
* If USER calls GreClientRgnUpdated with the GCR_DELAYFINALUPDATE flag
* set, it must subsequently call this function to complete the update.
*
* If the driver calls EngCreateWnd with the WO_NEED_DRAW_NOTIFY flag,
* the GreClientRgnUpdated function will complete the notification with
* a WOC_CHANGED followed by a WOC_DRAWN.  However, if the
* GCR_DELAYFINALUPDATE flag is specified when calling GreClientRgnUpdated,
* the WOC_DRAWN message is suppressed until GreClientRgnDone is called.
*
* If the driver does not call EngCreateWnd with the WO_NEED_DRAW_NOTIFY
* flag, GreClientRgnDone will only send the WOC_CHANGED notification and
* this function will have no effect.
*
* This function should only be called when the calling thread has the
* usercrit and devlock in that order.
*
* Note: Currently this function does not do anything if GCR_WNDOBJEXISTS
* is not set (GreClientRgnUpdated will update the vis rgn uniqneness and
* so must be called even if there are not WNDOBJs).  Therefore, for now
* it is acceptable for the caller to skip GreClientRgnDone if no WNDOBJs
* exist.
*
* History:
*  19-Dec-1997 -by- Gilman Wong [gilmanw]
* Wrote it.
\**************************************************************************/

VOID APIENTRY GreClientRgnDone(FLONG flUpdate)
{
    PTRACKOBJ pto;
    PEWNDOBJ pwo;

    DBGENTRY("GreClientRgnDone\n");

// Go any further only if some WNDOBJs exist.

    if (!(flUpdate & GCR_WNDOBJEXISTS))
    {
        return;
    }

// Assert that we are in user critical section.
// This ensures that no one is updating the hwnd.

    CHECKUSERCRITIN;

// Enter the semphore for window object.

    SEMOBJ so(ghsemWndobj);

// Check which tracking objects need WOC_DRAWN notification.

    for (pto = gpto; pto; pto = pto->ptoNext)
    {
        if (pto->pwo->hwnd)
        {
            CHECKDEVLOCKIN2(pto->pSurface);
        }

        if (pto->fl & WO_NEED_DRAW_NOTIFY)
        {
            pto->fl &= ~WO_NEED_DRAW_NOTIFY;

            pto->vUpdateDrv((PEWNDOBJ)NULL, WOC_DRAWN);
        }

// Inform the sprite code of the change in the WNDOBJ.

        for (pwo = pto->pwo; pwo; pwo = pwo->pwoNext)
        {
            vSpWndobjChange(pto->pSurface->hdev(), pwo);
        }

    } // for (pto = gpto; pto; pto = pto->ptoNext)
}

/******************************Public*Function*****************************\
* GreSetClientRgn
*
* User calls this function to update the client region in a WNDOBJ.
* After all the regions have been updated, user must call GreClientRgnUpdated
* to complete the update.
*
* User creates a new region to give to this function.  This function must
* delete the region before it returns!
*
* This function should only be called when the calling thread has the
* usercrit and devlock in that order.
*
* History:
*  Thu Jan 13 09:55:23 1994     -by-    Hock San Lee    [hockl]
* Wrote it.
\**************************************************************************/

VOID GreSetClientRgn(PVOID _pwoClient, HRGN hrgnClient, LPRECT prcClient)
{
    PEWNDOBJ pwoClient = (PEWNDOBJ)_pwoClient;

    DBGENTRY("GreSetClientRgn\n");

// Assert that we are in user critical section and also hold the devlock.
// This ensures that no one is updating the hwnd.

    CHECKUSERCRITIN;
    if (pwoClient->hwnd)
    {
        CHECKDEVLOCKIN2(pwoClient->pto->pSurface);
    }

// Validate hrgnClient.

    if (!hrgnClient)
    {
        ASSERTGDI(FALSE, "GreSetClientRgn: hrgnClient is NULL\n");
        return;
    }

// Validate pwoClient.

    if (!pwoClient->bValid())
    {
        ASSERTGDI(FALSE, "GreSetClientRgn: Invalid pwoClient\n");
        bDeleteRegion(hrgnClient);
        return;
    }

// The WNDOBJ should only be modified once per update.  A complete
// update includes a call to GreClientRgnUpdated.

#if DBG
    if ((pwoClient->fl & (WO_RGN_CLIENT|WO_RGN_UPDATE_ALL))
     == (WO_RGN_CLIENT|WO_RGN_UPDATE_ALL))
        if (pwoClient->fl & WO_NOTIFIED)
            DbgPrint("GreSetClientRgn: WNDOBJ updated more than once!\n");
#endif // DBG

// Get new and old regions.

    GreSetRegionOwner(hrgnClient, OBJECT_OWNER_PUBLIC);
    RGNOBJAPI roClient(hrgnClient,FALSE);
    ASSERTGDI(roClient.bValid(), "GreSetClientRgn: invalid hrgnClient\n");
    RGNOBJ    roOld(pwoClient->prgn);
    ERECTL    erclClient(prcClient->left,  prcClient->top,
                         prcClient->right, prcClient->bottom);

#ifdef OPENGL_MM

// Under Multi-mon we need to adjust the client region and
// client rectangle by the offset of the surface.
// Additionally we need to clip the client region to the
// surface of the TRACKOBJ.

    if (!(pwoClient->fl & WO_RGN_DESKTOP_COORD))
    {
        PDEVOBJ pdo(pwoClient->pto->pSurface->hdev());

        if (pdo.bValid())
        {
            if (pdo.bPrimary(pwoClient->pto->pSurface))
            {
                POINTL ptlOrigin;

                ptlOrigin.x = -pdo.pptlOrigin()->x;
                ptlOrigin.y = -pdo.pptlOrigin()->y;

                if ((ptlOrigin.x != 0) || (ptlOrigin.y != 0))
                {
// offset the region and window rect if necessary

                    roClient.bOffset(&ptlOrigin);
                    erclClient += ptlOrigin;      /* this offsets by ptl */
                }
            }
        }

        RGNMEMOBJTMP rmoTmp;
        RGNMEMOBJTMP rmoRcl;

        if (rmoTmp.bValid() && rmoRcl.bValid())
        {
// this clips the client region to the pto surface

            rmoRcl.vSet((RECTL *) &pwoClient->pto->erclSurf);
            rmoTmp.bCopy(roClient);
            roClient.iCombine(rmoTmp,rmoRcl,RGN_AND);

            if (rmoTmp.iCombine(roClient,rmoRcl,RGN_AND) != ERROR)
            {
                roClient.bSwap(&rmoTmp);
            }
        }
    }

#endif // OPENGL_MM

// If the regions are equal, no need to notify driver here.

    if (roOld.bEqual(roClient)
     && ((ERECTL *)&pwoClient->rclClient)->bEqual(erclClient))
    {
        roClient.bDeleteRGNOBJAPI();     // delete handle too
        return;
    }

// Enter the semphore for window object.

    SEMOBJ so(ghsemWndobj);

// Now the WNDOBJ is going to get updated.  Hold the per-WNDOBJ semaphore
// and keep it until we are done modifying and the driver update call has
// been made.

    SEMOBJ soClient(pwoClient->hsem);

// Give the driver the client region delta.
// The delta is valid for this call only!

    if (pwoClient->fl & WO_RGN_CLIENT_DELTA)
    {
        RGNMEMOBJTMP rmoDiff((BOOL)FALSE);
        if (rmoDiff.bValid() &&
            (rmoDiff.iCombine(roClient, roOld, RGN_DIFF) != ERROR))
        {
            pwoClient->bSwap(&rmoDiff);
            pwoClient->prgn->vStamp();          // new iUniq
            pwoClient->vSetClip(pwoClient->prgn, erclClient);

            pwoClient->pto->vUpdateDrvDelta(pwoClient, WOC_RGN_CLIENT_DELTA);
        }
    }

// Update the new client region in WNDOBJ.

    roClient.bSwap(pwoClient);
    pwoClient->prgn->vStamp();       // new iUniq
    pwoClient->vSetClip(pwoClient->prgn, erclClient);
    roClient.bDeleteRGNOBJAPI();     // delete handle too

// Give the driver the new client region.

    if (pwoClient->fl & WO_RGN_CLIENT)
    {
        pwoClient->pto->vUpdateDrv(pwoClient, WOC_RGN_CLIENT);
    }

// Mark that we have visited this WNDOBJ and TRACKOBJ.

    pwoClient->fl      |= WO_NOTIFIED;
    pwoClient->pto->fl |= WO_NOTIFIED;

    return;
}

/******************************Member*Function*****************************\
* WNDOBJ_cEnumStart
*
* Start the window client region enumeration for the window object.
*
* This function can be called from the wndobjchangeproc that is passed to
* EngCreateWnd.  It can also be called from DDI function where a WNDOBJ
* is given.
*
* This function should only be called when the calling thread has the
* devlock to ensure that there is no client region change.
*
* In future, we may want to add the pvConsumer and WNDOBJ pointer to the
* CLIPOBJ that is passed to the existing DDI.  In this way, the WNDOBJ
* is always available to the DDI instead of the selected few.
*
* History:
*  Thu Jan 13 09:55:23 1994     -by-    Hock San Lee    [hockl]
* Rewrote it.
*  11-Nov-1993 -by- Wendy Wu [wendywu]
* Wrote it.
\**************************************************************************/

extern "C" ULONG WNDOBJ_cEnumStart(
WNDOBJ  *pwo,
ULONG    iType,
ULONG    iDir,
ULONG    cLimit)
{
    DBGENTRY("WNDOBJ_cEnumStart\n");

    ASSERTGDI(((PEWNDOBJ)pwo)->bValid(), "WNDOBJ_cEnumStart: Invalid pwo\n");

// We need to ensure that the caller holds the devlock before calling this
// function.  Otherwise, some other threads may be drawing into the wrong
// client region.

    if (((PEWNDOBJ)pwo)->hwnd)
    {
        CHECKDEVLOCKIN2(((PEWNDOBJ)pwo)->pto->pSurface);
    }

    return (*(XCLIPOBJ *)pwo).cEnumStart(TRUE, iType, iDir, cLimit);
}

/******************************Member*Function*****************************\
* WNDOBJ_bEnum
*
* Enumerate the client region object in the window object.
*
* This function can be called from the wndobjchangeproc that is passed to
* EngCreateWnd.  It can also be called from DDI function where a WNDOBJ
* is given.
*
* This function should only be called when the calling thread has the
* devlock to ensure that there is no client region change.
*
* In future, we may want to add the pvConsumer and WNDOBJ pointer to the
* CLIPOBJ that is passed to the existing DDI.  In this way, the WNDOBJ
* is always available to the DDI instead of the selected few.
*
* History:
*  Thu Jan 13 09:55:23 1994     -by-    Hock San Lee    [hockl]
* Rewrote it.
*  11-Nov-1993 -by- Wendy Wu [wendywu]
* Wrote it.
\**************************************************************************/

extern "C" BOOL WNDOBJ_bEnum(
WNDOBJ  *pwo,
ULONG   cj,
ULONG   *pul)
{
    DBGENTRY("WNDOBJ_bEnum\n");

    ASSERTGDI(((PEWNDOBJ)pwo)->bValid(), "WNDOBJ_bEnum: Invalid pwo\n");

// We need to ensure that the caller holds the devlock before calling this
// function.  Otherwise, some other threads may be drawing into the wrong
// client region.

    if (((PEWNDOBJ)pwo)->hwnd)
    {
        CHECKDEVLOCKIN2(((PEWNDOBJ)pwo)->pto->pSurface);
    }

    return (*(XCLIPOBJ *)pwo).bEnum(cj, (VOID *)pul);
}

/******************************Member*Function*****************************\
* WNDOBJ_vSetConsumer
*
* Set the driver pvConsumer value in the window object.  It should be
* used to modify the existing pvConsumer value.
*
* This function can be called from the wndobjchangeproc that is passed to
* EngCreateWnd.  It can also be called from DDI function where a WNDOBJ
* is given.
*
* This function should only be called when the calling thread has the
* devlock to ensure that there is no client region change.
*
* History:
*  Thu Jan 13 09:55:23 1994     -by-    Hock San Lee    [hockl]
* Wrote it.
\**************************************************************************/

extern "C" VOID WNDOBJ_vSetConsumer(
WNDOBJ  *_pwo,
PVOID   pvConsumer)
{
    PEWNDOBJ pwo = (PEWNDOBJ)_pwo;

    DBGENTRY("WNDOBJ_vSetConsumer\n");

    ASSERTGDI(pwo->bValid(), "WNDOBJ_vSetConsumer: Invalid pwo\n");

// We need to ensure that the caller holds the devlock before calling this
// function.  Otherwise, some other threads may be drawing into the wrong
// client region.

    if (pwo->hwnd)
    {
        CHECKDEVLOCKIN2(pwo->pto->pSurface);
    }

// Do not allow changes to surface wndobj.  One reason is that there is
// no delete notification for surface wndobj.

    if (pwo == pwo->pto->pwoSurf)
    {
        KdPrint(("WNDOBJ_vSetConsumer: cannot modify surface wndobj!\n"));
        return;
    }

    pwo->pvConsumer = pvConsumer;
}

/******************************Member*Function*****************************\
* EWNDOBJ::vOffset
*
* Offset the WNDOBJ by some vector.
\**************************************************************************/

VOID EWNDOBJ::vOffset(LONG x, LONG y)
{
    if ((x != 0) || (y != 0))
    {
        EPOINTL eptl(x, y);
    
        bOffset(&eptl);
        *((ERECTL*) &rclBounds) += eptl;
        *((ERECTL*) &rclClient) += eptl;
    }
}