terminal/src/cascadia/TerminalApp/CascadiaSettings.cpp
Dustin L. Howett (MSFT) 8da6737d64
Switch to v5 UUIDs as profile GUIDs for the default profiles (#913)
This commit switches the GUIDs for default profiles from being randomly generated to being version 5 UUIDs. More info in #870.

## PR Checklist
* [x] Closes #870
* [x] CLA signed
* [x] Tests added/passed
* [x] Requires documentation to be updated (#883)
* [x] I've discussed this with core contributors already.

## Detailed Description of the Pull Request / Additional comments
This commit has a number of changes that seem ancillary, but they're general goodness. Let me explain:

* I've added a whole new Types test library with only two tests in
* Since UUIDv5 generation requires SHA1, we needed to take a dependency on bcrypt
* I honestly don't think we should have to link bcrypt in conhost, but LTO should take care of that
  * I considered adding a new Terminal-specific Utils/Types library, but that seemed like a waste
* The best way to link bcrypt turned out to be in line with a discussion @miniksa and I had, where we decided we both love APISets and think that the console should link against them exclusively... so I've added `onecore_apiset.lib` to the front of the link line, where it will deflect the linker away from most of the other libs automagically.

```
StartGroup: UuidTests::TestV5UuidU8String
Verify: AreEqual(uuidExpected, uuidActual)
EndGroup: UuidTests::TestV5UuidU8String [Passed]

StartGroup: UuidTests::TestV5UuidU16String
Verify: AreEqual(uuidExpected, uuidActual)
EndGroup: UuidTests::TestV5UuidU16String [Passed]
```
2019-05-21 13:29:16 -07:00

489 lines
20 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include <argb.h>
#include <conattrs.hpp>
#include "CascadiaSettings.h"
#include "../../types/inc/utils.hpp"
#include "../../inc/DefaultSettings.h"
using namespace winrt::Microsoft::Terminal::Settings;
using namespace ::TerminalApp;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::TerminalApp;
// {2bde4a90-d05f-401c-9492-e40884ead1d8}
// uuidv5 properties: name format is UTF-16LE bytes
static constexpr GUID TERMINAL_PROFILE_NAMESPACE_GUID =
{ 0x2bde4a90, 0xd05f, 0x401c, { 0x94, 0x92, 0xe4, 0x8, 0x84, 0xea, 0xd1, 0xd8 } };
CascadiaSettings::CascadiaSettings() :
_globals{},
_profiles{}
{
}
CascadiaSettings::~CascadiaSettings()
{
}
ColorScheme _CreateCampbellScheme()
{
ColorScheme campbellScheme { L"Campbell",
RGB(242, 242, 242),
RGB(12, 12, 12) };
auto& campbellTable = campbellScheme.GetTable();
auto campbellSpan = gsl::span<COLORREF>(&campbellTable[0], gsl::narrow<ptrdiff_t>(COLOR_TABLE_SIZE));
Microsoft::Console::Utils::InitializeCampbellColorTable(campbellSpan);
Microsoft::Console::Utils::SetColorTableAlpha(campbellSpan, 0xff);
return campbellScheme;
}
ColorScheme _CreateOneHalfDarkScheme()
{
// First 8 dark colors per: https://github.com/sonph/onehalf/blob/master/putty/onehalf-dark.reg
// Dark gray is per colortool scheme, the other 7 of the last 8 colors from the colortool
// scheme are the same as their dark color equivalents.
ColorScheme oneHalfDarkScheme { L"One Half Dark",
RGB(220, 223, 228),
RGB( 40, 44, 52) };
auto& oneHalfDarkTable = oneHalfDarkScheme.GetTable();
auto oneHalfDarkSpan = gsl::span<COLORREF>(&oneHalfDarkTable[0], gsl::narrow<ptrdiff_t>(COLOR_TABLE_SIZE));
oneHalfDarkTable[0] = RGB( 40, 44, 52); // black
oneHalfDarkTable[1] = RGB(224, 108, 117); // dark red
oneHalfDarkTable[2] = RGB(152, 195, 121); // dark green
oneHalfDarkTable[3] = RGB(229, 192, 123); // dark yellow
oneHalfDarkTable[4] = RGB( 97, 175, 239); // dark blue
oneHalfDarkTable[5] = RGB(198, 120, 221); // dark magenta
oneHalfDarkTable[6] = RGB( 86, 182, 194); // dark cyan
oneHalfDarkTable[7] = RGB(220, 223, 228); // gray
oneHalfDarkTable[8] = RGB( 90, 99, 116); // dark gray
oneHalfDarkTable[9] = RGB(224, 108, 117); // red
oneHalfDarkTable[10] = RGB(152, 195, 121); // green
oneHalfDarkTable[11] = RGB(229, 192, 123); // yellow
oneHalfDarkTable[12] = RGB( 97, 175, 239); // blue
oneHalfDarkTable[13] = RGB(198, 120, 221); // magenta
oneHalfDarkTable[14] = RGB( 86, 182, 194); // cyan
oneHalfDarkTable[15] = RGB(220, 223, 228); // white
Microsoft::Console::Utils::SetColorTableAlpha(oneHalfDarkSpan, 0xff);
return oneHalfDarkScheme;
}
ColorScheme _CreateOneHalfLightScheme()
{
// First 8 dark colors per: https://github.com/sonph/onehalf/blob/master/putty/onehalf-light.reg
// Last 8 colors per colortool scheme.
ColorScheme oneHalfLightScheme { L"One Half Light",
RGB(56, 58, 66),
RGB(250, 250, 250) };
auto& oneHalfLightTable = oneHalfLightScheme.GetTable();
auto oneHalfLightSpan = gsl::span<COLORREF>(&oneHalfLightTable[0], gsl::narrow<ptrdiff_t>(COLOR_TABLE_SIZE));
oneHalfLightTable[0] = RGB( 56, 58, 66); // black
oneHalfLightTable[1] = RGB(228, 86, 73); // dark red
oneHalfLightTable[2] = RGB( 80, 161, 79); // dark green
oneHalfLightTable[3] = RGB(193, 131, 1); // dark yellow
oneHalfLightTable[4] = RGB( 1, 132, 188); // dark blue
oneHalfLightTable[5] = RGB(166, 38, 164); // dark magenta
oneHalfLightTable[6] = RGB( 9, 151, 179); // dark cyan
oneHalfLightTable[7] = RGB(250, 250, 250); // gray
oneHalfLightTable[8] = RGB( 79, 82, 93); // dark gray
oneHalfLightTable[9] = RGB(223, 108, 117); // red
oneHalfLightTable[10] = RGB(152, 195, 121); // green
oneHalfLightTable[11] = RGB(228, 192, 122); // yellow
oneHalfLightTable[12] = RGB( 97, 175, 239); // blue
oneHalfLightTable[13] = RGB(197, 119, 221); // magenta
oneHalfLightTable[14] = RGB( 86, 181, 193); // cyan
oneHalfLightTable[15] = RGB(255, 255, 255); // white
Microsoft::Console::Utils::SetColorTableAlpha(oneHalfLightSpan, 0xff);
return oneHalfLightScheme;
}
ColorScheme _CreateSolarizedDarkScheme()
{
ColorScheme solarizedDarkScheme { L"Solarized Dark",
RGB(253, 246, 227),
RGB( 7, 54, 66) };
auto& solarizedDarkTable = solarizedDarkScheme.GetTable();
auto solarizedDarkSpan = gsl::span<COLORREF>(&solarizedDarkTable[0], gsl::narrow<ptrdiff_t>(COLOR_TABLE_SIZE));
solarizedDarkTable[0] = RGB( 7, 54, 66);
solarizedDarkTable[1] = RGB(211, 1, 2);
solarizedDarkTable[2] = RGB(133, 153, 0);
solarizedDarkTable[3] = RGB(181, 137, 0);
solarizedDarkTable[4] = RGB( 38, 139, 210);
solarizedDarkTable[5] = RGB(211, 54, 130);
solarizedDarkTable[6] = RGB( 42, 161, 152);
solarizedDarkTable[7] = RGB(238, 232, 213);
solarizedDarkTable[8] = RGB( 0, 43, 54);
solarizedDarkTable[9] = RGB(203, 75, 22);
solarizedDarkTable[10] = RGB( 88, 110, 117);
solarizedDarkTable[11] = RGB(101, 123, 131);
solarizedDarkTable[12] = RGB(131, 148, 150);
solarizedDarkTable[13] = RGB(108, 113, 196);
solarizedDarkTable[14] = RGB(147, 161, 161);
solarizedDarkTable[15] = RGB(253, 246, 227);
Microsoft::Console::Utils::SetColorTableAlpha(solarizedDarkSpan, 0xff);
return solarizedDarkScheme;
}
ColorScheme _CreateSolarizedLightScheme()
{
ColorScheme solarizedLightScheme { L"Solarized Light",
RGB( 7, 54, 66),
RGB(253, 246, 227) };
auto& solarizedLightTable = solarizedLightScheme.GetTable();
auto solarizedLightSpan = gsl::span<COLORREF>(&solarizedLightTable[0], gsl::narrow<ptrdiff_t>(COLOR_TABLE_SIZE));
solarizedLightTable[0] = RGB( 7, 54, 66);
solarizedLightTable[1] = RGB(211, 1, 2);
solarizedLightTable[2] = RGB(133, 153, 0);
solarizedLightTable[3] = RGB(181, 137, 0);
solarizedLightTable[4] = RGB( 38, 139, 210);
solarizedLightTable[5] = RGB(211, 54, 130);
solarizedLightTable[6] = RGB( 42, 161, 152);
solarizedLightTable[7] = RGB(238, 232, 213);
solarizedLightTable[8] = RGB( 0, 43, 54);
solarizedLightTable[9] = RGB(203, 75, 22);
solarizedLightTable[10] = RGB( 88, 110, 117);
solarizedLightTable[11] = RGB(101, 123, 131);
solarizedLightTable[12] = RGB(131, 148, 150);
solarizedLightTable[13] = RGB(108, 113, 196);
solarizedLightTable[14] = RGB(147, 161, 161);
solarizedLightTable[15] = RGB(253, 246, 227);
Microsoft::Console::Utils::SetColorTableAlpha(solarizedLightSpan, 0xff);
return solarizedLightScheme;
}
// Method Description:
// - Create the set of schemes to use as the default schemes. Currently creates
// five default color schemes - Campbell (the new cmd color scheme),
// One Half Dark, One Half Light, Solarized Dark, and Solarized Light.
// Arguments:
// - <none>
// Return Value:
// - <none>
void CascadiaSettings::_CreateDefaultSchemes()
{
_globals.GetColorSchemes().emplace_back(_CreateCampbellScheme());
_globals.GetColorSchemes().emplace_back(_CreateOneHalfDarkScheme());
_globals.GetColorSchemes().emplace_back(_CreateOneHalfLightScheme());
_globals.GetColorSchemes().emplace_back(_CreateSolarizedDarkScheme());
_globals.GetColorSchemes().emplace_back(_CreateSolarizedLightScheme());
}
// Method Description:
// - Create a set of profiles to use as the "default" profiles when initializing
// the terminal. Currently, we create two profiles: one for cmd.exe, and
// one for powershell.
// Arguments:
// - <none>
// Return Value:
// - <none>
void CascadiaSettings::_CreateDefaultProfiles()
{
auto cmdProfile{ _CreateDefaultProfile(L"cmd") };
cmdProfile.SetFontFace(L"Consolas");
cmdProfile.SetCommandline(L"cmd.exe");
cmdProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
cmdProfile.SetColorScheme({ L"Campbell" });
cmdProfile.SetAcrylicOpacity(0.75);
cmdProfile.SetUseAcrylic(true);
auto powershellProfile{ _CreateDefaultProfile(L"PowerShell") };
// If the user has installed PowerShell Core, we add PowerShell Core as a default.
// PowerShell Core default folder is "%PROGRAMFILES%\PowerShell\[Version]\".
std::wstring psCmdline = L"powershell.exe";
std::filesystem::path psCoreCmdline{};
if (_IsPowerShellCoreInstalled(L"%ProgramFiles%", psCoreCmdline))
{
psCmdline = psCoreCmdline;
}
else if (_IsPowerShellCoreInstalled(L"%ProgramFiles(x86)%", psCoreCmdline))
{
psCmdline = psCoreCmdline;
}
powershellProfile.SetFontFace(L"Courier New");
powershellProfile.SetCommandline(psCmdline);
powershellProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
powershellProfile.SetColorScheme({ L"Campbell" });
powershellProfile.SetDefaultBackground(RGB(1, 36, 86));
powershellProfile.SetUseAcrylic(false);
_profiles.emplace_back(powershellProfile);
_profiles.emplace_back(cmdProfile);
_globals.SetDefaultProfile(powershellProfile.GetGuid());
}
// Method Description:
// - Set up some default keybindings for the terminal.
// Arguments:
// - <none>
// Return Value:
// - <none>
void CascadiaSettings::_CreateDefaultKeybindings()
{
AppKeyBindings keyBindings = _globals.GetKeybindings();
// Set up spme basic default keybindings
// TODO:MSFT:20700157 read our settings from some source, and configure
// keychord,action pairings from that file
keyBindings.SetKeyBinding(ShortcutAction::NewTab,
KeyChord{ KeyModifiers::Ctrl,
static_cast<int>('T') });
keyBindings.SetKeyBinding(ShortcutAction::CloseTab,
KeyChord{ KeyModifiers::Ctrl,
static_cast<int>('W') });
keyBindings.SetKeyBinding(ShortcutAction::OpenSettings,
KeyChord{ KeyModifiers::Ctrl,
VK_OEM_COMMA });
keyBindings.SetKeyBinding(ShortcutAction::NextTab,
KeyChord{ KeyModifiers::Ctrl,
VK_TAB });
keyBindings.SetKeyBinding(ShortcutAction::PrevTab,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
VK_TAB });
// Yes these are offset by one.
// Ideally, you'd want C-S-1 to open the _first_ profile, which is index 0
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile0,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('1') });
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile1,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('2') });
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile2,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('3') });
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile3,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('4') });
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile4,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('5') });
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile5,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('6') });
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile6,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('7') });
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile7,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('8') });
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile8,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('9') });
keyBindings.SetKeyBinding(ShortcutAction::ScrollUp,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
VK_UP });
keyBindings.SetKeyBinding(ShortcutAction::ScrollDown,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
VK_DOWN });
keyBindings.SetKeyBinding(ShortcutAction::ScrollDownPage,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
VK_NEXT });
keyBindings.SetKeyBinding(ShortcutAction::ScrollUpPage,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
VK_PRIOR });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab0,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('1') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab1,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('2') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab2,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('3') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab3,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('4') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab4,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('5') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab5,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('6') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab6,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('7') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab7,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('8') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab8,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('9') });
}
// Method Description:
// - Initialize this object with default color schemes, profiles, and keybindings.
// Arguments:
// - <none>
// Return Value:
// - <none>
void CascadiaSettings::_CreateDefaults()
{
_CreateDefaultProfiles();
_CreateDefaultSchemes();
_CreateDefaultKeybindings();
}
// Method Description:
// - Finds a profile that matches the given GUID. If there is no profile in this
// settings object that matches, returns nullptr.
// Arguments:
// - profileGuid: the GUID of the profile to return.
// Return Value:
// - a non-ownership pointer to the profile matching the given guid, or nullptr
// if there is no match.
const Profile* CascadiaSettings::FindProfile(GUID profileGuid) const noexcept
{
for (auto& profile : _profiles)
{
if (profile.GetGuid() == profileGuid)
{
return &profile;
}
}
return nullptr;
}
// Method Description:
// - Create a TerminalSettings object from the given profile.
// If the profileGuidArg is not provided, this method will use the default
// profile.
// The TerminalSettings object that is created can be used to initialize both
// the Control's settings, and the Core settings of the terminal.
// Arguments:
// - profileGuidArg: an optional GUID to use to lookup the profile to create the
// settings from. If this arg is not provided, or the GUID does not match a
// profile, then this method will use the default profile.
// Return Value:
// - <none>
TerminalSettings CascadiaSettings::MakeSettings(std::optional<GUID> profileGuidArg) const
{
GUID profileGuid = profileGuidArg ? profileGuidArg.value() : _globals.GetDefaultProfile();
const Profile* const profile = FindProfile(profileGuid);
if (profile == nullptr)
{
throw E_INVALIDARG;
}
TerminalSettings result = profile->CreateTerminalSettings(_globals.GetColorSchemes());
// Place our appropriate global settings into the Terminal Settings
_globals.ApplyToSettings(result);
return result;
}
// Method Description:
// - Returns an iterable collection of all of our Profiles.
// Arguments:
// - <none>
// Return Value:
// - an iterable collection of all of our Profiles.
std::basic_string_view<Profile> CascadiaSettings::GetProfiles() const noexcept
{
return { &_profiles[0], _profiles.size() };
}
// Method Description:
// - Returns the globally configured keybindings
// Arguments:
// - <none>
// Return Value:
// - the globally configured keybindings
AppKeyBindings CascadiaSettings::GetKeybindings() const noexcept
{
return _globals.GetKeybindings();
}
// Method Description:
// - Get a reference to our global settings
// Arguments:
// - <none>
// Return Value:
// - a reference to our global settings
GlobalAppSettings& CascadiaSettings::GlobalSettings()
{
return _globals;
}
// Function Description:
// - Returns true if the user has installed PowerShell Core.
// Arguments:
// - A string that contains an environment-variable string in the form: %variableName%.
// - A ref of a path that receives the result of PowerShell Core pwsh.exe full path.
// Return Value:
// - true or false.
bool CascadiaSettings::_IsPowerShellCoreInstalled(std::wstring_view programFileEnv, std::filesystem::path& cmdline)
{
std::filesystem::path psCorePath = ExpandEnvironmentVariableString(programFileEnv.data());
psCorePath /= L"PowerShell";
if (std::filesystem::exists(psCorePath))
{
for (auto& p : std::filesystem::directory_iterator(psCorePath))
{
psCorePath = p.path();
psCorePath /= L"pwsh.exe";
if (std::filesystem::exists(psCorePath))
{
cmdline = psCorePath;
return true;
}
}
}
return false;
}
// Function Description:
// - Get a environment variable string.
// Arguments:
// - A string that contains an environment-variable string in the form: %variableName%.
// Return Value:
// - a string of the expending environment-variable string.
std::wstring CascadiaSettings::ExpandEnvironmentVariableString(std::wstring_view source)
{
std::wstring result{};
DWORD requiredSize = 0;
do
{
result.resize(requiredSize);
requiredSize = ::ExpandEnvironmentStringsW(source.data(), result.data(), gsl::narrow<DWORD>(result.size()));
} while (requiredSize != result.size());
// Trim the terminating null character
result.resize(requiredSize-1);
return result;
}
// Method Description:
// - Helper function for creating a skeleton default profile with a pre-populated
// guid and name.
// Arguments:
// - name: the name of the new profile.
// Return Value:
// - A Profile, ready to be filled in
Profile CascadiaSettings::_CreateDefaultProfile(const std::wstring_view& name)
{
Profile newProfile{
Microsoft::Console::Utils::CreateV5Uuid(
TERMINAL_PROFILE_NAMESPACE_GUID,
gsl::as_bytes(gsl::make_span(name))
)
};
newProfile.SetName(static_cast<std::wstring>(name));
return newProfile;
}