Merge branch 'dev/migrie/f/non-terminal-content-elevation-warning' into dev/migrie/f/632-on-warning-dialog
This commit is contained in:
commit
8b8de52961
1
.github/actions/spelling/allow/allow.txt
vendored
1
.github/actions/spelling/allow/allow.txt
vendored
|
@ -1,3 +1,4 @@
|
|||
admins
|
||||
apc
|
||||
calt
|
||||
ccmp
|
||||
|
|
6
.github/actions/spelling/allow/apis.txt
vendored
6
.github/actions/spelling/allow/apis.txt
vendored
|
@ -1,5 +1,7 @@
|
|||
ACCEPTFILES
|
||||
ACCESSDENIED
|
||||
acl
|
||||
aclapi
|
||||
alignas
|
||||
alignof
|
||||
APPLYTOSUBMENUS
|
||||
|
@ -19,6 +21,7 @@ comparand
|
|||
cstdint
|
||||
CXICON
|
||||
CYICON
|
||||
Dacl
|
||||
dataobject
|
||||
dcomp
|
||||
DERR
|
||||
|
@ -113,9 +116,12 @@ OSVERSIONINFOEXW
|
|||
otms
|
||||
OUTLINETEXTMETRICW
|
||||
overridable
|
||||
PACL
|
||||
PAGESCROLL
|
||||
PEXPLICIT
|
||||
PICKFOLDERS
|
||||
pmr
|
||||
ptstr
|
||||
rcx
|
||||
REGCLS
|
||||
RETURNCMD
|
||||
|
|
|
@ -61,6 +61,9 @@ Author(s):
|
|||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
|
||||
// Common includes for most tests:
|
||||
#include "../../inc/argb.h"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
|
40
src/cascadia/TerminalApp/AdminWarningPlaceholder.cpp
Normal file
40
src/cascadia/TerminalApp/AdminWarningPlaceholder.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pch.h"
|
||||
#include "AdminWarningPlaceholder.h"
|
||||
#include "AdminWarningPlaceholder.g.cpp"
|
||||
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<winrt::Microsoft::Terminal::Control::TermControl>() })
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
31
src/cascadia/TerminalApp/AdminWarningPlaceholder.h
Normal file
31
src/cascadia/TerminalApp/AdminWarningPlaceholder.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AdminWarningPlaceholder.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct AdminWarningPlaceholder : AdminWarningPlaceholderT<AdminWarningPlaceholder>
|
||||
{
|
||||
AdminWarningPlaceholder(const winrt::Microsoft::Terminal::Control::TermControl& control, const winrt::hstring& cmdline);
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::UserControl Control();
|
||||
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<AdminWarningPlaceholder>; // 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);
|
||||
};
|
||||
}
|
11
src/cascadia/TerminalApp/AdminWarningPlaceholder.idl
Normal file
11
src/cascadia/TerminalApp/AdminWarningPlaceholder.idl
Normal file
|
@ -0,0 +1,11 @@
|
|||
// 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; };
|
||||
}
|
||||
}
|
73
src/cascadia/TerminalApp/AdminWarningPlaceholder.xaml
Normal file
73
src/cascadia/TerminalApp/AdminWarningPlaceholder.xaml
Normal file
|
@ -0,0 +1,73 @@
|
|||
<!--
|
||||
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information.
|
||||
-->
|
||||
<UserControl x:Class="TerminalApp.AdminWarningPlaceholder"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<!--
|
||||
We have to use two grids to be tricky here:
|
||||
- The outer grid will get its background from the control it hosts, and
|
||||
expands to fit its container. This will make it look like the background
|
||||
fills the space available.
|
||||
- The inner grid is set to Center,Center, so that the "dialog" appears
|
||||
centered
|
||||
-->
|
||||
|
||||
<Grid x:Name="RootGrid"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<Border Margin="8,8,8,8"
|
||||
Padding="16,8,16,8"
|
||||
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
|
||||
BorderBrush="{ThemeResource SystemAccentColor}"
|
||||
BorderThickness="2,2,2,2"
|
||||
CornerRadius="{ThemeResource OverlayCornerRadius}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock x:Uid="ApproveCommandlineWarningTitle"
|
||||
Padding="0,0,0,16"
|
||||
HorizontalAlignment="Left"
|
||||
FontSize="20"
|
||||
FontWeight="Normal"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
|
||||
<TextBlock x:Uid="ApproveCommandlineWarningPrefixTextBlock"
|
||||
HorizontalAlignment="Left"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
|
||||
<TextBlock Margin="0,8,0,8"
|
||||
HorizontalAlignment="Center"
|
||||
FontFamily="Cascadia Code"
|
||||
Text="{x:Bind Commandline, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
|
||||
<TextBlock x:Uid="ApproveCommandlineWarningSuffixTextBlock"
|
||||
HorizontalAlignment="Left"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<StackPanel HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button x:Name="PrimaryButton"
|
||||
x:Uid="ApproveCommandlineWarning_PrimaryButton"
|
||||
Margin="8"
|
||||
HorizontalAlignment="Right"
|
||||
Click="_primaryButtonClick"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
<Button x:Name="CancelButton"
|
||||
x:Uid="ApproveCommandlineWarning_CancelButton"
|
||||
HorizontalAlignment="Right"
|
||||
Click="_cancelButtonClick" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
|
@ -12,6 +12,8 @@
|
|||
#include <WtExeUtils.h>
|
||||
#include <wil/token_helpers.h >
|
||||
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
using namespace winrt::Windows::ApplicationModel;
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
|
@ -139,28 +141,8 @@ static Documents::Run _BuildErrorRun(const winrt::hstring& text, const ResourceD
|
|||
// Return Value:
|
||||
// - true if the user is an administrator
|
||||
static bool _isUserAdmin() noexcept
|
||||
try
|
||||
{
|
||||
wil::unique_handle processToken{ GetCurrentProcessToken() };
|
||||
const auto elevationType = wil::get_token_information<TOKEN_ELEVATION_TYPE>(processToken.get());
|
||||
const auto elevationState = wil::get_token_information<TOKEN_ELEVATION>(processToken.get());
|
||||
if (elevationType == TokenElevationTypeDefault && elevationState.TokenIsElevated)
|
||||
{
|
||||
// In this case, the user has UAC entirely disabled. This is sort of
|
||||
// weird, we treat this like the user isn't an admin at all. There's no
|
||||
// separation of powers, so the things we normally want to gate on
|
||||
// "having special powers" doesn't apply.
|
||||
//
|
||||
// See GH#7754, GH#11096
|
||||
return false;
|
||||
}
|
||||
|
||||
return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return false;
|
||||
return Microsoft::Console::Utils::IsElevated();
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
|
@ -899,8 +881,6 @@ namespace winrt::TerminalApp::implementation
|
|||
void AppLogic::_RegisterSettingsChange()
|
||||
{
|
||||
const std::filesystem::path settingsPath{ std::wstring_view{ CascadiaSettings::SettingsPath() } };
|
||||
const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } };
|
||||
|
||||
_reader.create(
|
||||
settingsPath.parent_path().c_str(),
|
||||
false,
|
||||
|
@ -909,14 +889,15 @@ namespace winrt::TerminalApp::implementation
|
|||
// editors, who will write a temp file, then rename it to be the
|
||||
// actual file you wrote. So listen for that too.
|
||||
wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime,
|
||||
[this, settingsBasename = settingsPath.filename(), stateBasename = statePath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) {
|
||||
const auto modifiedBasename = std::filesystem::path{ fileModified }.filename();
|
||||
[this, settingsBasename = settingsPath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) {
|
||||
static const auto appState{ ApplicationState::SharedInstance() };
|
||||
const winrt::hstring modifiedBasename{ std::filesystem::path{ fileModified }.filename().c_str() };
|
||||
|
||||
if (modifiedBasename == settingsBasename)
|
||||
{
|
||||
_reloadSettings->Run();
|
||||
}
|
||||
else if (modifiedBasename == stateBasename)
|
||||
else if (appState.IsStatePath(modifiedBasename))
|
||||
{
|
||||
_reloadState();
|
||||
}
|
||||
|
|
|
@ -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,12 @@ Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFo
|
|||
_root.Children().Append(_border);
|
||||
_border.Child(_control);
|
||||
|
||||
_connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
|
||||
_warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler });
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (termControl)
|
||||
{
|
||||
_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
|
||||
|
@ -80,7 +84,19 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const
|
|||
assert(_IsLeaf());
|
||||
|
||||
NewTerminalArgs args{};
|
||||
auto controlSettings = _control.Settings().as<TerminalSettings>();
|
||||
auto termControl{ _control.try_as<TermControl>() };
|
||||
if (!termControl)
|
||||
{
|
||||
if (auto adminWarning{ _control.try_as<AdminWarningPlaceholder>() })
|
||||
{
|
||||
termControl = adminWarning.Content().try_as<TermControl>();
|
||||
}
|
||||
}
|
||||
if (!termControl)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
auto controlSettings = termControl.Settings().as<TerminalSettings>();
|
||||
|
||||
args.Profile(controlSettings.ProfileName());
|
||||
args.StartingDirectory(controlSettings.StartingDirectory());
|
||||
|
@ -166,6 +182,7 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n
|
|||
if (_firstChild->_IsLeaf() && _secondChild->_IsLeaf())
|
||||
{
|
||||
auto actionAndArgs = buildSplitPane(_secondChild);
|
||||
|
||||
std::optional<uint32_t> focusedPaneId = std::nullopt;
|
||||
if (_firstChild->_lastActive)
|
||||
{
|
||||
|
@ -697,6 +714,33 @@ bool Pane::SwapPanes(std::shared_ptr<Pane> first, std::shared_ptr<Pane> second)
|
|||
return false;
|
||||
}
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::UserControl Pane::ReplaceControl(const winrt::Windows::UI::Xaml::Controls::UserControl& control)
|
||||
{
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& oldControl = _control;
|
||||
const auto& oldTermControl{ _control.try_as<TermControl>() };
|
||||
if (oldTermControl)
|
||||
{
|
||||
oldTermControl.ConnectionStateChanged(_connectionStateChangedToken);
|
||||
oldTermControl.WarningBell(_warningBellToken);
|
||||
}
|
||||
|
||||
_control = control;
|
||||
_border.Child(_control);
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (termControl)
|
||||
{
|
||||
_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:
|
||||
|
@ -944,8 +988,12 @@ void Pane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundatio
|
|||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto newConnectionState = _control.ConnectionState();
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (!termControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const auto newConnectionState = termControl.ConnectionState();
|
||||
const auto previousConnectionState = std::exchange(_connectionState, newConnectionState);
|
||||
|
||||
if (newConnectionState < ConnectionState::Closed)
|
||||
|
@ -988,7 +1036,9 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect
|
|||
{
|
||||
return;
|
||||
}
|
||||
if (_profile)
|
||||
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (_profile && termControl)
|
||||
{
|
||||
// We don't want to do anything if nothing is set, so check for that first
|
||||
if (static_cast<int>(_profile.BellStyle()) != 0)
|
||||
|
@ -1002,7 +1052,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
|
||||
|
@ -1057,7 +1107,11 @@ void Pane::Shutdown()
|
|||
std::unique_lock lock{ _createCloseLock };
|
||||
if (_IsLeaf())
|
||||
{
|
||||
_control.Close();
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (termControl)
|
||||
{
|
||||
termControl.Close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1067,7 +1121,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:
|
||||
// - <none>
|
||||
|
@ -1108,8 +1162,14 @@ std::shared_ptr<Pane> Pane::GetActivePane()
|
|||
// Arguments:
|
||||
// - <none>
|
||||
// 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
|
||||
{
|
||||
return _IsLeaf() ? _control.try_as<TermControl>() : nullptr;
|
||||
}
|
||||
|
||||
Controls::UserControl Pane::GetUserControl() const
|
||||
{
|
||||
return _IsLeaf() ? _control : nullptr;
|
||||
}
|
||||
|
@ -1262,9 +1322,13 @@ void Pane::_FocusFirstChild()
|
|||
void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const Profile& profile)
|
||||
{
|
||||
assert(_IsLeaf());
|
||||
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (!termControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_profile = profile;
|
||||
auto controlSettings = _control.Settings().as<TerminalSettings>();
|
||||
auto controlSettings = termControl.Settings().as<TerminalSettings>();
|
||||
// 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());
|
||||
|
@ -1277,8 +1341,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:
|
||||
|
@ -1409,8 +1473,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<TermControl>() };
|
||||
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
|
||||
|
@ -1421,21 +1489,30 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
|
|||
// handlers since it is just getting moved.
|
||||
if (!isDetaching)
|
||||
{
|
||||
closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||
closedChild->_control.WarningBell(closedChild->_warningBellToken);
|
||||
const auto& closedControl{ closedChild->_control.try_as<TermControl>() };
|
||||
if (closedControl)
|
||||
{
|
||||
closedControl.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||
closedControl.WarningBell(closedChild->_warningBellToken);
|
||||
}
|
||||
}
|
||||
|
||||
closedChild->Closed(closedChildClosedToken);
|
||||
remainingChild->Closed(remainingChildClosedToken);
|
||||
remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken);
|
||||
remainingChild->_control.WarningBell(remainingChild->_warningBellToken);
|
||||
|
||||
const auto& remainingControl{ remainingChild->_control.try_as<TermControl>() };
|
||||
if (remainingControl)
|
||||
{
|
||||
remainingControl.ConnectionStateChanged(remainingChild->_connectionStateChangedToken);
|
||||
remainingControl.WarningBell(remainingChild->_warningBellToken);
|
||||
}
|
||||
|
||||
// If either of our children was focused, we want to take that focus from
|
||||
// them.
|
||||
_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->_border.Child(nullptr);
|
||||
|
||||
|
@ -1445,7 +1522,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(_border);
|
||||
_border.Child(_control);
|
||||
|
||||
|
@ -1504,8 +1581,12 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
|
|||
closedChild->Closed(closedChildClosedToken);
|
||||
if (!isDetaching)
|
||||
{
|
||||
closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||
closedChild->_control.WarningBell(closedChild->_warningBellToken);
|
||||
const auto& closedControl{ closedChild->_control.try_as<TermControl>() };
|
||||
if (closedControl)
|
||||
{
|
||||
closedControl.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||
closedControl.WarningBell(closedChild->_warningBellToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset our UI:
|
||||
|
@ -2085,18 +2166,18 @@ std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> targe
|
|||
|
||||
// Method Description:
|
||||
// - Split the focused pane in our tree of panes, and place the given
|
||||
// TermControl into the newly created pane. If we're the focused pane, then
|
||||
// UserControl into the newly created pane. If we're the focused pane, then
|
||||
// we'll create two new children, and place them side-by-side in our Grid.
|
||||
// Arguments:
|
||||
// - splitType: what type of split we want to create.
|
||||
// - profile: The profile to associate with the newly created pane.
|
||||
// - control: A TermControl to use in the new pane.
|
||||
// - control: A UserControl to use in the new pane.
|
||||
// Return Value:
|
||||
// - The two newly created Panes, with the original pane first
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::Split(SplitDirection splitType,
|
||||
const float splitSize,
|
||||
const Profile& profile,
|
||||
const TermControl& control)
|
||||
const Controls::UserControl& control)
|
||||
{
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
|
@ -2202,11 +2283,15 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
|
|||
// modify our tree
|
||||
std::unique_lock lock{ _createCloseLock };
|
||||
|
||||
// revoke our handler - the child will take care of the control now.
|
||||
_control.ConnectionStateChanged(_connectionStateChangedToken);
|
||||
_connectionStateChangedToken.value = 0;
|
||||
_control.WarningBell(_warningBellToken);
|
||||
_warningBellToken.value = 0;
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (termControl)
|
||||
{
|
||||
// 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 what the
|
||||
// control telling us that it's now focused, we want it telling its new
|
||||
|
@ -2218,7 +2303,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
|
|||
_desiredSplitPosition = 1.0f - splitSize;
|
||||
|
||||
// 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();
|
||||
_border.Child(nullptr);
|
||||
|
||||
|
@ -2553,8 +2638,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<TermControl>() };
|
||||
if (_IsLeaf())
|
||||
{
|
||||
if (!termControl)
|
||||
{
|
||||
return { dimension, dimension };
|
||||
}
|
||||
// If we're a leaf pane, align to the grid of controlling terminal
|
||||
|
||||
const auto minSize = _GetMinSize();
|
||||
|
@ -2565,7 +2655,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;
|
||||
|
@ -2585,7 +2675,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 };
|
||||
}
|
||||
|
@ -2630,7 +2720,8 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const
|
|||
// - <none>
|
||||
void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const
|
||||
{
|
||||
if (_IsLeaf())
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (_IsLeaf() && termControl)
|
||||
{
|
||||
// We're a leaf pane, so just add one more row or column (unless isMinimumSize
|
||||
// is true, see below).
|
||||
|
@ -2645,11 +2736,20 @@ void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& si
|
|||
}
|
||||
else
|
||||
{
|
||||
const auto cellSize = _control.CharacterDimensions();
|
||||
const auto cellSize = termControl.CharacterDimensions();
|
||||
sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (_IsLeaf())
|
||||
{
|
||||
// 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 if (!_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.
|
||||
|
@ -2751,7 +2851,8 @@ Size Pane::_GetMinSize() const
|
|||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
auto controlSize = _control.MinimumSize();
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
auto controlSize = termControl ? termControl.MinimumSize() : Size{ 1, 1 };
|
||||
auto newWidth = controlSize.Width;
|
||||
auto newHeight = controlSize.Height;
|
||||
|
||||
|
@ -2940,7 +3041,10 @@ std::optional<SplitDirection> 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:
|
||||
|
@ -2953,13 +3057,14 @@ bool Pane::ContainsReadOnly() const
|
|||
// - <none>
|
||||
void Pane::CollectTaskbarStates(std::vector<winrt::TerminalApp::TaskbarState>& states)
|
||||
{
|
||||
if (_IsLeaf())
|
||||
const auto& termControl{ GetTerminalControl() };
|
||||
if (termControl)
|
||||
{
|
||||
auto tbState{ winrt::make<winrt::TerminalApp::implementation::TaskbarState>(_control.TaskbarState(),
|
||||
_control.TaskbarProgress()) };
|
||||
auto tbState{ winrt::make<winrt::TerminalApp::implementation::TaskbarState>(termControl.TaskbarState(),
|
||||
termControl.TaskbarProgress()) };
|
||||
states.push_back(tbState);
|
||||
}
|
||||
else
|
||||
else if (!_IsLeaf())
|
||||
{
|
||||
_firstChild->CollectTaskbarStates(states);
|
||||
_secondChild->CollectTaskbarStates(states);
|
||||
|
|
|
@ -55,11 +55,12 @@ class Pane : public std::enable_shared_from_this<Pane>
|
|||
{
|
||||
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);
|
||||
|
||||
std::shared_ptr<Pane> GetActivePane();
|
||||
winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl();
|
||||
winrt::Windows::UI::Xaml::Controls::UserControl GetUserControl() const;
|
||||
winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl() const;
|
||||
winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile();
|
||||
|
||||
// Method Description:
|
||||
|
@ -103,7 +104,7 @@ public:
|
|||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
|
||||
const float splitSize,
|
||||
const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
|
||||
const winrt::Microsoft::Terminal::Control::TermControl& control);
|
||||
const winrt::Windows::UI::Xaml::Controls::UserControl& control);
|
||||
bool ToggleSplitOrientation();
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
std::optional<winrt::Microsoft::Terminal::Settings::Model::SplitDirection> PreCalculateAutoSplit(const std::shared_ptr<Pane> target,
|
||||
|
@ -119,6 +120,8 @@ public:
|
|||
winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType);
|
||||
std::shared_ptr<Pane> DetachPane(std::shared_ptr<Pane> 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<Pane> zoomedPane);
|
||||
|
@ -174,7 +177,7 @@ private:
|
|||
|
||||
winrt::Windows::UI::Xaml::Controls::Grid _root{};
|
||||
winrt::Windows::UI::Xaml::Controls::Border _border{};
|
||||
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;
|
||||
|
|
|
@ -498,6 +498,21 @@
|
|||
<data name="MultiLinePasteDialog.Title" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
</data>
|
||||
<data name="ApproveCommandlineWarning_CancelButton.Content" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="ApproveCommandlineWarningPrefixTextBlock.Text" xml:space="preserve">
|
||||
<value>You are about to execute the following commandline:</value>
|
||||
</data>
|
||||
<data name="ApproveCommandlineWarningSuffixTextBlock.Text" xml:space="preserve">
|
||||
<value>Do you wish to continue?</value>
|
||||
</data>
|
||||
<data name="ApproveCommandlineWarning_PrimaryButton.Content" xml:space="preserve">
|
||||
<value>Allow Commandline</value>
|
||||
</data>
|
||||
<data name="ApproveCommandlineWarningTitle.Text" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
</data>
|
||||
<data name="CommandPalette_SearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Type a command name...</value>
|
||||
</data>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <LibraryResources.h>
|
||||
|
||||
#include "TabRowControl.h"
|
||||
#include "AdminWarningPlaceholder.h"
|
||||
#include "ColorHelper.h"
|
||||
#include "DebugTapConnection.h"
|
||||
#include "SettingsTab.h"
|
||||
|
@ -268,7 +269,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// - profile: profile settings for this connection
|
||||
// - settings: the TerminalSettings object to use to create the TerminalControl with.
|
||||
// - existingConnection: optionally receives a connection from the outside world instead of attempting to create one
|
||||
void TerminalPage::_CreateNewTabWithProfileAndSettings(const Profile& profile, const TerminalSettingsCreateResult& settings, TerminalConnection::ITerminalConnection existingConnection)
|
||||
void TerminalPage::_CreateNewTabWithProfileAndSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings, TerminalConnection::ITerminalConnection existingConnection)
|
||||
{
|
||||
// Initialize the new tab
|
||||
// Create a connection based on the values in our settings object if we weren't given one.
|
||||
|
@ -299,8 +300,19 @@ namespace winrt::TerminalApp::implementation
|
|||
// Give term control a child of the settings so that any overrides go in the child
|
||||
// This way, when we do a settings reload we just update the parent and the overrides remain
|
||||
auto term = _InitControl(settings, connection);
|
||||
WUX::Controls::UserControl controlToAdd{ term };
|
||||
|
||||
auto newTabImpl = winrt::make_self<TerminalTab>(profile, term);
|
||||
const auto& cmdline{ settings.DefaultSettings().Commandline() };
|
||||
const bool doAdminWarning = _shouldPromptForCommandline(cmdline);
|
||||
if (doAdminWarning)
|
||||
{
|
||||
auto warningControl{ winrt::make_self<implementation::AdminWarningPlaceholder>(term, cmdline) };
|
||||
warningControl->PrimaryButtonClicked({ get_weak(), &TerminalPage::_adminWarningPrimaryClicked });
|
||||
warningControl->CancelButtonClicked({ get_weak(), &TerminalPage::_adminWarningCancelClicked });
|
||||
controlToAdd = *warningControl;
|
||||
}
|
||||
|
||||
auto newTabImpl = winrt::make_self<TerminalTab>(profile, controlToAdd);
|
||||
_RegisterTerminalEvents(term);
|
||||
_InitializeTab(newTabImpl);
|
||||
|
||||
|
|
|
@ -63,6 +63,9 @@
|
|||
<Page Include="CommandPalette.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="AdminWarningPlaceholder.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
|
@ -141,6 +144,9 @@
|
|||
<ClInclude Include="AppLogic.h">
|
||||
<DependentUpon>AppLogic.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AdminWarningPlaceholder.h">
|
||||
<DependentUpon>AdminWarningPlaceholder.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Toast.h" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Cpp Files ======================== -->
|
||||
|
@ -234,6 +240,9 @@
|
|||
<ClCompile Include="AppLogic.cpp">
|
||||
<DependentUpon>AppLogic.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AdminWarningPlaceholder.cpp">
|
||||
<DependentUpon>AdminWarningPlaceholder.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="Toast.cpp" />
|
||||
</ItemGroup>
|
||||
|
@ -295,6 +304,10 @@
|
|||
<DependentUpon>CommandPalette.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="AdminWarningPlaceholder.idl">
|
||||
<DependentUpon>AdminWarningPlaceholder.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="FilteredCommand.idl" />
|
||||
<Midl Include="EmptyStringVisibilityConverter.idl" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "DebugTapConnection.h"
|
||||
#include "SettingsTab.h"
|
||||
#include "RenameWindowRequestedArgs.g.cpp"
|
||||
#include "AdminWarningPlaceholder.h"
|
||||
#include "../inc/WindowingBehavior.h"
|
||||
|
||||
#include <til/latch.h>
|
||||
|
@ -153,19 +154,7 @@ namespace winrt::TerminalApp::implementation
|
|||
_tabView = _tabRow.TabView();
|
||||
_rearranging = false;
|
||||
|
||||
// GH#2455 - Make sure to try/catch calls to Application::Current,
|
||||
// because that _won't_ be an instance of TerminalApp::App in the
|
||||
// LocalTests
|
||||
auto isElevated = false;
|
||||
try
|
||||
{
|
||||
// GH#3581 - There's a platform limitation that causes us to crash when we rearrange tabs.
|
||||
// Xaml tries to send a drag visual (to wit: a screenshot) to the drag hosting process,
|
||||
// but that process is running at a different IL than us.
|
||||
// For now, we're disabling elevated drag.
|
||||
isElevated = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated();
|
||||
}
|
||||
CATCH_LOG();
|
||||
const auto isElevated = _isElevated();
|
||||
|
||||
if (_settings.GlobalSettings().UseAcrylicInTabRow())
|
||||
{
|
||||
|
@ -690,6 +679,15 @@ namespace winrt::TerminalApp::implementation
|
|||
co_return ContentDialogResult::None;
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IAsyncOperation<ContentDialogResult> TerminalPage::_ShowCommandlineApproveWarning()
|
||||
{
|
||||
if (auto presenter{ _dialogPresenter.get() })
|
||||
{
|
||||
co_return co_await presenter.ShowDialog(FindName(L"ApproveCommandlineWarning").try_as<WUX::Controls::ContentDialog>());
|
||||
}
|
||||
co_return ContentDialogResult::None;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Builds the flyout (dropdown) attached to the new tab button, and
|
||||
// attaches it to the button. Populates the flyout with one entry per
|
||||
|
@ -1523,6 +1521,242 @@ namespace winrt::TerminalApp::implementation
|
|||
return true;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Returns true if this commandline is precisely an executable 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.
|
||||
// - Will attempt to resolve environment strings.
|
||||
// - Will also manually allow commandlines as generated for the default WSL
|
||||
// distros.
|
||||
// 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 <distroname> -> returns true
|
||||
static bool _isInSystem32(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::filesystem::path fullCommandlinePath{
|
||||
wil::ExpandEnvironmentStringsW<std::wstring>(commandLine.data())
|
||||
};
|
||||
|
||||
if (fullCommandlinePath.wstring().size() > systemDirectory.size())
|
||||
{
|
||||
// Get the first part of the executable path
|
||||
const auto start = fullCommandlinePath.wstring().substr(0, systemDirectory.size());
|
||||
const auto pathEquals = til::equals_insensitive_ascii(start, systemDirectory);
|
||||
if (pathEquals && std::filesystem::exists(fullCommandlinePath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also, if the path is literally
|
||||
// %SystemRoot%\System32\wsl.exe -d <distroname>
|
||||
// then allow it.
|
||||
|
||||
// Stolen from _tryMangleStartingDirectoryForWSL
|
||||
// Find the first space, quote or the end of the string -- we'll look
|
||||
// for wsl before that.
|
||||
const auto terminator{ commandLine.find_first_of(LR"(" )", 1) }; // look past the first character in case it starts with "
|
||||
const auto start{ til::at(commandLine, 0) == L'"' ? 1 : 0 };
|
||||
const std::filesystem::path executablePath{ commandLine.substr(start, terminator - start) };
|
||||
const auto executableFilename{ executablePath.filename().wstring() };
|
||||
|
||||
if (executableFilename == L"wsl" || executableFilename == L"wsl.exe")
|
||||
{
|
||||
// We've got a WSL -- let's just make sure it's the right one.
|
||||
if (executablePath.has_parent_path())
|
||||
{
|
||||
if (executablePath.parent_path().wstring() != systemDirectory)
|
||||
{
|
||||
return false; // it wasn't in system32!
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unqualified WSL, this is dangerous, so return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get everything after the wsl.exe
|
||||
const auto arguments{ terminator == std::wstring_view::npos ? std::wstring_view{} : commandLine.substr(terminator + 1) };
|
||||
const auto dashD{ arguments.find(L"-d ") };
|
||||
|
||||
// If we found a "-d " IMMEDIATELY AFTER wsl.exe. If it wasn't
|
||||
// immediately after, it could have been `wsl.exe --doSomethingEvil`
|
||||
if (dashD == 0)
|
||||
{
|
||||
// Using the string following "-d "...
|
||||
const auto afterDashD{ arguments.substr(dashD + 3) };
|
||||
// Find the next space
|
||||
const auto afterFirstWord = afterDashD.substr(dashD + 3).find(L" ");
|
||||
// if that space _wasn't_ at the end of the commandline, then
|
||||
// there were some other args. That means it was `wsl -d distro
|
||||
// anything`, and we should ask the user.
|
||||
//
|
||||
// So if it was at the end of the commandline, then there were
|
||||
// no other args besides the distro name.
|
||||
if (afterFirstWord == std::wstring::npos)
|
||||
{
|
||||
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 (_isInSystem32(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<AdminWarningPlaceholder>(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> pane) -> bool {
|
||||
if (pane->GetUserControl() == *warningControl)
|
||||
{
|
||||
// Hooray, we found us!
|
||||
pane->ReplaceControl(warningControl->Control());
|
||||
// Update the title, because replacing the control like
|
||||
// this is a little weird, and doesn't actually trigger
|
||||
// a TitleChanged by itself.
|
||||
tabImpl->UpdateTitle();
|
||||
// Don't return true here. We want to make sure to check
|
||||
// all the panes for the same commandline we just
|
||||
// approved.
|
||||
}
|
||||
else if (const auto& otherWarning{ winrt::get_self<AdminWarningPlaceholder>(pane->GetUserControl().try_as<TerminalApp::AdminWarningPlaceholder>()) })
|
||||
{
|
||||
// This pane wasn't us, but it did have 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 as well.
|
||||
pane->ReplaceControl(otherWarning->Control());
|
||||
tabImpl->UpdateTitle();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update the list of approved commandlines.
|
||||
auto allowedCommandlines{ ApplicationState::SharedInstance().AllowedCommandlines() };
|
||||
if (!allowedCommandlines)
|
||||
{
|
||||
allowedCommandlines = winrt::single_threaded_vector<winrt::hstring>();
|
||||
}
|
||||
|
||||
// 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<AdminWarningPlaceholder>(sender) };
|
||||
|
||||
for (const auto& tab : _tabs)
|
||||
{
|
||||
if (const auto& tabImpl{ _GetTerminalTabImpl(tab) })
|
||||
{
|
||||
tabImpl->GetRootPane()->WalkTree([warningControl](std::shared_ptr<Pane> 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 TermControl into the newly created pane.
|
||||
|
@ -1641,20 +1875,34 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
|
||||
auto newControl = _InitControl(controlSettings, controlConnection);
|
||||
WUX::Controls::UserControl controlToAdd{ newControl };
|
||||
|
||||
const auto& cmdline{ controlSettings.DefaultSettings().Commandline() };
|
||||
const bool doAdminWarning = _shouldPromptForCommandline(cmdline);
|
||||
if (doAdminWarning)
|
||||
{
|
||||
auto warningControl{ winrt::make_self<implementation::AdminWarningPlaceholder>(newControl, cmdline) };
|
||||
warningControl->PrimaryButtonClicked({ get_weak(), &TerminalPage::_adminWarningPrimaryClicked });
|
||||
warningControl->CancelButtonClicked({ get_weak(), &TerminalPage::_adminWarningCancelClicked });
|
||||
controlToAdd = *warningControl;
|
||||
}
|
||||
|
||||
// Hookup our event handlers to the new terminal
|
||||
_RegisterTerminalEvents(newControl);
|
||||
|
||||
_UnZoomIfNeeded();
|
||||
|
||||
tab.SplitPane(realSplitType, splitSize, profile, newControl);
|
||||
tab.SplitPane(realSplitType, splitSize, profile, controlToAdd);
|
||||
|
||||
// After GH#6586, the control will no longer focus itself
|
||||
// automatically when it's finished being laid out. Manually focus
|
||||
// the control here instead.
|
||||
if (_startupState == StartupState::Initialized)
|
||||
{
|
||||
_GetActiveControl().Focus(FocusState::Programmatic);
|
||||
if (const auto& activeControl{ _GetActiveControl() })
|
||||
{
|
||||
activeControl.Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
|
|
@ -197,12 +197,13 @@ namespace winrt::TerminalApp::implementation
|
|||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowCloseReadOnlyDialog();
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowMultiLinePasteWarningDialog();
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowLargePasteWarningDialog();
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowCommandlineApproveWarning();
|
||||
|
||||
void _CreateNewTabFlyout();
|
||||
void _OpenNewTabDropdown();
|
||||
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
|
||||
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane);
|
||||
void _CreateNewTabWithProfileAndSettings(const Microsoft::Terminal::Settings::Model::Profile& profile, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
|
||||
void _CreateNewTabWithProfileAndSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings);
|
||||
|
||||
winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
|
||||
|
@ -378,6 +379,12 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
winrt::fire_and_forget _OpenElevatedWT(winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
|
||||
|
||||
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;
|
||||
|
|
|
@ -109,6 +109,11 @@
|
|||
</TextBlock>
|
||||
</ContentDialog>
|
||||
|
||||
<!-- <ContentDialog x:Name="ApproveCommandlineWarning"
|
||||
x:Uid="ApproveCommandlineWarning"
|
||||
x:Load="False"
|
||||
DefaultButton="Primary" />-->
|
||||
|
||||
<local:CommandPalette x:Name="CommandPalette"
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Stretch"
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace winrt
|
|||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
TerminalTab::TerminalTab(const Profile& profile, const TermControl& control)
|
||||
TerminalTab::TerminalTab(const Profile& profile, const WUX::Controls::UserControl& control)
|
||||
{
|
||||
_rootPane = std::make_shared<Pane>(profile, control, true);
|
||||
|
||||
|
@ -430,7 +430,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();
|
||||
|
@ -493,7 +496,7 @@ namespace winrt::TerminalApp::implementation
|
|||
void TerminalTab::SplitPane(SplitDirection splitType,
|
||||
const float splitSize,
|
||||
const Profile& profile,
|
||||
TermControl& control)
|
||||
const WUX::Controls::UserControl& control)
|
||||
{
|
||||
// Make sure to take the ID before calling Split() - Split() will clear out the active pane's ID
|
||||
const auto activePaneId = _activePane->Id();
|
||||
|
@ -518,7 +521,8 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
// Add a event handlers to the new panes' GotFocus event. When the pane
|
||||
// gains focus, we'll mark it as the new active pane.
|
||||
_AttachEventHandlersToControl(newPane->Id().value(), control);
|
||||
const auto& termControl{ control.try_as<TermControl>() };
|
||||
_AttachEventHandlersToControl(newPane->Id().value(), termControl);
|
||||
_AttachEventHandlersToPane(original);
|
||||
_AttachEventHandlersToPane(newPane);
|
||||
|
||||
|
@ -830,6 +834,10 @@ namespace winrt::TerminalApp::implementation
|
|||
// - <none>
|
||||
void TerminalTab::_AttachEventHandlersToControl(const uint32_t paneId, const TermControl& control)
|
||||
{
|
||||
if (!control)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto weakThis{ get_weak() };
|
||||
auto dispatcher = TabViewItem().Dispatcher();
|
||||
ControlEventTokens events{};
|
||||
|
@ -1290,7 +1298,12 @@ namespace winrt::TerminalApp::implementation
|
|||
// - The tab's color, if any
|
||||
std::optional<winrt::Windows::UI::Color> TerminalTab::GetTabColor()
|
||||
{
|
||||
const auto currControlColor{ GetActiveTerminalControl().TabColor() };
|
||||
const auto& termControl{ GetActiveTerminalControl() };
|
||||
if (!termControl)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto currControlColor{ termControl.TabColor() };
|
||||
std::optional<winrt::Windows::UI::Color> controlTabColor;
|
||||
if (currControlColor != nullptr)
|
||||
{
|
||||
|
|
|
@ -21,7 +21,8 @@ namespace winrt::TerminalApp::implementation
|
|||
struct TerminalTab : TerminalTabT<TerminalTab, TabBase>
|
||||
{
|
||||
public:
|
||||
TerminalTab(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, const winrt::Microsoft::Terminal::Control::TermControl& control);
|
||||
TerminalTab(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
|
||||
const winrt::Windows::UI::Xaml::Controls::UserControl& control);
|
||||
TerminalTab(std::shared_ptr<Pane> rootPane);
|
||||
|
||||
// Called after construction to perform the necessary setup, which relies on weak_ptr
|
||||
|
@ -41,7 +42,7 @@ namespace winrt::TerminalApp::implementation
|
|||
void SplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
|
||||
const float splitSize,
|
||||
const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
|
||||
winrt::Microsoft::Terminal::Control::TermControl& control);
|
||||
const winrt::Windows::UI::Xaml::Controls::UserControl& control);
|
||||
|
||||
void ToggleSplitOrientation();
|
||||
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
|
||||
|
|
|
@ -2590,4 +2590,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
{
|
||||
return _core.ReadEntireBuffer();
|
||||
}
|
||||
|
||||
Media::Brush TermControl::BackgroundBrush()
|
||||
{
|
||||
return RootGrid().Background();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,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);
|
||||
|
|
|
@ -71,5 +71,7 @@ namespace Microsoft.Terminal.Control
|
|||
void ToggleReadOnly();
|
||||
|
||||
String ReadEntireBuffer();
|
||||
|
||||
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,12 @@
|
|||
#include "ActionAndArgs.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "FileUtils.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
static constexpr std::wstring_view stateFileName{ L"state.json" };
|
||||
static constexpr std::wstring_view elevatedStateFileName{ L"elevated-state.json" };
|
||||
static constexpr std::wstring_view unelevatedStateFileName{ L"user-state.json" };
|
||||
|
||||
static constexpr std::string_view TabLayoutKey{ "tabLayout" };
|
||||
static constexpr std::string_view InitialPositionKey{ "initialPosition" };
|
||||
static constexpr std::string_view InitialSizeKey{ "initialSize" };
|
||||
|
@ -60,15 +64,10 @@ using namespace ::Microsoft::Terminal::Settings::Model;
|
|||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
// Returns the application-global ApplicationState object.
|
||||
Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance()
|
||||
{
|
||||
static auto state = winrt::make_self<ApplicationState>(GetBaseSettingsPath() / stateFileName);
|
||||
return *state;
|
||||
}
|
||||
|
||||
ApplicationState::ApplicationState(std::filesystem::path path) noexcept :
|
||||
_path{ std::move(path) },
|
||||
ApplicationState::ApplicationState(const std::filesystem::path& stateRoot) noexcept :
|
||||
_sharedPath{ stateRoot / stateFileName },
|
||||
_userPath{ stateRoot / unelevatedStateFileName },
|
||||
_elevatedPath{ stateRoot / elevatedStateFileName },
|
||||
_throttler{ std::chrono::seconds(1), [this]() { _write(); } }
|
||||
{
|
||||
_read();
|
||||
|
@ -88,63 +87,48 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
_read();
|
||||
}
|
||||
|
||||
// Returns the state.json path on the disk.
|
||||
winrt::hstring ApplicationState::FilePath() const noexcept
|
||||
bool ApplicationState::IsStatePath(const winrt::hstring& filename)
|
||||
{
|
||||
return winrt::hstring{ _path.wstring() };
|
||||
static const auto sharedPath{ _sharedPath.filename() };
|
||||
static const auto elevatedPath{ _elevatedPath.filename() };
|
||||
static const auto userPath{ _userPath.filename() };
|
||||
return filename == sharedPath || filename == elevatedPath || filename == userPath;
|
||||
}
|
||||
|
||||
// Generate all getter/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
type ApplicationState::name() const noexcept \
|
||||
{ \
|
||||
const auto state = _state.lock_shared(); \
|
||||
const auto& value = state->name; \
|
||||
return value ? *value : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
void ApplicationState::name(const type& value) noexcept \
|
||||
{ \
|
||||
{ \
|
||||
auto state = _state.lock(); \
|
||||
state->name.emplace(value); \
|
||||
} \
|
||||
\
|
||||
_throttler(); \
|
||||
}
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
|
||||
// Deserializes the state.json at _path into this ApplicationState.
|
||||
// Deserializes the state.json and user-state (or elevated-state if
|
||||
// elevated) into this ApplicationState.
|
||||
// * ANY errors during app state will result in the creation of a new empty state.
|
||||
// * ANY errors during runtime will result in changes being partially ignored.
|
||||
void ApplicationState::_read() const noexcept
|
||||
try
|
||||
{
|
||||
const auto data = ReadUTF8FileIfExists(_path).value_or(std::string{});
|
||||
if (data.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string errs;
|
||||
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
|
||||
Json::Value root;
|
||||
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
|
||||
// First get shared state out of `state.json` into us
|
||||
const auto sharedData = _readSharedContents().value_or(std::string{});
|
||||
if (!sharedData.empty())
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
Json::Value root;
|
||||
if (!reader->parse(sharedData.data(), sharedData.data() + sharedData.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
|
||||
FromJson(root, FileSource::Shared);
|
||||
}
|
||||
|
||||
auto state = _state.lock();
|
||||
// GetValueForKey() comes in two variants:
|
||||
// * take a std::optional<T> reference
|
||||
// * return std::optional<T> by value
|
||||
// At the time of writing the former version skips missing fields in the json,
|
||||
// but we want to explicitly clear state fields that were removed from state.json.
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
// Then, try and get anything in user-state/elevated-state
|
||||
if (const auto localData{ _readLocalContents().value_or(std::string{}) }; !localData.empty())
|
||||
{
|
||||
Json::Value root;
|
||||
if (!reader->parse(localData.data(), localData.data() + localData.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
|
||||
FromJson(root, FileSource::Local);
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
|
@ -154,19 +138,136 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// setters that _synchronize() needs to be called again.
|
||||
void ApplicationState::_write() const noexcept
|
||||
try
|
||||
{
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
|
||||
_writeSharedContents(Json::writeString(wbuilder, ToJson(FileSource::Shared)));
|
||||
_writeLocalContents(Json::writeString(wbuilder, ToJson(FileSource::Local)));
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
// Returns the application-global ApplicationState object.
|
||||
Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance()
|
||||
{
|
||||
std::filesystem::path root{ GetBaseSettingsPath() };
|
||||
static auto state = winrt::make_self<ApplicationState>(root);
|
||||
return *state;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Loads data from the given json blob. Will only read the data that's in
|
||||
// the specified parseSource - so if we're reading the Local state file,
|
||||
// we won't destroy previously parsed Shared data.
|
||||
// - READ: there's no layering for app state.
|
||||
void ApplicationState::FromJson(const Json::Value& root, FileSource parseSource) const noexcept
|
||||
{
|
||||
auto state = _state.lock();
|
||||
// GetValueForKey() comes in two variants:
|
||||
// * take a std::optional<T> reference
|
||||
// * return std::optional<T> by value
|
||||
// At the time of writing the former version skips missing fields in the json,
|
||||
// but we want to explicitly clear state fields that were removed from state.json.
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
if (parseSource == source) \
|
||||
state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
|
||||
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
}
|
||||
|
||||
Json::Value ApplicationState::ToJson(FileSource parseSource) const noexcept
|
||||
{
|
||||
Json::Value root{ Json::objectValue };
|
||||
|
||||
{
|
||||
auto state = _state.lock_shared();
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name);
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
if (WI_IsFlagSet(parseSource, source)) \
|
||||
JsonUtils::SetValueForKey(root, key, state->name);
|
||||
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
}
|
||||
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
const auto content = Json::writeString(wbuilder, root);
|
||||
WriteUTF8FileAtomic(_path, content);
|
||||
return root;
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
// Generate all getter/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
type ApplicationState::name() const noexcept \
|
||||
{ \
|
||||
const auto state = _state.lock_shared(); \
|
||||
const auto& value = state->name; \
|
||||
return value ? *value : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
void ApplicationState::name(const type& value) noexcept \
|
||||
{ \
|
||||
{ \
|
||||
auto state = _state.lock(); \
|
||||
state->name.emplace(value); \
|
||||
} \
|
||||
\
|
||||
_throttler(); \
|
||||
}
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
|
||||
// Method Description:
|
||||
// - Read the contents of our "shared" state - state that should be shared
|
||||
// for elevated and unelevated instances. This is things like the list of
|
||||
// generated profiles, the command palette commandlines.
|
||||
std::optional<std::string> ApplicationState::_readSharedContents() const
|
||||
{
|
||||
return ReadUTF8FileIfExists(_sharedPath);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Read the contents of our "local" state - state that should be kept in
|
||||
// separate files for elevated and unelevated instances. This is things
|
||||
// like the persisted window state, and the approved commandlines (though,
|
||||
// those don't matter when unelevated).
|
||||
// - When elevated, this will DELETE `elevated-state.json` if it has bad
|
||||
// permissions, so we don't potentially read malicious data.
|
||||
std::optional<std::string> ApplicationState::_readLocalContents() const
|
||||
{
|
||||
return ::Microsoft::Console::Utils::IsElevated() ?
|
||||
ReadUTF8FileIfExists(_elevatedPath, true) :
|
||||
ReadUTF8FileIfExists(_userPath, false);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Write the contents of our "shared" state - state that should be shared
|
||||
// for elevated and unelevated instances. This will atomically write to
|
||||
// `state.json`
|
||||
void ApplicationState::_writeSharedContents(const std::string_view content) const
|
||||
{
|
||||
WriteUTF8FileAtomic(_sharedPath, content);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Write the contents of our "local" state - state that should be kept in
|
||||
// separate files for elevated and unelevated instances. When elevated,
|
||||
// this will write to `elevated-state.json`, and when unelevated, this
|
||||
// will atomically write to `user-state.json`
|
||||
void ApplicationState::_writeLocalContents(const std::string_view content) const
|
||||
{
|
||||
if (::Microsoft::Console::Utils::IsElevated())
|
||||
{
|
||||
// DON'T use WriteUTF8FileAtomic, which will write to a temporary file
|
||||
// then rename that file to the final filename. That actually lets us
|
||||
// overwrite the elevate file's contents even when unelevated, because
|
||||
// we're effectively deleting the original file, then renaming a
|
||||
// different file in it's place.
|
||||
//
|
||||
// We're not worried about someone else doing that though, if they do
|
||||
// that with the wrong permissions, then we'll just ignore the file and
|
||||
// start over.
|
||||
WriteUTF8File(_elevatedPath, content, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteUTF8FileAtomic(_userPath, content);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,20 +16,30 @@ Abstract:
|
|||
#include "WindowLayout.g.h"
|
||||
|
||||
#include <inc/cppwinrt_utils.h>
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
#include <JsonUtils.h>
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
// If a property is Shared, then it'll be stored in `state.json`, and used
|
||||
// in both elevated and unelevated instances of the Terminal. If a property
|
||||
// is marked Local, then it will have separate values for elevated and
|
||||
// unelevated instances.
|
||||
enum FileSource : int
|
||||
{
|
||||
Shared = 0x1,
|
||||
Local = 0x2
|
||||
};
|
||||
DEFINE_ENUM_FLAG_OPERATORS(FileSource);
|
||||
|
||||
// This macro generates all getters and setters for ApplicationState.
|
||||
// It provides X with the following arguments:
|
||||
// (type, function name, JSON key, ...variadic construction arguments)
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
#define MTSM_APPLICATION_STATE_FIELDS(X) \
|
||||
X(std::unordered_set<winrt::guid>, GeneratedProfiles, "generatedProfiles") \
|
||||
X(Windows::Foundation::Collections::IVector<Model::WindowLayout>, PersistedWindowLayouts, "persistedWindowLayouts") \
|
||||
X(Windows::Foundation::Collections::IVector<hstring>, RecentCommands, "recentCommands") \
|
||||
X(Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage>, DismissedMessages, "dismissedMessages")
|
||||
// (source, type, function name, JSON key, ...variadic construction arguments)
|
||||
#define MTSM_APPLICATION_STATE_FIELDS(X) \
|
||||
X(FileSource::Shared, std::unordered_set<winrt::guid>, GeneratedProfiles, "generatedProfiles") \
|
||||
X(FileSource::Local, Windows::Foundation::Collections::IVector<Model::WindowLayout>, PersistedWindowLayouts, "persistedWindowLayouts") \
|
||||
X(FileSource::Shared, Windows::Foundation::Collections::IVector<hstring>, RecentCommands, "recentCommands") \
|
||||
X(FileSource::Shared, Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage>, DismissedMessages, "dismissedMessages") \
|
||||
X(FileSource::Local, Windows::Foundation::Collections::IVector<hstring>, AllowedCommandlines, "allowedCommandlines")
|
||||
|
||||
struct WindowLayout : WindowLayoutT<WindowLayout>
|
||||
{
|
||||
|
@ -40,22 +50,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
friend ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<Model::WindowLayout>;
|
||||
};
|
||||
|
||||
struct ApplicationState : ApplicationStateT<ApplicationState>
|
||||
struct ApplicationState : public ApplicationStateT<ApplicationState>
|
||||
{
|
||||
static Microsoft::Terminal::Settings::Model::ApplicationState SharedInstance();
|
||||
|
||||
ApplicationState(std::filesystem::path path) noexcept;
|
||||
ApplicationState(const std::filesystem::path& stateRoot) noexcept;
|
||||
~ApplicationState();
|
||||
|
||||
// Methods
|
||||
void Reload() const noexcept;
|
||||
void FromJson(const Json::Value& root, FileSource parseSource) const noexcept;
|
||||
Json::Value ToJson(FileSource parseSource) const noexcept;
|
||||
|
||||
// General getters/setters
|
||||
winrt::hstring FilePath() const noexcept;
|
||||
bool IsStatePath(const winrt::hstring& filename);
|
||||
|
||||
// State getters/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
type name() const noexcept; \
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
type name() const noexcept; \
|
||||
void name(const type& value) noexcept;
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
|
@ -63,17 +75,23 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
private:
|
||||
struct state_t
|
||||
{
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) std::optional<type> name{ __VA_ARGS__ };
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) std::optional<type> name{ __VA_ARGS__ };
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
};
|
||||
til::shared_mutex<state_t> _state;
|
||||
std::filesystem::path _sharedPath;
|
||||
std::filesystem::path _userPath;
|
||||
std::filesystem::path _elevatedPath;
|
||||
til::throttled_func_trailing<> _throttler;
|
||||
|
||||
void _write() const noexcept;
|
||||
void _read() const noexcept;
|
||||
|
||||
std::filesystem::path _path;
|
||||
til::shared_mutex<state_t> _state;
|
||||
til::throttled_func_trailing<> _throttler;
|
||||
std::optional<std::string> _readSharedContents() const;
|
||||
void _writeSharedContents(const std::string_view content) const;
|
||||
std::optional<std::string> _readLocalContents() const;
|
||||
void _writeLocalContents(const std::string_view content) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -25,12 +25,15 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
|
||||
void Reload();
|
||||
|
||||
String FilePath { get; };
|
||||
Boolean IsStatePath(String filename);
|
||||
|
||||
Windows.Foundation.Collections.IVector<WindowLayout> PersistedWindowLayouts { get; set; };
|
||||
|
||||
Windows.Foundation.Collections.IVector<String> RecentCommands { get; set; };
|
||||
|
||||
Windows.Foundation.Collections.IVector<InfoBarMessage> DismissedMessages { get; set; };
|
||||
|
||||
Windows.Foundation.Collections.IVector<String> AllowedCommandlines { get; set; };
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include <shlobj.h>
|
||||
#include <WtExeUtils.h>
|
||||
|
||||
#include <aclapi.h>
|
||||
|
||||
static constexpr std::string_view Utf8Bom{ u8"\uFEFF" };
|
||||
static constexpr std::wstring_view UnpackagedSettingsFolderName{ L"Microsoft\\Windows Terminal\\" };
|
||||
|
||||
|
@ -39,10 +41,89 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
|||
return baseSettingsPath;
|
||||
}
|
||||
|
||||
static bool _hasExpectedPermissions(const std::filesystem::path& path)
|
||||
{
|
||||
// If we want to only open the file if it's elevated, check the
|
||||
// permissions on this file. We want to make sure that:
|
||||
// * Everyone has permission to read
|
||||
// * admins can do anything
|
||||
// * no one else can do anything.
|
||||
PACL pAcl{ nullptr }; // This doesn't need to be cleanup up apparently
|
||||
|
||||
auto status = GetNamedSecurityInfo(path.c_str(),
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&pAcl,
|
||||
nullptr,
|
||||
nullptr);
|
||||
THROW_IF_WIN32_ERROR(status);
|
||||
|
||||
PEXPLICIT_ACCESS pEA{ nullptr };
|
||||
DWORD count = 0;
|
||||
status = GetExplicitEntriesFromAcl(pAcl, &count, &pEA);
|
||||
THROW_IF_WIN32_ERROR(status);
|
||||
|
||||
auto explicitAccessCleanup = wil::scope_exit([&]() { ::LocalFree(pEA); });
|
||||
|
||||
if (count != 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now, get the Everyone and Admins SIDS so we can make sure they're
|
||||
// the ones in this file.
|
||||
|
||||
wil::unique_sid everyoneSid;
|
||||
wil::unique_sid adminGroupSid;
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
|
||||
|
||||
// Create a SID for the BUILTIN\Administrators group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid));
|
||||
|
||||
// Create a well-known SID for the Everyone group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid));
|
||||
|
||||
bool hadExpectedPermissions = true;
|
||||
|
||||
// Check that the permissions are what we'd expect them to be if only
|
||||
// admins can write to the file. This is basically a mirror of what we
|
||||
// set up in `WriteUTF8File`.
|
||||
|
||||
// For grfAccessPermissions, GENERIC_ALL turns into STANDARD_RIGHTS_ALL,
|
||||
// and GENERIC_READ -> READ_CONTROL
|
||||
hadExpectedPermissions &= WI_AreAllFlagsSet(pEA[0].grfAccessPermissions, STANDARD_RIGHTS_ALL);
|
||||
hadExpectedPermissions &= pEA[0].grfInheritance == NO_INHERITANCE;
|
||||
hadExpectedPermissions &= pEA[0].Trustee.TrusteeForm == TRUSTEE_IS_SID;
|
||||
// SIDs are void*'s that happen to convert to a wchar_t
|
||||
hadExpectedPermissions &= *(pEA[0].Trustee.ptstrName) == *(LPWSTR)(adminGroupSid.get());
|
||||
|
||||
// Now check the other EXPLICIT_ACCESS
|
||||
hadExpectedPermissions &= WI_IsFlagSet(pEA[1].grfAccessPermissions, READ_CONTROL);
|
||||
hadExpectedPermissions &= pEA[1].grfInheritance == NO_INHERITANCE;
|
||||
hadExpectedPermissions &= pEA[1].Trustee.TrusteeForm == TRUSTEE_IS_SID;
|
||||
hadExpectedPermissions &= *(pEA[1].Trustee.ptstrName) == *(LPWSTR)(everyoneSid.get());
|
||||
|
||||
return hadExpectedPermissions;
|
||||
}
|
||||
// Tries to read a file somewhat atomically without locking it.
|
||||
// Strips the UTF8 BOM if it exists.
|
||||
std::string ReadUTF8File(const std::filesystem::path& path)
|
||||
std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly)
|
||||
{
|
||||
if (elevatedOnly)
|
||||
{
|
||||
const bool hadExpectedPermissions{ _hasExpectedPermissions(path) };
|
||||
if (!hadExpectedPermissions)
|
||||
{
|
||||
// delete the file. It's been compromised.
|
||||
LOG_LAST_ERROR_IF(!DeleteFile(path.c_str()));
|
||||
// Exit early, because obviously there's nothing to read from the deleted file.
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// From some casual observations we can determine that:
|
||||
// * ReadFile() always returns the requested amount of data (unless the file is smaller)
|
||||
// * It's unlikely that the file was changed between GetFileSize() and ReadFile()
|
||||
|
@ -89,11 +170,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
|||
}
|
||||
|
||||
// Same as ReadUTF8File, but returns an empty optional, if the file couldn't be opened.
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path)
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
return { ReadUTF8File(path) };
|
||||
return { ReadUTF8File(path, elevatedOnly) };
|
||||
}
|
||||
catch (const wil::ResultException& exception)
|
||||
{
|
||||
|
@ -106,9 +187,75 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
|||
}
|
||||
}
|
||||
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content)
|
||||
void WriteUTF8File(const std::filesystem::path& path,
|
||||
const std::string_view& content,
|
||||
const bool elevatedOnly)
|
||||
{
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
if (elevatedOnly)
|
||||
{
|
||||
// This is very vaguely taken from
|
||||
// https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c--
|
||||
// With using https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids
|
||||
// to find out that
|
||||
// * SECURITY_NT_AUTHORITY+SECURITY_LOCAL_SYSTEM_RID == NT AUTHORITY\SYSTEM
|
||||
// * SECURITY_NT_AUTHORITY+SECURITY_BUILTIN_DOMAIN_RID+DOMAIN_ALIAS_RID_ADMINS == BUILTIN\Administrators
|
||||
// * SECURITY_WORLD_SID_AUTHORITY+SECURITY_WORLD_RID == Everyone
|
||||
//
|
||||
// Raymond Chen recommended that I make this file only writable by
|
||||
// SYSTEM, but if I did that, then even we can't write the file
|
||||
// while elevated, which isn't what we want.
|
||||
|
||||
wil::unique_sid everyoneSid;
|
||||
wil::unique_sid adminGroupSid;
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
|
||||
|
||||
// Create a SID for the BUILTIN\Administrators group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid));
|
||||
|
||||
// Create a well-known SID for the Everyone group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid));
|
||||
|
||||
EXPLICIT_ACCESS ea[2]{};
|
||||
|
||||
// Grant Admins all permissions on this file
|
||||
ea[0].grfAccessPermissions = GENERIC_ALL;
|
||||
ea[0].grfAccessMode = SET_ACCESS;
|
||||
ea[0].grfInheritance = NO_INHERITANCE;
|
||||
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
||||
ea[0].Trustee.ptstrName = (LPWSTR)(adminGroupSid.get());
|
||||
|
||||
// Grant Everyone the permission or read this file
|
||||
ea[1].grfAccessPermissions = GENERIC_READ;
|
||||
ea[1].grfAccessMode = SET_ACCESS;
|
||||
ea[1].grfInheritance = NO_INHERITANCE;
|
||||
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
||||
ea[1].Trustee.ptstrName = (LPWSTR)(everyoneSid.get());
|
||||
|
||||
ACL acl;
|
||||
PACL pAcl = &acl;
|
||||
THROW_IF_WIN32_ERROR(SetEntriesInAcl(2, ea, nullptr, &pAcl));
|
||||
|
||||
SECURITY_DESCRIPTOR sd;
|
||||
THROW_IF_WIN32_BOOL_FALSE(InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION));
|
||||
THROW_IF_WIN32_BOOL_FALSE(SetSecurityDescriptorDacl(&sd, true, pAcl, false));
|
||||
|
||||
// Initialize a security attributes structure.
|
||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa.lpSecurityDescriptor = &sd;
|
||||
sa.bInheritHandle = false;
|
||||
}
|
||||
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(),
|
||||
GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
elevatedOnly ? &sa : nullptr,
|
||||
CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr) };
|
||||
THROW_LAST_ERROR_IF(!file);
|
||||
|
||||
const auto fileSize = gsl::narrow<DWORD>(content.size());
|
||||
|
@ -121,7 +268,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
|||
}
|
||||
}
|
||||
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view& content)
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path,
|
||||
const std::string_view& content)
|
||||
{
|
||||
// GH#10787: rename() will replace symbolic links themselves and not the path they point at.
|
||||
// It's thus important that we first resolve them before generating temporary path.
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
std::filesystem::path GetBaseSettingsPath();
|
||||
std::string ReadUTF8File(const std::filesystem::path& path);
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path);
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content);
|
||||
std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly = false);
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly = false);
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content, const bool elevatedOnly = false);
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view& content);
|
||||
}
|
||||
|
|
|
@ -382,7 +382,6 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
|
|||
};
|
||||
|
||||
template<typename T>
|
||||
|
||||
struct ConversionTrait<std::unordered_map<std::string, T>>
|
||||
{
|
||||
std::unordered_map<std::string, T> FromJson(const Json::Value& json) const
|
||||
|
|
|
@ -37,7 +37,12 @@ std::wstring_view WslDistroGenerator::GetNamespace() const noexcept
|
|||
static winrt::com_ptr<implementation::Profile> makeProfile(const std::wstring& distName)
|
||||
{
|
||||
const auto WSLDistro{ CreateDynamicProfile(distName) };
|
||||
WSLDistro->Commandline(winrt::hstring{ L"wsl.exe -d " + distName });
|
||||
// GH#11096 - make sure the WSL path starts explicitly with
|
||||
// C:\Windows\System32. Don't want someone path hijacking wsl.exe.
|
||||
wil::unique_cotaskmem_string systemPath;
|
||||
THROW_IF_FAILED(wil::GetSystemDirectoryW(systemPath));
|
||||
std::wstring command(systemPath.get());
|
||||
WSLDistro->Commandline(winrt::hstring{ command + L"\\wsl.exe -d " + distName });
|
||||
WSLDistro->DefaultAppearance().ColorSchemeName(L"Campbell");
|
||||
WSLDistro->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY });
|
||||
WSLDistro->Icon(L"ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png");
|
||||
|
|
|
@ -53,3 +53,6 @@ TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider);
|
|||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
|
|
|
@ -10,13 +10,13 @@
|
|||
{
|
||||
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
|
||||
"name": "Windows PowerShell",
|
||||
"commandline": "powershell.exe",
|
||||
"commandline": "%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
// "name" is filled in by CascadiaSettings as a localized string.
|
||||
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
|
||||
"commandline": "cmd.exe",
|
||||
"commandline": "%SystemRoot%\\System32\\cmd.exe",
|
||||
"hidden": false
|
||||
}
|
||||
]
|
||||
|
|
|
@ -94,4 +94,5 @@ namespace Microsoft::Console::Utils
|
|||
|
||||
GUID CreateV5Uuid(const GUID& namespaceGuid, const gsl::span<const gsl::byte> name);
|
||||
|
||||
bool IsElevated();
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include "inc/utils.hpp"
|
||||
#include "inc/colorTable.hpp"
|
||||
|
||||
#include <wil/token_helpers.h >
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
|
||||
// Routine Description:
|
||||
|
@ -559,3 +561,33 @@ GUID Utils::CreateV5Uuid(const GUID& namespaceGuid, const gsl::span<const gsl::b
|
|||
::memcpy_s(&newGuid, sizeof(GUID), buffer.data(), sizeof(GUID));
|
||||
return EndianSwap(newGuid);
|
||||
}
|
||||
|
||||
bool Utils::IsElevated()
|
||||
{
|
||||
static bool isElevated = []() {
|
||||
try
|
||||
{
|
||||
wil::unique_handle processToken{ GetCurrentProcessToken() };
|
||||
const auto elevationType = wil::get_token_information<TOKEN_ELEVATION_TYPE>(processToken.get());
|
||||
const auto elevationState = wil::get_token_information<TOKEN_ELEVATION>(processToken.get());
|
||||
if (elevationType == TokenElevationTypeDefault && elevationState.TokenIsElevated)
|
||||
{
|
||||
// In this case, the user has UAC entirely disabled. This is sort of
|
||||
// weird, we treat this like the user isn't an admin at all. There's no
|
||||
// separation of powers, so the things we normally want to gate on
|
||||
// "having special powers" doesn't apply.
|
||||
//
|
||||
// See GH#7754, GH#11096
|
||||
return false;
|
||||
}
|
||||
|
||||
return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
return isElevated;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue