Among the questions that I received today, was “How can the default color scheme be changed?”  The default color scheme is the one used with the system boots.   It turns out that changing the default color scheme in an OS image isn’t very easy to do. A quick look in the registry might suggest that it should be easy. It looks like just changing the following registry setting would be enough to do it:
[HKEY_CURRENT_USER\ControlPanel\Appearance]
    "Current"=mui_sz:"<Scheme name>"
But when you test the change, you will see that the color scheme didn’t change. Then you would probably go to the Control Panel and see that the change takes effect as soon as you open the Display\Appearance applet.
The reason is that the Display\Appearance tab contains code to process the registry settings and set the colors based on the scheme. That means that to change the default color scheme, you are going to need to write some code. The good news is that Microsoft has provided a starting point in the source code for the color scheme Control Panel. The code can be found in PUBLIC\WCESHELLFE\OAK\CTLPNL\CPLMAIN\colschem.cpp.
The code in the control panel applet isn’t that easy to follow because it doesn’t have a single function that sets the scheme. The algorithm for setting the color scheme is:
1.       Get the color scheme name from the registry
2.       Find the color scheme data in the registry associated with the chosen color scheme
3.       Tell the system to use the color scheme data
The following function, SetSystemColors(), implements the algorithm:
BOOL SetColorScheme()
{
                TCHAR *SchemeName = NULL;
                BOOL Success = FALSE;
                HKEY hkCurrentScheme;
 
                //   1. Get the color scheme name from the registry
                if( GetSchemeName( &SchemeName ) )
                {
                                // 2. Find the color scheme data in the registry associated with the chosen color scheme
                                //       FindScheme() returns the open registry key (HKEY) for the color scheme data
                                hkCurrentScheme = FindScheme( SchemeName );
 
                                if( hkCurrentScheme )
                                {
                                                //   3. Tell the system to use the color scheme data
                                                SetSystemColors( hkCurrentScheme );
                                                RegCloseKey( hkCurrentScheme );
                                }
                                free( SchemeName );
                }
                return Success;
}
1. Get the color scheme name from the registry
Start by getting the color scheme name from the registry. The function GetSchemeName() allocates space for the scheme name string and reads the name from the registry:
BOOL GetSchemeName( TCHAR **SchemeName )
{
                DWORD Result;
                HKEY hKey;
                DWORD NumBytes = 0;
                DWORD Type;
                BOOL Success = FALSE;
 
                // Open the Registry Key
                Result = RegOpenKeyEx(HKEY_CURRENT_USER, (LPCWSTR)SCHEME_APPEARANCE, 0, 0, &hKey);
 
                if( ERROR_SUCCESS == Result )
                {
                                // This is a fake read, all it does is fill in NumBytes with the number of
                                // bytes in the string value plus the null character.
                                Result = RegQueryValueEx( hKey, SCHEME_CURRENT, NULL, &Type, NULL, &NumBytes );
                                if( NumBytes > 0 )
                                {
                                                // Now we know how big the string is allocate and read it
                                                *SchemeName = (TCHAR *)malloc( NumBytes );
                                                if( *SchemeName != NULL )
                                                {
                                                                Result = RegQueryValueEx( hKey, SCHEME_CURRENT, NULL, &Type, (LPBYTE)*SchemeName, &NumBytes );
                                                                if( ERROR_SUCCESS == Result )
                                                                                Success = TRUE;
                                                }
                                }
                                RegCloseKey( hKey );
                }
                return Success;
}
Regular readers of this blog will recognize this code from Windows CE: Reading a String from the Registry. This modifies that code to make is special purpose for reading the color scheme name.
2. Find the color scheme data in the registry associated with the chosen color scheme
To find the color scheme data in the registry, we need to enumerate the sub-keys of HKEY_CURRENT_USER\ControlPanel\Appearance\Schemes to find a key with the value DisplayName that matches the currently selected color scheme.   English speaking software engineers might make the incorrect assumption that they could use the key name, but they should be cautious because while the key names are fixed, the DisplayName may be localized.
The function FindScheme() handles enumerating the registry and then returns an HKEY that is the handle to the open registry key:
HKEY FindScheme( TCHAR *SchemeName )
{
                HKEY hRegKey;
                DWORD Result;
                DWORD Index = 0;
                TCHAR *ResultName = NULL;
                DWORD ResultNameLength;
                DWORD MaxSubKeyLength;
                DWORD SubKeys;
                DWORD NumValues;
                DWORD MaxValueNameLen;
                DWORD MaxValueLen;
                HKEY hkSchema = NULL;
                BOOL Done = FALSE;
                TCHAR *CurrentSchema;
 
                if( ERROR_SUCCESS != RegOpenKeyEx( HKEY_CURRENT_USER, SCHEME_COLORSCHEMES, 0, 0, &hRegKey ) )
                {
                                RETAILMSG( 1, (TEXT("Failed to create reg key\n")));
                                return NULL;
                }
 
                Result = RegQueryInfoKey(
                                                                hRegKey,
                                                                NULL,
                                                                NULL,
                                                                NULL,
                                                                &SubKeys,
                                                                &MaxSubKeyLength,
                                                                NULL,
                                                                &NumValues,
                                                                &MaxValueNameLen,
                                                                &MaxValueLen,
                                                                NULL,
                                                                NULL
                                                );
 
                if( SubKeys > 0 )
                {
                                // Need to add one for the null character at the end.
                                ResultNameLength = MaxSubKeyLength + 1;
                                // Allocate a buffer to read the subkeys. We only need to
                                // do this once because we know the length of the longest
                                // subkey is set in ResultNameLength
                                ResultName = malloc( ResultNameLength * sizeof( TCHAR ));
                                do {
 
                                                // ResultNameLength will change when RegEnumKeyEx() is called
                                                // so reinitialize it on each iteration
                                                ResultNameLength = MaxSubKeyLength + 1;
 
                                                Result = RegEnumKeyEx(
                                                                                                hRegKey,
                                                                                                Index,
                                                                                                ResultName,
                                                                                                &ResultNameLength,
                                                                                                NULL,
                                                                                                NULL,
                                                                                                NULL,
                                                                                                NULL
                                                                                );
                                                if( Result == ERROR_SUCCESS )
                                                {
                                                                if( ERROR_SUCCESS == RegOpenKeyEx( hRegKey, (LPCWSTR)ResultName, 0, 0, &hkSchema ) )
                                                                {
                                                                                DWORD Type;
                                                                                DWORD NumBytes;
                                                                                // This is a fake read, all it does is fill in NumBytes with the number of
                                                                                // bytes in the string value plus the null character.
                                                                                Result = RegQueryValueEx( hkSchema, SCHEME_DISPLAYNAME, NULL, &Type, NULL, &NumBytes );
                                                                                if( NumBytes > 0 )
                                                                                {
                                                                                                // Now we know how big the string is allocate and read it
                                                                                                CurrentSchema = (TCHAR *)malloc( NumBytes );
                                                                                                if( CurrentSchema != NULL )
                                                                                                {
                                                                                                                Result = RegQueryValueEx( hkSchema, SCHEME_DISPLAYNAME, NULL, &Type, (LPBYTE)CurrentSchema, &NumBytes );
                                                                                                                if( ERROR_SUCCESS == Result )
                                                                                                                {
                                                                                                                                if( !wcscmp( CurrentSchema, SchemeName ) )
                                                                                                                                {
                                                                                                                                                Done = TRUE;
                                                                                                                                }
                                                                                                                }
                                                                                                                free( CurrentSchema );
                                                                                                }
                                                                                }
                                                                                if( !Done )
                                                                                {
                                                                                                RegCloseKey( hkSchema );
                                                                                                hkSchema = NULL;
                                                                                }
                                                                }
                                                }
                                                Index++;
 
                                } while( !Done && Result != ERROR_NO_MORE_ITEMS );
                                free( ResultName );
                }
                RegCloseKey( hRegKey );
 
                return hkSchema;
}
Again, regular readers of this blog might recognize this code. I copied the code from Windows CE: Enumerating the Registry and modified it to look for the color scheme registry key.
3. Tell the system to use the color scheme data
Finally, we need to send the color scheme data to the system so that the colors will be used. The function SetSystemColors() will do this using the system function SetSysColors(). Before calling SetSysColors() we need to make sure that the data in the registry is complete. If the data is not complete, then fill in the missing data with existing system colors. Then we need to fill in an array, SystemColors, with the system color identifiers:
BOOL SetSystemColors( HKEY hkCurrentScheme )
{
                BOOL Success = FALSE;
                SCHEME_SETTINGS_TYPE Settings;
                DWORD NumBytes = 0;
                DWORD Type;
                DWORD Result;
                int SystemColors[COLOR_MAX];
                DWORD Color;
 
                Result = RegQueryValueEx( hkCurrentScheme, SCHEME_SETTINGS, NULL, &Type, NULL, &NumBytes );
                if( NumBytes <= sizeof( Settings ) )
                {
                                Result = RegQueryValueEx( hkCurrentScheme, SCHEME_SETTINGS, NULL, &Type, (LPBYTE)&Settings, &NumBytes );
                                if( ERROR_SUCCESS == Result )
                                {
                                                if( Settings.Version == SCHEME_VERSION_WINCE )
                                                {    for (Color = ( COLOR_MAX - ((NumBytes - ( sizeof( Settings.Version ) + sizeof( Settings.Filler )))/sizeof( Settings.Colors))); Color < COLOR_MAX; Color++)
                                                                {
                                                                                Settings.Colors[Color] = GetSysColor(Color | SYS_COLOR_INDEX_FLAG);
                                                                }
                                                                for( Color = 0; Color < COLOR_MAX; Color++)
                                                                                SystemColors[Color] = Color | SYS_COLOR_INDEX_FLAG;
 
                                                                SetSysColors(COLOR_MAX, SystemColors, Settings.Colors);
                                                               
                                                                Success = TRUE;
                                                }
                                }
                }
                return Success;
}
4 Pulling it all together
To use this code, we need to defines some macros and typedefs. The following does that and includes a WinMain() that calls SetSystemColors() and just in case you want to run this application from the HKEY_LOCAL_MACHINE\Init key it calls SignalStarted():
#include <Windows.h>
 
// Color schemes
#define SCHEME_COLORSCHEMES              TEXT("ControlPanel\\Appearance\\Schemes")
#define SCHEME_APPEARANCE                TEXT("ControlPanel\\Appearance")
#define SCHEME_CURRENT                   TEXT("Current")
#define SCHEME_FULLCONTROL               TEXT("FullControl")
#define SCHEME_DISPLAYNAME                                              TEXT("DisplayName")
#define SCHEME_SETTINGS                                        TEXT("Settings")
 
#define SCHEME_VERSION_WINCE -1
 
#define COLOR_MAX C_SYS_COLOR_TYPES
 
typedef struct
{
    SHORT Version;
    SHORT Filler;
    COLORREF Colors[COLOR_MAX];
} SCHEME_SETTINGS_TYPE;
 
int WINAPI WinMain(     HINSTANCE hInstance,
                                                                                HINSTANCE hPrevInstance,
                                                                                LPTSTR    lpCmdLine,
                                                                                int       nCmdShow)
{
                SetColorScheme();
 
                // Just in case this is used from the HKLM\Init key
                SignalStarted( _wtol(lpCmdLine) );
}
Now we have an application that will set the default color scheme when the system boots.
 
Copyright © 2009 – Bruce Eitman
All Rights Reserved