#include "precomp.h"


//
// HET.C
// Hosted Entity Tracker, NT Display Driver version
//
// Copyright(c)Microsoft 1997-
//

#include <limits.h>

//
// HET_DDTerm()
//
void HET_DDTerm(void)
{
    LPHET_WINDOW_MEMORY pMem;

    DebugEntry(HET_DDTerm);

    //
    // Clean up any window/graphics tracking stuff
    //
    g_hetDDDesktopIsShared = FALSE;
    HETDDViewing(NULL, FALSE);
    HETDDUnshareAll();


    //
    // Loop through the memory list blocks, freeing each.  Then clear
    // the Window and Free lists.
    //                           
    while (pMem = COM_BasedListFirst(&g_hetMemoryList, FIELD_OFFSET(HET_WINDOW_MEMORY, chain)))
    {
        TRACE_OUT(("HET_DDTerm:  Freeing memory block %lx", pMem));

        COM_BasedListRemove(&(pMem->chain));
        EngFreeMem(pMem);
    }

    //
    // Clear the window linked lists since they contain elements in
    // the now free memory block.
    //
    COM_BasedListInit(&g_hetFreeWndList);
    COM_BasedListInit(&g_hetWindowList);

    DebugExitVOID(HET_DDTerm);
}


//
// HET_DDProcessRequest - see host.h
//
ULONG HET_DDProcessRequest(SURFOBJ  *pso,
                               UINT  cjIn,
                               void *   pvIn,
                               UINT  cjOut,
                               void *   pvOut)
{
    ULONG rc = TRUE;
    LPOSI_ESCAPE_HEADER  pHeader;

    DebugEntry(HET_DDProcessRequest);

    pHeader = pvIn;
    TRACE_OUT(( "Request %#x", pHeader->escapeFn));
    switch (pHeader->escapeFn)
    {
        case HET_ESC_SHARE_WINDOW:
        {
            if ((cjIn != sizeof(HET_SHARE_WINDOW)) ||
                (cjOut != sizeof(HET_SHARE_WINDOW)))
            {
                ERROR_OUT(("HET_DDProcessRequest:  Invalid sizes %d, %d for HET_ESC_SHARE_WINDOW",
                    cjIn, cjOut));
                rc = FALSE;
                DC_QUIT;
            }

            ((LPHET_SHARE_WINDOW)pvOut)->result =
                HETDDShareWindow(pso, (LPHET_SHARE_WINDOW)pvIn);
        }
        break;

        case HET_ESC_UNSHARE_WINDOW:
        {
            if ((cjIn != sizeof(HET_UNSHARE_WINDOW)) ||
                (cjOut != sizeof(HET_UNSHARE_WINDOW)))
            {
                ERROR_OUT(("HET_DDProcessRequest:  Invalid sizes %d, %d for HET_ESC_UNSHARE_WINDOW",
                    cjIn, cjOut));
                rc = FALSE;
                DC_QUIT;
            }

            HETDDUnshareWindow((LPHET_UNSHARE_WINDOW)pvIn);
        }
        break;

        case HET_ESC_UNSHARE_ALL:
        {
            if ((cjIn != sizeof(HET_UNSHARE_ALL)) ||
                (cjOut != sizeof(HET_UNSHARE_ALL)))
            {
                ERROR_OUT(("HET_DDProcessRequest:  Invalid sizes %d, %d for HET_ESC_UNSHARE_ALL",
                    cjIn, cjOut));
                rc = FALSE;
                DC_QUIT;
            }

            HETDDUnshareAll();
        }
        break;

        case HET_ESC_SHARE_DESKTOP:
        {
            if ((cjIn != sizeof(HET_SHARE_DESKTOP)) ||
                (cjOut != sizeof(HET_SHARE_DESKTOP)))
            {
                ERROR_OUT(("HET_DDProcessRequest:  Invalid sizes %d, %d for HET_ESC_SHARE_DESKTOP",
                    cjIn, cjOut));
                rc = FALSE;
                DC_QUIT;
            }

            g_hetDDDesktopIsShared = TRUE;
        }
        break;

        case HET_ESC_UNSHARE_DESKTOP:
        {
            if ((cjIn != sizeof(HET_UNSHARE_DESKTOP)) ||
                (cjOut != sizeof(HET_UNSHARE_DESKTOP)))
            {
                ERROR_OUT(("HET_DDProcessRequest:  Invalid sizes %d, %d for HET_ESC_UNSHARE_DESKTOP",
                    cjIn, cjOut));
                rc = FALSE;
                DC_QUIT;
            }

            g_hetDDDesktopIsShared = FALSE;
            HETDDViewing(NULL, FALSE);
        }
        break;

        case HET_ESC_VIEWER:
        {
            //
            // We may turn OFF viewing but keep stuff shared and the windows
            // tracked -- hosting a meeting and sharing something, for 
            // example. 
            //
            if ((cjIn != sizeof(HET_VIEWER)) ||
                (cjOut != sizeof(HET_VIEWER)))
            {
                ERROR_OUT(("HET_DDProcessRequest:  Invalid sizes %d, %d for HET_ESC_VIEWER",
                    cjIn, cjOut));
                rc = FALSE;
                DC_QUIT;
            }

            HETDDViewing(pso, (((LPHET_VIEWER)pvIn)->viewersPresent != 0));
            break;
        }

        default:
        {
            ERROR_OUT(( "Unknown request type %#x", pHeader->escapeFn));
            rc = FALSE;
        }
        break;
    }

DC_EXIT_POINT:
    DebugExitDWORD(HET_DDProcessRequest, rc);
    return(rc);
}


