Compare commits

...

124 Commits

Author SHA1 Message Date
Mike Griese 065e5f7d90 fix unit test build 2021-11-16 07:07:01 -06:00
Mike Griese 841ced7e57 cant type when it's 26 degrees in here 2021-11-16 06:06:42 -06:00
Mike Griese 056446cd3e guard some GetActiveTerminalControl calls 2021-11-16 05:58:06 -06:00
Mike Griese 25947c2f40 cleanup for the tests" 2021-11-16 05:35:47 -06:00
Mike Griese 2445cedb92 this passes all the tests 2021-11-16 05:13:16 -06:00
Mike Griese 8e43c9d8ce more tests 2021-11-15 11:22:41 -06:00
Mike Griese fd72b7992e Merge remote-tracking branch 'origin/main' into dev/migrie/f/non-terminal-content-elevation-warning 2021-11-15 10:39:36 -06:00
Mike Griese bad27a97ba THIS NEEDS TO GO TO THE PARENT
(cherry picked from commit b499d44d4baf21c279dbb9f3a766bc9c37528b62)
2021-11-11 17:05:28 -06:00
Mike Griese 97d11d1bd3 Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-11-11 13:01:37 -06:00
Mike Griese 7f03d4d1ea dustins nits 2021-11-11 12:58:50 -06:00
Mike Griese 33e96e7e66 mitigate a TOCTOU 2021-11-11 12:56:05 -06:00
Mike Griese 999f21fcf8 Merge remote-tracking branch 'origin/main' into dev/migrie/f/just-elevated-state-2 2021-11-11 11:55:05 -06:00
Mike Griese 08cbd16d47 the last of it? 2021-11-10 10:17:41 -06:00
Mike Griese db9cbf3fa8 spell 2021-11-10 10:16:31 -06:00
Mike Griese 7024f44c96 whoops 2021-11-10 10:01:52 -06:00
Mike Griese 1c66877b72 pwsh core fixed too 2021-11-09 12:44:05 -06:00
Mike Griese 5253c114ae make sure event handlers get replaced, too 2021-11-09 11:57:46 -06:00
Mike Griese fdc574929b make text selectable 2021-11-09 10:34:54 -06:00
Mike Griese c09bdbd25e Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-11-09 10:32:47 -06:00
Mike Griese ce6a9c571b Merge remote-tracking branch 'origin/main' into dev/migrie/f/just-elevated-state-2 2021-11-09 08:24:25 -06:00
Mike Griese a93d17ef09 I believe this is the rest of the comments 2021-11-09 08:24:18 -06:00
Mike Griese 4f16dfb5fd many comments 2021-11-09 08:01:47 -06:00
Mike Griese b21287140d this is the right way to initialize the unique_hlocal_security_descriptor 2021-11-09 07:47:12 -06:00
Mike Griese 25b2675d8d this works really quite well 2021-11-09 06:24:16 -06:00
Mike Griese fd849a5241 trying to do the thing eryksun mentioned. This seems to actually work to prevent non-admins from writing the file, and BOY is it simple. Still doesn't prevent the vim situation, but also do we need to prevent that footgun? probably 2021-11-08 14:30:07 -06:00
Mike Griese 4976a091a0 this is a collection of the trivial nits while I wait on a reply to the hard questions 2021-11-04 12:48:45 -05:00
Mike Griese c90eb8763a good catch 2021-11-01 09:09:33 -05:00
Mike Griese 25c34dfcad Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-11-01 08:55:07 -05:00
Mike Griese 9b4ae9ec55 Merge remote-tracking branch 'origin/main' into dev/migrie/f/just-elevated-state-2 2021-11-01 08:53:07 -05:00
Mike Griese 3a8a83a810 last pr nits 2021-11-01 08:52:47 -05:00
Mike Griese bdf08165d4 spel 2021-10-28 08:20:09 -05:00
Mike Griese b9979ffaf8 Esc to dismiss too 2021-10-28 07:03:11 -05:00
Mike Griese 7fb490629b More cleanup 2021-10-27 16:04:32 -05:00
Mike Griese d6d708796a Cleanup from the experimentation phase 2021-10-27 15:59:32 -05:00
Mike Griese 242de14722 Incredible that this works at all 2021-10-27 15:06:40 -05:00
Mike Griese 69f1068050 update appearance 2021-10-27 12:10:28 -05:00
Mike Griese 90b79624ca Merge remote-tracking branch 'origin/dev/lhecker/proxy-no-default-lib' into dev/migrie/f/non-terminal-content-elevation-warning 2021-10-27 11:55:21 -05:00
Leonard Hecker fe5a78cff1 Fix OpenConsoleProxy for Debug builds 2021-10-27 18:35:29 +02:00
Mike Griese 21d6ffe89c Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-10-27 11:04:36 -05:00
Mike Griese 945c81d1df Merge remote-tracking branch 'origin/main' into dev/migrie/f/just-elevated-state-2 2021-10-27 10:38:52 -05:00
Mike Griese faa06f807d Merge remote-tracking branch 'origin/main' into dev/migrie/f/just-elevated-state-2 2021-10-07 11:49:00 -05:00
Mike Griese bf3c6e7029 spel is hard 2021-09-30 12:53:08 -05:00
Mike Griese f49c3fca01 Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-30 12:49:03 -05:00
Mike Griese ae99ce9c36 teh 2021-09-30 12:48:09 -05:00
Mike Griese 0e7217d354 Merge commit 'aea37520b3cdb1c1752a6c8e0ff598991518ce28' into dev/migrie/f/just-elevated-state-2 2021-09-30 12:47:33 -05:00
Mike Griese aea37520b3 we want this 2021-09-30 12:47:03 -05:00
Mike Griese 3c1866ac53 Merge remote-tracking branch 'origin/dev/migrie/b/1.12-crash-on-exit' into dev/migrie/f/investigate-crashing 2021-09-30 11:28:32 -05:00
Mike Griese 48b20de4f4 Add some logging. Can't seem to get the crash to repro? 2021-09-30 11:15:35 -05:00
Mike Griese 9ff2775122 Merge branch 'dev/migrie/f/just-trying-something-on-just-elevated-state-2' into dev/migrie/f/just-elevated-state-2 2021-09-30 10:58:04 -05:00
Mike Griese b4e0496eff cleanup 2021-09-30 10:27:57 -05:00
Mike Griese ff333870fc Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-30 10:06:23 -05:00
Mike Griese 866832b665 This seemingly works the way I'd expect, going to merge into the warning dialog and check 2021-09-30 09:39:17 -05:00
Mike Griese 56992296bf
Apply suggestions from code review
Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com>
2021-09-30 08:25:16 -05:00
Mike Griese d053f6cc9e Merge remote-tracking branch 'origin/main' into dev/migrie/f/just-elevated-state-2 2021-09-30 07:45:23 -05:00
Mike Griese a751156fcc Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-28 10:53:35 -05:00
Mike Griese 620ee302fd Merge remote-tracking branch 'origin/main' into dev/migrie/f/just-elevated-state-2
Tossed out the AppState changes from #11083, since we're planning on just
  moving that to the Monarch anyways.
