mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-17 15:36:35 +00:00
Merge remote-tracking branch 'origin/main' into dev/migrie/b/9053-part-3-the-actual-doing-of-the-thing
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" />
|
||||
<package id="Microsoft.Internal.Windows.Terminal.ThemeHelpers" version="0.6.220404001" targetFramework="native" />
|
||||
<package id="Microsoft.VisualStudio.Setup.Configuration.Native" version="2.3.2262" targetFramework="native" developmentDependency="true" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.8.2" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.8.3" targetFramework="native" />
|
||||
<package id="Microsoft.Web.WebView2" version="1.0.1661.34" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220201.1" targetFramework="native" developmentDependency="true" />
|
||||
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -272,10 +272,6 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
|
||||
// - false otherwise (out of memory)
|
||||
bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute)
|
||||
{
|
||||
// This function corrects most errors. If this is false, we had an uncorrectable one which
|
||||
// older versions of conhost simply let pass by unflinching.
|
||||
LOG_HR_IF(E_NOT_VALID_STATE, !(_AssertValidDoubleByteSequence(dbcsAttribute))); // Shouldn't be uncorrectable sequences unless something is very wrong.
|
||||
|
||||
auto fSuccess = true;
|
||||
// Now compensate if we don't have enough space for the upcoming double byte sequence
|
||||
// We only need to compensate for leading bytes
|
||||
|
||||
@@ -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"(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "pch.h"
|
||||
#include "App.h"
|
||||
#include "App.g.cpp"
|
||||
#include <CoreWindow.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::ApplicationModel::Activation;
|
||||
@@ -32,6 +33,27 @@ namespace winrt::TerminalApp::implementation
|
||||
if (!dispatcherQueue)
|
||||
{
|
||||
_windowsXamlManager = xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread();
|
||||
|
||||
// As of Process Model v3, terminal windows are all created on their
|
||||
// own threads, but we still initiate XAML for the App on the main
|
||||
// thread. Thing is, just initializing XAML creates a CoreWindow for
|
||||
// us. On Windows 10, that CoreWindow will show up as a visible
|
||||
// window on the taskbar, unless we hide it manually. So, go get it
|
||||
// and do the SW_HIDE thing on it.
|
||||
if (const auto& coreWindow{ winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread() })
|
||||
{
|
||||
if (const auto& interop{ coreWindow.try_as<ICoreWindowInterop>() })
|
||||
{
|
||||
HWND coreHandle{ 0 };
|
||||
interop->get_WindowHandle(&coreHandle);
|
||||
if (coreHandle)
|
||||
{
|
||||
// This prevents an empty "DesktopWindowXamlSource" from
|
||||
// appearing on the taskbar
|
||||
ShowWindow(coreHandle, SW_HIDE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -37,37 +37,10 @@ Pane::LayoutSizeNode& Pane::LayoutSizeNode::operator=(const LayoutSizeNode& othe
|
||||
size = other.size;
|
||||
isMinimumSize = other.isMinimumSize;
|
||||
|
||||
_AssignChildNode(firstChild, other.firstChild.get());
|
||||
_AssignChildNode(secondChild, other.secondChild.get());
|
||||
_AssignChildNode(nextFirstChild, other.nextFirstChild.get());
|
||||
_AssignChildNode(nextSecondChild, other.nextSecondChild.get());
|
||||
firstChild = other.firstChild ? std::make_unique<LayoutSizeNode>(*other.firstChild) : nullptr;
|
||||
secondChild = other.secondChild ? std::make_unique<LayoutSizeNode>(*other.secondChild) : nullptr;
|
||||
nextFirstChild = other.nextFirstChild ? std::make_unique<LayoutSizeNode>(*other.nextFirstChild) : nullptr;
|
||||
nextSecondChild = other.nextSecondChild ? std::make_unique<LayoutSizeNode>(*other.nextSecondChild) : nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Performs assignment operation on a single child node reusing
|
||||
// - current one if present.
|
||||
// Arguments:
|
||||
// - nodeField: Reference to our field holding concerned node.
|
||||
// - other: Node to take the values from.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::LayoutSizeNode::_AssignChildNode(std::unique_ptr<LayoutSizeNode>& nodeField, const LayoutSizeNode* const newNode)
|
||||
{
|
||||
if (newNode)
|
||||
{
|
||||
if (nodeField)
|
||||
{
|
||||
*nodeField = *newNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeField = std::make_unique<LayoutSizeNode>(*newNode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeField.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,9 +394,6 @@ private:
|
||||
LayoutSizeNode(const LayoutSizeNode& other);
|
||||
|
||||
LayoutSizeNode& operator=(const LayoutSizeNode& other);
|
||||
|
||||
private:
|
||||
void _AssignChildNode(std::unique_ptr<LayoutSizeNode>& nodeField, const LayoutSizeNode* const newNode);
|
||||
};
|
||||
|
||||
friend struct winrt::TerminalApp::implementation::TerminalTab;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -890,34 +890,6 @@ namespace winrt::TerminalApp::implementation
|
||||
co_await _HandleCloseTabRequested(tab);
|
||||
}
|
||||
}
|
||||
// Method Description:
|
||||
// - Responds to changes in the TabView's item list by changing the
|
||||
// tabview's visibility.
|
||||
// - This method is also invoked when tabs are dragged / dropped as part of
|
||||
// tab reordering and this method hands that case as well in concert with
|
||||
// TabDragStarting and TabDragCompleted handlers that are set up in
|
||||
// TerminalPage::Create()
|
||||
// Arguments:
|
||||
// - sender: the control that originated this event
|
||||
// - eventArgs: the event's constituent arguments
|
||||
void TerminalPage::_OnTabItemsChanged(const IInspectable& /*sender*/, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs)
|
||||
{
|
||||
if (_rearranging)
|
||||
{
|
||||
if (eventArgs.CollectionChange() == Windows::Foundation::Collections::CollectionChange::ItemRemoved)
|
||||
{
|
||||
_rearrangeFrom = eventArgs.Index();
|
||||
}
|
||||
|
||||
if (eventArgs.CollectionChange() == Windows::Foundation::Collections::CollectionChange::ItemInserted)
|
||||
{
|
||||
_rearrangeTo = eventArgs.Index();
|
||||
}
|
||||
}
|
||||
|
||||
CommandPalette().Visibility(Visibility::Collapsed);
|
||||
_UpdateTabView();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Additional responses to clicking on a TabView's item. Currently, just remove tab with middle click
|
||||
@@ -1079,16 +1051,37 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
|
||||
void TerminalPage::_TabDragStarted(const IInspectable& /*sender*/,
|
||||
const IInspectable& /*eventArgs*/)
|
||||
const winrt::MUX::Controls::TabViewTabDragStartingEventArgs& eventArgs)
|
||||
{
|
||||
_rearranging = true;
|
||||
_rearrangeFrom = std::nullopt;
|
||||
_rearrangeTo = std::nullopt;
|
||||
|
||||
// Start tracking the index of the tab that is being dragged. In
|
||||
// `_TabDragCompleted`, we'll use this to determine how to reorder our
|
||||
// internal tabs list.
|
||||
const auto& draggedTabViewItem{ eventArgs.Tab() };
|
||||
uint32_t tabIndexFromControl{};
|
||||
const auto tabItems{ _tabView.TabItems() };
|
||||
if (tabItems.IndexOf(draggedTabViewItem, tabIndexFromControl))
|
||||
{
|
||||
// If IndexOf returns true, we've actually got an index
|
||||
_rearrangeFrom = tabIndexFromControl;
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_TabDragCompleted(const IInspectable& /*sender*/,
|
||||
const IInspectable& /*eventArgs*/)
|
||||
const winrt::MUX::Controls::TabViewTabDragCompletedEventArgs& eventArgs)
|
||||
{
|
||||
const auto& draggedTabViewItem{ eventArgs.Tab() };
|
||||
|
||||
uint32_t tabIndexFromControl{};
|
||||
const auto tabItems{ _tabView.TabItems() };
|
||||
if (tabItems.IndexOf(draggedTabViewItem, tabIndexFromControl))
|
||||
{
|
||||
_rearrangeTo = tabIndexFromControl;
|
||||
}
|
||||
|
||||
auto& from{ _rearrangeFrom };
|
||||
auto& to{ _rearrangeTo };
|
||||
|
||||
|
||||
@@ -245,7 +245,6 @@ namespace winrt::TerminalApp::implementation
|
||||
});
|
||||
_tabView.SelectionChanged({ this, &TerminalPage::_OnTabSelectionChanged });
|
||||
_tabView.TabCloseRequested({ this, &TerminalPage::_OnTabCloseRequested });
|
||||
_tabView.TabItemsChanged({ this, &TerminalPage::_OnTabItemsChanged });
|
||||
|
||||
_tabView.TabDragStarting({ this, &TerminalPage::_onTabDragStarting });
|
||||
_tabView.TabStripDragOver({ this, &TerminalPage::_onTabStripDragOver });
|
||||
@@ -1216,7 +1215,8 @@ namespace winrt::TerminalApp::implementation
|
||||
nullptr,
|
||||
settings.InitialRows(),
|
||||
settings.InitialCols(),
|
||||
winrt::guid());
|
||||
winrt::guid(),
|
||||
profile.Guid());
|
||||
|
||||
if constexpr (Feature_VtPassthroughMode::IsEnabled())
|
||||
{
|
||||
@@ -1228,12 +1228,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 +1261,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",
|
||||
@@ -4721,6 +4719,8 @@ namespace winrt::TerminalApp::implementation
|
||||
co_await wil::resume_foreground(Dispatcher());
|
||||
if (const auto& page{ weakThis.get() })
|
||||
{
|
||||
// `this` is safe to use
|
||||
//
|
||||
// First we need to get the position in the List to drop to
|
||||
auto index = -1;
|
||||
|
||||
@@ -4740,8 +4740,8 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// `this` is safe to use
|
||||
const auto request = winrt::make_self<RequestReceiveContentArgs>(src, _WindowProperties.WindowId(), index);
|
||||
const auto myId{ _WindowProperties.WindowId() };
|
||||
const auto request = winrt::make_self<RequestReceiveContentArgs>(src, myId, index);
|
||||
|
||||
// This will go up to the monarch, who will then dispatch the request
|
||||
// back down to the source TerminalPage, who will then perform a
|
||||
|
||||
@@ -412,12 +412,11 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
fire_and_forget _LaunchSettings(const Microsoft::Terminal::Settings::Model::SettingsTarget target);
|
||||
|
||||
void _TabDragStarted(const IInspectable& sender, const IInspectable& eventArgs);
|
||||
void _TabDragCompleted(const IInspectable& sender, const IInspectable& eventArgs);
|
||||
void _TabDragStarted(const IInspectable& sender, const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs& eventArgs);
|
||||
void _TabDragCompleted(const IInspectable& sender, const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragCompletedEventArgs& eventArgs);
|
||||
|
||||
void _OnTabClick(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
|
||||
void _OnTabSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& eventArgs);
|
||||
void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs);
|
||||
void _OnTabCloseRequested(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabCloseRequestedEventArgs& eventArgs);
|
||||
void _OnFirstLayout(const IInspectable& sender, const IInspectable& eventArgs);
|
||||
void _UpdatedSelectedTab(const winrt::TerminalApp::TabBase& tab);
|
||||
|
||||
@@ -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"),
|
||||
@@ -739,9 +740,19 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// If
|
||||
// * the position has been specified on the commandline,
|
||||
// * we're re-opening from a persisted layout,
|
||||
// * We're opening the window as a part of tear out (and _contentBounds were set)
|
||||
// then don't center on launch
|
||||
return !_contentBounds && _settings.GlobalSettings().CenterOnLaunch() && !_appArgs.GetPosition().has_value();
|
||||
bool hadPersistedPosition = false;
|
||||
if (const auto layout = LoadPersistedLayout())
|
||||
{
|
||||
hadPersistedPosition = (bool)layout.InitialPosition();
|
||||
}
|
||||
|
||||
return !_contentBounds &&
|
||||
!hadPersistedPosition &&
|
||||
_settings.GlobalSettings().CenterOnLaunch() &&
|
||||
!_appArgs.GetPosition().has_value();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -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; };
|
||||
|
||||
|
||||
@@ -666,7 +666,7 @@ std::optional<PointTree::interval> Terminal::GetHyperlinkIntervalFromViewportPos
|
||||
// - Character events (e.g. WM_CHAR) are generally the best way to properly receive
|
||||
// keyboard input on Windows though, as the OS is suited best at handling the
|
||||
// translation of the current keyboard layout, dead keys, etc.
|
||||
// As a result of this false is returned for all key events that contain characters.
|
||||
// As a result of this false is returned for all key down events that contain characters.
|
||||
// SendCharEvent may then be called with the data obtained from a character event.
|
||||
// - As a special case we'll always handle VK_TAB key events.
|
||||
// This must be done due to TermControl::_KeyDownHandler (one of the callers)
|
||||
@@ -728,15 +728,15 @@ bool Terminal::SendKeyEvent(const WORD vkey,
|
||||
const auto isSuppressedAltGrAlias = !_altGrAliasing && states.IsAltPressed() && states.IsCtrlPressed() && !states.IsAltGrPressed();
|
||||
const auto ch = isSuppressedAltGrAlias ? UNICODE_NULL : _CharacterFromKeyEvent(vkey, sc, states);
|
||||
|
||||
// Delegate it to the character event handler if this key event can be
|
||||
// mapped to one (see method description above). For Alt+key combinations
|
||||
// Delegate it to the character event handler if this is a key down event that
|
||||
// can be mapped to one (see method description above). For Alt+key combinations
|
||||
// we'll not receive another character event for some reason though.
|
||||
// -> Don't delegate the event if this is a Alt+key combination.
|
||||
//
|
||||
// As a special case we'll furthermore always handle VK_TAB
|
||||
// key events here instead of in Terminal::SendCharEvent.
|
||||
// See the method description for more information.
|
||||
if (!isAltOnlyPressed && vkey != VK_TAB && ch != UNICODE_NULL)
|
||||
if (keyDown && !isAltOnlyPressed && vkey != VK_TAB && ch != UNICODE_NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -818,15 +818,8 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro
|
||||
MarkOutputStart();
|
||||
}
|
||||
|
||||
// Unfortunately, the UI doesn't give us both a character down and a
|
||||
// character up event, only a character received event. So fake sending both
|
||||
// to the terminal input translator. Unless it's in win32-input-mode, it'll
|
||||
// ignore the keyup.
|
||||
const KeyEvent keyDown{ true, 1, vkey, scanCode, ch, states.Value() };
|
||||
const KeyEvent keyUp{ false, 1, vkey, scanCode, ch, states.Value() };
|
||||
const auto handledDown = _terminalInput->HandleKey(&keyDown);
|
||||
const auto handledUp = _terminalInput->HandleKey(&keyUp);
|
||||
return handledDown || handledUp;
|
||||
return _terminalInput->HandleKey(&keyDown);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -56,6 +56,15 @@ constexpr std::wstring_view legacySystemThemeName{ L"legacySystem" };
|
||||
constexpr std::wstring_view legacyDarkThemeName{ L"legacyDark" };
|
||||
constexpr std::wstring_view legacyLightThemeName{ L"legacyLight" };
|
||||
|
||||
static constexpr std::array builtinThemes{
|
||||
systemThemeName,
|
||||
lightThemeName,
|
||||
darkThemeName,
|
||||
legacySystemThemeName,
|
||||
legacyLightThemeName,
|
||||
legacyDarkThemeName,
|
||||
};
|
||||
|
||||
static constexpr std::wstring_view jsonExtension{ L".json" };
|
||||
static constexpr std::wstring_view FragmentsSubDirectory{ L"\\Fragments" };
|
||||
static constexpr std::wstring_view FragmentsPath{ L"\\Microsoft\\Windows Terminal\\Fragments" };
|
||||
@@ -566,8 +575,9 @@ void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source
|
||||
if (const auto theme = Theme::FromJson(themeJson))
|
||||
{
|
||||
const auto& name{ theme->Name() };
|
||||
|
||||
if (origin != OriginTag::InBox &&
|
||||
(name == systemThemeName || name == lightThemeName || name == darkThemeName || name == legacySystemThemeName || name == legacyDarkThemeName || name == legacyLightThemeName))
|
||||
(std::ranges::find(builtinThemes, name) != builtinThemes.end()))
|
||||
{
|
||||
// If the theme didn't come from the in-box themes, and its
|
||||
// name was one of the reserved names, then just ignore it.
|
||||
@@ -1274,7 +1284,8 @@ Json::Value CascadiaSettings::ToJson() const
|
||||
// Ignore the built in themes, when serializing the themes back out. We
|
||||
// don't want to re-include them in the user settings file.
|
||||
const auto theme{ winrt::get_self<Theme>(entry.Value()) };
|
||||
if (theme->Name() == systemThemeName || theme->Name() == lightThemeName || theme->Name() == darkThemeName)
|
||||
const auto& name{ theme->Name() };
|
||||
if (std::ranges::find(builtinThemes, name) != builtinThemes.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
static constexpr std::wstring_view WslHomeDirectory{ L"~" };
|
||||
static constexpr std::wstring_view DockerDistributionPrefix{ L"docker-desktop" };
|
||||
static constexpr std::wstring_view RancherDistributionPrefix{ L"rancher-desktop" };
|
||||
|
||||
// The WSL entries are structured as such:
|
||||
// HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss
|
||||
@@ -78,9 +79,9 @@ static void namesToProfiles(const std::vector<std::wstring>& names, std::vector<
|
||||
{
|
||||
for (const auto& distName : names)
|
||||
{
|
||||
if (til::starts_with(distName, DockerDistributionPrefix))
|
||||
if (til::starts_with(distName, DockerDistributionPrefix) || til::starts_with(distName, RancherDistributionPrefix))
|
||||
{
|
||||
// Docker for Windows creates some utility distributions to handle Docker commands.
|
||||
// Docker for Windows and Rancher for Windows creates some utility distributions to handle Docker commands.
|
||||
// Pursuant to GH#3556, because they are _not_ user-facing we want to hide them.
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -87,13 +87,6 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
|
||||
_window->SetAutoHideWindow(_windowLogic.AutoHideWindow());
|
||||
|
||||
_window->MakeWindow();
|
||||
|
||||
_GetWindowLayoutRequestedToken = _peasant.GetWindowLayoutRequested([this](auto&&,
|
||||
const Remoting::GetWindowLayoutArgs& args) {
|
||||
// The peasants are running on separate threads, so they'll need to
|
||||
// swap what context they are in to the ui thread to get the actual layout.
|
||||
args.WindowLayoutJsonAsync(_GetWindowLayoutAsync());
|
||||
});
|
||||
}
|
||||
|
||||
AppHost::~AppHost()
|
||||
@@ -390,6 +383,23 @@ void AppHost::Initialize()
|
||||
_revokers.RequestMoveContent = _windowLogic.RequestMoveContent(winrt::auto_revoke, { this, &AppHost::_handleMoveContent });
|
||||
_revokers.RequestReceiveContent = _windowLogic.RequestReceiveContent(winrt::auto_revoke, { this, &AppHost::_handleReceiveContent });
|
||||
_revokers.SendContentRequested = _peasant.SendContentRequested(winrt::auto_revoke, { this, &AppHost::_handleSendContent });
|
||||
|
||||
// Add our GetWindowLayoutRequested handler AFTER the xaml island is
|
||||
// started. Our _GetWindowLayoutAsync handler requires us to be able to work
|
||||
// on our UI thread, which requires that we have a Dispatcher ready for us
|
||||
// to move to. If we set up this callback in the ctor, then it is possible
|
||||
// for there to be a time slice where
|
||||
// * the monarch creates the peasant for us,
|
||||
// * we get constructed (registering the callback)
|
||||
// * then the monarch attempts to query all _peasants_ for their layout,
|
||||
// coming back to ask us even before XAML has been created.
|
||||
_GetWindowLayoutRequestedToken = _peasant.GetWindowLayoutRequested([this](auto&&,
|
||||
const Remoting::GetWindowLayoutArgs& args) {
|
||||
// The peasants are running on separate threads, so they'll need to
|
||||
// swap what context they are in to the ui thread to get the actual layout.
|
||||
args.WindowLayoutJsonAsync(_GetWindowLayoutAsync());
|
||||
});
|
||||
|
||||
// BODGY
|
||||
// On certain builds of Windows, when Terminal is set as the default
|
||||
// it will accumulate an unbounded amount of queued animations while
|
||||
|
||||
@@ -307,6 +307,11 @@ void IslandWindow::Initialize()
|
||||
// stash the child interop handle so we can resize it when the main hwnd is resized
|
||||
interop->get_WindowHandle(&_interopWindowHandle);
|
||||
|
||||
// Immediately hide our XAML island hwnd. On earlier versions of Windows,
|
||||
// this HWND could sometimes appear as an actual window in the taskbar
|
||||
// without this!
|
||||
ShowWindow(_interopWindowHandle, SW_HIDE);
|
||||
|
||||
_rootGrid = winrt::Windows::UI::Xaml::Controls::Grid();
|
||||
_source.Content(_rootGrid);
|
||||
|
||||
|
||||
@@ -94,8 +94,8 @@ protected:
|
||||
|
||||
HWND _interopWindowHandle;
|
||||
|
||||
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _source;
|
||||
winrt::Windows::UI::Xaml::Controls::Grid _rootGrid;
|
||||
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _source; // nulled in ctor
|
||||
winrt::Windows::UI::Xaml::Controls::Grid _rootGrid; // nulled in ctor
|
||||
wil::com_ptr<ITaskbarList3> _taskbar;
|
||||
|
||||
std::function<void(const HWND, const til::rect&)> _pfnCreateCallback;
|
||||
|
||||
@@ -113,10 +113,23 @@ namespace Microsoft.Terminal.Wpf
|
||||
{
|
||||
this.connection.TerminalOutput -= this.Connection_TerminalOutput;
|
||||
}
|
||||
|
||||
this.Connection_TerminalOutput(this, new TerminalOutputEventArgs("\x001bc\x1b]104\x1b\\")); //reset console/clear screen - https://github.com/microsoft/terminal/pull/15062#issuecomment-1505654110
|
||||
var wasNull = this.connection == null;
|
||||
this.connection = value;
|
||||
this.connection.TerminalOutput += this.Connection_TerminalOutput;
|
||||
this.connection.Start();
|
||||
if (this.connection != null)
|
||||
{
|
||||
if (wasNull)
|
||||
{
|
||||
this.Connection_TerminalOutput(this, new TerminalOutputEventArgs("\x1b[?25h")); //show cursor
|
||||
}
|
||||
this.connection.TerminalOutput += this.Connection_TerminalOutput;
|
||||
this.connection.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Connection_TerminalOutput(this, new TerminalOutputEventArgs("\x1b[?25l")); //hide cursor
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -37,6 +37,6 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- WinUI (which depends on WebView2 as of 2.8.0) -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.2\build\native\Microsoft.UI.Xaml.props" Condition="'$(TerminalMUX)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.2\build\native\Microsoft.UI.Xaml.props')" />
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.3\build\native\Microsoft.UI.Xaml.props" Condition="'$(TerminalMUX)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.3\build\native\Microsoft.UI.Xaml.props')" />
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets" Condition="'$(TerminalVisualStudioSetup)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" />
|
||||
|
||||
<!-- WinUI (which depends on WebView2 as of 2.8.0) -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.2\build\native\Microsoft.UI.Xaml.targets" Condition="'$(TerminalMUX)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.2\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.3\build\native\Microsoft.UI.Xaml.targets" Condition="'$(TerminalMUX)' == 'true' and Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.3\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<!--
|
||||
Instead of adding our dependency on WebView2, fake it with another set of rules for XAML.
|
||||
See Microsoft.UI.Xaml.Additional.targets for more info.
|
||||
@@ -88,7 +88,7 @@
|
||||
<Error Condition="'$(TerminalVisualStudioSetup)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets'))" />
|
||||
|
||||
<!-- WinUI (which depends on WebView2 as of 2.8.0) -->
|
||||
<Error Condition="'$(TerminalMUX)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.2\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.2\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="'$(TerminalMUX)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.3\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.UI.Xaml.2.8.3\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="'$(TerminalMUX)' == 'true' AND !Exists('$(MSBuildThisFileDirectory)..\packages\Microsoft.Web.WebView2.1.0.1661.34\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MSBuildThisFileDirectory)..\packages\Microsoft.Web.WebView2.1.0.1661.34\build\native\Microsoft.Web.WebView2.targets'))" />
|
||||
|
||||
<!-- WIL (so widely used that this one does not have a TerminalWIL opt-in property; it is automatic) -->
|
||||
|
||||
@@ -237,11 +237,12 @@ MODULE_SETUP(ModuleSetup)
|
||||
// to the one that belongs to the CMD.exe in the new OpenConsole.exe window.
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(FreeConsole());
|
||||
|
||||
// Wait a moment for the driver to be ready after freeing to attach.
|
||||
VERIFY_WIN32_BOOL_SUCCEEDED_RETURN(AttachConsole(dwFindPid));
|
||||
|
||||
auto tries = 0;
|
||||
while (tries < 5)
|
||||
int tries = 0;
|
||||
DWORD delay;
|
||||
// This will wait for up to 32s in total (from 10ms to 163840ms)
|
||||
for (delay = 10; delay < 30000u; delay *= 2)
|
||||
{
|
||||
tries++;
|
||||
Log::Comment(NoThrowString().Format(L"Attempt #%d to confirm we've attached", tries));
|
||||
@@ -267,17 +268,20 @@ MODULE_SETUP(ModuleSetup)
|
||||
auto succeeded = GetConsoleScreenBufferInfoEx(hOut, &csbiexBefore);
|
||||
if (!succeeded)
|
||||
{
|
||||
auto gle = GetLastError();
|
||||
const auto gle = GetLastError();
|
||||
VERIFY_ARE_EQUAL(6u, gle, L"If we fail to set up the console, GetLastError should return 6 here.");
|
||||
Sleep(1000);
|
||||
|
||||
// Sleep with a backoff, to give us longer to try next time.
|
||||
WaitForSingleObject(GetCurrentThread(), delay);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"Succeeded on try #%d", tries));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
VERIFY_IS_LESS_THAN(tries, 5, L"Make sure we set up the new console in time");
|
||||
VERIFY_IS_LESS_THAN(delay, 30000u, L"Make sure we set up the new console in time");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -139,18 +139,26 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
|
||||
|
||||
if (isWrapping)
|
||||
{
|
||||
// We want to wrap, but we failed to write even a single character into the row.
|
||||
// ROW::Write() returns the lineWidth and leaves stringIterator untouched. To prevent a
|
||||
// deadlock, because stringIterator never advances, we need to throw that glyph away.
|
||||
//
|
||||
// This can happen under two circumstances:
|
||||
// * The glyph is wider than the buffer and can never be inserted in
|
||||
// the first place. There's no good way to detect this, so we check
|
||||
// whether the begin column is the left margin, which is the column
|
||||
// at which any legit insertion should work at a minimum.
|
||||
// * The DECAWM Autowrap mode is disabled ("\x1b[?7l", !wrapAtEOL) and
|
||||
// we tried writing a wide glyph into the last column which can't work.
|
||||
if (textPositionBefore == textPositionAfter && (state.columnBegin == 0 || !wrapAtEOL))
|
||||
{
|
||||
textBuffer.ConsumeGrapheme(state.text);
|
||||
}
|
||||
|
||||
if (wrapAtEOL)
|
||||
{
|
||||
cursor.DelayEOLWrap();
|
||||
}
|
||||
else if (textPositionBefore == textPositionAfter)
|
||||
{
|
||||
// We want to wrap, but we're not allowed to and we failed to write even a single character into the row.
|
||||
// This can only mean one thing! The DECAWM Autowrap mode is disabled ("\x1b[?7l") and we tried writing a
|
||||
// wide glyph into the last column. ROW::Write() returns the lineWidth and leaves stringIterator untouched.
|
||||
// To prevent a deadlock, because stringIterator never advances, we need to throw that glyph away.
|
||||
textBuffer.ConsumeGrapheme(state.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user