//
// HET_DDOutputIsHosted - see host.h
//
BOOL HET_DDOutputIsHosted(POINT pt)
{
    BOOL              rc = FALSE;
    UINT              j;
    LPHET_WINDOW_STRUCT  pWnd;

    DebugEntry(HET_DDOutputIsHosted);

    //
    // Now check to see if the desktop is shared - if it is then simply
    // return TRUE.
    //
    if (g_hetDDDesktopIsShared)
    {
        rc = TRUE;
        DC_QUIT;
    }

    //
    // Search through the window list
    //
    pWnd = COM_BasedListFirst(&g_hetWindowList, FIELD_OFFSET(HET_WINDOW_STRUCT, chain));
    while (pWnd != NULL)
    {
        //
        // Search each enumerated rectangle
        //
        TRACE_OUT(( "Window %#x has %u rectangle(s)",
                pWnd, pWnd->rects.c));
        for (j = 0; j < pWnd->rects.c; j++)
        {
            //
            // See whether the point passed in is within this rectangle.
            // Note that at this point we are dealing with exclusive
            // co-ordinates.
            //
            if ((pt.x >= pWnd->rects.arcl[j].left) &&
                (pt.x <  pWnd->rects.arcl[j].right) &&
                (pt.y >= pWnd->rects.arcl[j].top) &&
                (pt.y <  pWnd->rects.arcl[j].bottom))
            {
                TRACE_OUT((
                    "Pt {%d, %d}, in win %#x rect %u {%ld, %ld, %ld, %ld}",
                    pt.x, pt.y, pWnd->hwnd, j,
                    pWnd->rects.arcl[j].left, pWnd->rects.arcl[j].right,
                    pWnd->rects.arcl[j].top, pWnd->rects.arcl[j].bottom ));

                //
                // Found it!  Re-order the list, most recently used first
                //
                COM_BasedListRemove(&(pWnd->chain));
                COM_BasedListInsertAfter(&g_hetWindowList, &(pWnd->chain));

                //
                // Stop looking
                //
                rc = TRUE;
                DC_QUIT;
            }

            TRACE_OUT(( "Pt not in win %#x rect %u {%ld, %ld, %ld, %ld}",
                    pWnd->hwnd, j,
                    pWnd->rects.arcl[j].left, pWnd->rects.arcl[j].right,
                    pWnd->rects.arcl[j].top, pWnd->rects.arcl[j].bottom ));

        } // for all rectangles

        //
        // Move on to next window
        //
        pWnd = COM_BasedListNext(&g_hetWindowList, pWnd, FIELD_OFFSET(HET_WINDOW_STRUCT, chain));
    }

DC_EXIT_POINT:
    DebugExitBOOL(HET_DDOutputIsHosted, rc);
    return(rc);
}


