#include "precomp.h"


//
// PM.CPP
// Palette Manager
//
// Copyright(c) Microsoft 1997-
//

#define MLZ_FILE_ZONE  ZONE_CORE

//
//
// PALETTE MANAGER (PM) OVERVIEW
//
// Palette Manager is responsible for sending palette packets.  A palette
// packet:
//
// (1) indicates the colors being used on the host machine - and therefore
// specifies which colors the remote machine should use if it can choose
// (e.g.  by selecting and realizing the given colors into the display
// hardware).  [A palette packet may not contain the exact colors being
// used on the host if the protocol bpp is different from the host bpp].
//
// (2) specifies the colors which correspond to the values in bitmap
// (screen) data i.e.  the values in 4bpp and 8bpp bitmap data are indices
// into the table of colors sent in the palette packet.
//
//
// (1) affects order replay and (2) affects screen data replay, so a
// correct palette packet must be sent (by calling
// PM_MaybeSendPalettePacket) before a batch of updates are sent.
//
// Palette Manager also handles incoming palette packets from other parties
// in the conference and creates corresponding local palettes which the
// Update Receiver can query and use when processing updates.
//
// When a new palette packet is sent (e.g.  due to the System Palette
// changing), all shared areas of the screen will be retransmitted in due
// course.  A receiving Palette Manager therefore does not have to (and
// should not attempt to) convert any updates/bitmaps that have been
// received prior to the arrival of the new palette packet.
//
//

//
// PM strategy when network packets cannot be allocated.
//
// PM_MaybeSendPalettePacket returns a boolean indicating whether it has
// succesfully sent a palette packet.  The USR will only send updates if
// the corresponding palette packet is successfully sent.
//
//


const COLORREF s_apmGreyRGB[PM_GREY_COUNT] =
{
    PM_GREY1,
    PM_GREY2,
    PM_GREY3,
    PM_GREY4,
    PM_GREY5
};



//
// PM_PartyLeftShare()
//
void  ASShare::PM_PartyLeftShare(ASPerson * pasPerson)
{
    DebugEntry(ASShare::PM_PartyLeftShare);

    ValidatePerson(pasPerson);

    // This should be cleared already!
    ASSERT(!pasPerson->pmcColorTable);
    ASSERT(!pasPerson->apmColorTable);
    ASSERT(!pasPerson->pmPalette);

    DebugExitVOID(ASShare::PM_PartyLeftShare);
}



//
// PM_RecalcCaps()
//
// This calculates the PM hosting caps when
//      * we start to host
//      * we're hosting and somebody joins the share
//      * we're hosting and somebody leaves the share
//
// This can GO AWAY WHEN 2.x COMPAT IS GONE -- no more min() of cache size
//
void  ASShare::PM_RecalcCaps(BOOL fJoiner)
{
    ASPerson *  pasT;

    DebugEntry(ASShare::PM_RecalcCaps);

    if (!m_pHost || !fJoiner)
    {
        //
        // Nothing to do if we're not hosting.  And also, if somebody has
        // left, no recalculation -- 2.x didn't.
        //
        DC_QUIT;
    }

    ValidatePerson(m_pasLocal);

    //
    // NOTE:
    // The default size is 6 palettes cached.  The result is going to be
    // <= that number.  There's no point in recreating the cache, it's
    // so small.
    //
    m_pHost->m_pmNumTxCacheEntries = m_pasLocal->cpcCaps.palette.capsColorTableCacheSize;

DC_EXIT_POINT:
    DebugExitVOID(ASShare::PM_Recalccaps);
}


//
// PM_HostStarting()
//
// Called when we start to host; sets up color palette stuff and creates
// outgoing palette cache
//
BOOL  ASHost::PM_HostStarting(void)
{
    BOOL    rc = FALSE;
    TSHR_COLOR  localPalColors[PM_NUM_8BPP_PAL_ENTRIES];

    DebugEntry(ASHost::PM_HostStarting);

    //
    // Get palette caps.  NOTE PM_RecalcCaps must be called AFTER
    // USR_RecalcCaps(), because that updates m_usrSendingBPP.
    //
    if (g_usrPalettized)
    {
        ASSERT(g_usrScreenBPP <= 8);

        ZeroMemory(localPalColors, sizeof(localPalColors));

        //
        // Now create the Local Palette.
        //
        if (!m_pShare->PM_CreatePalette(COLORS_FOR_BPP(g_usrScreenBPP),
                localPalColors, &m_pmTxPalette))
        {
            ERROR_OUT(( "Failed to create Local Palette"));
            DC_QUIT;
        }
    }
    else
    {
        m_pmTxPalette = (HPALETTE)GetStockObject(DEFAULT_PALETTE);
        PMGetGrays();
    }

    //
    // With NM 3.0, why not just create a receive cache the size that
    // the host specifies in his caps?
    //
    // So I did that.  For back compat, OUTGOING caches use the min size.
    // When we only have to be compatible with NM 3.0 and up, we won't
    // have to do this min stuff.
    //
    // Note similar code in CM, SSI, and SBC
    //

    // Figure out how many outgoing entries we can actually use
    m_pShare->PM_RecalcCaps(TRUE);

    //
    // Create the PM color table cache with a single eviction
    // category.
    //
    if (!CH_CreateCache(&m_pmTxCacheHandle, TSHR_PM_CACHE_ENTRIES,
            1, 0, PMCacheCallback))
    {
        ERROR_OUT(("Could not create PM cache"));
        DC_QUIT;
    }

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitBOOL(ASHost::PM_HostStarting, rc);
    return(rc);
}