2021-09-28 10:36:29 -05:00
Mike Griese abb847b4c7 pr nits from miniksa 2021-09-27 10:29:38 -05:00
Mike Griese a3ac32ab25 derp 2021-09-22 15:57:44 -05:00
Mike Griese 5e9d0b8195 use the background from the control when we can 2021-09-22 15:49:41 -05:00
Mike Griese 62fe8235dc Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-22 14:50:12 -05:00
Mike Griese 5ff9a247d4 okay so I guess that's not a word 2021-09-22 14:49:46 -05:00
Mike Griese 7e2b371dae Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-22 14:48:29 -05:00
Mike Griese 02e9871f2a Merge remote-tracking branch 'origin/main' into dev/migrie/f/just-elevated-state-2 2021-09-22 14:46:25 -05:00
Mike Griese eee657b502 fix hot reloading for this file 2021-09-22 14:37:33 -05:00
Mike Griese a6e044d91c pr nits 2021-09-22 14:37:32 -05:00
Mike Griese f3738f5c1b Move window state, approvedCommandlines into `user-state.json`
This fixes the issue I had in the last commit. It's a little weird, but gets
  rid of the muckiness of layering. Things that are local to one elevation level
  won't pollute the other, and we don't need to worry about layering or where
  they came from. Just write shared state to `state.json`, and window state to
  `elevated-state`/`user-state`