//
// HET_DDOutputRectIsHosted - see host.h
//
BOOL HET_DDOutputRectIsHosted(LPRECT pRect)
{
    BOOL              rc = FALSE;
    UINT              j;
    LPHET_WINDOW_STRUCT  pWnd;
    RECT              rectIntersect;

    DebugEntry(HET_DDOutputRectIsHosted);

    //
    // Now check to see if the desktop is shared - if it is then simply
    // return TRUE.
    //
    if (g_hetDDDesktopIsShared)
    {
        rc = TRUE;
        DC_QUIT;
    }

    //
    // Search through the window list
    //
    pWnd = COM_BasedListFirst(&g_hetWindowList, FIELD_OFFSET(HET_WINDOW_STRUCT, chain));
    while (pWnd != NULL)
    {
        //
        // Search each enumerated rectangle
        //
        TRACE_OUT(( "Window %#x has %u rectangle(s)",
                pWnd, pWnd->rects.c));
        for (j = 0; j < pWnd->rects.c; j++)
        {
            //
            // See whether the rect passed in intersects this rectangle.
            // Note that at this point we are dealing with exclusive
            // co-ordinates.
            //
            rectIntersect.left = max( pRect->left,
                                         pWnd->rects.arcl[j].left );
            rectIntersect.top = max( pRect->top,
                                        pWnd->rects.arcl[j].top );
            rectIntersect.right = min( pRect->right,
                                          pWnd->rects.arcl[j].right );
            rectIntersect.bottom = min( pRect->bottom,
                                           pWnd->rects.arcl[j].bottom );

            //
            // If the intersection rectangle is well-ordered and non-NULL
            // then we have an intersection.
            //
            // The rects that we are dealing with are exclusive.
            //
            if ((rectIntersect.left < rectIntersect.right) &&
                (rectIntersect.top < rectIntersect.bottom))
            {
                TRACE_OUT((
             "Rect  {%d, %d, %d, %d} intersects win %#x rect %u {%ld, %ld, %ld, %ld}",
                    pRect->left, pRect->top, pRect->right, pRect->bottom,
                    pWnd, j,
                    pWnd->rects.arcl[j].left, pWnd->rects.arcl[j].right,
                    pWnd->rects.arcl[j].top, pWnd->rects.arcl[j].bottom ));

                //
                // Found it!  Re-order the list, most recently used first
                //
                COM_BasedListRemove(&(pWnd->chain));
                COM_BasedListInsertAfter(&g_hetWindowList, &(pWnd->chain));

                //
                // Stop looking
                //
                rc = TRUE;
                DC_QUIT;
            }

            TRACE_OUT(( "Rect not in win %#x rect %u {%ld, %ld, %ld, %ld}",
                    pWnd, j,
                    pWnd->rects.arcl[j].left, pWnd->rects.arcl[j].right,
                    pWnd->rects.arcl[j].top, pWnd->rects.arcl[j].bottom ));

        } // for all rectangles

        //
        // Move on to next window
        //
        pWnd = COM_BasedListNext(&g_hetWindowList, pWnd, FIELD_OFFSET(HET_WINDOW_STRUCT, chain));
    }

DC_EXIT_POINT:
    DebugExitBOOL(HET_DDOutputRectIsHosted, rc);
    return(rc);
}


