Skip to content

Customizing your console colors

Mike Griese edited this page May 26, 2017 · 1 revision

NOTE - HERE BE DRAGONS

This is a very experimental customization. It involves shelling out to powershell.exe from inside your linux distro, which maybe isn't a super great idea. It's not very tested, any probably full of bugs. It's also not very fast. The call to powershell could probably be optimized, instead of invoking powershell.exe 16 times.


This is a sample that lets you change the colors of the console color table for your users so that the console may more closely reflect your own branding.

For this example, the color setup is done during the SetupDistro phase of the first-launch, right after the user inputs a new username and password. It is done then instead of on every launch for a few reasons:

  • This mechanism for setting the table takes a few seconds. Doing that on every launch would be VERY noticable.
  • You want to let your users customize the colors away from your defaults too. Say that they have their own preferences. This mechanism allows you to set your defaults, then let the user set their own preferences over them.
  • You don't necessarily want to affect the currently running console. SetConsoleScreenBufferInfoEx lets a program change the colors of the current console window, but this
    • overwrites any of the user's preferences
    • only applies to the current window
    • persists even after your launcher is exited, say the user launched your distro from inside another shell, like cmd.exe. Now the cmd.exe window has your colors.

So, in order to provide the best user experience, we'll install our color palette during setup, so on future launches, they'll get our color scheme. To do this, we're going to be writing to the console window's registry entries under HKCU\Console. This is where the per-application defaults for console applications live. If your user launches your distro using the execution alias from the commandline, or click on the tile, they'll get the settings from the registry first, and any changes they make will be applied on top of those. We need to find our application name and translate it into the write format that the console looks for (translate slashes to underscores).

There's also another small trick - because we're running in an app container, we have a virtualized registry. This means that any changes we make in the app container won't be present in the actual registry, only our own view of it. So, to get to the main registry, we need to launch a process outside the app container. Fortunately, WSL exists outside the app container! So, we'll use our freshly installed distro to invoke powershell.exe through the power of interop, and write the registry using powershell.

We define a helper function to help us perform this task. I put this in helpers.cpp, but it doesn't really matter.

const size_t DARK_BLACK     =  0;
const size_t DARK_BLUE      =  1;
const size_t DARK_GREEN     =  2;
const size_t DARK_CYAN      =  3;
const size_t DARK_RED       =  4;
const size_t DARK_MAGENTA   =  5;
const size_t DARK_YELLOW    =  6;
const size_t DARK_WHITE     =  7;
const size_t BRIGHT_BLACK   =  8;
const size_t BRIGHT_BLUE    =  9;
const size_t BRIGHT_GREEN   = 10;
const size_t BRIGHT_CYAN    = 11;
const size_t BRIGHT_RED     = 12;
const size_t BRIGHT_MAGENTA = 13;
const size_t BRIGHT_YELLOW  = 14;
const size_t BRIGHT_WHITE   = 15;

void _SetTableIndex(const wchar_t * distroName, size_t index, DWORD color, std::wstring& regRoot)
{

    // ColorTable always has two digits for the index
    std::wstring colorTableKey = index<10? L"ColorTable0" : L"ColorTable";
    colorTableKey += std::to_wstring(index);

    std::wstring colorTableValue = std::to_wstring(color);

    std::wstring commandLine =  L"powershell.exe 'set-ItemProperty -path ";
    commandLine += regRoot;
    commandLine += L" -name \"";
    commandLine += colorTableKey;
    commandLine += L"\" -value ";
    commandLine += colorTableValue;
    commandLine += L" -Force | Out-Null'";

    DWORD returnValue = 0;
    wslApi.WslLaunchInteractive(distroName, commandLine.c_str(), true, &returnValue);

}

// Function Description:
// EXPERIMENTAL
// - Takes a 16 element long array of colors and applies them as the default 
//      color table for this exe. These are in the order of the Windows console 
//      color table, NOT the linux table. See included constants for indicies.
//   DOES NOT apply the colors to the current window. You don't necessarily want
//      to overwrite the user's preferences without their input.
//   This uses interop to launch powershell.exe. If powershell isn't available, 
//      I believe it won't work at all.
HRESULT Helpers::SetColorTable(const wchar_t * distroName, const DWORD* const colorTable)
{
    // First get the exe path - The console uses this to load settings
    wchar_t tmp[MAX_PATH];
    GetModuleFileName( nullptr, tmp, MAX_PATH );
    // Make sure to translate slashes to underscores
    for (int i = 0; i < MAX_PATH; ++i)
    {
        if (tmp[i] == '\\') tmp[i] = '_';
    }


    std::wstring regRoot = L"\\\"hkcu:\\Console\\";
    regRoot += tmp;
    regRoot += L"\\\"";

    std::wstring commandLine = L"powershell.exe 'New-Item -path ";// + regRoot;
    commandLine += regRoot;
    commandLine += L" -Force | Out-Null'";
    
    DWORD returnValue = 0;
    wslApi.WslLaunchInteractive(distroName, commandLine.c_str(), true, &returnValue);

    for (size_t i = 0; i < 16; i++)
    {
        _SetTableIndex(distroName, i, colorTable[i], regRoot);   
    }

    return S_OK;

}

Then, you can call the helper in your distro. This function sets up the color table with the colors we've chosen for our branding:

// Method Description:
// - Allows you to add your own default colors to the color palette of the console.
//      This can be used to differentiate your distro's console windows from 
//      those of other applications.
// - Note that this only applies to future launches of the console.
// - These values can be manually changed by the user if they so choose.
// - This does not apply to running `mydistro` from an existing console window.
// Arguments:
// - None
// Return Value:
// - An HRESULT with the failure code, or S_OK if it succeeded.
void MyLinuxDistroLauncher::_SetMyDefaultColorTable()
{
    DWORD colorTable[16];
    colorTable[DARK_BLACK]     = RGB(19,17,23);
    colorTable[DARK_BLUE]      = RGB(6,54,222);
    colorTable[DARK_GREEN]     = RGB(57,151,50);
    colorTable[DARK_CYAN]      = RGB(48,151,168);
    colorTable[DARK_RED]       = RGB(185,0,5);
    colorTable[DARK_MAGENTA]   = RGB(141,2,180);
    colorTable[DARK_YELLOW]    = RGB(187,182,0);
    colorTable[DARK_WHITE]     = RGB(192,190,197);
    colorTable[BRIGHT_BLACK]   = RGB(85,82,92);
    colorTable[BRIGHT_BLUE]    = RGB(62,109,253);
    colorTable[BRIGHT_GREEN]   = RGB(11,213,0);
    colorTable[BRIGHT_CYAN]    = RGB(128,205,253);
    colorTable[BRIGHT_RED]     = RGB(237,57,96);
    colorTable[BRIGHT_MAGENTA] = RGB(198,2,253);
    colorTable[BRIGHT_YELLOW]  = RGB(255,247,149);
    colorTable[BRIGHT_WHITE]   = RGB(240,239,241);
    Helpers::SetColorTable(this->_myName, colorTable);
}

Then in your SetupDistro, call the method.

HRESULT MyLinuxDistroLauncher::SetupDistro()
{
    HRESULT hr = _SetupFirstUser();

    _SetMyDefaultColorTable();


    return hr;
}