mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-24 15:12:02 +00:00
I legitimately cannot figure out how I forgot this. Bell should support all the same validation as other media resources! Technically this means you can set `bellSound` to `desktopWallpaper`, but... we'll pretend that makes sense. I reworked the viewmodel to be a little more sensible. It no longer requires somebody else to check that its files exist. The settings UI now also displays `File not found` in the _preview_ for the bell if it is a single file which failed validation!
589 lines
26 KiB
C++
589 lines
26 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "Profile.h"
|
|
#include "JsonUtils.h"
|
|
#include "../../types/inc/Utils.hpp"
|
|
|
|
#include "TerminalSettingsSerializationHelpers.h"
|
|
#include "AppearanceConfig.h"
|
|
#include "FontConfig.h"
|
|
|
|
#include "Profile.g.cpp"
|
|
|
|
#include <shellapi.h>
|
|
|
|
using namespace Microsoft::Terminal::Settings::Model;
|
|
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
|
using namespace winrt::Microsoft::Terminal::Control;
|
|
using namespace winrt::Windows::UI;
|
|
using namespace winrt::Windows::UI::Xaml;
|
|
using namespace winrt::Windows::Foundation;
|
|
using namespace ::Microsoft::Console;
|
|
|
|
static constexpr std::string_view UpdatesKey{ "updates" };
|
|
static constexpr std::string_view NameKey{ "name" };
|
|
static constexpr std::string_view GuidKey{ "guid" };
|
|
static constexpr std::string_view SourceKey{ "source" };
|
|
static constexpr std::string_view HiddenKey{ "hidden" };
|
|
|
|
static constexpr std::string_view FontInfoKey{ "font" };
|
|
static constexpr std::string_view PaddingKey{ "padding" };
|
|
static constexpr std::string_view TabColorKey{ "tabColor" };
|
|
static constexpr std::string_view UnfocusedAppearanceKey{ "unfocusedAppearance" };
|
|
|
|
static constexpr std::string_view LegacyAutoMarkPromptsKey{ "experimental.autoMarkPrompts" };
|
|
static constexpr std::string_view LegacyShowMarksKey{ "experimental.showMarksOnScrollbar" };
|
|
static constexpr std::string_view LegacyRightClickContextMenuKey{ "experimental.rightClickContextMenu" };
|
|
|
|
Profile::Profile(guid guid) noexcept :
|
|
_Guid(guid)
|
|
{
|
|
}
|
|
|
|
void Profile::CreateUnfocusedAppearance()
|
|
{
|
|
if (!_UnfocusedAppearance)
|
|
{
|
|
auto unfocusedAppearance{ winrt::make_self<implementation::AppearanceConfig>(weak_ref<Model::Profile>(*this)) };
|
|
|
|
// If an unfocused appearance is defined in this profile, any undefined parameters are
|
|
// taken from this profile's default appearance, so add it as a parent
|
|
com_ptr<AppearanceConfig> parentCom;
|
|
parentCom.copy_from(winrt::get_self<implementation::AppearanceConfig>(_DefaultAppearance));
|
|
unfocusedAppearance->AddLeastImportantParent(parentCom);
|
|
|
|
_UnfocusedAppearance = *unfocusedAppearance;
|
|
}
|
|
}
|
|
|
|
void Profile::DeleteUnfocusedAppearance()
|
|
{
|
|
_UnfocusedAppearance = std::nullopt;
|
|
}
|
|
|
|
// See CopyInheritanceGraph (singular) for more information.
|
|
// This does the same, but runs it on a list of graph nodes and clones each sub-graph.
|
|
void Profile::CopyInheritanceGraphs(std::unordered_map<const Profile*, winrt::com_ptr<Profile>>& visited, const std::vector<winrt::com_ptr<Profile>>& source, std::vector<winrt::com_ptr<Profile>>& target)
|
|
{
|
|
for (const auto& sourceProfile : source)
|
|
{
|
|
target.emplace_back(sourceProfile->CopyInheritanceGraph(visited));
|
|
}
|
|
}
|
|
|
|
// A profile and its IInheritable parents basically behave like a directed acyclic graph (DAG).
|
|
// Cloning a DAG requires us to prevent the duplication of already cloned nodes (or profiles).
|
|
// This is where "visited" comes into play: It contains previously cloned sub-graphs of profiles and "interns" them.
|
|
winrt::com_ptr<Profile>& Profile::CopyInheritanceGraph(std::unordered_map<const Profile*, winrt::com_ptr<Profile>>& visited) const
|
|
{
|
|
// The operator[] is usually considered to suck, because it implicitly creates entries
|
|
// in maps/sets if the entry doesn't exist yet, which is often an unwanted behavior.
|
|
// But in this case it's just perfect. We want to return a reference to the profile if it's
|
|
// been created before and create a cloned profile if it doesn't. With the operator[]
|
|
// we can just assign the returned reference allowing us to write some lean code.
|
|
auto& clone = visited[this];
|
|
|
|
if (!clone)
|
|
{
|
|
clone = CopySettings();
|
|
CopyInheritanceGraphs(visited, _parents, clone->_parents);
|
|
clone->_FinalizeInheritance();
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
winrt::com_ptr<Profile> Profile::CopySettings() const
|
|
{
|
|
const auto profile = winrt::make_self<Profile>();
|
|
const auto weakProfile = winrt::make_weak<Model::Profile>(*profile);
|
|
const auto fontInfo = FontConfig::CopyFontInfo(winrt::get_self<FontConfig>(_FontInfo), weakProfile);
|
|
const auto defaultAppearance = AppearanceConfig::CopyAppearance(winrt::get_self<AppearanceConfig>(_DefaultAppearance), weakProfile);
|
|
|
|
profile->_Deleted = _Deleted;
|
|
profile->_Orphaned = _Orphaned;
|
|
profile->_Updates = _Updates;
|
|
profile->_Guid = _Guid;
|
|
profile->_Name = _Name;
|
|
profile->_Source = _Source;
|
|
profile->_Hidden = _Hidden;
|
|
profile->_TabColor = _TabColor;
|
|
profile->_Padding = _Padding;
|
|
|
|
profile->_Origin = _Origin;
|
|
profile->_FontInfo = *fontInfo;
|
|
profile->_DefaultAppearance = *defaultAppearance;
|
|
|
|
#define PROFILE_SETTINGS_COPY(type, name, jsonKey, ...) \
|
|
profile->_##name = _##name;
|
|
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_COPY)
|
|
#undef PROFILE_SETTINGS_COPY
|
|
|
|
if (_BellSound)
|
|
{
|
|
// BellSound is an IVector<>, so we need to make a new vector pointing at the same objects
|
|
profile->_BellSound = winrt::single_threaded_vector(wil::to_vector(*_BellSound));
|
|
}
|
|
|
|
if (_UnfocusedAppearance)
|
|
{
|
|
Model::AppearanceConfig unfocused{ nullptr };
|
|
if (*_UnfocusedAppearance)
|
|
{
|
|
const auto appearance = AppearanceConfig::CopyAppearance(winrt::get_self<AppearanceConfig>(*_UnfocusedAppearance), weakProfile);
|
|
appearance->AddLeastImportantParent(defaultAppearance);
|
|
unfocused = *appearance;
|
|
}
|
|
profile->_UnfocusedAppearance = unfocused;
|
|
}
|
|
|
|
return profile;
|
|
}
|
|
|
|
// Method Description:
|
|
// - Create a new instance of this class from a serialized JsonObject.
|
|
// Arguments:
|
|
// - json: an object which should be a serialization of a Profile object.
|
|
// Return Value:
|
|
// - a new Profile instance created from the values in `json`
|
|
winrt::com_ptr<winrt::Microsoft::Terminal::Settings::Model::implementation::Profile> Profile::FromJson(const Json::Value& json)
|
|
{
|
|
auto result = winrt::make_self<Profile>();
|
|
result->LayerJson(json);
|
|
return result;
|
|
}
|
|
|
|
// Method Description:
|
|
// - Layer values from the given json object on top of the existing properties
|
|
// of this object. For any keys we're expecting to be able to parse in the
|
|
// given object, we'll parse them and replace our settings with values from
|
|
// the new json object. Properties that _aren't_ in the json object will _not_
|
|
// be replaced.
|
|
// - Optional values in the profile that are set to `null` in the json object
|
|
// will be set to nullopt.
|
|
// Arguments:
|
|
// - json: an object which should be a partial serialization of a Profile object.
|
|
// Return Value:
|
|
// <none>
|
|
void Profile::LayerJson(const Json::Value& json)
|
|
{
|
|
// Appearance Settings
|
|
auto defaultAppearanceImpl = winrt::get_self<implementation::AppearanceConfig>(_DefaultAppearance);
|
|
defaultAppearanceImpl->LayerJson(json);
|
|
|
|
// Font Settings
|
|
auto fontInfoImpl = winrt::get_self<implementation::FontConfig>(_FontInfo);
|
|
fontInfoImpl->LayerJson(json);
|
|
|
|
// Profile-specific Settings
|
|
JsonUtils::GetValueForKey(json, NameKey, _Name);
|
|
JsonUtils::GetValueForKey(json, UpdatesKey, _Updates);
|
|
JsonUtils::GetValueForKey(json, GuidKey, _Guid);
|
|
|
|
// Make sure Source is before Hidden! We use that to exclude false positives from the settings logger!
|
|
JsonUtils::GetValueForKey(json, SourceKey, _Source);
|
|
JsonUtils::GetValueForKey(json, HiddenKey, _Hidden);
|
|
_logSettingIfSet(HiddenKey, _Hidden.has_value());
|
|
|
|
// Padding was never specified as an integer, but it was a common working mistake.
|
|
// Allow it to be permissive.
|
|
JsonUtils::GetValueForKey(json, PaddingKey, _Padding, JsonUtils::OptionalConverter<hstring, JsonUtils::PermissiveStringConverter<std::wstring>>{});
|
|
_logSettingIfSet(PaddingKey, _Padding.has_value());
|
|
|
|
JsonUtils::GetValueForKey(json, TabColorKey, _TabColor);
|
|
_logSettingIfSet(TabColorKey, _TabColor.has_value());
|
|
|
|
// Try to load some legacy keys, to migrate them.
|
|
// Done _before_ the MTSM_PROFILE_SETTINGS, which have the updated keys.
|
|
JsonUtils::GetValueForKey(json, LegacyShowMarksKey, _ShowMarks);
|
|
JsonUtils::GetValueForKey(json, LegacyAutoMarkPromptsKey, _AutoMarkPrompts);
|
|
JsonUtils::GetValueForKey(json, LegacyRightClickContextMenuKey, _RightClickContextMenu);
|
|
|
|
#define PROFILE_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
|
JsonUtils::GetValueForKey(json, jsonKey, _##name); \
|
|
_logSettingIfSet(jsonKey, _##name.has_value());
|
|
|
|
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_LAYER_JSON)
|
|
#undef PROFILE_SETTINGS_LAYER_JSON
|
|
|
|
if (json.isMember(JsonKey(UnfocusedAppearanceKey)))
|
|
{
|
|
auto unfocusedAppearance{ winrt::make_self<implementation::AppearanceConfig>(weak_ref<Model::Profile>(*this)) };
|
|
|
|
// If an unfocused appearance is defined in this profile, any undefined parameters are
|
|
// taken from this profile's default appearance, so add it as a parent
|
|
com_ptr<AppearanceConfig> parentCom;
|
|
parentCom.copy_from(defaultAppearanceImpl);
|
|
unfocusedAppearance->AddLeastImportantParent(parentCom);
|
|
|
|
unfocusedAppearance->LayerJson(json[JsonKey(UnfocusedAppearanceKey)]);
|
|
_UnfocusedAppearance = *unfocusedAppearance;
|
|
|
|
_logSettingSet(UnfocusedAppearanceKey);
|
|
}
|
|
}
|
|
|
|
winrt::hstring Profile::EvaluatedStartingDirectory() const
|
|
{
|
|
auto path{ StartingDirectory() };
|
|
if (!path.empty())
|
|
{
|
|
return winrt::hstring{ Profile::EvaluateStartingDirectory(path.c_str()) };
|
|
}
|
|
// treated as "inherit directory from parent process"
|
|
return path;
|
|
}
|
|
|
|
void Profile::_FinalizeInheritance()
|
|
{
|
|
if (auto defaultAppearanceImpl = get_self<AppearanceConfig>(_DefaultAppearance))
|
|
{
|
|
// Clear any existing parents first, we don't want duplicates from any previous
|
|
// calls to this function
|
|
defaultAppearanceImpl->ClearParents();
|
|
for (auto& parent : _parents)
|
|
{
|
|
if (auto parentDefaultAppearanceImpl = parent->_DefaultAppearance.try_as<AppearanceConfig>())
|
|
{
|
|
defaultAppearanceImpl->AddLeastImportantParent(parentDefaultAppearanceImpl);
|
|
}
|
|
}
|
|
}
|
|
if (auto fontInfoImpl = get_self<FontConfig>(_FontInfo))
|
|
{
|
|
// Clear any existing parents first, we don't want duplicates from any previous
|
|
// calls to this function
|
|
fontInfoImpl->ClearParents();
|
|
for (auto& parent : _parents)
|
|
{
|
|
if (auto parentFontInfoImpl = parent->_FontInfo.try_as<FontConfig>())
|
|
{
|
|
fontInfoImpl->AddLeastImportantParent(parentFontInfoImpl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
winrt::Microsoft::Terminal::Settings::Model::IAppearanceConfig Profile::DefaultAppearance()
|
|
{
|
|
return _DefaultAppearance;
|
|
}
|
|
|
|
winrt::Microsoft::Terminal::Settings::Model::FontConfig Profile::FontInfo()
|
|
{
|
|
return _FontInfo;
|
|
}
|
|
|
|
// Method Description:
|
|
// - Helper function for expanding any environment variables in a user-supplied starting directory and validating the resulting path
|
|
// Arguments:
|
|
// - The value from the settings.json file
|
|
// Return Value:
|
|
// - The directory string with any environment variables expanded. If the resulting path is invalid,
|
|
// - the function returns an evaluated version of %userprofile% to avoid blocking the session from starting.
|
|
std::wstring Profile::EvaluateStartingDirectory(const std::wstring& directory)
|
|
{
|
|
// Prior to GH#9541, we'd validate that the user's startingDirectory existed
|
|
// here. If it was invalid, we'd gracefully fall back to %USERPROFILE%.
|
|
//
|
|
// However, that could cause hangs when combined with WSL. When the WSL
|
|
// filesystem is slow to respond, we'll end up waiting indefinitely for
|
|
// their filesystem driver to respond. This can result in the whole terminal
|
|
// becoming unresponsive.
|
|
//
|
|
// If the path is eventually invalid, we'll display warning in the
|
|
// ConptyConnection when the process fails to launch.
|
|
return wil::ExpandEnvironmentStringsW<std::wstring>(directory.c_str());
|
|
}
|
|
|
|
// Function Description:
|
|
// - Generates a unique guid for a profile, given the name. For a given name, will always return the same GUID.
|
|
// Arguments:
|
|
// - name: The name to generate a unique GUID from
|
|
// Return Value:
|
|
// - a uuidv5 GUID generated from the given name.
|
|
winrt::guid Profile::_GenerateGuidForProfile(const std::wstring_view& name, const std::wstring_view& source) noexcept
|
|
{
|
|
// If we have a _source, then we can from a dynamic profile generator. Use
|
|
// our source to build the namespace guid, instead of using the default GUID.
|
|
|
|
const auto namespaceGuid = !source.empty() ?
|
|
Utils::CreateV5Uuid(RUNTIME_GENERATED_PROFILE_NAMESPACE_GUID, std::as_bytes(std::span{ source })) :
|
|
RUNTIME_GENERATED_PROFILE_NAMESPACE_GUID;
|
|
|
|
// Always use the name to generate the temporary GUID. That way, across
|
|
// reloads, we'll generate the same static GUID.
|
|
return { Utils::CreateV5Uuid(namespaceGuid, std::as_bytes(std::span{ name })) };
|
|
}
|
|
|
|
// Method Description:
|
|
// - Create a new serialized JsonObject from an instance of this class
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - the JsonObject representing this instance
|
|
Json::Value Profile::ToJson() const
|
|
{
|
|
// Initialize the json with the appearance settings
|
|
auto json{ winrt::get_self<implementation::AppearanceConfig>(_DefaultAppearance)->ToJson() };
|
|
|
|
// GH #9962:
|
|
// If the settings.json was missing, when we load the dynamic profiles, they are completely empty.
|
|
// This caused us to serialize empty profiles "{}" on accident.
|
|
const auto writeBasicSettings{ !Source().empty() };
|
|
|
|
// Profile-specific Settings
|
|
JsonUtils::SetValueForKey(json, NameKey, writeBasicSettings ? Name() : _Name);
|
|
JsonUtils::SetValueForKey(json, GuidKey, writeBasicSettings ? Guid() : _Guid);
|
|
JsonUtils::SetValueForKey(json, HiddenKey, writeBasicSettings ? Hidden() : _Hidden);
|
|
JsonUtils::SetValueForKey(json, SourceKey, writeBasicSettings ? Source() : _Source);
|
|
|
|
// PermissiveStringConverter is unnecessary for serialization
|
|
JsonUtils::SetValueForKey(json, PaddingKey, _Padding);
|
|
|
|
JsonUtils::SetValueForKey(json, TabColorKey, _TabColor);
|
|
|
|
#define PROFILE_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
|
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
|
|
|
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_TO_JSON)
|
|
#undef PROFILE_SETTINGS_TO_JSON
|
|
|
|
if (auto fontJSON = winrt::get_self<FontConfig>(_FontInfo)->ToJson(); !fontJSON.empty())
|
|
{
|
|
json[JsonKey(FontInfoKey)] = std::move(fontJSON);
|
|
}
|
|
|
|
if (_UnfocusedAppearance)
|
|
{
|
|
json[JsonKey(UnfocusedAppearanceKey)] = winrt::get_self<AppearanceConfig>(_UnfocusedAppearance.value())->ToJson();
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
// Given a commandLine like the following:
|
|
// * "C:\WINDOWS\System32\cmd.exe"
|
|
// * "pwsh -WorkingDirectory ~"
|
|
// * "C:\Program Files\PowerShell\7\pwsh.exe"
|
|
// * "C:\Program Files\PowerShell\7\pwsh.exe -WorkingDirectory ~"
|
|
//
|
|
// This function returns:
|
|
// * "C:\Windows\System32\cmd.exe"
|
|
// * "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~"
|
|
// * "C:\Program Files\PowerShell\7\pwsh.exe"
|
|
// * "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~"
|
|
//
|
|
// The resulting strings are then used for comparisons in _getProfileForCommandLine().
|
|
// For instance a resulting string of
|
|
// "C:\Program Files\PowerShell\7\pwsh.exe"
|
|
// is considered a compatible profile with
|
|
// "C:\Program Files\PowerShell\7\pwsh.exe -WorkingDirectory ~"
|
|
// as it shares the same (normalized) prefix.
|
|
std::wstring Profile::NormalizeCommandLine(LPCWSTR commandLine)
|
|
{
|
|
// Turn "%SystemRoot%\System32\cmd.exe" into "C:\WINDOWS\System32\cmd.exe".
|
|
// We do this early, as environment variables might occur anywhere in the commandLine.
|
|
std::wstring normalized;
|
|
THROW_IF_FAILED(wil::ExpandEnvironmentStringsW(commandLine, normalized));
|
|
|
|
// One of the most important things this function does is to strip quotes.
|
|
// That way the commandLine "foo.exe -bar" and "\"foo.exe\" \"-bar\"" appear identical.
|
|
// We'll abuse CommandLineToArgvW for that as it's close to what CreateProcessW uses.
|
|
auto argc = 0;
|
|
wil::unique_hlocal_ptr<PWSTR[]> argv{ CommandLineToArgvW(normalized.c_str(), &argc) };
|
|
THROW_LAST_ERROR_IF(!argc);
|
|
|
|
// The index of the first argument in argv for our executable in argv[0].
|
|
// Given {"C:\Program Files\PowerShell\7\pwsh.exe", "-WorkingDirectory", "~"} this will be 1.
|
|
auto startOfArguments = 1;
|
|
|
|
// The given commandLine should start with an executable name or path.
|
|
// For instance given the following argv arrays:
|
|
// * {"C:\WINDOWS\System32\cmd.exe"}
|
|
// * {"pwsh", "-WorkingDirectory", "~"}
|
|
// * {"C:\Program", "Files\PowerShell\7\pwsh.exe"}
|
|
// ^^^^
|
|
// Notice how there used to be a space in the path, which was split by ExpandEnvironmentStringsW().
|
|
// CreateProcessW() supports such atrocities, so we got to do the same.
|
|
// * {"C:\Program Files\PowerShell\7\pwsh.exe", "-WorkingDirectory", "~"}
|
|
//
|
|
// This loop tries to resolve relative paths, as well as executable names in %PATH%
|
|
// into absolute paths and normalizes them. The results for the above would be:
|
|
// * "C:\Windows\System32\cmd.exe"
|
|
// * "C:\Program Files\PowerShell\7\pwsh.exe"
|
|
// * "C:\Program Files\PowerShell\7\pwsh.exe"
|
|
// * "C:\Program Files\PowerShell\7\pwsh.exe"
|
|
for (;;)
|
|
{
|
|
// CreateProcessW uses RtlGetExePath to get the lpPath for SearchPathW.
|
|
// The difference between the behavior of SearchPathW if lpPath is nullptr and what RtlGetExePath returns
|
|
// seems to be mostly whether SafeProcessSearchMode is respected and the support for relative paths.
|
|
// Windows Terminal makes the use of relative paths rather impractical which is why we simply dropped the call to RtlGetExePath.
|
|
const auto status = wil::SearchPathW(nullptr, argv[0], L".exe", normalized);
|
|
|
|
if (status == S_OK)
|
|
{
|
|
const auto attributes = GetFileAttributesW(normalized.c_str());
|
|
|
|
if (attributes != INVALID_FILE_ATTRIBUTES && WI_IsFlagClear(attributes, FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
std::filesystem::path path{ std::move(normalized) };
|
|
|
|
// canonical() will resolve symlinks, etc. for us.
|
|
{
|
|
std::error_code ec;
|
|
auto canonicalPath = std::filesystem::canonical(path, ec);
|
|
if (!ec)
|
|
{
|
|
path = std::move(canonicalPath);
|
|
}
|
|
}
|
|
|
|
// std::filesystem::path has no way to extract the internal path.
|
|
// So about that.... I own you, computer. Give me that path.
|
|
normalized = std::move(const_cast<std::wstring&>(path.native()));
|
|
break;
|
|
}
|
|
}
|
|
// All other error types aren't handled at the moment.
|
|
else if (status != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
|
|
{
|
|
break;
|
|
}
|
|
// If the file path couldn't be found by SearchPathW this could be the result of us being given a commandLine
|
|
// like "C:\foo bar\baz.exe -arg" which is resolved to the argv array {"C:\foo", "bar\baz.exe", "-arg"},
|
|
// or we were erroneously given a directory to execute (e.g. someone ran `wt .`).
|
|
// Just like CreateProcessW() we thus try to concatenate arguments until we successfully resolve a valid path.
|
|
// Of course we can only do that if we have at least 2 remaining arguments in argv.
|
|
if ((argc - startOfArguments) < 2)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// As described in the comment right above, we concatenate arguments in an attempt to resolve a valid path.
|
|
// The code below turns argv from {"C:\foo", "bar\baz.exe", "-arg"} into {"C:\foo bar\baz.exe", "-arg"}.
|
|
// The code abuses the fact that CommandLineToArgvW allocates all arguments back-to-back on the heap separated by '\0'.
|
|
argv[startOfArguments][-1] = L' ';
|
|
++startOfArguments;
|
|
}
|
|
|
|
// We've (hopefully) finished resolving the path to the executable.
|
|
// We're now going to append all remaining arguments to the resulting string.
|
|
// If argv is {"C:\Program Files\PowerShell\7\pwsh.exe", "-WorkingDirectory", "~"},
|
|
// then we'll get "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~"
|
|
if (startOfArguments < argc)
|
|
{
|
|
// normalized contains a canonical form of argv[0] at this point.
|
|
// -1 allows us to include the \0 between argv[0] and argv[1] in the call to append().
|
|
const auto beg = argv[startOfArguments] - 1;
|
|
const auto lastArg = argv[argc - 1];
|
|
const auto end = lastArg + wcslen(lastArg);
|
|
normalized.append(beg, end);
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
void Profile::_logSettingSet(const std::string_view& setting)
|
|
{
|
|
_changeLog.emplace(setting);
|
|
}
|
|
|
|
void Profile::_logSettingIfSet(const std::string_view& setting, const bool isSet)
|
|
{
|
|
if (isSet)
|
|
{
|
|
// make sure this matches defaults.json.
|
|
static constexpr winrt::guid DEFAULT_WINDOWS_POWERSHELL_GUID{ 0x61c54bbd, 0xc2c6, 0x5271, { 0x96, 0xe7, 0x00, 0x9a, 0x87, 0xff, 0x44, 0xbf } };
|
|
static constexpr winrt::guid DEFAULT_COMMAND_PROMPT_GUID{ 0x0caa0dad, 0x35be, 0x5f56, { 0xa8, 0xff, 0xaf, 0xce, 0xee, 0xaa, 0x61, 0x01 } };
|
|
|
|
// Exclude some false positives from userDefaults.json
|
|
// NOTE: we can't use the OriginTag here because it hasn't been set yet!
|
|
const bool isWinPow = _Guid.has_value() && *_Guid == DEFAULT_WINDOWS_POWERSHELL_GUID; //_Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Windows PowerShell");
|
|
const bool isCmd = _Guid.has_value() && *_Guid == DEFAULT_COMMAND_PROMPT_GUID; //_Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Command Prompt");
|
|
const bool isACS = _Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Azure Cloud Shell");
|
|
const bool isWTDynamicProfile = _Source.has_value() && til::starts_with(*_Source, L"Windows.Terminal");
|
|
const bool settingHiddenToFalse = til::equals_insensitive_ascii(setting, HiddenKey) && _Hidden.has_value() && _Hidden == false;
|
|
const bool settingCommandlineToWinPow = til::equals_insensitive_ascii(setting, "commandline") && _Commandline.has_value() && til::equals_insensitive_ascii(*_Commandline, L"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe");
|
|
const bool settingCommandlineToCmd = til::equals_insensitive_ascii(setting, "commandline") && _Commandline.has_value() && til::equals_insensitive_ascii(*_Commandline, L"%SystemRoot%\\System32\\cmd.exe");
|
|
// clang-format off
|
|
if (!(isWinPow && (settingHiddenToFalse || settingCommandlineToWinPow))
|
|
&& !(isCmd && (settingHiddenToFalse || settingCommandlineToCmd))
|
|
&& !(isACS && settingHiddenToFalse)
|
|
&& !(isWTDynamicProfile && settingHiddenToFalse))
|
|
{
|
|
// clang-format on
|
|
_logSettingSet(setting);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Profile::ResolveMediaResources(const Model::MediaResourceResolver& resolver)
|
|
{
|
|
if (const auto icon{ _getIconImpl() }; icon && *icon)
|
|
{
|
|
const auto iconSource{ _getIconOverrideSourceImpl() };
|
|
ResolveIconMediaResource(iconSource->_Origin, iconSource->SourceBasePath, *icon, resolver);
|
|
|
|
// If the icon was specified at any layer, but fails resolution *or* contains the empty string,
|
|
// fall back to the normalized command line at or above this layer.
|
|
if (!icon->Ok() || icon->Resolved().empty() && !iconSource->Commandline().empty())
|
|
{
|
|
// We want to resolve the icon to the commandline, but we risk that the icon was already
|
|
// resolved. We don't want to do that (as MediaResource asserts that it was only resolved
|
|
// one time). However, we can't just make a new empty resource and resolve it -- that
|
|
// might replace the value in the user's settings when we serialize it again...
|
|
// So instead, make a new one derived from the icon we started with, then resolve it
|
|
// ourselves.
|
|
auto newIcon{ MediaResource::FromString(icon->Path()) };
|
|
const std::wstring cmdline{ NormalizeCommandLine(iconSource->Commandline().c_str()) };
|
|
newIcon.Resolve(cmdline.c_str() /* c_str: give hstring a chance to find the null terminator */);
|
|
iconSource->_Icon = std::move(newIcon);
|
|
}
|
|
}
|
|
|
|
if (const auto container{ _DefaultAppearance.as<IMediaResourceContainer>() })
|
|
{
|
|
container->ResolveMediaResources(resolver);
|
|
}
|
|
|
|
if (const auto container{ UnfocusedAppearance().try_as<IMediaResourceContainer>() })
|
|
{
|
|
container->ResolveMediaResources(resolver);
|
|
}
|
|
|
|
if (const auto [bellSoundSource, bellSounds]{ _getBellSoundOverrideSourceAndValueImpl() };
|
|
bellSoundSource && bellSounds && *bellSounds && bellSounds->Size() > 0)
|
|
{
|
|
for (const auto& bellSound : *bellSounds)
|
|
{
|
|
ResolveMediaResource(bellSoundSource->_Origin, bellSoundSource->SourceBasePath, bellSound, resolver);
|
|
}
|
|
// It's important that we keep the invalid bell sounds in the list, because we may want to write it back out to disk
|
|
}
|
|
}
|
|
|
|
void Profile::LogSettingChanges(std::set<std::string>& changes, const std::string_view& context) const
|
|
{
|
|
for (const auto& setting : _changeLog)
|
|
{
|
|
changes.emplace(fmt::format(FMT_COMPILE("{}.{}"), context, setting));
|
|
}
|
|
|
|
std::string fontContext{ fmt::format(FMT_COMPILE("{}.{}"), context, FontInfoKey) };
|
|
winrt::get_self<implementation::FontConfig>(_FontInfo)->LogSettingChanges(changes, fontContext);
|
|
|
|
// We don't want to distinguish between "profile.defaultAppearance.*" and "profile.unfocusedAppearance.*" settings,
|
|
// but we still want to aggregate all of the appearance settings from both appearances.
|
|
// Log them as "profile.appearance.*"
|
|
std::string appContext{ fmt::format(FMT_COMPILE("{}.{}"), context, "appearance") };
|
|
winrt::get_self<implementation::AppearanceConfig>(_DefaultAppearance)->LogSettingChanges(changes, appContext);
|
|
if (_UnfocusedAppearance)
|
|
{
|
|
winrt::get_self<implementation::AppearanceConfig>(*_UnfocusedAppearance)->LogSettingChanges(changes, appContext);
|
|
}
|
|
}
|