//
//
// Name:        HETDDVisRgnCallback
//
// Description: WNDOBJ Callback
//
// Params:      pWo - pointer to the WNDOBJ which has changed
//              fl  - flags (se NT DDK documentation)
//
// Returns:     none
//
// Operation:
//
//
VOID CALLBACK HETDDVisRgnCallback(PWNDOBJ pWo, FLONG fl)
{
    ULONG               count;
    int               size;
    LPHET_WINDOW_STRUCT  pWnd;
    RECTL             rectl;
    UINT              i;

    DebugEntry(HETDDVisRgnCallback);

    //
    // Some calls pass a NULL pWo - exit now in this case
    //
    if (pWo == NULL)
    {
        DC_QUIT;
    }

    //
    // Find the window structure for this window
    //
    pWnd = pWo->pvConsumer;
    if (pWnd == NULL)
    {
        ERROR_OUT(( "Wndobj %x (fl %x) has no window structure", pWo, fl));
        DC_QUIT;
    }

    //
    // Check for window deletion
    //
    if (fl & WOC_DELETE)
    {
        TRACE_OUT(( "Wndobj %x (structure %x) deleted", pWo, pWo->pvConsumer));

        // ASSERT the window is valid
        ASSERT(pWnd->hwnd != NULL);

        //
        // Move the window from the active to the free list
        //
        COM_BasedListRemove(&(pWnd->chain));
        COM_BasedListInsertAfter(&g_hetFreeWndList, &(pWnd->chain));

#ifdef DEBUG
        // Check if this has reentrancy problems
        pWnd->hwnd = NULL;
#endif

        //
        // Do any processing if this is the last window to be unshared.
        //
        // If we are not keeping track of any windows, the first pointer in
        // the list will point to itself, ie list head->next == 0
        //
        if (g_hetWindowList.next == 0)
        {
            HETDDViewing(NULL, FALSE);
        }

        //
        // Exit now
        //
        DC_QUIT;
    }

    //
    // If we get here, this callback must be for a new visible region on a
    // tracked window.
    //

    //
    // Start the enumeration.  This function is supposed to count the
    // rectangles, but it always returns 0.
    //
    WNDOBJ_cEnumStart(pWo, CT_RECTANGLES, CD_ANY, 200);

    //
    // BOGUS BUGBUG LAURABU (perf opt for NT):
    //
    // NT will enum up to HET_WINDOW_RECTS at a time.  Note that the enum
    // function returns FALSE if, after obtaining the current batch, none
    // are left to grab the next time.
    //
    // If the visrgn is composed of more than that, we will wipe out the 
    // previous set of rects, then ensure that the bounding box of the 
    // preceding rects is the last rect in the list.
    //
    // This pulls in several cases.  For example if there are n visrgn piece
    // rects, and n == c*HET_WINDOW_RECTS + 1, we will end up with 2 entries:
    //      * The last piece rect
    //      * The bounding box of the previous n-1 piece rects 
    // A lot of output may be accumulated in deadspace as a result.
    //
    // A better algorithm may be to fill the first HET_WINDOW_RECTS-1 slots,
    // then union the rest into the last rectangle.  That way we make use of
    // all the slots.  But this could be awkward, since we need a scratch
    // ENUM_RECT struct rather than using the HET_WINDOW_STRUCT directly.
    //

    //
    // First time through, enumerate HET_WINDOW_RECTS rectangles.
    // Subsequent times, enumerate HET_WINDOW_RECTS-1 (see bottom of loop).
    // This guarantees that there will be room to store a combined
    // rectangle when we finally finish enumerating them.
    //
    pWnd->rects.c = HET_WINDOW_RECTS;
    rectl.left   = LONG_MAX;
    rectl.top    = LONG_MAX;
    rectl.right  = 0;
    rectl.bottom = 0;

    //
    // Enumerate the rectangles
    // NOTE that WNDOBJ_bEnum returns FALSE when there is nothing left
    // to enumerate AFTER grabbing this set.
    //

    while (WNDOBJ_bEnum(pWo, sizeof(pWnd->rects), (ULONG *)&pWnd->rects))
    {
#ifdef _DEBUG
        {
            char    trcStr[200];
            UINT    j;

            sprintf(trcStr, "WNDOBJ %p %d: ", pWo, pWnd->rects.c);

            for (j = 0; j < pWnd->rects.c; j++)
            {
                sprintf(trcStr, "%s {%ld, %ld, %ld, %ld} ", trcStr,
                    pWnd->rects.arcl[j].left, pWnd->rects.arcl[j].top,
                    pWnd->rects.arcl[j].right, pWnd->rects.arcl[j].bottom);
                if ((j & 3) == 3)       // output every 4th rect
                {
                    TRACE_OUT(( "%s", trcStr));
                    strcpy(trcStr, "                ");
                }
            }
            if ((j & 3) != 0)           // if any rects left
            {
                TRACE_OUT(( "%s", trcStr));
            }
        }
#endif

        //
        // Combine the preceding rectangles into one bounding rectangle
        //
        for (i = 0; i < pWnd->rects.c; i++)
        {
            if (pWnd->rects.arcl[i].left < rectl.left)
            {
                rectl.left = pWnd->rects.arcl[i].left;
            }
            if (pWnd->rects.arcl[i].top < rectl.top)
            {
                rectl.top = pWnd->rects.arcl[i].top;
            }
            if (pWnd->rects.arcl[i].right > rectl.right)
            {
                rectl.right = pWnd->rects.arcl[i].right;
            }
            if (pWnd->rects.arcl[i].bottom > rectl.bottom)
            {
                rectl.bottom = pWnd->rects.arcl[i].bottom;
            }
        }
        TRACE_OUT(( "Combined into {%ld, %ld, %ld, %ld}",
                rectl.left, rectl.top, rectl.right, rectl.bottom));

        //
        // Second & subsequent times, enumerate HET_WINDOW_RECTS-1
        //
        pWnd->rects.c = HET_WINDOW_RECTS - 1;
    }

    //
    // If any combining was done, save the combined rectangle now.
    //
    if (rectl.right != 0)
    {
        pWnd->rects.arcl[pWnd->rects.c] = rectl;
        pWnd->rects.c++;
        TRACE_OUT(( "Add combined rectangle to list"));
    }

    //
    // On the assumption that this WNDOBJ is the most likely to be the
    // target of the next output command, move it to the top of the list.
    //
    COM_BasedListRemove(&(pWnd->chain));
    COM_BasedListInsertAfter(&g_hetWindowList, &(pWnd->chain));

    //
    // Return to caller
    //
DC_EXIT_POINT:
    DebugExitVOID(HETDDVisRgnCallback);
}


