Support environment variables in the settings (#15082)
Existing environment variables can be referenced by enclosing the name in percent characters (e.g. `%PATH%`). Resurrects #9287 by @christapley. Tests added and manually tested. Closes #2785 Closes #9233 Co-authored-by: Chris Tapley <chris.tapley.81@gmail.com>
This commit is contained in:
parent
508adbb1ec
commit
56d451ded7
|
@ -2574,6 +2574,13 @@
|
|||
"default": false,
|
||||
"description": "When true, this profile should always open in an elevated context. If the window isn't running as an Administrator, then a new elevated window will be created."
|
||||
},
|
||||
"environment": {
|
||||
"description": "Key-value pairs representing environment variables to set. Environment variable names are not case sensitive. You can reference existing environment variable names by enclosing them in literal percent characters (e.g. %PATH%).",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"experimental.autoMarkPrompts": {
|
||||
"default": false,
|
||||
"description": "When set to true, prompts will automatically be marked.",
|
||||
|
|
|
@ -38,6 +38,9 @@ namespace SettingsModelLocalTests
|
|||
TEST_METHOD(LayerProfileProperties);
|
||||
TEST_METHOD(LayerProfileIcon);
|
||||
TEST_METHOD(LayerProfilesOnArray);
|
||||
TEST_METHOD(ProfileWithEnvVars);
|
||||
TEST_METHOD(ProfileWithEnvVarsSameNameDifferentCases);
|
||||
|
||||
TEST_METHOD(DuplicateProfileTest);
|
||||
TEST_METHOD(TestGenGuidsForProfiles);
|
||||
TEST_METHOD(TestCorrectOldDefaultShellPaths);
|
||||
|
@ -349,6 +352,50 @@ namespace SettingsModelLocalTests
|
|||
VERIFY_ARE_NOT_EQUAL(settings->AllProfiles().GetAt(0).Guid(), settings->AllProfiles().GetAt(1).Guid());
|
||||
}
|
||||
|
||||
void ProfileTests::ProfileWithEnvVars()
|
||||
{
|
||||
const std::string profileString{ R"({
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"environment": {
|
||||
"VAR_1": "value1",
|
||||
"VAR_2": "value2",
|
||||
"VAR_3": "%VAR_3%;value3"
|
||||
}
|
||||
})" };
|
||||
const auto profile = implementation::Profile::FromJson(VerifyParseSucceeded(profileString));
|
||||
std::vector<IEnvironmentVariableMap> envVarMaps{};
|
||||
envVarMaps.emplace_back(profile->EnvironmentVariables());
|
||||
for (auto& envMap : envVarMaps)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(static_cast<uint32_t>(3), envMap.Size());
|
||||
VERIFY_ARE_EQUAL(L"value1", envMap.Lookup(L"VAR_1"));
|
||||
VERIFY_ARE_EQUAL(L"value2", envMap.Lookup(L"VAR_2"));
|
||||
VERIFY_ARE_EQUAL(L"%VAR_3%;value3", envMap.Lookup(L"VAR_3"));
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileTests::ProfileWithEnvVarsSameNameDifferentCases()
|
||||
{
|
||||
const std::string userSettings{ R"({
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"environment": {
|
||||
"FOO": "VALUE",
|
||||
"Foo": "Value"
|
||||
}
|
||||
}
|
||||
]
|
||||
})" };
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(userSettings);
|
||||
const auto warnings = settings->Warnings();
|
||||
VERIFY_ARE_EQUAL(static_cast<uint32_t>(2), warnings.Size());
|
||||
uint32_t index;
|
||||
VERIFY_IS_TRUE(warnings.IndexOf(SettingsLoadWarnings::InvalidProfileEnvironmentVariables, index));
|
||||
}
|
||||
|
||||
void ProfileTests::TestCorrectOldDefaultShellPaths()
|
||||
{
|
||||
static constexpr std::string_view inboxProfiles{ R"({
|
||||
|
|
|
@ -165,7 +165,13 @@ namespace SettingsModelLocalTests
|
|||
"historySize": 9001,
|
||||
|
||||
"closeOnExit": "graceful",
|
||||
"experimental.retroTerminalEffect": false
|
||||
"experimental.retroTerminalEffect": false,
|
||||
"environment":
|
||||
{
|
||||
"KEY_1": "VALUE_1",
|
||||
"KEY_2": "%KEY_1%",
|
||||
"KEY_3": "%PATH%"
|
||||
}
|
||||
})" };
|
||||
|
||||
static constexpr std::string_view smallProfileString{ R"(
|
||||
|
|
|
@ -282,6 +282,9 @@
|
|||
<value>Failed to parse "startupActions".</value>
|
||||
<comment>{Locked="\"startupActions\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidProfileEnvironmentVariables" xml:space="preserve">
|
||||
<value>Found multiple environment variables with the same name in different cases (lower/upper) - only one value will be used.</value>
|
||||
</data>
|
||||
<data name="CmdCommandArgDesc" xml:space="preserve">
|
||||
<value>An optional command, with arguments, to be spawned in the new tab or pane</value>
|
||||
</data>
|
||||
|
|
|
@ -1216,7 +1216,8 @@ namespace winrt::TerminalApp::implementation
|
|||
nullptr,
|
||||
settings.InitialRows(),
|
||||
settings.InitialCols(),
|
||||
winrt::guid());
|
||||
winrt::guid(),
|
||||
profile.Guid());
|
||||
|
||||
if constexpr (Feature_VtPassthroughMode::IsEnabled())
|
||||
{
|
||||
|
@ -1228,12 +1229,9 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
else
|
||||
{
|
||||
// profile is guaranteed to exist here
|
||||
auto guidWString = Utils::GuidToString(profile.Guid());
|
||||
|
||||
StringMap envMap{};
|
||||
envMap.Insert(L"WT_PROFILE_ID", guidWString);
|
||||
envMap.Insert(L"WSLENV", L"WT_PROFILE_ID");
|
||||
const auto environment = settings.EnvironmentVariables() != nullptr ?
|
||||
settings.EnvironmentVariables().GetView() :
|
||||
nullptr;
|
||||
|
||||
// Update the path to be relative to whatever our CWD is.
|
||||
//
|
||||
|
@ -1264,10 +1262,11 @@ namespace winrt::TerminalApp::implementation
|
|||
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
|
||||
newWorkingDirectory,
|
||||
settings.StartingTitle(),
|
||||
envMap.GetView(),
|
||||
environment,
|
||||
settings.InitialRows(),
|
||||
settings.InitialCols(),
|
||||
winrt::guid());
|
||||
winrt::guid(),
|
||||
profile.Guid());
|
||||
|
||||
valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough()));
|
||||
valueSet.Insert(L"reloadEnvironmentVariables",
|
||||
|
|
|
@ -50,6 +50,7 @@ static const std::array settingsLoadWarningsLabels{
|
|||
USES_RESOURCE(L"InvalidColorSchemeInCmd"),
|
||||
USES_RESOURCE(L"InvalidSplitSize"),
|
||||
USES_RESOURCE(L"FailedToParseStartupActions"),
|
||||
USES_RESOURCE(L"InvalidProfileEnvironmentVariables"),
|
||||
USES_RESOURCE(L"FailedToParseSubCommands"),
|
||||
USES_RESOURCE(L"UnknownTheme"),
|
||||
USES_RESOURCE(L"DuplicateRemainingProfilesEntry"),
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
#include "ConptyConnection.h"
|
||||
|
||||
#include <conpty-static.h>
|
||||
#include <til/string.h>
|
||||
#include <til/env.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#include "CTerminalHandoff.h"
|
||||
#include "LibraryResources.h"
|
||||
#include "../../types/inc/Environment.hpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
#include "ConptyConnection.g.cpp"
|
||||
|
@ -84,31 +84,19 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
|
||||
auto cmdline{ wil::ExpandEnvironmentStringsW<std::wstring>(_commandline.c_str()) }; // mutable copy -- required for CreateProcessW
|
||||
|
||||
Utils::EnvironmentVariableMapW environment;
|
||||
til::env environment;
|
||||
auto zeroEnvMap = wil::scope_exit([&]() noexcept {
|
||||
// Can't zero the keys, but at least we can zero the values.
|
||||
for (auto& [name, value] : environment)
|
||||
{
|
||||
::SecureZeroMemory(value.data(), value.size() * sizeof(decltype(value.begin())::value_type));
|
||||
}
|
||||
|
||||
environment.clear();
|
||||
});
|
||||
|
||||
// Populate the environment map with the current environment.
|
||||
if (_reloadEnvironmentVariables)
|
||||
{
|
||||
til::env refreshedEnvironment;
|
||||
refreshedEnvironment.regenerate();
|
||||
|
||||
for (auto& [key, value] : refreshedEnvironment.as_map())
|
||||
{
|
||||
environment.try_emplace(key, std::move(value));
|
||||
}
|
||||
environment.regenerate();
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_IF_FAILED(Utils::UpdateEnvironmentMapW(environment));
|
||||
environment = til::env::from_current_environment();
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -119,39 +107,45 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
const auto guidSubStr = std::wstring_view{ wsGuid }.substr(1);
|
||||
|
||||
// Ensure every connection has the unique identifier in the environment.
|
||||
environment.insert_or_assign(L"WT_SESSION", guidSubStr.data());
|
||||
environment.as_map().insert_or_assign(L"WT_SESSION", guidSubStr.data());
|
||||
|
||||
// The profile Guid does include the enclosing '{}'
|
||||
const auto profileGuid{ Utils::GuidToString(_profileGuid) };
|
||||
environment.as_map().insert_or_assign(L"WT_PROFILE_ID", profileGuid.data());
|
||||
|
||||
// WSLENV is a colon-delimited list of environment variables (+flags) that should appear inside WSL
|
||||
// https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/
|
||||
std::wstring wslEnv{ L"WT_SESSION:WT_PROFILE_ID:" };
|
||||
if (_environment)
|
||||
{
|
||||
// add additional WT env vars like WT_SETTINGS, WT_DEFAULTS and WT_PROFILE_ID
|
||||
for (auto item : _environment)
|
||||
// Order the environment variable names so that resolution order is consistent
|
||||
std::set<std::wstring, til::wstring_case_insensitive_compare> keys{};
|
||||
for (const auto item : _environment)
|
||||
{
|
||||
keys.insert(item.Key().c_str());
|
||||
}
|
||||
// add additional env vars
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto key = item.Key();
|
||||
// This will throw if the value isn't a string. If that
|
||||
// happens, then just skip this entry.
|
||||
auto value = winrt::unbox_value<hstring>(item.Value());
|
||||
const auto value = winrt::unbox_value<hstring>(_environment.Lookup(key));
|
||||
|
||||
// avoid clobbering WSLENV
|
||||
if (std::wstring_view{ key } == L"WSLENV")
|
||||
{
|
||||
auto current = environment[L"WSLENV"];
|
||||
value = current + L":" + value;
|
||||
}
|
||||
|
||||
environment.insert_or_assign(key.c_str(), value.c_str());
|
||||
environment.set_user_environment_var(key.c_str(), value.c_str());
|
||||
// For each environment variable added to the environment, also add it to WSLENV
|
||||
wslEnv += key + L":";
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
// WSLENV is a colon-delimited list of environment variables (+flags) that should appear inside WSL
|
||||
// https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/
|
||||
|
||||
auto wslEnv = environment[L"WSLENV"];
|
||||
wslEnv = L"WT_SESSION:" + wslEnv; // prepend WT_SESSION to make sure it's visible inside WSL.
|
||||
environment.insert_or_assign(L"WSLENV", wslEnv);
|
||||
// We want to prepend new environment variables to WSLENV - that way if a variable already
|
||||
// exists in WSLENV but with a flag, the flag will be respected.
|
||||
// (This behaviour was empirically observed)
|
||||
wslEnv += environment.as_map()[L"WSLENV"];
|
||||
environment.as_map().insert_or_assign(L"WSLENV", wslEnv);
|
||||
}
|
||||
|
||||
std::vector<wchar_t> newEnvVars;
|
||||
|
@ -160,7 +154,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
newEnvVars.size() * sizeof(decltype(newEnvVars.begin())::value_type));
|
||||
});
|
||||
|
||||
RETURN_IF_FAILED(Utils::EnvironmentMapToEnvironmentStringsW(environment, newEnvVars));
|
||||
RETURN_IF_FAILED(environment.to_environment_strings_w(newEnvVars));
|
||||
|
||||
auto lpEnvironment = newEnvVars.empty() ? nullptr : newEnvVars.data();
|
||||
|
||||
|
@ -244,7 +238,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
const Windows::Foundation::Collections::IMapView<hstring, hstring>& environment,
|
||||
uint32_t rows,
|
||||
uint32_t columns,
|
||||
const winrt::guid& guid)
|
||||
const winrt::guid& guid,
|
||||
const winrt::guid& profileGuid)
|
||||
{
|
||||
Windows::Foundation::Collections::ValueSet vs{};
|
||||
|
||||
|
@ -254,6 +249,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
vs.Insert(L"initialRows", Windows::Foundation::PropertyValue::CreateUInt32(rows));
|
||||
vs.Insert(L"initialCols", Windows::Foundation::PropertyValue::CreateUInt32(columns));
|
||||
vs.Insert(L"guid", Windows::Foundation::PropertyValue::CreateGuid(guid));
|
||||
vs.Insert(L"profileGuid", Windows::Foundation::PropertyValue::CreateGuid(profileGuid));
|
||||
|
||||
if (environment)
|
||||
{
|
||||
|
@ -288,6 +284,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
}
|
||||
_reloadEnvironmentVariables = winrt::unbox_value_or<bool>(settings.TryLookup(L"reloadEnvironmentVariables").try_as<Windows::Foundation::IPropertyValue>(),
|
||||
_reloadEnvironmentVariables);
|
||||
_profileGuid = winrt::unbox_value_or<winrt::guid>(settings.TryLookup(L"profileGuid").try_as<Windows::Foundation::IPropertyValue>(), _profileGuid);
|
||||
}
|
||||
|
||||
if (_guid == guid{})
|
||||
|
|
|
@ -52,7 +52,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
const Windows::Foundation::Collections::IMapView<hstring, hstring>& environment,
|
||||
uint32_t rows,
|
||||
uint32_t columns,
|
||||
const winrt::guid& guid);
|
||||
const winrt::guid& guid,
|
||||
const winrt::guid& profileGuid);
|
||||
|
||||
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
|
||||
|
||||
|
@ -90,6 +91,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|||
std::array<char, 4096> _buffer{};
|
||||
bool _passthroughMode{};
|
||||
bool _reloadEnvironmentVariables{};
|
||||
guid _profileGuid{};
|
||||
|
||||
struct StartupInfoFromDefTerm
|
||||
{
|
||||
|
|
|
@ -31,6 +31,7 @@ namespace Microsoft.Terminal.TerminalConnection
|
|||
IMapView<String, String> environment,
|
||||
UInt32 rows,
|
||||
UInt32 columns,
|
||||
Guid guid);
|
||||
Guid guid,
|
||||
Guid profileGuid);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -52,7 +52,6 @@ namespace Microsoft.Terminal.Control
|
|||
|
||||
String Commandline { get; };
|
||||
String StartingDirectory { get; };
|
||||
String EnvironmentVariables { get; };
|
||||
|
||||
TextAntialiasingMode AntialiasingMode { get; };
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <shellapi.h>
|
||||
#include <shlwapi.h>
|
||||
#include <til/latch.h>
|
||||
#include <til/string.h>
|
||||
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
|
@ -417,6 +418,7 @@ void CascadiaSettings::_validateSettings()
|
|||
_validateKeybindings();
|
||||
_validateColorSchemesInCommands();
|
||||
_validateThemeExists();
|
||||
_validateProfileEnvironmentVariables();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -541,6 +543,30 @@ void CascadiaSettings::_validateMediaResources()
|
|||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Checks if the profiles contain multiple environment variables with the same name, but different
|
||||
// cases
|
||||
void CascadiaSettings::_validateProfileEnvironmentVariables()
|
||||
{
|
||||
for (const auto& profile : _allProfiles)
|
||||
{
|
||||
std::set<std::wstring, til::wstring_case_insensitive_compare> envVarNames{};
|
||||
if (profile.EnvironmentVariables() == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (const auto [key, value] : profile.EnvironmentVariables())
|
||||
{
|
||||
const auto iterator = envVarNames.insert(key.c_str());
|
||||
if (!iterator.second)
|
||||
{
|
||||
_warnings.Append(SettingsLoadWarnings::InvalidProfileEnvironmentVariables);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper to get the GUID of a profile, given an optional index and a possible
|
||||
// "profile" value to override that.
|
||||
|
|
|
@ -162,6 +162,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
void _validateSettings();
|
||||
void _validateAllSchemesExist();
|
||||
void _validateMediaResources();
|
||||
void _validateProfileEnvironmentVariables();
|
||||
void _validateKeybindings() const;
|
||||
void _validateColorSchemesInCommands() const;
|
||||
bool _hasInvalidColorScheme(const Model::Command& command) const;
|
||||
|
|
|
@ -82,6 +82,7 @@ Author(s):
|
|||
X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \
|
||||
X(hstring, TabTitle, "tabTitle") \
|
||||
X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \
|
||||
X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \
|
||||
X(bool, UseAtlasEngine, "useAtlasEngine", Feature_AtlasEngine::IsEnabled()) \
|
||||
X(bool, RightClickContextMenu, "experimental.rightClickContextMenu", false) \
|
||||
X(Windows::Foundation::Collections::IVector<winrt::hstring>, BellSound, "bellSound", nullptr) \
|
||||
|
|
|
@ -67,6 +67,8 @@ namespace TerminalAppUnitTests
|
|||
class JsonTests;
|
||||
};
|
||||
|
||||
using IEnvironmentVariableMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>;
|
||||
|
||||
// GUID used for generating GUIDs at runtime, for profiles that did not have a
|
||||
// GUID specified manually.
|
||||
constexpr GUID RUNTIME_GENERATED_PROFILE_NAMESPACE_GUID = { 0xf65ddb7e, 0x706b, 0x4499, { 0x8a, 0x50, 0x40, 0x31, 0x3c, 0xaf, 0x51, 0x0a } };
|
||||
|
|
|
@ -9,6 +9,8 @@ import "FontConfig.idl";
|
|||
_BASE_INHERITABLE_SETTING(Type, Name); \
|
||||
Microsoft.Terminal.Settings.Model.Profile Name##OverrideSource { get; }
|
||||
|
||||
#define COMMA ,
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Model
|
||||
{
|
||||
// This tag is used to identify the context in which the Profile was created
|
||||
|
@ -81,6 +83,9 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
INHERITABLE_PROFILE_SETTING(Boolean, SnapOnInput);
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, AltGrAliasing);
|
||||
INHERITABLE_PROFILE_SETTING(BellStyle, BellStyle);
|
||||
|
||||
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IMap<String COMMA String>, EnvironmentVariables);
|
||||
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, UseAtlasEngine);
|
||||
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IVector<String>, BellSound);
|
||||
|
||||
|
|
|
@ -306,6 +306,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
_TabColor = static_cast<winrt::Microsoft::Terminal::Core::Color>(colorRef);
|
||||
}
|
||||
|
||||
const auto profileEnvVars = profile.EnvironmentVariables();
|
||||
if (profileEnvVars == nullptr)
|
||||
{
|
||||
_EnvironmentVariables = std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
_EnvironmentVariables = winrt::single_threaded_map<winrt::hstring, winrt::hstring>();
|
||||
for (const auto& [key, value] : profileEnvVars)
|
||||
{
|
||||
_EnvironmentVariables.value().Insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
_Elevate = profile.Elevate();
|
||||
_AutoMarkPrompts = Feature_ScrollbarMarks::IsEnabled() && profile.AutoMarkPrompts();
|
||||
_ShowMarks = Feature_ScrollbarMarks::IsEnabled() && profile.ShowMarks();
|
||||
|
|
|
@ -22,6 +22,7 @@ Author(s):
|
|||
|
||||
using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
|
||||
using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
|
||||
using IEnvironmentVariableMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>;
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace SettingsModelLocalTests
|
||||
|
@ -143,7 +144,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
INHERITABLE_SETTING(Model::TerminalSettings, hstring, StartingDirectory);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, hstring, StartingTitle);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, bool, SuppressApplicationTitle);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, hstring, EnvironmentVariables);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, IEnvironmentVariableMap, EnvironmentVariables);
|
||||
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::ScrollbarState, ScrollState, Microsoft::Terminal::Control::ScrollbarState::Visible);
|
||||
INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, false);
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
import "CascadiaSettings.idl";
|
||||
|
||||
#define COMMA ,
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Model
|
||||
{
|
||||
runtimeclass TerminalSettingsCreateResult
|
||||
|
@ -26,6 +28,8 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
{
|
||||
TerminalSettings();
|
||||
|
||||
Windows.Foundation.Collections.IMap<String COMMA String> EnvironmentVariables;
|
||||
|
||||
static TerminalSettings CreateForPreview(CascadiaSettings appSettings, Profile profile);
|
||||
static TerminalSettingsCreateResult CreateWithProfile(CascadiaSettings appSettings, Profile profile, Microsoft.Terminal.Control.IKeyBindings keybindings);
|
||||
static TerminalSettingsCreateResult CreateWithNewTerminalArgs(CascadiaSettings appSettings, NewTerminalArgs newTerminalArgs, Microsoft.Terminal.Control.IKeyBindings keybindings);
|
||||
|
@ -39,7 +43,6 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
// able to change these at runtime (e.g. when duplicating a pane).
|
||||
String Commandline { set; };
|
||||
String StartingDirectory { set; };
|
||||
String EnvironmentVariables { set; };
|
||||
|
||||
Boolean Elevate;
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
InvalidColorSchemeInCmd,
|
||||
InvalidSplitSize,
|
||||
FailedToParseStartupActions,
|
||||
InvalidProfileEnvironmentVariables,
|
||||
FailedToParseSubCommands,
|
||||
UnknownTheme,
|
||||
DuplicateRemainingProfilesEntry,
|
||||
|
|
|
@ -66,7 +66,6 @@
|
|||
X(winrt::Microsoft::Terminal::Control::IKeyBindings, KeyBindings, nullptr) \
|
||||
X(winrt::hstring, Commandline) \
|
||||
X(winrt::hstring, StartingDirectory) \
|
||||
X(winrt::hstring, EnvironmentVariables) \
|
||||
X(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible) \
|
||||
X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \
|
||||
X(bool, ForceFullRepaintRendering, false) \
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <wil/token_helpers.h>
|
||||
#include <winternl.h>
|
||||
#include <til/string.h>
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26429) // Symbol '...' is never tested for nullness, it can be marked as not_null (f.23).
|
||||
|
@ -21,37 +22,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
namespace details
|
||||
{
|
||||
|
||||
//
|
||||
// A case-insensitive wide-character map is used to store environment variables
|
||||
// due to documented requirements:
|
||||
//
|
||||
// "All strings in the environment block must be sorted alphabetically by name.
|
||||
// The sort is case-insensitive, Unicode order, without regard to locale.
|
||||
// Because the equal sign is a separator, it must not be used in the name of
|
||||
// an environment variable."
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/ProcThread/changing-environment-variables
|
||||
//
|
||||
// - Returns CSTR_LESS_THAN, CSTR_EQUAL or CSTR_GREATER_THAN
|
||||
[[nodiscard]] inline int compare_string_ordinal(const std::wstring_view& lhs, const std::wstring_view& rhs) noexcept
|
||||
{
|
||||
const auto result = CompareStringOrdinal(
|
||||
lhs.data(),
|
||||
::base::saturated_cast<int>(lhs.size()),
|
||||
rhs.data(),
|
||||
::base::saturated_cast<int>(rhs.size()),
|
||||
TRUE);
|
||||
FAIL_FAST_LAST_ERROR_IF(!result);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct wstring_case_insensitive_compare
|
||||
{
|
||||
[[nodiscard]] bool operator()(const std::wstring& lhs, const std::wstring& rhs) const noexcept
|
||||
{
|
||||
return compare_string_ordinal(lhs, rhs) == CSTR_LESS_THAN;
|
||||
}
|
||||
};
|
||||
|
||||
namespace vars
|
||||
{
|
||||
inline constexpr wil::zwstring_view system_root{ L"SystemRoot" };
|
||||
|
@ -250,7 +220,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
friend class ::EnvTests;
|
||||
#endif
|
||||
|
||||
std::map<std::wstring, std::wstring, til::details::wstring_case_insensitive_compare> _envMap{};
|
||||
std::map<std::wstring, std::wstring, til::wstring_case_insensitive_compare> _envMap{};
|
||||
|
||||
// We make copies of the environment variable names to ensure they are null terminated.
|
||||
void get(wil::zwstring_view variable)
|
||||
|
@ -438,13 +408,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
return expanded;
|
||||
}
|
||||
|
||||
void set_user_environment_var(std::wstring_view var, std::wstring_view value)
|
||||
{
|
||||
auto valueString = expand_environment_strings(value);
|
||||
valueString = check_for_temp(var, valueString);
|
||||
save_to_map(std::wstring{ var }, std::move(valueString));
|
||||
}
|
||||
|
||||
void concat_var(std::wstring var, std::wstring value)
|
||||
{
|
||||
if (const auto existing = _envMap.find(var); existing != _envMap.end())
|
||||
|
@ -475,8 +438,8 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
{
|
||||
static constexpr std::wstring_view temp{ L"temp" };
|
||||
static constexpr std::wstring_view tmp{ L"tmp" };
|
||||
if (til::details::compare_string_ordinal(var, temp) == CSTR_EQUAL ||
|
||||
til::details::compare_string_ordinal(var, tmp) == CSTR_EQUAL)
|
||||
if (til::compare_string_ordinal(var, temp) == CSTR_EQUAL ||
|
||||
til::compare_string_ordinal(var, tmp) == CSTR_EQUAL)
|
||||
{
|
||||
return til::details::wil_env::GetShortPathNameW<std::wstring, 256>(value.data());
|
||||
}
|
||||
|
@ -491,9 +454,9 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
static constexpr std::wstring_view path{ L"Path" };
|
||||
static constexpr std::wstring_view libPath{ L"LibPath" };
|
||||
static constexpr std::wstring_view os2LibPath{ L"Os2LibPath" };
|
||||
return til::details::compare_string_ordinal(input, path) == CSTR_EQUAL ||
|
||||
til::details::compare_string_ordinal(input, libPath) == CSTR_EQUAL ||
|
||||
til::details::compare_string_ordinal(input, os2LibPath) == CSTR_EQUAL;
|
||||
return til::compare_string_ordinal(input, path) == CSTR_EQUAL ||
|
||||
til::compare_string_ordinal(input, libPath) == CSTR_EQUAL ||
|
||||
til::compare_string_ordinal(input, os2LibPath) == CSTR_EQUAL;
|
||||
}
|
||||
|
||||
static void strip_trailing_null(std::wstring& str) noexcept
|
||||
|
@ -533,6 +496,35 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
parse(block);
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Creates a new environment with the current process's unicode environment
|
||||
// variables.
|
||||
// Return Value:
|
||||
// - A new environment
|
||||
static til::env from_current_environment()
|
||||
{
|
||||
LPWCH currentEnvVars{};
|
||||
auto freeCurrentEnv = wil::scope_exit([&] {
|
||||
if (currentEnvVars)
|
||||
{
|
||||
FreeEnvironmentStringsW(currentEnvVars);
|
||||
currentEnvVars = nullptr;
|
||||
}
|
||||
});
|
||||
|
||||
currentEnvVars = ::GetEnvironmentStringsW();
|
||||
THROW_HR_IF_NULL(E_OUTOFMEMORY, currentEnvVars);
|
||||
|
||||
return til::env{ currentEnvVars };
|
||||
}
|
||||
|
||||
void set_user_environment_var(std::wstring_view var, std::wstring_view value)
|
||||
{
|
||||
auto valueString = expand_environment_strings(value);
|
||||
valueString = check_for_temp(var, valueString);
|
||||
save_to_map(std::wstring{ var }, std::move(valueString));
|
||||
}
|
||||
|
||||
void regenerate()
|
||||
{
|
||||
// Generally replicates the behavior of shell32!RegenerateUserEnvironment
|
||||
|
@ -572,6 +564,93 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
return result;
|
||||
}
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
// Can't zero the keys, but at least we can zero the values.
|
||||
for (auto& [name, value] : _envMap)
|
||||
{
|
||||
::SecureZeroMemory(value.data(), value.size() * sizeof(decltype(value.begin())::value_type));
|
||||
}
|
||||
|
||||
_envMap.clear();
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Creates a new environment block using the provided vector as appropriate
|
||||
// (resizing if needed) based on the current environment variable map
|
||||
// matching the format of GetEnvironmentStringsW.
|
||||
// Arguments:
|
||||
// - newEnvVars: The vector that will be used to create the new environment block.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, or an appropriate HRESULT for failing
|
||||
HRESULT to_environment_strings_w(std::vector<wchar_t>& newEnvVars)
|
||||
try
|
||||
{
|
||||
// Clear environment block before use.
|
||||
constexpr auto cbChar{ sizeof(decltype(newEnvVars.begin())::value_type) };
|
||||
|
||||
if (!newEnvVars.empty())
|
||||
{
|
||||
::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar);
|
||||
}
|
||||
|
||||
// Resize environment block to fit map.
|
||||
size_t cchEnv{ 2 }; // For the block's double NULL-terminator.
|
||||
for (const auto& [name, value] : _envMap)
|
||||
{
|
||||
// Final form of "name=value\0".
|
||||
cchEnv += name.size() + 1 + value.size() + 1;
|
||||
}
|
||||
newEnvVars.resize(cchEnv);
|
||||
|
||||
// Ensure new block is wiped if we exit due to failure.
|
||||
auto zeroNewEnv = wil::scope_exit([&]() noexcept {
|
||||
::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar);
|
||||
});
|
||||
|
||||
// Transform each map entry and copy it into the new environment block.
|
||||
auto pEnvVars{ newEnvVars.data() };
|
||||
auto cbRemaining{ cchEnv * cbChar };
|
||||
for (const auto& [name, value] : _envMap)
|
||||
{
|
||||
// Final form of "name=value\0" for every entry.
|
||||
{
|
||||
const auto cchSrc{ name.size() };
|
||||
const auto cbSrc{ cchSrc * cbChar };
|
||||
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, name.c_str(), cbSrc) != 0);
|
||||
pEnvVars += cchSrc;
|
||||
cbRemaining -= cbSrc;
|
||||
}
|
||||
|
||||
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"=", cbChar) != 0);
|
||||
++pEnvVars;
|
||||
cbRemaining -= cbChar;
|
||||
|
||||
{
|
||||
const auto cchSrc{ value.size() };
|
||||
const auto cbSrc{ cchSrc * cbChar };
|
||||
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, value.c_str(), cbSrc) != 0);
|
||||
pEnvVars += cchSrc;
|
||||
cbRemaining -= cbSrc;
|
||||
}
|
||||
|
||||
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0", cbChar) != 0);
|
||||
++pEnvVars;
|
||||
cbRemaining -= cbChar;
|
||||
}
|
||||
|
||||
// Environment block only has to be NULL-terminated, but double NULL-terminate anyway.
|
||||
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0\0", cbChar * 2) != 0);
|
||||
cbRemaining -= cbChar * 2;
|
||||
|
||||
RETURN_HR_IF(E_UNEXPECTED, cbRemaining != 0);
|
||||
|
||||
zeroNewEnv.release(); // success; don't wipe new environment block on exit
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
auto& as_map() noexcept
|
||||
{
|
||||
return _envMap;
|
||||
|
|
|
@ -342,4 +342,35 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
{
|
||||
return prefix_split<>(str, needle);
|
||||
}
|
||||
|
||||
//
|
||||
// A case-insensitive wide-character map is used to store environment variables
|
||||
// due to documented requirements:
|
||||
//
|
||||
// "All strings in the environment block must be sorted alphabetically by name.
|
||||
// The sort is case-insensitive, Unicode order, without regard to locale.
|
||||
// Because the equal sign is a separator, it must not be used in the name of
|
||||
// an environment variable."
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/ProcThread/changing-environment-variables
|
||||
//
|
||||
// - Returns CSTR_LESS_THAN, CSTR_EQUAL or CSTR_GREATER_THAN
|
||||
[[nodiscard]] inline int compare_string_ordinal(const std::wstring_view& lhs, const std::wstring_view& rhs) noexcept
|
||||
{
|
||||
const auto result = CompareStringOrdinal(
|
||||
lhs.data(),
|
||||
::base::saturated_cast<int>(lhs.size()),
|
||||
rhs.data(),
|
||||
::base::saturated_cast<int>(rhs.size()),
|
||||
TRUE);
|
||||
FAIL_FAST_LAST_ERROR_IF(!result);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct wstring_case_insensitive_compare
|
||||
{
|
||||
[[nodiscard]] bool operator()(const std::wstring& lhs, const std::wstring& rhs) const noexcept
|
||||
{
|
||||
return compare_string_ordinal(lhs, rhs) == CSTR_LESS_THAN;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "inc/Environment.hpp"
|
||||
|
||||
using namespace ::Microsoft::Console::Utils;
|
||||
|
||||
// We cannot use spand or not_null because we're dealing with \0\0-terminated buffers of unknown length
|
||||
#pragma warning(disable : 26481 26429)
|
||||
|
||||
// Function Description:
|
||||
// - Updates an EnvironmentVariableMapW with the current process's unicode
|
||||
// environment variables ignoring ones already set in the provided map.
|
||||
// Arguments:
|
||||
// - map: The map to populate with the current processes's environment variables.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, or an appropriate HRESULT for failing
|
||||
HRESULT Microsoft::Console::Utils::UpdateEnvironmentMapW(EnvironmentVariableMapW& map) noexcept
|
||||
try
|
||||
{
|
||||
LPWCH currentEnvVars{};
|
||||
auto freeCurrentEnv = wil::scope_exit([&] {
|
||||
if (currentEnvVars)
|
||||
{
|
||||
FreeEnvironmentStringsW(currentEnvVars);
|
||||
currentEnvVars = nullptr;
|
||||
}
|
||||
});
|
||||
|
||||
currentEnvVars = ::GetEnvironmentStringsW();
|
||||
RETURN_HR_IF_NULL(E_OUTOFMEMORY, currentEnvVars);
|
||||
|
||||
// Each entry is NULL-terminated; block is guaranteed to be double-NULL terminated at a minimum.
|
||||
for (const wchar_t* lastCh{ currentEnvVars }; *lastCh != '\0'; ++lastCh)
|
||||
{
|
||||
// Copy current entry into temporary map.
|
||||
const auto cchEntry{ ::wcslen(lastCh) };
|
||||
const std::wstring_view entry{ lastCh, cchEntry };
|
||||
|
||||
// Every entry is of the form "name=value\0".
|
||||
const auto pos = entry.find_first_of(L"=", 0, 1);
|
||||
RETURN_HR_IF(E_UNEXPECTED, pos == std::wstring::npos);
|
||||
|
||||
std::wstring name{ entry.substr(0, pos) }; // portion before '='
|
||||
std::wstring value{ entry.substr(pos + 1) }; // portion after '='
|
||||
|
||||
// Don't replace entries that already exist.
|
||||
map.try_emplace(std::move(name), std::move(value));
|
||||
lastCh += cchEntry;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Function Description:
|
||||
// - Creates a new environment block using the provided vector as appropriate
|
||||
// (resizing if needed) based on the provided environment variable map
|
||||
// matching the format of GetEnvironmentStringsW.
|
||||
// Arguments:
|
||||
// - map: The map to populate the new environment block vector with.
|
||||
// - newEnvVars: The vector that will be used to create the new environment block.
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, or an appropriate HRESULT for failing
|
||||
HRESULT Microsoft::Console::Utils::EnvironmentMapToEnvironmentStringsW(EnvironmentVariableMapW& map, std::vector<wchar_t>& newEnvVars) noexcept
|
||||
try
|
||||
{
|
||||
// Clear environment block before use.
|
||||
constexpr auto cbChar{ sizeof(decltype(newEnvVars.begin())::value_type) };
|
||||
|
||||
if (!newEnvVars.empty())
|
||||
{
|
||||
::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar);
|
||||
}
|
||||
|
||||
// Resize environment block to fit map.
|
||||
size_t cchEnv{ 2 }; // For the block's double NULL-terminator.
|
||||
for (const auto& [name, value] : map)
|
||||
{
|
||||
// Final form of "name=value\0".
|
||||
cchEnv += name.size() + 1 + value.size() + 1;
|
||||
}
|
||||
newEnvVars.resize(cchEnv);
|
||||
|
||||
// Ensure new block is wiped if we exit due to failure.
|
||||
auto zeroNewEnv = wil::scope_exit([&]() noexcept {
|
||||
::SecureZeroMemory(newEnvVars.data(), newEnvVars.size() * cbChar);
|
||||
});
|
||||
|
||||
// Transform each map entry and copy it into the new environment block.
|
||||
auto pEnvVars{ newEnvVars.data() };
|
||||
auto cbRemaining{ cchEnv * cbChar };
|
||||
for (const auto& [name, value] : map)
|
||||
{
|
||||
// Final form of "name=value\0" for every entry.
|
||||
{
|
||||
const auto cchSrc{ name.size() };
|
||||
const auto cbSrc{ cchSrc * cbChar };
|
||||
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, name.c_str(), cbSrc) != 0);
|
||||
pEnvVars += cchSrc;
|
||||
cbRemaining -= cbSrc;
|
||||
}
|
||||
|
||||
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"=", cbChar) != 0);
|
||||
++pEnvVars;
|
||||
cbRemaining -= cbChar;
|
||||
|
||||
{
|
||||
const auto cchSrc{ value.size() };
|
||||
const auto cbSrc{ cchSrc * cbChar };
|
||||
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, value.c_str(), cbSrc) != 0);
|
||||
pEnvVars += cchSrc;
|
||||
cbRemaining -= cbSrc;
|
||||
}
|
||||
|
||||
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0", cbChar) != 0);
|
||||
++pEnvVars;
|
||||
cbRemaining -= cbChar;
|
||||
}
|
||||
|
||||
// Environment block only has to be NULL-terminated, but double NULL-terminate anyway.
|
||||
RETURN_HR_IF(E_OUTOFMEMORY, memcpy_s(pEnvVars, cbRemaining, L"\0\0", cbChar * 2) != 0);
|
||||
cbRemaining -= cbChar * 2;
|
||||
|
||||
RETURN_HR_IF(E_UNEXPECTED, cbRemaining != 0);
|
||||
|
||||
zeroNewEnv.release(); // success; don't wipe new environment block on exit
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
|
@ -1,31 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft::Console::Utils
|
||||
{
|
||||
//
|
||||
// A case-insensitive wide-character map is used to store environment variables
|
||||
// due to documented requirements:
|
||||
//
|
||||
// "All strings in the environment block must be sorted alphabetically by name.
|
||||
// The sort is case-insensitive, Unicode order, without regard to locale.
|
||||
// Because the equal sign is a separator, it must not be used in the name of
|
||||
// an environment variable."
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/ProcThread/changing-environment-variables
|
||||
//
|
||||
struct WStringCaseInsensitiveCompare
|
||||
{
|
||||
[[nodiscard]] bool operator()(const std::wstring& lhs, const std::wstring& rhs) const noexcept
|
||||
{
|
||||
return (::_wcsicmp(lhs.c_str(), rhs.c_str()) < 0);
|
||||
}
|
||||
};
|
||||
|
||||
using EnvironmentVariableMapW = std::map<std::wstring, std::wstring, WStringCaseInsensitiveCompare>;
|
||||
|
||||
[[nodiscard]] HRESULT UpdateEnvironmentMapW(EnvironmentVariableMapW& map) noexcept;
|
||||
|
||||
[[nodiscard]] HRESULT EnvironmentMapToEnvironmentStringsW(EnvironmentVariableMapW& map,
|
||||
std::vector<wchar_t>& newEnvVars) noexcept;
|
||||
|
||||
};
|
|
@ -15,7 +15,6 @@
|
|||
<ClCompile Include="..\ColorFix.cpp" />
|
||||
<ClCompile Include="..\convert.cpp" />
|
||||
<ClCompile Include="..\colorTable.cpp" />
|
||||
<ClCompile Include="..\Environment.cpp" />
|
||||
<ClCompile Include="..\GlyphWidth.cpp" />
|
||||
<ClCompile Include="..\MouseEvent.cpp" />
|
||||
<ClCompile Include="..\FocusEvent.cpp" />
|
||||
|
@ -43,7 +42,6 @@
|
|||
<ClInclude Include="..\inc\ColorFix.hpp" />
|
||||
<ClInclude Include="..\inc\convert.hpp" />
|
||||
<ClInclude Include="..\inc\colorTable.hpp" />
|
||||
<ClInclude Include="..\inc\Environment.hpp" />
|
||||
<ClInclude Include="..\inc\GlyphWidth.hpp" />
|
||||
<ClInclude Include="..\inc\IInputEvent.hpp" />
|
||||
<ClInclude Include="..\inc\sgrStack.hpp" />
|
||||
|
|
Loading…
Reference in New Issue