2021-09-22 14:37:32 -05:00
Mike Griese 7e2e4eaf49 I think this works ALMOST as I want
* [x] delete all state, open terminal elevated - generated profiles go to the correct place, approved commandlines go to the elevated one.
  * [x] delete some dynamic profiles, open the terminal - they don't resurrect in either IL
  * [ ] GAH saving a window layout unelevated, then trying to save one elevated will blow away the unelevated one
2021-09-22 14:37:25 -05:00
Mike Griese 8635537ebc fix hot reloading for this file 2021-09-22 14:35:02 -05:00
Mike Griese de9dc32aa0 pr nits 2021-09-22 12:26:33 -05:00
Mike Griese 64d02f2b2b Move window state, approvedCommandlines into `user-state.json`
This fixes the issue I had in the last commit. It's a little weird, but gets
  rid of the muckiness of layering. Things that are local to one elevation level
  won't pollute the other, and we don't need to worry about layering or where
  they came from. Just write shared state to `state.json`, and window state to
  `elevated-state`/`user-state`
2021-09-22 12:07:34 -05:00
Mike Griese 94c4cca176 I think this works ALMOST as I want
* [x] delete all state, open terminal elevated - generated profiles go to the correct place, approved commandlines go to the elevated one.
  * [x] delete some dynamic profiles, open the terminal - they don't resurrect in either IL
  * [ ] GAH saving a window layout unelevated, then trying to save one elevated will blow away the unelevated one
