diff --git a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj
index 95d8ea17f..1e00c19c8 100644
--- a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj
+++ b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj
@@ -43,7 +43,8 @@
-
+
+ Create
diff --git a/src/cascadia/LocalTests_TerminalApp/TrustCommandlineTests.cpp b/src/cascadia/LocalTests_TerminalApp/TrustCommandlineTests.cpp
new file mode 100644
index 000000000..cafa368f3
--- /dev/null
+++ b/src/cascadia/LocalTests_TerminalApp/TrustCommandlineTests.cpp
@@ -0,0 +1,123 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+//
+// A series of tests for the TerminalPage::_isTrustedCommandline function.
+// That's a heuristic function for deciding if we should automatically trust
+// certain commandlines by default. The logic in there is weird and there are
+// lots of edge cases, so it's easier to just write a unit test.
+
+#include "pch.h"
+
+#include "../TerminalApp/TerminalPage.h"
+
+using namespace Microsoft::Console;
+using namespace TerminalApp;
+using namespace winrt::TerminalApp;
+using namespace winrt::Microsoft::Terminal::Settings::Model;
+
+using namespace WEX::Logging;
+using namespace WEX::TestExecution;
+using namespace WEX::Common;
+
+using namespace winrt::Windows::ApplicationModel::DataTransfer;
+using namespace winrt::Windows::Foundation::Collections;
+using namespace winrt::Windows::System;
+using namespace winrt::Windows::UI::Xaml;
+using namespace winrt::Windows::UI::Xaml::Controls;
+using namespace winrt::Windows::UI::Core;
+using namespace winrt::Windows::UI::Text;
+
+namespace winrt
+{
+ namespace MUX = Microsoft::UI::Xaml;
+ namespace WUX = Windows::UI::Xaml;
+ using IInspectable = Windows::Foundation::IInspectable;
+}
+
+namespace TerminalAppLocalTests
+{
+ class TrustCommandlineTests
+ {
+ BEGIN_TEST_CLASS(TrustCommandlineTests)
+ END_TEST_CLASS()
+
+ TEST_METHOD(SimpleTests);
+ TEST_METHOD(TestCommandlineWithArgs);
+ TEST_METHOD(TestCommandlineWithSpaces);
+ TEST_METHOD(TestCommandlineWithEnvVars);
+ TEST_METHOD(WslTests);
+ TEST_METHOD(TestPwshLocation);
+
+ bool trust(std::wstring_view cmdline);
+ };
+
+ bool TrustCommandlineTests::trust(std::wstring_view cmdline)
+ {
+ return implementation::TerminalPage::_isTrustedCommandline(cmdline);
+ }
+
+ void TrustCommandlineTests::SimpleTests()
+ {
+ VERIFY_IS_TRUE(trust(L"C:\\Windows\\System32\\cmd.exe"));
+ VERIFY_IS_TRUE(trust(L"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"));
+ VERIFY_IS_FALSE(trust(L"C:\\Windows\\System32\\i-definitely-don't-exist.exe"));
+
+ Log::Comment(L"These are not fully qualified, and _shouldn't_ be trusted");
+ VERIFY_IS_FALSE(trust(L"cmd.exe"));
+ VERIFY_IS_FALSE(trust(L"powershell.exe"));
+ }
+
+ void TrustCommandlineTests::TestCommandlineWithArgs()
+ {
+ Log::Comment(L"These are sneaky things that _shouldn't_ be trusted");
+ VERIFY_IS_FALSE(trust(L"C:\\Windows\\System32\\cmd.exe /k echo Boo!"));
+ VERIFY_IS_FALSE(trust(L"C:\\Windows\\System32\\cmd.exe /k echo Boo! & cmd.exe"));
+ }
+
+ void TrustCommandlineTests::TestCommandlineWithSpaces()
+ {
+ Log::Comment(L"This is a valid place for powershell to live, and the space can be tricky");
+ VERIFY_IS_TRUE(trust(L"C:\\Program Files\\PowerShell\\7\\pwsh.exe"));
+
+ Log::Comment(L"These are sneaky things that _shouldn't_ be trusted");
+ VERIFY_IS_FALSE(trust(L"C:\\Windows\\System 32\\cmd.exe"));
+ VERIFY_IS_FALSE(trust(L"C:\\Windows\\System32\\ cmd.exe"));
+ VERIFY_IS_FALSE(trust(L"C:\\Windows\\System32\\cmd.exe /c cmd.exe"));
+ }
+
+ void TrustCommandlineTests::TestCommandlineWithEnvVars()
+ {
+ Log::Comment(L"Make sure we auto-expand environment variables");
+
+ VERIFY_IS_TRUE(trust(L"%WINDIR%\\system32\\cmd.exe"));
+ VERIFY_IS_TRUE(trust(L"%WINDIR%\\system32\\WindowsPowerShell\\v1.0\\powershell.exe"));
+ VERIFY_IS_TRUE(trust(L"%ProgramFiles%\\PowerShell\\7\\pwsh.exe"));
+ }
+
+ void TrustCommandlineTests::WslTests()
+ {
+ Log::Comment(L"We are explicitly deciding to not auto-approve "
+ L"`wsl.exe -d distro`-like commandlines. If we change this"
+ L" policy, remove this test.");
+
+ VERIFY_IS_FALSE(trust(L"C:\\Windows\\System32\\wsl"));
+ VERIFY_IS_TRUE(trust(L"C:\\Windows\\System32\\wsl.exe"), L"This we will trust though, since it's an exe in system32");
+ VERIFY_IS_FALSE(trust(L"C:\\Windows\\System32\\wsl.exe -d Ubuntu"));
+ VERIFY_IS_FALSE(trust(L"wsl.exe"));
+ }
+
+ void TrustCommandlineTests::TestPwshLocation()
+ {
+ Log::Comment(L"Test various locations that pwsh.exe can be in");
+
+ VERIFY_IS_TRUE(trust(L"%ProgramFiles%\\PowerShell\\7\\pwsh.exe"));
+ VERIFY_IS_TRUE(trust(L"%LOCALAPPDATA%\\Microsoft\\WindowsApps\\pwsh.exe"));
+ VERIFY_IS_TRUE(trust(L"%ProgramFiles%\\PowerShell\\10\\pwsh.exe"));
+ VERIFY_IS_TRUE(trust(L"%ProgramFiles%\\PowerShell\\7.1.5\\pwsh.exe"));
+
+ Log::Comment(L"These are sneaky things that _shouldn't_ be trusted");
+ VERIFY_IS_FALSE(trust(L"%ProgramFiles%\\PowerShell\\7\\pwsh.exe bad-stuff pwsh.exe"));
+ VERIFY_IS_FALSE(trust(L"%ProgramFiles%\\PowerShell\\7\\pwsh.exe bad-stuff c:\\pwsh.exe"));
+ VERIFY_IS_FALSE(trust(L"%ProgramFiles%\\PowerShell\\7\\pwsh.exe bad-stuff c:\\ %ProgramFiles%\\PowerShell\\7\\pwsh.exe"));
+ }
+}
diff --git a/src/cascadia/TerminalApp/AdminWarningPlaceholder.cpp b/src/cascadia/TerminalApp/AdminWarningPlaceholder.cpp
new file mode 100644
index 000000000..e2f53992d
--- /dev/null
+++ b/src/cascadia/TerminalApp/AdminWarningPlaceholder.cpp
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include "pch.h"
+#include "AdminWarningPlaceholder.h"
+#include "AdminWarningPlaceholder.g.cpp"
+#include
+using namespace winrt::Windows::UI::Xaml;
+
+namespace winrt::TerminalApp::implementation
+{
+ AdminWarningPlaceholder::AdminWarningPlaceholder(const winrt::Microsoft::Terminal::Control::TermControl& control, const winrt::hstring& cmdline) :
+ _control{ control },
+ _Commandline{ cmdline }
+ {
+ InitializeComponent();
+ // If the content we're hosting is a TermControl, then use the control's
+ // BG as our BG, to give the impression that it's there, under the
+ // dialog.
+ if (const auto termControl{ control.try_as() })
+ {
+ RootGrid().Background(termControl.BackgroundBrush());
+ }
+ }
+ void AdminWarningPlaceholder::_primaryButtonClick(winrt::Windows::Foundation::IInspectable const& /*sender*/,
+ RoutedEventArgs const& e)
+ {
+ _PrimaryButtonClickedHandlers(*this, e);
+ }
+ void AdminWarningPlaceholder::_cancelButtonClick(winrt::Windows::Foundation::IInspectable const& /*sender*/,
+ RoutedEventArgs const& e)
+ {
+ _CancelButtonClickedHandlers(*this, e);
+ }
+ winrt::Windows::UI::Xaml::Controls::UserControl AdminWarningPlaceholder::Control()
+ {
+ return _control;
+ }
+
+ // Method Description:
+ // - Move the focus to the cancel button by default. This has the LOAD
+ // BEARING side effect of also triggering Narrator to read out the
+ // contents of the dialog. It's unclear why doing this works, but it does.
+ // - Using a LayoutUpdated event to trigger the focus change when we're
+ // added to the UI tree did not seem to work.
+ // - Whoever is adding us to the UI tree is responsible for calling this!
+ // Arguments:
+ // -
+ // Return Value:
+ // -
+ void AdminWarningPlaceholder::FocusOnLaunch()
+ {
+ CancelButton().Focus(FocusState::Programmatic);
+ }
+
+ winrt::hstring AdminWarningPlaceholder::ControlName()
+ {
+ return RS_(L"AdminWarningPlaceholderName");
+ }
+
+ void AdminWarningPlaceholder::_keyUpHandler(IInspectable const& /*sender*/,
+ Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
+ {
+ // If the user presses escape, close the dialog (without confirming)
+ const auto key = e.OriginalKey();
+ if (key == winrt::Windows::System::VirtualKey::Escape)
+ {
+ _CancelButtonClickedHandlers(*this, e);
+ e.Handled(true);
+ }
+ }
+}
diff --git a/src/cascadia/TerminalApp/AdminWarningPlaceholder.h b/src/cascadia/TerminalApp/AdminWarningPlaceholder.h
new file mode 100644
index 000000000..a46e6c1cc
--- /dev/null
+++ b/src/cascadia/TerminalApp/AdminWarningPlaceholder.h
@@ -0,0 +1,51 @@
+/*++
+Copyright (c) Microsoft Corporation
+Licensed under the MIT license.
+
+Module Name:
+- AdminWarningPlaceholder
+
+Abstract:
+- The AdminWarningPlaceholder is a control used to fill space in a pane and
+ present a warning to the user. It holds on to a real control that it should be
+ replaced with. It looks just like a ContentDialog, except it exists per-pane,
+ whereas a ContentDialog can only be added to take up the whole window.
+- The caller should make sure to bind our PrimaryButtonClicked and
+ CancelButtonClicked events, to be informed as to which was pressed.
+
+Author(s):
+- Mike Griese - September 2021
+
+--*/
+
+#pragma once
+
+#include "AdminWarningPlaceholder.g.h"
+#include "../../cascadia/inc/cppwinrt_utils.h"
+
+namespace winrt::TerminalApp::implementation
+{
+ struct AdminWarningPlaceholder : AdminWarningPlaceholderT
+ {
+ AdminWarningPlaceholder(const winrt::Microsoft::Terminal::Control::TermControl& control, const winrt::hstring& cmdline);
+ void FocusOnLaunch();
+ winrt::Windows::UI::Xaml::Controls::UserControl Control();
+ winrt::hstring ControlName();
+ WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
+ WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Commandline, _PropertyChangedHandlers);
+ TYPED_EVENT(PrimaryButtonClicked, TerminalApp::AdminWarningPlaceholder, winrt::Windows::UI::Xaml::RoutedEventArgs);
+ TYPED_EVENT(CancelButtonClicked, TerminalApp::AdminWarningPlaceholder, winrt::Windows::UI::Xaml::RoutedEventArgs);
+
+ private:
+ friend struct AdminWarningPlaceholderT; // friend our parent so it can bind private event handlers
+
+ winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr };
+
+ void _primaryButtonClick(winrt::Windows::Foundation::IInspectable const& sender,
+ winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
+ void _cancelButtonClick(winrt::Windows::Foundation::IInspectable const& sender,
+ winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
+ void _keyUpHandler(Windows::Foundation::IInspectable const& sender,
+ Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
+ };
+}
diff --git a/src/cascadia/TerminalApp/AdminWarningPlaceholder.idl b/src/cascadia/TerminalApp/AdminWarningPlaceholder.idl
new file mode 100644
index 000000000..5cb315473
--- /dev/null
+++ b/src/cascadia/TerminalApp/AdminWarningPlaceholder.idl
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+namespace TerminalApp
+{
+ [default_interface] runtimeclass AdminWarningPlaceholder : Windows.UI.Xaml.Controls.UserControl,
+ Windows.UI.Xaml.Data.INotifyPropertyChanged
+ {
+ String Commandline { get; };
+ String ControlName { get; };
+ }
+}
diff --git a/src/cascadia/TerminalApp/AdminWarningPlaceholder.xaml b/src/cascadia/TerminalApp/AdminWarningPlaceholder.xaml
new file mode 100644
index 000000000..ba8510188
--- /dev/null
+++ b/src/cascadia/TerminalApp/AdminWarningPlaceholder.xaml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp
index ef8e12522..f7809179c 100644
--- a/src/cascadia/TerminalApp/Pane.cpp
+++ b/src/cascadia/TerminalApp/Pane.cpp
@@ -34,7 +34,7 @@ static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Wi
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_focusedBorderBrush = { nullptr };
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_unfocusedBorderBrush = { nullptr };
-Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFocused) :
+Pane::Pane(const Profile& profile, const Controls::UserControl& control, const bool lastFocused) :
_control{ control },
_lastActive{ lastFocused },
_profile{ profile }
@@ -42,8 +42,11 @@ Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFo
_root.Children().Append(_borderFirst);
_borderFirst.Child(_control);
- _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
- _warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler });
+ if (const auto& termControl{ _control.try_as() })
+ {
+ _connectionStateChangedToken = termControl.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
+ _warningBellToken = termControl.WarningBell({ this, &Pane::_ControlWarningBellHandler });
+ }
// On the first Pane's creation, lookup resources we'll use to theme the
// Pane, including the brushed to use for the focused/unfocused border
@@ -125,11 +128,23 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const
assert(_IsLeaf());
NewTerminalArgs args{};
- auto controlSettings = _control.Settings().as();
+ auto termControl{ _control.try_as() };
+ if (!termControl)
+ {
+ if (auto adminWarning{ _control.try_as() })
+ {
+ termControl = adminWarning.Content().try_as();
+ }
+ }
+ if (!termControl)
+ {
+ return nullptr;
+ }
+ auto controlSettings = termControl.Settings().as();
args.Profile(controlSettings.ProfileName());
// If we know the user's working directory use it instead of the profile.
- if (const auto dir = _control.WorkingDirectory(); !dir.empty())
+ if (const auto dir = termControl.WorkingDirectory(); !dir.empty())
{
args.StartingDirectory(dir);
}
@@ -832,6 +847,40 @@ bool Pane::SwapPanes(std::shared_ptr first, std::shared_ptr second)
return false;
}
+Controls::UserControl Pane::ReplaceControl(const Controls::UserControl& control)
+{
+ if (!_IsLeaf())
+ {
+ return nullptr;
+ }
+
+ // Remove old control's event handlers
+ const auto& oldControl = _control;
+ _gotFocusRevoker.revoke();
+ _lostFocusRevoker.revoke();
+ if (const auto& oldTermControl{ _control.try_as() })
+ {
+ oldTermControl.ConnectionStateChanged(_connectionStateChangedToken);
+ oldTermControl.WarningBell(_warningBellToken);
+ }
+
+ _control = control;
+
+ _borderFirst.Child(_control);
+
+ // Register an event with the control to have it inform us when it gains focus.
+ _gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
+ _lostFocusRevoker = _control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler });
+
+ if (const auto& termControl{ _control.try_as() })
+ {
+ _connectionStateChangedToken = termControl.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
+ _warningBellToken = termControl.WarningBell({ this, &Pane::_ControlWarningBellHandler });
+ }
+
+ return oldControl;
+}
+
// Method Description:
// - Given two panes' offsets, test whether the `direction` side of first is adjacent to second.
// Arguments:
@@ -1079,8 +1128,12 @@ void Pane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundatio
{
return;
}
-
- const auto newConnectionState = _control.ConnectionState();
+ const auto& termControl{ _control.try_as() };
+ if (!termControl)
+ {
+ return;
+ }
+ const auto newConnectionState = termControl.ConnectionState();
const auto previousConnectionState = std::exchange(_connectionState, newConnectionState);
if (newConnectionState < ConnectionState::Closed)
@@ -1123,7 +1176,9 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect
{
return;
}
- if (_profile)
+
+ const auto& termControl{ _control.try_as() };
+ if (_profile && termControl)
{
// We don't want to do anything if nothing is set, so check for that first
if (static_cast(_profile.BellStyle()) != 0)
@@ -1137,7 +1192,7 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect
if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Window))
{
- _control.BellLightOn();
+ termControl.BellLightOn();
}
// raise the event with the bool value corresponding to the taskbar flag
@@ -1197,7 +1252,11 @@ void Pane::Shutdown()
std::unique_lock lock{ _createCloseLock };
if (_IsLeaf())
{
- _control.Close();
+ const auto& termControl{ _control.try_as() };
+ if (termControl)
+ {
+ termControl.Close();
+ }
}
else
{
@@ -1207,7 +1266,7 @@ void Pane::Shutdown()
}
// Method Description:
-// - Get the root UIElement of this pane. There may be a single TermControl as a
+// - Get the root UIElement of this pane. There may be a single UserControl as a
// child, or an entire tree of grids and panes as children of this element.
// Arguments:
// -
@@ -1266,7 +1325,7 @@ TermControl Pane::GetLastFocusedTerminalControl()
{
if (p->_IsLeaf())
{
- return p->_control;
+ return p->GetTerminalControl();
}
pane = p;
}
@@ -1274,7 +1333,7 @@ TermControl Pane::GetLastFocusedTerminalControl()
}
return _firstChild->GetLastFocusedTerminalControl();
}
- return _control;
+ return GetTerminalControl();
}
// Method Description:
@@ -1283,8 +1342,15 @@ TermControl Pane::GetLastFocusedTerminalControl()
// Arguments:
// -
// Return Value:
-// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane.
-TermControl Pane::GetTerminalControl()
+// - nullptr if this Pane is a parent or isn't hosting a Terminal, otherwise the
+// TermControl of this Pane.
+TermControl Pane::GetTerminalControl() const
+{
+ auto control{ GetUserControl() };
+ return control ? control.try_as() : nullptr;
+}
+
+Controls::UserControl Pane::GetUserControl() const
{
return _IsLeaf() ? _control : nullptr;
}
@@ -1457,9 +1523,13 @@ void Pane::_FocusFirstChild()
void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const Profile& profile)
{
assert(_IsLeaf());
-
+ const auto& termControl{ _control.try_as() };
+ if (!termControl)
+ {
+ return;
+ }
_profile = profile;
- auto controlSettings = _control.Settings().as();
+ auto controlSettings = termControl.Settings().as();
// Update the parent of the control's settings object (and not the object itself) so
// that any overrides made by the control don't get affected by the reload
controlSettings.SetParent(settings.DefaultSettings());
@@ -1472,8 +1542,8 @@ void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const Pr
// sure the unfocused settings inherit from that.
unfocusedSettings.SetParent(controlSettings);
}
- _control.UnfocusedAppearance(unfocusedSettings);
- _control.UpdateSettings();
+ termControl.UnfocusedAppearance(unfocusedSettings);
+ termControl.UpdateSettings();
}
// Method Description:
@@ -1614,8 +1684,12 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
_id = remainingChild->Id();
// Add our new event handler before revoking the old one.
- _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
- _warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler });
+ const auto& termControl{ _control.try_as() };
+ if (termControl)
+ {
+ _connectionStateChangedToken = termControl.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
+ _warningBellToken = termControl.WarningBell({ this, &Pane::_ControlWarningBellHandler });
+ }
// Revoke the old event handlers. Remove both the handlers for the panes
// themselves closing, and remove their handlers for their controls
@@ -1629,8 +1703,11 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
closedChild->WalkTree([](auto p) {
if (p->_IsLeaf())
{
- p->_control.ConnectionStateChanged(p->_connectionStateChangedToken);
- p->_control.WarningBell(p->_warningBellToken);
+ if (const auto& closedControl{ p->_control.try_as() })
+ {
+ closedControl.ConnectionStateChanged(p->_connectionStateChangedToken);
+ closedControl.WarningBell(p->_warningBellToken);
+ }
}
return false;
});
@@ -1638,15 +1715,19 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
closedChild->Closed(closedChildClosedToken);
remainingChild->Closed(remainingChildClosedToken);
- remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken);
- remainingChild->_control.WarningBell(remainingChild->_warningBellToken);
+
+ if (const auto& remainingControl{ remainingChild->_control.try_as() })
+ {
+ remainingControl.ConnectionStateChanged(remainingChild->_connectionStateChangedToken);
+ remainingControl.WarningBell(remainingChild->_warningBellToken);
+ }
// If we or either of our children was focused, we want to take that
// focus from them.
_lastActive = _lastActive || _firstChild->_lastActive || _secondChild->_lastActive;
// Remove all the ui elements of the remaining child. This'll make sure
- // we can re-attach the TermControl to our Grid.
+ // we can re-attach the UserControl to our Grid.
remainingChild->_root.Children().Clear();
remainingChild->_borderFirst.Child(nullptr);
@@ -1657,7 +1738,7 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
_root.ColumnDefinitions().Clear();
_root.RowDefinitions().Clear();
- // Reattach the TermControl to our grid.
+ // Reattach the UserControl to our grid.
_root.Children().Append(_borderFirst);
_borderFirst.Child(_control);
@@ -1719,8 +1800,11 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
closedChild->WalkTree([](auto p) {
if (p->_IsLeaf())
{
- p->_control.ConnectionStateChanged(p->_connectionStateChangedToken);
- p->_control.WarningBell(p->_warningBellToken);
+ if (const auto& closedControl{ p->_control.try_as() })
+ {
+ closedControl.ConnectionStateChanged(p->_connectionStateChangedToken);
+ closedControl.WarningBell(p->_warningBellToken);
+ }
}
return false;
});
@@ -2468,11 +2552,14 @@ std::pair, std::shared_ptr> Pane::_Split(SplitDirect
if (_IsLeaf())
{
- // revoke our handler - the child will take care of the control now.
- _control.ConnectionStateChanged(_connectionStateChangedToken);
- _connectionStateChangedToken.value = 0;
- _control.WarningBell(_warningBellToken);
- _warningBellToken.value = 0;
+ if (const auto& termControl{ _control.try_as() })
+ {
+ // revoke our handler - the child will take care of the control now.
+ termControl.ConnectionStateChanged(_connectionStateChangedToken);
+ termControl.WarningBell(_warningBellToken);
+ _connectionStateChangedToken.value = 0;
+ _warningBellToken.value = 0;
+ }
// Remove our old GotFocus handler from the control. We don't want the
// control telling us that it's now focused, we want it telling its new
@@ -2482,7 +2569,7 @@ std::pair, std::shared_ptr> Pane::_Split(SplitDirect
}
// Remove any children we currently have. We can't add the existing
- // TermControl to a new grid until we do this.
+ // UserControl to a new grid until we do this.
_root.Children().Clear();
_borderFirst.Child(nullptr);
_borderSecond.Child(nullptr);
@@ -2860,8 +2947,13 @@ float Pane::CalcSnappedDimension(const bool widthOrHeight, const float dimension
// If requested size is already snapped, then both returned values equal this value.
Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
{
+ const auto& termControl{ _control.try_as() };
if (_IsLeaf())
{
+ if (!termControl)
+ {
+ return { dimension, dimension };
+ }
// If we're a leaf pane, align to the grid of controlling terminal
const auto minSize = _GetMinSize();
@@ -2872,7 +2964,7 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const
return { minDimension, minDimension };
}
- float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension);
+ float lower = termControl.SnapDimensionToGrid(widthOrHeight, dimension);
if (widthOrHeight)
{
lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0;
@@ -2892,7 +2984,7 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const
}
else
{
- const auto cellSize = _control.CharacterDimensions();
+ const auto cellSize = termControl.CharacterDimensions();
const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height);
return { lower, higher };
}
@@ -2937,26 +3029,39 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const
// -
void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const
{
+ const auto& termControl{ _control.try_as() };
if (_IsLeaf())
{
- // We're a leaf pane, so just add one more row or column (unless isMinimumSize
- // is true, see below).
-
- if (sizeNode.isMinimumSize)
+ if (termControl)
{
- // If the node is of its minimum size, this size might not be snapped (it might
- // be, say, half a character, or fixed 10 pixels), so snap it upward. It might
- // however be already snapped, so add 1 to make sure it really increases
- // (not strictly necessary but to avoid surprises).
- sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher;
+ // We're a leaf pane, so just add one more row or column (unless isMinimumSize
+ // is true, see below).
+
+ if (sizeNode.isMinimumSize)
+ {
+ // If the node is of its minimum size, this size might not be snapped (it might
+ // be, say, half a character, or fixed 10 pixels), so snap it upward. It might
+ // however be already snapped, so add 1 to make sure it really increases
+ // (not strictly necessary but to avoid surprises).
+ sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher;
+ }
+ else
+ {
+ const auto cellSize = termControl.CharacterDimensions();
+ sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height;
+ }
}
else
{
- const auto cellSize = _control.CharacterDimensions();
- sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height;
+ // If we're a leaf that didn't have a TermControl, then just increment
+ // by one. We have to increment by _some_ value, because this is used in
+ // a while() loop to find the next bigger size we can snap to. But since
+ // a non-terminal control doesn't really care what size it's snapped to,
+ // we can just say "one pixel larger is the next snap point"
+ sizeNode.size += 1;
}
}
- else
+ else // !_IsLeaf()
{
// We're a parent pane, so we have to advance dimension of our children panes. In
// fact, we advance only one child (chosen later) to keep the growth fine-grained.
@@ -3058,7 +3163,8 @@ Size Pane::_GetMinSize() const
{
if (_IsLeaf())
{
- auto controlSize = _control.MinimumSize();
+ const auto& termControl{ _control.try_as() };
+ auto controlSize = termControl ? termControl.MinimumSize() : Size{ 1, 1 };
auto newWidth = controlSize.Width;
auto newHeight = controlSize.Height;
@@ -3244,7 +3350,10 @@ std::optional Pane::PreCalculateAutoSplit(const std::shared_ptr<
// - Returns true if the pane or one of its descendants is read-only
bool Pane::ContainsReadOnly() const
{
- return _IsLeaf() ? _control.ReadOnly() : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly());
+ const auto& termControl{ GetTerminalControl() };
+ return termControl ?
+ termControl.ReadOnly() :
+ (_IsLeaf() ? false : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly()));
}
// Method Description:
@@ -3257,13 +3366,14 @@ bool Pane::ContainsReadOnly() const
// -
void Pane::CollectTaskbarStates(std::vector& states)
{
- if (_IsLeaf())
+ const auto& termControl{ GetTerminalControl() };
+ if (termControl)
{
- auto tbState{ winrt::make(_control.TaskbarState(),
- _control.TaskbarProgress()) };
+ auto tbState{ winrt::make(termControl.TaskbarState(),
+ termControl.TaskbarProgress()) };
states.push_back(tbState);
}
- else
+ else if (!_IsLeaf())
{
_firstChild->CollectTaskbarStates(states);
_secondChild->CollectTaskbarStates(states);
diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h
index 920124c33..5ad95c372 100644
--- a/src/cascadia/TerminalApp/Pane.h
+++ b/src/cascadia/TerminalApp/Pane.h
@@ -56,7 +56,7 @@ class Pane : public std::enable_shared_from_this
{
public:
Pane(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
- const winrt::Microsoft::Terminal::Control::TermControl& control,
+ const winrt::Windows::UI::Xaml::Controls::UserControl& control,
const bool lastFocused = false);
Pane(std::shared_ptr first,
@@ -66,8 +66,9 @@ public:
const bool lastFocused = false);
std::shared_ptr GetActivePane();
+ winrt::Windows::UI::Xaml::Controls::UserControl GetUserControl() const;
winrt::Microsoft::Terminal::Control::TermControl GetLastFocusedTerminalControl();
- winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl();
+ winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl() const;
winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile();
// Method Description:
@@ -126,6 +127,8 @@ public:
winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType);
std::shared_ptr DetachPane(std::shared_ptr pane);
+ winrt::Windows::UI::Xaml::Controls::UserControl ReplaceControl(const winrt::Windows::UI::Xaml::Controls::UserControl& control);
+
int GetLeafPaneCount() const noexcept;
void Maximize(std::shared_ptr zoomedPane);
@@ -200,7 +203,8 @@ private:
winrt::Windows::UI::Xaml::Controls::Grid _root{};
winrt::Windows::UI::Xaml::Controls::Border _borderFirst{};
winrt::Windows::UI::Xaml::Controls::Border _borderSecond{};
- winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr };
+ winrt::Windows::UI::Xaml::Controls::UserControl _control{ nullptr };
+
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected };
static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush;
static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush;
diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
index 56bc3ed17..d71ec4535 100644
--- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
@@ -504,6 +504,24 @@
Warning
+
+ You are about to execute the following command-line:
+
+
+ Do you wish to continue?
+
+
+ Allow command-line
+
+
+ Cancel
+
+
+ Warning
+
+
+ Elevated command-line warning
+
Type a command name...
diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp
index e67ad7780..d5b4fe275 100644
--- a/src/cascadia/TerminalApp/TabManagement.cpp
+++ b/src/cascadia/TerminalApp/TabManagement.cpp
@@ -16,6 +16,7 @@
#include
#include "TabRowControl.h"
+#include "AdminWarningPlaceholder.h"
#include "ColorHelper.h"
#include "DebugTapConnection.h"
#include "SettingsTab.h"
diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj
index 9360e9dfa..5fcc8ae02 100644
--- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj
+++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj
@@ -63,6 +63,9 @@
Designer
+
+ Designer
+
@@ -141,6 +144,9 @@
AppLogic.idl
+
+ AdminWarningPlaceholder.xaml
+
@@ -234,6 +240,9 @@
AppLogic.idl
+
+ AdminWarningPlaceholder.xaml
+
@@ -295,6 +304,10 @@
CommandPalette.xamlCode
+
+ AdminWarningPlaceholder.xaml
+ Code
+
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index 81e2bc517..5ffb5ca83 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -17,6 +17,7 @@
#include "DebugTapConnection.h"
#include "SettingsTab.h"
#include "RenameWindowRequestedArgs.g.cpp"
+#include "AdminWarningPlaceholder.h"
#include "../inc/WindowingBehavior.h"
#include
@@ -1415,21 +1416,24 @@ namespace winrt::TerminalApp::implementation
{
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
- uint32_t realRowsToScroll;
- if (rowsToScroll == nullptr)
+ if (const auto& termControl{ terminalTab->GetActiveTerminalControl() })
{
- // The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page
- realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ?
- terminalTab->GetActiveTerminalControl().ViewHeight() :
- _systemRowsToScroll;
+ uint32_t realRowsToScroll;
+ if (rowsToScroll == nullptr)
+ {
+ // The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page
+ realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ?
+ termControl.ViewHeight() :
+ _systemRowsToScroll;
+ }
+ else
+ {
+ // use the custom value specified in the command
+ realRowsToScroll = rowsToScroll.Value();
+ }
+ auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll);
+ terminalTab->Scroll(scrollDelta);
}
- else
- {
- // use the custom value specified in the command
- realRowsToScroll = rowsToScroll.Value();
- }
- auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll);
- terminalTab->Scroll(scrollDelta);
}
}
@@ -1483,6 +1487,246 @@ namespace winrt::TerminalApp::implementation
return true;
}
+ // Function Description:
+ // - Returns true if this commandline is a commandline that we know is safe.
+ // Generally, this is true for any executables in system32. We can use
+ // this to bypass the elevated state check, because we're confident that
+ // executables in that path won't have been hijacked.
+ // - TECHNICALLY a user can take ownership of a file in system32 and
+ // replace it as the system administrator. You could say it's OK though
+ // because you'd already have to have had admin rights to mess that
+ // folder up or something.
+ // - Will attempt to resolve environment strings.
+ // - Will also manually allow commandlines as generated for the default WSL
+ // distros.
+ // - Will also trust %ProgramFiles%\Powershell\...\pwsh.exe paths, for
+ // PowerShell Core.
+ // Arguments:
+ // - commandLine: the command to check.
+ // Return Value (example):
+ // - C:\windows\system32\cmd.exe -> returns true
+ // - cmd.exe -> returns false
+ // - C:\windows\system32\cmd.exe /k echo sneaky sneak -> returns false
+ // - %SystemRoot%\System32\cmd.exe -> returns true
+ // - %SystemRoot%\System32\wsl.exe -d -> returns true
+ bool TerminalPage::_isTrustedCommandline(std::wstring_view commandLine)
+ {
+ // use C++11 magic statics to make sure we only do this once.
+ static std::wstring systemDirectory = []() -> std::wstring {
+ // *** THIS IS A SINGLETON ***
+ static std::wstring sys32{};
+ if (FAILED(wil::GetSystemDirectoryW(sys32)))
+ {
+ // we couldn't look up where system32 is?? Then it's definitely not
+ // in System32
+ return {};
+ }
+ return sys32;
+ }();
+
+ if (systemDirectory.empty())
+ {
+ return false;
+ }
+
+ const std::wstring fullCommandline{
+ wil::ExpandEnvironmentStringsW(commandLine.data())
+ };
+
+ if (fullCommandline.size() > systemDirectory.size())
+ {
+ // Get the first part of the executable path
+ const auto start = fullCommandline.substr(0, systemDirectory.size());
+ // Doing this as an ASCII only check might be wrong, but I'm
+ // guessing if system32 isn't at X:\windows\system32... this isn't
+ // the only thing that is going to be sad in Windows.
+ const auto pathEquals = til::equals_insensitive_ascii(start, systemDirectory);
+ if (pathEquals && std::filesystem::exists(std::filesystem::path{ fullCommandline }))
+ {
+ return true;
+ }
+ }
+
+ // We're explicitly not auto-allowing wsl.exe -d . It's
+ // trivial to insert some malicious stuff into WSL, via .bash_profile,
+ // so we're not giving them the (y)
+
+ // But we do want to allow `pwsh.exe` profiles in the expected place to
+ // work.
+ const std::vector powershellCoreRoots
+ {
+ // Always look in "%LOCALAPPDATA%\Microsoft\WindowsApps", which is
+ // where the pwsh.exe execution alias lives.
+ { wil::ExpandEnvironmentStringsW(L"%LOCALAPPDATA%\\Microsoft\\WindowsApps") },
+
+ // Always look in "%ProgramFiles%\PowerShell"
+ { wil::ExpandEnvironmentStringsW(L"%ProgramFiles%\\PowerShell") },
+
+#if defined(_M_AMD64) || defined(_M_ARM64) // No point in looking for WOW if we're not somewhere it exists
+ { wil::ExpandEnvironmentStringsW(L"%ProgramFiles(x86)%\\PowerShell") },
+#endif
+
+#if defined(_M_ARM64) // same with ARM
+ {
+ wil::ExpandEnvironmentStringsW(L"%ProgramFiles(Arm)%\\PowerShell")
+ }
+#endif
+ };
+
+ // Is the filename for this commandline `pwsh.exe`?
+ const std::filesystem::path exePath{ fullCommandline };
+ const auto endsWithPwsh{ exePath.filename() == L"pwsh.exe" };
+ // We'll also need to check the parent path, so make sure it has one here.
+
+ if (endsWithPwsh && exePath.has_parent_path())
+ {
+ const auto parentPath{ exePath.parent_path() };
+ for (const auto& pwshRoot : powershellCoreRoots)
+ {
+ // Does the commandline start with this root, and end with pwsh.exe?
+ const auto startsWithRoot{ til::starts_with(fullCommandline, pwshRoot.c_str()) };
+
+ // Is either the immediate parent, or the grandparent, this root exactly?
+ //
+ // We need to check the grandparent for the
+ // `%ProgramFiles%\\PowerShell\\7\\pwsh.exe` case.
+ const auto parentIsCorrect = (parentPath == pwshRoot) ||
+ (parentPath.has_parent_path() && parentPath.parent_path() == pwshRoot);
+
+ if (startsWithRoot && parentIsCorrect)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // Method Description:
+ // - For a given commandline, determines if we should prompt the user for
+ // approval. We only do this check when elevated. This will check the
+ // AllowedCommandlines in `elevated-state.json`, to see if the commandline
+ // already exists in that list.
+ // Arguments:
+ // - cmdline: The commandline to check
+ // Return Value:
+ // - true if we should prompt the user for approval.
+ bool TerminalPage::_shouldPromptForCommandline(const winrt::hstring& cmdline) const
+ {
+ // NOTE: For debugging purposes, changing this to `true || IsElevated()`
+ // is a handy way of forcing the elevation logic, even when unelevated.
+ if (IsElevated())
+ {
+ // If the cmdline is EXACTLY an executable in
+ // `C:\WINDOWS\System32`, then ignore this check.
+ if (_isTrustedCommandline(cmdline))
+ {
+ return false;
+ }
+
+ if (const auto& allowedCommandlines{ ApplicationState::SharedInstance().AllowedCommandlines() })
+ {
+ for (const auto& approved : allowedCommandlines)
+ {
+ if (approved == cmdline)
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ void TerminalPage::_adminWarningPrimaryClicked(const TerminalApp::AdminWarningPlaceholder& sender,
+ const winrt::Windows::UI::Xaml::RoutedEventArgs& /*args*/)
+ {
+ auto warningControl{ winrt::get_self(sender) };
+ const auto& cmdline{ warningControl->Commandline() };
+ // Look through the tabs and panes to look for us. Whichever pane had us
+ // as content - replace their content with the TermControl we were
+ // holding on to.
+ for (const auto& tab : _tabs)
+ {
+ if (const auto& tabImpl{ _GetTerminalTabImpl(tab) })
+ {
+ tabImpl->GetRootPane()->WalkTree([warningControl, cmdline, tabImpl](std::shared_ptr pane) -> bool {
+ const auto& projectedWarningControl{ pane->GetUserControl().try_as() };
+ // If it was a warning control, then get our implementation
+ // type out of it.
+ if (const auto& otherWarning{ winrt::get_self(projectedWarningControl) })
+ {
+ // This pane had a warning in it.
+ // Was it a warning for the same commandline that we
+ // just approved?
+ if (otherWarning->Commandline() == cmdline)
+ {
+ // Go ahead and allow them. Swap the control into
+ // the pane, which will initialize and start it.
+ tabImpl->ReplaceControl(pane, otherWarning->Control());
+ }
+ // Don't return true here. We want to make sure to check
+ // all the panes for the same commandline we just
+ // approved.
+ }
+ // return false so we make sure to iterate on every leaf.
+ return false;
+ });
+ }
+ }
+
+ // Update the list of approved commandlines.
+ auto allowedCommandlines{ ApplicationState::SharedInstance().AllowedCommandlines() };
+ if (!allowedCommandlines)
+ {
+ allowedCommandlines = winrt::single_threaded_vector();
+ }
+
+ // But of course, we don't need to add this commandline if it's already
+ // in the list of approved commandlines.
+ bool foundCopy = false;
+ for (const auto& approved : allowedCommandlines)
+ {
+ if (approved == cmdline)
+ {
+ foundCopy = true;
+ break;
+ }
+ }
+ if (!foundCopy)
+ {
+ allowedCommandlines.Append(cmdline);
+ }
+ ApplicationState::SharedInstance().AllowedCommandlines(allowedCommandlines);
+ }
+
+ void TerminalPage::_adminWarningCancelClicked(const TerminalApp::AdminWarningPlaceholder& sender,
+ const winrt::Windows::UI::Xaml::RoutedEventArgs& /*args*/)
+ {
+ auto warningControl{ winrt::get_self(sender) };
+
+ for (const auto& tab : _tabs)
+ {
+ if (const auto& tabImpl{ _GetTerminalTabImpl(tab) })
+ {
+ tabImpl->GetRootPane()->WalkTree([warningControl](std::shared_ptr pane) -> bool {
+ if (pane->GetUserControl() == *warningControl)
+ {
+ pane->Close();
+ return true;
+ }
+ // We're not going to auto-close all the other panes with
+ // the same commandline warning, akin to what we're doing in
+ // the approve handler. If they want to reject one pane, but
+ // accept the next one, that's okay.
+ return false;
+ });
+ }
+ }
+ }
+
// Method Description:
// - Split the focused pane either horizontally or vertically, and place the
// given pane accordingly in the tree
@@ -1852,8 +2096,10 @@ namespace winrt::TerminalApp::implementation
if (warnMultiLine)
{
const auto focusedTab = _GetFocusedTabImpl();
+ const auto& termControl{ focusedTab->GetActiveTerminalControl() };
// Do not warn about multi line pasting if the current tab has bracketed paste enabled.
- warnMultiLine = warnMultiLine && !focusedTab->GetActiveTerminalControl().BracketedPasteEnabled();
+ warnMultiLine = warnMultiLine &&
+ (termControl && !termControl.BracketedPasteEnabled());
}
// We have to initialize the dialog here to be able to change the text of the text block within it
@@ -2161,11 +2407,14 @@ namespace winrt::TerminalApp::implementation
// TODO GH#5047 If we cache the NewTerminalArgs, we no longer need to do this.
profile = GetClosestProfileForDuplicationOfProfile(profile);
controlSettings = TerminalSettings::CreateWithProfile(_settings, profile, *_bindings);
- const auto workingDirectory = focusedTab->GetActiveTerminalControl().WorkingDirectory();
- const auto validWorkingDirectory = !workingDirectory.empty();
- if (validWorkingDirectory)
+ if (const auto& control{ focusedTab->GetActiveTerminalControl() })
{
- controlSettings.DefaultSettings().StartingDirectory(workingDirectory);
+ const auto workingDirectory = control.WorkingDirectory();
+ const auto validWorkingDirectory = !workingDirectory.empty();
+ if (validWorkingDirectory)
+ {
+ controlSettings.DefaultSettings().StartingDirectory(workingDirectory);
+ }
}
}
}
@@ -2199,7 +2448,21 @@ namespace winrt::TerminalApp::implementation
const auto control = _InitControl(controlSettings, connection);
_RegisterTerminalEvents(control);
- auto resultPane = std::make_shared(profile, control);
+ WUX::Controls::UserControl controlToAdd{ control };
+
+ // Check if we should warn the user about running a new unelevated
+ // commandline.
+ const auto& cmdline{ controlSettings.DefaultSettings().Commandline() };
+ const auto doAdminWarning{ _shouldPromptForCommandline(cmdline) };
+ if (doAdminWarning)
+ {
+ auto warningControl{ winrt::make_self(control, cmdline) };
+ warningControl->PrimaryButtonClicked({ get_weak(), &TerminalPage::_adminWarningPrimaryClicked });
+ warningControl->CancelButtonClicked({ get_weak(), &TerminalPage::_adminWarningCancelClicked });
+ controlToAdd = *warningControl;
+ }
+
+ auto resultPane = std::make_shared(profile, controlToAdd);
if (debugConnection) // this will only be set if global debugging is on and tap is active
{
@@ -2220,6 +2483,17 @@ namespace winrt::TerminalApp::implementation
original->SetActive();
}
+ if (doAdminWarning)
+ {
+ // We know this is safe - we literally just added the
+ // AdminWarningPlaceholder as the controlToAdd like 20 lines up.
+ //
+ // Focus the warning here. The LayoutUpdated within the dialog
+ // itself isn't good enough. That, for some reason, fires _before_
+ // the dialog is in the UI tree, which is useless for us.
+ controlToAdd.try_as()->FocusOnLaunch();
+ }
+
return resultPane;
}
diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h
index cf9477d2e..0689743c4 100644
--- a/src/cascadia/TerminalApp/TerminalPage.h
+++ b/src/cascadia/TerminalApp/TerminalPage.h
@@ -19,6 +19,7 @@ namespace TerminalAppLocalTests
{
class TabTests;
class SettingsTests;
+ class TrustCommandlineTests;
};
namespace winrt::TerminalApp::implementation
@@ -207,6 +208,7 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::Foundation::IAsyncOperation _ShowCloseReadOnlyDialog();
winrt::Windows::Foundation::IAsyncOperation _ShowMultiLinePasteWarningDialog();
winrt::Windows::Foundation::IAsyncOperation _ShowLargePasteWarningDialog();
+ winrt::Windows::Foundation::IAsyncOperation _ShowCommandlineApproveWarning();
void _CreateNewTabFlyout();
void _OpenNewTabDropdown();
@@ -403,6 +405,12 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::Settings::Model::Profile GetClosestProfileForDuplicationOfProfile(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile) const noexcept;
+ bool _shouldPromptForCommandline(const winrt::hstring& cmdline) const;
+ void _adminWarningPrimaryClicked(const winrt::TerminalApp::AdminWarningPlaceholder& sender,
+ const winrt::Windows::UI::Xaml::RoutedEventArgs& args);
+ void _adminWarningCancelClicked(const winrt::TerminalApp::AdminWarningPlaceholder& sender,
+ const winrt::Windows::UI::Xaml::RoutedEventArgs& args);
+
winrt::fire_and_forget _ConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const;
void _CloseOnExitInfoDismissHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const;
void _KeyboardServiceWarningInfoDismissHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const;
@@ -410,6 +418,7 @@ namespace winrt::TerminalApp::implementation
void _SetAsDefaultOpenSettingsHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);
static bool _IsMessageDismissed(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message);
static void _DismissMessage(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message);
+ static bool _isTrustedCommandline(std::wstring_view commandLine);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
@@ -420,6 +429,7 @@ namespace winrt::TerminalApp::implementation
friend class TerminalAppLocalTests::TabTests;
friend class TerminalAppLocalTests::SettingsTests;
+ friend class TerminalAppLocalTests::TrustCommandlineTests;
};
}
diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp
index 411693254..b2860f713 100644
--- a/src/cascadia/TerminalApp/TerminalTab.cpp
+++ b/src/cascadia/TerminalApp/TerminalTab.cpp
@@ -426,7 +426,10 @@ namespace winrt::TerminalApp::implementation
winrt::fire_and_forget TerminalTab::Scroll(const int delta)
{
auto control = GetActiveTerminalControl();
-
+ if (!control)
+ {
+ co_return;
+ }
co_await winrt::resume_foreground(control.Dispatcher());
const auto currentOffset = control.ScrollOffset();
@@ -511,7 +514,11 @@ namespace winrt::TerminalApp::implementation
if (p->_IsLeaf())
{
p->Id(_nextPaneId);
- _AttachEventHandlersToControl(p->Id().value(), p->_control);
+ if (auto termControl{ p->_control.try_as() })
+ {
+ _AttachEventHandlersToControl(p->Id().value(), termControl);
+ }
+
_nextPaneId++;
}
return false;
@@ -856,6 +863,10 @@ namespace winrt::TerminalApp::implementation
// -
void TerminalTab::_AttachEventHandlersToControl(const uint32_t paneId, const TermControl& control)
{
+ if (!control)
+ {
+ return;
+ }
auto weakThis{ get_weak() };
auto dispatcher = TabViewItem().Dispatcher();
ControlEventTokens events{};
@@ -1744,6 +1755,19 @@ namespace winrt::TerminalApp::implementation
return Title();
}
+ void TerminalTab::ReplaceControl(std::shared_ptr pane, const Controls::UserControl& control)
+ {
+ pane->ReplaceControl(control);
+
+ if (auto termControl{ pane->_control.try_as() })
+ {
+ _AttachEventHandlersToControl(pane->Id().value(), termControl);
+ }
+
+ // Update the title manually.
+ UpdateTitle();
+ }
+
DEFINE_EVENT(TerminalTab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, ColorSelected, _colorSelected, winrt::delegate);
DEFINE_EVENT(TerminalTab, ColorCleared, _colorCleared, winrt::delegate<>);
diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h
index 4b5fe9ef4..a7e37f11b 100644
--- a/src/cascadia/TerminalApp/TerminalTab.h
+++ b/src/cascadia/TerminalApp/TerminalTab.h
@@ -93,6 +93,9 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr GetRootPane() const { return _rootPane; }
+ void ReplaceControl(std::shared_ptr pane,
+ const winrt::Windows::UI::Xaml::Controls::UserControl& control);
+
winrt::TerminalApp::TerminalTabStatus TabStatus()
{
return _tabStatus;
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index 25fd6d2d4..155c7cb1d 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -2645,4 +2645,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
return _core.ReadEntireBuffer();
}
+
+ Media::Brush TermControl::BackgroundBrush()
+ {
+ return RootGrid().Background();
+ }
}
diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h
index cde51ec12..74d0b03ac 100644
--- a/src/cascadia/TerminalControl/TermControl.h
+++ b/src/cascadia/TerminalControl/TermControl.h
@@ -105,6 +105,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
hstring ReadEntireBuffer() const;
+ Windows::UI::Xaml::Media::Brush BackgroundBrush();
+
// -------------------------------- WinRT Events ---------------------------------
// clang-format off
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl
index 26db0862c..e3019fa8b 100644
--- a/src/cascadia/TerminalControl/TermControl.idl
+++ b/src/cascadia/TerminalControl/TermControl.idl
@@ -71,5 +71,7 @@ namespace Microsoft.Terminal.Control
void ToggleReadOnly();
String ReadEntireBuffer();
+
+ Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
}
}