Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Classic Shell

, 23 Feb 2010
Classic Start menu and other shell features for Windows 7 and Vista.
Prize winner in Competition "Windows 7 "Windows @ Work" Article Contest"
ClassicShellSetup.zip
ClassicShellSrc.zip
ClassicShell
ClassicExplorer
ClassicCopyExt.rgs
ClassicExplorer.rgs
ClassicExplorer32.def
ClassicExplorer64.def
Explorer.ini
ExplorerBand.rgs
ExplorerBHO.rgs
ExplorerL10N.ini
up.ico
up2Disabled.ico
up2Hot.ico
up2Normal.ico
up2Pressed.ico
upDisabled.ico
ClassicShellSetup
ClassicShell.ico
ClassicShellBanner.jpg
ClassicShellSetup.manifest
ClassicShellSetup32
ClassicShellSetup32.vdproj
ClassicShellSetup64
ClassicShellSetup64.vdproj
ClassicStartMenu
ClassicStartMenu.manifest
ClassicStartMenuDLL
StartMenu.ini
StartMenuItems.ini
StartMenuL10N.ini
Docs
ClassicExplorer_files
after.png
before.png
ClassicShell.png
FolderView.gif
iconoffset.png
statusbar.png
titlebar.png
toolbar.png
ClassicShellEULA.rtf
ClassicShellReadme.rtf
ClassicStartMenu_files
ClassicShell.png
screenshot.png
skins.gif
ParseSettings
Skins
ClassicSkin
main_bitmap.bmp
selection.bmp
VistaAero
main_bitmap.bmp
selection.bmp
Win7Aero
main_bitmap.bmp
selection.bmp
Win7Basic
main_bitmap.bmp
selection.bmp
// Classic Shell (c) 2009-2010, Ivo Beltchev
// The sources for Classic Shell are distributed under the MIT open source license

#include "stdafx.h"
#include "IconManager.h"
#include "FNVHash.h"
#include "GlobalSettings.h"
#include "TranslationSettings.h"

const int MAX_FOLDER_LEVEL=10; // don't go more than 10 levels deep

int CIconManager::s_DPI;
int CIconManager::LARGE_ICON_SIZE;
int CIconManager::SMALL_ICON_SIZE;
bool CIconManager::s_bStopLoading;
std::map<unsigned int,HICON> CIconManager::s_PreloadedIcons;
CRITICAL_SECTION CIconManager::s_PreloadSection;

CIconManager g_IconManager; // must be after s_PreloadedIcons and s_PreloadSection because the constructor starts a thread that uses the Preloaded stuff

void CIconManager::Init( void )
{
	wchar_t path[_MAX_PATH];
	GetModuleFileName(NULL,path,_countof(path));
	// the ClassicStartMenu.exe is not marked as DPI-aware. It can't call SetProcessDPIAware because it happens after
	// our DLL is loaded, and is too late. DPI-awareness can be set with a manifest, but that adds link-time warnings.
	// so we hack it here
	if (_wcsicmp(PathFindFileName(path),L"ClassicStartMenu.exe")==0)
		SetProcessDPIAware();

	{
		// get the DPI setting
		HDC hdc=::GetDC(NULL);
		s_DPI=GetDeviceCaps(hdc,LOGPIXELSY);
		::ReleaseDC(NULL,hdc);
	}

	int iconSize;
	const wchar_t *str=FindSetting("SmallIconSize");
	if (str)
	{
		iconSize=_wtol(str);
		if (iconSize<8) iconSize=8;
		if (iconSize>128) iconSize=128;
	}
	else
	{
		if (s_DPI>120)
			iconSize=24;
		else if (s_DPI>96)
			iconSize=20;
		else
			iconSize=16;
	}
	SMALL_ICON_SIZE=iconSize;
	str=FindSetting("LargeIconSize");
	if (str)
		LARGE_ICON_SIZE=_wtol(str);
	else
		LARGE_ICON_SIZE=iconSize*2;
	if (LARGE_ICON_SIZE<8) LARGE_ICON_SIZE=8;
	if (LARGE_ICON_SIZE>128) LARGE_ICON_SIZE=128;

	bool bRTL=IsLanguageRTL();
	// create the image lists
	m_LargeIcons=ImageList_Create(LARGE_ICON_SIZE,LARGE_ICON_SIZE,ILC_COLOR32|ILC_MASK|(bRTL?ILC_MIRROR:0),1,16);
	m_SmallIcons=ImageList_Create(SMALL_ICON_SIZE,SMALL_ICON_SIZE,ILC_COLOR32|ILC_MASK|(bRTL?ILC_MIRROR:0),1,16);

	// add default blank icons (default icon for file with no extension)
	SHFILEINFO info;
	if (SHGetFileInfo(L"file",FILE_ATTRIBUTE_NORMAL,&info,sizeof(info),SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_LARGEICON))
	{
		ImageList_AddIcon(m_LargeIcons,info.hIcon);
		DestroyIcon(info.hIcon);
	}
	if (SHGetFileInfo(L"file",FILE_ATTRIBUTE_NORMAL,&info,sizeof(info),SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_SMALLICON))
	{
		ImageList_AddIcon(m_SmallIcons,info.hIcon);
		DestroyIcon(info.hIcon);
	}

	InitializeCriticalSection(&s_PreloadSection);

	if (_wcsicmp(PathFindFileName(path),L"explorer.exe")==0)
	{
		// don't preload icons if running outside of the explorer
		s_bStopLoading=false;
		m_PreloadThread=CreateThread(NULL,0,PreloadThread,NULL,0,NULL);
	}
	else
		m_PreloadThread=INVALID_HANDLE_VALUE;
}

