Compare commits

..

8 Commits

Author SHA1 Message Date
Dustin L. Howett
1a0842a314 Migrate spelling-0.0.21 changes from main 2020-03-25 16:59:12 -07:00
Dustin L. Howett
34f83a60ca Migrate spelling-0.0.19 changes from main 2020-03-25 16:59:12 -07:00
Dustin Howett
61e46e3159 whitelist IBeam 2020-03-25 16:59:12 -07:00
Dustin Howett
698bca2a49 Merge remote-tracking branch 'origin/master' into dev/duhowett/eyebeam 2020-03-25 16:52:54 -07:00
Dustin Howett
6db3c8f499 Merge remote-tracking branch 'origin' into dev/duhowett/eyebeam 2020-03-25 15:47:29 -07:00
Dustin Howett
1ae08d4114 Merge remote-tracking branch 'origin/master' into dev/duhowett/eyebeam 2020-03-24 10:25:14 -07:00
Dustin Howett
2e04851dc4 Fix SA (well, guess what SA was mad about) and format 2020-03-20 15:37:21 -07:00
Dustin L. Howett
0f386592e4 Switch to the I-beam cursor when hovering over the terminal
This commit makes us use the I-beam cursor when the user hovers over the
terminal, *unless* mouse mode is enabled. I've also plumbed up a bunch
of events so that:

* If mouse mode is _toggled_ while hovering, the cursor will switch to
  the arrow if it's on or the I-beam if it's off.
* If you hold down shift to suppress mouse mode, the cursor will switch
  back to the I-beam.

Fixes #1441.
2020-03-19 20:23:03 -07:00
68 changed files with 1042 additions and 2832 deletions

View File

@@ -72,25 +72,12 @@ Assuming that you've installed Git Bash into `C:/Program Files/Git`:
```json
{
"name" : "Git Bash",
"commandline" : "C:/Program Files/Git/bin/bash.exe -li",
"commandline" : "C:/Program Files/Git/bin/bash.exe",
"icon" : "C:/Program Files/Git/mingw64/share/git/git-for-windows.ico",
"startingDirectory" : "%USERPROFILE%"
}
````
## Git Bash (WOW64)
Assuming that you've installed Git Bash into `C:/Program Files (x86)/Git`:
```json
{
"name" : "Git Bash",
"commandline" : "%ProgramFiles(x86)%/Git/bin/bash.exe -li",
"icon" : "%ProgramFiles(x86)%/Git/mingw32/share/git/git-for-windows.ico",
"startingDirectory" : "%USERPROFILE%"
}
```
## MSYS2
Assuming that you've installed MSYS2 into `C:/msys64`:
@@ -104,16 +91,4 @@ Assuming that you've installed MSYS2 into `C:/msys64`:
}
````
## Developer Command Prompt for Visual Studio
Assuming that you've installed VS 2019 Professional:
```json
{
"name" : "Developer Command Prompt for VS 2019",
"commandline" : "cmd.exe /k \"C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/Tools/VsDevCmd.bat\"",
"startingDirectory" : "%USERPROFILE%"
}
```
<!-- Adding a tool here? Make sure to add it in alphabetical order! -->

View File

@@ -107,22 +107,6 @@ add the following to your keybindings:
This will _unbind_ <kbd>Ctrl+Shift+6</kbd>, allowing vim to use the keystroke
instead of the terminal.
### Binding multiple keys
You can have multiple key chords bound to the same action. To do this, simply
add multiple bindings for the same action. For example:
```json
"keybindings" :
[
{ "command": "copy", "keys": "ctrl+shift+c" },
{ "command": "copy", "keys": "ctrl+c" },
{ "command": "copy", "keys": "enter" }
]
```
In this snippet, all three of <kbd>ctrl+shift+c</kbd>, <kbd>ctrl+c</kbd> and <kbd>enter</kbd> are bound to `copy`.
## Profiles
A profile contains the settings applied when a new WT tab is opened. Each

View File

@@ -70,10 +70,6 @@ namespace TerminalAppLocalTests
TEST_METHOD(TestTerminalArgsForBinding);
TEST_METHOD(FindMissingProfile);
TEST_METHOD(MakeSettingsForProfileThatDoesntExist);
TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist);
TEST_METHOD(TestLayerProfileOnColorScheme);
TEST_METHOD(ValidateKeybindingsWarnings);
@@ -2098,146 +2094,6 @@ namespace TerminalAppLocalTests
}
}
void SettingsTests::FindMissingProfile()
{
// Test that CascadiaSettings::FindProfile returns null for a GUID that
// doesn't exist
const std::string settingsString{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
}
]
})" };
const auto settingsJsonObj = VerifyParseSucceeded(settingsString);
auto settings = CascadiaSettings::FromJson(settingsJsonObj);
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
const Profile* const profile1 = settings->FindProfile(guid1);
const Profile* const profile2 = settings->FindProfile(guid2);
const Profile* const profile3 = settings->FindProfile(guid3);
VERIFY_IS_NOT_NULL(profile1);
VERIFY_IS_NOT_NULL(profile2);
VERIFY_IS_NULL(profile3);
VERIFY_ARE_EQUAL(L"profile0", profile1->GetName());
VERIFY_ARE_EQUAL(L"profile1", profile2->GetName());
}
void SettingsTests::MakeSettingsForProfileThatDoesntExist()
{
// Test that MakeSettings throws when the GUID doesn't exist
const std::string settingsString{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
const auto settingsJsonObj = VerifyParseSucceeded(settingsString);
auto settings = CascadiaSettings::FromJson(settingsJsonObj);
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
try
{
auto terminalSettings = settings->BuildSettings(guid1);
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
VERIFY_ARE_EQUAL(1, terminalSettings.HistorySize());
}
catch (...)
{
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
}
try
{
auto terminalSettings = settings->BuildSettings(guid2);
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
VERIFY_ARE_EQUAL(2, terminalSettings.HistorySize());
}
catch (...)
{
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
}
VERIFY_THROWS(auto terminalSettings = settings->BuildSettings(guid3), wil::ResultException, L"This call to BuildSettings should fail");
try
{
const auto [guid, termSettings] = settings->BuildSettings(nullptr);
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
catch (...)
{
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
}
}
void SettingsTests::MakeSettingsForDefaultProfileThatDoesntExist()
{
// Test that MakeSettings _doesnt_ throw when we load settings with a
// defaultProfile that's not in the list, we validate the settings, and
// then call MakeSettings(nullopt). The validation should ensure that
// the default profile is something reasonable
const std::string settingsString{ R"(
{
"defaultProfile": "{6239a42c-3333-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
const auto settingsJsonObj = VerifyParseSucceeded(settingsString);
auto settings = CascadiaSettings::FromJson(settingsJsonObj);
settings->_ValidateSettings();
VERIFY_ARE_EQUAL(2u, settings->_warnings.size());
VERIFY_ARE_EQUAL(2u, settings->_profiles.size());
VERIFY_ARE_EQUAL(settings->_globals.GetDefaultProfile(), settings->_profiles.at(0).GetGuid());
try
{
const auto [guid, termSettings] = settings->BuildSettings(nullptr);
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
}
catch (...)
{
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
}
}
void SettingsTests::TestLayerProfileOnColorScheme()
{
Log::Comment(NoThrowString().Format(

View File

@@ -13,10 +13,8 @@
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace winrt::TerminalApp;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace TerminalAppLocalTests
{
@@ -55,20 +53,11 @@ namespace TerminalAppLocalTests
TEST_METHOD(CreateSimpleTerminalXamlType);
TEST_METHOD(CreateTerminalMuxXamlType);
TEST_METHOD(CreateTerminalPage);
TEST_METHOD(TryDuplicateBadTab);
TEST_METHOD(TryDuplicateBadPane);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
return true;
}
private:
void _initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
std::shared_ptr<CascadiaSettings> initialSettings);
};
void TabTests::EnsureTestsActivate()
@@ -175,321 +164,4 @@ namespace TerminalAppLocalTests
VERIFY_SUCCEEDED(result);
}
void TabTests::CreateTerminalPage()
{
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
auto result = RunOnUIThread([&page]() {
page = winrt::make_self<winrt::TerminalApp::implementation::TerminalPage>();
VERIFY_IS_NOT_NULL(page);
});
VERIFY_SUCCEEDED(result);
}
// Method Description:
// - This is a helper to set up a TerminalPage for a unittest. This method
// does a couple things:
// * Create()'s a TerminalPage with the given settings. Constructing a
// TerminalPage so that we can get at its implementation is wacky, so
// this helper will do it correctly for you, even if this doesn't make a
// ton of sense on the surface. This is also why you need to pass both a
// projection and a com_ptr to this method.
// * It will use the provided settings object to initialize the TerminalPage
// * It will add the TerminalPage to the test Application, so that we can
// get actual layout events. Much of the Terminal assumes there's a
// non-zero ActualSize to the Terminal window, and adding the Page to
// the Application will make it behave as expected.
// * It will wait for the TerminalPage to finish initialization before
// returning control to the caller. It does this by creating an event and
// only setting the event when the TerminalPage raises its Initialized
// event, to signal that startup is complete. At this point, there will
// be one tab with the default profile in the page.
// * It will also ensure that the first tab is focused, since that happens
// asynchronously in the application typically.
// Arguments:
// - page: a TerminalPage implementation ptr that will receive the new TerminalPage instance
// - initialSettings: a CascadiaSettings to initialize the TerminalPage with.
// Return Value:
// - <none>
void TabTests::_initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
std::shared_ptr<CascadiaSettings> initialSettings)
{
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::TerminalApp::TerminalPage projectedPage{ nullptr };
Log::Comment(NoThrowString().Format(L"Construct the TerminalPage"));
auto result = RunOnUIThread([&projectedPage, &page, initialSettings]() {
projectedPage = winrt::TerminalApp::TerminalPage();
page.copy_from(winrt::get_self<winrt::TerminalApp::implementation::TerminalPage>(projectedPage));
page->_settings = initialSettings;
});
VERIFY_SUCCEEDED(result);
VERIFY_IS_NOT_NULL(page);
VERIFY_IS_NOT_NULL(page->_settings);
::details::Event waitForInitEvent;
if (!waitForInitEvent.IsValid())
{
VERIFY_SUCCEEDED(HRESULT_FROM_WIN32(::GetLastError()));
}
page->Initialized([&waitForInitEvent](auto&&, auto&&) {
waitForInitEvent.Set();
});
Log::Comment(L"Create() the TerminalPage");
result = RunOnUIThread([&page]() {
VERIFY_IS_NOT_NULL(page);
VERIFY_IS_NOT_NULL(page->_settings);
page->Create();
Log::Comment(L"Create()'d the page successfully");
auto app = ::winrt::Windows::UI::Xaml::Application::Current();
winrt::TerminalApp::TerminalPage pp = *page;
winrt::Windows::UI::Xaml::Window::Current().Content(pp);
winrt::Windows::UI::Xaml::Window::Current().Activate();
});
VERIFY_SUCCEEDED(result);
Log::Comment(L"Wait for the page to finish initializing...");
VERIFY_SUCCEEDED(waitForInitEvent.Wait());
Log::Comment(L"...Done");
result = RunOnUIThread([&page]() {
// In the real app, this isn't a problem, but doesn't happen
// reliably in the unit tests.
Log::Comment(L"Ensure we set the first tab as the selected one.");
auto tab{ page->_GetStrongTabImpl(0) };
page->_tabView.SelectedItem(tab->GetTabViewItem());
page->_UpdatedSelectedTab(0);
});
VERIFY_SUCCEEDED(result);
}
void TabTests::TryDuplicateBadTab()
{
// * Create a tab with a profile with GUID 1
// * Reload the settings so that GUID 1 is no longer in the list of profiles
// * Try calling _DuplicateTabViewItem on tab 1
// * No new tab should be created (and more importantly, the app should not crash)
//
// Created to test GH#2455
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
const std::string settingsJson1{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
VerifyParseSucceeded(settingsJson0);
auto settings0 = std::make_shared<CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings0);
settings0->_ParseJsonString(settingsJson0, false);
settings0->LayerJson(settings0->_userSettings);
settings0->_ValidateSettings();
VerifyParseSucceeded(settingsJson1);
auto settings1 = std::make_shared<CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings1);
settings1->_ParseJsonString(settingsJson1, false);
settings1->LayerJson(settings1->_userSettings);
settings1->_ValidateSettings();
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_initializeTerminalPage(page, settings0);
auto result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
Log::Comment(L"Duplicate the first tab");
result = RunOnUIThread([&page]() {
page->_DuplicateTabViewItem();
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(
L"Change the settings of the TerminalPage so the first profile is "
L"no longer in the list of profiles"));
result = RunOnUIThread([&page, settings1]() {
page->_settings = settings1;
});
VERIFY_SUCCEEDED(result);
Log::Comment(L"Duplicate the tab, and don't crash");
result = RunOnUIThread([&page]() {
page->_DuplicateTabViewItem();
VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
});
VERIFY_SUCCEEDED(result);
}
void TabTests::TryDuplicateBadPane()
{
// * Create a tab with a profile with GUID 1
// * Reload the settings so that GUID 1 is no longer in the list of profiles
// * Try calling _SplitPane(Duplicate) on tab 1
// * No new pane should be created (and more importantly, the app should not crash)
//
// Created to test GH#2455
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
const std::string settingsJson1{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
VerifyParseSucceeded(settingsJson0);
auto settings0 = std::make_shared<CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings0);
settings0->_ParseJsonString(settingsJson0, false);
settings0->LayerJson(settings0->_userSettings);
settings0->_ValidateSettings();
VerifyParseSucceeded(settingsJson1);
auto settings1 = std::make_shared<CascadiaSettings>(false);
VERIFY_IS_NOT_NULL(settings1);
settings1->_ParseJsonString(settingsJson1, false);
settings1->LayerJson(settings1->_userSettings);
settings1->_ValidateSettings();
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
// during TerminalPage::Create() below.
//
// Instead, create the winrt object, then get a com_ptr to the
// implementation _from_ the winrt object. This seems to work, even if
// it's weird.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
_initializeTerminalPage(page, settings0);
auto result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
});
VERIFY_SUCCEEDED(result);
result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(1, tab->_GetLeafPaneCount());
});
VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
result = RunOnUIThread([&page]() {
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2, tab->_GetLeafPaneCount());
});
VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(
L"Change the settings of the TerminalPage so the first profile is "
L"no longer in the list of profiles"));
result = RunOnUIThread([&page, settings1]() {
page->_settings = settings1;
});
VERIFY_SUCCEEDED(result);
Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
result = RunOnUIThread([&page]() {
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
VERIFY_ARE_EQUAL(2,
tab->_GetLeafPaneCount(),
L"We should gracefully do nothing here - the profile no longer exists.");
});
VERIFY_SUCCEEDED(result);
auto cleanup = wil::scope_exit([] {
auto result = RunOnUIThread([]() {
// There's something causing us to crash north of
// TSFInputControl::NotifyEnter, or LayoutRequested. It's very
// unclear what that issue is. Since these tests don't run in
// CI, simply log a message so that the dev running these tests
// knows it's expected.
Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay.");
});
VERIFY_SUCCEEDED(result);
});
}
}

View File

@@ -1,147 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "DebugTapConnection.h"
using namespace ::winrt::Microsoft::Terminal::TerminalConnection;
using namespace ::winrt::Windows::Foundation;
namespace winrt::Microsoft::TerminalApp::implementation
{
// DebugInputTapConnection is an implementation detail of DebugTapConnection.
// It wraps the _actual_ connection so it can hook WriteInput and forward it
// into the actual debug panel.
class DebugInputTapConnection : public winrt::implements<DebugInputTapConnection, ITerminalConnection>
{
public:
DebugInputTapConnection(winrt::com_ptr<DebugTapConnection> pairedTap, ITerminalConnection wrappedConnection) :
_pairedTap{ pairedTap },
_wrappedConnection{ std::move(wrappedConnection) }
{
}
~DebugInputTapConnection() = default;
void Start()
{
_wrappedConnection.Start();
}
void WriteInput(hstring const& data)
{
_pairedTap->_PrintInput(data);
_wrappedConnection.WriteInput(data);
}
void Resize(uint32_t rows, uint32_t columns) { _wrappedConnection.Resize(rows, columns); }
void Close() { _wrappedConnection.Close(); }
winrt::event_token TerminalOutput(TerminalOutputHandler const& args) { return _wrappedConnection.TerminalOutput(args); };
void TerminalOutput(winrt::event_token const& token) noexcept { _wrappedConnection.TerminalOutput(token); };
winrt::event_token StateChanged(TypedEventHandler<ITerminalConnection, IInspectable> const& handler) { return _wrappedConnection.StateChanged(handler); };
void StateChanged(winrt::event_token const& token) noexcept { _wrappedConnection.StateChanged(token); };
ConnectionState State() const noexcept { return _wrappedConnection.State(); }
private:
winrt::com_ptr<DebugTapConnection> _pairedTap;
ITerminalConnection _wrappedConnection;
};
DebugTapConnection::DebugTapConnection(ITerminalConnection wrappedConnection)
{
_outputRevoker = wrappedConnection.TerminalOutput(winrt::auto_revoke, { this, &DebugTapConnection::_OutputHandler });
_stateChangedRevoker = wrappedConnection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*e*/) {
_StateChangedHandlers(*this, nullptr);
});
_wrappedConnection = wrappedConnection;
}
DebugTapConnection::~DebugTapConnection()
{
}
void DebugTapConnection::Start()
{
// presume the wrapped connection is started.
}
void DebugTapConnection::WriteInput(hstring const& data)
{
// If the user types into the tap side, forward it to the input side
if (auto strongInput{ _inputSide.get() })
{
auto inputAsTap{ winrt::get_self<DebugInputTapConnection>(strongInput) };
inputAsTap->WriteInput(data);
}
}
void DebugTapConnection::Resize(uint32_t /*rows*/, uint32_t /*columns*/)
{
// no resize events are propagated
}
void DebugTapConnection::Close()
{
_outputRevoker.revoke();
_stateChangedRevoker.revoke();
_wrappedConnection = nullptr;
}
ConnectionState DebugTapConnection::State() const noexcept
{
if (auto strongConnection{ _wrappedConnection.get() })
{
return strongConnection.State();
}
return ConnectionState::Failed;
}
static std::wstring _sanitizeString(const std::wstring_view str)
{
std::wstring newString{ str.begin(), str.end() };
for (auto& ch : newString)
{
if (ch < 0x20)
{
ch += 0x2400;
}
else if (ch == 0x20)
{
ch = 0x2423; // replace space with ␣
}
else if (ch == 0x7f)
{
ch = 0x2421; // replace del with ␡
}
}
return newString;
}
void DebugTapConnection::_OutputHandler(const hstring str)
{
_TerminalOutputHandlers(_sanitizeString(str));
}
// Called by the DebugInputTapConnection to print user input
void DebugTapConnection::_PrintInput(const hstring& str)
{
auto clean{ _sanitizeString(str) };
auto formatted{ wil::str_printf<std::wstring>(L"\x1b[91m%ls\x1b[m", clean.data()) };
_TerminalOutputHandlers(formatted);
}
// Wire us up so that we can forward input through
void DebugTapConnection::SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap)
{
_inputSide = inputTap;
}
}
// Function Description
// - Takes one connection and returns two connections:
// 1. One that can be used in place of the original connection (wrapped)
// 2. One that will print raw VT sequences sent into and received _from_ the original connection.
std::tuple<ITerminalConnection, ITerminalConnection> OpenDebugTapConnection(ITerminalConnection baseConnection)
{
using namespace winrt::Microsoft::TerminalApp::implementation;
auto debugSide{ winrt::make_self<DebugTapConnection>(baseConnection) };
auto inputSide{ winrt::make_self<DebugInputTapConnection>(debugSide, baseConnection) };
debugSide->SetInputTap(*inputSide);
std::tuple<ITerminalConnection, ITerminalConnection> p{ *inputSide, *debugSide };
return p;
}

View File

@@ -1,42 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include "../../inc/cppwinrt_utils.h"
namespace winrt::Microsoft::TerminalApp::implementation
{
class DebugInputTapConnection;
class DebugTapConnection : public winrt::implements<DebugTapConnection, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection>
{
public:
DebugTapConnection(Microsoft::Terminal::TerminalConnection::ITerminalConnection wrappedConnection);
~DebugTapConnection();
void Start();
void WriteInput(hstring const& data);
void Resize(uint32_t rows, uint32_t columns);
void Close();
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept;
void SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap);
WINRT_CALLBACK(TerminalOutput, winrt::Microsoft::Terminal::TerminalConnection::TerminalOutputHandler);
TYPED_EVENT(StateChanged, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection, winrt::Windows::Foundation::IInspectable);
private:
void _PrintInput(const hstring& data);
void _OutputHandler(const hstring str);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection::TerminalOutput_revoker _outputRevoker;
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection::StateChanged_revoker _stateChangedRevoker;
winrt::weak_ref<Microsoft::Terminal::TerminalConnection::ITerminalConnection> _wrappedConnection;
winrt::weak_ref<Microsoft::Terminal::TerminalConnection::ITerminalConnection> _inputSide;
friend class DebugInputTapConnection;
};
}
std::tuple<winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection> OpenDebugTapConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection baseConnection);

View File

@@ -41,14 +41,6 @@ static constexpr std::wstring_view LightThemeValue{ L"light" };
static constexpr std::wstring_view DarkThemeValue{ L"dark" };
static constexpr std::wstring_view SystemThemeValue{ L"system" };
static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };
#ifdef _DEBUG
static constexpr bool debugFeaturesDefault{ true };
#else
static constexpr bool debugFeaturesDefault{ false };
#endif
GlobalAppSettings::GlobalAppSettings() :
_keybindings{ winrt::make_self<winrt::TerminalApp::implementation::AppKeyBindings>() },
_keybindingsWarnings{},
@@ -67,8 +59,7 @@ GlobalAppSettings::GlobalAppSettings() :
_tabWidthMode{ TabViewWidthMode::Equal },
_wordDelimiters{ DEFAULT_WORD_DELIMITERS },
_copyOnSelect{ false },
_launchMode{ LaunchMode::DefaultMode },
_debugFeatures{ debugFeaturesDefault }
_launchMode{ LaunchMode::DefaultMode }
{
}
@@ -180,11 +171,6 @@ void GlobalAppSettings::SetConfirmCloseAllTabs(const bool confirmCloseAllTabs) n
_confirmCloseAllTabs = confirmCloseAllTabs;
}
bool GlobalAppSettings::DebugFeaturesEnabled() const noexcept
{
return _debugFeatures;
}
#pragma region ExperimentalSettings
bool GlobalAppSettings::GetShowTabsInTitlebar() const noexcept
{
@@ -251,7 +237,6 @@ Json::Value GlobalAppSettings::ToJson() const
jsonObject[JsonKey(KeybindingsKey)] = _keybindings->ToJson();
jsonObject[JsonKey(ConfirmCloseAllKey)] = _confirmCloseAllTabs;
jsonObject[JsonKey(SnapToGridOnResizeKey)] = _SnapToGridOnResize;
jsonObject[JsonKey(DebugFeaturesKey)] = _debugFeatures;
return jsonObject;
}
@@ -339,9 +324,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
}
JsonUtils::GetBool(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
// GetBool will only override the current value if the key exists
JsonUtils::GetBool(json, DebugFeaturesKey, _debugFeatures);
}
// Method Description:

View File

@@ -77,8 +77,6 @@ public:
winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode GetTabWidthMode() const noexcept;
bool DebugFeaturesEnabled() const noexcept;
Json::Value ToJson() const;
static GlobalAppSettings FromJson(const Json::Value& json);
void LayerJson(const Json::Value& json);
@@ -117,8 +115,6 @@ private:
winrt::TerminalApp::LaunchMode _launchMode;
bool _debugFeatures;
static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;
static std::wstring_view _SerializeTheme(const winrt::Windows::UI::Xaml::ElementTheme theme) noexcept;

View File

@@ -70,7 +70,7 @@ void Pane::ResizeContent(const Size& newSize)
const auto width = newSize.Width;
const auto height = newSize.Height;
_CreateRowColDefinitions();
_CreateRowColDefinitions(newSize);
if (_splitState == SplitState::Vertical)
{
@@ -790,24 +790,23 @@ void Pane::_SetupChildCloseHandlers()
// which is stored in _desiredSplitPosition
// - Does nothing if our split state is currently set to SplitState::None
// Arguments:
// - <none>
// - rootSize: The dimensions in pixels that this pane (and its children should consume.)
// Return Value:
// - <none>
void Pane::_CreateRowColDefinitions()
void Pane::_CreateRowColDefinitions(const Size& rootSize)
{
const auto first = _desiredSplitPosition * 100.0f;
const auto second = 100.0f - first;
if (_splitState == SplitState::Vertical)
{
_root.ColumnDefinitions().Clear();
// Create two columns in this grid: one for each pane
const auto paneSizes = _CalcChildrenSizes(rootSize.Width);
auto firstColDef = Controls::ColumnDefinition();
firstColDef.Width(GridLengthHelper::FromValueAndType(first, GridUnitType::Star));
firstColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
auto secondColDef = Controls::ColumnDefinition();
secondColDef.Width(GridLengthHelper::FromValueAndType(second, GridUnitType::Star));
secondColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
_root.ColumnDefinitions().Append(firstColDef);
_root.ColumnDefinitions().Append(secondColDef);
@@ -817,18 +816,35 @@ void Pane::_CreateRowColDefinitions()
_root.RowDefinitions().Clear();
// Create two rows in this grid: one for each pane
const auto paneSizes = _CalcChildrenSizes(rootSize.Height);
auto firstRowDef = Controls::RowDefinition();
firstRowDef.Height(GridLengthHelper::FromValueAndType(first, GridUnitType::Star));
firstRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
auto secondRowDef = Controls::RowDefinition();
secondRowDef.Height(GridLengthHelper::FromValueAndType(second, GridUnitType::Star));
secondRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
_root.RowDefinitions().Append(firstRowDef);
_root.RowDefinitions().Append(secondRowDef);
}
}
// Method Description:
// - Initializes our UI for a new split in this pane. Sets up row/column
// definitions, and initializes the separator grid. Does nothing if our split
// state is currently set to SplitState::None
// Arguments:
// - <none>
// Return Value:
// - <none>
void Pane::_CreateSplitContent()
{
Size actualSize{ gsl::narrow_cast<float>(_root.ActualWidth()),
gsl::narrow_cast<float>(_root.ActualHeight()) };
_CreateRowColDefinitions(actualSize);
}
// Method Description:
// - Sets the thickness of each side of our borders to match our _borders state.
// Arguments:
@@ -1061,7 +1077,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState
_control = { nullptr };
_secondChild = std::make_shared<Pane>(profile, control);
_CreateRowColDefinitions();
_CreateSplitContent();
_root.Children().Append(_firstChild->GetRootElement());
_root.Children().Append(_secondChild->GetRootElement());
@@ -1506,74 +1522,4 @@ void Pane::_SetupResources()
}
}
int Pane::GetLeafPaneCount() const noexcept
{
return _IsLeaf() ? 1 : (_firstChild->GetLeafPaneCount() + _secondChild->GetLeafPaneCount());
}
// Method Description:
// - This is a helper to determine which direction an "Automatic" split should
// happen in for a given pane, but without using the ActualWidth() and
// ActualHeight() methods. This is used during the initialization of the
// Terminal, when we could be processing many "split-pane" commands _before_
// we've ever laid out the Terminal for the first time. When this happens, the
// Pane's don't have an actual size yet. However, we'd still like to figure
// out how to do an "auto" split when these Panes are all laid out.
// - This method assumes that the Pane we're attempting to split is `target`,
// and this method should be called on the root of a tree of Panes.
// - We'll walk down the tree attempting to find `target`. As we traverse the
// tree, we'll reduce the size passed to each subsequent recursive call. The
// size passed to this method represents how much space this Pane _will_ have
// to use.
// * If this pane is a leaf, and it's the pane we're looking for, use the
// available space to calculate which direction to split in.
// * If this pane is _any other leaf_, then just return nullopt, to indicate
// that the `target` Pane is not down this branch.
// * If this pane is a parent, calculate how much space our children will be
// able to use, and recurse into them.
// Arguments:
// - target: The Pane we're attempting to split.
// - availableSpace: The theoretical space that's available for this pane to be able to split.
// Return Value:
// - nullopt if `target` is not this pane or a child of this pane, otherwise the
// SplitState that `target` would use for an `Automatic` split given
// `availableSpace`
std::optional<winrt::TerminalApp::SplitState> Pane::PreCalculateAutoSplit(const std::shared_ptr<Pane> target,
const winrt::Windows::Foundation::Size availableSpace) const
{
if (_IsLeaf())
{
if (target.get() == this)
{
//If this pane is a leaf, and it's the pane we're looking for, use
//the available space to calculate which direction to split in.
return availableSpace.Width > availableSpace.Height ? SplitState::Vertical : SplitState::Horizontal;
}
else
{
// If this pane is _any other leaf_, then just return nullopt, to
// indicate that the `target` Pane is not down this branch.
return std::nullopt;
}
}
else
{
// If this pane is a parent, calculate how much space our children will
// be able to use, and recurse into them.
const bool isVerticalSplit = _splitState == SplitState::Vertical;
const float firstWidth = isVerticalSplit ? (availableSpace.Width * _desiredSplitPosition) : availableSpace.Width;
const float secondWidth = isVerticalSplit ? (availableSpace.Width - firstWidth) : availableSpace.Width;
const float firstHeight = !isVerticalSplit ? (availableSpace.Height * _desiredSplitPosition) : availableSpace.Height;
const float secondHeight = !isVerticalSplit ? (availableSpace.Height - firstHeight) : availableSpace.Height;
const auto firstResult = _firstChild->PreCalculateAutoSplit(target, { firstWidth, firstHeight });
return firstResult.has_value() ? firstResult : _secondChild->PreCalculateAutoSplit(target, { secondWidth, secondHeight });
}
// We should not possibly be getting here - both the above branches should
// return a value.
FAIL_FAST();
}
DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);

View File

@@ -63,13 +63,10 @@ public:
const GUID& profile,
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
std::optional<winrt::TerminalApp::SplitState> PreCalculateAutoSplit(const std::shared_ptr<Pane> target, const winrt::Windows::Foundation::Size parentSize) const;
void Shutdown();
void Close();
int GetLeafPaneCount() const noexcept;
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
@@ -110,7 +107,8 @@ private:
const GUID& profile,
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
void _CreateRowColDefinitions();
void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize);
void _CreateSplitContent();
void _ApplySplitDefinitions();
void _UpdateBorders();
@@ -135,9 +133,6 @@ private:
float _ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const;
winrt::TerminalApp::SplitState _convertAutomaticSplitState(const winrt::TerminalApp::SplitState& splitType) const;
std::optional<winrt::TerminalApp::SplitState> _preCalculateAutoSplit(const std::shared_ptr<Pane> target, const winrt::Windows::Foundation::Size parentSize) const;
// Function Description:
// - Returns true if the given direction can be used with the given split
// type.

View File

@@ -162,6 +162,40 @@ Temporarily using the Windows Terminal default settings.
<data name="ReloadJsonParseErrorTitle" xml:space="preserve">
<value>Failed to reload settings</value>
</data>
<data name="AboutTitleText" xml:space="preserve">
<value>About</value>
</data>
<data name="VersionLabelText" xml:space="preserve">
<value>Version:</value>
</data>
<data name="DocumentationLabelText" xml:space="preserve">
<value>Documentation
</value>
</data>
<data name="GettingStartedLabelText" xml:space="preserve">
<value>Getting Started
</value>
</data>
<data name="ReleaseNotesLabelText" xml:space="preserve">
<value>Release Notes
</value>
</data>
<data name="PrivacyPolicyLabelText" xml:space="preserve">
<value>Privacy Policy
</value>
</data>
<data name="DocumentationUriValue" xml:space="preserve">
<value>https://aka.ms/terminal-documentation</value>
</data>
<data name="GettingStartedUriValue" xml:space="preserve">
<value>https://aka.ms/terminal-getting-started</value>
</data>
<data name="ReleaseNotesUriValue" xml:space="preserve">
<value>https://aka.ms/terminal-release-notes</value>
</data>
<data name="PrivacyPolicyUriValue" xml:space="preserve">
<value>https://aka.ms/terminal-privacy-policy</value>
</data>
<data name="FeedbackUriValue" xml:space="preserve">
<value>https://aka.ms/terminal-feedback</value>
</data>
@@ -174,6 +208,15 @@ Temporarily using the Windows Terminal default settings.
<data name="SettingsMenuItem" xml:space="preserve">
<value>Settings</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="CloseAll" xml:space="preserve">
<value>Close all</value>
</data>
<data name="CloseWindowWarningTitle" xml:space="preserve">
<value>Do you want to close all tabs?</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>Found a profile with an invalid "backgroundImage". Defaulting that profile to have no background image. Make sure that when setting a "backgroundImage", the value is a valid file path to an image.</value>
</data>
@@ -240,47 +283,4 @@ Temporarily using the Windows Terminal default settings.
<data name="WindowMinimizeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Minimize</value>
</data>
<data name="AboutDialog.Title" xml:space="preserve">
<value>About</value>
</data>
<data name="AboutDialog.CloseButtonText" xml:space="preserve">
<value>OK</value>
</data>
<data name="AboutDialog_VersionLabel.Text" xml:space="preserve">
<value>Version:</value>
<comment>This is the heading for a version number label</comment>
</data>
<data name="AboutDialog_GettingStartedLink.Content" xml:space="preserve">
<value>Getting Started</value>
<comment>A hyperlink name for a guide on how to get started using Terminal</comment>
</data>
<data name="AboutDialog_DocumentationLink.Content" xml:space="preserve">
<value>Documentation</value>
<comment>A hyperlink name for user documentation</comment>
</data>
<data name="AboutDialog_ReleaseNotesLink.Content" xml:space="preserve">
<value>Release Notes</value>
<comment>A hyperlink name for the Terminal's release notes</comment>
</data>
<data name="AboutDialog_PrivacyPolicyLink.Content" xml:space="preserve">
<value>Privacy Policy</value>
<comment>A hyperlink name for the Terminal's privacy policy</comment>
</data>
<data name="AboutDialog_DisplayNameUnpackaged" xml:space="preserve">
<value>Windows Terminal (Unpackaged)</value>
<comment>This display name is used when the application's name cannot be determined</comment>
</data>
<data name="AboutDialog_VersionUnknown" xml:space="preserve">
<value>Unknown</value>
<comment>This is displayed when the version of the application cannot be determined</comment>
</data>
<data name="CloseAllDialog.CloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="CloseAllDialog.PrimaryButtonText" xml:space="preserve">
<value>Close all</value>
</data>
<data name="CloseAllDialog.Title" xml:space="preserve">
<value>Do you want to close all tabs?</value>
</data>
</root>
</root>

View File

@@ -277,12 +277,6 @@ namespace winrt::TerminalApp::implementation
// gains focus, we'll mark it as the new active pane.
_AttachEventHandlersToPane(first);
_AttachEventHandlersToPane(second);
// Immediately update our tracker of the focused pane now. If we're
// splitting panes during startup (from a commandline), then it's
// possible that the focus events won't propagate immediately. Updating
// the focus here will give the same effect though.
_UpdateActivePane(second);
}
// Method Description:
@@ -393,28 +387,6 @@ namespace winrt::TerminalApp::implementation
});
}
// Method Description:
// - Mark the given pane as the active pane in this tab. All other panes
// will be marked as inactive. We'll also update our own UI state to
// reflect this newly active pane.
// Arguments:
// - pane: a Pane to mark as active.
// Return Value:
// - <none>
void Tab::_UpdateActivePane(std::shared_ptr<Pane> pane)
{
// Clear the active state of the entire tree, and mark only the pane as active.
_rootPane->ClearActive();
_activePane = pane;
_activePane->SetActive();
// Update our own title text to match the newly-active pane.
SetTabText(GetActiveTitle());
// Raise our own ActivePaneChanged event.
_ActivePaneChangedHandlers();
}
// Method Description:
// - Add an event handler to this pane's GotFocus event. When that pane gains
// focus, we'll mark it as the new active pane. We'll also query the title of
@@ -434,37 +406,19 @@ namespace winrt::TerminalApp::implementation
if (tab && sender != tab->_activePane)
{
tab->_UpdateActivePane(sender);
// Clear the active state of the entire tree, and mark only the sender as active.
tab->_rootPane->ClearActive();
tab->_activePane = sender;
tab->_activePane->SetActive();
// Update our own title text to match the newly-active pane.
tab->SetTabText(tab->GetActiveTitle());
// Raise our own ActivePaneChanged event.
tab->_ActivePaneChangedHandlers();
}
});
}
// Method Description:
// - Get the total number of leaf panes in this tab. This will be the number
// of actual controls hosted by this tab.
// Arguments:
// - <none>
// Return Value:
// - The total number of leaf panes hosted by this tab.
int Tab::_GetLeafPaneCount() const noexcept
{
return _rootPane->GetLeafPaneCount();
}
// Method Description:
// - This is a helper to determine which direction an "Automatic" split should
// happen in for the active pane of this tab, but without using the ActualWidth() and
// ActualHeight() methods.
// - See Pane::PreCalculateAutoSplit
// Arguments:
// - availableSpace: The theoretical space that's available for this Tab's content
// Return Value:
// - The SplitState that we should use for an `Automatic` split given
// `availableSpace`
SplitState Tab::PreCalculateAutoSplit(winrt::Windows::Foundation::Size availableSpace) const
{
return _rootPane->PreCalculateAutoSplit(_activePane, availableSpace).value_or(SplitState::Vertical);
}
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
}

View File

@@ -5,12 +5,6 @@
#include "Pane.h"
#include "Tab.g.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class TabTests;
};
namespace winrt::TerminalApp::implementation
{
struct Tab : public TabT<Tab>
@@ -38,7 +32,6 @@ namespace winrt::TerminalApp::implementation
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
SplitState PreCalculateAutoSplit(winrt::Windows::Foundation::Size rootSize) const;
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void ResizePane(const winrt::TerminalApp::Direction& direction);
@@ -71,10 +64,5 @@ namespace winrt::TerminalApp::implementation
void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);
int _GetLeafPaneCount() const noexcept;
void _UpdateActivePane(std::shared_ptr<Pane> pane);
friend class ::TerminalAppLocalTests::TabTests;
};
}

View File

@@ -15,7 +15,6 @@
#include "AzureCloudShellGenerator.h" // For AzureConnectionType
#include "TelnetGenerator.h" // For TelnetConnectionType
#include "TabRowControl.h"
#include "DebugTapConnection.h"
using namespace winrt;
using namespace winrt::Windows::UI::Xaml;
@@ -64,20 +63,11 @@ 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().IsUwp();
}
CATCH_LOG();
// 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.
const auto isElevated = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated();
_tabView.CanReorderTabs(!isElevated);
_tabView.CanDragTabs(!isElevated);
@@ -147,115 +137,161 @@ namespace winrt::TerminalApp::implementation
_tabContent.SizeChanged({ this, &TerminalPage::_OnContentSizeChanged });
// Once the page is actually laid out on the screen, trigger all our
// startup actions. Things like Panes need to know at least how big the
// window will be, so they can subdivide that space.
//
// _OnFirstLayout will remove this handler so it doesn't get called more than once.
_layoutUpdatedRevoker = _tabContent.LayoutUpdated(winrt::auto_revoke, { this, &TerminalPage::_OnFirstLayout });
}
// Method Description:
// - This method is called once on startup, on the first LayoutUpdated event.
// We'll use this event to know that we have an ActualWidth and
// ActualHeight, so we can now attempt to process our list of startup
// actions.
// - We'll remove this event handler when the event is first handled.
// - If there are no startup actions, we'll open a single tab with the
// default profile.
// Arguments:
// - <unused>
// Return Value:
// - <none>
void TerminalPage::_OnFirstLayout(const IInspectable& /*sender*/, const IInspectable& /*eventArgs*/)
{
// Only let this succeed once.
_layoutUpdatedRevoker.revoke();
// This event fires every time the layout changes, but it is always the
// last one to fire in any layout change chain. That gives us great
// flexibility in finding the right point at which to initialize our
// renderer (and our terminal). Any earlier than the last layout update
// and we may not know the terminal's starting size.
if (_startupState == StartupState::NotInitialized)
// Actually start the terminal.
if (_appArgs.GetStartupActions().empty())
{
_OpenNewTab(nullptr);
}
else
{
_startupState = StartupState::InStartup;
_appArgs.ValidateStartupCommands();
if (_appArgs.GetStartupActions().empty())
{
_OpenNewTab(nullptr);
_startupState = StartupState::Initialized;
_InitializedHandlers(*this, nullptr);
}
else
{
_ProcessStartupActions();
}
// This will kick off a chain of events to perform each startup
// action. As each startup action is completed, the next will be
// fired.
_ProcessNextStartupAction();
}
}
// Method Description:
// - Process all the startup actions in our list of startup actions. We'll
// do this all at once here.
// - Process the next startup action in our list of startup actions. When
// that action is complete, fire the next (if there are any more).
// Arguments:
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget TerminalPage::_ProcessStartupActions()
fire_and_forget TerminalPage::_ProcessNextStartupAction()
{
// If there are no actions left, do nothing.
if (_appArgs.GetStartupActions().empty())
{
return;
}
// Get the next action to be processed
auto nextAction = _appArgs.GetStartupActions().front();
_appArgs.GetStartupActions().pop_front();
auto weakThis{ get_weak() };
// Handle it on a subsequent pass of the UI thread.
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
// Handle it on the UI thread.
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Low);
if (auto page{ weakThis.get() })
{
for (const auto& action : _appArgs.GetStartupActions())
{
_actionDispatch->DoAction(action);
}
_startupState = StartupState::Initialized;
_InitializedHandlers(*this, nullptr);
page->_actionDispatch->DoAction(nextAction);
// Kick off the next action to be handled (if necessary)
page->_ProcessNextStartupAction();
}
}
// Method Description:
// - Show a ContentDialog with a single "Ok" button to dismiss. Looks up the
// the title and text from our Resources using the provided keys.
// - Only one dialog can be visible at a time. If another dialog is visible
// when this is called, nothing happens. See _ShowDialog for details
// Arguments:
// - titleKey: The key to use to lookup the title text from our resources.
// - contentKey: The key to use to lookup the content text from our resources.
void TerminalPage::ShowOkDialog(const winrt::hstring& titleKey,
const winrt::hstring& contentKey)
{
auto title = GetLibraryResourceString(titleKey);
auto message = GetLibraryResourceString(contentKey);
auto buttonText = RS_(L"Ok");
WUX::Controls::ContentDialog dialog;
dialog.Title(winrt::box_value(title));
dialog.Content(winrt::box_value(message));
dialog.CloseButtonText(buttonText);
dialog.DefaultButton(WUX::Controls::ContentDialogButton::Close);
_showDialogHandlers(*this, dialog);
}
// Method Description:
// - Show a dialog with "About" information. Displays the app's Display
// Name, version, getting started link, documentation link, release
// Notes link, and privacy policy link.
void TerminalPage::_ShowAboutDialog()
{
_showDialogHandlers(*this, FindName(L"AboutDialog").try_as<WUX::Controls::ContentDialog>());
}
const auto title = RS_(L"AboutTitleText");
const auto versionLabel = RS_(L"VersionLabelText");
const auto gettingStartedLabel = RS_(L"GettingStartedLabelText");
const auto documentationLabel = RS_(L"DocumentationLabelText");
const auto releaseNotesLabel = RS_(L"ReleaseNotesLabelText");
const auto privacyPolicyLabel = RS_(L"PrivacyPolicyLabelText");
const auto gettingStartedUriValue = RS_(L"GettingStartedUriValue");
const auto documentationUriValue = RS_(L"DocumentationUriValue");
const auto releaseNotesUriValue = RS_(L"ReleaseNotesUriValue");
const auto privacyPolicyUriValue = RS_(L"PrivacyPolicyUriValue");
const auto package = winrt::Windows::ApplicationModel::Package::Current();
const auto packageName = package.DisplayName();
const auto version = package.Id().Version();
winrt::Windows::UI::Xaml::Documents::Run about;
winrt::Windows::UI::Xaml::Documents::Run gettingStarted;
winrt::Windows::UI::Xaml::Documents::Run documentation;
winrt::Windows::UI::Xaml::Documents::Run releaseNotes;
winrt::Windows::UI::Xaml::Documents::Run privacyPolicy;
winrt::Windows::UI::Xaml::Documents::Hyperlink gettingStartedLink;
winrt::Windows::UI::Xaml::Documents::Hyperlink documentationLink;
winrt::Windows::UI::Xaml::Documents::Hyperlink releaseNotesLink;
winrt::Windows::UI::Xaml::Documents::Hyperlink privacyPolicyLink;
std::wstringstream aboutTextStream;
winrt::hstring TerminalPage::ApplicationDisplayName()
{
try
{
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
return package.DisplayName();
}
CATCH_LOG();
gettingStarted.Text(gettingStartedLabel);
documentation.Text(documentationLabel);
releaseNotes.Text(releaseNotesLabel);
privacyPolicy.Text(privacyPolicyLabel);
return RS_(L"AboutDialog_DisplayNameUnpackaged");
}
winrt::Windows::Foundation::Uri gettingStartedUri{ gettingStartedUriValue };
winrt::Windows::Foundation::Uri documentationUri{ documentationUriValue };
winrt::Windows::Foundation::Uri releaseNotesUri{ releaseNotesUriValue };
winrt::Windows::Foundation::Uri privacyPolicyUri{ privacyPolicyUriValue };
winrt::hstring TerminalPage::ApplicationVersion()
{
try
{
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
const auto version{ package.Id().Version() };
winrt::hstring formatted{ wil::str_printf<std::wstring>(L"%u.%u.%u.%u", version.Major, version.Minor, version.Build, version.Revision) };
return formatted;
}
CATCH_LOG();
gettingStartedLink.NavigateUri(gettingStartedUri);
documentationLink.NavigateUri(documentationUri);
releaseNotesLink.NavigateUri(releaseNotesUri);
privacyPolicyLink.NavigateUri(privacyPolicyUri);
return RS_(L"AboutDialog_VersionUnknown");
gettingStartedLink.Inlines().Append(gettingStarted);
documentationLink.Inlines().Append(documentation);
releaseNotesLink.Inlines().Append(releaseNotes);
privacyPolicyLink.Inlines().Append(privacyPolicy);
// Format our about text. It will look like the following:
// <Display Name>
// Version: <Major>.<Minor>.<Build>.<Revision>
// Getting Started
// Documentation
// Release Notes
// Privacy Policy
aboutTextStream << packageName.c_str() << L"\n";
aboutTextStream << versionLabel.c_str() << L" ";
aboutTextStream << version.Major << L"." << version.Minor << L"." << version.Build << L"." << version.Revision << L"\n";
winrt::hstring aboutText{ aboutTextStream.str() };
about.Text(aboutText);
const auto buttonText = RS_(L"Ok");
WUX::Controls::TextBlock aboutTextBlock;
aboutTextBlock.Inlines().Append(about);
aboutTextBlock.Inlines().Append(gettingStartedLink);
aboutTextBlock.Inlines().Append(documentationLink);
aboutTextBlock.Inlines().Append(releaseNotesLink);
aboutTextBlock.Inlines().Append(privacyPolicyLink);
aboutTextBlock.IsTextSelectionEnabled(true);
WUX::Controls::ContentDialog dialog;
dialog.Title(winrt::box_value(title));
dialog.Content(aboutTextBlock);
dialog.CloseButtonText(buttonText);
dialog.DefaultButton(WUX::Controls::ContentDialogButton::Close);
_showDialogHandlers(*this, dialog);
}
// Method Description:
@@ -267,7 +303,19 @@ namespace winrt::TerminalApp::implementation
// when this is called, nothing happens. See _ShowDialog for details
void TerminalPage::_ShowCloseWarningDialog()
{
_showDialogHandlers(*this, FindName(L"CloseAllDialog").try_as<WUX::Controls::ContentDialog>());
auto title = RS_(L"CloseWindowWarningTitle");
auto primaryButtonText = RS_(L"CloseAll");
auto closeButtonText = RS_(L"Cancel");
WUX::Controls::ContentDialog dialog;
dialog.Title(winrt::box_value(title));
dialog.CloseButtonText(closeButtonText);
dialog.PrimaryButtonText(primaryButtonText);
dialog.DefaultButton(WUX::Controls::ContentDialogButton::Primary);
auto token = dialog.PrimaryButtonClick({ this, &TerminalPage::_CloseWarningPrimaryButtonOnClick });
_showDialogHandlers(*this, dialog);
}
// Method Description:
@@ -358,15 +406,7 @@ namespace winrt::TerminalApp::implementation
// add static items
{
// 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 isUwp = false;
try
{
isUwp = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp();
}
CATCH_LOG();
const auto isUwp = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp();
if (!isUwp)
{
@@ -436,7 +476,6 @@ namespace winrt::TerminalApp::implementation
// control which profile is created and with possible other
// configurations. See CascadiaSettings::BuildSettings for more details.
void TerminalPage::_OpenNewTab(const winrt::TerminalApp::NewTerminalArgs& newTerminalArgs)
try
{
const auto [profileGuid, settings] = _settings->BuildSettings(newTerminalArgs);
@@ -460,7 +499,6 @@ namespace winrt::TerminalApp::implementation
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
}
CATCH_LOG();
winrt::fire_and_forget TerminalPage::_RemoveOnCloseRoutine(Microsoft::UI::Xaml::Controls::TabViewItem tabViewItem, winrt::com_ptr<TerminalPage> page)
{
@@ -476,25 +514,11 @@ namespace winrt::TerminalApp::implementation
// - settings: the TerminalSettings object to use to create the TerminalControl with.
void TerminalPage::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings)
{
const bool isFirstTab = _tabs.Size() == 0;
// Initialize the new tab
// Create a connection based on the values in our settings object.
auto connection = _CreateConnectionFromSettings(profileGuid, settings);
TerminalConnection::ITerminalConnection debugConnection{ nullptr };
if (_settings->GlobalSettings().DebugFeaturesEnabled())
{
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool bothAltsPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (bothAltsPressed)
{
std::tie(connection, debugConnection) = OpenDebugTapConnection(connection);
}
}
const auto connection = _CreateConnectionFromSettings(profileGuid, settings);
TermControl term{ settings, connection };
// Add the new tab to the list of our tabs.
@@ -544,17 +568,20 @@ namespace winrt::TerminalApp::implementation
}
});
if (debugConnection) // this will only be set if global debugging is on and tap is active
// If this is the first tab, we don't need to kick off the event to get
// the tab's content added to the root of the page. just do it
// immediately.
if (isFirstTab)
{
TermControl newControl{ settings, debugConnection };
_RegisterTerminalEvents(newControl, *newTabImpl);
// Split (auto) with the debug tap.
newTabImpl->SplitPane(SplitState::Automatic, profileGuid, newControl);
_tabContent.Children().Clear();
_tabContent.Children().Append(newTabImpl->GetRootElement());
}
else
{
// This kicks off TabView::SelectionChanged, in response to which
// we'll attach the terminal's Xaml control to the Xaml root.
_tabView.SelectedItem(tabViewItem);
}
// This kicks off TabView::SelectionChanged, in response to which
// we'll attach the terminal's Xaml control to the Xaml root.
_tabView.SelectedItem(tabViewItem);
}
// Method Description:
@@ -783,30 +810,13 @@ namespace winrt::TerminalApp::implementation
{
if (auto index{ _GetFocusedTabIndex() })
{
try
auto focusedTab = _GetStrongTabImpl(*index);
const auto& profileGuid = focusedTab->GetFocusedProfile();
if (profileGuid.has_value())
{
auto focusedTab = _GetStrongTabImpl(*index);
// TODO: GH#5047 - In the future, we should get the Profile of
// the focused pane, and use that to build a new instance of the
// settings so we can duplicate this tab/pane.
//
// Currently, if the profile doesn't exist anymore in our
// settings, we'll silently do nothing.
//
// In the future, it will be preferable to just duplicate the
// current control's settings, but we can't do that currently,
// because we won't be able to create a new instance of the
// connection without keeping an instance of the original Profile
// object around.
const auto& profileGuid = focusedTab->GetFocusedProfile();
if (profileGuid.has_value())
{
const auto settings = _settings->BuildSettings(profileGuid.value());
_CreateNewTabFromSettings(profileGuid.value(), settings);
}
const auto settings = _settings->BuildSettings(profileGuid.value());
_CreateNewTabFromSettings(profileGuid.value(), settings);
}
CATCH_LOG();
}
}
@@ -891,36 +901,20 @@ namespace winrt::TerminalApp::implementation
// Wraparound math. By adding tabCount and then calculating modulo tabCount,
// we clamp the values to the range [0, tabCount) while still supporting moving
// leftward from 0 to tabCount - 1.
const auto newTabIndex = ((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount);
_SelectTab(newTabIndex);
_SetFocusedTabIndex(((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount));
}
}
// Method Description:
// - Sets focus to the desired tab. Returns false if the provided tabIndex
// is greater than the number of tabs we have.
// - During startup, we'll immediately set the selected tab as focused.
// - After startup, we'll dispatch an async method to set the the selected
// item of the TabView, which will then also trigger a
// TabView::SelectionChanged, handled in
// TerminalPage::_OnTabSelectionChanged
// Return Value:
// true iff we were able to select that tab index, false otherwise
bool TerminalPage::_SelectTab(const uint32_t tabIndex)
{
if (tabIndex >= 0 && tabIndex < _tabs.Size())
{
if (_startupState == StartupState::InStartup)
{
auto tab{ _GetStrongTabImpl(tabIndex) };
_tabView.SelectedItem(tab->GetTabViewItem());
_UpdatedSelectedTab(tabIndex);
}
else
{
_SetFocusedTabIndex(tabIndex);
}
_SetFocusedTabIndex(tabIndex);
return true;
}
return false;
@@ -973,16 +967,6 @@ namespace winrt::TerminalApp::implementation
return std::nullopt;
}
// Method Description:
// - An async method for changing the focused tab on the UI thread. This
// method will _only_ set the selected item of the TabView, which will
// then also trigger a TabView::SelectionChanged event, which we'll handle
// in TerminalPage::_OnTabSelectionChanged, where we'll mark the new tab
// as focused.
// Arguments:
// - tabIndex: the index in the list of tabs to focus.
// Return Value:
// - <none>
winrt::fire_and_forget TerminalPage::_SetFocusedTabIndex(const uint32_t tabIndex)
{
// GH#1117: This is a workaround because _tabView.SelectedIndex(tabIndex)
@@ -1091,65 +1075,42 @@ namespace winrt::TerminalApp::implementation
return;
}
try
auto focusedTab = _GetStrongTabImpl(*indexOpt);
winrt::Microsoft::Terminal::Settings::TerminalSettings controlSettings;
GUID realGuid;
bool profileFound = false;
if (splitMode == TerminalApp::SplitType::Duplicate)
{
auto focusedTab = _GetStrongTabImpl(*indexOpt);
winrt::Microsoft::Terminal::Settings::TerminalSettings controlSettings;
GUID realGuid;
bool profileFound = false;
if (splitMode == TerminalApp::SplitType::Duplicate)
std::optional<GUID> current_guid = focusedTab->GetFocusedProfile();
if (current_guid)
{
std::optional<GUID> current_guid = focusedTab->GetFocusedProfile();
if (current_guid)
{
profileFound = true;
controlSettings = _settings->BuildSettings(current_guid.value());
realGuid = current_guid.value();
}
// TODO: GH#5047 - In the future, we should get the Profile of
// the focused pane, and use that to build a new instance of the
// settings so we can duplicate this tab/pane.
//
// Currently, if the profile doesn't exist anymore in our
// settings, we'll silently do nothing.
//
// In the future, it will be preferable to just duplicate the
// current control's settings, but we can't do that currently,
// because we won't be able to create a new instance of the
// connection without keeping an instance of the original Profile
// object around.
profileFound = true;
controlSettings = _settings->BuildSettings(current_guid.value());
realGuid = current_guid.value();
}
if (!profileFound)
{
std::tie(realGuid, controlSettings) = _settings->BuildSettings(newTerminalArgs);
}
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings);
const auto canSplit = focusedTab->CanSplitPane(splitType);
if (!canSplit && _startupState == StartupState::Initialized)
{
return;
}
auto realSplitType = splitType;
if (realSplitType == SplitState::Automatic && _startupState < StartupState::Initialized)
{
float contentWidth = gsl::narrow_cast<float>(_tabContent.ActualWidth());
float contentHeight = gsl::narrow_cast<float>(_tabContent.ActualHeight());
realSplitType = focusedTab->PreCalculateAutoSplit({ contentWidth, contentHeight });
}
TermControl newControl{ controlSettings, controlConnection };
// Hookup our event handlers to the new terminal
_RegisterTerminalEvents(newControl, *focusedTab);
focusedTab->SplitPane(realSplitType, realGuid, newControl);
}
CATCH_LOG();
if (!profileFound)
{
std::tie(realGuid, controlSettings) = _settings->BuildSettings(newTerminalArgs);
}
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings);
const auto canSplit = focusedTab->CanSplitPane(splitType);
if (!canSplit)
{
return;
}
TermControl newControl{ controlSettings, controlConnection };
// Hookup our event handlers to the new terminal
_RegisterTerminalEvents(newControl, *focusedTab);
focusedTab->SplitPane(splitType, realGuid, newControl);
}
// Method Description:
@@ -1465,31 +1426,6 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_UpdatedSelectedTab(const int32_t index)
{
// Unfocus all the tabs.
for (auto tab : _tabs)
{
auto tabImpl{ _GetStrongTabImpl(tab) };
tabImpl->SetFocused(false);
}
if (index >= 0)
{
try
{
auto tab{ _GetStrongTabImpl(index) };
_tabContent.Children().Clear();
_tabContent.Children().Append(tab->GetRootElement());
tab->SetFocused(true);
_titleChangeHandlers(*this, Title());
}
CATCH_LOG();
}
}
// Method Description:
// - Responds to the TabView control's Selection Changed event (to move a
// new terminal control into focus) when not in in the middle of a tab rearrangement.
@@ -1502,7 +1438,28 @@ namespace winrt::TerminalApp::implementation
{
auto tabView = sender.as<MUX::Controls::TabView>();
auto selectedIndex = tabView.SelectedIndex();
_UpdatedSelectedTab(selectedIndex);
// Unfocus all the tabs.
for (auto tab : _tabs)
{
auto tabImpl{ _GetStrongTabImpl(tab) };
tabImpl->SetFocused(false);
}
if (selectedIndex >= 0)
{
try
{
auto tab{ _GetStrongTabImpl(selectedIndex) };
_tabContent.Children().Clear();
_tabContent.Children().Append(tab->GetRootElement());
tab->SetFocused(true);
_titleChangeHandlers(*this, Title());
}
CATCH_LOG();
}
}
}
@@ -1568,28 +1525,16 @@ namespace winrt::TerminalApp::implementation
for (auto& profile : profiles)
{
const GUID profileGuid = profile.GetGuid();
const auto settings = _settings->BuildSettings(profileGuid);
try
for (auto tab : _tabs)
{
// BuildSettings can throw an exception if the profileGuid does
// not belong to an actual profile in the list of profiles.
const auto settings = _settings->BuildSettings(profileGuid);
for (auto tab : _tabs)
{
// Attempt to reload the settings of any panes with this profile
auto tabImpl{ _GetStrongTabImpl(tab) };
tabImpl->UpdateSettings(settings, profileGuid);
}
// Attempt to reload the settings of any panes with this profile
auto tabImpl{ _GetStrongTabImpl(tab) };
tabImpl->UpdateSettings(settings, profileGuid);
}
CATCH_LOG();
}
// GH#2455: If there are any panes with controls that had been
// initialized with a Profile that no longer exists in our list of
// profiles, we'll leave it unmodified. The profile doesn't exist
// anymore, so we can't possibly update its settings.
// Update the icon of the tab for the currently focused profile in that tab.
for (auto tab : _tabs)
{

View File

@@ -20,13 +20,6 @@ namespace TerminalAppLocalTests
namespace winrt::TerminalApp::implementation
{
enum StartupState : int
{
NotInitialized = 0,
InStartup = 1,
Initialized = 2
};
struct TerminalPage : TerminalPageT<TerminalPage>
{
public:
@@ -38,13 +31,12 @@ namespace winrt::TerminalApp::implementation
hstring Title();
void ShowOkDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey);
void TitlebarClicked();
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
winrt::hstring ApplicationDisplayName();
winrt::hstring ApplicationVersion();
void CloseWindow();
int32_t SetStartupCommandline(winrt::array_view<const hstring> args);
@@ -56,11 +48,8 @@ namespace winrt::TerminalApp::implementation
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTitleBarContent, _setTitleBarContentHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(ShowDialog, _showDialogHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Controls::ContentDialog);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(ToggleFullscreen, _toggleFullscreenHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::ToggleFullscreenEventArgs);
TYPED_EVENT(Initialized, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::RoutedEventArgs);
private:
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
// If you add controls here, but forget to null them either here or in
// the ctor, you're going to have a bad time. It'll mysteriously fail to
// activate the app.
@@ -86,12 +75,9 @@ namespace winrt::TerminalApp::implementation
winrt::com_ptr<ShortcutActionDispatch> _actionDispatch{ winrt::make_self<ShortcutActionDispatch>() };
winrt::Windows::UI::Xaml::Controls::Grid::LayoutUpdated_revoker _layoutUpdatedRevoker;
StartupState _startupState{ StartupState::NotInitialized };
::TerminalApp::AppCommandlineArgs _appArgs;
int _ParseArgs(winrt::array_view<const hstring>& args);
winrt::fire_and_forget _ProcessStartupActions();
fire_and_forget _ProcessNextStartupAction();
void _ShowAboutDialog();
void _ShowCloseWarningDialog();
@@ -155,8 +141,6 @@ namespace winrt::TerminalApp::implementation
void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs);
void _OnContentSizeChanged(const IInspectable& /*sender*/, Windows::UI::Xaml::SizeChangedEventArgs const& e);
void _OnTabCloseRequested(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabCloseRequestedEventArgs& eventArgs);
void _OnFirstLayout(const IInspectable& sender, const IInspectable& eventArgs);
void _UpdatedSelectedTab(const int32_t index);
void _Find();

View File

@@ -13,15 +13,10 @@ namespace TerminalApp
Int32 SetStartupCommandline(String[] commands);
String EarlyExitMessage { get; };
// XAML bound properties
String ApplicationDisplayName { get; };
String ApplicationVersion { get; };
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.Controls.ContentDialog> ShowDialog;
event Windows.Foundation.TypedEventHandler<Object, ToggleFullscreenEventArgs> ToggleFullscreen;
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.RoutedEventArgs> Initialized;
}
}

View File

@@ -19,39 +19,5 @@ the MIT License. See LICENSE in the project root for license information. -->
<local:TabRowControl x:Name="TabRow" Grid.Row="0" />
<Grid x:Name="TabContent" Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
<ContentDialog
x:Load="False"
x:Name="AboutDialog"
x:Uid="AboutDialog"
DefaultButton="Close">
<StackPanel Orientation="Vertical">
<TextBlock IsTextSelectionEnabled="True">
<Run Text="{x:Bind ApplicationDisplayName}" /> <LineBreak />
<Run x:Uid="AboutDialog_VersionLabel" />
<Run Text="{x:Bind ApplicationVersion}" />
</TextBlock>
<HyperlinkButton
x:Uid="AboutDialog_GettingStartedLink"
NavigateUri="https://aka.ms/terminal-getting-started" />
<HyperlinkButton
x:Uid="AboutDialog_DocumentationLink"
NavigateUri="https://aka.ms/terminal-documentation" />
<HyperlinkButton
x:Uid="AboutDialog_ReleaseNotesLink"
NavigateUri="https://aka.ms/terminal-release-notes" />
<HyperlinkButton
x:Uid="AboutDialog_PrivacyPolicyLink"
NavigateUri="https://aka.ms/terminal-privacy-policy" />
</StackPanel>
</ContentDialog>
<ContentDialog
x:Load="False"
x:Name="CloseAllDialog"
x:Uid="CloseAllDialog"
DefaultButton="Primary"
PrimaryButtonClick="_CloseWarningPrimaryButtonOnClick">
</ContentDialog>
</Grid>
</Page>

View File

@@ -107,7 +107,6 @@
<ClInclude Include="../ActionAndArgs.h">
<DependentUpon>../ActionArgs.idl</DependentUpon>
</ClInclude>
<ClInclude Include="../DebugTapConnection.h" />
<ClInclude Include="../AppKeyBindings.h">
<DependentUpon>../AppKeyBindings.idl</DependentUpon>
</ClInclude>
@@ -154,7 +153,6 @@
<ClCompile Include="../WslDistroGenerator.cpp" />
<ClCompile Include="../AzureCloudShellGenerator.cpp" />
<ClCompile Include="../Pane.LayoutSizeNode.cpp" />
<ClCompile Include="../DebugTapConnection.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>

View File

@@ -172,13 +172,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
TextBlock().FontSize(fontSizePx);
TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));
const auto canvasActualWidth = Canvas().ActualWidth();
const auto widthToTerminalEnd = canvasActualWidth - ::base::ClampedNumeric<double>(clientCursorPos.X);
// Make sure that we're setting the MaxWidth to a positive number - a
// negative number here will crash us in mysterious ways with a useless
// stack trace
const auto newMaxWidth = std::max<double>(0.0, widthToTerminalEnd);
TextBlock().MaxWidth(newMaxWidth);
const auto widthToTerminalEnd = Canvas().ActualWidth() - ::base::ClampedNumeric<double>(clientCursorPos.X);
TextBlock().MaxWidth(widthToTerminalEnd);
// Set the text block bounds
const auto yOffset = ::base::ClampedNumeric<float>(TextBlock().ActualHeight()) - fontHeight;

View File

@@ -70,36 +70,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_lastMouseClickTimestamp{},
_lastMouseClickPos{},
_selectionNeedsToBeCopied{ false },
_searchBox{ nullptr }
_searchBox{ nullptr },
_textCursor{ Windows::UI::Core::CoreCursorType::IBeam, 0 },
_pointerCursor{ Windows::UI::Core::CoreCursorType::Arrow, 0 }
{
_EnsureStaticInitialization();
InitializeComponent();
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
_terminal->SetTitleChangedCallback(pfnTitleChanged);
auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1);
_terminal->SetBackgroundCallback(pfnBackgroundColorChanged);
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
// This event is explicitly revoked in the destructor: does not need weak_ref
auto onReceiveOutputFn = [this](const hstring str) {
_terminal->Write(str);
};
_connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn);
auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1);
_terminal->SetWriteInputCallback(inputFn);
// Subscribe to the connection's disconnected event and call our connection closed handlers.
_connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) {
_ConnectionStateChangedHandlers(*this, nullptr);
});
// Initialize the terminal only once the swapchainpanel is loaded - that
// way, we'll be able to query the real pixel size it got on layout
_layoutUpdatedRevoker = SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
@@ -115,9 +92,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
});
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
_autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll });
// Subscribe to the connection's disconnected event and call our connection closed handlers.
_connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) {
_ConnectionStateChangedHandlers(*this, nullptr);
});
_ApplyUISettings();
}
@@ -150,7 +128,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TermControl::_Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive)
{
if (text.size() == 0 || _closing)
if (text.size() == 0)
{
return;
}
@@ -207,11 +185,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
{
if (_closing)
{
return;
}
// Update our control settings
_ApplyUISettings();
@@ -441,7 +414,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
Windows::UI::Xaml::Automation::Peers::AutomationPeer TermControl::OnCreateAutomationPeer()
try
{
if (_initializedTerminal && !_closing) // only set up the automation peer if we're ready to go live
if (GetUiaData())
{
// create a custom automation peer with this code pattern:
// (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers)
@@ -479,14 +452,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return _connection.State();
}
winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged()
winrt::fire_and_forget TermControl::SwapChainChanged()
{
{ // lock scope
auto terminalLock = _terminal->LockForReading();
if (!_initializedTerminal)
{
return;
}
if (!_initializedTerminal)
{
return;
}
auto chain = _renderEngine->GetSwapChain();
@@ -494,153 +464,192 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
co_await winrt::resume_foreground(Dispatcher());
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
{
auto terminalLock = _terminal->LockForWriting();
_AttachDxgiSwapChainToXaml(chain.Get());
if (_terminal)
{
auto lock = _terminal->LockForWriting();
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
nativePanel->SetSwapChain(chain.Get());
}
}
}
void TermControl::_AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain)
winrt::fire_and_forget TermControl::_SwapChainRoutine()
{
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
nativePanel->SetSwapChain(swapChain);
auto chain = _renderEngine->GetSwapChain();
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(Dispatcher());
if (auto control{ weakThis.get() })
{
if (_terminal)
{
auto lock = _terminal->LockForWriting();
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
nativePanel->SetSwapChain(chain.Get());
}
}
}
bool TermControl::_InitializeTerminal()
{
{ // scope for terminalLock
auto terminalLock = _terminal->LockForWriting();
if (_initializedTerminal)
{
return false;
}
if (_initializedTerminal)
const auto windowWidth = SwapChainPanel().ActualWidth(); // Width() and Height() are NaN?
const auto windowHeight = SwapChainPanel().ActualHeight();
if (windowWidth == 0 || windowHeight == 0)
{
return false;
}
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
// First create the render thread.
// Then stash a local pointer to the render thread so we can initialize it and enable it
// to paint itself *after* we hand off its ownership to the renderer.
// We split up construction and initialization of the render thread object this way
// because the renderer and render thread have circular references to each other.
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
auto* const localPointerToThread = renderThread.get();
// Now create the renderer and initialize the render thread.
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
// Set up the DX Engine
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
_renderer->AddRenderEngine(dxEngine.get());
// Initialize our font with the renderer
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
// and react accordingly.
_UpdateFont(true);
const COORD windowSize{ static_cast<short>(windowWidth), static_cast<short>(windowHeight) };
// Fist set up the dx engine with the window size in pixels.
// Then, using the font, get the number of characters that can fit.
// Resize our terminal connection to match that size, and initialize the terminal with that size.
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
THROW_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
// Update DxEngine's SelectionBackground
dxEngine->SetSelectionBackground(_settings.SelectionBackground());
const auto vp = dxEngine->GetViewportInCharacters(viewInPixels);
const auto width = vp.Width();
const auto height = vp.Height();
_connection.Resize(height, width);
// Override the default width and height to match the size of the swapChainPanel
_settings.InitialCols(width);
_settings.InitialRows(height);
_terminal->CreateFromSettings(_settings, renderTarget);
// Tell the DX Engine to notify us when the swap chain changes.
dxEngine->SetCallback(std::bind(&TermControl::SwapChainChanged, this));
// TODO:GH#3927 - Make it possible to hot-reload this setting. Right
// here, the setting will only be used when the Terminal is initialized.
dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
// TODO:GH#3927 - hot-reload this one too
// Update DxEngine's AntialiasingMode
switch (_settings.AntialiasingMode())
{
case TextAntialiasingMode::Cleartype:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
break;
case TextAntialiasingMode::Aliased:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
break;
case TextAntialiasingMode::Grayscale:
default:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
break;
}
THROW_IF_FAILED(dxEngine->Enable());
_renderEngine = std::move(dxEngine);
// This event is explicitly revoked in the destructor: does not need weak_ref
auto onReceiveOutputFn = [this](const hstring str) {
_terminal->Write(str);
};
_connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn);
auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1);
_terminal->SetWriteInputCallback(inputFn);
_terminal->SetMouseModeChangedCallback([weakThis = get_weak()]() {
if (auto strongThis{ weakThis.get() })
{
return false;
strongThis->_TerminalMouseModeChanged();
}
});
const auto windowWidth = SwapChainPanel().ActualWidth(); // Width() and Height() are NaN?
const auto windowHeight = SwapChainPanel().ActualHeight();
_SwapChainRoutine();
if (windowWidth == 0 || windowHeight == 0)
{
return false;
}
// Set up the height of the ScrollViewer and the grid we're using to fake our scrolling height
auto bottom = _terminal->GetViewport().BottomExclusive();
auto bufferHeight = bottom;
// First create the render thread.
// Then stash a local pointer to the render thread so we can initialize it and enable it
// to paint itself *after* we hand off its ownership to the renderer.
// We split up construction and initialization of the render thread object this way
// because the renderer and render thread have circular references to each other.
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
auto* const localPointerToThread = renderThread.get();
ScrollBar().Maximum(bufferHeight - bufferHeight);
ScrollBar().Minimum(0);
ScrollBar().Value(0);
ScrollBar().ViewportSize(bufferHeight);
// Now create the renderer and initialize the render thread.
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
localPointerToThread->EnablePainting();
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
_terminal->SetTitleChangedCallback(pfnTitleChanged);
// Set up the DX Engine
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
_renderer->AddRenderEngine(dxEngine.get());
auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1);
_terminal->SetBackgroundCallback(pfnBackgroundColorChanged);
// Initialize our font with the renderer
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
// and react accordingly.
_UpdateFont(true);
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
const COORD windowSize{ static_cast<short>(windowWidth), static_cast<short>(windowHeight) };
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
_autoScrollTimer.Tick({ get_weak(), &TermControl::_UpdateAutoScroll });
// Fist set up the dx engine with the window size in pixels.
// Then, using the font, get the number of characters that can fit.
// Resize our terminal connection to match that size, and initialize the terminal with that size.
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
THROW_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
// Set up blinking cursor
int blinkTime = GetCaretBlinkTime();
if (blinkTime != INFINITE)
{
// Create a timer
_cursorTimer = std::make_optional(DispatcherTimer());
_cursorTimer.value().Interval(std::chrono::milliseconds(blinkTime));
_cursorTimer.value().Tick({ get_weak(), &TermControl::_BlinkCursor });
_cursorTimer.value().Start();
}
else
{
// The user has disabled cursor blinking
_cursorTimer = std::nullopt;
}
// Update DxEngine's SelectionBackground
dxEngine->SetSelectionBackground(_settings.SelectionBackground());
// import value from WinUser (convert from milli-seconds to micro-seconds)
_multiClickTimer = GetDoubleClickTime() * 1000;
const auto vp = dxEngine->GetViewportInCharacters(viewInPixels);
const auto width = vp.Width();
const auto height = vp.Height();
_connection.Resize(height, width);
// Override the default width and height to match the size of the swapChainPanel
_settings.InitialCols(width);
_settings.InitialRows(height);
_terminal->CreateFromSettings(_settings, renderTarget);
// TODO:GH#3927 - Make it possible to hot-reload this setting. Right
// here, the setting will only be used when the Terminal is initialized.
dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
// TODO:GH#3927 - hot-reload this one too
// Update DxEngine's AntialiasingMode
switch (_settings.AntialiasingMode())
{
case TextAntialiasingMode::Cleartype:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
break;
case TextAntialiasingMode::Aliased:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
break;
case TextAntialiasingMode::Grayscale:
default:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
break;
}
THROW_IF_FAILED(dxEngine->Enable());
_renderEngine = std::move(dxEngine);
_AttachDxgiSwapChainToXaml(_renderEngine->GetSwapChain().Get());
// Tell the DX Engine to notify us when the swap chain changes.
// We do this after we initially set the swapchain so as to avoid unnecessary callbacks (and locking problems)
_renderEngine->SetCallback(std::bind(&TermControl::RenderEngineSwapChainChanged, this));
auto bottom = _terminal->GetViewport().BottomExclusive();
auto bufferHeight = bottom;
ScrollBar().Maximum(bufferHeight - bufferHeight);
ScrollBar().Minimum(0);
ScrollBar().Value(0);
ScrollBar().ViewportSize(bufferHeight);
localPointerToThread->EnablePainting();
// Set up blinking cursor
int blinkTime = GetCaretBlinkTime();
if (blinkTime != INFINITE)
{
// Create a timer
DispatcherTimer cursorTimer;
cursorTimer.Interval(std::chrono::milliseconds(blinkTime));
cursorTimer.Tick({ get_weak(), &TermControl::_CursorTimerTick });
cursorTimer.Start();
_cursorTimer.emplace(std::move(cursorTimer));
}
else
{
// The user has disabled cursor blinking
_cursorTimer = std::nullopt;
}
// import value from WinUser (convert from milli-seconds to micro-seconds)
_multiClickTimer = GetDoubleClickTime() * 1000;
// Focus the control here. If we do it during control initialization, then
// focus won't actually get passed to us. I believe this is because
// we're not technically a part of the UI tree yet, so focusing us
// becomes a no-op.
this->Focus(FocusState::Programmatic);
_connection.Start();
_initializedTerminal = true;
} // scope for TerminalLock
// call this event dispatcher outside of lock
// Focus the control here. If we do it during control initialization, then
// focus won't actually get passed to us. I believe this is because
// we're not technically a part of the UI tree yet, so focusing us
// becomes a no-op.
this->Focus(FocusState::Programmatic);
_connection.Start();
_initializedTerminal = true;
_InitializedHandlers(*this, nullptr);
return true;
}
@@ -713,6 +722,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
e.OriginalKey() == VirtualKey::RightWindows)
{
if (!_closing && e.OriginalKey() == VirtualKey::Shift)
{
// If the user presses or releases shift, check whether we're in mouse mode and the cursor needs updating
_TerminalMouseModeChanged();
}
e.Handled(true);
return;
}
@@ -764,6 +778,23 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
e.Handled(handled);
}
void TermControl::_KeyUpHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/,
Input::KeyRoutedEventArgs const& e)
{
// If the current focused element is a child element of searchbox,
// we do not send this event up to terminal
if (_searchBox && _searchBox->ContainsFocus())
{
return;
}
if (!_closing && e.OriginalKey() == VirtualKey::Shift)
{
// If the user presses or releases shift, check whether we're in mouse mode and the cursor needs updating
_TerminalMouseModeChanged();
}
}
// Method Description:
// - Send this particular key event to the terminal.
// See Terminal::SendKeyEvent for more information.
@@ -877,10 +908,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - point: the PointerPoint object representing a mouse event from our XAML input handler
bool TermControl::_CanSendVTMouseInput()
{
if (!_terminal)
{
return false;
}
// If the user is holding down Shift, suppress mouse events
// TODO GH#4875: disable/customize this functionality
const auto modifiers = _GetPressedModifierKeys();
@@ -891,6 +918,18 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return _terminal->IsTrackingMouseInput();
}
// Method Description:
// - Handles changes in mouse mode state
winrt::fire_and_forget TermControl::_TerminalMouseModeChanged()
{
co_await Dispatcher();
if (_oldCursor) // if we have an active cursor transition
{
auto coreWindow = Window::Current().CoreWindow();
coreWindow.PointerCursor(_CanSendVTMouseInput() ? _pointerCursor : _textCursor);
}
}
// Method Description:
// - handle a mouse click event. Begin selection process.
// Arguments:
@@ -899,11 +938,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_PointerPressedHandler(Windows::Foundation::IInspectable const& sender,
Input::PointerRoutedEventArgs const& args)
{
if (_closing)
{
return;
}
_CapturePointer(sender, args);
const auto ptr = args.Pointer();
@@ -1011,11 +1045,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_PointerMovedHandler(Windows::Foundation::IInspectable const& /*sender*/,
Input::PointerRoutedEventArgs const& args)
{
if (_closing)
{
return;
}
const auto ptr = args.Pointer();
const auto point = args.GetCurrentPoint(*this);
@@ -1119,11 +1148,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_PointerReleasedHandler(Windows::Foundation::IInspectable const& sender,
Input::PointerRoutedEventArgs const& args)
{
if (_closing)
{
return;
}
const auto ptr = args.Pointer();
const auto point = args.GetCurrentPoint(*this);
@@ -1165,6 +1189,45 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
args.Handled(true);
}
// Method Description:
// - Event handler for the PointerEntered event. We use this for cursor manipulation.
// Arguments:
// - sender: the XAML element responding to the pointer input
// - args: event data
void TermControl::_PointerEnteredHandler(Windows::Foundation::IInspectable const& /*sender*/,
Input::PointerRoutedEventArgs const& /*args*/)
{
if (_closing)
{
return;
}
auto coreWindow = Window::Current().CoreWindow();
_oldCursor = coreWindow.PointerCursor();
if (_terminal->IsTrackingMouseInput())
{
return;
}
coreWindow.PointerCursor(_textCursor);
}
// Method Description:
// - Event handler for the PointerExited event. We use this for cursor manipulation.
// Arguments:
// - sender: the XAML element responding to the pointer input
// - args: event data
void TermControl::_PointerExitedHandler(Windows::Foundation::IInspectable const& /*sender*/,
Input::PointerRoutedEventArgs const& /*args*/)
{
if (auto oldCursor{ std::exchange(_oldCursor, std::nullopt) })
{
auto coreWindow = Window::Current().CoreWindow();
coreWindow.PointerCursor(*oldCursor);
}
}
// Method Description:
// - Event handler for the PointerWheelChanged event. This is raised in
// response to mouse wheel changes. Depending upon what modifier keys are
@@ -1174,11 +1237,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/,
Input::PointerRoutedEventArgs const& args)
{
if (_closing)
{
return;
}
const auto point = args.GetCurrentPoint(*this);
if (_CanSendVTMouseInput())
@@ -1309,7 +1367,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_ScrollbarChangeHandler(Windows::Foundation::IInspectable const& /*sender*/,
Controls::Primitives::RangeBaseValueChangedEventArgs const& args)
{
if (_isTerminalInitiatedScroll || _closing)
if (_isTerminalInitiatedScroll)
{
return;
}
@@ -1494,7 +1552,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
return;
}
_focused = false;
if (_uiaEngine.get())
@@ -1566,6 +1623,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// concerned with initialization process. Value forwarded to event handler.
void TermControl::_UpdateFont(const bool initialUpdate)
{
auto lock = _terminal->LockForWriting();
const int newDpi = static_cast<int>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * SwapChainPanel().CompositionScaleX());
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
@@ -1592,12 +1651,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Refresh our font with the renderer
_UpdateFont();
auto lock = _terminal->LockForWriting();
// Resize the terminal's BUFFER to match the new font size. This does
// NOT change the size of the window, because that can lead to more
// problems (like what happens when you change the font size while the
// window is maximized?)
auto lock = _terminal->LockForWriting();
_DoResize(SwapChainPanel().ActualWidth(), SwapChainPanel().ActualHeight());
}
CATCH_LOG();
@@ -1611,7 +1669,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_SwapChainSizeChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/,
SizeChangedEventArgs const& e)
{
if (!_initializedTerminal || _closing)
if (!_initializedTerminal)
{
return;
}
@@ -1642,8 +1700,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Arguments:
// - sender: not used
// - e: not used
void TermControl::_CursorTimerTick(Windows::Foundation::IInspectable const& /* sender */,
Windows::Foundation::IInspectable const& /* e */)
void TermControl::_BlinkCursor(Windows::Foundation::IInspectable const& /* sender */,
Windows::Foundation::IInspectable const& /* e */)
{
if ((_closing) || (!_terminal->IsCursorBlinkingAllowed() && _terminal->IsCursorVisible()))
{
@@ -1795,7 +1853,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
hstring TermControl::Title()
{
hstring hstr{ _terminal->GetConsoleTitle() };
if (!_initializedTerminal)
return L"";
hstring hstr(_terminal->GetConsoleTitle());
return hstr;
}
@@ -1812,13 +1873,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - collapseText: collapse all of the text to one line
bool TermControl::CopySelectionToClipboard(bool collapseText)
{
if (_closing)
{
return false;
}
// no selection --> nothing to copy
if (!_terminal->IsSelectionActive())
if (_terminal == nullptr || !_terminal->IsSelectionActive())
{
return false;
}
@@ -1885,7 +1941,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_connectionStateChangedRevoker.revoke();
TSFInputControl().Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
_autoScrollTimer.Stop();
if (auto localConnection{ std::exchange(_connection, nullptr) })
{
@@ -1903,8 +1958,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// renderEngine is destroyed
}
// we don't destroy _terminal here; it now has the same lifetime as the
// control.
if (auto localTerminal{ std::exchange(_terminal, nullptr) })
{
_initializedTerminal = false;
// terminal is destroyed.
}
}
}
@@ -2218,11 +2276,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TermControl::_CompositionCompleted(winrt::hstring text)
{
if (_closing)
{
return;
}
_connection.WriteInput(text);
}
@@ -2235,14 +2288,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TermControl::_CurrentCursorPositionHandler(const IInspectable& /*sender*/, const CursorPositionEventArgs& eventArgs)
{
auto lock = _terminal->LockForReading();
if (!_initializedTerminal)
// If we haven't initialized yet, just quick return.
if (!_terminal)
{
// fake it
eventArgs.CurrentPosition({ 0, 0 });
return;
}
const COORD cursorPos = _terminal->GetCursorPosition();
Windows::Foundation::Point p = { gsl::narrow_cast<float>(cursorPos.X), gsl::narrow_cast<float>(cursorPos.Y) };
eventArgs.CurrentPosition(p);
@@ -2312,14 +2362,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - e: The DragEventArgs from the Drop event
// Return Value:
// - <none>
winrt::fire_and_forget TermControl::_DragDropHandler(Windows::Foundation::IInspectable const& /*sender*/,
DragEventArgs const e)
winrt::fire_and_forget TermControl::_DoDragDrop(DragEventArgs const e)
{
if (_closing)
{
return;
}
if (e.DataView().Contains(StandardDataFormats::StorageItems()))
{
auto items = co_await e.DataView().GetStorageItemsAsync();
@@ -2354,6 +2398,22 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
}
// Method Description:
// - Synchronous handler for the "Drop" event. We'll dispatch the async
// _DoDragDrop method to handle this, because getting information about
// the file that was potentially dropped onto us must be done off the UI
// thread.
// Arguments:
// - e: The DragEventArgs from the Drop event
// Return Value:
// - <none>
void TermControl::_DragDropHandler(Windows::Foundation::IInspectable const& /*sender*/,
DragEventArgs const& e)
{
// Dispatch an async method to handle the drop event.
_DoDragDrop(e);
}
// Method Description:
// - Handle the DragOver event. We'll signal that the drag operation we
// support is the "copy" operation, and we'll also customize the
@@ -2367,11 +2427,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_DragOverHandler(Windows::Foundation::IInspectable const& /*sender*/,
DragEventArgs const& e)
{
if (_closing)
{
return;
}
if (!e.DataView().Contains(StandardDataFormats::StorageItems()))
{
// We can't do anything for non-storageitems right now.

View File

@@ -77,8 +77,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void AdjustFontSize(int fontSizeDelta);
void ResetFontSize();
winrt::fire_and_forget RenderEngineSwapChainChanged();
void _AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain);
winrt::fire_and_forget SwapChainChanged();
void CreateSearchBoxControl();
@@ -168,6 +167,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
std::optional<winrt::Windows::UI::Core::CoreCursor> _oldCursor; // when we toggle the cursor, we have to save it here to restore it
winrt::Windows::UI::Core::CoreCursor _textCursor;
winrt::Windows::UI::Core::CoreCursor _pointerCursor;
void _ApplyUISettings();
void _InitializeBackgroundBrush();
winrt::fire_and_forget _BackgroundColorChanged(const uint32_t color);
@@ -176,21 +179,26 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _SetFontSize(int fontSize);
void _TappedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs const& e);
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _KeyUpHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _CharacterHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::CharacterReceivedRoutedEventArgs const& e);
void _PointerPressedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
void _PointerMovedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
void _PointerReleasedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
void _PointerEnteredHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
void _PointerExitedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
void _MouseWheelHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
void _ScrollbarChangeHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e);
void _GotFocusHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
void _LostFocusHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
winrt::fire_and_forget _DragDropHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const e);
void _DragDropHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const& e);
void _DragOverHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const& e);
winrt::fire_and_forget _DoDragDrop(Windows::UI::Xaml::DragEventArgs const e);
void _CursorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
void _BlinkCursor(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition);
void _SendInputToConnection(const std::wstring& wstr);
void _SendPastedTextToConnection(const std::wstring& wstr);
winrt::fire_and_forget _SwapChainRoutine();
void _SwapChainSizeChanged(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e);
void _SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender, Windows::Foundation::IInspectable const& args);
void _DoResize(const double newWidth, const double newHeight);
@@ -215,6 +223,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
bool _CanSendVTMouseInput();
winrt::fire_and_forget _TerminalMouseModeChanged();
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime);

View File

@@ -19,6 +19,7 @@
Tapped="_TappedHandler"
PointerWheelChanged="_MouseWheelHandler"
PreviewKeyDown="_KeyDownHandler"
KeyUp="_KeyUpHandler"
CharacterReceived="_CharacterHandler"
GotFocus="_GotFocusHandler"
LostFocus="_LostFocusHandler">
@@ -43,7 +44,9 @@
CompositionScaleChanged="_SwapChainScaleChanged"
PointerPressed="_PointerPressedHandler"
PointerMoved="_PointerMovedHandler"
PointerReleased="_PointerReleasedHandler" />
PointerReleased="_PointerReleasedHandler"
PointerEntered="_PointerEnteredHandler"
PointerExited="_PointerExitedHandler" />
<!-- Putting this in a grid w/ the SwapChainPanel
ensures that it's always aligned w/ the scrollbar -->

View File

@@ -701,7 +701,6 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
if (notifyScroll)
{
// TODO: don't do this, thanks migrie
_buffer->GetRenderTarget().TriggerRedrawAll();
_NotifyScrollEvent();
}
@@ -792,3 +791,12 @@ bool Terminal::IsCursorBlinkingAllowed() const noexcept
const auto& cursor = _buffer->GetCursor();
return cursor.IsBlinkingAllowed();
}
// Routine Description:
// - Sets up the callback for mouse input mode changes
// Parameters:
// - mouseModeChangedCallback: the callback
void Terminal::SetMouseModeChangedCallback(std::function<void()> mouseModeChangedCallback) noexcept
{
_terminalInput->SetMouseModeChangedCallback(std::move(mouseModeChangedCallback));
}

View File

@@ -172,6 +172,7 @@ public:
void SetCursorOn(const bool isOn) noexcept;
bool IsCursorBlinkingAllowed() const noexcept;
void SetMouseModeChangedCallback(std::function<void()> mouseModeChangedCallback) noexcept;
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp

View File

@@ -53,9 +53,6 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
static const SHORT TerminalViewWidth = 80;
static const SHORT TerminalViewHeight = 32;
// This test class is for tests that are supposed to emit something in the PTY layer
// and then check that they've been staged for presentation correctly inside
// the Terminal application. Which sequences were used to get here don't matter.
TEST_CLASS(ConptyRoundtripTests);
TEST_CLASS_SETUP(ClassSetup)
@@ -174,8 +171,6 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
TEST_METHOD(TestResizeHeight);
TEST_METHOD(ScrollWithMargins);
private:
bool _writeCallback(const char* const pch, size_t const cch);
void _flushFirstFrame();
@@ -1060,245 +1055,3 @@ void ConptyRoundtripTests::PassthroughHardReset()
TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y });
}
}
void ConptyRoundtripTests::ScrollWithMargins()
{
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
const auto initialTermView = term->GetViewport();
Log::Comment(L"Flush first frame.");
_flushFirstFrame();
// Fill up the buffer with some text.
// We're going to write something like this:
// AAAA
// BBBB
// CCCC
// ........
// QQQQ
// ****************
// The letters represent the data in the TMUX pane.
// The final *** line represents the mode line which we will
// attempt to hold in place and not scroll.
Log::Comment(L"Fill host with text pattern by feeding it into VT parser.");
const auto rowsToWrite = initialTermView.Height() - 1;
// For all lines but the last one, write out a few of a letter.
for (auto i = 0; i < rowsToWrite; ++i)
{
const wchar_t wch = static_cast<wchar_t>(L'A' + i);
hostSm.ProcessCharacter(wch);
hostSm.ProcessCharacter(wch);
hostSm.ProcessCharacter(wch);
hostSm.ProcessCharacter(wch);
hostSm.ProcessCharacter('\n');
}
// For the last one, write out the asterisks for the mode line.
for (auto i = 0; i < initialTermView.Width(); ++i)
{
hostSm.ProcessCharacter('*');
}
// no newline in the bottom right corner or it will move unexpectedly.
// Now set up the verification that the buffers are full of the pattern we expect.
// This function will verify the text backing buffers.
auto verifyBuffer = [&](const TextBuffer& tb) {
auto& cursor = tb.GetCursor();
// Verify the cursor is waiting in the bottom right corner
VERIFY_ARE_EQUAL(initialTermView.Height() - 1, cursor.GetPosition().Y);
VERIFY_ARE_EQUAL(initialTermView.Width() - 1, cursor.GetPosition().X);
// For all rows except the last one, verify that we have a run of four letters.
for (auto i = 0; i < rowsToWrite; ++i)
{
const std::wstring expectedString(4, static_cast<wchar_t>(L'A' + i));
const COORD expectedPos{ 0, gsl::narrow<SHORT>(i) };
TestUtils::VerifyExpectedString(tb, expectedString, expectedPos);
}
// For the last row, verify we have an entire row of asterisks for the mode line.
const std::wstring expectedModeLine(initialTermView.Width(), L'*');
const COORD expectedPos{ 0, gsl::narrow<SHORT>(rowsToWrite) };
TestUtils::VerifyExpectedString(tb, expectedModeLine, expectedPos);
};
// This will verify the text emitted from the PTY.
for (auto i = 0; i < rowsToWrite; ++i)
{
const std::string expectedString(4, static_cast<char>('A' + i));
expectedOutput.push_back(expectedString);
expectedOutput.push_back("\r\n");
}
{
const std::string expectedString(initialTermView.Width(), '*');
expectedOutput.push_back(expectedString);
// Cursor gets reset into bottom right corner as we're writing all the way into that corner.
std::stringstream ss;
ss << "\x1b[" << initialTermView.Height() << ";" << initialTermView.Width() << "H";
expectedOutput.push_back(ss.str());
}
Log::Comment(L"Verify host buffer contains pattern.");
// Verify the host side.
verifyBuffer(hostTb);
Log::Comment(L"Emit PTY frame and validate it transmits the right data.");
// Paint the frame
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"Verify terminal buffer contains pattern.");
// Verify the terminal side.
verifyBuffer(termTb);
Log::Comment(L"!!! OK. Set up the scroll region and let's get scrolling!");
// This is a simulation of what TMUX does to scroll everything except the mode line.
// First build up our VT strings...
std::wstring reducedScrollRegion;
{
std::wstringstream wss;
// For 20 tall buffer...
// ESC[1;19r
// Set scroll region to lines 1-19.
wss << L"\x1b[1;" << initialTermView.Height() - 1 << L"r";
reducedScrollRegion = wss.str();
}
std::wstring completeScrollRegion;
{
std::wstringstream wss;
// For 20 tall buffer...
// ESC[1;20r
// Set scroll region to lines 1-20. (or the whole buffer)
wss << L"\x1b[1;" << initialTermView.Height() << L"r";
completeScrollRegion = wss.str();
}
std::wstring reducedCursorBottomRight;
{
std::wstringstream wss;
// For 20 tall and 100 wide buffer
// ESC[19;100H
// Put cursor on line 19 (1 before last) and the right most column 100.
// (Remember that in VT, we start counting from 1 not 0.)
wss << L"\x1b[" << initialTermView.Height() - 1 << L";" << initialTermView.Width() << "H";
reducedCursorBottomRight = wss.str();
}
std::wstring completeCursorAtPromptLine;
{
std::wstringstream wss;
// For 20 tall and 100 wide buffer
// ESC[19;1H
// Put cursor on line 19 (1 before last) and the left most column 1.
// (Remember that in VT, we start counting from 1 not 0.)
wss << L"\x1b[" << initialTermView.Height() - 1 << L";1H";
completeCursorAtPromptLine = wss.str();
}
Log::Comment(L"Perform all the operations on the buffer.");
// OK this is what TMUX does.
// 1. Mark off the scroll area as everything but the mode line.
hostSm.ProcessString(reducedScrollRegion);
// 2. Put the cursor in the bottom-right corner of the scroll region.
hostSm.ProcessString(reducedCursorBottomRight);
// 3. Send a single newline which should do the heavy lifting
// of pushing everything in the scroll region up by 1 line and
// leave everything outside the region alone.
// This entire block is subject to change in the future with optimizations.
{
// Cursor gets redrawn in the bottom right of the scroll region with the repaint that is forced
// early while the screen is rotated.
std::stringstream ss;
ss << "\x1b[" << initialTermView.Height() - 1 << ";" << initialTermView.Width() << "H";
expectedOutput.push_back(ss.str());
expectedOutput.push_back("\x1b[?25h"); // turn the cursor back on too.
}
hostSm.ProcessString(L"\n");
// 4. Remove the scroll area by setting it to the entire size of the viewport.
hostSm.ProcessString(completeScrollRegion);
// 5. Put the cursor back at the beginning of the new line that was just revealed.
hostSm.ProcessString(completeCursorAtPromptLine);
// Set up the verifications like above.
auto verifyBufferAfter = [&](const TextBuffer& tb) {
auto& cursor = tb.GetCursor();
// Verify the cursor is waiting on the freshly revealed line (1 above mode line)
// and in the left most column.
VERIFY_ARE_EQUAL(initialTermView.Height() - 2, cursor.GetPosition().Y);
VERIFY_ARE_EQUAL(0, cursor.GetPosition().X);
// For all rows except the last two, verify that we have a run of four letters.
for (auto i = 0; i < rowsToWrite - 1; ++i)
{
// Start with B this time because the A line got scrolled off the top.
const std::wstring expectedString(4, static_cast<wchar_t>(L'B' + i));
const COORD expectedPos{ 0, gsl::narrow<SHORT>(i) };
TestUtils::VerifyExpectedString(tb, expectedString, expectedPos);
}
// For the second to last row, verify that it is blank.
{
const std::wstring expectedBlankLine(initialTermView.Width(), L' ');
const COORD blankLinePos{ 0, gsl::narrow<SHORT>(rowsToWrite - 1) };
TestUtils::VerifyExpectedString(tb, expectedBlankLine, blankLinePos);
}
// For the last row, verify we have an entire row of asterisks for the mode line.
{
const std::wstring expectedModeLine(initialTermView.Width(), L'*');
const COORD modeLinePos{ 0, gsl::narrow<SHORT>(rowsToWrite) };
TestUtils::VerifyExpectedString(tb, expectedModeLine, modeLinePos);
}
};
// This will verify the text emitted from the PTY.
expectedOutput.push_back("\x1b[H"); // cursor returns to top left corner.
for (auto i = 0; i < rowsToWrite - 1; ++i)
{
const std::string expectedString(4, static_cast<char>('B' + i));
expectedOutput.push_back(expectedString);
expectedOutput.push_back("\x1b[K"); // erase the rest of the line.
expectedOutput.push_back("\r\n");
}
{
expectedOutput.push_back(""); // nothing for the empty line
expectedOutput.push_back("\x1b[K"); // erase the rest of the line.
expectedOutput.push_back("\r\n");
}
{
const std::string expectedString(initialTermView.Width(), '*');
expectedOutput.push_back(expectedString);
}
{
// Cursor gets reset into second line from bottom, left most column
std::stringstream ss;
ss << "\x1b[" << initialTermView.Height() - 1 << ";1H";
expectedOutput.push_back(ss.str());
}
expectedOutput.push_back("\x1b[?25h"); // turn the cursor back on too.
Log::Comment(L"Verify host buffer contains pattern moved up one and mode line still in place.");
// Verify the host side.
verifyBufferAfter(hostTb);
Log::Comment(L"Emit PTY frame and validate it transmits the right data.");
// Paint the frame
VERIFY_SUCCEEDED(renderer.PaintFrame());
Log::Comment(L"Verify terminal buffer contains pattern moved up one and mode line still in place.");
// Verify the terminal side.
verifyBufferAfter(termTb);
}

View File

@@ -38,7 +38,7 @@ namespace Microsoft.Terminal.Wpf
WM_MOUSEACTIVATE = 0x0021,
WM_GETOBJECT = 0x003D,
/// <summary>
/// The WM_WINDOWPOSCHANGED message is sent to a window whose size, position, or place in the Z order has changed as a result of a call to the SetWindowPos function or another window-management function.
/// </summary>

View File

@@ -26,7 +26,7 @@ namespace Microsoft.Terminal.Wpf
private DispatcherTimer blinkTimer;
private NativeMethods.ScrollCallback scrollCallback;
private NativeMethods.WriteCallback writeCallback;
/// <summary>
/// Initializes a new instance of the <see cref="TerminalContainer"/> class.
/// </summary>
@@ -35,7 +35,7 @@ namespace Microsoft.Terminal.Wpf
this.MessageHook += this.TerminalContainer_MessageHook;
this.GotFocus += this.TerminalContainer_GotFocus;
this.Focusable = true;
var blinkTime = NativeMethods.GetCaretBlinkTime();
if (blinkTime != uint.MaxValue)

View File

@@ -9,7 +9,7 @@ namespace Microsoft.Terminal.Wpf
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
/// <summary>
/// A basic terminal control. This control can receive and render standard VT100 sequences.
/// </summary>

View File

@@ -171,33 +171,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
// displays the correct text.
if (newViewOrigin == viewport.Origin())
{
// Inside this block, we're shifting down at the bottom.
// This means that we had something like this:
// AAAA
// BBBB
// CCCC
// DDDD
// EEEE
//
// Our margins were set for lines A-D, but not on line E.
// So we circled the whole buffer up by one:
// BBBB
// CCCC
// DDDD
// EEEE
// <blank, was AAAA>
//
// Then we scrolled the contents of everything OUTSIDE the margin frame down.
// BBBB
// CCCC
// DDDD
// <blank, filled during scroll down of EEEE>
// EEEE
//
// And now we need to report that only the bottom line didn't "move" as we put the EEEE
// back where it started, but everything else moved.
// In this case, delta was 1. So the amount that moved is the entire viewport height minus the delta.
Viewport invalid = Viewport::FromDimensions(viewport.Origin(), { viewport.Width(), viewport.Height() - delta });
Viewport invalid = Viewport::FromDimensions(viewport.Origin(), { viewport.Width(), delta });
screenInfo.GetRenderTarget().TriggerRedraw(invalid);
}

View File

@@ -481,7 +481,7 @@ namespace Conhost.UIA.Tests
TextPatternRange testRange = visibleRanges.First().Clone();
// assumes that range is a line range at the top of the screen buffer
Action<TextPatternRange> testTopBoundary = delegate (TextPatternRange range)
Action<TextPatternRange> testTopBoundary = delegate(TextPatternRange range)
{
// the first visible range is at the top of the screen
// buffer, we shouldn't be able to move the starting endpoint up

View File

@@ -31,7 +31,7 @@ namespace Conhost.UIA.Tests.Common
~ShortcutHelper()
{
this.Dispose(false);
this.Dispose(false);
}
public void Dispose()

View File

@@ -52,7 +52,7 @@ namespace Conhost.UIA.Tests.Elements
tab.Click();
Globals.WaitForTimeout();
this.PopulateItemsOnNavigate(this.propDialog.PropWindow);
}

View File

@@ -33,7 +33,7 @@ namespace Conhost.UIA.Tests.Elements
this.tabs.Add(new FontTab(this.propDialog));
this.tabs.Add(new LayoutTab(this.propDialog));
this.tabs.Add(new ColorsTab(this.propDialog));
}
private Tabs()

View File

@@ -105,7 +105,7 @@ namespace Conhost.UIA.Tests.Elements
app.UIRoot.SendKeys(Keys.Escape);
this.state = ViewportStates.Normal;
}
public void EnterMode(ViewportStates state)
{
if (state == ViewportStates.Normal)

View File

@@ -111,7 +111,7 @@ namespace Conhost.UIA.Tests
default:
throw new NotSupportedException();
}
afterScroll = app.GetScreenBufferInfo();
switch (dir)

View File

@@ -57,7 +57,7 @@ namespace Conhost.UIA.Tests
reg.BackupRegistry(); // we're going to modify the virtual terminal state for this, so back it up first.
VersionSelector.SetConsoleVersion(reg, ConsoleVersion.V2);
reg.SetDefaultValue(VIRTUAL_TERMINAL_KEY_NAME, VIRTUAL_TERMINAL_ON_VALUE);
bool haveVtAppPath = !string.IsNullOrEmpty(vtAppLocation);
Verify.IsTrue(haveVtAppPath, "Ensure that we passed in the location to VtApp.exe");
@@ -114,12 +114,12 @@ namespace Conhost.UIA.Tests
Log.Comment("Move cursor to the middle-ish");
Point cursorExpected = new Point();
// H is at 5, 1. VT coords are 1-based and buffer is 0-based so adjust.
cursorExpected.Y = 5 - 1;
cursorExpected.Y = 5 - 1;
cursorExpected.X = 1 - 1;
app.UIRoot.SendKeys("H");
// Move to middle-ish from here. 10 Bs and 10 Cs should about do it.
for (int i = 0; i < 10; i++)
for (int i=0; i < 10; i++)
{
app.UIRoot.SendKeys("BC");
cursorExpected.Y++;
@@ -715,7 +715,7 @@ namespace Conhost.UIA.Tests
}
delegate char GetExpectedChar(int rowId, int colId, int height, int width);
private static void ScreenFillHelper(CmdApp app, ViewportArea area, IntPtr hConsole)
{
Log.Comment("Fill screen with junk");

View File

@@ -212,7 +212,7 @@ namespace Conhost.UIA.Tests
[TestMethod]
public void TestSelection()
{
RunTest(TestSelectionImpl);
RunTest(TestSelectionImpl);
}
private void TestSelectionImpl(CmdApp app, ViewportArea area, IntPtr hConsole, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiex, Queue<EventData> expected, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiexOriginal)
@@ -295,7 +295,7 @@ namespace Conhost.UIA.Tests
[TestMethod]
public void TestLaunchAndExitChild()
{
RunTest(TestLaunchAndExitChildImpl);
RunTest(TestLaunchAndExitChildImpl);
}
private void TestLaunchAndExitChildImpl(CmdApp app, ViewportArea area, IntPtr hConsole, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiex, Queue<EventData> expected, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiexOriginal)
@@ -357,11 +357,11 @@ namespace Conhost.UIA.Tests
{
RunTest(TestScrollByWheelImpl);
}
private void TestScrollByWheelImpl(CmdApp app, ViewportArea area, IntPtr hConsole, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiex, Queue<EventData> expected, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiexOriginal)
{
int rowsPerScroll = app.GetRowsPerScroll();
int scrollDelta;
int scrollDelta;
// A. Scroll down.
{

View File

@@ -27,9 +27,6 @@ using namespace Microsoft::Console::Types;
class ConptyOutputTests
{
// This test class is to write some things into the PTY and then check that
// the rendering that is coming out of the VT-sequence generator is exactly
// as we expect it to be.
BEGIN_TEST_CLASS(ConptyOutputTests)
TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class")
END_TEST_CLASS()

View File

@@ -7,12 +7,11 @@
#include "til/at.h"
#include "til/color.h"
#include "til/math.h"
#include "til/some.h"
#include "til/size.h"
#include "til/point.h"
#include "til/operators.h"
#include "til/rectangle.h"
#include "til/operators.h"
#include "til/bitmap.h"
#include "til/u8u16convert.h"

View File

@@ -285,8 +285,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
for (const auto pt : rc)
{
auto idx = _rc.index_of(pt);
til::at(_bits, idx) = true;
til::at(_bits, _rc.index_of(pt)) = true;
}
_dirty |= rc;

View File

@@ -1,85 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
namespace til
{
// The til::math namespace contains TIL math guidance casts;
// they are intended to be used as the first argument to
// floating-point universal converters elsewhere in the til namespace.
namespace math
{
namespace details
{
struct ceiling_t
{
template<typename O, typename T>
static O cast(T val)
{
if constexpr (std::is_floating_point_v<T>)
{
THROW_HR_IF(E_ABORT, ::std::isnan(val));
return ::base::saturated_cast<O>(::std::ceil(val));
}
else
{
return ::base::saturated_cast<O>(val);
}
}
};
struct flooring_t
{
template<typename O, typename T>
static O cast(T val)
{
if constexpr (std::is_floating_point_v<T>)
{
THROW_HR_IF(E_ABORT, ::std::isnan(val));
return ::base::saturated_cast<O>(::std::floor(val));
}
else
{
return ::base::saturated_cast<O>(val);
}
}
};
struct rounding_t
{
template<typename O, typename T>
static O cast(T val)
{
if constexpr (std::is_floating_point_v<T>)
{
THROW_HR_IF(E_ABORT, ::std::isnan(val));
return ::base::saturated_cast<O>(::std::round(val));
}
else
{
return ::base::saturated_cast<O>(val);
}
}
};
struct truncating_t
{
template<typename O, typename T>
static O cast(T val)
{
if constexpr (std::is_floating_point_v<T>)
{
THROW_HR_IF(E_ABORT, ::std::isnan(val));
}
return ::base::saturated_cast<O>(val);
}
};
}
static constexpr details::ceiling_t ceiling; // positives become more positive, negatives become less negative
static constexpr details::flooring_t flooring; // positives become less positive, negatives become more negative
static constexpr details::rounding_t rounding; // it's rounding, from math class
static constexpr details::truncating_t truncating; // drop the decimal point, regardless of how close it is to the next value
}
}

View File

@@ -3,6 +3,10 @@
#pragma once
#include "rectangle.h"
#include "size.h"
#include "bitmap.h"
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
// Operators go here when they involve two headers that can't/don't include each other.

View File

@@ -53,22 +53,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
}
// This template will convert to size from anything that has a X and a Y field that are floating-point;
// a math type is required.
template<typename TilMath, typename TOther>
constexpr point(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().X)> && std::is_floating_point_v<decltype(std::declval<TOther>().Y)>, int> /*sentinel*/ = 0) :
point(TilMath::template cast<ptrdiff_t>(other.X), TilMath::template cast<ptrdiff_t>(other.Y))
{
}
// This template will convert to size from anything that has a x and a y field that are floating-point;
// a math type is required.
template<typename TilMath, typename TOther>
constexpr point(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().x)> && std::is_floating_point_v<decltype(std::declval<TOther>().y)>, int> /*sentinel*/ = 0) :
point(TilMath::template cast<ptrdiff_t>(other.x), TilMath::template cast<ptrdiff_t>(other.y))
{
}
constexpr bool operator==(const point& other) const noexcept
{
return _x == other._x &&
@@ -123,12 +107,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return point{ x, y };
}
point& operator+=(const point& other)
{
*this = *this + other;
return *this;
}
point operator-(const point& other) const
{
ptrdiff_t x;
@@ -140,12 +118,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return point{ x, y };
}
point& operator-=(const point& other)
{
*this = *this - other;
return *this;
}
point operator*(const point& other) const
{
ptrdiff_t x;
@@ -157,12 +129,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return point{ x, y };
}
point& operator*=(const point& other)
{
*this = *this * other;
return *this;
}
point operator/(const point& other) const
{
ptrdiff_t x;
@@ -174,12 +140,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return point{ x, y };
}
point& operator/=(const point& other)
{
*this = *this / other;
return *this;
}
constexpr ptrdiff_t x() const noexcept
{
return _x;

View File

@@ -3,6 +3,10 @@
#pragma once
#include "point.h"
#include "size.h"
#include "some.h"
#ifdef UNIT_TESTING
class RectangleTests;
#endif
@@ -632,32 +636,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return *this;
}
// MUL will scale the entire rectangle up by the size factor
rectangle operator*(const size& size)
{
auto topLeft = _topLeft;
auto bottomRight = _bottomRight;
topLeft = topLeft * size;
bottomRight = bottomRight * size;
return til::rectangle{ topLeft, bottomRight };
}
// DIV will scale the entire rectangle down by the size factor,
// but rounds the bottom-right corner out.
rectangle operator/(const size& size)
{
auto topLeft = _topLeft;
auto bottomRight = _bottomRight;
topLeft = topLeft / size;
// Move bottom right point into a size
// Use size specialization of divide_ceil to round up against the size given.
// Add leading addition to point to convert it back into a point.
bottomRight = til::point{} + til::size{ right(), bottom() }.divide_ceil(size);
return til::rectangle{ topLeft, bottomRight };
}
#pragma endregion
constexpr ptrdiff_t top() const noexcept

View File

@@ -53,30 +53,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
}
// This template will convert to size from anything that has a X and a Y field that are floating-point;
// a math type is required.
template<typename TilMath, typename TOther>
constexpr size(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().X)> && std::is_floating_point_v<decltype(std::declval<TOther>().Y)>, int> /*sentinel*/ = 0) :
size(TilMath::template cast<ptrdiff_t>(other.X), TilMath::template cast<ptrdiff_t>(other.Y))
{
}
// This template will convert to size from anything that has a cx and a cy field that are floating-point;
// a math type is required.
template<typename TilMath, typename TOther>
constexpr size(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().cx)> && std::is_floating_point_v<decltype(std::declval<TOther>().cy)>, int> /*sentinel*/ = 0) :
size(TilMath::template cast<ptrdiff_t>(other.cx), TilMath::template cast<ptrdiff_t>(other.cy))
{
}
// This template will convert to size from anything that has a Width and a Height field that are floating-point;
// a math type is required.
template<typename TilMath, typename TOther>
constexpr size(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().Width)> && std::is_floating_point_v<decltype(std::declval<TOther>().Height)>, int> /*sentinel*/ = 0) :
size(TilMath::template cast<ptrdiff_t>(other.Width), TilMath::template cast<ptrdiff_t>(other.Height))
{
}
constexpr bool operator==(const size& other) const noexcept
{
return _width == other._width &&

View File

@@ -273,12 +273,7 @@ using namespace Microsoft::Console::Render;
rect.right = std::accumulate(advancesSpan.cbegin(), advancesSpan.cend(), rect.right);
// Clip all drawing in this glyph run to where we expect.
// We need the AntialiasMode here to be Aliased to ensure
// that background boxes line up with each other and don't leave behind
// stray colors.
// See GH#3626 for more details.
d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);
d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
// Ensure we pop it on the way out
auto popclip = wil::scope_exit([&d2dContext]() noexcept {
d2dContext->PopAxisAlignedClip();

View File

@@ -65,8 +65,9 @@ using namespace Microsoft::Console::Types;
// TODO GH 2683: The default constructor should not throw.
DxEngine::DxEngine() :
RenderEngineBase(),
_invalidMap{},
_invalidScroll{},
_isInvalidUsed{ false },
_invalidRect{ 0 },
_invalidScroll{ 0 },
_presentParams{ 0 },
_presentReady{ false },
_presentScroll{ 0 },
@@ -74,16 +75,16 @@ DxEngine::DxEngine() :
_presentOffset{ 0 },
_isEnabled{ false },
_isPainting{ false },
_displaySizePixels{},
_displaySizePixels{ 0 },
_foregroundColor{ 0 },
_backgroundColor{ 0 },
_selectionBackground{},
_glyphCell{},
_glyphCell{ 0 },
_haveDeviceResources{ false },
_retroTerminalEffects{ false },
_antialiasingMode{ D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE },
_hwndTarget{ static_cast<HWND>(INVALID_HANDLE_VALUE) },
_sizeTarget{},
_sizeTarget{ 0 },
_dpi{ USER_DEFAULT_SCREEN_DPI },
_scale{ 1.0f },
_chainMode{ SwapChainMode::ForComposition },
@@ -237,8 +238,8 @@ HRESULT DxEngine::_SetupTerminalEffects()
// Setup the viewport.
D3D11_VIEWPORT vp;
vp.Width = _displaySizePixels.width<float>();
vp.Height = _displaySizePixels.height<float>();
vp.Width = static_cast<FLOAT>(_displaySizePixels.cx);
vp.Height = static_cast<FLOAT>(_displaySizePixels.cy);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
@@ -369,7 +370,7 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
// You can find out how to install it here:
// https://docs.microsoft.com/en-us/windows/uwp/gaming/use-the-directx-runtime-and-visual-studio-graphics-diagnostic-features
// clang-format on
D3D11_CREATE_DEVICE_DEBUG |
// D3D11_CREATE_DEVICE_DEBUG |
D3D11_CREATE_DEVICE_SINGLETHREADED;
const std::array<D3D_FEATURE_LEVEL, 5> FeatureLevels{ D3D_FEATURE_LEVEL_11_1,
@@ -423,7 +424,8 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
{
switch (_chainMode)
{
case SwapChainMode::ForHwnd: {
case SwapChainMode::ForHwnd:
{
// use the HWND's dimensions for the swap chain dimensions.
RECT rect = { 0 };
RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect));
@@ -452,10 +454,11 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
break;
}
case SwapChainMode::ForComposition: {
case SwapChainMode::ForComposition:
{
// Use the given target size for compositions.
SwapChainDesc.Width = _displaySizePixels.width<UINT>();
SwapChainDesc.Height = _displaySizePixels.height<UINT>();
SwapChainDesc.Width = _displaySizePixels.cx;
SwapChainDesc.Height = _displaySizePixels.cy;
// We're doing advanced composition pretty much for the purpose of pretty alpha, so turn it on.
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
@@ -530,11 +533,6 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
&props,
&_d2dRenderTarget));
// We need the AntialiasMode for non-text object to be Aliased to ensure
// that background boxes line up with each other and don't leave behind
// stray colors.
// See GH#3626 for more details.
_d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
_d2dRenderTarget->SetTextAntialiasMode(_antialiasingMode);
RETURN_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkRed),
@@ -630,8 +628,8 @@ void DxEngine::_ReleaseDeviceResources() noexcept
return _dwriteFactory->CreateTextLayout(string,
gsl::narrow<UINT32>(stringLength),
_dwriteTextFormat.Get(),
_displaySizePixels.width<float>(),
_glyphCell.height() != 0 ? _glyphCell.height<float>() : _displaySizePixels.height<float>(),
gsl::narrow<float>(_displaySizePixels.cx),
_glyphCell.cy != 0 ? _glyphCell.cy : gsl::narrow<float>(_displaySizePixels.cy),
ppTextLayout);
}
@@ -652,7 +650,9 @@ void DxEngine::_ReleaseDeviceResources() noexcept
[[nodiscard]] HRESULT DxEngine::SetWindowSize(const SIZE Pixels) noexcept
{
_sizeTarget = Pixels;
_invalidMap.resize(_sizeTarget / _glyphCell, true);
RETURN_IF_FAILED(InvalidateAll());
return S_OK;
}
@@ -686,8 +686,7 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
{
RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion);
_invalidMap.set(Viewport::FromExclusive(*psrRegion).ToInclusive());
_InvalidOr(*psrRegion);
return S_OK;
}
@@ -701,9 +700,8 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
{
RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor);
_invalidMap.set(*pcoordCursor);
return S_OK;
const SMALL_RECT sr = Microsoft::Console::Types::Viewport::FromCoord(*pcoordCursor).ToInclusive();
return Invalidate(&sr);
}
// Routine Description:
@@ -713,17 +711,13 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
// Return Value:
// - S_OK
[[nodiscard]] HRESULT DxEngine::InvalidateSystem(const RECT* const prcDirtyClient) noexcept
try
{
RETURN_HR_IF_NULL(E_INVALIDARG, prcDirtyClient);
// Dirty client is in pixels. Use divide specialization against glyph factor to make conversion
// to cells.
_invalidMap.set(til::rectangle{ *prcDirtyClient } / _glyphCell);
_InvalidOr(*prcDirtyClient);
return S_OK;
}
CATCH_RETURN();
// Routine Description:
// - Invalidates a series of character rectangles
@@ -749,24 +743,50 @@ CATCH_RETURN();
// Return Value:
// - S_OK
[[nodiscard]] HRESULT DxEngine::InvalidateScroll(const COORD* const pcoordDelta) noexcept
try
{
const til::point deltaCells{ *pcoordDelta };
if (deltaCells != til::point{ 0, 0 })
if (pcoordDelta->X != 0 || pcoordDelta->Y != 0)
{
const til::point deltaPixels = deltaCells * _glyphCell;
try
{
POINT delta = { 0 };
delta.x = pcoordDelta->X * _glyphCell.cx;
delta.y = pcoordDelta->Y * _glyphCell.cy;
// Shift the contents of the map and fill in revealed area.
_invalidMap.translate(deltaCells, true);
_InvalidOffset(delta);
// TODO: should we just maintain it all in cells?
_invalidScroll += deltaPixels;
_invalidScroll.cx += delta.x;
_invalidScroll.cy += delta.y;
// Add the revealed portion of the screen from the scroll to the invalid area.
const RECT display = _GetDisplayRect();
RECT reveal = display;
// X delta first
OffsetRect(&reveal, delta.x, 0);
IntersectRect(&reveal, &reveal, &display);
SubtractRect(&reveal, &display, &reveal);
if (!IsRectEmpty(&reveal))
{
_InvalidOr(reveal);
}
// Y delta second (subtract rect won't work if you move both)
reveal = display;
OffsetRect(&reveal, 0, delta.y);
IntersectRect(&reveal, &reveal, &display);
SubtractRect(&reveal, &display, &reveal);
if (!IsRectEmpty(&reveal))
{
_InvalidOr(reveal);
}
}
CATCH_RETURN();
}
return S_OK;
}
CATCH_RETURN();
// Routine Description:
// - Invalidates the entire window area
@@ -775,12 +795,12 @@ CATCH_RETURN();
// Return Value:
// - S_OK
[[nodiscard]] HRESULT DxEngine::InvalidateAll() noexcept
try
{
_invalidMap.set_all();
const RECT screen = _GetDisplayRect();
_InvalidOr(screen);
return S_OK;
}
CATCH_RETURN();
// Routine Description:
// - This currently has no effect in this renderer.
@@ -802,17 +822,23 @@ CATCH_RETURN();
// - <none>
// Return Value:
// - X by Y area in pixels of the surface
[[nodiscard]] til::size DxEngine::_GetClientSize() const noexcept
[[nodiscard]] SIZE DxEngine::_GetClientSize() const noexcept
{
switch (_chainMode)
{
case SwapChainMode::ForHwnd: {
case SwapChainMode::ForHwnd:
{
RECT clientRect = { 0 };
LOG_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &clientRect));
return til::rectangle{ clientRect }.size();
SIZE clientSize = { 0 };
clientSize.cx = clientRect.right - clientRect.left;
clientSize.cy = clientRect.bottom - clientRect.top;
return clientSize;
}
case SwapChainMode::ForComposition: {
case SwapChainMode::ForComposition:
{
SIZE size = _sizeTarget;
size.cx = static_cast<LONG>(size.cx * _scale);
size.cy = static_cast<LONG>(size.cy * _scale);
@@ -839,6 +865,90 @@ void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
cellsToPixels.bottom *= fontSize.cy;
}
// Routine Description:
// - Retrieves a rectangle representation of the pixel size of the
// surface we are drawing on
// Arguments:
// - <none>
// Return Value;
// - Origin-placed rectangle representing the pixel size of the surface
[[nodiscard]] RECT DxEngine::_GetDisplayRect() const noexcept
{
return { 0, 0, _displaySizePixels.cx, _displaySizePixels.cy };
}
// Routine Description:
// - Helper to shift the existing dirty rectangle by a pixel offset
// and crop it to still be within the bounds of the display surface
// Arguments:
// - delta - Adjustment distance in pixels
// - -Y is up, Y is down, -X is left, X is right.
// Return Value:
// - <none>
void DxEngine::_InvalidOffset(POINT delta)
{
if (_isInvalidUsed)
{
// Copy the existing invalid rect
RECT invalidNew = _invalidRect;
// Offset it to the new position
THROW_IF_WIN32_BOOL_FALSE(OffsetRect(&invalidNew, delta.x, delta.y));
// Get the rect representing the display
const RECT rectScreen = _GetDisplayRect();
// Ensure that the new invalid rectangle is still on the display
IntersectRect(&invalidNew, &invalidNew, &rectScreen);
_invalidRect = invalidNew;
}
}
// Routine description:
// - Adds the given character rectangle to the total dirty region
// - Will scale internally to pixels based on the current font.
// Arguments:
// - sr - character rectangle
// Return Value:
// - <none>
void DxEngine::_InvalidOr(SMALL_RECT sr) noexcept
{
RECT region;
region.left = sr.Left;
region.top = sr.Top;
region.right = sr.Right;
region.bottom = sr.Bottom;
_ScaleByFont(region, _glyphCell);
region.right += _glyphCell.cx;
region.bottom += _glyphCell.cy;
_InvalidOr(region);
}
// Routine Description:
// - Adds the given pixel rectangle to the total dirty region
// Arguments:
// - rc - Dirty pixel rectangle
// Return Value:
// - <none>
void DxEngine::_InvalidOr(RECT rc) noexcept
{
if (_isInvalidUsed)
{
UnionRect(&_invalidRect, &_invalidRect, &rc);
const RECT rcScreen = _GetDisplayRect();
IntersectRect(&_invalidRect, &_invalidRect, &rcScreen);
}
else
{
_invalidRect = rc;
_isInvalidUsed = true;
}
}
// Routine Description:
// - This is unused by this renderer.
// Arguments:
@@ -861,19 +971,24 @@ void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
// - Any DirectX error, a memory error, etc.
[[nodiscard]] HRESULT DxEngine::StartPaint() noexcept
{
FAIL_FAST_IF_FAILED(InvalidateAll());
RETURN_HR_IF(E_NOT_VALID_STATE, _isPainting); // invalid to start a paint while painting.
if (TraceLoggingProviderEnabled(g_hDxRenderProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto invalidatedStr = _invalidMap.to_string();
const auto invalidated = invalidatedStr.c_str();
#pragma warning(suppress : 26477 26485 26494 26482 26446 26447) // We don't control TraceLoggingWrite
TraceLoggingWrite(g_hDxRenderProvider,
"Invalid",
TraceLoggingWideString(invalidated),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
TraceLoggingWrite(g_hDxRenderProvider,
"Invalid",
TraceLoggingInt32(_invalidRect.bottom - _invalidRect.top, "InvalidHeight"),
TraceLoggingInt32((_invalidRect.bottom - _invalidRect.top) / _glyphCell.cy, "InvalidHeightChars"),
TraceLoggingInt32(_invalidRect.right - _invalidRect.left, "InvalidWidth"),
TraceLoggingInt32((_invalidRect.right - _invalidRect.left) / _glyphCell.cx, "InvalidWidthChars"),
TraceLoggingInt32(_invalidRect.left, "InvalidX"),
TraceLoggingInt32(_invalidRect.left / _glyphCell.cx, "InvalidXChars"),
TraceLoggingInt32(_invalidRect.top, "InvalidY"),
TraceLoggingInt32(_invalidRect.top / _glyphCell.cy, "InvalidYChars"),
TraceLoggingInt32(_invalidScroll.cx, "ScrollWidth"),
TraceLoggingInt32(_invalidScroll.cx / _glyphCell.cx, "ScrollWidthChars"),
TraceLoggingInt32(_invalidScroll.cy, "ScrollHeight"),
TraceLoggingInt32(_invalidScroll.cy / _glyphCell.cy, "ScrollHeightChars"));
if (_isEnabled)
{
@@ -884,7 +999,8 @@ void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
{
RETURN_IF_FAILED(_CreateDeviceResources(true));
}
else if (_displaySizePixels != clientSize)
else if (_displaySizePixels.cy != clientSize.cy ||
_displaySizePixels.cx != clientSize.cx)
{
// OK, we're going to play a dangerous game here for the sake of optimizing resize
// First, set up a complete clear of all device resources if something goes terribly wrong.
@@ -897,7 +1013,7 @@ void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
_d2dRenderTarget.Reset();
// Change the buffer size and recreate the render target (and surface)
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.width<UINT>(), clientSize.height<UINT>(), DXGI_FORMAT_B8G8R8A8_UNORM, 0));
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.cx, clientSize.cy, DXGI_FORMAT_B8G8R8A8_UNORM, 0));
RETURN_IF_FAILED(_PrepareRenderTarget());
// OK we made it past the parts that can cause errors. We can release our failure handler.
@@ -923,7 +1039,6 @@ void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
// Return Value:
// - Any DirectX error, a memory error, etc.
[[nodiscard]] HRESULT DxEngine::EndPaint() noexcept
try
{
RETURN_HR_IF(E_INVALIDARG, !_isPainting); // invalid to end paint when we're not painting
@@ -937,33 +1052,21 @@ try
if (SUCCEEDED(hr))
{
if (_invalidScroll != til::point{ 0, 0 })
if (_invalidScroll.cy != 0 || _invalidScroll.cx != 0)
{
// Copy `til::rectangles` into RECT map.
_presentDirty.assign(_invalidMap.begin(), _invalidMap.end());
_presentDirty = _invalidRect;
// The scroll rect is the entire screen minus the revealed areas.
// Get the entire screen into a rectangle.
til::rectangle scrollArea{ _displaySizePixels };
const RECT display = _GetDisplayRect();
SubtractRect(&_presentScroll, &display, &_presentDirty);
_presentOffset.x = _invalidScroll.cx;
_presentOffset.y = _invalidScroll.cy;
// Reduce the size of the rectangle by the scroll.
scrollArea -= til::size{} - _invalidScroll;
// Assign the area to the present storage
_presentScroll = scrollArea;
// Pass the offset.
_presentOffset = _invalidScroll;
// Now fill up the parameters structure from the member variables.
_presentParams.DirtyRectsCount = gsl::narrow<UINT>(_presentDirty.size());
_presentParams.pDirtyRects = _presentDirty.data();
_presentParams.DirtyRectsCount = 1;
_presentParams.pDirtyRects = &_presentDirty;
_presentParams.pScrollOffset = &_presentOffset;
_presentParams.pScrollRect = &_presentScroll;
// The scroll rect will be empty if we scrolled >= 1 full screen size.
// Present1 doesn't like that. So clear it out. Everything will be dirty anyway.
if (IsRectEmpty(&_presentScroll))
{
_presentParams.pScrollRect = nullptr;
@@ -980,13 +1083,13 @@ try
}
}
_invalidMap.reset_all();
_invalidRect = { 0 };
_isInvalidUsed = false;
_invalidScroll = {};
_invalidScroll = { 0 };
return hr;
}
CATCH_RETURN()
// Routine Description:
// - Copies the front surface of the swap chain (the one being displayed)
@@ -1038,8 +1141,8 @@ CATCH_RETURN()
{
HRESULT hr = S_OK;
/*hr = _dxgiSwapChain->Present(1, 0);*/
hr = _dxgiSwapChain->Present1(1, 0, &_presentParams);
hr = _dxgiSwapChain->Present(1, 0);
/*hr = _dxgiSwapChain->Present1(1, 0, &_presentParams);*/
if (FAILED(hr))
{
@@ -1058,7 +1161,7 @@ CATCH_RETURN()
RETURN_IF_FAILED(_CopyFrontToBack());
_presentReady = false;
_presentDirty.clear();
_presentDirty = { 0 };
_presentOffset = { 0 };
_presentScroll = { 0 };
_presentParams = { 0 };
@@ -1088,19 +1191,21 @@ CATCH_RETURN()
// - S_OK
[[nodiscard]] HRESULT DxEngine::PaintBackground() noexcept
{
D2D1_COLOR_F nothing = { 0 };
// Runs are counts of cells.
// Use a transform by the size of one cell to convert cells-to-pixels
// as we clear.
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Scale(_glyphCell));
for (const auto rect : _invalidMap.runs())
switch (_chainMode)
{
_d2dRenderTarget->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);
case SwapChainMode::ForHwnd:
_d2dRenderTarget->FillRectangle(D2D1::RectF(static_cast<float>(_invalidRect.left),
static_cast<float>(_invalidRect.top),
static_cast<float>(_invalidRect.right),
static_cast<float>(_invalidRect.bottom)),
_d2dBrushBackground.Get());
break;
case SwapChainMode::ForComposition:
D2D1_COLOR_F nothing = { 0 };
_d2dRenderTarget->Clear(nothing);
_d2dRenderTarget->PopAxisAlignedClip();
break;
}
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
return S_OK;
}
@@ -1121,7 +1226,9 @@ CATCH_RETURN()
try
{
// Calculate positioning of our origin.
D2D1_POINT_2F origin = til::point{ coord } * _glyphCell;
D2D1_POINT_2F origin;
origin.x = static_cast<float>(coord.X * _glyphCell.cx);
origin.y = static_cast<float>(coord.Y * _glyphCell.cy);
// Create the text layout
CustomTextLayout layout(_dwriteFactory.Get(),
@@ -1129,7 +1236,7 @@ CATCH_RETURN()
_dwriteTextFormat.Get(),
_dwriteFontFace.Get(),
clusters,
_glyphCell.width());
_glyphCell.cx);
// Get the baseline for this font as that's where we draw from
DWRITE_LINE_SPACING spacing;
@@ -1141,7 +1248,7 @@ CATCH_RETURN()
_d2dBrushBackground.Get(),
_dwriteFactory.Get(),
spacing,
_glyphCell,
D2D1::SizeF(gsl::narrow<FLOAT>(_glyphCell.cx), gsl::narrow<FLOAT>(_glyphCell.cy)),
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
// Layout then render the text
@@ -1172,8 +1279,10 @@ CATCH_RETURN()
_d2dBrushForeground->SetColor(_ColorFFromColorRef(color));
const auto font = _glyphCell;
D2D_POINT_2F target = til::point{ coordTarget } * font;
const auto font = _GetFontSize();
D2D_POINT_2F target;
target.x = static_cast<float>(coordTarget.X) * font.X;
target.y = static_cast<float>(coordTarget.Y) * font.Y;
D2D_POINT_2F start = { 0 };
D2D_POINT_2F end = { 0 };
@@ -1186,7 +1295,7 @@ CATCH_RETURN()
if (lines & GridLines::Top)
{
end = start;
end.x += font.width();
end.x += font.X;
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
}
@@ -1194,7 +1303,7 @@ CATCH_RETURN()
if (lines & GridLines::Left)
{
end = start;
end.y += font.height();
end.y += font.Y;
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
}
@@ -1207,28 +1316,28 @@ CATCH_RETURN()
// The top right corner inclusive is at 7,0 which is X (0) + Font Height (8) - 1 = 7.
// 0.5 pixel offset for crisp lines; -0.5 on the Y to fit _in_ the cell, not outside it.
start = { target.x + 0.5f, target.y + font.height() - 0.5f };
start = { target.x + 0.5f, target.y + font.Y - 0.5f };
if (lines & GridLines::Bottom)
{
end = start;
end.x += font.width() - 1.f;
end.x += font.X - 1.f;
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
}
start = { target.x + font.width() - 0.5f, target.y + 0.5f };
start = { target.x + font.X - 0.5f, target.y + 0.5f };
if (lines & GridLines::Right)
{
end = start;
end.y += font.height() - 1.f;
end.y += font.Y - 1.f;
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
}
// Move to the next character in this run.
target.x += font.width();
target.x += font.X;
}
return S_OK;
@@ -1247,7 +1356,17 @@ CATCH_RETURN()
_d2dBrushForeground->SetColor(_selectionBackground);
const auto resetColorOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); });
const D2D1_RECT_F draw = til::rectangle{ rect } *_glyphCell;
RECT pixels;
pixels.left = rect.Left * _glyphCell.cx;
pixels.top = rect.Top * _glyphCell.cy;
pixels.right = rect.Right * _glyphCell.cx;
pixels.bottom = rect.Bottom * _glyphCell.cy;
D2D1_RECT_F draw = { 0 };
draw.left = static_cast<float>(pixels.left);
draw.top = static_cast<float>(pixels.top);
draw.right = static_cast<float>(pixels.right);
draw.bottom = static_cast<float>(pixels.bottom);
_d2dRenderTarget->FillRectangle(draw, _d2dBrushForeground.Get());
@@ -1275,42 +1394,52 @@ enum class CursorPaintType
{
return S_FALSE;
}
// Create rectangular block representing where the cursor can fill.s
D2D1_RECT_F rect = til::rectangle{ til::point{options.coordCursor} } *_glyphCell;
// Create rectangular block representing where the cursor can fill.
D2D1_RECT_F rect = { 0 };
rect.left = static_cast<float>(options.coordCursor.X * _glyphCell.cx);
rect.top = static_cast<float>(options.coordCursor.Y * _glyphCell.cy);
rect.right = static_cast<float>(rect.left + _glyphCell.cx);
rect.bottom = static_cast<float>(rect.top + _glyphCell.cy);
// If we're double-width, make it one extra glyph wider
if (options.fIsDoubleWidth)
{
rect.right += _glyphCell.width();
rect.right += _glyphCell.cx;
}
CursorPaintType paintType = CursorPaintType::Fill;
switch (options.cursorType)
{
case CursorType::Legacy: {
case CursorType::Legacy:
{
// Enforce min/max cursor height
ULONG ulHeight = std::clamp(options.ulCursorHeightPercent, s_ulMinCursorHeightPercent, s_ulMaxCursorHeightPercent);
ulHeight = (_glyphCell.height<ULONG>() * ulHeight) / 100;
ulHeight = gsl::narrow<ULONG>((_glyphCell.cy * ulHeight) / 100);
rect.top = rect.bottom - ulHeight;
break;
}
case CursorType::VerticalBar: {
case CursorType::VerticalBar:
{
// It can't be wider than one cell or we'll have problems in invalidation, so restrict here.
// It's either the left + the proposed width from the ease of access setting, or
// it's the right edge of the block cursor as a maximum.
rect.right = std::min(rect.right, rect.left + options.cursorPixelWidth);
break;
}
case CursorType::Underscore: {
case CursorType::Underscore:
{
rect.top = rect.bottom - 1;
break;
}
case CursorType::EmptyBox: {
case CursorType::EmptyBox:
{
paintType = CursorPaintType::Outline;
break;
}
case CursorType::FullBox: {
case CursorType::FullBox:
{
break;
}
default:
@@ -1327,11 +1456,13 @@ enum class CursorPaintType
switch (paintType)
{
case CursorPaintType::Fill: {
case CursorPaintType::Fill:
{
_d2dRenderTarget->FillRectangle(rect, brush.Get());
break;
}
case CursorPaintType::Outline: {
case CursorPaintType::Outline:
{
// DrawRectangle in straddles physical pixels in an attempt to draw a line
// between them. To avoid this, bump the rectangle around by half the stroke width.
rect.top += 0.5f;
@@ -1445,7 +1576,6 @@ CATCH_RETURN()
// Return Value:
// - S_OK or relevant DirectX error
[[nodiscard]] HRESULT DxEngine::UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo) noexcept
try
{
RETURN_IF_FAILED(_GetProposedFont(pfiFontInfoDesired,
fiFontInfo,
@@ -1454,16 +1584,22 @@ try
_dwriteTextAnalyzer,
_dwriteFontFace));
_glyphCell = fiFontInfo.GetSize();
try
{
const auto size = fiFontInfo.GetSize();
_glyphCell.cx = size.X;
_glyphCell.cy = size.Y;
}
CATCH_RETURN();
return S_OK;
}
CATCH_RETURN();
[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) noexcept
{
const short widthInChars = gsl::narrow_cast<short>(viewInPixels.Width() / _glyphCell.width());
const short heightInChars = gsl::narrow_cast<short>(viewInPixels.Height() / _glyphCell.height());
const short widthInChars = gsl::narrow_cast<short>(viewInPixels.Width() / _glyphCell.cx);
const short heightInChars = gsl::narrow_cast<short>(viewInPixels.Height() / _glyphCell.cy);
return Viewport::FromDimensions(viewInPixels.Origin(), { widthInChars, heightInChars });
}
@@ -1552,7 +1688,29 @@ float DxEngine::GetScaling() const noexcept
// - Rectangle describing dirty area in characters.
[[nodiscard]] std::vector<til::rectangle> DxEngine::GetDirtyArea()
{
return _invalidMap.runs();
SMALL_RECT r;
r.Top = gsl::narrow<SHORT>(floor(_invalidRect.top / _glyphCell.cy));
r.Left = gsl::narrow<SHORT>(floor(_invalidRect.left / _glyphCell.cx));
r.Bottom = gsl::narrow<SHORT>(floor(_invalidRect.bottom / _glyphCell.cy));
r.Right = gsl::narrow<SHORT>(floor(_invalidRect.right / _glyphCell.cx));
// Exclusive to inclusive
r.Bottom--;
r.Right--;
return { r };
}
// Routine Description:
// - Gets COORD packed with shorts of each glyph (character) cell's
// height and width.
// Arguments:
// - <none>
// Return Value:
// - Nearest integer short x and y values for each cell.
[[nodiscard]] COORD DxEngine::_GetFontSize() const noexcept
{
return { gsl::narrow<SHORT>(_glyphCell.cx), gsl::narrow<SHORT>(_glyphCell.cy) };
}
// Routine Description:
@@ -1562,12 +1720,10 @@ float DxEngine::GetScaling() const noexcept
// Return Value:
// - S_OK
[[nodiscard]] HRESULT DxEngine::GetFontSize(_Out_ COORD* const pFontSize) noexcept
try
{
*pFontSize = _glyphCell;
*pFontSize = _GetFontSize();
return S_OK;
}
CATCH_RETURN();
// Routine Description:
// - Currently unused by this renderer.
@@ -1577,28 +1733,30 @@ CATCH_RETURN();
// Return Value:
// - S_OK or relevant DirectWrite error.
[[nodiscard]] HRESULT DxEngine::IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept
try
{
RETURN_HR_IF_NULL(E_INVALIDARG, pResult);
const Cluster cluster(glyph, 0); // columns don't matter, we're doing analysis not layout.
try
{
const Cluster cluster(glyph, 0); // columns don't matter, we're doing analysis not layout.
// Create the text layout
CustomTextLayout layout(_dwriteFactory.Get(),
_dwriteTextAnalyzer.Get(),
_dwriteTextFormat.Get(),
_dwriteFontFace.Get(),
{ &cluster, 1 },
_glyphCell.width());
// Create the text layout
CustomTextLayout layout(_dwriteFactory.Get(),
_dwriteTextAnalyzer.Get(),
_dwriteTextFormat.Get(),
_dwriteFontFace.Get(),
{ &cluster, 1 },
_glyphCell.cx);
UINT32 columns = 0;
RETURN_IF_FAILED(layout.GetColumns(&columns));
UINT32 columns = 0;
RETURN_IF_FAILED(layout.GetColumns(&columns));
*pResult = columns != 1;
*pResult = columns != 1;
}
CATCH_RETURN();
return S_OK;
}
CATCH_RETURN();
// Method Description:
// - Updates the window's title string.
@@ -1972,10 +2130,12 @@ CATCH_RETURN();
switch (_chainMode)
{
case SwapChainMode::ForHwnd: {
case SwapChainMode::ForHwnd:
{
return D2D1::ColorF(rgb);
}
case SwapChainMode::ForComposition: {
case SwapChainMode::ForComposition:
{
// Get the A value we've snuck into the highest byte
const BYTE a = ((color >> 24) & 0xFF);
const float aFloat = a / 255.0f;

View File

@@ -121,7 +121,7 @@ namespace Microsoft::Console::Render
SwapChainMode _chainMode;
HWND _hwndTarget;
til::size _sizeTarget;
SIZE _sizeTarget;
int _dpi;
float _scale;
@@ -130,8 +130,8 @@ namespace Microsoft::Console::Render
bool _isEnabled;
bool _isPainting;
til::size _displaySizePixels;
til::size _glyphCell;
SIZE _displaySizePixels;
SIZE _glyphCell;
D2D1_COLOR_F _defaultForegroundColor;
D2D1_COLOR_F _defaultBackgroundColor;
@@ -140,11 +140,19 @@ namespace Microsoft::Console::Render
D2D1_COLOR_F _backgroundColor;
D2D1_COLOR_F _selectionBackground;
til::bitmap _invalidMap;
til::point _invalidScroll;
[[nodiscard]] RECT _GetDisplayRect() const noexcept;
bool _isInvalidUsed;
RECT _invalidRect;
SIZE _invalidScroll;
void _InvalidOr(SMALL_RECT sr) noexcept;
void _InvalidOr(RECT rc) noexcept;
void _InvalidOffset(POINT pt);
bool _presentReady;
std::vector<RECT> _presentDirty;
RECT _presentDirty;
RECT _presentScroll;
POINT _presentOffset;
DXGI_PRESENT_PARAMETERS _presentParams;
@@ -236,7 +244,9 @@ namespace Microsoft::Console::Render
::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1>& textAnalyzer,
::Microsoft::WRL::ComPtr<IDWriteFontFace1>& fontFace) const noexcept;
[[nodiscard]] til::size _GetClientSize() const noexcept;
[[nodiscard]] COORD _GetFontSize() const noexcept;
[[nodiscard]] SIZE _GetClientSize() const noexcept;
[[nodiscard]] D2D1_COLOR_F _ColorFFromColorRef(const COLORREF color) noexcept;

View File

@@ -4,11 +4,9 @@
#pragma once
// This includes support libraries from the CRT, STL, WIL, and GSL
#define BLOCK_TIL // We want to include it later, after DX.
#include "LibraryIncludes.h"
#include <windows.h>
#include <winmeta.h>
#include "..\host\conddkrefs.h"
#include <condrv.h>
@@ -36,7 +34,4 @@
#include <dwrite_2.h>
#include <dwrite_3.h>
// Re-include TIL at the bottom to gain DX superpowers.
#include "til.h"
#pragma hdrstop

View File

@@ -343,20 +343,19 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT XtermEngine::ScrollFrame() noexcept
try
{
if (_scrollDelta.x() != 0)
if (_scrollDelta.X != 0)
{
// No easy way to shift left-right. Everything needs repainting.
return InvalidateAll();
}
if (_scrollDelta.y() == 0)
if (_scrollDelta.Y == 0)
{
// There's nothing to do here. Do nothing.
return S_OK;
}
const short dy = _scrollDelta.y<SHORT>();
const short dy = _scrollDelta.Y;
const short absDy = static_cast<short>(abs(dy));
HRESULT hr = S_OK;
@@ -392,7 +391,6 @@ try
return hr;
}
CATCH_RETURN();
// Routine Description:
// - Notifies us that the console is attempting to scroll the existing screen
@@ -404,23 +402,25 @@ CATCH_RETURN();
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for safemath failure
[[nodiscard]] HRESULT XtermEngine::InvalidateScroll(const COORD* const pcoordDelta) noexcept
try
{
const til::point delta{ *pcoordDelta };
const short dx = pcoordDelta->X;
const short dy = pcoordDelta->Y;
if (delta != til::point{ 0, 0 })
if (dx != 0 || dy != 0)
{
_trace.TraceInvalidateScroll(delta);
// Scroll the current offset and invalidate the revealed area
_invalidMap.translate(delta, true);
_invalidMap.translate(til::point(*pcoordDelta), true);
_scrollDelta += delta;
COORD invalidScrollNew;
RETURN_IF_FAILED(ShortAdd(_scrollDelta.X, dx, &invalidScrollNew.X));
RETURN_IF_FAILED(ShortAdd(_scrollDelta.Y, dy, &invalidScrollNew.Y));
// Store if safemath succeeded
_scrollDelta = invalidScrollNew;
}
return S_OK;
}
CATCH_RETURN();
// Routine Description:
// - Draws one line of the buffer to the screen. Writes the characters to the

View File

@@ -121,8 +121,6 @@ CATCH_RETURN();
_circled = true;
}
_trace.TraceTriggerCircling(*pForcePaint);
return S_OK;
}

View File

@@ -63,8 +63,8 @@ void VtEngine::_OrRect(_Inout_ SMALL_RECT* const pRectExisting, const SMALL_RECT
// - true iff only the next character is invalid
bool VtEngine::_WillWriteSingleChar() const
{
// If there is no scroll delta, return false.
if (til::point{ 0, 0 } != _scrollDelta)
// If there is scroll delta, return false.
if (til::point{ 0, 0 } != til::point{ _scrollDelta })
{
return false;
}

View File

@@ -27,7 +27,7 @@ using namespace Microsoft::Console::Types;
// If there's nothing to do, quick return
bool somethingToDo = _invalidMap.any() ||
_scrollDelta != til::point{ 0, 0 } ||
(_scrollDelta.X != 0 || _scrollDelta.Y != 0) ||
_cursorMoved ||
_titleChanged;
@@ -52,7 +52,7 @@ using namespace Microsoft::Console::Types;
_invalidMap.reset_all();
_scrollDelta = { 0, 0 };
_scrollDelta = { 0 };
_clearedAllThisFrame = false;
_cursorMoved = false;
_firstPaint = false;

View File

@@ -38,7 +38,7 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
_invalidMap(initialViewport.Dimensions()),
_lastRealCursor({ 0 }),
_lastText({ 0 }),
_scrollDelta({ 0, 0 }),
_scrollDelta({ 0 }),
_quickReturn(false),
_clearedAllThisFrame(false),
_cursorMoved(false),

View File

@@ -127,23 +127,6 @@ void RenderTracing::TraceTriggerCircling(const bool newFrame) const
#endif UNIT_TESTING
}
void RenderTracing::TraceInvalidateScroll(const til::point scroll) const
{
#ifndef UNIT_TESTING
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto scrollDeltaStr = scroll.to_string();
const auto scrollDelta = scrollDeltaStr.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceInvalidateScroll",
TraceLoggingWideString(scrollDelta),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
UNREFERENCED_PARAMETER(scroll);
#endif
}
void RenderTracing::TraceStartPaint(const bool quickReturn,
const til::bitmap invalidMap,
const til::rectangle lastViewport,

View File

@@ -34,7 +34,6 @@ namespace Microsoft::Console::VirtualTerminal
void TracePaintCursor(const til::point coordCursor) const;
void TraceInvalidateAll(const til::rectangle view) const;
void TraceTriggerCircling(const bool newFrame) const;
void TraceInvalidateScroll(const til::point scroll) const;
void TraceStartPaint(const bool quickReturn,
const til::bitmap invalidMap,
const til::rectangle lastViewport,

View File

@@ -14,7 +14,3 @@ TEST_CODE = 1
# -------------------------------------
C_DEFINES = $(C_DEFINES) -DINLINE_TEST_METHOD_MARKUP -DUNIT_TESTING
INCLUDES = \
$(INCLUDES); \
$(ONECORESDKTOOLS_INTERNAL_INC_PATH_L)\wextest\cue; \

View File

@@ -126,7 +126,7 @@ namespace Microsoft::Console::Render
COORD _lastRealCursor;
COORD _lastText;
til::point _scrollDelta;
COORD _scrollDelta;
bool _quickReturn;
bool _clearedAllThisFrame;

View File

@@ -47,6 +47,15 @@ void TerminalInput::EnableDefaultTracking(const bool enable) noexcept
_mouseInputState.trackingMode = enable ? TrackingMode::Default : TrackingMode::None;
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
_mouseInputState.lastButton = 0;
if (_mouseModeChangedCallback)
{
try
{
_mouseModeChangedCallback();
}
CATCH_LOG();
}
}
// Routine Description:
@@ -63,6 +72,15 @@ void TerminalInput::EnableButtonEventTracking(const bool enable) noexcept
_mouseInputState.trackingMode = enable ? TrackingMode::ButtonEvent : TrackingMode::None;
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
_mouseInputState.lastButton = 0;
if (_mouseModeChangedCallback)
{
try
{
_mouseModeChangedCallback();
}
CATCH_LOG();
}
}
// Routine Description:
@@ -79,6 +97,15 @@ void TerminalInput::EnableAnyEventTracking(const bool enable) noexcept
_mouseInputState.trackingMode = enable ? TrackingMode::AnyEvent : TrackingMode::None;
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
_mouseInputState.lastButton = 0;
if (_mouseModeChangedCallback)
{
try
{
_mouseModeChangedCallback();
}
CATCH_LOG();
}
}
// Routine Description:
@@ -113,3 +140,12 @@ void TerminalInput::UseMainScreenBuffer() noexcept
{
_mouseInputState.inAlternateBuffer = false;
}
// Routine Description:
// - Sets up the callback for mouse input mode changes
// Parameters:
// - mouseModeChangedCallback: the callback
void TerminalInput::SetMouseModeChangedCallback(std::function<void()> mouseModeChangedCallback) noexcept
{
_mouseModeChangedCallback = std::move(mouseModeChangedCallback);
}

View File

@@ -60,10 +60,13 @@ namespace Microsoft::Console::VirtualTerminal
void EnableAlternateScroll(const bool enable) noexcept;
void UseAlternateScreenBuffer() noexcept;
void UseMainScreenBuffer() noexcept;
void SetMouseModeChangedCallback(std::function<void()> mouseModeChangedCallback) noexcept;
#pragma endregion
private:
std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> _pfnWriteEvents;
std::function<void()> _mouseModeChangedCallback;
// storage location for the leading surrogate of a utf-16 surrogate pair
std::optional<wchar_t> _leadingSurrogate;

View File

@@ -50,7 +50,6 @@ SOURCES = \
INCLUDES = \
$(INCLUDES); \
$(ONECORESDKTOOLS_INTERNAL_INC_PATH_L)\wextest\cue; \
TARGETLIBS = \
$(TARGETLIBS) \

View File

@@ -1,127 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "til/math.h"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
class MathTests
{
TEST_CLASS(MathTests);
template<typename TG, typename TX = ptrdiff_t>
struct TestCase
{
TG given;
TX expected;
};
template<class TilMath, typename TG, typename TX, int N>
static void _RunCases(TilMath, const std::array<TestCase<TG, TX>, N>& cases)
{
for (const auto& tc : cases)
{
VERIFY_ARE_EQUAL(tc.expected, TilMath::template cast<decltype(tc.expected)>(tc.given));
}
}
TEST_METHOD(Truncating)
{
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
TestCase<long double, ptrdiff_t>{ 1., 1 },
{ 1.9, 1 },
{ -7.1, -7 },
{ -8.5, -8 },
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
{ INFINITY, PTRDIFF_MAX },
{ -INFINITY, PTRDIFF_MIN },
};
_RunCases(til::math::truncating, cases);
const auto fn = []() {
const auto v = til::math::details::truncating_t::cast<ptrdiff_t>(NAN);
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
TEST_METHOD(Ceiling)
{
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
TestCase<long double, ptrdiff_t>{ 1., 1 },
{ 1.9, 2 },
{ -7.1, -7 },
{ -8.5, -8 },
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
{ INFINITY, PTRDIFF_MAX },
{ -INFINITY, PTRDIFF_MIN },
};
_RunCases(til::math::ceiling, cases);
const auto fn = []() {
const auto v = til::math::details::ceiling_t::cast<ptrdiff_t>(NAN);
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
TEST_METHOD(Flooring)
{
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
TestCase<long double, ptrdiff_t>{ 1., 1 },
{ 1.9, 1 },
{ -7.1, -8 },
{ -8.5, -9 },
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
{ INFINITY, PTRDIFF_MAX },
{ -INFINITY, PTRDIFF_MIN },
};
_RunCases(til::math::flooring, cases);
const auto fn = []() {
const auto v = til::math::details::flooring_t::cast<ptrdiff_t>(NAN);
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
TEST_METHOD(Rounding)
{
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
TestCase<long double, ptrdiff_t>{ 1., 1 },
{ 1.9, 2 },
{ -7.1, -7 },
{ -8.5, -9 },
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
{ INFINITY, PTRDIFF_MAX },
{ -INFINITY, PTRDIFF_MIN },
};
_RunCases(til::math::rounding, cases);
const auto fn = []() {
const auto v = til::math::details::rounding_t::cast<ptrdiff_t>(NAN);
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
TEST_METHOD(NormalIntegers)
{
std::array<TestCase<ptrdiff_t, int>, 4> cases{
TestCase<ptrdiff_t, int>{ 1, 1 },
{ -1, -1 },
{ PTRDIFF_MAX, INT_MAX },
{ PTRDIFF_MIN, INT_MIN },
};
_RunCases(til::math::rounding, cases);
}
};

View File

@@ -207,50 +207,6 @@ class PointTests
}
}
TEST_METHOD(AdditionInplace)
{
Log::Comment(L"0.) Addition of two things that should be in bounds.");
{
const til::point pt{ 5, 10 };
const til::point pt2{ 23, 47 };
const til::point expected{ pt.x() + pt2.x(), pt.y() + pt2.y() };
auto actual = pt;
actual += pt2;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"1.) Addition results in value that is too large (x).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
const til::point pt2{ 1, 1 };
auto fn = [&]() {
auto actual = pt;
actual += pt2;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
Log::Comment(L"2.) Addition results in value that is too large (y).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::point pt{ static_cast<ptrdiff_t>(0), bigSize };
const til::point pt2{ 1, 1 };
auto fn = [&]() {
auto actual = pt;
actual += pt2;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(Subtraction)
{
Log::Comment(L"0.) Subtraction of two things that should be in bounds.");
@@ -290,50 +246,6 @@ class PointTests
}
}
TEST_METHOD(SubtractionInplace)
{
Log::Comment(L"0.) Subtraction of two things that should be in bounds.");
{
const til::point pt{ 5, 10 };
const til::point pt2{ 23, 47 };
const til::point expected{ pt.x() - pt2.x(), pt.y() - pt2.y() };
auto actual = pt;
actual -= pt2;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"1.) Subtraction results in value that is too small (x).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
const til::point pt2{ -2, -2 };
auto fn = [&]() {
auto actual = pt2;
actual -= pt;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
Log::Comment(L"2.) Subtraction results in value that is too small (y).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::point pt{ static_cast<ptrdiff_t>(0), bigSize };
const til::point pt2{ -2, -2 };
auto fn = [&]() {
auto actual = pt2;
actual -= pt;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(Multiplication)
{
Log::Comment(L"0.) Multiplication of two things that should be in bounds.");
@@ -373,50 +285,6 @@ class PointTests
}
}
TEST_METHOD(MultiplicationInplace)
{
Log::Comment(L"0.) Multiplication of two things that should be in bounds.");
{
const til::point pt{ 5, 10 };
const til::point pt2{ 23, 47 };
const til::point expected{ pt.x() * pt2.x(), pt.y() * pt2.y() };
auto actual = pt;
actual *= pt2;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"1.) Multiplication results in value that is too large (x).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
const til::point pt2{ 10, 10 };
auto fn = [&]() {
auto actual = pt;
actual *= pt2;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
Log::Comment(L"2.) Multiplication results in value that is too large (y).");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::point pt{ static_cast<ptrdiff_t>(0), bigSize };
const til::point pt2{ 10, 10 };
auto fn = [&]() {
auto actual = pt;
actual *= pt2;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(Division)
{
Log::Comment(L"0.) Division of two things that should be in bounds.");
@@ -443,35 +311,6 @@ class PointTests
}
}
TEST_METHOD(DivisionInplace)
{
Log::Comment(L"0.) Division of two things that should be in bounds.");
{
const til::point pt{ 555, 510 };
const til::point pt2{ 23, 47 };
const til::point expected{ pt.x() / pt2.x(), pt.y() / pt2.y() };
auto actual = pt;
actual /= pt2;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"1.) Division by zero");
{
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
const til::point pt2{ 1, 1 };
auto fn = [&]() {
auto actual = pt2;
actual /= pt;
};
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
}
}
TEST_METHOD(X)
{
const til::point pt{ 5, 10 };
@@ -604,101 +443,4 @@ class PointTests
// All ptrdiff_ts fit into a float, so there's no exception tests.
}
template<typename T>
struct PointTypeWith_xy
{
T x, y;
};
template<typename T>
struct PointTypeWith_XY
{
T X, Y;
};
TEST_METHOD(CastFromFloatWithMathTypes)
{
PointTypeWith_xy<float> xyFloatIntegral{ 1.f, 2.f };
PointTypeWith_xy<float> xyFloat{ 1.6f, 2.4f };
PointTypeWith_XY<double> XYDoubleIntegral{ 3., 4. };
PointTypeWith_XY<double> XYDouble{ 3.6, 4.4 };
Log::Comment(L"0.) Ceiling");
{
{
til::point converted{ til::math::ceiling, xyFloatIntegral };
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
}
{
til::point converted{ til::math::ceiling, xyFloat };
VERIFY_ARE_EQUAL((til::point{ 2, 3 }), converted);
}
{
til::point converted{ til::math::ceiling, XYDoubleIntegral };
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
}
{
til::point converted{ til::math::ceiling, XYDouble };
VERIFY_ARE_EQUAL((til::point{ 4, 5 }), converted);
}
}
Log::Comment(L"1.) Flooring");
{
{
til::point converted{ til::math::flooring, xyFloatIntegral };
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
}
{
til::point converted{ til::math::flooring, xyFloat };
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
}
{
til::point converted{ til::math::flooring, XYDoubleIntegral };
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
}
{
til::point converted{ til::math::flooring, XYDouble };
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
}
}
Log::Comment(L"2.) Rounding");
{
{
til::point converted{ til::math::rounding, xyFloatIntegral };
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
}
{
til::point converted{ til::math::rounding, xyFloat };
VERIFY_ARE_EQUAL((til::point{ 2, 2 }), converted);
}
{
til::point converted{ til::math::rounding, XYDoubleIntegral };
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
}
{
til::point converted{ til::math::rounding, XYDouble };
VERIFY_ARE_EQUAL((til::point{ 4, 4 }), converted);
}
}
Log::Comment(L"3.) Truncating");
{
{
til::point converted{ til::math::truncating, xyFloatIntegral };
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
}
{
til::point converted{ til::math::truncating, xyFloat };
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
}
{
til::point converted{ til::math::truncating, XYDoubleIntegral };
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
}
{
til::point converted{ til::math::truncating, XYDouble };
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
}
}
}
};

View File

@@ -515,140 +515,4 @@ class SizeTests
// All ptrdiff_ts fit into a float, so there's no exception tests.
}
template<typename T>
struct SizeTypeWith_XY
{
T X, Y;
};
template<typename T>
struct SizeTypeWith_cxcy
{
T cx, cy;
};
template<typename T>
struct SizeTypeWith_WidthHeight
{
T Width, Height;
};
TEST_METHOD(CastFromFloatWithMathTypes)
{
SizeTypeWith_XY<float> XYFloatIntegral{ 1.f, 2.f };
SizeTypeWith_XY<float> XYFloat{ 1.6f, 2.4f };
SizeTypeWith_cxcy<double> cxcyDoubleIntegral{ 3., 4. };
SizeTypeWith_cxcy<double> cxcyDouble{ 3.6, 4.4 };
SizeTypeWith_WidthHeight<double> WHDoubleIntegral{ 5., 6. };
SizeTypeWith_WidthHeight<double> WHDouble{ 5.6, 6.4 };
Log::Comment(L"0.) Ceiling");
{
{
til::size converted{ til::math::ceiling, XYFloatIntegral };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::ceiling, XYFloat };
VERIFY_ARE_EQUAL((til::size{ 2, 3 }), converted);
}
{
til::size converted{ til::math::ceiling, cxcyDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::ceiling, cxcyDouble };
VERIFY_ARE_EQUAL((til::size{ 4, 5 }), converted);
}
{
til::size converted{ til::math::ceiling, WHDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
{
til::size converted{ til::math::ceiling, WHDouble };
VERIFY_ARE_EQUAL((til::size{ 6, 7 }), converted);
}
}
Log::Comment(L"1.) Flooring");
{
{
til::size converted{ til::math::flooring, XYFloatIntegral };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::flooring, XYFloat };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::flooring, cxcyDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::flooring, cxcyDouble };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::flooring, WHDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
{
til::size converted{ til::math::flooring, WHDouble };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
}
Log::Comment(L"2.) Rounding");
{
{
til::size converted{ til::math::rounding, XYFloatIntegral };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::rounding, XYFloat };
VERIFY_ARE_EQUAL((til::size{ 2, 2 }), converted);
}
{
til::size converted{ til::math::rounding, cxcyDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::rounding, cxcyDouble };
VERIFY_ARE_EQUAL((til::size{ 4, 4 }), converted);
}
{
til::size converted{ til::math::rounding, WHDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
{
til::size converted{ til::math::rounding, WHDouble };
VERIFY_ARE_EQUAL((til::size{ 6, 6 }), converted);
}
}
Log::Comment(L"3.) Truncating");
{
{
til::size converted{ til::math::truncating, XYFloatIntegral };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::truncating, XYFloat };
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
}
{
til::size converted{ til::math::truncating, cxcyDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::truncating, cxcyDouble };
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
}
{
til::size converted{ til::math::truncating, WHDoubleIntegral };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
{
til::size converted{ til::math::truncating, WHDouble };
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
}
}
}
};

View File

@@ -18,7 +18,6 @@ SOURCES = \
ColorTests.cpp \
OperatorTests.cpp \
PointTests.cpp \
MathTests.cpp \
RectangleTests.cpp \
SizeTests.cpp \
SomeTests.cpp \

View File

@@ -13,7 +13,6 @@
<ClCompile Include="BitmapTests.cpp" />
<ClCompile Include="OperatorTests.cpp" />
<ClCompile Include="PointTests.cpp" />
<ClCompile Include="MathTests.cpp" />
<ClCompile Include="RectangleTests.cpp" />
<ClCompile Include="SizeTests.cpp" />
<ClCompile Include="ColorTests.cpp" />

View File

@@ -23,7 +23,6 @@ bool gVtInput = false;
bool gVtOutput = true;
bool gWindowInput = false;
bool gUseAltBuffer = false;
bool gUseAscii = false;
bool gExitRequested = false;
@@ -53,7 +52,7 @@ void useMainBuffer()
csi("?1049l");
}
void toPrintableBufferA(char c, char* printBuffer, int* printCch)
void toPrintableBuffer(char c, char* printBuffer, int* printCch)
{
if (c == '\x1b')
{
@@ -112,72 +111,13 @@ void toPrintableBufferA(char c, char* printBuffer, int* printCch)
*printCch = 2;
}
}
void toPrintableBufferW(wchar_t c, wchar_t* printBuffer, int* printCch)
{
if (c == L'\x1b')
{
printBuffer[0] = L'^';
printBuffer[1] = L'[';
printBuffer[2] = L'\0';
*printCch = 2;
}
else if (c == '\x03')
{
printBuffer[0] = L'^';
printBuffer[1] = L'C';
printBuffer[2] = L'\0';
*printCch = 2;
}
else if (c == '\x0')
{
printBuffer[0] = L'\\';
printBuffer[1] = L'0';
printBuffer[2] = L'\0';
*printCch = 2;
}
else if (c == '\r')
{
printBuffer[0] = L'\\';
printBuffer[1] = L'r';
printBuffer[2] = L'\0';
*printCch = 2;
}
else if (c == '\n')
{
printBuffer[0] = L'\\';
printBuffer[1] = L'n';
printBuffer[2] = L'\0';
*printCch = 2;
}
else if (c == '\t')
{
printBuffer[0] = L'\\';
printBuffer[1] = L't';
printBuffer[2] = L'\0';
*printCch = 2;
}
else if (c == '\b')
{
printBuffer[0] = L'\\';
printBuffer[1] = L'b';
printBuffer[2] = L'\0';
*printCch = 2;
}
else
{
printBuffer[0] = (wchar_t)c;
printBuffer[1] = L' ';
printBuffer[2] = L'\0';
*printCch = 2;
}
}
void handleKeyEventA(KEY_EVENT_RECORD keyEvent)
void handleKeyEvent(KEY_EVENT_RECORD keyEvent)
{
char printBuffer[3];
int printCch = 0;
const char c = keyEvent.uChar.AsciiChar;
toPrintableBufferA(c, printBuffer, &printCch);
toPrintableBuffer(c, printBuffer, &printCch);
if (!keyEvent.bKeyDown)
{
@@ -197,45 +137,13 @@ void handleKeyEventA(KEY_EVENT_RECORD keyEvent)
// restore colors
csi("0m");
// Die on Ctrl+D
// Die on Ctrl+C
if (keyEvent.uChar.AsciiChar == CTRL_D)
{
gExitRequested = true;
}
}
void handleKeyEventW(KEY_EVENT_RECORD keyEvent)
{
wchar_t printBuffer[3];
int printCch = 0;
const wchar_t c = keyEvent.uChar.UnicodeChar;
toPrintableBufferW(c, printBuffer, &printCch);
if (!keyEvent.bKeyDown)
{
// Print in grey
csi("38;5;242m");
}
wprintf(L"Down: %d Repeat: %d KeyCode: 0x%x ScanCode: 0x%x Char: %s (0x%x) KeyState: 0x%x\r\n",
keyEvent.bKeyDown,
keyEvent.wRepeatCount,
keyEvent.wVirtualKeyCode,
keyEvent.wVirtualScanCode,
printBuffer,
keyEvent.uChar.UnicodeChar,
keyEvent.dwControlKeyState);
// restore colors
csi("0m");
// Die on Ctrl+D
if (c == CTRL_D)
{
gExitRequested = true;
}
}
void handleWindowEvent(WINDOW_BUFFER_SIZE_RECORD windowEvent)
{
SHORT bufferWidth = windowEvent.dwSize.X;
@@ -282,7 +190,6 @@ void usage()
wprintf(L"\t-i: enable reading VT input mode.\n");
wprintf(L"\t-o: disable VT output.\n");
wprintf(L"\t-w: enable reading window events.\n");
wprintf(L"\t-a: Use ReadConsoleInputA instead.\n");
wprintf(L"\t--alt: run in the alt buffer. Cannot be combined with `-o`\n");
wprintf(L"\t-?: print this help message\n");
}
@@ -294,7 +201,6 @@ int __cdecl wmain(int argc, wchar_t* argv[])
gWindowInput = false;
gUseAltBuffer = false;
gExitRequested = false;
gUseAscii = false;
for (int i = 1; i < argc; i++)
{
@@ -320,11 +226,6 @@ int __cdecl wmain(int argc, wchar_t* argv[])
gVtOutput = false;
wprintf(L"Disabling VT Output\n");
}
else if (arg.compare(L"-a") == 0)
{
gUseAscii = true;
wprintf(L"Using ReadConsoleInputA\n");
}
else if (arg.compare(L"-?") == 0)
{
usage();
@@ -387,28 +288,13 @@ int __cdecl wmain(int argc, wchar_t* argv[])
{
INPUT_RECORD rc;
DWORD dwRead = 0;
if (gUseAscii)
{
ReadConsoleInputA(g_hIn, &rc, 1, &dwRead);
}
else
{
ReadConsoleInputW(g_hIn, &rc, 1, &dwRead);
}
ReadConsoleInputA(g_hIn, &rc, 1, &dwRead);
switch (rc.EventType)
{
case KEY_EVENT:
{
if (gUseAscii)
{
handleKeyEventA(rc.Event.KeyEvent);
}
else
{
handleKeyEventW(rc.Event.KeyEvent);
}
handleKeyEvent(rc.Event.KeyEvent);
break;
}
case WINDOW_BUFFER_SIZE_EVENT:

View File

@@ -13,14 +13,14 @@ namespace VTApp
{
class Program
{
static string CSI = ((char)0x1b) + "[";
static string CSI = ((char)0x1b)+"[";
static void Main(string[] args)
{
Console.WindowHeight = 25;
Console.BufferHeight = 9000;
Console.WindowWidth = 80;
Console.BufferWidth = 80;
Console.WriteLine("VT Tester");
while (true)
@@ -271,11 +271,11 @@ namespace VTApp
Console.Write("49m");
break;
case '<':
Console.Write('\xD'); // carriage return \r
break;
Console.Write('\xD'); // carriage return \r
break;
case '>':
Console.Write('\xA'); // line feed/new line \n
break;
Console.Write('\xA'); // line feed/new line \n
break;
case '`':
Console.Write("z");
break;
@@ -385,7 +385,7 @@ namespace VTApp
for (int j = 0; j < 80; j++)
{
if (j == 0)
Console.Write(i % 10);
Console.Write(i%10);
else
Console.Write("Z");
}