//
// PM_HostEnded()
//
// We free resources created when we started to host
//
void  ASHost::PM_HostEnded(void)
{
    DebugEntry(ASHost::PM_HostEnded);

    if (m_pmTxPalette)
    {
        m_pShare->PM_DeletePalette(m_pmTxPalette);
        m_pmTxPalette = NULL;
    }

    if (m_pmTxCacheHandle)
    {
        CH_DestroyCache(m_pmTxCacheHandle);
        m_pmTxCacheHandle = 0;
        m_pmNumTxCacheEntries = 0;
    }

    DebugExitVOID(ASHost::PM_HostEnded);
}



//
// PM_ViewStarting()
//
// For 3.0 nodes, we create the PM cache each time they start hosting
// For 2.x nodes, we create the PM cache once and use it until they leave
//      the share.
//
BOOL  ASShare::PM_ViewStarting(ASPerson * pasPerson)
{
    BOOL    rc = FALSE;

    DebugEntry(ASShare::PM_ViewStarting);

    ValidatePerson(pasPerson);

    //
    // In normal operation, we will receive a palette packet from the host
    // before any updates, which we use to create the correct palette for
    // this host.
    //
    // However, in some back-level calls we may not receive a palette
    // packet before the first updates, so we initialize this host's
    // palette to the default palette to allow us to generate some sort
    // of output.
    //
    pasPerson->pmPalette = (HPALETTE)GetStockObject(DEFAULT_PALETTE);

    //
    // Allocate color table cache memory based on the negotiated options
    // Space needed is (n)x256xRGBQUAD where n is the number of color
    // tables the conference supports.
    //
    pasPerson->pmcColorTable = pasPerson->cpcCaps.palette.capsColorTableCacheSize;

    if (!pasPerson->pmcColorTable)
    {
        WARNING_OUT(("PM_ViewStarting: person [%d] has no palette cache size",
            pasPerson->cpcCaps.palette.capsColorTableCacheSize));
        rc = TRUE;
        DC_QUIT;
    }

    pasPerson->apmColorTable = new COLORTABLECACHE[pasPerson->pmcColorTable];
    if (!pasPerson->apmColorTable)
    {
        ERROR_OUT(( "Failed to get memory for PM color table cache"));
        DC_QUIT;
    }

    ZeroMemory(pasPerson->apmColorTable, pasPerson->pmcColorTable * sizeof(COLORTABLECACHE));

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitBOOL(ASShare::PM_ViewStarting, rc);
    return(rc);
}



//
// PM_ViewEnded()
//
void  ASShare::PM_ViewEnded(ASPerson * pasPerson)
{
    DebugEntry(ASShare::PM_ViewEnded);

    ValidatePerson(pasPerson);

    PMFreeIncoming(pasPerson);

    DebugExitVOID(ASShare::PM_PartyViewEnded);
}



//
// PMFreeIncoming()
//
void ASShare::PMFreeIncoming(ASPerson * pasPerson)
{
    DebugEntry(ASShare::PMFreeIncoming);

    //
    // Free the color table cache
    //
    pasPerson->pmcColorTable = 0;
    if (pasPerson->apmColorTable)
    {
        delete[] pasPerson->apmColorTable;
        pasPerson->apmColorTable = NULL;
    }

    if (pasPerson->pmPalette != NULL)
    {
        //
        // Free this host's palette.  and set it to NULL so that we can tell
        // that this host has left the share.
        //
        PM_DeletePalette(pasPerson->pmPalette);
        pasPerson->pmPalette = NULL;
    }

    DebugExitVOID(ASShare::PMFreeIncoming);
}

//
// PM_MaybeSendPalettePacket()
//
BOOL  ASHost::PM_MaybeSendPalettePacket(void)
{
    BOOL  rc = TRUE;

    DebugEntry(ASHost::PM_MaybeSendPalettePacket);

    if (m_pmMustSendPalette)
    {
        ASSERT(m_usrSendingBPP <= 8);

        //
        // Ensure that our palette colors are up to date before we send the
        // palette packet.
        //
        if (g_usrPalettized)
        {
            PMUpdateSystemPaletteColors();
        }

        PMUpdateTxPaletteColors();
    }
    else if (g_usrPalettized)
    {
        ASSERT(m_usrSendingBPP <= 8);

        //
        // If the System Palette has changed then we may need to send
        // another palette packet.
        //
        if (PMUpdateSystemPaletteColors())
        {
            //
            // The System Palette has changed, but we only need to send
            // another palette packet if the palette colors have changed.
            //
            TRACE_OUT(( "System Palette changed"));

            if (PMUpdateTxPaletteColors())
            {
                TRACE_OUT(( "Tx Palette changed"));
                m_pmMustSendPalette = TRUE;
            }
        }
    }

    if (m_pmMustSendPalette)
    {
        ASSERT(m_usrSendingBPP <= 8);

        TRACE_OUT(( "Send palette packet"));

        rc = PMSendPalettePacket(m_apmTxPaletteColors, COLORS_FOR_BPP(m_usrSendingBPP));

        if (rc)
        {
            m_pmMustSendPalette = FALSE;
        }
    }

    DebugExitBOOL(ASHost::PM_MaybeSendPalettePacket, rc);
    return(rc);
}