void CIconManager::StopPreloading( bool bWait )
{
	if (m_PreloadThread!=INVALID_HANDLE_VALUE)
	{
		s_bStopLoading=true;
		WaitForSingleObject(m_PreloadThread,bWait?INFINITE:0);
		CloseHandle(m_PreloadThread);
		DeleteCriticalSection(&s_PreloadSection);
		m_PreloadThread=INVALID_HANDLE_VALUE;
	}
}

CIconManager::~CIconManager( void )
{
	if (m_LargeIcons) ImageList_Destroy(m_LargeIcons);
	if (m_SmallIcons) ImageList_Destroy(m_SmallIcons);
}

// Retrieves an icon from a shell folder and child ID
int CIconManager::GetIcon( IShellFolder *pFolder, PCUITEMID_CHILD item, bool bLarge )
{
	ProcessPreloadedIcons();

	// get the IExtractIcon object
	CComPtr<IExtractIcon> pExtract;
	HRESULT hr=pFolder->GetUIObjectOf(NULL,1,&item,IID_IExtractIcon,NULL,(void**)&pExtract);
	HICON hIcon;
	unsigned int key;
	if (SUCCEEDED(hr))
	{
		// get the icon location
		wchar_t location[_MAX_PATH];
		int index=0;
		UINT flags=0;
		hr=pExtract->GetIconLocation(0,location,_countof(location),&index,&flags);
		if (hr!=S_OK)
			return 0;

		// check if this location+index is in the cache
		key=CalcFNVHash(location,CalcFNVHash(&index,4));
		if (bLarge)
		{
			if (m_LargeCache.find(key)!=m_LargeCache.end())
				return m_LargeCache[key];
		}
		else
		{
			EnterCriticalSection(&s_PreloadSection);
			int res=-1;
			if (m_SmallCache.find(key)!=m_SmallCache.end())
				res=m_SmallCache[key];
			LeaveCriticalSection(&s_PreloadSection);
			if (res>=0) return res;
		}

		// extract the icon
		hr=pExtract->Extract(location,index,bLarge?&hIcon:NULL,bLarge?NULL:&hIcon,MAKELONG(LARGE_ICON_SIZE,SMALL_ICON_SIZE));
		if (hr==S_FALSE)
		{
			// the IExtractIcon object didn't do anything - use ExtractIconEx instead
			if (ExtractIconEx(location,index,bLarge?&hIcon:NULL,bLarge?NULL:&hIcon,1)==1)
				hr=S_OK;
		}
	}
	else
	{
		// try again using the ANSI version
		CComPtr<IExtractIconA> pExtractA;
		hr=pFolder->GetUIObjectOf(NULL,1,&item,IID_IExtractIconA,NULL,(void**)&pExtractA);
		if (FAILED(hr))
			return 0;

		// get the icon location
		char location[_MAX_PATH];
		int index=0;
		UINT flags=0;
		hr=pExtractA->GetIconLocation(0,location,_countof(location),&index,&flags);
		if (hr!=S_OK)
			return 0;

		// check if this location+index is in the cache
		key=CalcFNVHash(location,CalcFNVHash(&index,4));
		if (bLarge)
		{
			if (m_LargeCache.find(key)!=m_LargeCache.end())
				return m_LargeCache[key];
		}
		else
		{
			EnterCriticalSection(&s_PreloadSection);
			int res=-1;
			if (m_SmallCache.find(key)!=m_SmallCache.end())
				res=m_SmallCache[key];
			LeaveCriticalSection(&s_PreloadSection);
			if (res>=0) return res;
		}

		// extract the icon
		hr=pExtractA->Extract(location,index,bLarge?&hIcon:NULL,bLarge?NULL:&hIcon,MAKELONG(LARGE_ICON_SIZE,SMALL_ICON_SIZE));
		if (hr==S_FALSE)
		{
			// the IExtractIcon object didn't do anything - use ExtractIconEx instead
			if (ExtractIconExA(location,index,bLarge?&hIcon:NULL,bLarge?NULL:&hIcon,1)==1)
				hr=S_OK;
		}
	}

	// add to the image list
	int index=0;
	if (hr==S_OK)
	{
		index=ImageList_AddIcon(bLarge?m_LargeIcons:m_SmallIcons,hIcon);
		DestroyIcon(hIcon);
	}

	// add to the cache
	if (bLarge)
		m_LargeCache[key]=index;
	else
	{
		EnterCriticalSection(&s_PreloadSection);
		m_SmallCache[key]=index;
		LeaveCriticalSection(&s_PreloadSection);
	}

	return index;
}

// Retrieves an icon from a file and icon index (index>=0 - icon index, index<0 - resource ID)
int CIconManager::GetIcon( const wchar_t *location, int index, bool bLarge )
{
	ProcessPreloadedIcons();

	// check if this location+index is in the cache
	unsigned int key=CalcFNVHash(location,CalcFNVHash(&index,4));
	if (bLarge)
	{
		if (m_LargeCache.find(key)!=m_LargeCache.end())
			return m_LargeCache[key];
	}
	else
	{
		EnterCriticalSection(&s_PreloadSection);
		int res=-1;
		if (m_SmallCache.find(key)!=m_SmallCache.end())
			res=m_SmallCache[key];
		LeaveCriticalSection(&s_PreloadSection);
		if (res>=0) return res;
	}

	// extract icon
	HICON hIcon;
	HMODULE hMod=GetModuleHandle(PathFindFileName(location));
	if (hMod && index<0)
	{
		int iconSize=bLarge?LARGE_ICON_SIZE:SMALL_ICON_SIZE;
		hIcon=(HICON)LoadImage(hMod,MAKEINTRESOURCE(-index),IMAGE_ICON,iconSize,iconSize,LR_DEFAULTCOLOR);
	}
	else if (ExtractIconEx(location,index,bLarge?&hIcon:NULL,bLarge?NULL:&hIcon,1)!=1)
		hIcon=NULL;

	// add to the image list
	if (hIcon)
	{
		index=ImageList_AddIcon(bLarge?m_LargeIcons:m_SmallIcons,hIcon);
		DestroyIcon(hIcon);
	}
	else
		index=0;

	// add to the cache
	if (bLarge)
		m_LargeCache[key]=index;
	else
	{
		EnterCriticalSection(&s_PreloadSection);
		m_SmallCache[key]=index;
		LeaveCriticalSection(&s_PreloadSection);
	}

	return index;
}