2021-09-22 11:23:33 -05:00
Mike Griese b2796019f8 Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-22 10:03:45 -05:00
Mike Griese b755eb0f20 Merge remote-tracking branch 'origin/main' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-22 10:03:37 -05:00
Mike Griese b1b1befeb9 this is a crazy idea that I hate but gotta start somewhere 2021-09-22 10:03:07 -05:00
Mike Griese 507a48ed68 Merge remote-tracking branch 'origin/main' into dev/migrie/f/just-elevated-state-2 2021-09-22 08:27:01 -05:00
Mike Griese 5a8e27e60a Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-20 12:42:59 -05:00
Mike Griese 9b3b9e0109 remove baseapplicationstate and just merge it back in 2021-09-20 12:42:41 -05:00
Mike Griese 7f9f75cab3 gah, I might have broke persisting window state 2021-09-20 12:11:30 -05:00
Mike Griese d0f05f60e9 Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-20 11:38:19 -05:00
Mike Griese 56850639c5 Merge remote-tracking branch 'origin/main' into dev/migrie/f/just-elevated-state-2 2021-09-20 11:34:59 -05:00
Mike Griese a4acdeb5f2 blindly remove ElevatedState 2021-09-20 11:34:11 -05:00
Mike Griese 56d5f9ee3a every day I'm mangling 2021-09-14 16:44:38 -05:00
Mike Griese d944a68ded manually allow env var parsing. Remember that wsl needs manual allows too 2021-09-14 16:07:47 -05:00
Mike Griese 723037ef99 fix the focus thing, frick the env vars thing 2021-09-14 15:47:01 -05:00
Mike Griese 54a002762a comments 2021-09-14 15:36:03 -05:00
Mike Griese 47d55a8fd0 don't do the lookup for things in system32 2021-09-14 15:22:20 -05:00
Mike Griese edd71265d8 Merge branch 'dev/migrie/f/just-elevated-state-2' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-14 12:16:25 -05:00
Mike Griese 6757452d6d fix the tests 2021-09-14 12:14:50 -05:00
Mike Griese 4e69a32de7 bunch of new allowed words 2021-09-14 06:01:24 -05:00
Mike Griese da0cc7bae5 todo! in the code 2021-09-14 05:59:54 -05:00
Mike Griese 51e0473560 minor nits
(cherry picked from commit 306ad30753)
2021-09-14 05:49:10 -05:00
Mike Griese c106f64bc7 🤦
(cherry picked from commit 00c7647594)
2021-09-14 05:49:04 -05:00
Mike Griese 6be697221d This is everything from dev/migrie/f/non-terminal-content-elevation-warning for specifically ElevatedState
(cherry picked from commit 97b0f06504)
2021-09-14 05:48:56 -05:00
Mike Griese d6c2fb593d THis fixes #7754 2021-09-14 05:20:45 -05:00
Mike Griese 1e3a319314 more unique_sid's, and m,ore comments 2021-09-13 14:32:46 -05:00
Mike Griese 4da965f901 Merge remote-tracking branch 'origin/main' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-13 14:20:03 -05:00
Mike Griese 4f697eca92 I thing this was unneeded 2021-09-13 12:11:04 -05:00
Mike Griese 1111d41347 This works to do the 'blow the file away when permissions have changed' thing 2021-09-13 11:45:11 -05:00
Mike Griese 6265f4f1d7 this looks like it checks the permissions as we'd expect 2021-09-09 14:00:51 -05:00
Mike Griese 7854abe0a3 I bet that's what I was doing wrong. The rename is just dandy, it don't care about elevation or none of that, so the unelevated terminal could atomically rewrite the elevated file, which isn't what we wanted. 2021-09-09 12:09:06 -05:00
Mike Griese 9a1cf5ac6e bunch of dead ends 2021-09-09 12:03:42 -05:00
Mike Griese aef422d5f1 This is definitely not right.
Sublime can delete the file just fine (and write any old file in it's place)
  Even we can modify the file when running unelevated, which is **B**ad.

  So there's gotta be some way to allow _us_ to write the file only when elevated
2021-09-09 10:23:53 -05:00
Mike Griese 8569211d0f Now, we can update the file after it's created 2021-09-09 10:18:22 -05:00
Mike Griese 721b6367a2 unfortunately, we can't _write_ this file... 2021-09-09 10:11:52 -05:00
Mike Griese 5197dc4e50 this worked, nice 2021-09-09 08:56:53 -05:00
Mike Griese 880222dc1b This file isn't even readable by admins, so maybe that's bad 2021-09-09 08:23:59 -05:00
Mike Griese 447c2f9d4f I think this creates a test file that's only accessible by SYSTEM. Asked raymond chen for help. 2021-09-08 15:51:21 -05:00
Mike Griese b592155d2b Merge remote-tracking branch 'origin/main' into dev/migrie/f/non-terminal-content-elevation-warning 2021-09-08 11:50:56 -05:00
Mike Griese 64898b1caf update other panes too 2021-09-02 11:50:33 -05:00
Mike Griese e5dc64e085 Hot-reload the elevated state? 2021-09-02 11:12:07 -05:00
Mike Griese 58c6646132 Format that dialog with the actual commandline 2021-09-02 11:01:11 -05:00
Mike Griese 9b32681ae1 This works to prompt before splitting a pane 2021-09-02 10:09:22 -05:00
Mike Griese 7f29e1e268 More things we might need for Non-Terminal content, #997 2021-09-02 10:09:05 -05:00
Mike Griese 736a351c27 This works amazingly well 2021-09-01 17:08:00 -05:00
Mike Griese 631cdf7b18 Who ever said nested lambdas is a bad thing? 2021-09-01 16:42:36 -05:00
Mike Griese 7fb7d64b91 These are things I might need for #997 2021-09-01 16:38:58 -05:00
Mike Griese 1ee3522cd8 Allow a TerminalTab to have a UserControl 2021-09-01 15:27:30 -05:00
Mike Griese ed1cf2aeac add the boilerplate for a custom content dialog like thing 2021-09-01 14:57:41 -05:00
Mike Griese c66a56656e Allow a Pane to host a UserControl instead of a TermControl 2021-09-01 12:53:46 -05:00
Mike Griese 14d21f492b nits, cleanup 2021-09-01 12:52:45 -05:00
Mike Griese ccbcb425da Merge remote-tracking branch 'origin/main' into dev/migrie/f/elevation-warning 2021-08-31 16:16:02 -05:00
Mike Griese 2857324777 proof of concept add a dialog to pop when opening new tabs 2021-08-31 16:15:49 -05:00
Mike Griese eb243f5e11 Split ApplicationState into a Base and add an Elevated version 2021-08-31 16:15:22 -05:00
Mike Griese 42c3eea136 pull application state members into a base class 2021-08-30 12:47:10 -05:00
18 changed files with 904 additions and 80 deletions

View File

@ -43,7 +43,8 @@
<ClCompile Include="CommandlineTest.cpp" />
<ClCompile Include="SettingsTests.cpp" />
<ClCompile Include="TabTests.cpp" />
<ClCompile Include="FilteredCommandTests.cpp" />
<ClCompile Include="TrustCommandlineTests.cpp" />
<ClCompile Include="FilteredCommandTests.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>

View File

@ -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"));
}
}