//
// PM_ReceivedPacket
//
void  ASShare::PM_ReceivedPacket
(
    ASPerson *      pasPerson,
    PS20DATAPACKET  pPacket
)
{
    PPMPACKET       pPMPacket;
    HPALETTE        newPalette    = NULL;

    DebugEntry(ASShare::PM_ReceivedPacket);

    ValidateView(pasPerson);

    pPMPacket = (PPMPACKET)pPacket;

    //
    // Create a new palette from the received packet.
    //
    // We cannot just update the current palette colors (using
    // SetPaletteEntries) because Windows does not handle the repainting
    // of other local Palette Manager apps correctly (it does not
    // broadcast the WM_PALETTE.. messages as the palette mapping does
    // not change).
    //
    if (PM_CreatePalette(pPMPacket->numColors, pPMPacket->aColors,
            &newPalette))
    {
        PM_DeletePalette(pasPerson->pmPalette);
        pasPerson->pmPalette = newPalette;

        TRACE_OUT(( "Created new palette 0x%08x from packet", newPalette));
    }
    else
    {
        WARNING_OUT(( "Failed to create palette. person(%u) numColors(%u)",
            pasPerson, pPMPacket->numColors));
    }


    DebugExitVOID(ASShare::PM_ReceivedPacket);
}


//
// PM_SyncOutgoing()
//
void  ASHost::PM_SyncOutgoing(void)
{
    DebugEntry(ASHost::PM_SyncOutgoing);

    //
    //  Ensure we send a palette to the remote PM next time we are called.
    //
    if (m_usrSendingBPP <= 8)
    {
        m_pmMustSendPalette = TRUE;

        //
        // The sync discards any as-yet-unsent accumulated orders. Since these
        // orders may include color table cache orders, clear the cache.
        //
        ASSERT(m_pmTxCacheHandle);
        CH_ClearCache(m_pmTxCacheHandle);
    }

    DebugExitVOID(ASHost::PM_SyncOutgoing);
}


//
// PM_CacheTxColorTable
//
BOOL  ASHost::PM_CacheTxColorTable
(
    LPUINT          pIndex,
    LPBOOL          pCacheChanged,
    UINT            cColors,
    LPTSHR_RGBQUAD  pColors
)
{
    BOOL                rc             = FALSE;
    UINT                cacheIndex     = 0;
    UINT                i              = 0;
    PCOLORTABLECACHE    pEntry         = NULL;
    COLORTABLECACHE     newEntry       = { 0 };

    DebugEntry(ASHost::PM_CacheTxColorTable);

    ASSERT(m_usrSendingBPP <= 8);
    ASSERT(m_pmTxCacheHandle);

    TRACE_OUT(( "Caching table of %u colors", cColors));

    //
    // Create the data we want to cache.  It may be that there is already
    // an entry in the cache for this set of colors, but we still need to
    // create a cache entry in local memory so we can search the cache to
    // find out.
    //
    ZeroMemory(&newEntry, sizeof(COLORTABLECACHE));

    newEntry.inUse = TRUE;
    newEntry.cColors = cColors;
    memcpy(&newEntry.colors, pColors, cColors * sizeof(TSHR_RGBQUAD));

    //
    // Check to see if the table is already cached. (No hint or eviction
    // category.)
    //
    if (CH_SearchCache(m_pmTxCacheHandle, (LPBYTE)(&newEntry),
            sizeof(COLORTABLECACHE), 0, &cacheIndex ))
    {
        TRACE_OUT(( "Found existing entry at %u",cacheIndex));
        *pIndex = cacheIndex;
        *pCacheChanged = FALSE;
        rc = TRUE;
        DC_QUIT;
    }

    //
    // Find a free cache entry
    //
    // We arrange that our transmit cache is always one greater than the
    // negotiated cache size so that we should never fail to find a free
    // array entry.  Once we have fully populated our Tx cache we will
    // always find the free entry as the one last given back to us by CH.
    // Note the scan to <= m_pmNumTxCacheEntries is NOT a mistake.
    //
    if (m_pmNextTxCacheEntry != NULL)
    {
        pEntry = m_pmNextTxCacheEntry;
        m_pmNextTxCacheEntry = NULL;
    }
    else
    {
        for (i = 0; i <= m_pmNumTxCacheEntries; i++)
        {
            if (!m_apmTxCache[i].inUse)
            {
                break;
            }
        }

        //
        // We should never run out of free entries, but cope with it
        //
        if (i > m_pmNumTxCacheEntries)
        {
            ERROR_OUT(( "All PM cache entries in use"));
            rc = FALSE;
            DC_QUIT;
        }
        pEntry = m_apmTxCache + i;
    }


    //
    // Set up the color table in the free entry we just found
    //
    memcpy(pEntry, &newEntry, sizeof(COLORTABLECACHE));

    //
    // Add the new entry to the cache
    // We do not use hints or eviction so set to 0
    //
    cacheIndex = CH_CacheData(m_pmTxCacheHandle, (LPBYTE)pEntry,
        sizeof(COLORTABLECACHE), 0 );
    TRACE_OUT(( "Color table 0x%08x cached at index %u", pEntry, cacheIndex));
    *pIndex = cacheIndex;
    *pCacheChanged = TRUE;
    rc = TRUE;

DC_EXIT_POINT:
    DebugExitDWORD(ASHost::PM_CacheTxColorTable, rc);
    return(rc);
}


//
// PM_CacheRxColorTable
//
BOOL  ASShare::PM_CacheRxColorTable
(
    ASPerson *          pasPerson,
    UINT                index,
    UINT                cColors,
    LPTSHR_RGBQUAD      pColors
)
{
    BOOL                rc             = FALSE;
    PCOLORTABLECACHE    pColorTable;

    DebugEntry(ASShare::PM_CacheRxColorTable);

    ValidatePerson(pasPerson);

    pColorTable = pasPerson->apmColorTable;
    TRACE_OUT(( "Person [%d] color table rx cache 0x%08x cache %u, %u colors",
         pasPerson->mcsID, pColorTable, index, cColors));

    if (pColorTable == NULL)
    {
        ERROR_OUT(( "Asked to cache when no cache allocated"));
        DC_QUIT;
    }

    //
    // The index must be within the currently negotiated cache limits
    //
    if (index > pasPerson->pmcColorTable)
    {
        ERROR_OUT(( "Invalid color table index %u",index));
        DC_QUIT;
    }

    //
    // Set up the color table entry
    //
    pColorTable[index].inUse = TRUE;
    pColorTable[index].cColors = cColors;
    memcpy(pColorTable[index].colors, pColors, cColors * sizeof(TSHR_RGBQUAD));

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitDWORD(ASShare::PM_CacheRxColorTable, rc);
    return(rc);
}



//
// PMSendPalettePacket
//
// DESCRIPTION:
//
// Sends a palette packet containing the given colors.
//
// PARAMETERS:
//
// pColorTable - pointer to an array of TSHR_RGBQUAD colors to be sent in the
// palette packet.
//
// numColors - the number of entries in the TSHR_RGBQUAD array
//
// RETURNS: TRUE if the palette packet is sent, FALSE otherwise
//
//
BOOL  ASHost::PMSendPalettePacket
(
    LPTSHR_RGBQUAD  pColorTable,
    UINT            numColors
)
{
    PPMPACKET       pPMPacket;
    UINT            sizePkt;
    UINT            i;
    BOOL            rc = FALSE;
#ifdef _DEBUG
    UINT            sentSize;
#endif // _DEBUG

    DebugEntry(ASHost::PMSendPalettePacket);

    //
    // Send a palette packet.
    //
    // First calculate the packet size.
    //
    sizePkt = sizeof(PMPACKET) + (numColors - 1) * sizeof(TSHR_COLOR);
    pPMPacket = (PPMPACKET)m_pShare->SC_AllocPkt(PROT_STR_UPDATES, g_s20BroadcastID, sizePkt);
    if (!pPMPacket)
    {
        WARNING_OUT(("Failed to alloc PM packet, size %u", sizePkt));
        DC_QUIT;
    }

    //
    // Fill in the packet contents.
    //
    pPMPacket->header.header.data.dataType  = DT_UP;
    pPMPacket->header.updateType            = UPD_PALETTE;

    //
    // Convert the TSHR_RGBQUADs in the color table to TSHR_COLORs as we copy
    // them into the packet.
    //
    pPMPacket->numColors = numColors;
    for (i = 0; i < numColors; i++)
    {
        //
        // Convert each RGBQuad entry in the color table to a DCColor.
        //
        TSHR_RGBQUAD_TO_TSHR_COLOR(pColorTable[i],
            pPMPacket->aColors[i]);
    }

    //
    // Now send the packet to the remote application.
    //
    if (m_pShare->m_scfViewSelf)
        m_pShare->PM_ReceivedPacket(m_pShare->m_pasLocal, &(pPMPacket->header.header));

#ifdef _DEBUG
    sentSize =
#endif // _DEBUG
    m_pShare->DCS_CompressAndSendPacket(PROT_STR_UPDATES, g_s20BroadcastID,
        &(pPMPacket->header.header), sizePkt);

    TRACE_OUT(("PM packet size: %08d, sent %08d", sizePkt, sentSize));

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitDWORD(ASHost::PMSendPalettePacket, rc);
    return(rc);
}





//
// FUNCTION: PMCacheCallback
//
// DESCRIPTION:
//
// Cursor Manager's Cache Manager callback function.  Called whenever an
// entry is removed from the cache to allow us to free up the object.
//
// PARAMETERS:
//
// hCache - cache handle
//
// event - the cache event that has occured
//
// iCacheEntry - index of the cache entry that the event is affecting
//
// pData - pointer to the cache data associated with the given cache entry
//
// cbDataSize - size in bytes of the cached data
//
// RETURNS: Nothing
//
//
void  PMCacheCallback
(
    ASHost *    pHost,
    PCHCACHE    pCache,
    UINT        iCacheEntry,
    LPBYTE      pData
)
{
    DebugEntry(PMCacheCallback);


    //
    // Release the cache entry for reuse
    //
    TRACE_OUT(( "Releasing cache entry %d at 0x%08x",
            iCacheEntry, pData));
    pHost->m_pmNextTxCacheEntry = (PCOLORTABLECACHE)pData;
    pHost->m_pmNextTxCacheEntry->inUse = FALSE;

    //
    // Let SBC know that the cache entry has been released
    //
    pHost->SBC_PMCacheEntryRemoved(iCacheEntry);

    DebugExitVOID(PMCacheCallback);
}




//
// PM_GetSystemPaletteEntries
//
void  ASHost::PM_GetSystemPaletteEntries(LPTSHR_RGBQUAD pColors)
{
    UINT i;

    DebugEntry(ASHost::PM_GetSystemPaletteEntries);

    PMUpdateSystemPaletteColors();

    for (i = 0; i < PM_NUM_8BPP_PAL_ENTRIES; i++)
    {
        pColors[i].rgbRed       = m_apmCurrentSystemPaletteEntries[i].peRed;
        pColors[i].rgbGreen     = m_apmCurrentSystemPaletteEntries[i].peGreen;
        pColors[i].rgbBlue      = m_apmCurrentSystemPaletteEntries[i].peBlue;
        pColors[i].rgbReserved  = 0;
    }

    //
    // This function in its current form always returns TRUE - it is always
    // able to obtain the system colors.
    //
    DebugExitVOID(ASHost::PM_GetSystemPaletteEntries);
}


//
// PM_GetLocalPalette()
//
HPALETTE  ASHost::PM_GetLocalPalette(void)
{
    //
    // Ensure the palette is up to date
    //
    if (g_usrPalettized)
    {
        PMUpdateSystemPaletteColors();
    }

    //
    // Return the handle to the Local Palette.
    //
    return(m_pmTxPalette);
}



//
// PM_GetColorTable
//
void ASShare::PM_GetColorTable
(
    ASPerson *      pasPerson,
    UINT            index,
    LPUINT          pcColors,
    LPTSHR_RGBQUAD  pColors
)
{
    PCOLORTABLECACHE pColorTable;

    DebugEntry(ASShare::PM_GetColorTable);

    ValidatePerson(pasPerson);

    ASSERT(pasPerson->apmColorTable);

    pColorTable = &(pasPerson->apmColorTable[index]);
    TRACE_OUT(( "Color table requested for [%d], table ptr 0x%08x index %d",
            pasPerson->mcsID, pColorTable,index));

    if (!pColorTable->inUse)
    {
        ERROR_OUT(( "Asked for PM cache entry %hu when cache not yet in use",
                    index));
        DC_QUIT;
    }

    //
    // Copy the colors into the structure we have been passed
    //
    *pcColors = pColorTable->cColors;

    memcpy( pColors,
               pColorTable->colors,
               sizeof(TSHR_RGBQUAD) * pColorTable->cColors );

    TRACE_OUT(( "Returning %u colors",*pcColors));

DC_EXIT_POINT:
    DebugExitVOID(ASShare::PM_GetColorTable);
}





//
// PMADJUSTBUGGEDCOLOR()
//
// Macro used to tweak an 8 bit palette entry that the Win95 16 bit
// driver returns incorrectly
//
#define PMADJUSTBUGGEDCOLOR(pColor)                                          \
    if ( ((pColor)->rgbBlue != 0x00) &&                                      \
         ((pColor)->rgbBlue != 0xFF) )                                       \
    {                                                                        \
        (pColor)->rgbBlue += 0x40;                                           \
    }                                                                        \
                                                                             \
    if ( ((pColor)->rgbGreen != 0x00) &&                                     \
         ((pColor)->rgbGreen != 0xFF) )                                      \
    {                                                                        \
        (pColor)->rgbGreen += 0x20;                                          \
    }                                                                        \
                                                                             \
    if ( ((pColor)->rgbRed != 0x00) &&                                       \
         ((pColor)->rgbRed != 0xFF) )                                        \
    {                                                                        \
        (pColor)->rgbRed += 0x20;                                            \
    }

//
// PMGetGrays()
//
// Gets display driver specific versions of gray RGBs
//
void  ASHost::PMGetGrays(void)
{
    HBITMAP          hOldBitmap = NULL;
    BITMAPINFO_ours  bitmapInfo;
    BYTE          bitmapBuffer[16];
    UINT           i;

    DebugEntry(ASHost::PMGetGrays);

    //
    // Initialise the bitmapinfo local structure header contents.  This
    // structure will be used in the GetDIBits calls.
    //
    m_pShare->USR_InitDIBitmapHeader((BITMAPINFOHEADER *)&bitmapInfo, 8);

    bitmapInfo.bmiHeader.biWidth   = 16;
    bitmapInfo.bmiHeader.biHeight  = 1;

    //
    // Select the bitmap into the work DC
    //
    hOldBitmap = SelectBitmap(m_usrWorkDC, m_pShare->m_usrBmp16);
    if (hOldBitmap == NULL)
    {
        ERROR_OUT(( "Failed to select bitmap. hp(%08lX) hbmp(%08lX)",
            m_usrWorkDC, m_pShare->m_usrBmp16 ));
        DC_QUIT;
    }

    //
    // Use the real GDI to set each bit to each supplied color.
    //
    for (i = PM_GREY_COUNT; i-- != 0; )
    {
        SetPixel(m_usrWorkDC, i, 0, s_apmGreyRGB[i]);
    }

    //
    // Because this function is only used for true color scenarios we do
    // not need to select a palette into our compatible DC.  We just need
    // to get the bits.
    //
    if (!GetDIBits(m_usrWorkDC, m_pShare->m_usrBmp16, 0, 1, &bitmapBuffer,
            (BITMAPINFO *)&bitmapInfo, DIB_RGB_COLORS ))
    {
        ERROR_OUT(( "GetDIBits failed. hp(%x) hbmp(%x)",
                m_usrWorkDC, m_pShare->m_usrBmp16));
        DC_QUIT;
    }

    //
    // Check if we need to adjust the palette colors for the 16 bit driver
    // bug.
    //
    m_pmBuggedDriver = ((g_usrScreenBPP > 8) &&
                        (bitmapInfo.bmiColors[1].rgbRed == 0) &&
                        (bitmapInfo.bmiColors[1].rgbGreen == 0) &&
                        (bitmapInfo.bmiColors[1].rgbBlue == 0x40));

    //
    // Extract the RGBs returned by the display driver with the sending bpp
    // DIB.
    //
    for (i = PM_GREY_COUNT; i-- != 0; )
    {
        //
        // Extract the RGB from the color table
        //
        m_apmDDGreyRGB[i] = *((LPTSHR_RGBQUAD)(&bitmapInfo.bmiColors[bitmapBuffer[i]]));

        //
        // Adjust the palette colors for the 16 bit driver bug, if needed.
        //
        if (m_pmBuggedDriver)
        {
            TRACE_OUT(( "Adjusting for bugged driver"));
            PMADJUSTBUGGEDCOLOR(&m_apmDDGreyRGB[i]);
        }
    }

DC_EXIT_POINT:
    //
    // clean up
    //
    if (hOldBitmap != NULL)
    {
        SelectBitmap(m_usrWorkDC, hOldBitmap);

    }

    DebugExitVOID(ASHost::PMGetGrays);
}






//
// FUNCTION: PMUpdateSystemPaletteColors
//
// DESCRIPTION:
//
// Determines whether the colors in the System Palette have changed since
// the last time this function was called and if so, updates the supplied
// palette so that it contains the same colors as the System Palette.
//
// The first time that this function is called after PM_Init the System
// Palette colors will be returned and the function will return TRUE.
//
// PARAMETERS:
//
// shadowSystemPalette - handle of the palette to be updated with the
// current System Palette colors
//
// RETURNS: TRUE if the System Palette has changed since the last call,
// FALSE otherwise.
//
//
BOOL  ASHost::PMUpdateSystemPaletteColors(void)
{
    BOOL            rc = FALSE;
    PALETTEENTRY    systemPaletteEntries[PM_NUM_8BPP_PAL_ENTRIES];
    HDC             hdcScreen = NULL;
    UINT            cbSystemPaletteEntries;
    int             irgb, crgb, crgbFixed;

    DebugEntry(ASHost::PMUpdateSystemPaletteColors);

    ASSERT(g_usrPalettized);
    ASSERT(g_usrScreenBPP <= 8);
    ASSERT(m_usrSendingBPP <= 8);

    //
    // Don't bother with all this stuff if the system palette has not
    // changed at all.  We track notifications to our UI to detect
    // palette changes.
    //
    if (!g_asSharedMemory->pmPaletteChanged)
    {
        DC_QUIT;
    }

    hdcScreen = GetDC(NULL);
    if (!hdcScreen)
    {
        WARNING_OUT(( "GetDC failed"));
        DC_QUIT;
    }

    if (GetSystemPaletteEntries(hdcScreen, 0, COLORS_FOR_BPP(g_usrScreenBPP),
        systemPaletteEntries) != (UINT)COLORS_FOR_BPP(g_usrScreenBPP))
    {
        WARNING_OUT(( "GetSystemPaletteEntries failed"));
        DC_QUIT;
    }

    //
    // Now that we have succesfully queried the system palette, we can
    // reset our flag.
    //
    g_asSharedMemory->pmPaletteChanged = FALSE;

    cbSystemPaletteEntries = COLORS_FOR_BPP(g_usrScreenBPP) * sizeof(PALETTEENTRY);

    //
    // See if the System Palette has changed from the last time we queried.
    //
    if (!memcmp(systemPaletteEntries, m_apmCurrentSystemPaletteEntries,
            cbSystemPaletteEntries ))
    {
        //
        // The System Palette has not changed
        //
        TRACE_OUT(( "System palette has NOT changed"));
        rc = TRUE;
        DC_QUIT;
    }

    //
    // Take a copy of the new System Palette.
    //
    memcpy(m_apmCurrentSystemPaletteEntries, systemPaletteEntries, cbSystemPaletteEntries );

    //
    // Update the current local paleete.
    //
    // NOTE FOR WIN95:
    // We need to add PC_NOCOLLAPSE to non-system palette entries.
    //
    if (g_asWin95)
    {
        if (GetSystemPaletteUse(hdcScreen) == SYSPAL_STATIC)
            crgbFixed = GetDeviceCaps(hdcScreen, NUMRESERVED) / 2;
        else
            crgbFixed = 1;

        crgb = COLORS_FOR_BPP(g_usrScreenBPP) - crgbFixed;

        for (irgb = crgbFixed; irgb < crgb; irgb++)
        {
            systemPaletteEntries[irgb].peFlags = PC_NOCOLLAPSE;
        }
    }

    SetPaletteEntries(m_pmTxPalette, 0, COLORS_FOR_BPP(g_usrScreenBPP),
                       systemPaletteEntries );

    m_pmMustSendPalette = TRUE;

    //
    // SFR0407: The system palette has changed so re-fetch our set of RGBs
    // which the driver returns on an 8-bit GetDIBits for greys.
    //
    PMGetGrays();

    rc = TRUE;

DC_EXIT_POINT:
    if (hdcScreen)
    {
        ReleaseDC(NULL, hdcScreen);
    }

    DebugExitBOOL(ASHost::PMUpdateSystemPaletteColors, rc);
    return(rc);
}


//
// FUNCTION: PMUpdateTxPaletteColors
//
// DESCRIPTION:
//
// Returns the colors that make up the current Tx Palette (the palette that
// is SENT from the local machine).  These are not necessarily the colors
// in the local machine's palette, because the local machine's bpp and the
// protocol bpp may be different (e.g.  on an 8bpp machine talking at 4bpp
// the Tx Palette has 16 entries).
//
// PARAMETERS:
//
// pColorTable - pointer to an array of RGBQUADs which is filled with the
// colors that make up the current Tx Palette.
//
// RETURNS: TRUE if successful, FALSE otherwise.
//
//
BOOL  ASHost::PMUpdateTxPaletteColors(void)
{
    UINT            i;
    UINT            j;
    BOOL            rc = FALSE;
    HDC             hdcMem = NULL;
    HBITMAP         hbmpDummy = NULL;
    HPALETTE        hpalOld = NULL;
    BITMAPINFO_ours pmBitmapInfo;

    DebugEntry(ASHost::PMUpdateTxPaletteColors);

    //
    // Returns the values returned by a GetDIBits call with the
    // m_pmTxPalette selected.
    //
    ASSERT(m_usrSendingBPP <= 8);

    //
    // If we are at 8bpp locally, and sending at 8bpp, then the TxPalette
    // is simply the system palette.
    //
    if ((g_usrScreenBPP == 8) && (m_usrSendingBPP == 8))
    {
        PM_GetSystemPaletteEntries(pmBitmapInfo.bmiColors);
    }
    else
    {
        hdcMem = CreateCompatibleDC(NULL);
        if (!hdcMem)
        {
            ERROR_OUT(("PMUpdateTxPaletteColors: couldn't create memory DC"));
            DC_QUIT;
        }

        hpalOld = SelectPalette(hdcMem, m_pmTxPalette, TRUE);
        RealizePalette(hdcMem);

        #define DUMMY_WIDTH  8
        #define DUMMY_HEIGHT 8

        hbmpDummy = CreateBitmap(DUMMY_WIDTH, DUMMY_HEIGHT, 1,
            g_usrScreenBPP, NULL);
        if (hbmpDummy == NULL)
        {
            ERROR_OUT(( "Failed to create bitmap"));
            DC_QUIT;
        }


        //
        // Set up the structure required by GetDIBits.
        //
        pmBitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        pmBitmapInfo.bmiHeader.biWidth  = DUMMY_WIDTH;
        pmBitmapInfo.bmiHeader.biHeight = DUMMY_HEIGHT;
        pmBitmapInfo.bmiHeader.biPlanes = 1;
        pmBitmapInfo.bmiHeader.biBitCount = (WORD)m_usrSendingBPP;
        pmBitmapInfo.bmiHeader.biCompression = BI_RGB;
        pmBitmapInfo.bmiHeader.biSizeImage = 0;
        pmBitmapInfo.bmiHeader.biXPelsPerMeter = 10000;
        pmBitmapInfo.bmiHeader.biYPelsPerMeter = 10000;
        pmBitmapInfo.bmiHeader.biClrUsed = 0;
        pmBitmapInfo.bmiHeader.biClrImportant = 0;

        if (0 == GetDIBits( hdcMem,
                            hbmpDummy,
                            0,
                            DUMMY_HEIGHT,
                            NULL,
                            (LPBITMAPINFO)&pmBitmapInfo.bmiHeader,
                            DIB_RGB_COLORS ))
        {
            WARNING_OUT(( "GetDIBits failed hdc(%x) hbmp(%x)",
                                                        HandleToUlong(hdcMem),
                                                        HandleToUlong(hbmpDummy)));
            DC_QUIT;
        }

        SelectPalette(hdcMem, hpalOld, TRUE);

        PM_AdjustColorsForBuggedDisplayDrivers(
            (LPTSHR_RGBQUAD)pmBitmapInfo.bmiColors,
            COLORS_FOR_BPP(m_usrSendingBPP));

        //
        // This doesn't work for VGA.
        //
        if (g_usrScreenBPP > 4)
        {
            //
            // Check the new color table for any occurrences of the dodgy-grey
            // RGBs which the display driver returns (getDIBits at 8bpp can
            // return RGBs with unequal R, G and B for a supplied RGB with
            // equal components, causing poor quality output).
            //
            for (i = COLORS_FOR_BPP(m_usrSendingBPP); i-- != 0;)
            {
                for ( j = 0; j < PM_GREY_COUNT; j++ )
                {
                    if (!memcmp(&pmBitmapInfo.bmiColors[i],
                            &m_apmDDGreyRGB[j],
                            sizeof(pmBitmapInfo.bmiColors[i])) )
                    {
                        //
                        // Found a dodgy grey in the color table, so replace
                        // with a "good" grey, ie one with equal R, G and B.
                        //
                        pmBitmapInfo.bmiColors[i].rgbRed =
                                                   GetRValue(s_apmGreyRGB[j]);
                        pmBitmapInfo.bmiColors[i].rgbGreen =
                                                   GetGValue(s_apmGreyRGB[j]);
                        pmBitmapInfo.bmiColors[i].rgbBlue =
                                                   GetBValue(s_apmGreyRGB[j]);
                        TRACE_OUT(( "match our grey %#x", s_apmGreyRGB[j]));
                        break;
                    }
                }
            }
        }
    }

    //
    // If the colors have changed then return TRUE and copy the new color
    // table back, else return FALSE.
    //
    if (!memcmp(m_apmTxPaletteColors, pmBitmapInfo.bmiColors,
                COLORS_FOR_BPP(m_usrSendingBPP) * sizeof(RGBQUAD) ))
    {
        rc = FALSE;
    }
    else
    {
        memcpy(m_apmTxPaletteColors, pmBitmapInfo.bmiColors,
               COLORS_FOR_BPP(m_usrSendingBPP) * sizeof(RGBQUAD) );

        rc = TRUE;
    }

DC_EXIT_POINT:
    if (hbmpDummy != NULL)
    {
        DeleteBitmap(hbmpDummy);
    }

    if (hdcMem != NULL)
    {
        DeleteDC(hdcMem);
    }

    DebugExitDWORD(ASHost::PMUpdateTxPaletteColors, rc);
    return(rc);
}

//
// FUNCTION: PMCreatePalette
//
// DESCRIPTION:
//
// Creates a new palette using the given colors.
//
// PARAMETERS:
//
// cEntries - number of entries in the pNewEntries array
//
// pNewEntries - pointer to a TSHR_COLOR array containing the new palette
// entries
//
// phPal - pointer to a HPALETTE variable that receives the new palette
// handle.
//
//
// RETURNS - TRUE if successful, FALSE otherwise.
//
//
BOOL  ASShare::PM_CreatePalette
(
    UINT            cEntries,
    LPTSHR_COLOR    pNewEntries,
    HPALETTE *      phPal
)
{
    UINT            i;
    BYTE            pmLogPaletteBuffer[sizeof(LOGPALETTE) + (PM_NUM_8BPP_PAL_ENTRIES-1)*sizeof(PALETTEENTRY)];
    LPLOGPALETTE    pLogPalette;
    BOOL            rc = FALSE;

    DebugEntry(ASShare::PM_CreatePalette);

    ASSERT(cEntries <= PM_NUM_8BPP_PAL_ENTRIES);

    //
    // Set up a palette structure.
    //
    pLogPalette = (LPLOGPALETTE)pmLogPaletteBuffer;

    // This is a random windows constant
    pLogPalette->palVersion    = 0x300;
    pLogPalette->palNumEntries = (WORD)cEntries;

    //
    // This palette packet contains an array of TSHR_COLOR structures which
    // contains 3 fields (RGB).  We have to convert each of these
    // structures to a PALETTEENTRY structure which has the same 3 fields
    // (RGB) plus some flags.
    //
    for (i = 0; i < cEntries; i++)
    {
        TSHR_COLOR_TO_PALETTEENTRY( pNewEntries[i],
                                 pLogPalette->palPalEntry[i] );
    }

    //
    // Create the palette.
    //
    *phPal = CreatePalette(pLogPalette);

    //
    // Return TRUE if the palette was created.
    //
    rc = (*phPal != NULL);

    DebugExitDWORD(ASShare::PM_CreatePalette, rc);
    return(rc);
}





//
// FUNCTION: PM_AdjustColorsForBuggedDisplayDrivers
//
// DESCRIPTION:
//
// Adjusts the supplied color table if necessary to take account of display
// driver bugs.
//
// PARAMETERS:
//
// pColors - pointer to the color table (an array of RGBQUADs)
//
// cColors - number of colors in the supplied color table
//
// RETURNS: Nothing.
//
//
// NOTE: There is similar code in NormalizeRGB below (although not similar
// enough to macro it.)  If you change this code you should probably do
// the same there.)
//
void  ASHost::PM_AdjustColorsForBuggedDisplayDrivers
(
    LPTSHR_RGBQUAD  pColors,
    UINT            cColors
)
{
    LPTSHR_RGBQUAD  pColor;
    UINT      i;

    DebugEntry(ASHost::PM_AdjustColorsForBuggedDisplayDrivers);

    //
    // The Win95 16bpp display drivers return wrong colors when querying at
    // 8bpp.  The palette depends on the driver itself (5-6-5, 6-5-5, 5-6-5,
    // or 5-5-5).  Only when R, G, and B have the same # of bits are we
    // going to end up with an even distribution.
    //
    // Detect this case and try to adjust the colors.
    //
    m_pmBuggedDriver = ((g_usrScreenBPP > 8) &&
                        (pColors[1].rgbRed == 0) &&
                        (pColors[1].rgbGreen == 0) &&
                        (pColors[1].rgbBlue == 0x40));

    if (m_pmBuggedDriver)
    {
        TRACE_OUT(( "Adjusting for bugged driver"));
        pColor = pColors;

        for (i = 0; i < cColors; i++)
        {
            PMADJUSTBUGGEDCOLOR(pColor);
            pColor++;
        }
    }

    DebugExitVOID(ASHost::PM_AdjustColorsForBuggedDisplayDrivers);
}



//
// FUNCTION: PM_DeletePalette
//
// DESCRIPTION:
//
// Deletes the given palette, if it is not the default palette.
//
// PARAMETERS:
//
// palette - palette to be deleted
//
// RETURNS: Nothing.
//
//
void  ASShare::PM_DeletePalette(HPALETTE palette)
{
    DebugEntry(ASShare::PM_DeletePalette);

    if ((palette != NULL) &&
        (palette != (HPALETTE)GetStockObject(DEFAULT_PALETTE)))
    {
        DeletePalette(palette);
    }

    DebugExitVOID(ASShare::PM_DeletePalette);
}