#include "precomp.h" #include "winuser.h" #include // For CLSID_CDeskHtmlProp #include #include #include #include #include #include // hydra stuff #include #include #include "cplext.h" #include "cplp.h" HWND g_hDlg = NULL; /////////////////////////////////////////////////////////////////////////////// // Array defining each page in the sheet /////////////////////////////////////////////////////////////////////////////// typedef struct { int id; DLGPROC pfnDlgProc; RESTRICTIONS dwPolicy1; RESTRICTIONS dwPolicy2; long nExtensionID; // The page } PAGEINFO; PAGEINFO aPageInfo[] = { { 0, NULL, REST_NODISPLAYAPPEARANCEPAGE, REST_NOTHEMESTAB, PAGE_DISPLAY_THEMES}, // Theme page { DLG_BACKGROUND, BackgroundDlgProc, REST_NODISPBACKGROUND, (RESTRICTIONS)0, 0}, // Background page { DLG_SCREENSAVER, NULL, REST_NODISPSCREENSAVEPG, (RESTRICTIONS)0, 0}, // Screen Saver page { 0, NULL, REST_NODISPLAYAPPEARANCEPAGE, (RESTRICTIONS)0, PAGE_DISPLAY_APPEARANCE}, // Appearance page { 0, NULL, REST_NODISPSETTINGSPG, (RESTRICTIONS)0, PAGE_DISPLAY_SETTINGS}, // Settings page }; #define C_PAGES_DESK ARRAYSIZE(aPageInfo) #define IPI_SETTINGS (C_PAGES_DESK-1) // Index to "Settings" page #define WALLPAPER L"Wallpaper" #define EnableApplyButton(hdlg) PropSheet_Changed(GetParent(hdlg), hdlg) IThemeUIPages * g_pThemeUI = NULL; // Local Constant Declarations static const TCHAR sc_szCoverClass[] = TEXT("DeskSaysNoPeekingItsASurprise"); LRESULT CALLBACK CoverWindowProc( HWND, UINT, WPARAM, LPARAM ); // These are actions that can be passed in the cmdline. // FORMAT: "/Action:" #define DESKACTION_NONE 0x00000000 #define DESKACTION_OPENTHEME 0x00000001 #define DESKACTION_OPENMSTHEM 0x00000002 /////////////////////////////////////////////////////////////////////////////// // Globals /////////////////////////////////////////////////////////////////////////////// TCHAR gszDeskCaption[CCH_MAX_STRING]; TCHAR g_szNULL[] = TEXT(""); TCHAR g_szControlIni[] = TEXT("control.ini"); TCHAR g_szPatterns[] = TEXT("patterns") ; TCHAR g_szNone[CCH_NONE]; // this is the '(None)' string TCHAR g_szSystemIni[] = TEXT("system.ini"); TCHAR g_szWindows[] = TEXT("Windows"); TCHAR szRegStr_Colors[] = REGSTR_PATH_COLORS; HDC g_hdcMem = NULL; HBITMAP g_hbmDefault = NULL; BOOL g_bMirroredOS = FALSE; /////////////////////////////////////////////////////////////////////////////// // Externs /////////////////////////////////////////////////////////////////////////////// extern BOOL NEAR PASCAL GetStringFromReg(HKEY hKey, LPCTSTR lpszSubkey, LPCTSTR lpszValueName, LPCTSTR lpszDefault, LPTSTR lpszValue, DWORD cchSizeofValueBuff); //============================================================================================================ // Class //============================================================================================================ class CDisplayControlPanel : public CObjectWithSite { public: ////////////////////////////////////////////////////// // Public Interfaces ////////////////////////////////////////////////////// // *** IUnknown *** virtual STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj); virtual STDMETHODIMP_(ULONG) AddRef(void); virtual STDMETHODIMP_(ULONG) Release(void); void DisplayDialog(HINSTANCE hInst, HWND hwndParent, LPCTSTR pszCmdline); CDisplayControlPanel(void); virtual ~CDisplayControlPanel(void); private: // Private Member Variables long m_cRef; HANDLE m_hBackgroundThreads; void _ShowDialog(HINSTANCE hInst, HWND hwndParent, LPCTSTR pszCmdline); }; /*--------------------------------------------------------- ** **---------------------------------------------------------*/ BOOL NEAR PASCAL CreateGlobals() { WNDCLASS wc; HBITMAP hbm; HDC hdc; // // Check if the mirroring APIs exist on the current // platform. // g_bMirroredOS = IS_MIRRORING_ENABLED(); if( !GetClassInfo( hInstance, sc_szCoverClass, &wc ) ) { // if two pages put one up, share one dc wc.style = CS_CLASSDC; wc.lpfnWndProc = CoverWindowProc; wc.cbClsExtra = wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = (HICON)( wc.hCursor = NULL ); // use a real brush since user will try to paint us when we're "hung" wc.hbrBackground = (HBRUSH) GetStockObject( NULL_BRUSH ); wc.lpszMenuName = NULL; wc.lpszClassName = sc_szCoverClass; if( !RegisterClass( &wc ) ) return FALSE; } hdc = GetDC(NULL); g_hdcMem = CreateCompatibleDC(hdc); ReleaseDC(NULL, hdc); if (!g_hdcMem) return FALSE; hbm = CreateBitmap(1, 1, 1, 1, NULL); if (hbm) { g_hbmDefault = (HBITMAP) SelectObject(g_hdcMem, hbm); SelectObject(g_hdcMem, g_hbmDefault); DeleteObject(hbm); } LoadString(hInstance, IDS_NONE, g_szNone, ARRAYSIZE(g_szNone)); return TRUE; } BOOL AreExtraMonitorsDisabledOnPersonal(void) { BOOL fIsDisabled = IsOS(OS_PERSONAL); if (fIsDisabled) { // TODO: Insert call to SystemParametersInfo() to see if there are video cards that we had to disable. fIsDisabled = FALSE; } return fIsDisabled; } /*--------------------------------------------------------- ** **---------------------------------------------------------*/ HBITMAP FAR LoadMonitorBitmap( BOOL bFillDesktop ) { HBITMAP hbm = NULL; if (g_pThemeUI) { g_pThemeUI->LoadMonitorBitmap(bFillDesktop, &hbm); } return hbm; } int DisplaySaveSettings(PVOID pContext, HWND hwnd) { int iRet = 0; if (g_pThemeUI) { g_pThemeUI->DisplaySaveSettings(pContext, hwnd, &iRet); } return iRet; } /////////////////////////////////////////////////////////////////////////////// // // Messagebox wrapper // // /////////////////////////////////////////////////////////////////////////////// int FmtMessageBox( HWND hwnd, UINT fuStyle, DWORD dwTitleID, DWORD dwTextID) { TCHAR Title[256]; TCHAR Text[2000]; LoadString(hInstance, dwTextID, Text, ARRAYSIZE(Text)); LoadString(hInstance, dwTitleID, Title, ARRAYSIZE(Title)); return (ShellMessageBox(hInstance, hwnd, Text, Title, fuStyle)); } /////////////////////////////////////////////////////////////////////////////// // // InstallScreenSaver // // Provides a RUNDLL32-callable routine to install a screen saver // /////////////////////////////////////////////////////////////////////////////// #ifdef UNICODE // // Windows NT: // // Thunk ANSI version to the Unicode function // void WINAPI InstallScreenSaverW( HWND wnd, HINSTANCE inst, LPWSTR cmd, int shw ); void WINAPI InstallScreenSaverA( HWND wnd, HINSTANCE inst, LPSTR cmd, int shw ) { LPWSTR pwszCmd; int cch; cch = MultiByteToWideChar( CP_ACP, 0, cmd, -1, NULL, 0); if (cch == 0) return; pwszCmd = (LPWSTR) LocalAlloc( LMEM_FIXED, cch * SIZEOF(TCHAR) ); if (pwszCmd == NULL) return; if (0 != MultiByteToWideChar( CP_ACP, 0, cmd, -1, pwszCmd, cch)) { InstallScreenSaverW(wnd, inst, pwszCmd, shw); } LocalFree(pwszCmd); } # define REAL_INSTALL_SCREEN_SAVER InstallScreenSaverW #else // // Windows 95: // // Stub out Unicode version // void WINAPI InstallScreenSaverW( HWND wnd, HINSTANCE inst, LPWSTR cmd, int shw ) { SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); return; } # define REAL_INSTALL_SCREEN_SAVER InstallScreenSaverA #endif void WINAPI REAL_INSTALL_SCREEN_SAVER( HWND wnd, HINSTANCE inst, LPTSTR cmd, int shw ) { TCHAR buf[ MAX_PATH ]; int timeout; lstrcpy( buf, cmd ); PathGetShortPath( buf ); // so msscenes doesn't die WritePrivateProfileString( TEXT("boot"), TEXT("SCRNSAVE.EXE"), buf, TEXT("system.ini") ); SystemParametersInfo( SPI_SETSCREENSAVEACTIVE, TRUE, NULL, SPIF_UPDATEINIFILE ); // make sure the user has a reasonable timeout set SystemParametersInfo( SPI_GETSCREENSAVETIMEOUT, 0, &timeout, 0 ); if( timeout <= 0 ) { // 15 minutes seems like a nice default SystemParametersInfo( SPI_SETSCREENSAVETIMEOUT, 900, NULL, SPIF_UPDATEINIFILE ); } // bring up the screen saver page on our rundll #ifdef UNICODE Control_RunDLLW( wnd, inst, TEXT("DESK.CPL,,1"), shw ); #else Control_RunDLL( wnd, inst, TEXT("DESK.CPL,,1"), shw ); #endif } /*****************************************************************************\ * * DeskInitCpl( void ) * \*****************************************************************************/ BOOL DeskInitCpl(void) { // // Private Debug stuff // #if ANDREVA_DBG g_dwTraceFlags = 0xFFFFFFFF; #endif InitCommonControls(); CreateGlobals(); return TRUE; } HRESULT OpenAdvancedDialog(HWND hDlg, const CLSID * pClsid) { HRESULT hr = E_FAIL; IEnumUnknown * pEnumUnknown; hr = g_pThemeUI->GetBasePagesEnum(&pEnumUnknown); if (SUCCEEDED(hr)) { IUnknown * punk; hr = IEnumUnknown_FindCLSID(pEnumUnknown, *pClsid, &punk); if (SUCCEEDED(hr)) { IBasePropPage * pBasePage; hr = punk->QueryInterface(IID_PPV_ARG(IBasePropPage, &pBasePage)); if (SUCCEEDED(hr)) { IPropertyBag * pPropertyBag; hr = punk->QueryInterface(IID_PPV_ARG(IPropertyBag, &pPropertyBag)); if (SUCCEEDED(hr)) { if (IsEqualCLSID(PPID_Background, *pClsid)) { // We are going to treat the Background tab differently. We tell it to open // the advanced dialog. We do this so it can close the dialog if the user // clicks to open the Gallery and we need the CPL to close. hr = SHPropertyBag_WriteBOOL(pPropertyBag, SZ_PBPROP_OPENADVANCEDDLG, TRUE); } else { IAdvancedDialog * pAdvAppearDialog; hr = pBasePage->GetAdvancedDialog(&pAdvAppearDialog); if (SUCCEEDED(hr)) { BOOL fEnableApply = FALSE; hr = pAdvAppearDialog->DisplayAdvancedDialog(hDlg, pPropertyBag, &fEnableApply); if (SUCCEEDED(hr) && fEnableApply) { EnableApplyButton(hDlg); g_pThemeUI->UpdatePreview(0); // The Preview settings may have changed. } pAdvAppearDialog->Release(); } } pPropertyBag->Release(); } pBasePage->Release(); } punk->Release(); } pEnumUnknown->Release(); } return hr; } HRESULT SetAdvStartPage(LPTSTR pszStartPage, DWORD cchSize) { HRESULT hr = S_OK; // Does the caller want us to open the advanced dialog to a certain tab? if (g_pThemeUI) { // Yes, so open the dialog. if (!StrCmpI(pszStartPage, TEXT("Theme Settings"))) { OpenAdvancedDialog(g_hDlg, &PPID_Theme); } else if (!StrCmpI(pszStartPage, TEXT("Appearance"))) { OpenAdvancedDialog(g_hDlg, &PPID_BaseAppearance); } else if (!StrCmpI(pszStartPage, TEXT("Web"))) { OpenAdvancedDialog(g_hDlg, &PPID_Background); StrCpyNW(pszStartPage, L"Desktop", cchSize); } } return hr; } typedef struct { LPCTSTR pszCanonical; UINT nResourceID; } CANONICAL_TO_LOCALIZE_TABMAPPING; CANONICAL_TO_LOCALIZE_TABMAPPING s_TabMapping[] = { {SZ_DISPLAYCPL_OPENTO_THEMES, IDS_TAB_THEMES}, {SZ_DISPLAYCPL_OPENTO_DESKTOP, IDS_TAB_DESKTOP}, {TEXT("Background"), IDS_TAB_DESKTOP}, // These are other names people may use {TEXT("Screen Saver"), IDS_TAB_SCREENSAVER}, // These are other names people may use {SZ_DISPLAYCPL_OPENTO_SCREENSAVER, IDS_TAB_SCREENSAVER}, {SZ_DISPLAYCPL_OPENTO_APPEARANCE, IDS_TAB_APPEARANCE}, {SZ_DISPLAYCPL_OPENTO_SETTINGS, IDS_TAB_SETTINGS}, }; HRESULT _TabCanonicalToLocalized(IN OUT LPTSTR pszStartPage, DWORD cchSize) { HRESULT hr = S_OK; // pszStartPage is an in AND out param for (int nIndex = 0; nIndex < ARRAYSIZE(s_TabMapping); nIndex++) { if (!StrCmpI(s_TabMapping[nIndex].pszCanonical, pszStartPage)) { if (0 == s_TabMapping[nIndex].nResourceID) { hr = E_FAIL; } else { LoadString(hInstance, s_TabMapping[nIndex].nResourceID, pszStartPage, cchSize); } break; } } return hr; } /////////////////////////////////////////////////////////////////////////////// // SetStartPage checks the command line for start page by name. /////////////////////////////////////////////////////////////////////////////// #define SZ_ACTIONFLAG_THEME TEXT("/Action:OpenTheme") #define SZ_ACTIONFLAG_MSTHEME TEXT("/Action:OpenMSTheme") #define SZ_FILEFLAG TEXT("/File:\"") void SetStartPage(PROPSHEETHEADER *ppsh, LPCTSTR pszCmdLine, DWORD * pdwAction, LPTSTR pszPath, DWORD cchPathSize, LPTSTR pszStartPage, DWORD cchSize) { StrCpyNW(pszPath, L"", cchPathSize); StrCpyNW(pszStartPage, L"", cchSize); if (pszCmdLine) { // Strip spaces while (*pszCmdLine == TEXT(' ')) { pszCmdLine++; } // Check for @ sign. if (*pszCmdLine == TEXT('@')) { LPCTSTR pszBegin; BOOL fInQuote = FALSE; int cchLen; pszCmdLine++; // Skip past a quote if (*pszCmdLine == TEXT('"')) { pszCmdLine++; fInQuote = TRUE; } // Save the beginning of the name. pszBegin = pszCmdLine; // Find the end of the name. while (pszCmdLine[0] && (fInQuote || (pszCmdLine[0] != TEXT(' '))) && (!fInQuote || (pszCmdLine[0] != TEXT('"')))) { pszCmdLine++; } cchLen = (int)(pszCmdLine - pszBegin); TCHAR szStartPage[MAX_PATH]; StrCpyN(szStartPage, pszBegin, cchLen+1); SetAdvStartPage(szStartPage, ARRAYSIZE(szStartPage)); // Store the name in the pStartPage field. StrCpyN(pszStartPage, szStartPage, cchSize); if (StrStrIW(pszCmdLine, SZ_ACTIONFLAG_THEME) || StrStrW(pszCmdLine, SZ_ACTIONFLAG_MSTHEME)) { *pdwAction = (StrStrW(pszCmdLine, SZ_ACTIONFLAG_THEME) ? DESKACTION_OPENTHEME : DESKACTION_OPENMSTHEM); pszCmdLine = StrStrIW(pszCmdLine, SZ_FILEFLAG); if (pszCmdLine) { pszCmdLine += (ARRAYSIZE(SZ_FILEFLAG) - 1); // Skip past flag LPCWSTR pszEnd = StrStrIW(pszCmdLine, L"\""); if (pszEnd) { DWORD cchSize = (DWORD)((pszEnd - pszCmdLine) + 1); StrCpyNW(pszPath, pszCmdLine, min(cchPathSize, cchSize)); } } } if (SUCCEEDED(_TabCanonicalToLocalized(pszStartPage, cchSize))) // The caller passes a canonical name but the propsheet wants to localized name { ppsh->dwFlags |= PSH_USEPSTARTPAGE; ppsh->pStartPage = pszStartPage; } } } } /////////////////////////////////////////////////////////////////////////////// // _AddDisplayPropSheetPage adds pages for outside callers... /////////////////////////////////////////////////////////////////////////////// BOOL CALLBACK _AddDisplayPropSheetPage(HPROPSHEETPAGE hpage, LPARAM lParam) { PROPSHEETHEADER FAR * ppsh = (PROPSHEETHEADER FAR *) lParam; if (ppsh) { if (hpage && (ppsh->nPages < MAX_PAGES)) { ppsh->phpage[ppsh->nPages++] = hpage; return TRUE; } } return FALSE; } static int GetClInt( const TCHAR *p ) { BOOL neg = FALSE; int v = 0; while( *p == TEXT(' ') ) p++; // skip spaces if( *p == TEXT('-') ) // is it negative? { neg = TRUE; // yes, remember that p++; // skip '-' char } // parse the absolute portion while( ( *p >= TEXT('0') ) && ( *p <= TEXT('9') ) ) // digits only v = v * 10 + *p++ - TEXT('0'); // accumulate the value return ( neg? -v : v ); // return the result } BOOL CheckRestrictionPage(const PAGEINFO * pPageInfo) { BOOL fRestricted = SHRestricted(pPageInfo->dwPolicy1); if (!fRestricted && pPageInfo->dwPolicy2) { fRestricted = SHRestricted(pPageInfo->dwPolicy2); } return fRestricted; } /////////////////////////////////////////////////////////////////////////////// // CreateReplaceableHPSXA creates a new hpsxa that contains only the // interfaces with valid ReplacePage methods. // APPCOMPAT - EzDesk only implemented AddPages. ReplacePage is NULL for them. /////////////////////////////////////////////////////////////////////////////// typedef struct { UINT count, alloc; IShellPropSheetExt *interfaces[0]; } PSXA; HPSXA CreateReplaceableHPSXA(HPSXA hpsxa) { PSXA *psxa = (PSXA *)hpsxa; DWORD cb = SIZEOF(PSXA) + SIZEOF(IShellPropSheetExt *) * psxa->alloc; PSXA *psxaRet = (PSXA *)LocalAlloc(LPTR, cb); if (psxaRet) { UINT i; psxaRet->count = 0; psxaRet->alloc = psxa->alloc; for (i=0; icount; i++) { if (psxa->interfaces[i]) { psxaRet->interfaces[psxaRet->count++] = psxa->interfaces[i]; } } } return (HPSXA)psxaRet; } BOOL HideBackgroundTabOnTermServices(void) { BOOL fHideThisPage = FALSE; TCHAR szSessionName[WINSTATIONNAME_LENGTH * 2]; TCHAR szWallPaper[MAX_PATH*2]; TCHAR szBuf[MAX_PATH*2]; TCHAR szActualValue[MAX_PATH*2]; DWORD dwLen; DWORD i; ZeroMemory((PVOID)szSessionName,sizeof(szSessionName)); dwLen = GetEnvironmentVariable(TEXT("SESSIONNAME"), szSessionName, ARRAYSIZE(szSessionName)); if (dwLen != 0) { // Now that we have the session name, search for the # character. for(i = 0; i < dwLen; i++) { if (szSessionName[i] == TEXT('#')) { szSessionName[i] = TEXT('\0'); break; } } // Here is what we are looking for in NT5: // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\ // WinStations\RDP-Tcp\UserOverride\Control Panel\Desktop // // The value is: // Wallpaper // lstrcpy(szWallPaper,WALLPAPER); lstrcpy(szBuf, WINSTATION_REG_NAME ); lstrcat(szBuf, L"\\" ); lstrcat(szBuf, szSessionName ); lstrcat(szBuf, L"\\" ); lstrcat(szBuf, WIN_USEROVERRIDE ); lstrcat(szBuf, L"\\" ); lstrcat(szBuf, REGSTR_PATH_DESKTOP ); // Control Panel\\Desktop // See if we can get the wallpaper string. This will fail if the key // doesn't exist. This means the policy isn't set. // // hKey, lpszSubkey, lpszValueName, lpszDefault, if (GetStringFromReg(HKEY_LOCAL_MACHINE, szBuf, szWallPaper, TEXT(""), // lpszValue, cchSizeofValueBuff) szActualValue, ARRAYSIZE(szActualValue))) { fHideThisPage = TRUE; } } return fHideThisPage; } HRESULT AddPropSheetExtArrayToThemePageUI(IThemeUIPages * pThemeUI, HPSXA hpsxa) { HRESULT hr = E_INVALIDARG; if (pThemeUI && hpsxa) { PSXA *psxa = (PSXA *)hpsxa; IShellPropSheetExt **spsx = psxa->interfaces; UINT nIndex; for (nIndex = 0; nIndex < psxa->count; nIndex++) { if (psxa->interfaces[nIndex]) { IBasePropPage * pBasePropPage; if (SUCCEEDED(psxa->interfaces[nIndex]->QueryInterface(IID_PPV_ARG(IBasePropPage, &pBasePropPage)))) { pThemeUI->AddBasePage(pBasePropPage); pBasePropPage->Release(); } } } } return hr; } /*****************************************************************************\ DESCRIPTION: If the caller gave the page index, we need to open to that page. The order of the pages has changed from Win2k to Whistler, so map the indexes. Win2K: Index 0: Background Index 1: Screen Saver Index 2: Appearance None: Web None: Effects Index 3: Settings (Index 3) Whistler: (Base Dlg) None: Themes Index 0: Background Index 1: Screen Saver Index 2: Appearance Index 3: Settings Whistler: (Adv Dlg) None: Themes Settings None: Adv Appearance None: Web None: Effects \*****************************************************************************/ int UpgradeStartPageMappping(LPTSTR pszCmdLine, DWORD cchSize) { int nNewStartPage = GetClInt(pszCmdLine); if (pszCmdLine) { switch (nNewStartPage) { case 0: // Background StrCpyN(pszCmdLine, TEXT("@Desktop"), cchSize); break; case 1: // Screen Saver StrCpyN(pszCmdLine, TEXT("@ScreenSaver"), cchSize); break; case 2: // Screen Saver StrCpyN(pszCmdLine, TEXT("@ScreenSaver"), cchSize); break; case 3: // Settings StrCpyN(pszCmdLine, TEXT("@Settings"), cchSize); break; default: return nNewStartPage; break; } } else { return nNewStartPage; } return 0; } #define DestroyReplaceableHPSXA(hpsxa) LocalFree((HLOCAL)hpsxa) /*****************************************************************************\ * * DeskShowPropSheet( HWND hwndParent ) * \*****************************************************************************/ typedef HRESULT (*LPFNCOINIT)(LPVOID); typedef HRESULT (*LPFNCOUNINIT)(void); int ComputeNumberOfDisplayDevices(); void DeskShowPropSheet(HINSTANCE hInst, HWND hwndParent, LPCTSTR pszCmdline) { CDisplayControlPanel displayCPL; displayCPL.DisplayDialog(hInst, hwndParent, pszCmdline); } //=========================== // *** IUnknown Interface *** //=========================== ULONG CDisplayControlPanel::AddRef() { return InterlockedIncrement(&m_cRef); } ULONG CDisplayControlPanel::Release() { if (InterlockedDecrement(&m_cRef)) { if ((1 == m_cRef) && m_hBackgroundThreads) { SetEvent(m_hBackgroundThreads); } return m_cRef; } delete this; return 0; } HRESULT CDisplayControlPanel::QueryInterface(REFIID riid, void **ppvObj) { HRESULT hr = E_NOINTERFACE; static const QITAB qit[] = { QITABENT(CDisplayControlPanel, IObjectWithSite), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } CDisplayControlPanel::CDisplayControlPanel(void) : m_cRef(1) { m_hBackgroundThreads = NULL; } CDisplayControlPanel::~CDisplayControlPanel(void) { if (m_hBackgroundThreads) { CloseHandle(m_hBackgroundThreads); m_hBackgroundThreads = NULL; } } // Wait 30 seconds for hung apps to process our message before we give up. // It would be nice to wait longer, but if the user tries to launch the Display // Control Panel again, it will not launch because we are still running. The only // thing that we will give up on doing after 30 seconds it notifying apps. In the worse // case the user will need to log-off and back in to get apps to refresh. #define MAX_WAITFORHUNGAPPS (30) void CDisplayControlPanel::DisplayDialog(HINSTANCE hInst, HWND hwndParent, LPCTSTR pszCmdline) { if (SUCCEEDED(CoInitialize(NULL))) { SHSetInstanceExplorer(SAFECAST(this, IUnknown *)); _ShowDialog(hInst, hwndParent, pszCmdline); // Wait until the background threads finish. if (m_cRef > 1) { m_hBackgroundThreads = CreateEvent(NULL, FALSE, FALSE, NULL); if (m_hBackgroundThreads && (m_cRef > 1)) { DWORD dwResult = SHProcessMessagesUntilEvent(NULL, m_hBackgroundThreads, (MAX_WAITFORHUNGAPPS * 1000)); if (WAIT_TIMEOUT == dwResult) { TraceMsg(TF_GENERAL, "A thread hung and we needed to shutdown while it was still running."); } Sleep(100); } } SHSetInstanceExplorer(NULL); CoUninitialize(); } } void CDisplayControlPanel::_ShowDialog(HINSTANCE hInst, HWND hwndParent, LPCTSTR pszCmdline) { HPROPSHEETPAGE hpsp, ahPages[MAX_PAGES]; HPSXA hpsxa = NULL; PROPSHEETPAGE psp; PROPSHEETHEADER psh; int i; DWORD exitparam = 0UL; HRESULT hr = S_OK; TCHAR szCmdLine[MAX_PATH]; StrCpyN(szCmdLine, (pszCmdline ? pszCmdline : TEXT("")), ARRAYSIZE(szCmdLine)); // check if whole sheet is locked out if (SHRestricted(REST_NODISPLAYCPL)) { TCHAR szMessage[255],szTitle[255]; LoadString( hInst, IDS_DISPLAY_DISABLED, szMessage, ARRAYSIZE(szMessage) ); LoadString( hInst, IDS_DISPLAY_TITLE, szTitle, ARRAYSIZE(szTitle) ); MessageBox( hwndParent, szMessage, szTitle, MB_OK | MB_ICONINFORMATION ); return; } // Create the property sheet ZeroMemory(&psh, sizeof(psh)); psh.dwSize = sizeof(PROPSHEETHEADER); psh.dwFlags = (PSH_PROPTITLE | PSH_USECALLBACK); psh.hwndParent = hwndParent; psh.hInstance = hInst; psh.pszCaption = MAKEINTRESOURCE(IDS_DISPLAY_TITLE); psh.nPages = 0; psh.phpage = ahPages; psh.nStartPage = 0; psh.pfnCallback = NULL; if (szCmdLine && szCmdLine[0] && (TEXT('@') != szCmdLine[0])) { psh.nStartPage = UpgradeStartPageMappping(szCmdLine, ARRAYSIZE(szCmdLine)); // We changed the order so do the mapping } ZeroMemory( &psp, sizeof(psp) ); psp.dwSize = sizeof(psp); psp.dwFlags = PSP_DEFAULT; psp.hInstance = hInst; // Build the property sheet. If we are under setup, then just include // the "settings" page, and no otheres if (!g_pThemeUI) { // CoCreate Themes, Appearance, and Advanced Appearance tabs hr = CoCreateInstance(CLSID_ThemeUIPages, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IThemeUIPages, &g_pThemeUI)); } if (SUCCEEDED(hr)) { DWORD dwExecMode; if (g_pThemeUI && (SUCCEEDED(g_pThemeUI->GetExecMode(&dwExecMode))) && (dwExecMode == EM_NORMAL)) { if (!GetSystemMetrics(SM_CLEANBOOT)) { hpsxa = SHCreatePropSheetExtArray(HKEY_LOCAL_MACHINE, REGSTR_PATH_CONTROLSFOLDER TEXT("\\Desk"), 8); } for (i = 0; i < C_PAGES_DESK; i++) { BOOL fHideThisPage = FALSE; if (CheckRestrictionPage(&aPageInfo[i])) { // This page is locked out by admin, don't put it up fHideThisPage = TRUE; } // For Terminal Services, we need to hide the Background tab if a machine // policy is set to not allow wallpaper changes for this session. if ((aPageInfo[i].pfnDlgProc == BackgroundDlgProc) && GetSystemMetrics(SM_REMOTESESSION) && !fHideThisPage) { fHideThisPage = HideBackgroundTabOnTermServices(); } if (-1 == aPageInfo[i].nExtensionID) { psp.pszTemplate = MAKEINTRESOURCE(aPageInfo[i].id); psp.pfnDlgProc = aPageInfo[i].pfnDlgProc; psp.dwFlags = PSP_DEFAULT; psp.lParam = 0L; if (!fHideThisPage && (psp.pfnDlgProc == BackgroundDlgProc)) { // This page can be overridden by extensions if( hpsxa ) { UINT cutoff = psh.nPages; UINT added = 0; HPSXA hpsxaReplace = CreateReplaceableHPSXA(hpsxa); if (hpsxaReplace) { added = SHReplaceFromPropSheetExtArray( hpsxaReplace, CPLPAGE_DISPLAY_BACKGROUND, _AddDisplayPropSheetPage, (LPARAM)&psh); DestroyReplaceableHPSXA(hpsxaReplace); } if (added) { if (psh.nStartPage >= cutoff) psh.nStartPage += added-1; continue; } } } if (!fHideThisPage && (hpsp = CreatePropertySheetPage(&psp))) { psh.phpage[psh.nPages++] = hpsp; } } else if (g_pThemeUI && !fHideThisPage) { IBasePropPage * pBasePage = NULL; // add extensions from the registry // CAUTION: Do not check for "fHideThisPage" here. We need to add the pages for // property sheet extensions even if the "Settings" page is hidden. if (i == IPI_SETTINGS && hpsxa) { UINT cutoff = psh.nPages; UINT added = SHAddFromPropSheetExtArray(hpsxa, _AddDisplayPropSheetPage, (LPARAM)&psh); if (psh.nStartPage >= cutoff) psh.nStartPage += added; } switch (aPageInfo[i].id) { case 0: hr = g_pThemeUI->AddPage(_AddDisplayPropSheetPage, (LPARAM)&psh, aPageInfo[i].nExtensionID); break; case DLG_SCREENSAVER: hr = CoCreateInstance(CLSID_ScreenSaverPage, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IBasePropPage, &pBasePage)); break; case DLG_BACKGROUND: hr = CoCreateInstance(CLSID_CDeskHtmlProp, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IBasePropPage, &pBasePage)); break; default: AssertMsg(0, TEXT("The value must be specified")); break; }; if (pBasePage) { IShellPropSheetExt * pspse = NULL; // If they implement IShellPropSheetExt, then add their base pages. if (SUCCEEDED(pBasePage->QueryInterface(IID_PPV_ARG(IShellPropSheetExt, &pspse)))) { hr = pspse->AddPages(_AddDisplayPropSheetPage, (LPARAM)&psh); pspse->Release(); } hr = g_pThemeUI->AddBasePage(pBasePage); pBasePage->Release(); } } } if (hpsxa) { // Have the dynamically added pages added to IThemeUIPages. AddPropSheetExtArrayToThemePageUI(g_pThemeUI, hpsxa); } // add a fake settings page to fool OEM extensions // !!! this page must be last !!! if (hpsxa) { g_pThemeUI->AddFakeSettingsPage((LPVOID)&psh); } } else { // For the SETUP case, only the display page should show up. hr = g_pThemeUI->AddPage(_AddDisplayPropSheetPage, (LPARAM)&psh, aPageInfo[IPI_SETTINGS].nExtensionID); } if (psh.nStartPage >= psh.nPages) psh.nStartPage = 0; if (psh.nPages) { DWORD dwAction = DESKACTION_NONE; TCHAR szStartPage[MAX_PATH]; WCHAR szOpenPath[MAX_PATH]; IPropertyBag * pPropertyBag; SetStartPage(&psh, szCmdLine, &dwAction, szOpenPath, ARRAYSIZE(szOpenPath), szStartPage, ARRAYSIZE(szStartPage)); hr = g_pThemeUI->QueryInterface(IID_PPV_ARG(IPropertyBag, &pPropertyBag)); if (SUCCEEDED(hr)) { VARIANT var; if (DESKACTION_NONE != dwAction) { var.vt = VT_LPWSTR; var.bstrVal = szOpenPath; hr = pPropertyBag->Write(((DESKACTION_OPENTHEME == dwAction) ? SZ_PBPROP_THEME_LAUNCHTHEME : SZ_PBPROP_APPEARANCE_LAUNCHMSTHEME), &var); } // The following SZ_PBPROP_PREOPEN call will save a "Custom.theme" so users can always go back to // their settings if they don't like changes they make in the CPL. pPropertyBag->Write(SZ_PBPROP_PREOPEN, NULL); pPropertyBag->Release(); } if (PropertySheet(&psh) == ID_PSRESTARTWINDOWS) { exitparam = EWX_REBOOT; } } if (g_pThemeUI) { IUnknown_SetSite(g_pThemeUI, NULL); // Tell him to break the ref-count cycle with his children. g_pThemeUI->Release(); g_pThemeUI = NULL; } // free any loaded extensions if (hpsxa) { SHDestroyPropSheetExtArray(hpsxa); } if (exitparam == EWX_REBOOT) { RestartDialogEx(hwndParent, NULL, exitparam, (SHTDN_REASON_MAJOR_SYSTEM | SHTDN_REASON_MINOR_RECONFIG)); } } return; } DWORD gdwCoverStyle = WS_EX_TOPMOST | WS_EX_TOOLWINDOW; #if 0 // This code creates another thread for the cover window, which we don't really need // and is causing a problem. I am not sure if this is needed for NT, so leave it here.... /////////////////////////////////////////////////////////////////////////////// // // CreateCoverWindow // // creates a window which obscures the display // flags: // 0 means erase to black // COVER_NOPAINT means "freeze" the display // // just post it a WM_CLOSE when you're done with it // /////////////////////////////////////////////////////////////////////////////// typedef struct { DWORD flags; HWND hwnd; HANDLE heRetvalSet; } CVRWNDPARM, * PCVRWNDPARM; DWORD WINAPI CreateCoverWindowThread( LPVOID pv ) { PCVRWNDPARM pcwp = (PCVRWNDPARM)pv; MSG msg; pcwp->hwnd = CreateWindowEx( gdwCoverStyle, sc_szCoverClass, g_szNULL, WS_POPUP | WS_VISIBLE | pcwp->flags, 0, 0, GetSystemMetrics( SM_CXSCREEN ), GetSystemMetrics( SM_CYSCREEN ), NULL, NULL, hInstance, NULL ); if( pcwp->hwnd ) { SetForegroundWindow( pcwp->hwnd ); UpdateWindow( pcwp->hwnd ); } // return wnd; SetEvent(pcwp->heRetvalSet); /* Acquire and dispatch messages until a WM_QUIT message is received. */ while (GetMessage(&msg, NULL, 0L, 0L)) { TranslateMessage(&msg); DispatchMessage(&msg); } ExitThread(0); return 0; } void DestroyCoverWindow(HWND hwndCover) { if (hwndCover) PostMessage(hwndCover, WM_CLOSE, 0, 0L); } HWND FAR PASCAL CreateCoverWindow( DWORD flags ) { CVRWNDPARM cwp; HANDLE hThread; DWORD idTh; DWORD dwWaitResult = 0; // Init params cwp.flags = flags; cwp.hwnd = NULL; cwp.heRetvalSet = CreateEvent(NULL, TRUE, FALSE, NULL); if (cwp.heRetvalSet == NULL) return NULL; // CreateThread hThread = CreateThread(NULL, 0, CreateCoverWindowThread, &cwp, 0, &idTh); CloseHandle(hThread); // Wait for Thread to return the handle to us do { dwWaitResult = MsgWaitForMultipleObjects(1, &cwp.heRetvalSet, FALSE, INFINITE, QS_ALLINPUT); switch(dwWaitResult) { case WAIT_OBJECT_0 + 1: { MSG msg ; // // Allow blocked thread to respond to sent messages. // while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if ( WM_QUIT != msg.message ) { TranslateMessage(&msg); DispatchMessage(&msg); } else { // // Received WM_QUIT. // Don't wait for event. // dwWaitResult = WAIT_FAILED; } } break; } default: break; } } while((WAIT_OBJECT_0 + 1) == dwWaitResult); CloseHandle(cwp.heRetvalSet); return cwp.hwnd; } #endif void DestroyCoverWindow(HWND hwndCover) { DestroyWindow(hwndCover); } HWND FAR PASCAL CreateCoverWindow( DWORD flags ) { HWND hwndCover = CreateWindowEx( gdwCoverStyle, sc_szCoverClass, g_szNULL, WS_POPUP | WS_VISIBLE | flags, GetSystemMetrics( SM_XVIRTUALSCREEN ), GetSystemMetrics( SM_YVIRTUALSCREEN ), GetSystemMetrics( SM_CXVIRTUALSCREEN ), GetSystemMetrics( SM_CYVIRTUALSCREEN ), NULL, NULL, hInstance, NULL ); if( hwndCover ) { SetForegroundWindow( hwndCover ); if (flags & COVER_NOPAINT) SetCursor(LoadCursor(NULL, IDC_WAIT)); UpdateWindow( hwndCover); } return hwndCover; } /////////////////////////////////////////////////////////////////////////////// // CoverWndProc (see CreateCoverWindow) /////////////////////////////////////////////////////////////////////////////// #define WM_PRIV_KILL_LATER (WM_APP + 100) //Private message to kill ourselves later. LRESULT CALLBACK CoverWindowProc( HWND window, UINT message, WPARAM wparam, LPARAM lparam ) { switch( message ) { case WM_CREATE: SetTimer( window, ID_CVRWND_TIMER, CMSEC_COVER_WINDOW_TIMEOUT, NULL ); break; case WM_TIMER: // Times up... Shut ourself down if (wparam == ID_CVRWND_TIMER) DestroyWindow(window); break; case WM_ERASEBKGND: // NOTE: assumes our class brush is the NULL_BRUSH stock object if( !( GetWindowLong( window, GWL_STYLE ) & COVER_NOPAINT ) ) { HDC dc = (HDC)wparam; RECT rc; if( GetClipBox( dc, (LPRECT)&rc ) != NULLREGION ) { FillRect( dc, (LPRECT)&rc, (HBRUSH) GetStockObject( BLACK_BRUSH ) ); // HACK: make sure fillrect is done before we return // this is to better hide flicker during dynares-crap GetPixel( dc, rc.left + 1, rc.top + 1 ); } } break; // We post a private message to ourselves because: // When WM_CLOSE is processed by this window, it calls DestroyWindow() which results in // WM_ACTIVATE (WA_INACTIVE) message to be sent to this window. If this code calls // DestroyWindow again, it causes a loop. So, instead of calling DestroyWindow immediately, // we post ourselves a message and destroy us letter. case WM_ACTIVATE: if( GET_WM_ACTIVATE_STATE( wparam, lparam ) == WA_INACTIVE ) { PostMessage( window, WM_PRIV_KILL_LATER, 0L, 0L ); return 1L; } break; case WM_PRIV_KILL_LATER: DestroyWindow(window); break; case WM_DESTROY: KillTimer(window, ID_CVRWND_TIMER); break; } return DefWindowProc( window, message, wparam, lparam ); } BOOL _FindCoverWindowCallback(HWND hwnd, LPARAM lParam) { TCHAR szClass[MAX_PATH]; HWND *phwnd = (HWND*)lParam; if( !GetClassName(hwnd, szClass, ARRAYSIZE(szClass)) ) return TRUE; if( StrCmp(szClass, sc_szCoverClass) == 0 ) { if( phwnd ) *phwnd = hwnd; return FALSE; } return TRUE; } BYTE WINAPI MyStrToByte(LPCTSTR sz) { BYTE l=0; while (*sz >= TEXT('0') && *sz <= TEXT('9')) { l = l*10 + (*sz++ - TEXT('0')); } return l; } COLORREF ConvertColor(LPTSTR lpColor) { BYTE RGBTemp[3]; LPTSTR lpTemp = lpColor; UINT i; if (!lpColor || !*lpColor) { return RGB(0,0,0); } for (i =0; i < 3; i++) { // Remove leading spaces while (*lpTemp == TEXT(' ')) { lpTemp++; } // Set lpColor to the beginning of the number lpColor = lpTemp; // Find the end of the number and null terminate while ((*lpTemp) && (*lpTemp != TEXT(' '))) { lpTemp++; } if (*lpTemp != TEXT('\0')) { *lpTemp = TEXT('\0'); } lpTemp++; RGBTemp[i] = MyStrToByte(lpColor); } return (RGB(RGBTemp[0], RGBTemp[1], RGBTemp[2])); } LONG APIENTRY CPlApplet( HWND hwnd, WORD message, LPARAM wParam, LPARAM lParam) { LPCPLINFO lpCPlInfo; LPNEWCPLINFO lpNCPlInfo; HWND hwndCover; switch (message) { case CPL_INIT: // Is any one there ? // Init the common controls if (!DeskInitCpl()) return 0; // Load ONE string for emergencies. LoadString (hInstance, IDS_DISPLAY_TITLE, gszDeskCaption, ARRAYSIZE(gszDeskCaption)); return !0; case CPL_GETCOUNT: // How many applets do you support ? return 1; case CPL_INQUIRE: // Fill CplInfo structure lpCPlInfo = (LPCPLINFO)lParam; lpCPlInfo->idIcon = IDI_DISPLAY; lpCPlInfo->idName = IDS_NAME; lpCPlInfo->idInfo = IDS_INFO; lpCPlInfo->lData = 0; break; case CPL_NEWINQUIRE: lpNCPlInfo = (LPNEWCPLINFO)lParam; lpNCPlInfo->hIcon = LoadIcon(hInstance, (LPTSTR) MAKEINTRESOURCE(IDI_DISPLAY)); LoadString(hInstance, IDS_NAME, lpNCPlInfo->szName, ARRAYSIZE(lpNCPlInfo->szName)); if (!LoadString(hInstance, IDS_INFO, lpNCPlInfo->szInfo, ARRAYSIZE(lpNCPlInfo->szInfo))) lpNCPlInfo->szInfo[0] = (TCHAR) 0; lpNCPlInfo->dwSize = sizeof( NEWCPLINFO ); lpNCPlInfo->lData = 0; #if 0 lpNCPlInfo->dwHelpContext = IDH_CHILD_DISPLAY; lstrcpy(lpNCPlInfo->szHelpFile, xszControlHlp); #else lpNCPlInfo->dwHelpContext = 0; lstrcpy(lpNCPlInfo->szHelpFile, TEXT("")); #endif return TRUE; case CPL_DBLCLK: // You have been chosen to run /* * One of your applets has been double-clicked. * wParam is an index from 0 to (NUM_APPLETS-1) * lParam is the lData value associated with the applet */ lParam = 0L; // fall through... case CPL_STARTWPARMS: DeskShowPropSheet( hInstance, hwnd, (LPTSTR)lParam ); // ensure that any cover windows we've created have been destroyed do { hwndCover = 0; EnumWindows( _FindCoverWindowCallback, (LPARAM)&hwndCover ); if( hwndCover ) { DestroyWindow( hwndCover ); } } while( hwndCover ); return TRUE; // Tell RunDLL.exe that I succeeded case CPL_EXIT: // You must really die if (g_hdcMem) { ReleaseDC(NULL, g_hdcMem); g_hdcMem = NULL; } // Fall thru... case CPL_STOP: // You must die if (g_pThemeUI) { IUnknown_SetSite(g_pThemeUI, NULL); // Tell him to break the ref-count cycle with his children. g_pThemeUI->Release(); g_pThemeUI = NULL; } break; case CPL_SELECT: // You have been selected /* * Sent once for each applet prior to the CPL_EXIT msg. * wParam is an index from 0 to (NUM_APPLETS-1) * lParam is the lData value associated with the applet */ break; // // Private message sent when this applet is running under "Setup" // case CPL_SETUP: if (g_pThemeUI) { g_pThemeUI->SetExecMode(EM_SETUP); } break; // Private message used by userenv.dll to refresh the display colors case CPL_POLICYREFRESH: if (g_pThemeUI) // If this object doesn't exist, then we don't need to refresh anything. { IPreviewSystemMetrics * ppsm; if (SUCCEEDED(g_pThemeUI->QueryInterface(IID_PPV_ARG(IPreviewSystemMetrics, &ppsm)))) { ppsm->RefreshColors(); ppsm->Release(); } } SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, 0, FALSE); break; } return 0L; } BOOL WINAPI DeskSetCurrentSchemeW(IN LPCWSTR pwzSchemeName) { BOOL fSuccess = FALSE; IThemeUIPages * pThemeUI = NULL; HRESULT hr; HRESULT hrOle = SHCoInitialize(); if (g_pThemeUI) { hr = g_pThemeUI->QueryInterface(IID_PPV_ARG(IThemeUIPages, &pThemeUI)); } else { hr = CoCreateInstance(CLSID_ThemeManager, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IThemeUIPages, &pThemeUI)); } if (SUCCEEDED(hr)) { IPreviewSystemMetrics * ppsm; hr = pThemeUI->QueryInterface(IID_PPV_ARG(IPreviewSystemMetrics, &ppsm)); if (SUCCEEDED(hr)) { hr = ppsm->DeskSetCurrentScheme(pwzSchemeName); ppsm->Release(); } fSuccess = SUCCEEDED(hr); pThemeUI->Release(); } SHCoUninitialize(hrOle); return fSuccess; } //------------------------------------------------------------------------------------------------ // This function gets the current DPI, reads the last updated DPI from registry and compares // these two. If these two are equal, it returns immediately. // If these two DPI values are different, then it updates the size of UI fonts to reflect the // change in the DPI values. // // This function is called from explorer sothat when DPI value is changed by admin and then every // other user who logs-in gets this change. //------------------------------------------------------------------------------------------------ void WINAPI UpdateUIfontsDueToDPIchange(int iOldDPI, int iNewDPI) { BOOL fSuccess = FALSE; IThemeManager * pThemeMgr = NULL; HRESULT hr; HRESULT hrOle = SHCoInitialize(); if (g_pThemeUI) { hr = g_pThemeUI->QueryInterface(IID_PPV_ARG(IThemeManager, &pThemeMgr)); } else { hr = CoCreateInstance(CLSID_ThemeManager, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IThemeManager, &pThemeMgr)); } if (SUCCEEDED(hr)) { IPropertyBag * pPropertyBag; hr = GetPageByCLSID(pThemeMgr, &PPID_BaseAppearance, &pPropertyBag); if (SUCCEEDED(hr)) { hr = SHPropertyBag_WriteInt(pPropertyBag, SZ_PBPROP_DPI_APPLIED_VALUE, iOldDPI); // We are going to pretend we had the old DPI to force the scale to happen. hr = SHPropertyBag_WriteInt(pPropertyBag, SZ_PBPROP_DPI_MODIFIED_VALUE, iNewDPI); if (SUCCEEDED(hr)) { hr = pThemeMgr->ApplyNow(); } pPropertyBag->Release(); } fSuccess = SUCCEEDED(hr); pThemeMgr->Release(); } SHCoUninitialize(hrOle); } BOOL DeskSetCurrentSchemeA(IN LPCSTR pszSchemeName) { WCHAR wzSchemeName[MAX_PATH]; MultiByteToWideChar(CP_ACP, 0, pszSchemeName, -1, wzSchemeName, ARRAYSIZE(wzSchemeName)); return DeskSetCurrentSchemeW(wzSchemeName); } STDAPI UpdateCharsetChanges(void) { BOOL fSuccess = FALSE; IThemeUIPages * pThemeUI = NULL; HRESULT hr; HRESULT hrOle = SHCoInitialize(); if (g_pThemeUI) { hr = g_pThemeUI->QueryInterface(IID_PPV_ARG(IThemeUIPages, &pThemeUI)); } else { hr = CoCreateInstance(CLSID_ThemeUIPages, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IThemeUIPages, &pThemeUI)); } if (SUCCEEDED(hr)) { IPreviewSystemMetrics * ppsm; hr = pThemeUI->QueryInterface(IID_PPV_ARG(IPreviewSystemMetrics, &ppsm)); if (SUCCEEDED(hr)) { hr = ppsm->UpdateCharsetChanges(); ppsm->Release(); } pThemeUI->Release(); } SHCoUninitialize(hrOle); return hr; }