// Retrieves an icon from shell32.dll by resource ID
int CIconManager::GetStdIcon( int id, bool bLarge )
{
	// check if this id is in the cache
	unsigned int key=CalcFNVHash(&id,4);
	if (bLarge)
	{
		if (m_LargeCache.find(key)!=m_LargeCache.end())
			return m_LargeCache[key];
	}
	else
	{
		EnterCriticalSection(&s_PreloadSection);
		int res=-1;
		if (m_SmallCache.find(key)!=m_SmallCache.end())
			res=m_SmallCache[key];
		LeaveCriticalSection(&s_PreloadSection);
		if (res>=0) return res;
	}

	// get from shell32.dll
	HMODULE hShell32=GetModuleHandle(L"shell32.dll");
	if (!hShell32) return 0;
	int size=bLarge?LARGE_ICON_SIZE:SMALL_ICON_SIZE;
	HICON hIcon=(HICON)LoadImage(hShell32,MAKEINTRESOURCE(id),IMAGE_ICON,size,size,LR_DEFAULTCOLOR);
	if (!hIcon)
		return 0;

	// add to the image list
	int index=ImageList_AddIcon(bLarge?m_LargeIcons:m_SmallIcons,hIcon);
	DestroyIcon(hIcon);

	// add to the cache
	if (bLarge)
		m_LargeCache[key]=index;
	else
	{
		EnterCriticalSection(&s_PreloadSection);
		m_SmallCache[key]=index;
		LeaveCriticalSection(&s_PreloadSection);
	}

	return index;
}

int CIconManager::GetCustomIcon( const wchar_t *path, bool bLarge )
{
	wchar_t text[1024];
	wcscpy_s(text,path);
	DoEnvironmentSubst(text,_countof(text));
	wchar_t *c=wcsrchr(text,',');
	int index=0;
	if (c)
	{
		*c=0;
		index=-_wtol(c+1);
	}
	return GetIcon(text,index,bLarge);
}

void CIconManager::ProcessPreloadedIcons( void )
{
	EnterCriticalSection(&s_PreloadSection);
	for (std::map<unsigned int,HICON>::const_iterator it=s_PreloadedIcons.begin();it!=s_PreloadedIcons.end();++it)
	{
		unsigned int key=it->first;
		HICON hIcon=it->second;
		if (m_SmallCache.find(key)==m_SmallCache.end())
		{
			// add to the image list and to the cache
			m_SmallCache[key]=ImageList_AddIcon(m_SmallIcons,hIcon);
		}
		DestroyIcon(hIcon);
	}
	s_PreloadedIcons.clear();
	LeaveCriticalSection(&s_PreloadSection);
}

// Recursive function to preload the icons for a folder
void CIconManager::LoadFolderIcons( IShellFolder *pFolder, int level )
{
	CComPtr<IEnumIDList> pEnum;
	if (pFolder->EnumObjects(NULL,SHCONTF_NONFOLDERS|SHCONTF_FOLDERS,&pEnum)!=S_OK) pEnum=NULL;
	if (!pEnum) return;

	PITEMID_CHILD pidl;
	while (pEnum->Next(1,&pidl,NULL)==S_OK)
	{
		CComPtr<IExtractIcon> pExtract;
		if (SUCCEEDED(pFolder->GetUIObjectOf(NULL,1,&pidl,IID_IExtractIcon,NULL,(void**)&pExtract)))
		{
			// get the icon location
			wchar_t location[_MAX_PATH];
			int index=0;
			UINT flags=0;
			if (SUCCEEDED(pExtract->GetIconLocation(0,location,_countof(location),&index,&flags)))
			{
				// check if this location+index is in the cache
				unsigned int key=CalcFNVHash(location,CalcFNVHash(&index,4));
				EnterCriticalSection(&s_PreloadSection);
				bool bLoaded=g_IconManager.m_SmallCache.find(key)!=g_IconManager.m_SmallCache.end() || s_PreloadedIcons.find(key)!=s_PreloadedIcons.end();
				LeaveCriticalSection(&s_PreloadSection);
				if (!bLoaded)
				{
					// extract the icon
					HICON hIcon;
					HRESULT hr=pExtract->Extract(location,index,NULL,&hIcon,MAKELONG(LARGE_ICON_SIZE,SMALL_ICON_SIZE));
					if (hr==S_FALSE)
					{
						// the IExtractIcon object didn't do anything - use ExtractIconEx instead
						if (ExtractIconEx(location,index,NULL,&hIcon,1)==1)
							hr=S_OK;
					}
					Sleep(10); // pause for a bit to reduce the stress on the system
					if (hr==S_OK)
					{
						EnterCriticalSection(&s_PreloadSection);
						s_PreloadedIcons[key]=hIcon;
						LeaveCriticalSection(&s_PreloadSection);
					}
				}
			}
		}
		else
		{
			// try the ANSI version
			CComPtr<IExtractIconA> pExtractA;
			if (SUCCEEDED(pFolder->GetUIObjectOf(NULL,1,&pidl,IID_IExtractIconA,NULL,(void**)&pExtractA)))
			{
				// get the icon location
				char location[_MAX_PATH];
				int index=0;
				UINT flags=0;
				if (SUCCEEDED(pExtractA->GetIconLocation(0,location,_countof(location),&index,&flags)))
				{
					// check if this location+index is in the cache
					unsigned int key=CalcFNVHash(location,CalcFNVHash(&index,4));
					EnterCriticalSection(&s_PreloadSection);
					bool bLoaded=g_IconManager.m_SmallCache.find(key)!=g_IconManager.m_SmallCache.end() || s_PreloadedIcons.find(key)!=s_PreloadedIcons.end();
					LeaveCriticalSection(&s_PreloadSection);
					if (!bLoaded)
					{
						// extract the icon
						HICON hIcon;
						HRESULT hr=pExtractA->Extract(location,index,NULL,&hIcon,MAKELONG(LARGE_ICON_SIZE,SMALL_ICON_SIZE));
						if (hr==S_FALSE)
						{
							// the IExtractIcon object didn't do anything - use ExtractIconEx instead
							if (ExtractIconExA(location,index,NULL,&hIcon,1)==1)
								hr=S_OK;
						}
						Sleep(10); // pause for a bit to reduce the stress on the system
						if (hr==S_OK)
						{
							EnterCriticalSection(&s_PreloadSection);
							s_PreloadedIcons[key]=hIcon;
							LeaveCriticalSection(&s_PreloadSection);
						}
					}
				}
			}
		}

		if (level<MAX_FOLDER_LEVEL)
		{
			SFGAOF flags=SFGAO_FOLDER|SFGAO_STREAM|SFGAO_LINK;
			if (SUCCEEDED(pFolder->GetAttributesOf(1,&pidl,&flags)) && (flags&(SFGAO_FOLDER|SFGAO_STREAM|SFGAO_LINK))==SFGAO_FOLDER)
			{
				// go into subfolders but not archives or links to folders
				CComPtr<IShellFolder> pChild;
				if (SUCCEEDED(pFolder->BindToObject(pidl,NULL,IID_IShellFolder,(void**)&pChild)))
					LoadFolderIcons(pChild,level+1);
			}
		}
		ILFree(pidl);
		if (s_bStopLoading) break;
	}
}

static KNOWNFOLDERID g_CacheFolders[]=
{
	FOLDERID_StartMenu,
	FOLDERID_CommonStartMenu,
	FOLDERID_ControlPanelFolder,
	FOLDERID_Favorites,
};

DWORD CALLBACK CIconManager::PreloadThread( void *param )
{
	// wait 5 seconds before starting the preloading
	// so we don't interfere with the other boot-time processes
	for (int i=0;i<50;i++)
	{
		if (s_bStopLoading) return 0;
		Sleep(100);
	}

	wchar_t path[_MAX_PATH];
	GetModuleFileName(g_Instance,path,_countof(path));
	LoadLibrary(path); // stop the DLL from unloading
	SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_IDLE);
	CoInitialize(NULL);
	CComPtr<IShellFolder> pDesktop;
	SHGetDesktopFolder(&pDesktop);
	for (int i=0;i<_countof(g_CacheFolders);i++)
	{
		PIDLIST_ABSOLUTE path;
		SHGetKnownFolderIDList(g_CacheFolders[i],0,NULL,&path);
		CComPtr<IShellFolder> pFolder;
		pDesktop->BindToObject(path,NULL,IID_IShellFolder,(void**)&pFolder);
		LoadFolderIcons(pFolder,g_CacheFolders[i]==FOLDERID_ControlPanelFolder?MAX_FOLDER_LEVEL:0);
		ILFree(path);
		if (s_bStopLoading) break;
	}
	CoUninitialize();
	FreeLibraryAndExitThread(g_Instance,0); // release the DLL
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License

About the Author

Ivo Beltchev
Software Developer (Senior)
United States United States
Ivo started programming in 1985 on an Apple ][ clone. He graduated from Sofia University, Bulgaria with a MSCS degree. Ivo has been working as a professional programmer for over 12 years, and as a professional game programmer for over 10. He is currently employed in Pandemic Studios, a video game company in Los Angeles, California.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 23 Feb 2010
Article Copyright 2009 by Ivo Beltchev
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid