/****************************************************************************/ // asbcapi.cpp // // Send Bitmap Cache API functions. // // Copyright(c) Microsoft, PictureTel 1992-1997 // (C) 1997-2000 Microsoft Corp. /****************************************************************************/ #include #pragma hdrstop #define TRC_FILE "asbcapi" #include /****************************************************************************/ // SBC_Init(): Initializes the SBC. // // Returns: FALSE on failure to initialize. /****************************************************************************/ void RDPCALL SHCLASS SBC_Init(void) { long cachingDisabled; TS_BITMAPCACHE_CAPABILITYSET_HOSTSUPPORT HostCaps; DC_BEGIN_FN("SBC_Init"); // This initializes all the global data for this component. #define DC_INIT_DATA #include #undef DC_INIT_DATA COM_ReadProfInt32(m_pTSWd, SBC_INI_CACHING_DISABLED, SBC_DEFAULT_CACHING_DISABLED, &cachingDisabled); sbcBitmapCachingEnabled = !(cachingDisabled & SBC_DISABLE_BITMAP_CACHE); sbcBrushCachingEnabled = !(cachingDisabled & SBC_DISABLE_BRUSH_CACHE); sbcGlyphCachingEnabled = !(cachingDisabled & SBC_DISABLE_GLYPH_CACHE); sbcOffscreenCachingEnabled = !(cachingDisabled & SBC_DISABLE_OFFSCREEN_CACHE); #ifdef DRAW_GDIPLUS #ifdef DRAW_NINEGRID sbcDrawNineGridCachingEnabled = !(cachingDisabled & SBC_DISABLE_DRAWNINEGRID_CACHE); sbcDrawGdiplusEnabled = !(cachingDisabled & SBC_DISABLE_DRAWGDIPLUS_CACHE); TRC_NRM((TB, "Caches enabled: Bitmap=%u, Brush=%u, Glyph=%u, Offscreen=%u, DNG=%u, GDIP=%u", sbcBitmapCachingEnabled, sbcBrushCachingEnabled, sbcGlyphCachingEnabled, sbcOffscreenCachingEnabled, sbcDrawNineGridCachingEnabled, sbcDrawGdiplusEnabled)); #else sbcDrawGdiplusEnabled = !(cachingDisabled & SBC_DISABLE_DRAWGDIPLUS_CACHE); TRC_NRM((TB, "Caches enabled: Bitmap=%u, Brush=%u, Glyph=%u, Offscreen=%u", sbcBitmapCachingEnabled, sbcBrushCachingEnabled, sbcGlyphCachingEnabled, sbcOffscreenCachingEnabled)); #endif // DRAW_NINEGRID #else // DRAW_GDIPLUS #ifdef DRAW_NINEGRID sbcDrawNineGridCachingEnabled = !(cachingDisabled & SBC_DISABLE_DRAWNINEGRID_CACHE); TRC_NRM((TB, "Caches enabled: Bitmap=%u, Brush=%u, Glyph=%u, Offscreen=%u, DNG=%u", sbcBitmapCachingEnabled, sbcBrushCachingEnabled, sbcGlyphCachingEnabled, sbcOffscreenCachingEnabled, sbcDrawNineGridCachingEnabled)); #else TRC_NRM((TB, "Caches enabled: Bitmap=%u, Brush=%u, Glyph=%u, Offscreen=%u", sbcBitmapCachingEnabled, sbcBrushCachingEnabled, sbcGlyphCachingEnabled, sbcOffscreenCachingEnabled)); #endif // DRAW_NINEGRID #endif // DRAW_GDIPLUS // The server supports rev2 bitmap caching. Indicate this support with a // client-to-server capability so the client can respond in kind. HostCaps.capabilitySetType = TS_CAPSETTYPE_BITMAPCACHE_HOSTSUPPORT; HostCaps.lengthCapability = sizeof(HostCaps); HostCaps.CacheVersion = TS_BITMAPCACHE_REV2; HostCaps.Pad1 = 0; HostCaps.Pad2 = 0; CPC_RegisterCapabilities((PTS_CAPABILITYHEADER)&HostCaps, sizeof(TS_BITMAPCACHE_CAPABILITYSET_HOSTSUPPORT)); TRC_NRM((TB, "SBC initialized OK")); DC_END_FN(); } /****************************************************************************/ // SBC_Term(): Terminates the SBC. /****************************************************************************/ void RDPCALL SHCLASS SBC_Term(void) { DC_BEGIN_FN("SBC_Term"); SBC_FreeBitmapKeyDatabase(); DC_END_FN(); } /****************************************************************************/ // SBC_SyncUpdatesNow: Called to force a sync, which clears all caches. /****************************************************************************/ void RDPCALL SHCLASS SBC_SyncUpdatesNow(void) { DC_BEGIN_FN("SBC_SyncUpdatesNow"); #ifdef DRAW_GDIPLUS #ifdef DRAW_NINEGRID if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled || sbcDrawNineGridCachingEnabled || sbcDrawGdiplusEnabled) { #else if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled || sbcDrawGdiplusEnabled) { #endif // DRAW_NINEGRID #else // DRAW_GDIPLUS #ifdef DRAW_NINEGRID if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled || sbcDrawNineGridCachingEnabled) { #else if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled) { #endif // DRAW_NINEGRID #endif // DRAW_GDIPLUS sbcSyncRequired = TRUE; DCS_TriggerUpdateShmCallback(); } DC_END_FN(); } /****************************************************************************/ // SBC_DumpBitmapKeyDatabase // // Allocates a key database and fills it in with the current contents of // the bitmap caches. /****************************************************************************/ void RDPCALL SHCLASS SBC_DumpBitmapKeyDatabase(BOOLEAN bSaveDatabase) { unsigned i, j; unsigned TotalEntries, CurEntry; CHNODE *pNode; DC_BEGIN_FN("SBC_DumpBitmapKeyDatabase"); // If we have a previous database, possibly from the original client // persistent key upload, destroy it. SBC_FreeBitmapKeyDatabase(); // Count up the total number of entries we will need if preserving the key // database. For shadows, this information is always discarded. TotalEntries = 0; if (bSaveDatabase) { for (i = 0; i < m_pShm->sbc.NumBitmapCaches; i++) if (m_pShm->sbc.bitmapCacheInfo[i].cacheHandle != NULL) TotalEntries += CH_GetNumEntries(m_pShm->sbc.bitmapCacheInfo[i]. cacheHandle); } if (TotalEntries > 0) { // Allocate the database. sbcKeyDatabaseSize = sizeof(SBC_BITMAP_CACHE_KEY_INFO) + (TotalEntries - 1) * sizeof(SBC_MRU_KEY); sbcKeyDatabase = (SBC_BITMAP_CACHE_KEY_INFO *)COM_Malloc(sbcKeyDatabaseSize); if (sbcKeyDatabase != NULL) { sbcKeyDatabase->TotalKeys = TotalEntries; // Fill in the database from each cache. CurEntry = 0; for (i = 0; i < m_pShm->sbc.NumBitmapCaches; i++) { sbcKeyDatabase->NumKeys[i] = CH_GetNumEntries(m_pShm->sbc. bitmapCacheInfo[i].cacheHandle); sbcKeyDatabase->KeyStart[i] = CurEntry; SBC_DumpMRUList(m_pShm->sbc.bitmapCacheInfo[i].cacheHandle, &(sbcKeyDatabase->Keys[sbcKeyDatabase->KeyStart[i]])); CurEntry += sbcKeyDatabase->NumKeys[i]; } // Fill in remainder of pointers and info with zeros to indicate // nothing there. for (; i < TS_BITMAPCACHE_MAX_CELL_CACHES; i++) { sbcKeyDatabase->NumKeys[i] = 0; sbcKeyDatabase->KeyStart[i] = 0; } } else { // Not allocating the database is an error, but not fatal, since // it just means that the caches will be cleared instead of being // initialized. TRC_ERR((TB,"Failed to allocate key database")); sbcKeyDatabaseSize = 0; } } DC_END_FN(); } /****************************************************************************/ // SBC_DumpMRUList // // Walks a cache MRU list and dumps the keys and indices to an // SBC_MRU_KEY array. /****************************************************************************/ void RDPCALL SHCLASS SBC_DumpMRUList(CHCACHEHANDLE hCache, void *pList) { CHNODE *pNode; unsigned CurEntry; PLIST_ENTRY pCurrentListEntry; SBC_MRU_KEY *pKeys = (SBC_MRU_KEY *)pList; PCHCACHEDATA pCacheData; DC_BEGIN_FN("SBC_DumpMRUList"); pCacheData = (CHCACHEDATA *)hCache; CurEntry = 0; pCurrentListEntry = pCacheData->MRUList.Flink; while (pCurrentListEntry != &pCacheData->MRUList) { pNode = CONTAINING_RECORD(pCurrentListEntry, CHNODE, MRUList); pKeys[CurEntry].Key1 = pNode->Key1; pKeys[CurEntry].Key2 = pNode->Key2; pKeys[CurEntry].CacheIndex = (unsigned)(pNode - pCacheData->NodeArray); CurEntry++; pCurrentListEntry = pCurrentListEntry->Flink; } TRC_ASSERT((CurEntry == pCacheData->NumEntries), (TB,"NumEntries (%u) != # entries in MRU list (%u)", pCacheData->NumEntries, CurEntry)); DC_END_FN(); } /****************************************************************************/ // SBC_PartyJoiningShare: Called when a new party is joining the share. // // Params: // locPersonID - local person ID of remote person joining the share. // oldShareSize - the number of the parties which were in the share (ie // excludes the joining party). // // Returns: TRUE if the party can join the share, FALSE otherwise. /****************************************************************************/ BOOLEAN RDPCALL SHCLASS SBC_PartyJoiningShare( LOCALPERSONID locPersonID, unsigned oldShareSize) { DC_BEGIN_FN("SBC_PartyJoiningShare"); DC_IGNORE_PARAMETER(oldShareSize); TRC_NRM((TB, "[%x] joining share", locPersonID)); #ifdef DRAW_GDIPLUS #ifdef DRAW_NINEGRID if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled || sbcDrawNineGridCachingEnabled || sbcDrawGdiplusEnabled) { #else if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled || sbcDrawGdiplusEnabled) { #endif // DRAW_NINEGRID #else // DRAW_GDIPLUS #ifdef DRAW_NINEGRID if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled || sbcDrawNineGridCachingEnabled) { #else if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled) { #endif // DRAW_NINEGRID #endif // DRAW_GDIPLUS // Local server does not require new action. if (locPersonID != SC_LOCAL_PERSON_ID) { sbcCachingOn = TRUE; sbcNewCapsData = TRUE; // Redetermine the size of the bitmap cache. if (sbcBitmapCachingEnabled) SBCRedetermineBitmapCacheSize(); // Redetermine the size of the glyph cache. if (sbcGlyphCachingEnabled) SBCRedetermineGlyphCacheSize(); // Redetermine the brush support level if (sbcBrushCachingEnabled) SBCRedetermineBrushSupport(); // Redetermine the offscreen support level if (sbcOffscreenCachingEnabled) { SBCRedetermineOffscreenSupport(); } #ifdef DRAW_NINEGRID // Redetermine the drawninegrid support level if (sbcDrawNineGridCachingEnabled) { SBCRedetermineDrawNineGridSupport(); } #endif #ifdef DRAW_GDIPLUS if (sbcDrawGdiplusEnabled) { SBCRedetermineDrawGdiplusSupport(); } #endif // Force a callback on the WinStation context so we can update // the shared memory. DCS_TriggerUpdateShmCallback(); } } DC_END_FN(); return TRUE; } /****************************************************************************/ // SBC_PartyLeftShare(): Called when a party has left the share. // // Params: // locPersonID - local person ID of remote person leaving the share. // newShareSize - the number of the parties now in the call (ie excludes // the leaving party). /****************************************************************************/ void RDPCALL SHCLASS SBC_PartyLeftShare( LOCALPERSONID locPersonID, unsigned newShareSize) { DC_BEGIN_FN("SBC_PartyLeftShare"); DC_IGNORE_PARAMETER(newShareSize); TRC_NRM((TB, "[%x] left share", locPersonID)); // Must have active caches #ifdef DRAW_GDIPLUS #ifdef DRAW_NINEGRID if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled || sbcDrawNineGridCachingEnabled || sbcDrawGdiplusEnabled) { #else if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled || sbcDrawGdiplusEnabled) { #endif // DRAW_NINEGRID #else // DRAW_GDIPLUS #ifdef DRAW_NINEGRID if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled || sbcDrawNineGridCachingEnabled) { #else if (sbcBitmapCachingEnabled || sbcGlyphCachingEnabled || sbcBrushCachingEnabled || sbcOffscreenCachingEnabled) { #endif // DRAW_NINEGRID #endif // DRAW_GDIPLUS // If all people left the share then disable caching. if (locPersonID == SC_LOCAL_PERSON_ID) { TRC_NRM((TB, "Disable caching")); sbcCachingOn = FALSE; sbcNewCapsData = TRUE; // Force a callback on the WinStation context so we can update the // shared memory. DCS_TriggerUpdateShmCallback(); } // Else, look thru the capabilities again to see what's possible else { sbcCachingOn = TRUE; sbcNewCapsData = TRUE; // Redetermine the size of the bitmap cache. if (sbcBitmapCachingEnabled) SBCRedetermineBitmapCacheSize(); // Redetermine the size of the glyph cache. if (sbcGlyphCachingEnabled) SBCRedetermineGlyphCacheSize(); // Redetermine the brush support level if (sbcBrushCachingEnabled) SBCRedetermineBrushSupport(); // Redetermine the offscreen support level if (sbcOffscreenCachingEnabled) { SBCRedetermineOffscreenSupport(); } #ifdef DRAW_NINEGRID // Redetermine the drawninegrid support level if (sbcDrawNineGridCachingEnabled) { SBCRedetermineDrawNineGridSupport(); } #endif #ifdef DRAW_GDIPLUS if (sbcDrawGdiplusEnabled) { SBCRedetermineDrawGdiplusSupport(); } #endif // TODO: Is this really necessary? DCS_TriggerUpdateShmCallback(); } } DC_END_FN(); } /****************************************************************************/ // SBC_HandlePersistentCacheList(): Handles list of persistent cache keys // from the client. /****************************************************************************/ void RDPCALL SHCLASS SBC_HandlePersistentCacheList( TS_BITMAPCACHE_PERSISTENT_LIST *pPDU, unsigned DataLength, LOCALPERSONID LocalID) { unsigned i, j, CurEntry; INT TotalEntries; DC_BEGIN_FN("SBC_HandlePersistentCacheList"); // Check the packet length against its internal representation, make sure // it is as long as it needs to be. If not, we either received a buggy // packet or we're being attacked. if (DataLength >= (sizeof(TS_BITMAPCACHE_PERSISTENT_LIST) - sizeof(TS_BITMAPCACHE_PERSISTENT_LIST_ENTRY))) { if (pPDU->bFirstPDU) { // Check that we have not already received persistent key info. // If we have and we get a new PDU, it is a protocol error. if (sbcPersistentKeysReceived) { TRC_ERR((TB,"Persistent key packet received marked FIRST " "illegally")); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_PersistentKeyPDUIllegalFIRST, (PBYTE)pPDU, DataLength); goto ExitFunc; } // Get the total number of entries from the PDU array. Check // against the negotiated caps and make sure the client is not // trying to send too many. TotalEntries = 0; for (i = 0; i < TS_BITMAPCACHE_MAX_CELL_CACHES; i++) { TotalEntries += pPDU->TotalEntries[i]; if (pPDU->TotalEntries[i] > sbcCurrentBitmapCaps. CellCacheInfo[i].NumEntries) { TRC_ERR((TB,"Persistent key packet received specified " "more keys (%u) than there are cache entries (%u) " "for cache %u", pPDU->TotalEntries[i], sbcCurrentBitmapCaps.CellCacheInfo[i].NumEntries, i)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_PersistentKeyPDUTooManyCacheKeys, (PBYTE)pPDU, DataLength); goto ExitFunc; } } // Check if we receive 0 keys, in this case, we will just exit the // function quietly if (TotalEntries == 0) { TRC_ERR((TB, "0 persistent key")); goto ExitFunc; } // Check this against the max allowed by the protocol. if (TotalEntries > TS_BITMAPCACHE_MAX_TOTAL_PERSISTENT_KEYS) { TRC_ERR((TB,"Client specified %u total keys, beyond %u " "protocol limit", TotalEntries, TS_BITMAPCACHE_MAX_TOTAL_PERSISTENT_KEYS)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_PersistentKeyPDUTooManyTotalKeys, (PBYTE)pPDU, DataLength); goto ExitFunc; } sbcKeyDatabaseSize = sizeof(SBC_BITMAP_CACHE_KEY_INFO) + (TotalEntries - 1) * sizeof(SBC_MRU_KEY); sbcKeyDatabase = (SBC_BITMAP_CACHE_KEY_INFO *)COM_Malloc(sbcKeyDatabaseSize); if (sbcKeyDatabase == NULL) { sbcKeyDatabaseSize = 0; TRC_ERR((TB,"Could not alloc persistent key info")); goto ExitFunc; } // Set up general data and entry pointers within the key array. CurEntry = 0; for (i = 0; i < TS_BITMAPCACHE_MAX_CELL_CACHES; i++) { sbcKeyDatabase->KeyStart[i] = CurEntry; CurEntry += pPDU->TotalEntries[i]; sbcKeyDatabase->NumKeys[i] = 0; sbcNumKeysExpected[i] = pPDU->TotalEntries[i]; } sbcTotalKeysExpected = TotalEntries; sbcKeyDatabase->TotalKeys = 0; // Mark that we have received the first-keys packet. sbcPersistentKeysReceived = TRUE; } // If this is not the first PDU but we failed a previous allocation, // too bad, no persistent keys. This could also happen if the client // continues sending persistent cache keys after sending the // TS_BITMAPCACHE_LAST_OVERALL flag that indicates the end of the key // packet stream. if (sbcKeyDatabase == NULL) goto ExitFunc; // Total the supposed number of keys received in the PDU. Check against // the PDU size. TotalEntries = 0; for (i = 0; i < TS_BITMAPCACHE_MAX_CELL_CACHES; i++) TotalEntries += pPDU->NumEntries[i]; if (DataLength < (sizeof(TS_BITMAPCACHE_PERSISTENT_LIST) + (TotalEntries - 1) * sizeof(TS_BITMAPCACHE_PERSISTENT_LIST_ENTRY))) { TRC_ERR((TB,"Client specified %u keys in this PersistentListPDU, " "PDU data not long enough", TotalEntries)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_PersistentKeyPDUBadLength, (PBYTE)pPDU, DataLength); goto ExitFunc; } // Loop across the caches to check each one. CurEntry = 0; for (i = 0; i < TS_BITMAPCACHE_MAX_CELL_CACHES; i++) { // Make sure that we don't receive more keys than were specified in // the original PDU. if ((sbcKeyDatabase->NumKeys[i] + pPDU->NumEntries[i]) <= sbcNumKeysExpected[i]) { // Transfer keys into the key list. We set these up for the MRU // list in the same order received since the client does not // have any MRU priority info to give us. for (j = 0; j < pPDU->NumEntries[i]; j++) { (&(sbcKeyDatabase->Keys[sbcKeyDatabase->KeyStart[i]])) [sbcKeyDatabase->NumKeys[i] + j].Key1 = pPDU->Entries[CurEntry].Key1; (&(sbcKeyDatabase->Keys[sbcKeyDatabase->KeyStart[i]])) [sbcKeyDatabase->NumKeys[i] + j].Key2 = pPDU->Entries[CurEntry].Key2; (&(sbcKeyDatabase->Keys[sbcKeyDatabase->KeyStart[i]])) [sbcKeyDatabase->NumKeys[i] + j].CacheIndex = sbcKeyDatabase->NumKeys[i] + j; CurEntry++; } sbcKeyDatabase->NumKeys[i] += pPDU->NumEntries[i]; sbcKeyDatabase->TotalKeys += pPDU->NumEntries[i]; } else { TRC_ERR((TB,"Received too many keys in cache %u", i)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_PersistentKeyPDUTooManyCacheKeys, (PBYTE)pPDU, DataLength); goto ExitFunc; } } if (pPDU->bLastPDU) { // This is an assertion but not fatal -- we simply use what we // received. TRC_ASSERT((sbcKeyDatabase->TotalKeys == sbcTotalKeysExpected), (TB,"Num expected persistent keys does not match sent keys " "(rec'd=%d, expect=%d)", sbcKeyDatabase->TotalKeys, sbcTotalKeysExpected)); } } else { TRC_ERR((TB,"Persistent key PDU received but data is not long enough " "for header, LocalID=%u", LocalID)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_PersistentKeyPDUBadLength, (PBYTE)pPDU, DataLength); } ExitFunc: DC_END_FN(); } /****************************************************************************/ // Returns the persistent key database to the DD and free the local copy /****************************************************************************/ void RDPCALL SHCLASS SBC_GetBitmapKeyDatabase(unsigned* keyDBSize, BYTE* pKeyDB) { unsigned i, CurEntry; SBC_BITMAP_CACHE_KEY_INFO* pKeyDatabase; DC_BEGIN_FN("SBC_GetBitmapKeyDatabase"); // No persistent key database setup if ( sbcKeyDatabaseSize == 0 || sbcKeyDatabase == NULL) { TRC_NRM((TB, "Failed to get the key database: dd keysize=%d, wd keysize=%d, keydatabase=%p", *keyDBSize, sbcKeyDatabaseSize, sbcKeyDatabase)); *keyDBSize = 0; DC_QUIT; } // DD's buffer is too small if (*keyDBSize < sbcKeyDatabaseSize) { TRC_NRM((TB, "Failed to get the key database: dd keysize=%d, wd keysize=%d, keydatabase=%p", *keyDBSize, sbcKeyDatabaseSize, sbcKeyDatabase)); *keyDBSize = sbcKeyDatabaseSize; DC_QUIT; } TRC_NRM((TB, "get bitmapKeyDatabase: copy keys from wd to dd")); pKeyDatabase = (SBC_BITMAP_CACHE_KEY_INFO*)(pKeyDB); memcpy(pKeyDatabase, sbcKeyDatabase, sbcKeyDatabaseSize); *keyDBSize = sbcKeyDatabaseSize; SBC_FreeBitmapKeyDatabase(); DC_EXIT_POINT: return; } /****************************************************************************/ // Free the key databaes /****************************************************************************/ void RDPCALL SHCLASS SBC_FreeBitmapKeyDatabase() { if (sbcKeyDatabase != NULL) { COM_Free(sbcKeyDatabase); sbcKeyDatabase = NULL; } sbcKeyDatabaseSize = 0; } /****************************************************************************/ // SBC_HandleBitmapCacheErrorPDU: Handles a bitmap cache error PDU. // Right now this function just checks the length of the PDU to make // sure it is valid. If not, the server close the client connection // This function is for future support of error PDU implementation /****************************************************************************/ void RDPCALL SHCLASS SBC_HandleBitmapCacheErrorPDU( TS_BITMAPCACHE_ERROR_PDU *pPDU, unsigned DataLength, LOCALPERSONID LocalID) { unsigned i; DC_BEGIN_FN("SBC_HandleBitmapCacheErrorPDU"); if (DataLength >= (sizeof(TS_BITMAPCACHE_ERROR_PDU) - sizeof(TS_BITMAPCACHE_ERROR_INFO))) { if ((sizeof(TS_BITMAPCACHE_ERROR_PDU) - sizeof(TS_BITMAPCACHE_ERROR_INFO) + pPDU->NumInfoBlocks * sizeof(TS_BITMAPCACHE_ERROR_INFO)) == DataLength) { TRC_NRM((TB, "Received a bitmap cache error PDU")); // update the total number of error pdus received sbcTotalNumErrorPDUs++; // For the duration of a session, we will only handle maximum // MAX_NUM_ERROR_PDU_SEND numbers of error PDUs received. // This is to avoid bad clients attack the server with error pdu if (sbcTotalNumErrorPDUs <= MAX_NUM_ERROR_PDU_SEND) { for (i = 0; i < pPDU->NumInfoBlocks; i++) { if (pPDU->Info[i].CacheID < sbcCurrentBitmapCaps.NumCellCaches) { // For now, the server only handles the client clear cache // request. Server will clear the cache and then issue a screen // redraw. The server doesn't handle if the client requests to // resize the cache. sbcClearCache[pPDU->Info[i].CacheID] = pPDU->Info[i].bFlushCache; } } TRC_DBG((TB, "Issued clear cache to RDPDD")); // trigger a timer so that when DD gets it, it will clear the cache. DCS_TriggerUpdateShmCallback(); } else { TRC_DBG((TB, "Received more than %d bitmap error pdus.", MAX_NUM_ERROR_PDU_SEND)); } } else { TRC_ERR((TB,"Bitmap Cache Error PDU received but data Length is wrong, " "too many or too few info blocks, LocalID=%u", LocalID)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_BitmapCacheErrorPDUBadLength, (PBYTE)pPDU, DataLength); } } else { TRC_ERR((TB,"Bitmap Cache Error PDU received but data is not long enough " "for header, LocalID=%u", LocalID)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_BitmapCacheErrorPDUBadLength, (PBYTE)pPDU, DataLength); } DC_END_FN(); } /****************************************************************************/ // SBC_HandleOffscrCacheErrorPDU: Handles an offscr cache error PDU. // This function checks the length of the PDU to make // sure it is valid. If not, the server close the client connection // When this PDU is received, WD will pass disable offscreen rendering to // DD and DD will disable the offscreen rendering support and refresh // the screen /****************************************************************************/ void RDPCALL SHCLASS SBC_HandleOffscrCacheErrorPDU( TS_OFFSCRCACHE_ERROR_PDU *pPDU, unsigned DataLength, LOCALPERSONID LocalID) { DC_BEGIN_FN("SBC_HandleOffscrCacheErrorPDU"); if (DataLength >= sizeof(TS_OFFSCRCACHE_ERROR_PDU)) { TRC_NRM((TB, "Received an offscreen cache error PDU")); if (pPDU->flags & TS_FLUSH_AND_DISABLE_OFFSCREEN) { TRC_DBG((TB, "Issued clear cache to RDPDD")); sbcDisableOffscreenCaching = TRUE; // trigger a timer so that when DD gets it, it will disable // offscreen rendering and refresh the screen DCS_TriggerUpdateShmCallback(); } else { TRC_DBG((TB, "Unsupported flag, just ignore this PDU")); } } else { TRC_ERR((TB,"Offscr Cache Error PDU received but data is not long enough " "for header, LocalID=%u", LocalID)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_BitmapCacheErrorPDUBadLength, (PBYTE)pPDU, DataLength); } DC_END_FN(); } #ifdef DRAW_NINEGRID /****************************************************************************/ // SBC_HandleDrawNineGridErrorPDU: Handles a drawninegrid cache error PDU. // This function checks the length of the PDU to make // sure it is valid. If not, the server close the client connection // When this PDU is received, WD will pass disable drawninegrid rendering to // DD and DD will disable the drawninegrid rendering support and refresh // the screen /****************************************************************************/ void RDPCALL SHCLASS SBC_HandleDrawNineGridErrorPDU( TS_DRAWNINEGRID_ERROR_PDU *pPDU, unsigned DataLength, LOCALPERSONID LocalID) { DC_BEGIN_FN("SBC_HandleDrawNineGridErrorPDU"); if (DataLength >= sizeof(TS_DRAWNINEGRID_ERROR_PDU)) { TRC_NRM((TB, "Received an drawninegrid cache error PDU")); if (pPDU->flags & TS_FLUSH_AND_DISABLE_DRAWNINEGRID) { TRC_DBG((TB, "Issued clear cache to RDPDD")); sbcDisableDrawNineGridCaching = TRUE; // trigger a timer so that when DD gets it, it will disable // drawninegrid rendering and refresh the screen DCS_TriggerUpdateShmCallback(); } else { TRC_DBG((TB, "Unsupported flag, just ignore this PDU")); } } else { TRC_ERR((TB,"DrawNineGrid Cache Error PDU received but data is not long enough " "for header, LocalID=%u", LocalID)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_BitmapCacheErrorPDUBadLength, (PBYTE)pPDU, DataLength); } DC_END_FN(); } #endif #ifdef DRAW_GDIPLUS void RDPCALL SHCLASS SBC_HandleDrawGdiplusErrorPDU( TS_DRAWGDIPLUS_ERROR_PDU *pPDU, unsigned DataLength, LOCALPERSONID LocalID) { DC_BEGIN_FN("SBC_HandleDrawGdiplusErrorPDU"); if (DataLength >= sizeof(TS_DRAWGDIPLUS_ERROR_PDU)) { TRC_ERR((TB, "Received a drawgdiplus error PDU")); if (pPDU->flags & TS_FLUSH_AND_DISABLE_DRAWGDIPLUS) { TRC_DBG((TB, "Issued clear cache to RDPDD")); sbcDisableDrawGdiplus = TRUE; // trigger a timer so that when DD gets it, it will disable // drawninegrid rendering and refresh the screen DCS_TriggerUpdateShmCallback(); } else { TRC_DBG((TB, "Unsupported flag, just ignore this PDU")); } } else { TRC_ERR((TB,"DrawGdiplus Error PDU received but data is not long enough " "for header, LocalID=%u", LocalID)); WDW_LogAndDisconnect(m_pTSWd, TRUE, Log_RDP_BitmapCacheErrorPDUBadLength, (PBYTE)pPDU, DataLength); } DC_END_FN(); } #endif // DRAW_GDIPLUS /****************************************************************************/ // SBC_UpdateShm: Called on WinStation context to update the SBC shared // memory. /****************************************************************************/ void RDPCALL SHCLASS SBC_UpdateShm(void) { unsigned i; DC_BEGIN_FN("SBC_UpdateShm"); TRC_NRM((TB, "Update SBC shm")); // Cell bitmap caches. m_pShm->sbc.NumBitmapCaches = sbcCurrentBitmapCaps.NumCellCaches; m_pShm->sbc.fClearCache = FALSE; for (i = 0; i < sbcCurrentBitmapCaps.NumCellCaches; i++) { m_pShm->sbc.bitmapCacheInfo[i].Info = sbcCurrentBitmapCaps.CellCacheInfo[i]; // set the clear cache flag for all caches if (sbcClearCache[i] == TRUE) { m_pShm->sbc.fClearCache = TRUE; } m_pShm->sbc.bitmapCacheInfo[i].fClearCache = sbcClearCache[i]; sbcClearCache[i] = FALSE; TRC_NRM((TB, "bitmap cell cache(%u) NumEntries(%u) CellSize(%u)", i, m_pShm->sbc.bitmapCacheInfo[i].Info.NumEntries, SBC_CellSizeFromCacheID(i))); } // Cache bitmap order style. m_pShm->sbc.bUseRev2CacheBitmapOrder = ((sbcCurrentBitmapCaps.capabilitySetType >= TS_BITMAPCACHE_REV2) ? TRUE : FALSE); // Glyph and glyph fragment caching. for (i = 0; i < SBC_NUM_GLYPH_CACHES; i++) { m_pShm->sbc.caps.glyphCacheSize[i].cEntries = sbcGlyphCacheSizes[i].cEntries; m_pShm->sbc.caps.glyphCacheSize[i].cbCellSize = sbcGlyphCacheSizes[i].cbCellSize; TRC_NRM((TB, "glyph cache(%u) entries(%u) cellSize(%u)", i, m_pShm->sbc.caps.glyphCacheSize[i].cEntries, m_pShm->sbc.caps.glyphCacheSize[i].cbCellSize)); } m_pShm->sbc.caps.fragCacheSize[0].cEntries = sbcFragCacheSizes[0].cEntries; m_pShm->sbc.caps.fragCacheSize[0].cbCellSize = sbcFragCacheSizes[0].cbCellSize; m_pShm->sbc.syncRequired = (m_pShm->sbc.syncRequired || sbcSyncRequired) ? TRUE : FALSE; sbcSyncRequired = FALSE; m_pShm->sbc.fCachingEnabled = (sbcCachingOn ? TRUE : FALSE); m_pShm->sbc.caps.GlyphSupportLevel = sbcGlyphSupportLevel; m_pShm->sbc.caps.brushSupportLevel = sbcBrushSupportLevel; m_pShm->sbc.newCapsData = (m_pShm->sbc.newCapsData || sbcNewCapsData) ? TRUE : FALSE; sbcNewCapsData = FALSE; // Offscreen cache m_pShm->sbc.offscreenCacheInfo.supportLevel = sbcOffscreenCacheInfo.supportLevel; m_pShm->sbc.offscreenCacheInfo.cacheSize = sbcOffscreenCacheInfo.cacheSize; m_pShm->sbc.offscreenCacheInfo.cacheEntries = sbcOffscreenCacheInfo.cacheEntries; m_pShm->sbc.fDisableOffscreen = sbcDisableOffscreenCaching; #ifdef DRAW_NINEGRID // DrawNineGrid cache m_pShm->sbc.drawNineGridCacheInfo.supportLevel = sbcDrawNineGridCacheInfo.supportLevel; m_pShm->sbc.drawNineGridCacheInfo.cacheSize = sbcDrawNineGridCacheInfo.cacheSize; m_pShm->sbc.drawNineGridCacheInfo.cacheEntries = sbcDrawNineGridCacheInfo.cacheEntries; m_pShm->sbc.fDisableDrawNineGrid = sbcDisableDrawNineGridCaching; #endif #ifdef DRAW_GDIPLUS m_pShm->sbc.drawGdiplusInfo.supportLevel = sbcDrawGdiplusInfo.supportLevel; m_pShm->sbc.drawGdiplusInfo.GdipVersion = sbcDrawGdiplusInfo.GdipVersion; m_pShm->sbc.drawGdiplusInfo.GdipCacheLevel = sbcDrawGdiplusInfo.GdipCacheLevel; m_pShm->sbc.drawGdiplusInfo.GdipCacheEntries.GdipGraphicsCacheEntries = sbcDrawGdiplusInfo.GdipCacheEntries.GdipGraphicsCacheEntries; m_pShm->sbc.drawGdiplusInfo.GdipCacheEntries.GdipObjectBrushCacheEntries = sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectBrushCacheEntries; m_pShm->sbc.drawGdiplusInfo.GdipCacheEntries.GdipObjectPenCacheEntries = sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectPenCacheEntries; m_pShm->sbc.drawGdiplusInfo.GdipCacheEntries.GdipObjectImageCacheEntries = sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectImageCacheEntries; m_pShm->sbc.drawGdiplusInfo.GdipCacheEntries.GdipObjectImageAttributesCacheEntries = sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectImageAttributesCacheEntries; m_pShm->sbc.drawGdiplusInfo.GdipCacheChunkSize.GdipGraphicsCacheChunkSize = sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipGraphicsCacheChunkSize; m_pShm->sbc.drawGdiplusInfo.GdipCacheChunkSize.GdipObjectBrushCacheChunkSize = sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectBrushCacheChunkSize; m_pShm->sbc.drawGdiplusInfo.GdipCacheChunkSize.GdipObjectPenCacheChunkSize = sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectPenCacheChunkSize; m_pShm->sbc.drawGdiplusInfo.GdipCacheChunkSize.GdipObjectImageAttributesCacheChunkSize = sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectImageAttributesCacheChunkSize; m_pShm->sbc.drawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheChunkSize = sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheChunkSize; m_pShm->sbc.drawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheTotalSize = sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheTotalSize; m_pShm->sbc.drawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheMaxSize = sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheMaxSize; m_pShm->sbc.fDisableDrawGdiplus = sbcDisableDrawGdiplus; #endif // DRAW_GDIPLUS m_pShm->sbc.fAllowCacheWaitingList = sbcCurrentBitmapCaps.bAllowCacheWaitingList; #ifdef DC_HICOLOR m_pShm->sbc.clientBitsPerPel = sbcClientBitsPerPel; #endif TRC_NRM((TB, "syncRequired(%u) fCachingEnabled(%u) newCapsData(%u)", m_pShm->sbc.syncRequired, m_pShm->sbc.fCachingEnabled, m_pShm->sbc.newCapsData)); DC_END_FN(); } /****************************************************************************/ // SBCRedetermineBitmapCacheSize: Enumerates all the people in the share and // redetermines the overall capabilities. // // Returns: TRUE if caching should be enabled, FALSE otherwise. /****************************************************************************/ BOOLEAN RDPCALL SHCLASS SBCRedetermineBitmapCacheSize(void) { BOOLEAN rc = TRUE; unsigned i; DC_BEGIN_FN("SBCRedetermineBitmapCacheSize"); #ifdef DC_HICOLOR // Need to update the bpp as this affects the size of the caches sbcClientBitsPerPel = m_desktopBpp; #endif // Set the initial local max/min caps to defaults. sbcCurrentBitmapCaps = sbcDefaultBitmapCaps; // First attempt to enumerate rev2 capabilities, if present. CPC_EnumerateCapabilities(TS_CAPSETTYPE_BITMAPCACHE_REV2, NULL, SBCEnumBitmapCacheCaps); // Then enumerate rev1 caps if present. CPC_EnumerateCapabilities(TS_CAPSETTYPE_BITMAPCACHE, NULL, SBCEnumBitmapCacheCaps); // Trace the results and check to see if we negotiated any of the // cell caches to zero, in which case bitmap caching becomes disabled. TRC_NRM((TB,"New caps: bPersistentLists=%s, NumCellCaches=%u", (sbcCurrentBitmapCaps.bPersistentKeysExpected ? "TRUE" : "FALSE"), sbcCurrentBitmapCaps.NumCellCaches)); if (sbcCurrentBitmapCaps.NumCellCaches > 0) { for (i = 0; i < sbcCurrentBitmapCaps.NumCellCaches; i++) { TRC_NRM((TB, " Cell cache %u: Persistent=%s, NumEntries=%u", i, sbcCurrentBitmapCaps.CellCacheInfo[i].bSendBitmapKeys ? "TRUE" : "FALSE", sbcCurrentBitmapCaps.CellCacheInfo[i].NumEntries)); if (sbcCurrentBitmapCaps.CellCacheInfo[i].NumEntries == 0) { // Set the number of cell caches to zero as a signal for flag- // setting when the DD calls to get the new caps. sbcCurrentBitmapCaps.NumCellCaches = 0; // Return FALSE so that caching is disabled. TRC_ERR((TB, "Zero NumEntries on cache %u, caching disabled", i)); rc = FALSE; break; } } } else { // Return FALSE to disable caching. TRC_ERR((TB,"Zero caches, disabling caching")); rc = FALSE; } DC_END_FN(); return rc; } /****************************************************************************/ // SBCEnumBitmapCaps: Callback function passed to CPC_EnumerateCapabilities. // It will be called with a capability structure for each person in the share // corresponding to the TS_CAPSETTYPE_BITMAPCACHE and _REV2 capability // structures. // // Params: // personID - ID of person with these capabilities. // pProtCaps - pointer to capabilities structure for this person. This // pointer is only valid within the call to this function. /****************************************************************************/ void RDPCALL SHCLASS SBCEnumBitmapCacheCaps( LOCALPERSONID locPersonID, UINT_PTR UserData, PTS_CAPABILITYHEADER pCapHdr) { unsigned i; #ifdef DC_HICOLOR unsigned shadowerBpp; unsigned scaleNum = 1; unsigned scaleDenom = 1; #endif DC_BEGIN_FN("SBCEnumBitmapCacheCaps"); DC_IGNORE_PARAMETER(UserData); if (pCapHdr->capabilitySetType == TS_CAPSETTYPE_BITMAPCACHE_REV2) { TS_BITMAPCACHE_CAPABILITYSET_REV2 *pCaps; // We can receive a zero size for the capability if we didn't receive // any rev2 caps from any client. if (pCapHdr->lengthCapability >= sizeof(TS_BITMAPCACHE_CAPABILITYSET_REV2)) { pCaps = (PTS_BITMAPCACHE_CAPABILITYSET_REV2)pCapHdr; // Cache version defaults to rev2, we don't need to change // sbcCurrentBitmapCaps.capabilitySetType. TRC_NRM((TB,"[%ld]: Rec'd REV2 caps, # caches=%d", locPersonID, pCaps->NumCellCaches)); // Now we look at each capability parameter and take the max or min // of the local and remote settings, as appropriate. sbcCurrentBitmapCaps.bPersistentKeysExpected = pCaps->bPersistentKeysExpected; sbcCurrentBitmapCaps.bAllowCacheWaitingList = min(sbcCurrentBitmapCaps.bAllowCacheWaitingList, pCaps->bAllowCacheWaitingList); sbcCurrentBitmapCaps.NumCellCaches = min(pCaps->NumCellCaches, sbcCurrentBitmapCaps.NumCellCaches); for (i = 0; i < sbcCurrentBitmapCaps.NumCellCaches; i++) { // If all parties in a share are rev2, and any client wants keys, // send them. if (!sbcCurrentBitmapCaps.CellCacheInfo[i].bSendBitmapKeys) sbcCurrentBitmapCaps.CellCacheInfo[i].bSendBitmapKeys = pCaps->CellCacheInfo[i].bSendBitmapKeys; #ifdef DC_HICOLOR sbcCurrentBitmapCaps.CellCacheInfo[i].NumEntries = min(((pCaps->CellCacheInfo[i].NumEntries * scaleNum) / scaleDenom), sbcCurrentBitmapCaps.CellCacheInfo[i].NumEntries); #else sbcCurrentBitmapCaps.CellCacheInfo[i].NumEntries = min(pCaps->CellCacheInfo[i].NumEntries, sbcCurrentBitmapCaps.CellCacheInfo[i].NumEntries); #endif } } else { TRC_NRM((TB,"[%ld]: No rev2 caps received", locPersonID)); TRC_ASSERT((pCapHdr->lengthCapability == 0), (TB, "[%ld]: Rev2 capability length (%u) too small", locPersonID, pCapHdr->lengthCapability)); } } else { TS_BITMAPCACHE_CAPABILITYSET *pOldCaps; TRC_ASSERT((pCapHdr->capabilitySetType == TS_CAPSETTYPE_BITMAPCACHE), (TB,"Received caps that are neither rev1 nor rev2!")); // We can receive a zero size for the capability if we didn't receive // any rev1 caps from any client. if (pCapHdr->lengthCapability >= sizeof(TS_BITMAPCACHE_CAPABILITYSET)) { // Rev 1 (Hydra 4.0 release) bitmap caching caps. Map to the // rev2 caps structure, taking min of the cell sizes and numbers // of entries. TRC_NRM((TB,"[%ld]: Rec'd REV1 caps", locPersonID)); // We now have to use rev1 protocol to all clients. sbcCurrentBitmapCaps.capabilitySetType = TS_BITMAPCACHE_REV1; pOldCaps = (TS_BITMAPCACHE_CAPABILITYSET *)pCapHdr; sbcCurrentBitmapCaps.bPersistentKeysExpected = FALSE; sbcCurrentBitmapCaps.bAllowCacheWaitingList = FALSE; sbcCurrentBitmapCaps.NumCellCaches = min(3, sbcCurrentBitmapCaps.NumCellCaches); sbcCurrentBitmapCaps.CellCacheInfo[0].bSendBitmapKeys = FALSE; if (pOldCaps->Cache1MaximumCellSize == SBC_CellSizeFromCacheID(0)) { sbcCurrentBitmapCaps.CellCacheInfo[0].NumEntries = min(pOldCaps->Cache1Entries, sbcCurrentBitmapCaps.CellCacheInfo[0].NumEntries); } else { // Did not receive the required size from the client. This is // nonstandard behavior on RDP 4.0, so no problem to disable // caching. Set the NumEntries to zero to turn off caching. sbcCurrentBitmapCaps.CellCacheInfo[0].NumEntries = 0; } sbcCurrentBitmapCaps.CellCacheInfo[1].bSendBitmapKeys = FALSE; if (pOldCaps->Cache2MaximumCellSize == SBC_CellSizeFromCacheID(1)) { sbcCurrentBitmapCaps.CellCacheInfo[1].NumEntries = min(pOldCaps->Cache2Entries, sbcCurrentBitmapCaps.CellCacheInfo[1].NumEntries); } else { // Did not receive the required size from the client. This is // nonstandard behavior on RDP 4.0, so no problem to disable // caching. Set the NumEntries to zero to turn off caching. sbcCurrentBitmapCaps.CellCacheInfo[1].NumEntries = 0; } sbcCurrentBitmapCaps.CellCacheInfo[2].bSendBitmapKeys = FALSE; if (pOldCaps->Cache3MaximumCellSize == SBC_CellSizeFromCacheID(2)) { sbcCurrentBitmapCaps.CellCacheInfo[2].NumEntries = min(pOldCaps->Cache3Entries, sbcCurrentBitmapCaps.CellCacheInfo[2].NumEntries); } else { // Did not receive the required size from the client. This is // nonstandard behavior on RDP 4.0, so no problem to disable // caching. Set the NumEntries to zero to turn off caching. sbcCurrentBitmapCaps.CellCacheInfo[2].NumEntries = 0; } } else { TRC_NRM((TB,"No rev1 caps received")); TRC_ASSERT((pCapHdr->lengthCapability == 0), (TB, "Rev1 capability length (%u) too small", pCapHdr->lengthCapability)); } } DC_END_FN(); } /****************************************************************************/ /* FUNCTION: SBCRedetermineBrushSupport */ /* */ /* Enumerates all the people in the share and redetermines the brush */ /* support level depending on their and the local receive capabilities. */ /* */ /* RETURNS: the share brush support level */ /****************************************************************************/ BOOLEAN RDPCALL SHCLASS SBCRedetermineBrushSupport(void) { unsigned i; BOOLEAN rc = TRUE; DC_BEGIN_FN("SBCRedetermineBrushSupport"); /************************************************************************/ /* Start by setting brush support to the highest supported */ /************************************************************************/ sbcBrushSupportLevel = TS_BRUSH_COLOR8x8; TRC_NRM((TB, "Initial brush support level: %ld", sbcBrushSupportLevel)); /************************************************************************/ /* Enumerate all the brush support capabilities of all the parties. */ /* Brush support is set to the lowest common denominator. */ /************************************************************************/ CPC_EnumerateCapabilities(TS_CAPSETTYPE_BRUSH, NULL, SBCEnumBrushCaps); if (sbcBrushSupportLevel == TS_BRUSH_DEFAULT) rc = FALSE; TRC_NRM((TB, "Enumerated brush support level: %ld", sbcBrushSupportLevel)); DC_END_FN(); return rc; } /****************************************************************************/ /* FUNCTION: SBCEnumBrushCaps */ /* */ /* Function passed to CPC_EnumerateCapabilities. It will be called with a */ /* capability structure for each person in the share corresponding to the */ /* TS_CAPSETTYPE_BRUSH capability structure. */ /* */ /* PARAMETERS: */ /* */ /* personID - ID of person with these capabilities. */ /* */ /* pProtCaps - pointer to capabilities structure for this person. This */ /* pointer is only valid within the call to this function. */ /****************************************************************************/ void RDPCALL SHCLASS SBCEnumBrushCaps( LOCALPERSONID locPersonID, UINT_PTR UserData, PTS_CAPABILITYHEADER pCapabilities) { PTS_BRUSH_CAPABILITYSET pBrushCaps; unsigned i; DC_BEGIN_FN("SBCEnumBrushCaps"); DC_IGNORE_PARAMETER(UserData); pBrushCaps = (PTS_BRUSH_CAPABILITYSET)pCapabilities; /************************************************************************/ /* Set the brush support level to the lowest common denominator of all */ /* the parties in the call. */ /************************************************************************/ if (pCapabilities->lengthCapability >= sizeof(TS_BRUSH_CAPABILITYSET)) { pBrushCaps = (PTS_BRUSH_CAPABILITYSET)pCapabilities; TRC_NRM((TB, "Brush Support Level[ID=%u]: %ld", locPersonID, pBrushCaps->brushSupportLevel)); sbcBrushSupportLevel = min(sbcBrushSupportLevel, pBrushCaps->brushSupportLevel); } else { sbcBrushSupportLevel = TS_BRUSH_DEFAULT; TRC_NRM((TB, "[%ld]: Brush Support Level Unknown", locPersonID)); } TRC_NRM((TB, "[%ld]: Negotiated brush level: %ld", locPersonID, sbcBrushSupportLevel)); DC_END_FN(); } /****************************************************************************/ /* FUNCTION: SBCEnumOffscreenCaps */ /* */ /* Function passed to CPC_EnumerateCapabilities. It will be called with a */ /* capability structure for each person in the share corresponding to the */ /* TS_CAPSETTYPE_OFFSCREEN capability structure. */ /* */ /* PARAMETERS: */ /* */ /* personID - ID of person with these capabilities. */ /* */ /* pProtCaps - pointer to capabilities structure for this person. This */ /* pointer is only valid within the call to this function. */ /****************************************************************************/ void RDPCALL SHCLASS SBCEnumOffscreenCaps( LOCALPERSONID locPersonID, UINT_PTR UserData, PTS_CAPABILITYHEADER pCapabilities) { PTS_OFFSCREEN_CAPABILITYSET pOffscreenCaps; DC_BEGIN_FN("SBCEnumOffscreenCaps"); DC_IGNORE_PARAMETER(UserData); pOffscreenCaps = (PTS_OFFSCREEN_CAPABILITYSET)pCapabilities; /************************************************************************/ /* Set the offscr support level to the lowest common denominator of all */ /* the parties in the call. */ /************************************************************************/ if (pCapabilities->lengthCapability >= sizeof(TS_OFFSCREEN_CAPABILITYSET)) { pOffscreenCaps = (PTS_OFFSCREEN_CAPABILITYSET)pCapabilities; TRC_NRM((TB, "Offscreen Support Level[ID=%u]: %ld", locPersonID, pOffscreenCaps->offscreenSupportLevel)); sbcOffscreenCacheInfo.supportLevel = min(sbcOffscreenCacheInfo.supportLevel, pOffscreenCaps->offscreenSupportLevel); sbcOffscreenCacheInfo.cacheSize = min(sbcOffscreenCacheInfo.cacheSize, pOffscreenCaps->offscreenCacheSize); sbcOffscreenCacheInfo.cacheEntries = min(sbcOffscreenCacheInfo.cacheEntries, pOffscreenCaps->offscreenCacheEntries); } else { sbcOffscreenCacheInfo.supportLevel = TS_OFFSCREEN_DEFAULT; TRC_NRM((TB, "[%ld]: Offscreen Support Level Unknown", locPersonID)); } if (sbcOffscreenCacheInfo.cacheSize == 0 || sbcOffscreenCacheInfo.cacheEntries == 0) { sbcOffscreenCacheInfo.supportLevel = TS_OFFSCREEN_DEFAULT; } TRC_NRM((TB, "[%ld]: Negotiated offscreen level: %ld", locPersonID, sbcOffscreenCacheInfo.supportLevel)); DC_END_FN(); } /****************************************************************************/ /* FUNCTION: SBCRedetermineOffscreenSupport */ /* */ /* Enumerates all the people in the share and redetermines the offscreen */ /* support level depending on their and the local receive capabilities. */ /* */ /* RETURNS: the share offscreen support level */ /****************************************************************************/ BOOLEAN RDPCALL SHCLASS SBCRedetermineOffscreenSupport(void) { unsigned i; BOOLEAN rc = TRUE; DC_BEGIN_FN("SBCRedetermineOffscreenSupport"); /************************************************************************/ /* Start by setting offscreen support to the highest supported */ /************************************************************************/ sbcOffscreenCacheInfo.supportLevel = TS_OFFSCREEN_SUPPORTED; sbcOffscreenCacheInfo.cacheSize = TS_OFFSCREEN_CACHE_SIZE_SERVER_DEFAULT; sbcOffscreenCacheInfo.cacheEntries = TS_OFFSCREEN_CACHE_ENTRIES_DEFAULT; TRC_NRM((TB, "Initial offscreen support level: %ld", sbcOffscreenCacheInfo.supportLevel)); /************************************************************************/ /* Enumerate all the offscr support capabilities of all the parties. */ /* Offscr support is set to the lowest common denominator. */ /************************************************************************/ CPC_EnumerateCapabilities(TS_CAPSETTYPE_OFFSCREENCACHE, NULL, SBCEnumOffscreenCaps); if (sbcOffscreenCacheInfo.supportLevel == TS_OFFSCREEN_DEFAULT) rc = FALSE; TRC_NRM((TB, "Enumerated offscreen support level: %ld", sbcOffscreenCacheInfo.supportLevel)); DC_END_FN(); return rc; } #ifdef DRAW_NINEGRID /****************************************************************************/ // FUNCTION: SBCEnumDrawNineGridCaps // // Function passed to CPC_EnumerateCapabilities. It will be called with a // capability structure for each person in the share corresponding to the // TS_CAPSETTYPE_DRAWNINEGRID capability structure. // // PARAMETERS: // // personID - ID of person with these capabilities. // // pProtCaps - pointer to capabilities structure for this person. This // pointer is only valid within the call to this function. /****************************************************************************/ void RDPCALL SHCLASS SBCEnumDrawNineGridCaps( LOCALPERSONID locPersonID, UINT_PTR UserData, PTS_CAPABILITYHEADER pCapabilities) { PTS_DRAW_NINEGRID_CAPABILITYSET pDrawNineGridCaps; DC_BEGIN_FN("SBCEnumDrawNineGridCaps"); DC_IGNORE_PARAMETER(UserData); pDrawNineGridCaps = (PTS_DRAW_NINEGRID_CAPABILITYSET)pCapabilities; /************************************************************************/ // Set the drawninegrid support level to the lowest common denominator of all // the parties in the call. /************************************************************************/ if (pCapabilities->lengthCapability >= sizeof(TS_DRAW_NINEGRID_CAPABILITYSET)) { pDrawNineGridCaps = (PTS_DRAW_NINEGRID_CAPABILITYSET)pCapabilities; TRC_NRM((TB, "DrawNineGrid Support Level[ID=%u]: %ld", locPersonID, pDrawNineGridCaps->drawNineGridSupportLevel)); sbcDrawNineGridCacheInfo.supportLevel = min(sbcDrawNineGridCacheInfo.supportLevel, pDrawNineGridCaps->drawNineGridSupportLevel); sbcDrawNineGridCacheInfo.cacheSize = min(sbcDrawNineGridCacheInfo.cacheSize, pDrawNineGridCaps->drawNineGridCacheSize); sbcDrawNineGridCacheInfo.cacheEntries = min(sbcDrawNineGridCacheInfo.cacheEntries, pDrawNineGridCaps->drawNineGridCacheEntries); } else { sbcDrawNineGridCacheInfo.supportLevel = TS_DRAW_NINEGRID_DEFAULT; TRC_NRM((TB, "[%ld]: DrawNineGrid Support Level Unknown", locPersonID)); } if (sbcDrawNineGridCacheInfo.cacheSize == 0 || sbcDrawNineGridCacheInfo.cacheEntries == 0) { sbcDrawNineGridCacheInfo.supportLevel = TS_DRAW_NINEGRID_DEFAULT; } TRC_NRM((TB, "[%ld]: Negotiated drawninegrid level: %ld", locPersonID, sbcDrawNineGridCacheInfo.supportLevel)); DC_END_FN(); } /****************************************************************************/ // FUNCTION: SBCRedetermineDrawNineGridSupport // // Enumerates all the people in the share and redetermines the drawninegrid // support level depending on their and the local receive capabilities. // // RETURNS: the share drawninegrid support level /****************************************************************************/ BOOLEAN RDPCALL SHCLASS SBCRedetermineDrawNineGridSupport(void) { unsigned i; BOOLEAN rc = TRUE; DC_BEGIN_FN("SBCRedetermineDrawNineGridSupport"); /************************************************************************/ // Start by setting drawninegrid support to the highest supported /************************************************************************/ sbcDrawNineGridCacheInfo.supportLevel = TS_DRAW_NINEGRID_SUPPORTED_REV2; sbcDrawNineGridCacheInfo.cacheSize = TS_DRAW_NINEGRID_CACHE_SIZE_DEFAULT; sbcDrawNineGridCacheInfo.cacheEntries = TS_DRAW_NINEGRID_CACHE_ENTRIES_DEFAULT; TRC_NRM((TB, "Initial DrawNineGrid support level: %ld", sbcDrawNineGridCacheInfo.supportLevel)); /************************************************************************/ // Enumerate all the DrawNineGrid support capabilities of all the parties. // drawninegrid support is set to the lowest common denominator. /************************************************************************/ CPC_EnumerateCapabilities(TS_CAPSETTYPE_DRAWNINEGRIDCACHE, NULL, SBCEnumDrawNineGridCaps); if (sbcDrawNineGridCacheInfo.supportLevel == TS_DRAW_NINEGRID_DEFAULT) rc = FALSE; TRC_NRM((TB, "Enumerated drawninegrid support level: %ld", sbcDrawNineGridCacheInfo.supportLevel)); DC_END_FN(); return rc; } #endif #ifdef DRAW_GDIPLUS /****************************************************************************/ // FUNCTION: SBCEnumDrawGdiplusCaps // // Function passed to CPC_EnumerateCapabilities. It will be called with a // capability structure for each person in the share corresponding to the // TS_DRAW_GDIPLUS_CAPABILITYSET capability structure. // // PARAMETERS: // // personID - ID of person with these capabilities. // // pProtCaps - pointer to capabilities structure for this person. This // pointer is only valid within the call to this function. /****************************************************************************/ void RDPCALL SHCLASS SBCEnumDrawGdiplusCaps( LOCALPERSONID locPersonID, UINT_PTR UserData, PTS_CAPABILITYHEADER pCapabilities) { PTS_DRAW_GDIPLUS_CAPABILITYSET pDrawGdiplusCaps; DC_BEGIN_FN("SBCEnumDrawGdiplusCaps"); DC_IGNORE_PARAMETER(UserData); pDrawGdiplusCaps = (PTS_DRAW_GDIPLUS_CAPABILITYSET)pCapabilities; /************************************************************************/ // Set the drawgdiplus support level to the lowest common denominator of all // the parties in the call. /************************************************************************/ if (pCapabilities->lengthCapability >= sizeof(TS_DRAW_GDIPLUS_CAPABILITYSET)) { pDrawGdiplusCaps = (PTS_DRAW_GDIPLUS_CAPABILITYSET)pCapabilities; TRC_NRM((TB, "DrawGdiplus Support Level[ID=%u]: %ld", locPersonID, pDrawGdiplusCaps->drawGdiplusSupportLevel)); TRC_NRM((TB, "Gdip version is [ID=%u]: 0x%x, 0x%x", locPersonID, pDrawGdiplusCaps->GdipVersion, sbcDrawGdiplusInfo.GdipVersion)); sbcDrawGdiplusInfo.supportLevel = min(sbcDrawGdiplusInfo.supportLevel, pDrawGdiplusCaps->drawGdiplusSupportLevel); sbcDrawGdiplusInfo.GdipVersion = min(sbcDrawGdiplusInfo.GdipVersion, pDrawGdiplusCaps->GdipVersion); sbcDrawGdiplusInfo.GdipCacheLevel = min(sbcDrawGdiplusInfo.GdipCacheLevel, pDrawGdiplusCaps->drawGdiplusCacheLevel); sbcDrawGdiplusInfo.GdipCacheEntries.GdipGraphicsCacheEntries = min(sbcDrawGdiplusInfo.GdipCacheEntries.GdipGraphicsCacheEntries, pDrawGdiplusCaps->GdipCacheEntries.GdipGraphicsCacheEntries); sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectBrushCacheEntries = min(sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectBrushCacheEntries, pDrawGdiplusCaps->GdipCacheEntries.GdipObjectBrushCacheEntries); sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectPenCacheEntries = min(sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectPenCacheEntries, pDrawGdiplusCaps->GdipCacheEntries.GdipObjectPenCacheEntries); sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectImageCacheEntries = min(sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectImageCacheEntries, pDrawGdiplusCaps->GdipCacheEntries.GdipObjectImageCacheEntries); sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectImageAttributesCacheEntries = min(sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectImageAttributesCacheEntries, pDrawGdiplusCaps->GdipCacheEntries.GdipObjectImageAttributesCacheEntries); sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipGraphicsCacheChunkSize = min(sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipGraphicsCacheChunkSize, pDrawGdiplusCaps->GdipCacheChunkSize.GdipGraphicsCacheChunkSize); sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectBrushCacheChunkSize = min(sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectBrushCacheChunkSize, pDrawGdiplusCaps->GdipCacheChunkSize.GdipObjectBrushCacheChunkSize); sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectPenCacheChunkSize = min(sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectPenCacheChunkSize, pDrawGdiplusCaps->GdipCacheChunkSize.GdipObjectPenCacheChunkSize); sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectImageAttributesCacheChunkSize = min(sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectImageAttributesCacheChunkSize, pDrawGdiplusCaps->GdipCacheChunkSize.GdipObjectImageAttributesCacheChunkSize); sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheChunkSize = min(sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheChunkSize, pDrawGdiplusCaps->GdipImageCacheProperties.GdipObjectImageCacheChunkSize); sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheTotalSize = min(sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheTotalSize, pDrawGdiplusCaps->GdipImageCacheProperties.GdipObjectImageCacheTotalSize); sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheMaxSize = min(sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheMaxSize, pDrawGdiplusCaps->GdipImageCacheProperties.GdipObjectImageCacheMaxSize); } else { sbcDrawGdiplusInfo.supportLevel = TS_DRAW_GDIPLUS_DEFAULT; sbcDrawGdiplusInfo.GdipVersion = TS_GDIPVERSION_DEFAULT; TRC_ERR((TB, "[%ld]: DrawGdiplus Support Level Unknown", locPersonID)); } TRC_NRM((TB, "[%ld]: Negotiated drawgdiplus level: %ld", locPersonID, sbcDrawGdiplusInfo.supportLevel)); DC_END_FN(); } BOOLEAN RDPCALL SHCLASS SBCRedetermineDrawGdiplusSupport(void) { BOOLEAN rc = TRUE; DC_BEGIN_FN("SBCRedetermineDrawGdiplusSupport"); /************************************************************************/ // Start by setting drawgdiplus support to the highest supported /************************************************************************/ sbcDrawGdiplusInfo.supportLevel = TS_DRAW_GDIPLUS_SUPPORTED; sbcDrawGdiplusInfo.GdipVersion = 0xFFFFFFFF; sbcDrawGdiplusInfo.GdipCacheLevel = TS_DRAW_GDIPLUS_CACHE_LEVEL_ONE; sbcDrawGdiplusInfo.GdipCacheEntries.GdipGraphicsCacheEntries = TS_GDIP_GRAPHICS_CACHE_ENTRIES_DEFAULT; sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectBrushCacheEntries = TS_GDIP_BRUSH_CACHE_ENTRIES_DEFAULT; sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectPenCacheEntries = TS_GDIP_PEN_CACHE_ENTRIES_DEFAULT; sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectImageCacheEntries = TS_GDIP_IMAGE_CACHE_ENTRIES_DEFAULT; sbcDrawGdiplusInfo.GdipCacheEntries.GdipObjectImageAttributesCacheEntries = TS_GDIP_IMAGEATTRIBUTES_CACHE_ENTRIES_DEFAULT; sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipGraphicsCacheChunkSize = TS_GDIP_GRAPHICS_CACHE_CHUNK_SIZE_DEFAULT; sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectBrushCacheChunkSize = TS_GDIP_BRUSH_CACHE_CHUNK_SIZE_DEFAULT; sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectPenCacheChunkSize = TS_GDIP_PEN_CACHE_CHUNK_SIZE_DEFAULT; sbcDrawGdiplusInfo.GdipCacheChunkSize.GdipObjectImageAttributesCacheChunkSize = TS_GDIP_IMAGEATTRIBUTES_CACHE_CHUNK_SIZE_DEFAULT; sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheChunkSize = TS_GDIP_IMAGE_CACHE_CHUNK_SIZE_DEFAULT; sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheTotalSize = TS_GDIP_IMAGE_CACHE_TOTAL_SIZE_DEFAULT; sbcDrawGdiplusInfo.GdipImageCacheProperties.GdipObjectImageCacheMaxSize = TS_GDIP_IMAGE_CACHE_MAX_SIZE_DEFAULT; CPC_EnumerateCapabilities(TS_CAPSETTYPE_DRAWGDIPLUS, NULL, SBCEnumDrawGdiplusCaps); if (sbcDrawGdiplusInfo.supportLevel == TS_DRAW_GDIPLUS_DEFAULT) rc = FALSE; TRC_NRM((TB, "Enumerated drawgdiplus support level: %ld", sbcDrawGdiplusInfo.supportLevel)); DC_END_FN(); return rc; } #endif // DRAW_GDIPLUS /****************************************************************************/ // SBCRedetermineGlyphCacheSize: Enumerates all the people in the share and // redetermines the size of the glyph cache depending on their and the local // receive capabilities. // // Returns: TRUE if glyph caching should be enabled, FALSE otherwise. /****************************************************************************/ BOOLEAN RDPCALL SHCLASS SBCRedetermineGlyphCacheSize(void) { BOOLEAN rc = TRUE; unsigned i; DC_BEGIN_FN("SBCRedetermineGlyphCacheSize"); /************************************************************************/ /* Enumerate all the glyph cache receive capabilities of all the */ /* parties. The usable size of the send glyph cache is the minimum of */ /* all the remote receive caches and the local send cache size. */ /************************************************************************/ /************************************************************************/ /* Start by setting the size of the local send bitmap cache to the */ /* local default values. We DO need to do this, or we end up */ /* negotiating our glyph cache down to zero! */ /************************************************************************/ for (i = 0; i < SBC_NUM_GLYPH_CACHES; i++) { sbcGlyphCacheSizes[i].cEntries = sbcMaxGlyphCacheSizes[i].cEntries; sbcGlyphCacheSizes[i].cbCellSize = sbcMaxGlyphCacheSizes[i].cbCellSize; } sbcFragCacheSizes[0].cEntries = sbcMaxFragCacheSizes[0].cEntries; sbcFragCacheSizes[0].cbCellSize = sbcMaxFragCacheSizes[0].cbCellSize; /************************************************************************/ /* Now enumerate all the parties in the share and set our send glyph */ /* sizes appropriately. */ /************************************************************************/ CPC_EnumerateCapabilities(TS_CAPSETTYPE_GLYPHCACHE, NULL, SBCEnumGlyphCacheCaps); if (sbcGlyphSupportLevel == CAPS_GLYPH_SUPPORT_NONE) rc = FALSE; DC_END_FN(); return rc; } /****************************************************************************/ // SBCEnumGlyphCacheCaps: Function passed to CPC_EnumerateCapabilities. It // will be called with a capability structure for each person in the share // corresponding to the TS_CAPSETTYPE_BITMAPCACHE capability structure. // // Params: // personID - ID of person with these capabilities. // pProtCaps - pointer to capabilities structure for this person. This // pointer is only valid within the call to this function. /****************************************************************************/ void CALLBACK SHCLASS SBCEnumGlyphCacheCaps( LOCALPERSONID locPersonID, UINT_PTR UserData, PTS_CAPABILITYHEADER pCapabilities) { unsigned i; PTS_GLYPHCACHE_CAPABILITYSET pGlyphCacheCaps; DC_BEGIN_FN("SBCEnumGlyphCacheCaps"); DC_IGNORE_PARAMETER(UserData); if (pCapabilities->lengthCapability >= sizeof(TS_GLYPHCACHE_CAPABILITYSET)) { pGlyphCacheCaps = (PTS_GLYPHCACHE_CAPABILITYSET)pCapabilities; for (i = 0; iGlyphCache[i].CacheMaximumCellSize, pGlyphCacheCaps->GlyphCache[i].CacheEntries)); /************************************************************************/ /* Set the size of the glyph cache to the minimum of its current size */ /* and this party's receive glyph cache size. */ /************************************************************************/ sbcGlyphCacheSizes[i].cEntries = min(sbcGlyphCacheSizes[i].cEntries, pGlyphCacheCaps->GlyphCache[i].CacheEntries); sbcGlyphCacheSizes[i].cbCellSize = min(sbcGlyphCacheSizes[i].cbCellSize, pGlyphCacheCaps->GlyphCache[i].CacheMaximumCellSize); TRC_NRM((TB, "[%u]: Negotiated glyph cache %u size: cEntries(%u) cbCellSize(%u)", locPersonID, i, sbcGlyphCacheSizes[i].cEntries, sbcGlyphCacheSizes[i].cbCellSize)); } /************************************************************************/ /* Set the size of the glyph cache to the minimum of its current size */ /* and this party's receive glyph cache size. */ /************************************************************************/ sbcFragCacheSizes[0].cEntries = min(sbcFragCacheSizes[0].cEntries, pGlyphCacheCaps->FragCache.CacheEntries); sbcFragCacheSizes[0].cbCellSize = min(sbcFragCacheSizes[0].cbCellSize, pGlyphCacheCaps->FragCache.CacheMaximumCellSize); /************************************************************************/ /* Glyph support level */ /************************************************************************/ sbcGlyphSupportLevel = min(sbcGlyphSupportLevel, pGlyphCacheCaps->GlyphSupportLevel); } else { for (i = 0; i < SBC_NUM_GLYPH_CACHES; i++) { sbcGlyphCacheSizes[i].cEntries = 0; sbcGlyphCacheSizes[i].cbCellSize = 0; } sbcFragCacheSizes[0].cEntries = 0; sbcFragCacheSizes[0].cbCellSize = 0; sbcGlyphSupportLevel = CAPS_GLYPH_SUPPORT_NONE; } DC_END_FN(); }