//
//
// Name:        HETDDShareWindow
//
// Description: Share a window (DD processing)
//
// Params:      pso      - SURFOBJ
//              pReq     - request received from DrvEscape
//
//
BOOL HETDDShareWindow(SURFOBJ *pso, LPHET_SHARE_WINDOW  pReq)
{
    PWNDOBJ            pWo;
    FLONG              fl = WO_RGN_CLIENT | WO_RGN_UPDATE_ALL | WO_RGN_WINDOW;
    LPHET_WINDOW_STRUCT pWnd;
    BOOL                rc = FALSE;

    DebugEntry(HETDDShareWindow);

    ASSERT(!g_hetDDDesktopIsShared);

    //
    // Try to track the window
    //
    pWo = EngCreateWnd(pso, (HWND)pReq->winID, HETDDVisRgnCallback, fl, 0);

    //
    // Failed to track window - exit now
    //
    if (pWo == 0)
    {
        ERROR_OUT(( "Failed to track window %#x", pReq->winID));
        DC_QUIT;
    }

    //
    // Window is already tracked.  This happens when an invisible window is
    // shown in a process the USER shared, and we caught its create.
    //
    if (pWo == (PWNDOBJ)-1)
    {
        //
        // No more to do here
        //
        TRACE_OUT(( "Window %#x already tracked", pReq->winID));
        rc = TRUE;
        DC_QUIT;
    }

    //
    // Add window into our list.
    //

    //
    // Find free window structure
    //
    pWnd = COM_BasedListFirst(&g_hetFreeWndList, FIELD_OFFSET(HET_WINDOW_STRUCT, chain));

    //
    // If no free structures, grow the list
    //
    if (pWnd == NULL)
    {
        if (!HETDDAllocWndMem())
        {
            ERROR_OUT(( "Unable to allocate new window structures"));
            DC_QUIT;
        }

        pWnd = COM_BasedListFirst(&g_hetFreeWndList, FIELD_OFFSET(HET_WINDOW_STRUCT, chain));
    }

    //
    // Fill in the structure
    //
    TRACE_OUT(( "Fill in details for new window"));
    pWnd->hwnd     = (HWND)pReq->winID;
    pWnd->wndobj   = pWo;

    //
    // Set this to zero.  There's a brief period between the time we put
    // this in our tracked list and the time we get called back to recalc
    // the visrgn (because the ring 3 code invalidates the window completely).
    // We might get graphical output and we don't want to parse garbage
    // from this window's record.
    //
    pWnd->rects.c  = 0;

    //
    // Move the window structure from free to active list
    //
    COM_BasedListRemove(&(pWnd->chain));
    COM_BasedListInsertAfter(&g_hetWindowList, &(pWnd->chain));

    //
    // Save backwards pointer in the WNDOBJ
    // THIS MUST BE LAST since our callback can happen anytime afterwards.
    //
    // NOTE that the window's visrgn rects get into our list because the
    // ring3 code completely invalidates the window, causing the callback
    // to get called.
    //
    TRACE_OUT(( "Save pointer %#lx in Wndobj %#x", pWnd, pWo));
    WNDOBJ_vSetConsumer(pWo, pWnd);

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitBOOL(HETDDShareWindow, rc);
    return(rc);
}


//
//
// Name:        HETDDUnshareWindow
//
// Description: Unshare a window (DD processing)
//
//
//
void HETDDUnshareWindow(LPHET_UNSHARE_WINDOW  pReq)
{
    LPHET_WINDOW_STRUCT  pWnd, pNextWnd;

    DebugEntry(HETDDUnshareWindow);

    TRACE_OUT(( "Unshare %x", pReq->winID));
    //
    // Scan window list for this window and its descendants
    //
    pWnd = COM_BasedListFirst(&g_hetWindowList, FIELD_OFFSET(HET_WINDOW_STRUCT, chain));
    while (pWnd != NULL)
    {
        //
        // If this window is being unshared, free it
        //
        pNextWnd = COM_BasedListNext(&g_hetWindowList, pWnd, FIELD_OFFSET(HET_WINDOW_STRUCT, chain));

        if (pWnd->hwnd == (HWND)pReq->winID)
        {
            TRACE_OUT(( "Unsharing %x", pReq->winID));

            //
            // Stop tracking the window
            //
            HETDDDeleteAndFreeWnd(pWnd);
        }

        //
        // Go on to (previously saved) next window
        //
        pWnd = pNextWnd;
    }

    //
    // Return to caller
    //
    DebugExitVOID(HETDDUnshareWindow);
}


//
//
// Name:        HETDDUnshareAll
//
// Description: Unshare all windows (DD processing) (what did you expect)
//
//
void HETDDUnshareAll(void)
{
    LPHET_WINDOW_STRUCT pWnd;

    DebugEntry(HETDDUnshareAll);

    //
    // Clear all window structures
    //
    while (pWnd = COM_BasedListFirst(&g_hetWindowList, FIELD_OFFSET(HET_WINDOW_STRUCT, chain)))
    {
        TRACE_OUT(( "Unshare Window structure %x", pWnd));

        //
        // Stop tracking the window
        //
        HETDDDeleteAndFreeWnd(pWnd);
    }

    //
    // Return to caller
    //
    DebugExitVOID(HETDDUnshareAll);
}


//
//
// Name:        HETDDAllocWndMem
//
// Description: Allocate memory for a (new) window list
//
// Parameters:  None
//
//
BOOL HETDDAllocWndMem(void)
{
    BOOL             rc = FALSE;
    int              i;
    LPHET_WINDOW_MEMORY pNew;

    DebugEntry(HETDDAllocWndMem);

    //
    // Allocate a new strucure
    //
    pNew = EngAllocMem(FL_ZERO_MEMORY, sizeof(HET_WINDOW_MEMORY), OSI_ALLOC_TAG);
    if (pNew == NULL)
    {
        ERROR_OUT(("HETDDAllocWndMem: unable to allocate memory"));
        DC_QUIT;
    }

    //
    // Add this memory block to the list of memory blocks
    //
    COM_BasedListInsertAfter(&g_hetMemoryList, &(pNew->chain));

    //
    // Add all new entries to free list
    //
    TRACE_OUT(("HETDDAllocWndMem: adding new entries to free list"));
    for (i = 0; i < HET_WINDOW_COUNT; i++)
    {
        COM_BasedListInsertAfter(&g_hetFreeWndList, &(pNew->wnd[i].chain));
    }

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitBOOL(HETDDAllocWndMem, rc);
    return(rc);
}

//
//
// Name:        HETDDDeleteAndFreeWnd
//
// Description: Delete and window and free its window structure
//
// Parameters:  pWnd - pointer to window structure to delete & free
//
// Returns:     none
//
// Operation:   Ths function stops tracking a window and frees its memory
//
//
void HETDDDeleteAndFreeWnd(LPHET_WINDOW_STRUCT pWnd)
{
    DebugEntry(HETDDDeleteAndFreeWnd);

    //
    // Stop tracking the window
    //
    EngDeleteWnd(pWnd->wndobj);

    //
    // NOTE LAURABU!  EngDeleteWnd() will call the VisRgnCallback with
    // WO_DELETE, which will cause us to exectute a duplicate of exactly 
    // the code below.  So why do it twice (which is scary anyway), especially
    // the stop hosting code?
    //
    ASSERT(pWnd->hwnd == NULL);

    //
    // Return to caller
    //
    DebugExitVOID(HETDDDeleteAndFreeWnd);
}


//
// HETDDViewers()
//
// Called when viewing of our shared apps starts/stops.  Naturally, no longer
// sharing anything stops viewing also.
//
void HETDDViewing
(
    SURFOBJ *   pso,
    BOOL        fViewers
)
{
    DebugEntry(HETDDViewers);

    if (g_oeViewers != fViewers)
    {
        g_oeViewers = fViewers;
        CM_DDViewing(pso, fViewers);

        if (g_oeViewers)
        {
            //
            // Force palette grab.
            //
            g_asSharedMemory->pmPaletteChanged = TRUE;
        }
    }

    DebugExitVOID(HETDDViewing);
}