View File

@ -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 <LibraryResources.h>
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;
}
// 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:
// - <none>
// Return Value:
// - <none>
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);
}
}
}

View File

@ -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>
{
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<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);
void _keyUpHandler(Windows::Foundation::IInspectable const& sender,
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
};
}

View File

@ -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; };
}
}

View File

@ -0,0 +1,97 @@
<!--
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"
AutomationProperties.AccessibilityView="Content"
AutomationProperties.IsDialog="True"
AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}"
PreviewKeyUp="_keyUpHandler"
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"
AllowFocusOnInteraction="True"
AutomationProperties.AccessibilityView="Raw"
AutomationProperties.IsDialog="True"
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
BorderBrush="{ThemeResource SystemAccentColor}"
BorderThickness="2,2,2,2"
CornerRadius="{ThemeResource OverlayCornerRadius}">
<StackPanel Orientation="Vertical">
<TextBlock x:Name="ApproveCommandlineWarningTitle"
x:Uid="ApproveCommandlineWarningTitle"
Padding="0,0,0,16"
HorizontalAlignment="Left"
FontSize="20"
FontWeight="Normal"
IsTextSelectionEnabled="True"
TextWrapping="WrapWholeWords" />
<TextBlock x:Name="PrefixTextBlock"
x:Uid="ApproveCommandlineWarningPrefixTextBlock"
HorizontalAlignment="Left"
IsTextSelectionEnabled="True"
TextWrapping="WrapWholeWords" />
<TextBlock x:Name="CommandlineText"
Margin="0,16,0,16"
HorizontalAlignment="Left"
FontFamily="Cascadia Mono"
IsTextSelectionEnabled="True"
Text="{x:Bind Commandline, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
<TextBlock x:Name="SuffixTextBlock"
x:Uid="ApproveCommandlineWarningSuffixTextBlock"
HorizontalAlignment="Left"
IsTextSelectionEnabled="True"
TextWrapping="WrapWholeWords" />
<Grid Margin="0,8,0,8"
HorizontalAlignment="Stretch"
XYFocusKeyboardNavigation="Enabled">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="PrimaryButton"
x:Uid="ApproveCommandlineWarning_PrimaryButton"
Grid.Column="0"
Margin="0,0,8,0"
HorizontalAlignment="Stretch"
Click="_primaryButtonClick"
IsTabStop="True"
Style="{StaticResource AccentButtonStyle}" />
<Button x:Name="CancelButton"
x:Uid="ApproveCommandlineWarning_CancelButton"
Grid.Column="1"
HorizontalAlignment="Stretch"
Click="_cancelButtonClick"
IsTabStop="True" />
</Grid>
</StackPanel>
</Border>
</Grid>
</Grid>
</UserControl>

View File

@ -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<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
@ -125,11 +128,23 @@ 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());
// 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<Pane> first, std::shared_ptr<Pane> 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<TermControl>() })
{
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<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:
@ -1079,8 +1128,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)
@ -1123,7 +1176,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)
@ -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<TermControl>() };
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:
// - <none>
@ -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:
// - <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
{
auto control{ GetUserControl() };
return control ? control.try_as<TermControl>() : 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<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());
@ -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<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
@ -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<TermControl>() })
{
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<TermControl>() })
{
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<TermControl>() })
{
closedControl.ConnectionStateChanged(p->_connectionStateChangedToken);
closedControl.WarningBell(p->_warningBellToken);
}
}
return false;
});
@ -2468,11 +2552,14 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> 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<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 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>, std::shared_ptr<Pane>> 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<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();
@ -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
// - <none>
void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const
{
const auto& termControl{ _control.try_as<TermControl>() };
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<TermControl>() };
auto controlSize = termControl ? termControl.MinimumSize() : Size{ 1, 1 };
auto newWidth = controlSize.Width;
auto newHeight = controlSize.Height;
@ -3244,7 +3350,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:
@ -3257,13 +3366,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);

View File

@ -56,7 +56,7 @@ 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);
Pane(std::shared_ptr<Pane> first,
@ -66,8 +66,9 @@ public:
const bool lastFocused = false);
std::shared_ptr<Pane> 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<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);
@ -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;

View File

@ -504,6 +504,24 @@
<data name="MultiLinePasteDialog.Title" xml:space="preserve">
<value>Warning</value>
</data>
<data name="ApproveCommandlineWarningPrefixTextBlock.Text" xml:space="preserve">
<value>You are about to execute the following command-line:</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 command-line</value>
</data>
<data name="ApproveCommandlineWarning_CancelButton.Content" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="ApproveCommandlineWarningTitle.Text" xml:space="preserve">
<value>Warning</value>
</data>
<data name="AdminWarningPlaceholderName" xml:space="preserve">
<value>Elevated command-line warning</value>
</data>
<data name="CommandPalette_SearchBox.PlaceholderText" xml:space="preserve">
<value>Type a command name...</value>
</data>

View File

@ -16,6 +16,7 @@
#include <LibraryResources.h>
#include "TabRowControl.h"
#include "AdminWarningPlaceholder.h"
#include "ColorHelper.h"
#include "DebugTapConnection.h"
#include "SettingsTab.h"

View File

@ -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>

View File

@ -17,6 +17,7 @@
#include "DebugTapConnection.h"
#include "SettingsTab.h"
#include "RenameWindowRequestedArgs.g.cpp"
#include "AdminWarningPlaceholder.h"
#include "../inc/WindowingBehavior.h"
#include <til/latch.h>
@ -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 <distro name> -> 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<std::wstring>(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 <distro name>. 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<std::filesystem::path> powershellCoreRoots
{
// Always look in "%LOCALAPPDATA%\Microsoft\WindowsApps", which is
// where the pwsh.exe execution alias lives.
{ wil::ExpandEnvironmentStringsW<std::wstring>(L"%LOCALAPPDATA%\\Microsoft\\WindowsApps") },
// Always look in "%ProgramFiles%\PowerShell"
{ wil::ExpandEnvironmentStringsW<std::wstring>(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<std::wstring>(L"%ProgramFiles(x86)%\\PowerShell") },
#endif
#if defined(_M_ARM64) // same with ARM
{
wil::ExpandEnvironmentStringsW<std::wstring>(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<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 {
const auto& projectedWarningControl{ pane->GetUserControl().try_as<TerminalApp::AdminWarningPlaceholder>() };
// If it was a warning control, then get our implementation
// type out of it.
if (const auto& otherWarning{ winrt::get_self<AdminWarningPlaceholder>(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<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 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<Pane>(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<implementation::AdminWarningPlaceholder>(control, cmdline) };
warningControl->PrimaryButtonClicked({ get_weak(), &TerminalPage::_adminWarningPrimaryClicked });
warningControl->CancelButtonClicked({ get_weak(), &TerminalPage::_adminWarningCancelClicked });
controlToAdd = *warningControl;
}
auto resultPane = std::make_shared<Pane>(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<implementation::AdminWarningPlaceholder>()->FocusOnLaunch();
}
return resultPane;
}

View File

@ -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<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();
@ -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;
};
}

View File

@ -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<TermControl>() })
{
_AttachEventHandlersToControl(p->Id().value(), termControl);
}
_nextPaneId++;
}
return false;
@ -856,6 +863,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{};
@ -1744,6 +1755,19 @@ namespace winrt::TerminalApp::implementation
return Title();
}
void TerminalTab::ReplaceControl(std::shared_ptr<Pane> pane, const Controls::UserControl& control)
{
pane->ReplaceControl(control);
if (auto termControl{ pane->_control.try_as<TermControl>() })
{
_AttachEventHandlersToControl(pane->Id().value(), termControl);
}
// Update the title manually.
UpdateTitle();
}
DEFINE_EVENT(TerminalTab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
DEFINE_EVENT(TerminalTab, ColorCleared, _colorCleared, winrt::delegate<>);

View File

@ -93,6 +93,9 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr<Pane> GetRootPane() const { return _rootPane; }
void ReplaceControl(std::shared_ptr<Pane> pane,
const winrt::Windows::UI::Xaml::Controls::UserControl& control);
winrt::TerminalApp::TerminalTabStatus TabStatus()
{
return _tabStatus;

View File

@ -2645,4 +2645,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
return _core.ReadEntireBuffer();
}
Media::Brush TermControl::BackgroundBrush()
{
return RootGrid().Background();
}
}

View File

@ -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);

View File

@ -71,5 +71,7 @@ namespace Microsoft.Terminal.Control
void ToggleReadOnly();
String ReadEntireBuffer();
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
}
}