terminal/src/propslib/RegistrySerialization.cpp
James Holderness bb71179a24
Consolidate the color palette APIs (#11784)
This PR merges the default colors and cursor color into the main color
table, enabling us to simplify the `ConGetSet` and `ITerminalApi`
interfaces, with just two methods required for getting and setting any
form of color palette entry.

The is a follow-up to the color table standardization in #11602, and a
another small step towards de-duplicating `AdaptDispatch` and
`TerminalDispatch` for issue #3849. It should also make it easier to
support color queries (#3718) and a configurable bold color (#5682) in
the future.

On the conhost side, default colors could originally be either indexed
positions in the 16-color table, or separate standalone RGB values. With
the new system, the default colors will always be in the color table, so
we just need to track their index positions.

To make this work, those positions need to be calculated at startup
based on the loaded registry/shortcut settings, and updated when
settings are changed (this is handled in
`CalculateDefaultColorIndices`). But the plus side is that it's now much
easier to lookup the default color values for rendering.

For now the default colors in Windows Terminal use hardcoded positions,
because it doesn't need indexed default colors like conhost. But in the
future I'd like to extend the index handling to both terminals, so we
can eventually support the VT525 indexed color operations.

As for the cursor color, that was previously stored in the `Cursor`
class, which meant that it needed to be copied around in various places
where cursors were being instantiated. Now that it's managed separately
in the color table, a lot of that code is no longer required.

## Validation
Some of the unit test initialization code needed to be updated to setup
the color table and default index values as required for the new system.
There were also some adjustments needed to account for API changes, in
particular for methods that now take index values for the default colors
in place of COLORREFs. But for the most part, the essential behavior of
the tests remains unchanged.

I've also run a variety of manual tests looking at the legacy console
APIs as well as the various VT color sequences, and checking that
everything works as expected when color schemes are changed, both in
Windows Terminal and conhost, and in the latter case with both indexed
colors and RGB values.

Closes #11768
2021-11-23 18:28:55 +00:00

460 lines
21 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "RegistrySerialization.hpp"
#pragma hdrstop
#define SET_FIELD_AND_SIZE(x) UFIELD_OFFSET(Settings, x), RTL_FIELD_SIZE(Settings, x)
#define NT_TESTNULL(var) (((var) == nullptr) ? STATUS_NO_MEMORY : STATUS_SUCCESS)
DWORD RegistrySerialization::ToWin32RegistryType(const _RegPropertyType type)
{
switch (type)
{
case _RegPropertyType::Boolean:
case _RegPropertyType::Dword:
case _RegPropertyType::Word:
case _RegPropertyType::Byte:
case _RegPropertyType::Coordinate:
return REG_DWORD;
case _RegPropertyType::String:
return REG_SZ;
default:
return REG_NONE;
}
}
// clang-format off
// Registry settings to load (not all of them, some are special)
const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMappings[] =
{
//+---------------------------------+-----------------------------------------------+-----------------------------------------------+
//| Property type | Property Name | Corresponding Settings field |
{ _RegPropertyType::Word, CONSOLE_REGISTRY_POPUPATTR, SET_FIELD_AND_SIZE(_wPopupFillAttribute), },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_INSERTMODE, SET_FIELD_AND_SIZE(_bInsertMode) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_LINESELECTION, SET_FIELD_AND_SIZE(_bLineSelection) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_FILTERONPASTE, SET_FIELD_AND_SIZE(_fFilterOnPaste) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_LINEWRAP, SET_FIELD_AND_SIZE(_bWrapText) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_CTRLKEYSHORTCUTS_DISABLED, SET_FIELD_AND_SIZE(_fCtrlKeyShortcutsDisabled) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_QUICKEDIT, SET_FIELD_AND_SIZE(_bQuickEdit) },
{ _RegPropertyType::Byte, CONSOLE_REGISTRY_WINDOWALPHA, SET_FIELD_AND_SIZE(_bWindowAlpha) },
{ _RegPropertyType::Coordinate, CONSOLE_REGISTRY_FONTSIZE, SET_FIELD_AND_SIZE(_dwFontSize) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_FONTFAMILY, SET_FIELD_AND_SIZE(_uFontFamily) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_FONTWEIGHT, SET_FIELD_AND_SIZE(_uFontWeight) },
{ _RegPropertyType::String, CONSOLE_REGISTRY_FACENAME, SET_FIELD_AND_SIZE(_FaceName) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_CURSORSIZE, SET_FIELD_AND_SIZE(_uCursorSize) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_HISTORYSIZE, SET_FIELD_AND_SIZE(_uHistoryBufferSize) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_HISTORYBUFS, SET_FIELD_AND_SIZE(_uNumberOfHistoryBuffers) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_HISTORYNODUP, SET_FIELD_AND_SIZE(_bHistoryNoDup) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_SCROLLSCALE, SET_FIELD_AND_SIZE(_uScrollScale) },
{ _RegPropertyType::Word, CONSOLE_REGISTRY_FILLATTR, SET_FIELD_AND_SIZE(_wFillAttribute) },
{ _RegPropertyType::Coordinate, CONSOLE_REGISTRY_BUFFERSIZE, SET_FIELD_AND_SIZE(_dwScreenBufferSize) },
{ _RegPropertyType::Coordinate, CONSOLE_REGISTRY_WINDOWSIZE, SET_FIELD_AND_SIZE(_dwWindowSize) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_TRIMZEROHEADINGS, SET_FIELD_AND_SIZE(_fTrimLeadingZeros) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_ENABLE_COLOR_SELECTION, SET_FIELD_AND_SIZE(_fEnableColorSelection) },
{ _RegPropertyType::Coordinate, CONSOLE_REGISTRY_WINDOWPOS, SET_FIELD_AND_SIZE(_dwWindowOrigin) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_CURSORTYPE, SET_FIELD_AND_SIZE(_CursorType) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_INTERCEPTCOPYPASTE, SET_FIELD_AND_SIZE(_fInterceptCopyPaste) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_TERMINALSCROLLING, SET_FIELD_AND_SIZE(_TerminalScrolling) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_DEFAULTFOREGROUND, SET_FIELD_AND_SIZE(_colorTable[TextColor::DEFAULT_FOREGROUND]) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_DEFAULTBACKGROUND, SET_FIELD_AND_SIZE(_colorTable[TextColor::DEFAULT_BACKGROUND]) },
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_CURSORCOLOR, SET_FIELD_AND_SIZE(_colorTable[TextColor::CURSOR_COLOR]) }
};
const size_t RegistrySerialization::s_PropertyMappingsSize = ARRAYSIZE(s_PropertyMappings);
// Global registry settings to load
const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_GlobalPropMappings[] =
{
//+---------------------------------+-----------------------------------------------+-----------------------------------------------+
//| Property type | Property Name | Corresponding Settings field |
{ _RegPropertyType::Dword, CONSOLE_REGISTRY_VIRTTERM_LEVEL, SET_FIELD_AND_SIZE(_dwVirtTermLevel), }
};
const size_t RegistrySerialization::s_GlobalPropMappingsSize = ARRAYSIZE(s_GlobalPropMappings);
// clang-format off
// Routine Description:
// - Reads number from the registry and applies it to the given property if the value exists
// Supports: Dword, Word, Byte, Boolean, and Coordinate
// Arguments:
// - hKey - Registry key to read from
// - pPropMap - Contains property information to use in looking up/storing value data
// Return Value:
// - STATUS_SUCCESSFUL or appropriate NTSTATUS reply for registry operations.
[[nodiscard]]
NTSTATUS RegistrySerialization::s_LoadRegDword(const HKEY hKey, const _RegPropertyMap* const pPropMap, _In_ Settings* const pSettings)
{
// find offset into destination structure for this numerical value
PBYTE const pbField = (PBYTE)pSettings + pPropMap->dwFieldOffset;
// attempt to load number into this field
// If we're not successful, it's ok. Just don't fill it.
DWORD dwValue;
NTSTATUS Status = s_QueryValue(hKey,
pPropMap->pwszValueName,
sizeof(dwValue),
ToWin32RegistryType(pPropMap->propertyType),
(PBYTE)& dwValue,
nullptr);
if (NT_SUCCESS(Status))
{
switch (pPropMap->propertyType)
{
case _RegPropertyType::Dword:
{
DWORD* const pdField = (DWORD*)pbField;
*pdField = dwValue;
break;
}
case _RegPropertyType::Word:
{
WORD* const pwField = (WORD*)pbField;
*pwField = (WORD)dwValue;
break;
}
case _RegPropertyType::Boolean:
{
*pbField = !!dwValue;
break;
}
case _RegPropertyType::Byte:
{
*pbField = LOBYTE(dwValue);
break;
}
case _RegPropertyType::Coordinate:
{
PCOORD pcoordField = (PCOORD)pbField;
pcoordField->X = LOWORD(dwValue);
pcoordField->Y = HIWORD(dwValue);
break;
}
}
}
return Status;
}
// Routine Description:
// - Reads string from the registry and applies it to the given property if the value exists
// Arguments:
// - hKey - Registry key to read from
// - pPropMap - Contains property information to use in looking up/storing value data
// Return Value:
// - STATUS_SUCCESSFUL or appropriate NTSTATUS reply for registry operations.
[[nodiscard]]
NTSTATUS RegistrySerialization::s_LoadRegString(const HKEY hKey, const _RegPropertyMap* const pPropMap, _In_ Settings* const pSettings)
{
// find offset into destination structure for this numerical value
PBYTE const pbField = (PBYTE)pSettings + pPropMap->dwFieldOffset;
// number of characters within the field
size_t const cchField = pPropMap->cbFieldSize / sizeof(WCHAR);
PWCHAR pwchString = new(std::nothrow) WCHAR[cchField];
NTSTATUS Status = NT_TESTNULL(pwchString);
if (NT_SUCCESS(Status))
{
Status = s_QueryValue(hKey,
pPropMap->pwszValueName,
(DWORD)(cchField) * sizeof(WCHAR),
ToWin32RegistryType(pPropMap->propertyType),
(PBYTE)pwchString,
nullptr);
if (NT_SUCCESS(Status))
{
// ensure pwchString is null terminated
pwchString[cchField - 1] = UNICODE_NULL;
Status = StringCchCopyW((PWSTR)pbField, cchField, pwchString);
}
delete[] pwchString;
}
return Status;
}
#pragma region Helpers
// Routine Description:
// - Opens the root console key from HKCU
// Arguments:
// - phCurrentUserKey - Returns a handle to the HKCU root
// - phConsoleKey - Returns a handle to the Console subkey
// Return Value:
// - STATUS_SUCCESSFUL or appropriate NTSTATUS reply for registry operations.
[[nodiscard]]
NTSTATUS RegistrySerialization::s_OpenConsoleKey(_Out_ HKEY* phCurrentUserKey, _Out_ HKEY* phConsoleKey)
{
// Always set an output value. It will be made valid before the end if everything succeeds.
*phCurrentUserKey = static_cast<HKEY>(INVALID_HANDLE_VALUE);
*phConsoleKey = static_cast<HKEY>(INVALID_HANDLE_VALUE);
wil::unique_hkey currentUserKey;
wil::unique_hkey consoleKey;
// Open the current user registry key.
NTSTATUS Status = NTSTATUS_FROM_WIN32(RegOpenCurrentUser(KEY_READ | KEY_WRITE, &currentUserKey));
if (NT_SUCCESS(Status))
{
// Open the console registry key.
Status = s_OpenKey(currentUserKey.get(), CONSOLE_REGISTRY_STRING, &consoleKey);
// If we can't open the console registry key, create one and open it.
if (NTSTATUS_FROM_WIN32(ERROR_FILE_NOT_FOUND) == Status)
{
Status = s_CreateKey(currentUserKey.get(), CONSOLE_REGISTRY_STRING, &consoleKey);
}
// If we're successful, give the keys back.
if (NT_SUCCESS(Status))
{
*phCurrentUserKey = currentUserKey.release();
*phConsoleKey = consoleKey.release();
}
}
return Status;
}
// Routine Description:
// - Opens a subkey of the given key. Fails if it doesn't exist.
// - NOTE: To create if it doesn't exist and open otherwise, try `s_CreateKey`.
// Arguments:
// - hKey - Handle to a registry key
// - pwszSubKey - String name of sub key
// - phResult - Filled with handle to the sub key if available. Check return status.
// Return Value:
// - STATUS_SUCCESSFUL or appropriate NTSTATUS reply for registry operations.
[[nodiscard]]
NTSTATUS RegistrySerialization::s_OpenKey(_In_opt_ HKEY const hKey, _In_ PCWSTR const pwszSubKey, _Out_ HKEY* const phResult)
{
return NTSTATUS_FROM_WIN32(RegOpenKeyW(hKey, pwszSubKey, phResult));
}
// Routine Description:
// - Deletes the value under a given key
// Arguments:
// - hKey - Handle to a registry key
// - pwszValueName - String name of value to delete under that key
// Return Value:
// - STATUS_SUCCESSFUL or appropriate NTSTATUS reply for registry operations.
[[nodiscard]]
NTSTATUS RegistrySerialization::s_DeleteValue(const HKEY hKey, _In_ PCWSTR const pwszValueName)
{
const auto result = RegDeleteKeyValueW(hKey, nullptr, pwszValueName);
return result == ERROR_FILE_NOT_FOUND ? S_OK : NTSTATUS_FROM_WIN32(result);
}
// Routine Description:
// - Creates a subkey of the given key
// This function creates keys with read/write access.
// - If key already exists, opens existing.
// Arguments:
// - hKey - Handle to a registry key
// - pwszSubKey - String name of sub key
// - phResult - Filled with handle to the sub key if created/opened successfully. Check return status.
// Return Value:
// - STATUS_SUCCESSFUL or appropriate NTSTATUS reply for registry operations.
[[nodiscard]]
NTSTATUS RegistrySerialization::s_CreateKey(const HKEY hKey, _In_ PCWSTR const pwszSubKey, _Out_ HKEY* const phResult)
{
return NTSTATUS_FROM_WIN32(RegCreateKeyW(hKey, pwszSubKey, phResult));
}
// Routine Description:
// - Sets a value for the given key
// Arguments:
// - hKey - Handle to a registry key
// - pwszValueName - Name of the value to set
// - dwType - Type of the value being set (see: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724884(v=vs.85).aspx)
// - pbData - Pointer to byte stream of data to set into this value
// - cbDataLength - The length in bytes of the data provided
// Return Value:
// - STATUS_SUCCESSFUL or appropriate NTSTATUS reply for registry operations.
[[nodiscard]]
NTSTATUS RegistrySerialization::s_SetValue(const HKEY hKey,
_In_ PCWSTR const pValueName,
const DWORD dwType,
_In_reads_bytes_(cbDataLength) BYTE* const pbData,
const DWORD cbDataLength)
{
return NTSTATUS_FROM_WIN32(RegSetKeyValueW(hKey,
nullptr,
pValueName,
dwType,
pbData,
cbDataLength));
}
// Routine Description:
// - Queries a value for the given key
// Arguments:
// - hKey - Handle to a registry key
// - pwszValueName - Name of the value to query
// - cbValueLength - Length of the provided data buffer.
// - regType - the type of the registry key.
// - pbData - Pointer to byte stream of data to fill with the registry value data.
// - pcbDataLength - Number of bytes filled in the given data buffer
// Return Value:
// - STATUS_SUCCESSFUL or appropriate NTSTATUS reply for registry operations.
[[nodiscard]]
NTSTATUS RegistrySerialization::s_QueryValue(const HKEY hKey,
_In_ PCWSTR const pwszValueName,
const DWORD cbValueLength,
const DWORD regType,
_Out_writes_bytes_(cbValueLength) BYTE* const pbData,
_Out_opt_ _Out_range_(0, cbValueLength) DWORD* const pcbDataLength)
{
DWORD cbData = cbValueLength;
DWORD actualRegType = 0;
LONG const Result = RegQueryValueExW(hKey,
pwszValueName,
nullptr,
&actualRegType,
pbData,
&cbData);
if (ERROR_FILE_NOT_FOUND != Result &&
actualRegType != regType)
{
return STATUS_OBJECT_TYPE_MISMATCH;
}
if (nullptr != pcbDataLength)
{
*pcbDataLength = cbData;
}
return NTSTATUS_FROM_WIN32(Result);
}
// Routine Description:
// - Enumerates the values for the given key
// Arguments:
// - hKey - Handle to a registry key
// - dwIndex - Index of value within this key to return
// - cbValueLength - Length of the provided value name buffer.
// - pwszValueName - Value name buffer to be filled with null terminated string name of value.
// - cbDataLength - Length of the provided value data buffer.
// - pbData - Value data buffer to be filled based on data type. Will be null terminated for string types. (REG_SZ, REG_MULTI_SZ, REG_EXPAND_SZ)
// Return Value:
// - STATUS_SUCCESSFUL or appropriate NTSTATUS reply for registry operations.
[[nodiscard]]
NTSTATUS RegistrySerialization::s_EnumValue(const HKEY hKey,
const DWORD dwIndex,
const DWORD cbValueLength,
_Out_writes_bytes_(cbValueLength) PWSTR const pwszValueName,
const DWORD cbDataLength,
_Out_writes_bytes_(cbDataLength) BYTE* const pbData)
{
DWORD cchValueName = cbValueLength / sizeof(WCHAR);
DWORD cbData = cbDataLength;
#pragma prefast(suppress: 26015, "prefast doesn't realize that cbData == cbDataLength and cchValueName == cbValueLength/2")
return NTSTATUS_FROM_WIN32(RegEnumValueW(hKey,
dwIndex,
pwszValueName,
&cchValueName,
nullptr,
nullptr,
pbData,
&cbData));
}
// Routine Description:
// - Updates the value in a given key
// - NOTE: For the console registry, if we're filling a console subkey and the default matches, the subkey copy will be deleted.
// We only store settings in subkeys if they differ from the defaults.
// Arguments:
// - hConsoleKey - Handle to the default console key
// - hKey - Handle to the console subkey for this particular console
// - pwszValueName - Name of the value within the default and subkeys to check/update.
// - dwType - Type of the value being set (see: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724884(v=vs.85).aspx)
// - pbData - Value data buffer to be stored into the registry.
// - cbDataLength - Length of the provided value data buffer.
// Return Value:
// - STATUS_SUCCESSFUL or appropriate NTSTATUS reply for registry operations.
[[nodiscard]]
NTSTATUS RegistrySerialization::s_UpdateValue(const HKEY hConsoleKey,
const HKEY hKey,
_In_ PCWSTR const pwszValueName,
const DWORD dwType,
_In_reads_bytes_(cbDataLength) BYTE* pbData,
const DWORD cbDataLength)
{
NTSTATUS Status = STATUS_UNSUCCESSFUL; // This value won't be used, added to avoid compiler warnings.
BYTE* Data = new(std::nothrow) BYTE[cbDataLength];
if (Data != nullptr)
{
// If this is not the main console key but the value is the same,
// delete it. Otherwise, set it.
bool fDeleteKey = false;
if (hConsoleKey != hKey)
{
Status = s_QueryValue(hConsoleKey, pwszValueName, cbDataLength, dwType, Data, nullptr);
if (NT_SUCCESS(Status))
{
fDeleteKey = (memcmp(pbData, Data, cbDataLength) == 0);
}
}
if (fDeleteKey)
{
Status = s_DeleteValue(hKey, pwszValueName);
}
else
{
Status = s_SetValue(hKey, pwszValueName, dwType, pbData, cbDataLength);
}
delete[] Data;
}
return Status;
}
[[nodiscard]]
NTSTATUS RegistrySerialization::s_OpenCurrentUserConsoleTitleKey(_In_ PCWSTR const title,
_Out_ HKEY* phCurrentUserKey,
_Out_ HKEY* phConsoleKey,
_Out_ HKEY* phTitleKey)
{
NTSTATUS Status = NTSTATUS_FROM_WIN32(RegOpenKeyW(HKEY_CURRENT_USER,
nullptr,
phCurrentUserKey));
if (NT_SUCCESS(Status))
{
Status = RegistrySerialization::s_CreateKey(*phCurrentUserKey,
CONSOLE_REGISTRY_STRING,
phConsoleKey);
if (NT_SUCCESS(Status))
{
Status = RegistrySerialization::s_CreateKey(*phConsoleKey, title, phTitleKey);
if (!NT_SUCCESS(Status))
{
RegCloseKey(*phConsoleKey);
RegCloseKey(*phCurrentUserKey);
}
//else all keys were created/opened successfully, and we'll return success
}
else
{
RegCloseKey(*phCurrentUserKey);
}
}
return Status;
}
#pragma endregion