Split `TermControl` into a Core, Interactivity, and Control layer (#9820)

## Summary of the Pull Request

Brace yourselves, it's finally here. This PR does the dirty work of splitting the monolithic `TermControl` into three components. These components are: 

* `ControlCore`: This encapsulates the `Terminal` instance, the `DxEngine` and `Renderer`, and the `Connection`. This is intended to everything that someone might need to stand up a terminal instance in a control, but without any regard for how the UX works.
* `ControlInteractivity`: This is a wrapper for the `ControlCore`, which holds the logic for things like double-click, right click copy/paste, selection, etc. This is intended to be a UI framework-independent abstraction. The methods this layer exposes can be called the same from both the WinUI TermControl and the WPF control.
* `TermControl`: This is the UWP control. It's got a Core and Interactivity inside it, which it uses for the actual logic of the terminal itself. TermControl's main responsibility is now 

By splitting into smaller pieces, it will enable us to
* write unit tests for the `Core` and `Interactivity` bits, which we desparately need
* Combine `ControlCore` and `ControlInteractivity` in an out-of-proc core process in the future, to enable tab tearout.

However, we're not doing that work quite yet. There's still lots of work to be done to enable that, thought this is likely the biggest portion.

Ideally, this would just be methods moved wholesale from one file to another. Unfortunately, there are a bunch of cases where that didn't work as well as expected. Especially when trying to better enforce the boundary between the classes. 

We've got a couple tests here that I've added. These are partially examples, and partially things I ran into while implementing this. A bunch of things from #7001 can go in now that we have this.

This PR is gonna be a huge pain to review - 38 files with 3,730 additions and 1,661 deletions is nothing to scoff at. It will also conflict 100% with anything that's targeting `TermControl`. I'm hoping we can review this over the course of the next week and just be done with it, and leave plenty of runway for 1.9 bugs in post.

## References

* In pursuit of #1256
* Proc Model: #5000
* https://github.com/microsoft/terminal/projects/5

## PR Checklist
* [x] Closes #6842
* [x] Closes https://github.com/microsoft/terminal/projects/5#card-50760249
* [x] Closes https://github.com/microsoft/terminal/projects/5#card-50760258
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

* I don't love the names `ControlCore` and `ControlInteractivity`. Open to other names.
* I added a `ICoreState` interface for "properties that come from the `ControlCore`, but consumers of the `TermControl` need to know". In the future, these will all need to be handled specially, because they might involve an RPC call to retrieve the info from the core (or cache it) in the window process.
* I've added more `EventArgs` to make more events proper `TypedEvent`s.
* I've changed how the TerminalApp layer requests updated TaskbarProgress state. It doesn't need to pump TermControl to raise a new event anymore.
* ~~Something that snuck into this branch in the very long history is the switch to `DCompositionCreateSurfaceHandle` for the `DxEngine`. @miniksa wrote this originally in 30b8335, I'm just finally committing it here. We'll need that in the future for the out-of-proc stuff.~~
  * I reverted this in c113b65d9. We can revert _that_ commit when we want to come back to it.
* I've changed the acrylic handler a decent amount. But added tests!
* All the `ThrottledFunc` things are left in `TermControl`. Some might be able to move down into core/interactivity, but once we figure out how to use a different kind of Dispatcher (because a UI thread won't necessarily exist for those components).
* I've undoubtably messed up the merging of the locking around the appearance config stuff recently

## Validation Steps Performed

I've got a rolling list in https://github.com/microsoft/terminal/issues/6842#issuecomment-810990460 that I'm updating as I go.
This commit is contained in:
Mike Griese 2021-04-27 10:50:45 -05:00 committed by GitHub
parent 8d50609ba1
commit 8910a16fd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 3740 additions and 1711 deletions

View File

@ -12,6 +12,7 @@ CXICON
CYICON
D2DERR_SHADER_COMPILE_FAILED
dataobject
dcomp
DERR
dlldata
DONTADDTORECENT
@ -127,9 +128,12 @@ wsregex
wwinmain
XDocument
XElement
xhash
xlocmes
xlocmon
xlocnum
xloctime
XParse
xstring
xtree
xutility

15
.vscode/settings.json vendored
View File

@ -21,7 +21,20 @@
"xloctime": "cpp",
"multi_span": "cpp",
"pointers": "cpp",
"vector": "cpp"
"vector": "cpp",
"bitset": "cpp",
"deque": "cpp",
"initializer_list": "cpp",
"list": "cpp",
"queue": "cpp",
"random": "cpp",
"regex": "cpp",
"stack": "cpp",
"xhash": "cpp",
"xtree": "cpp",
"xutility": "cpp",
"span": "cpp",
"string_span": "cpp"
},
"files.exclude": {
"**/bin/**": true,

7
.vscode/tasks.json vendored
View File

@ -9,7 +9,7 @@
"-Command",
"Import-Module ${workspaceFolder}\\tools\\OpenConsole.psm1;",
"Set-MsBuildDevEnvironment;",
"$project = switch(\"${input:buildProjectChoice}\"){OpenConsole{\"Conhost\\Host_EXE\"} Terminal{\"Terminal\\CascadiaPackage\"}};",
"$project = switch(\"${input:buildProjectChoice}\"){OpenConsole{\"Conhost\\Host_EXE\"} Terminal{\"Terminal\\CascadiaPackage\"} TermControl{\"Terminal\\TerminalControl\"}};",
"$target = switch(\"${input:buildModeChoice}\"){Build{\"\"} Rebuild{\":Rebuild\"} Clean{\":Clean\"}};",
"$target = $project + $target;",
"msbuild",
@ -111,10 +111,11 @@
"description": "OpenConsole or Terminal?",
"options":[
"OpenConsole",
"Terminal"
"Terminal",
"TermControl"
],
"default": "Terminal"
}
]
}
}

View File

@ -175,7 +175,9 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Control.
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}
{CA5CAD1A-ABCD-429C-B551-8562EC954746} = {CA5CAD1A-ABCD-429C-B551-8562EC954746}
{1CF55140-EF6A-4736-A403-957E4F7430BB} = {1CF55140-EF6A-4736-A403-957E4F7430BB}
{48D21369-3D7B-4431-9967-24E81292CF62} = {48D21369-3D7B-4431-9967-24E81292CF62}
{48D21369-3D7B-4431-9967-24E81292CF63} = {48D21369-3D7B-4431-9967-24E81292CF63}
{AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} = {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Control", "src\cascadia\TerminalControl\dll\TerminalControl.vcxproj", "{CA5CAD1A-F542-4635-A069-7CAEFB930070}"

View File

@ -22,7 +22,7 @@ steps:
configPath: NuGet.config
restoreSolution: OpenConsole.sln
restoreDirectory: '$(Build.SourcesDirectory)\packages'
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
displayName: Restore NuGet packages for extraneous build actions
inputs:
@ -96,7 +96,7 @@ steps:
displayName: 'Upload converted test logs'
inputs:
testResultsFormat: 'xUnit' # Options: JUnit, NUnit, VSTest, xUnit, cTest
testResultsFiles: '**/onBuildMachineResults.xml'
testResultsFiles: '**/onBuildMachineResults.xml'
#searchFolder: '$(System.DefaultWorkingDirectory)' # Optional
#mergeTestResults: false # Optional
#failTaskOnFailedTests: false # Optional
@ -147,4 +147,4 @@ steps:
displayName: 'Publish All Build Artifacts'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
ArtifactName: 'drop'

View File

@ -19,8 +19,7 @@
// - Mike Griese (zadjii-msft) 16-May-2019
#pragma once
#include <winrt/Microsoft.Terminal.Control.h>
#include <winrt/TerminalApp.h>
#include "../../cascadia/inc/cppwinrt_utils.h"
enum class Borders : int

View File

@ -18,8 +18,6 @@ Author(s):
#pragma once
#include "TabBase.h"
#include "SettingsTab.g.h"
#include <winrt/TerminalApp.h>
#include <winrt/Microsoft.Terminal.Settings.Editor.h>
#include "../../cascadia/inc/cppwinrt_utils.h"
namespace winrt::TerminalApp::implementation

View File

@ -153,6 +153,11 @@ namespace winrt::TerminalApp::implementation
{
// Possibly update the icon of the tab.
page->_UpdateTabIcon(*tab);
// Update the taskbar progress as well. We'll raise our own
// SetTaskbarProgress event here, to get tell the hosting
// application to re-query this value from us.
page->_SetTaskbarProgressHandlers(*page, nullptr);
}
});

View File

@ -996,9 +996,6 @@ namespace winrt::TerminalApp::implementation
term.OpenHyperlink({ this, &TerminalPage::_OpenHyperlinkHandler });
// Add an event handler for when the terminal wants to set a progress indicator on the taskbar
term.SetTaskbarProgress({ this, &TerminalPage::_SetTaskbarProgressHandler });
term.HidePointerCursor({ get_weak(), &TerminalPage::_HidePointerCursorHandler });
term.RestorePointerCursor({ get_weak(), &TerminalPage::_RestorePointerCursorHandler });
@ -1053,6 +1050,11 @@ namespace winrt::TerminalApp::implementation
}
});
// Add an event handler for when the terminal or tab wants to set a
// progress indicator on the taskbar
hostingTab.TaskbarProgressChanged({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler });
term.SetTaskbarProgress({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler });
// TODO GH#3327: Once we support colorizing the NewTab button based on
// the color of the tab, we'll want to make sure to call
// _ClearNewTabButtonColor here, to reset it to the default (for the
@ -1154,7 +1156,7 @@ namespace winrt::TerminalApp::implementation
{
// The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page
realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ?
terminalTab->GetActiveTerminalControl().GetViewHeight() :
terminalTab->GetActiveTerminalControl().ViewHeight() :
_systemRowsToScroll;
}
else
@ -1295,7 +1297,7 @@ namespace winrt::TerminalApp::implementation
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
const auto control = _GetActiveControl();
const auto termHeight = control.GetViewHeight();
const auto termHeight = control.ViewHeight();
auto scrollDelta = _ComputeScrollDelta(scrollDirection, termHeight);
terminalTab->Scroll(scrollDelta);
}
@ -1647,29 +1649,37 @@ namespace winrt::TerminalApp::implementation
return false;
}
void TerminalPage::_ControlNoticeRaisedHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::NoticeEventArgs eventArgs)
// Important! Don't take this eventArgs by reference, we need to extend the
// lifetime of it to the other side of the co_await!
winrt::fire_and_forget TerminalPage::_ControlNoticeRaisedHandler(const IInspectable /*sender*/,
const Microsoft::Terminal::Control::NoticeEventArgs eventArgs)
{
winrt::hstring message = eventArgs.Message();
winrt::hstring title;
switch (eventArgs.Level())
auto weakThis = get_weak();
co_await winrt::resume_foreground(Dispatcher());
if (auto page = weakThis.get())
{
case NoticeLevel::Debug:
title = RS_(L"NoticeDebug"); //\xebe8
break;
case NoticeLevel::Info:
title = RS_(L"NoticeInfo"); // \xe946
break;
case NoticeLevel::Warning:
title = RS_(L"NoticeWarning"); //\xe7ba
break;
case NoticeLevel::Error:
title = RS_(L"NoticeError"); //\xe783
break;
}
winrt::hstring message = eventArgs.Message();
_ShowControlNoticeDialog(title, message);
winrt::hstring title;
switch (eventArgs.Level())
{
case NoticeLevel::Debug:
title = RS_(L"NoticeDebug"); //\xebe8
break;
case NoticeLevel::Info:
title = RS_(L"NoticeInfo"); // \xe946
break;
case NoticeLevel::Warning:
title = RS_(L"NoticeWarning"); //\xe7ba
break;
case NoticeLevel::Error:
title = RS_(L"NoticeError"); //\xe783
break;
}
page->_ShowControlNoticeDialog(title, message);
}
}
void TerminalPage::_ShowControlNoticeDialog(const winrt::hstring& title, const winrt::hstring& message)
@ -1707,8 +1717,9 @@ namespace winrt::TerminalApp::implementation
// Arguments:
// - sender (not used)
// - eventArgs: the arguments specifying how to set the progress indicator
void TerminalPage::_SetTaskbarProgressHandler(const IInspectable /*sender*/, const IInspectable /*eventArgs*/)
winrt::fire_and_forget TerminalPage::_SetTaskbarProgressHandler(const IInspectable /*sender*/, const IInspectable /*eventArgs*/)
{
co_await resume_foreground(Dispatcher());
_SetTaskbarProgressHandlers(*this, nullptr);
}

View File

@ -266,11 +266,11 @@ namespace winrt::TerminalApp::implementation
void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri);
bool _CopyText(const bool singleLine, const Windows::Foundation::IReference<Microsoft::Terminal::Control::CopyFormat>& formats);
void _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs);
winrt::fire_and_forget _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs);
void _PasteText();
void _ControlNoticeRaisedHandler(const IInspectable sender, const Microsoft::Terminal::Control::NoticeEventArgs eventArgs);
winrt::fire_and_forget _ControlNoticeRaisedHandler(const IInspectable sender, const Microsoft::Terminal::Control::NoticeEventArgs eventArgs);
void _ShowControlNoticeDialog(const winrt::hstring& title, const winrt::hstring& message);
fire_and_forget _LaunchSettings(const Microsoft::Terminal::Settings::Model::SettingsTarget target);

View File

@ -167,7 +167,11 @@ namespace winrt::TerminalApp::implementation
if (lastFocusedControl)
{
lastFocusedControl.Focus(_focusState);
lastFocusedControl.TaskbarProgressChanged();
// Update our own progress state, and fire an event signaling
// that our taskbar progress changed.
_UpdateProgressState();
_TaskbarProgressChangedHandlers(lastFocusedControl, nullptr);
}
// When we gain focus, remove the bell indicator if it is active
if (_tabStatus.BellIndicator())
@ -378,7 +382,7 @@ namespace winrt::TerminalApp::implementation
co_await winrt::resume_foreground(control.Dispatcher());
const auto currentOffset = control.GetScrollOffset();
const auto currentOffset = control.ScrollOffset();
control.ScrollViewport(::base::ClampAdd(currentOffset, delta));
}
@ -546,6 +550,7 @@ namespace winrt::TerminalApp::implementation
void TerminalTab::_AttachEventHandlersToControl(const TermControl& control)
{
auto weakThis{ get_weak() };
auto dispatcher = TabViewItem().Dispatcher();
control.TitleChanged([weakThis](auto&&, auto&&) {
// Check if Tab's lifetime has expired
@ -581,37 +586,12 @@ namespace winrt::TerminalApp::implementation
}
});
control.SetTaskbarProgress([weakThis](auto&&, auto&&) {
control.SetTaskbarProgress([dispatcher, weakThis](auto&&, auto &&) -> winrt::fire_and_forget {
co_await winrt::resume_foreground(dispatcher);
// Check if Tab's lifetime has expired
if (auto tab{ weakThis.get() })
{
// The progress of the control changed, but not necessarily the progress of the tab.
// Set the tab's progress ring to the active pane's progress
if (tab->GetActiveTerminalControl().TaskbarState() > 0)
{
if (tab->GetActiveTerminalControl().TaskbarState() == 3)
{
// 3 is the indeterminate state, set the progress ring as such
tab->_tabStatus.IsProgressRingIndeterminate(true);
}
else
{
// any non-indeterminate state has a value, set the progress ring as such
tab->_tabStatus.IsProgressRingIndeterminate(false);
const auto progressValue = gsl::narrow<uint32_t>(tab->GetActiveTerminalControl().TaskbarProgress());
tab->_tabStatus.ProgressValue(progressValue);
}
// Hide the tab icon (the progress ring is placed over it)
tab->HideIcon(true);
tab->_tabStatus.IsProgressRingActive(true);
}
else
{
// Show the tab icon
tab->HideIcon(false);
tab->_tabStatus.IsProgressRingActive(false);
}
tab->_UpdateProgressState();
}
});
@ -636,6 +616,54 @@ namespace winrt::TerminalApp::implementation
});
}
// Method Description:
// - This should be called on the UI thread. If you don't, then it might
// silently do nothing.
// - Update our TabStatus to reflect the progress state of the currently
// active pane.
// - This is called every time _any_ control's progress state changes,
// regardless of if that control is the active one or not. This is simpler
// then re-attaching this handler to the active control each time it
// changes.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TerminalTab::_UpdateProgressState()
{
if (const auto& activeControl{ GetActiveTerminalControl() })
{
const auto taskbarState = activeControl.TaskbarState();
// The progress of the control changed, but not necessarily the progress of the tab.
// Set the tab's progress ring to the active pane's progress
if (taskbarState > 0)
{
if (taskbarState == 3)
{
// 3 is the indeterminate state, set the progress ring as such
_tabStatus.IsProgressRingIndeterminate(true);
}
else
{
// any non-indeterminate state has a value, set the progress ring as such
_tabStatus.IsProgressRingIndeterminate(false);
const auto progressValue = gsl::narrow<uint32_t>(activeControl.TaskbarProgress());
_tabStatus.ProgressValue(progressValue);
}
// Hide the tab icon (the progress ring is placed over it)
HideIcon(true);
_tabStatus.IsProgressRingActive(true);
}
else
{
// Show the tab icon
HideIcon(false);
_tabStatus.IsProgressRingActive(false);
}
}
}
// 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
@ -653,6 +681,7 @@ namespace winrt::TerminalApp::implementation
// Update our own title text to match the newly-active pane.
UpdateTitle();
_UpdateProgressState();
// We need to move the pane to the top of our mru list
// If its already somewhere in the list, remove it first
@ -671,11 +700,6 @@ namespace winrt::TerminalApp::implementation
_RecalculateAndApplyReadOnly();
if (const auto control{ pane->GetTerminalControl() })
{
control.TaskbarProgressChanged();
}
// Raise our own ActivePaneChanged event.
_ActivePaneChangedHandlers();
}

View File

@ -92,6 +92,7 @@ namespace winrt::TerminalApp::implementation
DECLARE_EVENT(TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>);
DECLARE_EVENT(DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>);
FORWARDED_TYPED_EVENT(TabRenamerDeactivated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, (&_headerControl), RenameEnded);
TYPED_EVENT(TaskbarProgressChanged, IInspectable, IInspectable);
private:
std::shared_ptr<Pane> _rootPane{ nullptr };
@ -143,6 +144,8 @@ namespace winrt::TerminalApp::implementation
void _RecalculateAndApplyReadOnly();
void _UpdateProgressState();
void _DuplicateTab();
friend class ::TerminalAppLocalTests::TabTests;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,240 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// Module Name:
// - ControlCore.h
//
// Abstract:
// - This encapsulates a `Terminal` instance, a `DxEngine` and `Renderer`, and
// an `ITerminalConnection`. This is intended to be everything that someone
// might need to stand up a terminal instance in a control, but without any
// regard for how the UX works.
//
// Author:
// - Mike Griese (zadjii-msft) 01-Apr-2021
#pragma once
#include "EventArgs.h"
#include "ControlCore.g.h"
#include "../../renderer/base/Renderer.hpp"
#include "../../renderer/dx/DxRenderer.hpp"
#include "../../renderer/uia/UiaRenderer.hpp"
#include "../../cascadia/TerminalCore/Terminal.hpp"
#include "../buffer/out/search.h"
#include "cppwinrt_utils.h"
#include "ThrottledFunc.h"
namespace ControlUnitTests
{
class ControlCoreTests;
class ControlInteractivityTests;
};
namespace winrt::Microsoft::Terminal::Control::implementation
{
struct ControlCore : ControlCoreT<ControlCore>
{
public:
ControlCore(IControlSettings settings,
TerminalConnection::ITerminalConnection connection);
~ControlCore();
bool Initialize(const double actualWidth,
const double actualHeight,
const double compositionScale);
void EnablePainting();
void UpdateSettings(const IControlSettings& settings);
void UpdateAppearance(const IControlAppearance& newAppearance);
void SizeChanged(const double width, const double height);
void ScaleChanged(const double scale);
IDXGISwapChain1* GetSwapChain() const;
void AdjustFontSize(int fontSizeDelta);
void ResetFontSize();
FontInfo GetFont() const;
til::size FontSizeInDips() const;
til::color BackgroundColor() const;
void SetBackgroundOpacity(const float opacity);
void SendInput(const winrt::hstring& wstr);
void PasteText(const winrt::hstring& hstr);
bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
void ToggleShaderEffects();
void AdjustOpacity(const double adjustment);
void ResumeRendering();
void UpdatePatternLocations();
void UpdateHoveredCell(const std::optional<til::point>& terminalPosition);
winrt::hstring GetHyperlink(const til::point position) const;
winrt::hstring GetHoveredUriText() const;
std::optional<til::point> GetHoveredCell() const;
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
void Close();
#pragma region ICoreState
const size_t TaskbarState() const noexcept;
const size_t TaskbarProgress() const noexcept;
hstring Title();
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() noexcept;
hstring WorkingDirectory() const;
TerminalConnection::ConnectionState ConnectionState() const;
int ScrollOffset();
int ViewHeight() const;
int BufferHeight() const;
bool BracketedPasteEnabled() const noexcept;
#pragma endregion
#pragma region ITerminalInput
bool TrySendKeyEvent(const WORD vkey,
const WORD scanCode,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const bool keyDown);
bool SendCharEvent(const wchar_t ch,
const WORD scanCode,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
bool SendMouseEvent(const til::point viewportPos,
const unsigned int uiButton,
const ::Microsoft::Terminal::Core::ControlKeyStates states,
const short wheelDelta,
const ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state);
void UserScrollViewport(const int viewTop);
#pragma endregion
void BlinkAttributeTick();
void BlinkCursor();
bool CursorOn() const;
void CursorOn(const bool isCursorOn);
bool IsVtMouseModeEnabled() const;
til::point CursorPosition() const;
bool HasSelection() const;
bool CopyOnSelect() const;
std::vector<std::wstring> SelectedText(bool trimTrailingWhitespace) const;
void SetSelectionAnchor(til::point const& position);
void SetEndSelectionPoint(til::point const& position);
void Search(const winrt::hstring& text,
const bool goForward,
const bool caseSensitive);
void LeftClickOnTerminal(const til::point terminalPosition,
const int numberOfClicks,
const bool altEnabled,
const bool shiftEnabled,
const bool isOnOriginalPosition,
bool& selectionNeedsToBeCopied);
void AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine);
bool IsInReadOnlyMode() const;
void ToggleReadOnlyMode();
// -------------------------------- WinRT Events ---------------------------------
// clang-format off
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
TYPED_EVENT(CopyToClipboard, IInspectable, Control::CopyToClipboardEventArgs);
TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs);
TYPED_EVENT(WarningBell, IInspectable, IInspectable);
TYPED_EVENT(TabColorChanged, IInspectable, IInspectable);
TYPED_EVENT(BackgroundColorChanged, IInspectable, IInspectable);
TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
TYPED_EVENT(CursorPositionChanged, IInspectable, IInspectable);
TYPED_EVENT(TaskbarProgressChanged, IInspectable, IInspectable);
TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable);
TYPED_EVENT(HoveredHyperlinkChanged, IInspectable, IInspectable);
TYPED_EVENT(RendererEnteredErrorState, IInspectable, IInspectable);
TYPED_EVENT(SwapChainChanged, IInspectable, IInspectable);
TYPED_EVENT(RendererWarning, IInspectable, Control::RendererWarningArgs);
TYPED_EVENT(RaiseNotice, IInspectable, Control::NoticeEventArgs);
TYPED_EVENT(TransparencyChanged, IInspectable, Control::TransparencyChangedEventArgs);
TYPED_EVENT(ReceivedOutput, IInspectable, IInspectable);
// clang-format on
private:
bool _initializedTerminal{ false };
std::atomic<bool> _closing{ false };
TerminalConnection::ITerminalConnection _connection{ nullptr };
event_token _connectionOutputEventToken;
TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker;
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal{ nullptr };
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr };
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine{ nullptr };
IControlSettings _settings{ nullptr };
FontInfoDesired _desiredFont;
FontInfo _actualFont;
// storage location for the leading surrogate of a utf-16 surrogate pair
std::optional<wchar_t> _leadingSurrogate{ std::nullopt };
std::optional<til::point> _lastHoveredCell{ std::nullopt };
// Track the last hyperlink ID we hovered over
uint16_t _lastHoveredId{ 0 };
bool _isReadOnly{ false };
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _lastHoveredInterval{ std::nullopt };
// These members represent the size of the surface that we should be
// rendering to.
double _panelWidth{ 0 };
double _panelHeight{ 0 };
double _compositionScale{ 0 };
winrt::fire_and_forget _asyncCloseConnection();
void _setFontSize(int fontSize);
void _updateFont(const bool initialUpdate = false);
void _refreshSizeUnderLock();
void _doResizeUnderLock(const double newWidth,
const double newHeight);
void _sendInputToConnection(std::wstring_view wstr);
#pragma region TerminalCoreCallbacks
void _terminalCopyToClipboard(std::wstring_view wstr);
void _terminalWarningBell();
void _terminalTitleChanged(std::wstring_view wstr);
void _terminalTabColorChanged(const std::optional<til::color> color);
void _terminalBackgroundColorChanged(const COLORREF color);
void _terminalScrollPositionChanged(const int viewTop,
const int viewHeight,
const int bufferSize);
void _terminalCursorPositionChanged();
void _terminalTaskbarProgressChanged();
#pragma endregion
#pragma region RendererCallbacks
void _rendererWarning(const HRESULT hr);
void _renderEngineSwapChainChanged();
#pragma endregion
void _raiseReadOnlyWarning();
void _updateAntiAliasingMode(::Microsoft::Console::Render::DxEngine* const dxEngine);
void _connectionOutputHandler(const hstring& hstr);
friend class ControlUnitTests::ControlCoreTests;
friend class ControlUnitTests::ControlInteractivityTests;
};
}
namespace winrt::Microsoft::Terminal::Control::factory_implementation
{
BASIC_FACTORY(ControlCore);
}

View File

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "ICoreState.idl";
import "IControlSettings.idl";
import "EventArgs.idl";
namespace Microsoft.Terminal.Control
{
[default_interface] runtimeclass ControlCore : ICoreState
{
ControlCore(IControlSettings settings,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
};
}

View File

@ -0,0 +1,531 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ControlInteractivity.h"
#include <argb.h>
#include <DefaultSettings.h>
#include <unicode.hpp>
#include <Utf16Parser.hpp>
#include <Utils.h>
#include <WinUser.h>
#include <LibraryResources.h>
#include "../../types/inc/GlyphWidth.hpp"
#include "../../types/inc/Utils.hpp"
#include "../../buffer/out/search.h"
#include "ControlInteractivity.g.cpp"
using namespace ::Microsoft::Console::Types;
using namespace ::Microsoft::Console::VirtualTerminal;
using namespace ::Microsoft::Terminal::Core;
using namespace winrt::Windows::Graphics::Display;
using namespace winrt::Windows::System;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
static constexpr unsigned int MAX_CLICK_COUNT = 3;
namespace winrt::Microsoft::Terminal::Control::implementation
{
ControlInteractivity::ControlInteractivity(IControlSettings settings,
TerminalConnection::ITerminalConnection connection) :
_touchAnchor{ std::nullopt },
_lastMouseClickTimestamp{},
_lastMouseClickPos{},
_selectionNeedsToBeCopied{ false }
{
_core = winrt::make_self<ControlCore>(settings, connection);
}
void ControlInteractivity::UpdateSettings()
{
_updateSystemParameterSettings();
}
void ControlInteractivity::Initialize()
{
_updateSystemParameterSettings();
// import value from WinUser (convert from milli-seconds to micro-seconds)
_multiClickTimer = GetDoubleClickTime() * 1000;
}
winrt::com_ptr<ControlCore> ControlInteractivity::GetCore()
{
return _core;
}
// Method Description:
// - Returns the number of clicks that occurred (double and triple click support).
// Every call to this function registers a click.
// Arguments:
// - clickPos: the (x,y) position of a given cursor (i.e.: mouse cursor).
// NOTE: origin (0,0) is top-left.
// - clickTime: the timestamp that the click occurred
// Return Value:
// - if the click is in the same position as the last click and within the timeout, the number of clicks within that time window
// - otherwise, 1
unsigned int ControlInteractivity::_numberOfClicks(til::point clickPos,
Timestamp clickTime)
{
// if click occurred at a different location or past the multiClickTimer...
Timestamp delta;
THROW_IF_FAILED(UInt64Sub(clickTime, _lastMouseClickTimestamp, &delta));
if (clickPos != _lastMouseClickPos || delta > _multiClickTimer)
{
_multiClickCounter = 1;
}
else
{
_multiClickCounter++;
}
_lastMouseClickTimestamp = clickTime;
_lastMouseClickPos = clickPos;
return _multiClickCounter;
}
void ControlInteractivity::GainFocus()
{
_updateSystemParameterSettings();
}
// Method Description
// - Updates internal params based on system parameters
void ControlInteractivity::_updateSystemParameterSettings() noexcept
{
if (!SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &_rowsToScroll, 0))
{
LOG_LAST_ERROR();
// If SystemParametersInfoW fails, which it shouldn't, fall back to
// Windows' default value.
_rowsToScroll = 3;
}
}
// Method Description:
// - Given a copy-able selection, get the selected text from the buffer and send it to the
// Windows Clipboard (CascadiaWin32:main.cpp).
// - CopyOnSelect does NOT clear the selection
// Arguments:
// - singleLine: collapse all of the text to one line
// - formats: which formats to copy (defined by action's CopyFormatting arg). nullptr
// if we should defer which formats are copied to the global setting
bool ControlInteractivity::CopySelectionToClipboard(bool singleLine,
const Windows::Foundation::IReference<CopyFormat>& formats)
{
if (_core)
{
// Return false if there's no selection to copy. If there's no
// selection, returning false will indicate that the actions that
// triggered this should _not_ be marked as handled, so ctrl+c
// without a selection can still send ^C
if (!_core->HasSelection())
{
return false;
}
// Mark the current selection as copied
_selectionNeedsToBeCopied = false;
return _core->CopySelectionToClipboard(singleLine, formats);
}
return false;
}
// Method Description:
// - Initiate a paste operation.
void ControlInteractivity::RequestPasteTextFromClipboard()
{
// attach ControlInteractivity::_sendPastedTextToConnection() as the
// clipboardDataHandler. This is called when the clipboard data is
// loaded.
auto clipboardDataHandler = std::bind(&ControlInteractivity::_sendPastedTextToConnection, this, std::placeholders::_1);
auto pasteArgs = winrt::make_self<PasteFromClipboardEventArgs>(clipboardDataHandler);
// send paste event up to TermApp
_PasteFromClipboardHandlers(*this, *pasteArgs);
}
// Method Description:
// - Pre-process text pasted (presumably from the clipboard)
// before sending it over the terminal's connection.
void ControlInteractivity::_sendPastedTextToConnection(std::wstring_view wstr)
{
_core->PasteText(winrt::hstring{ wstr });
}
void ControlInteractivity::PointerPressed(TerminalInput::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const uint64_t timestamp,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const til::point pixelPosition)
{
const til::point terminalPosition = _getTerminalPosition(pixelPosition);
const auto altEnabled = modifiers.IsAltPressed();
const auto shiftEnabled = modifiers.IsShiftPressed();
const auto ctrlEnabled = modifiers.IsCtrlPressed();
// GH#9396: we prioritize hyper-link over VT mouse events
auto hyperlink = _core->GetHyperlink(terminalPosition);
if (buttonState.isLeftButtonDown &&
ctrlEnabled && !hyperlink.empty())
{
const auto clickCount = _numberOfClicks(pixelPosition, timestamp);
// Handle hyper-link only on the first click to prevent multiple activations
if (clickCount == 1)
{
_hyperlinkHandler(hyperlink);
}
}
else if (_canSendVTMouseInput(modifiers))
{
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
}
else if (buttonState.isLeftButtonDown)
{
const auto clickCount = _numberOfClicks(pixelPosition, timestamp);
// This formula enables the number of clicks to cycle properly
// between single-, double-, and triple-click. To increase the
// number of acceptable click states, simply increment
// MAX_CLICK_COUNT and add another if-statement
const auto multiClickMapper = clickCount > MAX_CLICK_COUNT ? ((clickCount + MAX_CLICK_COUNT - 1) % MAX_CLICK_COUNT) + 1 : clickCount;
// Capture the position of the first click when no selection is active
if (multiClickMapper == 1)
{
_singleClickTouchdownPos = pixelPosition;
_singleClickTouchdownTerminalPos = terminalPosition;
if (!_core->HasSelection())
{
_lastMouseClickPosNoSelection = pixelPosition;
}
}
const bool isOnOriginalPosition = _lastMouseClickPosNoSelection == pixelPosition;
_core->LeftClickOnTerminal(terminalPosition,
multiClickMapper,
altEnabled,
shiftEnabled,
isOnOriginalPosition,
_selectionNeedsToBeCopied);
if (_core->HasSelection())
{
// GH#9787: if selection is active we don't want to track the touchdown position
// so that dragging the mouse will extend the selection rather than starting the new one
_singleClickTouchdownPos = std::nullopt;
}
}
else if (buttonState.isRightButtonDown)
{
// CopyOnSelect right click always pastes
if (_core->CopyOnSelect() || !_core->HasSelection())
{
RequestPasteTextFromClipboard();
}
else
{
CopySelectionToClipboard(shiftEnabled, nullptr);
}
}
}
void ControlInteractivity::TouchPressed(const til::point contactPoint)
{
_touchAnchor = contactPoint;
}
void ControlInteractivity::PointerMoved(TerminalInput::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const bool focused,
const til::point pixelPosition)
{
const til::point terminalPosition = _getTerminalPosition(pixelPosition);
// Short-circuit isReadOnly check to avoid warning dialog
if (focused && !_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
{
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
}
else if (focused && buttonState.isLeftButtonDown)
{
if (_singleClickTouchdownPos)
{
// Figure out if the user's moved a quarter of a cell's smaller axis away from the clickdown point
auto& touchdownPoint{ *_singleClickTouchdownPos };
float dx = ::base::saturated_cast<float>(pixelPosition.x() - touchdownPoint.x());
float dy = ::base::saturated_cast<float>(pixelPosition.y() - touchdownPoint.y());
auto distance{ std::sqrtf(std::powf(dx, 2) +
std::powf(dy, 2)) };
const auto fontSizeInDips{ _core->FontSizeInDips() };
if (distance >= (std::min(fontSizeInDips.width(), fontSizeInDips.height()) / 4.f))
{
_core->SetSelectionAnchor(terminalPosition);
// stop tracking the touchdown point
_singleClickTouchdownPos = std::nullopt;
_singleClickTouchdownTerminalPos = std::nullopt;
}
}
SetEndSelectionPoint(pixelPosition);
}
_core->UpdateHoveredCell(terminalPosition);
}
void ControlInteractivity::TouchMoved(const til::point newTouchPoint,
const bool focused)
{
if (focused &&
_touchAnchor)
{
const auto anchor = _touchAnchor.value();
// Our actualFont's size is in pixels, convert to DIPs, which the
// rest of the Points here are in.
const auto fontSizeInDips{ _core->FontSizeInDips() };
// Get the difference between the point we've dragged to and the start of the touch.
const float dy = ::base::saturated_cast<float>(newTouchPoint.y() - anchor.y());
// Start viewport scroll after we've moved more than a half row of text
if (std::abs(dy) > (fontSizeInDips.height<float>() / 2.0f))
{
// Multiply by -1, because moving the touch point down will
// create a positive delta, but we want the viewport to move up,
// so we'll need a negative scroll amount (and the inverse for
// panning down)
const float numRows = -1.0f * (dy / fontSizeInDips.height<float>());
const auto currentOffset = ::base::ClampedNumeric<double>(_core->ScrollOffset());
const auto newValue = numRows + currentOffset;
// Update the Core's viewport position, and raise a
// ScrollPositionChanged event to update the scrollbar
_updateScrollbar(newValue);
// Use this point as our new scroll anchor.
_touchAnchor = newTouchPoint;
}
}
}
void ControlInteractivity::PointerReleased(TerminalInput::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const til::point pixelPosition)
{
const til::point terminalPosition = _getTerminalPosition(pixelPosition);
// Short-circuit isReadOnly check to avoid warning dialog
if (!_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
{
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
return;
}
// Only a left click release when copy on select is active should perform a copy.
// Right clicks and middle clicks should not need to do anything when released.
const bool isLeftMouseRelease = pointerUpdateKind == WM_LBUTTONUP;
if (_core->CopyOnSelect() &&
isLeftMouseRelease &&
_selectionNeedsToBeCopied)
{
CopySelectionToClipboard(false, nullptr);
}
_singleClickTouchdownPos = std::nullopt;
_singleClickTouchdownTerminalPos = std::nullopt;
}
void ControlInteractivity::TouchReleased()
{
_touchAnchor = std::nullopt;
}
// Method Description:
// - Actually handle a scrolling event, whether from a mouse wheel or a
// touchpad scroll. Depending upon what modifier keys are pressed,
// different actions will take place.
// * Attempts to first dispatch the mouse scroll as a VT event
// * If Ctrl+Shift are pressed, then attempts to change our opacity
// * If just Ctrl is pressed, we'll attempt to "zoom" by changing our font size
// * Otherwise, just scrolls the content of the viewport
// Arguments:
// - point: the location of the mouse during this event
// - modifiers: The modifiers pressed during this event, in the form of a VirtualKeyModifiers
// - delta: the mouse wheel delta that triggered this event.
bool ControlInteractivity::MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const int32_t delta,
const til::point pixelPosition,
const TerminalInput::MouseButtonState state)
{
const til::point terminalPosition = _getTerminalPosition(pixelPosition);
// Short-circuit isReadOnly check to avoid warning dialog
if (!_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
{
// Most mouse event handlers call
// _trySendMouseEvent(point);
// here with a PointerPoint. However, as of #979, we don't have a
// PointerPoint to work with. So, we're just going to do a
// mousewheel event manually
return _core->SendMouseEvent(terminalPosition,
WM_MOUSEWHEEL,
modifiers,
::base::saturated_cast<short>(delta),
state);
}
const auto ctrlPressed = modifiers.IsCtrlPressed();
const auto shiftPressed = modifiers.IsShiftPressed();
if (ctrlPressed && shiftPressed)
{
_mouseTransparencyHandler(delta);
}
else if (ctrlPressed)
{
_mouseZoomHandler(delta);
}
else
{
_mouseScrollHandler(delta, terminalPosition, state.isLeftButtonDown);
}
return false;
}
// Method Description:
// - Adjust the opacity of the acrylic background in response to a mouse
// scrolling event.
// Arguments:
// - mouseDelta: the mouse wheel delta that triggered this event.
void ControlInteractivity::_mouseTransparencyHandler(const double mouseDelta)
{
// Transparency is on a scale of [0.0,1.0], so only increment by .01.
const auto effectiveDelta = mouseDelta < 0 ? -.01 : .01;
_core->AdjustOpacity(effectiveDelta);
}
// Method Description:
// - Adjust the font size of the terminal in response to a mouse scrolling
// event.
// Arguments:
// - mouseDelta: the mouse wheel delta that triggered this event.
void ControlInteractivity::_mouseZoomHandler(const double mouseDelta)
{
const auto fontDelta = mouseDelta < 0 ? -1 : 1;
_core->AdjustFontSize(fontDelta);
}
// Method Description:
// - Scroll the visible viewport in response to a mouse wheel event.
// Arguments:
// - mouseDelta: the mouse wheel delta that triggered this event.
// - point: the location of the mouse during this event
// - isLeftButtonPressed: true iff the left mouse button was pressed during this event.
void ControlInteractivity::_mouseScrollHandler(const double mouseDelta,
const til::point terminalPosition,
const bool isLeftButtonPressed)
{
const auto currentOffset = _core->ScrollOffset();
// negative = down, positive = up
// However, for us, the signs are flipped.
// With one of the precision mice, one click is always a multiple of 120 (WHEEL_DELTA),
// but the "smooth scrolling" mode results in non-int values
const auto rowDelta = mouseDelta / (-1.0 * WHEEL_DELTA);
// WHEEL_PAGESCROLL is a Win32 constant that represents the "scroll one page
// at a time" setting. If we ignore it, we will scroll a truly absurd number
// of rows.
const auto rowsToScroll{ _rowsToScroll == WHEEL_PAGESCROLL ? _core->ViewHeight() : _rowsToScroll };
double newValue = (rowsToScroll * rowDelta) + (currentOffset);
// Update the Core's viewport position, and raise a
// ScrollPositionChanged event to update the scrollbar
_updateScrollbar(::base::saturated_cast<int>(newValue));
if (isLeftButtonPressed)
{
// If user is mouse selecting and scrolls, they then point at new
// character. Make sure selection reflects that immediately.
SetEndSelectionPoint(terminalPosition);
}
}
// Method Description:
// - Update the scroll position in such a way that should update the
// scrollbar. For example, when scrolling the buffer with the mouse or
// touch input. This will both update the Core's Terminal's buffer
// location, then also raise our own ScrollPositionChanged event.
// UserScrollViewport _won't_ raise the core's ScrollPositionChanged
// event, because it's assumed that's already being called from a context
// that knows about the change to the scrollbar. So we need to raise the
// event on our own.
// - The hosting control should make sure to listen to our own
// ScrollPositionChanged event and use that as an opportunity to update
// the location of the scrollbar.
// Arguments:
// - newValue: The new top of the viewport
// Return Value:
// - <none>
void ControlInteractivity::_updateScrollbar(const int newValue)
{
_core->UserScrollViewport(newValue);
// _core->ScrollOffset() is now set to newValue
_ScrollPositionChangedHandlers(*this,
winrt::make<ScrollPositionChangedArgs>(_core->ScrollOffset(),
_core->ViewHeight(),
_core->BufferHeight()));
}
void ControlInteractivity::_hyperlinkHandler(const std::wstring_view uri)
{
_OpenHyperlinkHandlers(*this, winrt::make<OpenHyperlinkEventArgs>(winrt::hstring{ uri }));
}
bool ControlInteractivity::_canSendVTMouseInput(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers)
{
// If the user is holding down Shift, suppress mouse events
// TODO GH#4875: disable/customize this functionality
if (modifiers.IsShiftPressed())
{
return false;
}
return _core->IsVtMouseModeEnabled();
}
// Method Description:
// - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging.
// Arguments:
// - cursorPosition: in pixels, relative to the origin of the control
void ControlInteractivity::SetEndSelectionPoint(const til::point pixelPosition)
{
_core->SetEndSelectionPoint(_getTerminalPosition(pixelPosition));
_selectionNeedsToBeCopied = true;
}
// Method Description:
// - Gets the corresponding viewport terminal position for the point in
// pixels, by normalizing with the font size.
// Arguments:
// - pixelPosition: the (x,y) position of a given point (i.e.: mouse cursor).
// NOTE: origin (0,0) is top-left.
// Return Value:
// - the corresponding viewport terminal position for the given Point parameter
til::point ControlInteractivity::_getTerminalPosition(const til::point& pixelPosition)
{
// Get the size of the font, which is in pixels
const til::size fontSize{ _core->GetFont().GetSize() };
// Convert the location in pixels to characters within the current viewport.
return til::point{ pixelPosition / fontSize };
}
}

View File

@ -0,0 +1,142 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// Module Name:
// - ControlInteractivity.h
//
// Abstract:
// - This is a wrapper for the `ControlCore`. It holds the logic for things like
// double-click, right click copy/paste, selection, etc. This is intended to
// be a UI framework-independent abstraction. The methods this layer exposes
// can be called the same from both the WinUI `TermControl` and the WPF
// control.
//
// Author:
// - Mike Griese (zadjii-msft) 01-Apr-2021
#pragma once
#include "ControlInteractivity.g.h"
#include "EventArgs.h"
#include "../buffer/out/search.h"
#include "cppwinrt_utils.h"
#include "ControlCore.h"
namespace Microsoft::Console::VirtualTerminal
{
struct MouseButtonState;
}
namespace ControlUnitTests
{
class ControlCoreTests;
class ControlInteractivityTests;
};
namespace winrt::Microsoft::Terminal::Control::implementation
{
struct ControlInteractivity : ControlInteractivityT<ControlInteractivity>
{
public:
ControlInteractivity(IControlSettings settings,
TerminalConnection::ITerminalConnection connection);
void GainFocus();
void UpdateSettings();
void Initialize();
winrt::com_ptr<ControlCore> GetCore();
#pragma region Input Methods
void PointerPressed(::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const uint64_t timestamp,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const til::point pixelPosition);
void TouchPressed(const til::point contactPoint);
void PointerMoved(::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const bool focused,
const til::point pixelPosition);
void TouchMoved(const til::point newTouchPoint,
const bool focused);
void PointerReleased(::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const til::point pixelPosition);
void TouchReleased();
bool MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const int32_t delta,
const til::point pixelPosition,
const ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state);
#pragma endregion
bool CopySelectionToClipboard(bool singleLine,
const Windows::Foundation::IReference<CopyFormat>& formats);
void RequestPasteTextFromClipboard();
void SetEndSelectionPoint(const til::point pixelPosition);
private:
winrt::com_ptr<ControlCore> _core{ nullptr };
unsigned int _rowsToScroll;
// If this is set, then we assume we are in the middle of panning the
// viewport via touch input.
std::optional<til::point> _touchAnchor;
using Timestamp = uint64_t;
// imported from WinUser
// Used for PointerPoint.Timestamp Property (https://docs.microsoft.com/en-us/uwp/api/windows.ui.input.pointerpoint.timestamp#Windows_UI_Input_PointerPoint_Timestamp)
Timestamp _multiClickTimer;
unsigned int _multiClickCounter;
Timestamp _lastMouseClickTimestamp;
std::optional<til::point> _lastMouseClickPos;
std::optional<til::point> _singleClickTouchdownPos;
std::optional<til::point> _singleClickTouchdownTerminalPos;
std::optional<til::point> _lastMouseClickPosNoSelection;
// This field tracks whether the selection has changed meaningfully
// since it was last copied. It's generally used to prevent copyOnSelect
// from firing when the pointer _just happens_ to be released over the
// terminal.
bool _selectionNeedsToBeCopied;
std::optional<COORD> _lastHoveredCell{ std::nullopt };
// Track the last hyperlink ID we hovered over
uint16_t _lastHoveredId{ 0 };
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _lastHoveredInterval{ std::nullopt };
unsigned int _numberOfClicks(til::point clickPos, Timestamp clickTime);
void _updateSystemParameterSettings() noexcept;
void _mouseTransparencyHandler(const double mouseDelta);
void _mouseZoomHandler(const double mouseDelta);
void _mouseScrollHandler(const double mouseDelta,
const til::point terminalPosition,
const bool isLeftButtonPressed);
void _hyperlinkHandler(const std::wstring_view uri);
bool _canSendVTMouseInput(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
void _sendPastedTextToConnection(std::wstring_view wstr);
void _updateScrollbar(const int newValue);
til::point _getTerminalPosition(const til::point& pixelPosition);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
friend class ControlUnitTests::ControlCoreTests;
friend class ControlUnitTests::ControlInteractivityTests;
};
}
namespace winrt::Microsoft::Terminal::Control::factory_implementation
{
BASIC_FACTORY(ControlInteractivity);
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "ICoreState.idl";
import "IControlSettings.idl";
import "ControlCore.idl";
import "EventArgs.idl";
namespace Microsoft.Terminal.Control
{
[default_interface] runtimeclass ControlInteractivity
{
ControlInteractivity(IControlSettings settings,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
};
}

View File

@ -10,3 +10,4 @@
#include "NoticeEventArgs.g.cpp"
#include "ScrollPositionChangedArgs.g.cpp"
#include "RendererWarningArgs.g.cpp"
#include "TransparencyChangedEventArgs.g.cpp"

View File

@ -10,12 +10,12 @@
#include "NoticeEventArgs.g.h"
#include "ScrollPositionChangedArgs.g.h"
#include "RendererWarningArgs.g.h"
#include "TransparencyChangedEventArgs.g.h"
#include "cppwinrt_utils.h"
namespace winrt::Microsoft::Terminal::Control::implementation
{
struct TitleChangedEventArgs :
public TitleChangedEventArgsT<TitleChangedEventArgs>
struct TitleChangedEventArgs : public TitleChangedEventArgsT<TitleChangedEventArgs>
{
public:
TitleChangedEventArgs(hstring title) :
@ -24,8 +24,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_PROPERTY(hstring, Title);
};
struct CopyToClipboardEventArgs :
public CopyToClipboardEventArgsT<CopyToClipboardEventArgs>
struct CopyToClipboardEventArgs : public CopyToClipboardEventArgsT<CopyToClipboardEventArgs>
{
public:
CopyToClipboardEventArgs(hstring text) :
@ -52,24 +51,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Windows::Foundation::IReference<CopyFormat> _formats;
};
struct PasteFromClipboardEventArgs :
public PasteFromClipboardEventArgsT<PasteFromClipboardEventArgs>
struct PasteFromClipboardEventArgs : public PasteFromClipboardEventArgsT<PasteFromClipboardEventArgs>
{
public:
PasteFromClipboardEventArgs(std::function<void(std::wstring)> clipboardDataHandler) :
PasteFromClipboardEventArgs(std::function<void(std::wstring_view)> clipboardDataHandler) :
m_clipboardDataHandler(clipboardDataHandler) {}
void HandleClipboardData(hstring value)
{
m_clipboardDataHandler(static_cast<std::wstring>(value));
m_clipboardDataHandler(value);
};
private:
std::function<void(std::wstring)> m_clipboardDataHandler;
std::function<void(std::wstring_view)> m_clipboardDataHandler;
};
struct OpenHyperlinkEventArgs :
public OpenHyperlinkEventArgsT<OpenHyperlinkEventArgs>
struct OpenHyperlinkEventArgs : public OpenHyperlinkEventArgsT<OpenHyperlinkEventArgs>
{
public:
OpenHyperlinkEventArgs(hstring uri) :
@ -81,8 +78,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
hstring _uri;
};
struct NoticeEventArgs :
public NoticeEventArgsT<NoticeEventArgs>
struct NoticeEventArgs : public NoticeEventArgsT<NoticeEventArgs>
{
public:
NoticeEventArgs(const NoticeLevel level, const hstring& message) :
@ -97,8 +93,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const hstring _message;
};
struct ScrollPositionChangedArgs :
public ScrollPositionChangedArgsT<ScrollPositionChangedArgs>
struct ScrollPositionChangedArgs : public ScrollPositionChangedArgsT<ScrollPositionChangedArgs>
{
public:
ScrollPositionChangedArgs(const int viewTop,
@ -115,8 +110,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_PROPERTY(int32_t, BufferSize);
};
struct RendererWarningArgs :
public RendererWarningArgsT<RendererWarningArgs>
struct RendererWarningArgs : public RendererWarningArgsT<RendererWarningArgs>
{
public:
RendererWarningArgs(const uint64_t hr) :
@ -126,4 +120,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_PROPERTY(uint64_t, Result);
};
struct TransparencyChangedEventArgs : public TransparencyChangedEventArgsT<TransparencyChangedEventArgs>
{
public:
TransparencyChangedEventArgs(const double opacity) :
_Opacity(opacity)
{
}
WINRT_PROPERTY(double, Opacity);
};
}

View File

@ -62,4 +62,9 @@ namespace Microsoft.Terminal.Control
{
UInt64 Result { get; };
}
runtimeclass TransparencyChangedEventArgs
{
Double Opacity { get; };
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Control
{
// These are properties of the TerminalCore that should be queryable by the
// rest of the app.
interface ICoreState
{
String Title { get; };
UInt64 TaskbarState { get; };
UInt64 TaskbarProgress { get; };
String WorkingDirectory { get; };
Windows.Foundation.IReference<Windows.UI.Color> TabColor { get; };
Int32 ScrollOffset { get; };
Int32 ViewHeight { get; };
Boolean BracketedPasteEnabled { get; };
Microsoft.Terminal.TerminalConnection.ConnectionState ConnectionState { get; };
};
}

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,8 @@
#include "SearchBoxControl.h"
#include "ThrottledFunc.h"
#include "ControlInteractivity.h"
namespace Microsoft::Console::VirtualTerminal
{
struct MouseButtonState;
@ -28,11 +30,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::fire_and_forget UpdateSettings();
winrt::fire_and_forget UpdateAppearance(const IControlAppearance newAppearance);
hstring Title();
hstring GetProfileName() const;
hstring WorkingDirectory() const;
bool BracketedPasteEnabled() const noexcept;
bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
void PasteTextFromClipboard();
void Close();
@ -40,21 +39,39 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Windows::Foundation::Size MinimumSize();
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension);
#pragma region ICoreState
const size_t TaskbarState() const noexcept;
const size_t TaskbarProgress() const noexcept;
hstring Title();
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() noexcept;
hstring WorkingDirectory() const;
TerminalConnection::ConnectionState ConnectionState() const;
int ScrollOffset();
int ViewHeight() const;
int BufferHeight() const;
bool BracketedPasteEnabled() const noexcept;
#pragma endregion
void ScrollViewport(int viewTop);
int GetScrollOffset();
int GetViewHeight() const;
void AdjustFontSize(int fontSizeDelta);
void ResetFontSize();
til::point GetFontSize() const;
void SendInput(const winrt::hstring& input);
void ToggleShaderEffects();
winrt::fire_and_forget RenderEngineSwapChainChanged();
winrt::fire_and_forget RenderEngineSwapChainChanged(IInspectable sender, IInspectable args);
void _AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain);
winrt::fire_and_forget _RendererEnteredErrorState();
winrt::fire_and_forget _RendererEnteredErrorState(IInspectable sender, IInspectable args);
void _RenderRetryButton_Click(IInspectable const& button, IInspectable const& args);
winrt::fire_and_forget _RendererWarning(const HRESULT hr);
winrt::fire_and_forget _RendererWarning(IInspectable sender,
Control::RendererWarningArgs args);
void CreateSearchBoxControl();
@ -64,16 +81,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
void UpdatePatternLocations();
~TermControl();
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
const FontInfo GetActualFont() const;
const Windows::UI::Xaml::Thickness GetPadding();
TerminalConnection::ConnectionState ConnectionState() const;
IControlSettings Settings() const;
static Windows::Foundation::Size GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi);
@ -85,64 +98,52 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const winrt::hstring& padding,
const uint32_t dpi);
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() noexcept;
winrt::fire_and_forget TaskbarProgressChanged();
const size_t TaskbarState() const noexcept;
const size_t TaskbarProgress() const noexcept;
bool ReadOnly() const noexcept;
void ToggleReadOnly();
// clang-format off
static ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState GetPressedMouseButtons(const winrt::Windows::UI::Input::PointerPoint point);
static unsigned int GetPointerUpdateKind(const winrt::Windows::UI::Input::PointerPoint point);
// -------------------------------- WinRT Events ---------------------------------
DECLARE_EVENT(FontSizeChanged, _fontSizeChangedHandlers, Control::FontSizeChangedEventArgs);
DECLARE_EVENT(ScrollPositionChanged, _scrollPositionChangedHandlers, Control::ScrollPositionChangedEventArgs);
// clang-format off
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs);
TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
TYPED_EVENT(CopyToClipboard, IInspectable, Control::CopyToClipboardEventArgs);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable);
TYPED_EVENT(RaiseNotice, IInspectable, Control::NoticeEventArgs);
FORWARDED_TYPED_EVENT(CopyToClipboard, IInspectable, Control::CopyToClipboardEventArgs, _core, CopyToClipboard);
FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs, _core, TitleChanged);
FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable, _core, TabColorChanged);
FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable, _core, TaskbarProgressChanged);
FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable, _core, ConnectionStateChanged);
FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs, _interactivity, PasteFromClipboard);
TYPED_EVENT(WarningBell, IInspectable, IInspectable);
TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable);
TYPED_EVENT(Initialized, Control::TermControl, Windows::UI::Xaml::RoutedEventArgs);
TYPED_EVENT(TabColorChanged, IInspectable, IInspectable);
TYPED_EVENT(HidePointerCursor, IInspectable, IInspectable);
TYPED_EVENT(RestorePointerCursor, IInspectable, IInspectable);
TYPED_EVENT(ReadOnlyChanged, IInspectable, IInspectable);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(RaiseNotice, IInspectable, Control::NoticeEventArgs);
TYPED_EVENT(HidePointerCursor, IInspectable, IInspectable);
TYPED_EVENT(RestorePointerCursor, IInspectable, IInspectable);
TYPED_EVENT(ReadOnlyChanged, IInspectable, IInspectable);
TYPED_EVENT(FocusFollowMouseRequested, IInspectable, IInspectable);
TYPED_EVENT(Initialized, Control::TermControl, Windows::UI::Xaml::RoutedEventArgs);
TYPED_EVENT(WarningBell, IInspectable, IInspectable);
// clang-format on
WINRT_PROPERTY(IControlAppearance, UnfocusedAppearance);
private:
friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers
TerminalConnection::ITerminalConnection _connection;
winrt::com_ptr<ControlCore> _core;
winrt::com_ptr<ControlInteractivity> _interactivity;
bool _initializedTerminal;
winrt::com_ptr<SearchBoxControl> _searchBox;
event_token _connectionOutputEventToken;
TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker;
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer;
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;
std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine;
IControlSettings _settings;
bool _focused;
std::atomic<bool> _closing;
FontInfoDesired _desiredFont;
FontInfo _actualFont;
std::shared_ptr<ThrottledFunc<>> _tsfTryRedrawCanvas;
std::shared_ptr<ThrottledFunc<>> _updatePatternLocations;
std::shared_ptr<ThrottledFunc<>> _playWarningBell;
@ -157,59 +158,28 @@ namespace winrt::Microsoft::Terminal::Control::implementation
std::shared_ptr<ThrottledFunc<ScrollBarUpdate>> _updateScrollBar;
bool _isInternalScrollBarUpdate;
unsigned int _rowsToScroll;
// Auto scroll occurs when user, while selecting, drags cursor outside viewport. View is then scrolled to 'follow' the cursor.
double _autoScrollVelocity;
std::optional<Windows::UI::Input::PointerPoint> _autoScrollingPointerPoint;
Windows::UI::Xaml::DispatcherTimer _autoScrollTimer;
std::optional<std::chrono::high_resolution_clock::time_point> _lastAutoScrollUpdateTime;
// storage location for the leading surrogate of a utf-16 surrogate pair
std::optional<wchar_t> _leadingSurrogate;
std::optional<Windows::UI::Xaml::DispatcherTimer> _cursorTimer;
std::optional<Windows::UI::Xaml::DispatcherTimer> _blinkTimer;
// If this is set, then we assume we are in the middle of panning the
// viewport via touch input.
std::optional<winrt::Windows::Foundation::Point> _touchAnchor;
// Track the last cell we hovered over (used in pointerMovedHandler)
std::optional<COORD> _lastHoveredCell;
// Track the last hyperlink ID we hovered over
uint16_t _lastHoveredId;
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _lastHoveredInterval;
using Timestamp = uint64_t;
// imported from WinUser
// Used for PointerPoint.Timestamp Property (https://docs.microsoft.com/en-us/uwp/api/windows.ui.input.pointerpoint.timestamp#Windows_UI_Input_PointerPoint_Timestamp)
Timestamp _multiClickTimer;
unsigned int _multiClickCounter;
Timestamp _lastMouseClickTimestamp;
std::optional<winrt::Windows::Foundation::Point> _lastMouseClickPos;
std::optional<winrt::Windows::Foundation::Point> _lastMouseClickPosNoSelection;
std::optional<winrt::Windows::Foundation::Point> _singleClickTouchdownPos;
// This field tracks whether the selection has changed meaningfully
// since it was last copied. It's generally used to prevent copyOnSelect
// from firing when the pointer _just happens_ to be released over the
// terminal.
bool _selectionNeedsToBeCopied;
event_token _coreOutputEventToken;
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
void _UpdateSettingsFromUIThreadUnderLock(IControlSettings newSettings);
void _UpdateAppearanceFromUIThreadUnderLock(IControlAppearance newAppearance);
bool _isReadOnly{ false };
void _UpdateSettingsFromUIThread(IControlSettings newSettings);
void _UpdateAppearanceFromUIThread(IControlAppearance newAppearance);
void _ApplyUISettings(const IControlSettings&);
void _UpdateSystemParameterSettings() noexcept;
void _InitializeBackgroundBrush();
winrt::fire_and_forget _BackgroundColorChanged(const til::color color);
void _BackgroundColorChangedHandler(const IInspectable& sender, const IInspectable& args);
winrt::fire_and_forget _changeBackgroundColor(const til::color bg);
bool _InitializeTerminal();
void _UpdateFont(const bool initialUpdate = false);
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);
@ -221,33 +191,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation
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);
winrt::fire_and_forget _DragDropHandler(Windows::Foundation::IInspectable sender, Windows::UI::Xaml::DragEventArgs e);
void _DragOverHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const& e);
winrt::fire_and_forget _HyperlinkHandler(const std::wstring_view uri);
winrt::fire_and_forget _HyperlinkHandler(Windows::Foundation::IInspectable sender, Control::OpenHyperlinkEventArgs e);
void _CursorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
void _BlinkTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition);
void _SendInputToConnection(const winrt::hstring& wstr);
void _SendInputToConnection(std::wstring_view wstr);
void _SendPastedTextToConnection(const std::wstring& wstr);
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 _DoResizeUnderLock(const double newWidth, const double newHeight);
void _RefreshSizeUnderLock();
void _TerminalWarningBell();
void _TerminalTitleChanged(const std::wstring_view& wstr);
void _TerminalTabColorChanged(const std::optional<til::color> color);
void _CopyToClipboard(const std::wstring_view& wstr);
void _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
void _TerminalCursorPositionChanged();
void _MouseScrollHandler(const double mouseDelta, const Windows::Foundation::Point point, const bool isLeftButtonPressed);
void _MouseZoomHandler(const double delta);
void _MouseTransparencyHandler(const double delta);
bool _DoMouseWheel(const Windows::Foundation::Point point, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta, const ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state);
void _TerminalTabColorChanged(const std::optional<til::color> color);
void _ScrollPositionChanged(const IInspectable& sender, const Control::ScrollPositionChangedArgs& args);
void _CursorPositionChanged(const IInspectable& sender, const IInspectable& args);
bool _CapturePointer(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
bool _ReleasePointerCapture(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
@ -263,11 +227,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool _TryHandleKeyBinding(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const;
void _ClearKeyboardState(const WORD vkey, const WORD scanCode) const noexcept;
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown);
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
bool _CanSendVTMouseInput();
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime);
const til::point _toTerminalOrigin(winrt::Windows::Foundation::Point cursorPosition);
double _GetAutoScrollSpeed(double cursorDistanceFromBorder) const;
void _Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive);
@ -277,11 +238,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _CompositionCompleted(winrt::hstring text);
void _CurrentCursorPositionHandler(const IInspectable& sender, const CursorPositionEventArgs& eventArgs);
void _FontInfoHandler(const IInspectable& sender, const FontInfoEventArgs& eventArgs);
winrt::fire_and_forget _AsyncCloseConnection();
winrt::fire_and_forget _RaiseReadOnlyWarning();
winrt::fire_and_forget _hoveredHyperlinkChanged(IInspectable sender, IInspectable args);
void _UpdateHoveredCell(const std::optional<COORD>& terminalPosition);
void _coreFontSizeChanged(const int fontWidth,
const int fontHeight,
const bool isInitialChange);
winrt::fire_and_forget _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args);
void _coreReceivedOutput(const IInspectable& sender, const IInspectable& args);
void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args);
void _coreWarningBell(const IInspectable& sender, const IInspectable& args);
};
}

View File

@ -5,23 +5,28 @@ import "IMouseWheelListener.idl";
import "IControlSettings.idl";
import "IDirectKeyListener.idl";
import "EventArgs.idl";
import "ICoreState.idl";
namespace Microsoft.Terminal.Control
{
[default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl, IDirectKeyListener, IMouseWheelListener
[default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl,
IDirectKeyListener,
IMouseWheelListener,
ICoreState
{
TermControl(Microsoft.Terminal.Control.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
TermControl(IControlSettings settings,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
static Windows.Foundation.Size GetProposedDimensions(Microsoft.Terminal.Control.IControlSettings settings, UInt32 dpi);
static Windows.Foundation.Size GetProposedDimensions(IControlSettings settings, UInt32 dpi);
void UpdateSettings();
Microsoft.Terminal.Control.IControlSettings Settings { get; };
Microsoft.Terminal.Control.IControlAppearance UnfocusedAppearance;
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
event FontSizeChangedEventArgs FontSizeChanged;
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, CopyToClipboardEventArgs> CopyToClipboard;
event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
@ -30,8 +35,6 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, Object> WarningBell;
event Windows.Foundation.TypedEventHandler<Object, Object> HidePointerCursor;
event Windows.Foundation.TypedEventHandler<Object, Object> RestorePointerCursor;
event ScrollPositionChangedEventArgs ScrollPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ReadOnlyChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FocusFollowMouseRequested;
@ -41,11 +44,6 @@ namespace Microsoft.Terminal.Control
// We expose this and ConnectionState here so that it might eventually be data bound.
event Windows.Foundation.TypedEventHandler<Object, IInspectable> ConnectionStateChanged;
Microsoft.Terminal.TerminalConnection.ConnectionState ConnectionState { get; };
String Title { get; };
Boolean BracketedPasteEnabled { get; };
Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
void PasteTextFromClipboard();
void Close();
@ -54,8 +52,6 @@ namespace Microsoft.Terminal.Control
Single SnapDimensionToGrid(Boolean widthOrHeight, Single dimension);
void ScrollViewport(Int32 viewTop);
Int32 GetScrollOffset();
Int32 GetViewHeight();
void CreateSearchBoxControl();
@ -67,14 +63,6 @@ namespace Microsoft.Terminal.Control
void ToggleShaderEffects();
void SendInput(String input);
void TaskbarProgressChanged();
UInt64 TaskbarState { get; };
UInt64 TaskbarProgress { get; };
String WorkingDirectory { get; };
Windows.Foundation.IReference<Windows.UI.Color> TabColor { get; };
Boolean ReadOnly { get; };
void ToggleReadOnly();
}

View File

@ -199,7 +199,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
#pragma region IControlAccessibilityInfo
COORD TermControlAutomationPeer::GetFontSize() const
{
return _termControl->GetActualFont().GetSize();
return _termControl->GetFontSize();
}
RECT TermControlAutomationPeer::GetBounds() const

View File

@ -28,12 +28,18 @@
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="EventArgs.h">
<DependentUpon>EventArgs.idl</DependentUpon>
<ClInclude Include="ControlCore.h">
<DependentUpon>ControlCore.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ControlInteractivity.h">
<DependentUpon>ControlInteractivity.idl</DependentUpon>
</ClInclude>
<ClInclude Include="KeyChord.h">
<DependentUpon>KeyChord.idl</DependentUpon>
</ClInclude>
<ClInclude Include="EventArgs.h">
<DependentUpon>EventArgs.idl</DependentUpon>
</ClInclude>
<ClInclude Include="SearchBoxControl.h">
<DependentUpon>SearchBoxControl.xaml</DependentUpon>
</ClInclude>
@ -54,6 +60,12 @@
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ControlCore.cpp">
<DependentUpon>ControlCore.idl</DependentUpon>
</ClCompile>
<ClCompile Include="ControlInteractivity.cpp">
<DependentUpon>ControlInteractivity.idl</DependentUpon>
</ClCompile>
<ClCompile Include="EventArgs.cpp">
<DependentUpon>EventArgs.idl</DependentUpon>
</ClCompile>
@ -78,9 +90,12 @@
</ItemGroup>
<!-- ========================= idl Files ======================== -->
<ItemGroup>
<Midl Include="EventArgs.idl" />
<Midl Include="KeyChord.idl" />
<Midl Include="ControlCore.idl" />
<Midl Include="ControlInteractivity.idl" />
<Midl Include="ICoreState.idl" />
<Midl Include="IDirectKeyListener.idl" />
<Midl Include="KeyChord.idl" />
<Midl Include="EventArgs.idl" />
<Midl Include="IKeyBindings.idl" />
<Midl Include="IControlSettings.idl" />
<Midl Include="IControlAppearance.idl" />

View File

@ -20,7 +20,6 @@ namespace Microsoft::Terminal::Core
virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) = 0;
virtual bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0;
// void SendMouseEvent(uint row, uint col, KeyModifiers modifiers);
[[nodiscard]] virtual HRESULT UserResize(const COORD size) noexcept = 0;
virtual void UserScrollViewport(const int viewTop) = 0;
virtual int GetScrollOffset() = 0;

View File

@ -1121,7 +1121,7 @@ void Terminal::SetWarningBellCallback(std::function<void()> pfn) noexcept
_pfnWarningBell.swap(pfn);
}
void Terminal::SetTitleChangedCallback(std::function<void(const std::wstring_view&)> pfn) noexcept
void Terminal::SetTitleChangedCallback(std::function<void(std::wstring_view)> pfn) noexcept
{
_pfnTitleChanged.swap(pfn);
}
@ -1131,7 +1131,7 @@ void Terminal::SetTabColorChangedCallback(std::function<void(const std::optional
_pfnTabColorChanged.swap(pfn);
}
void Terminal::SetCopyToClipboardCallback(std::function<void(const std::wstring_view&)> pfn) noexcept
void Terminal::SetCopyToClipboardCallback(std::function<void(std::wstring_view)> pfn) noexcept
{
_pfnCopyToClipboard.swap(pfn);
}

View File

@ -196,9 +196,9 @@ public:
void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;
void SetWarningBellCallback(std::function<void()> pfn) noexcept;
void SetTitleChangedCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
void SetTitleChangedCallback(std::function<void(std::wstring_view)> pfn) noexcept;
void SetTabColorChangedCallback(std::function<void(const std::optional<til::color>)> pfn) noexcept;
void SetCopyToClipboardCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
void SetCopyToClipboardCallback(std::function<void(std::wstring_view)> pfn) noexcept;
void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
void SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept;
void SetBackgroundCallback(std::function<void(const til::color)> pfn) noexcept;
@ -211,6 +211,7 @@ public:
void ClearPatternTree() noexcept;
const std::optional<til::color> GetTabColor() const noexcept;
til::color GetDefaultBackground() const noexcept;
Microsoft::Console::Render::BlinkingState& GetBlinkingState() const noexcept;
@ -230,14 +231,14 @@ public:
void SetSelectionEnd(const COORD position, std::optional<SelectionExpansionMode> newExpansionMode = std::nullopt);
void SetBlockSelection(const bool isEnabled) noexcept;
const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace) const;
const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace);
#pragma endregion
private:
std::function<void(std::wstring&)> _pfnWriteInput;
std::function<void()> _pfnWarningBell;
std::function<void(const std::wstring_view&)> _pfnTitleChanged;
std::function<void(const std::wstring_view&)> _pfnCopyToClipboard;
std::function<void(std::wstring_view)> _pfnTitleChanged;
std::function<void(std::wstring_view)> _pfnCopyToClipboard;
std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
std::function<void(const til::color)> _pfnBackgroundColorChanged;
std::function<void()> _pfnCursorPositionChanged;

View File

@ -477,6 +477,11 @@ try
}
CATCH_LOG_RETURN_FALSE()
til::color Terminal::GetDefaultBackground() const noexcept
{
return _defaultBg;
}
bool Terminal::EnableWin32InputMode(const bool win32InputMode) noexcept
{
_terminalInput->ChangeWin32InputMode(win32InputMode);

View File

@ -249,8 +249,10 @@ void Terminal::ClearSelection()
// - singleLine: collapse all of the text to one line
// Return Value:
// - wstring text from buffer. If extended to multiple lines, each line is separated by \r\n
const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool singleLine) const
const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool singleLine)
{
auto lock = LockForReading();
const auto selectionRects = _GetSelectionRects();
const auto GetAttributeColors = std::bind(&Terminal::GetAttributeColors, this, std::placeholders::_1);

View File

@ -5,7 +5,7 @@
<Keyword>Win32Proj</Keyword>
<RootNamespace>ControlUnitTests</RootNamespace>
<ProjectName>UnitTests_Control</ProjectName>
<TargetName>Control.UnitTests</TargetName>
<TargetName>Control.Unit.Tests</TargetName>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<WindowsTargetPlatformMinVersion>10.0.18362.0</WindowsTargetPlatformMinVersion>
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
@ -18,11 +18,14 @@
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="MockControlSettings.h" />
<ClInclude Include="MockConnection.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<ClCompile Include="ControlCoreTests.cpp" />
<ClCompile Include="ControlInteractivityTests.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@ -42,11 +45,16 @@
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalControl\TerminalControlLib.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)\src\types\lib\types.vcxproj" />
<!-- If you don't reference these projects here, the
_ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's -->
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalControl\dll\TerminalControl.vcxproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Terminal.TerminalConnection">
<HintPath>$(OpenConsoleCommonOutDir)TerminalConnection\Microsoft.Terminal.TerminalConnection.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
</ItemGroup>
<!-- ========================= Globals ======================== -->
<!-- ====================== Compiler & Linker Flags ===================== -->
@ -76,7 +84,7 @@
</PropertyGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="$(OpenConsoleDir)src\common.build.tests.props" />

View File

@ -3,6 +3,9 @@
#include "pch.h"
#include "../TerminalControl/EventArgs.h"
#include "../TerminalControl/ControlCore.h"
#include "MockControlSettings.h"
#include "MockConnection.h"
using namespace Microsoft::Console;
using namespace WEX::Logging;
@ -17,15 +20,181 @@ namespace ControlUnitTests
class ControlCoreTests
{
BEGIN_TEST_CLASS(ControlCoreTests)
TEST_CLASS_PROPERTY(L"TestTimeout", L"0:0:10") // 10s timeout
END_TEST_CLASS()
TEST_METHOD(PlaceholderTest);
TEST_METHOD(ComPtrSettings);
TEST_METHOD(InstantiateCore);
TEST_METHOD(TestInitialize);
TEST_METHOD(TestAdjustAcrylic);
TEST_METHOD(TestFreeAfterClose);
TEST_METHOD(TestFontInitializedInCtor);
TEST_CLASS_SETUP(ModuleSetup)
{
winrt::init_apartment(winrt::apartment_type::single_threaded);
return true;
}
TEST_CLASS_CLEANUP(ClassCleanup)
{
winrt::uninit_apartment();
return true;
}
std::tuple<winrt::com_ptr<MockControlSettings>, winrt::com_ptr<MockConnection>> _createSettingsAndConnection()
{
Log::Comment(L"Create settings object");
auto settings = winrt::make_self<MockControlSettings>();
VERIFY_IS_NOT_NULL(settings);
Log::Comment(L"Create connection object");
auto conn = winrt::make_self<MockConnection>();
VERIFY_IS_NOT_NULL(conn);
return { settings, conn };
}
};
void ControlCoreTests::PlaceholderTest()
void ControlCoreTests::ComPtrSettings()
{
Log::Comment(L"This test is a placeholder while the rest of this test library is being authored.");
VERIFY_IS_TRUE(true);
Log::Comment(L"Just make sure we can instantiate a settings obj in a com_ptr");
auto settings = winrt::make_self<MockControlSettings>();
Log::Comment(L"Verify literally any setting, it doesn't matter");
VERIFY_ARE_EQUAL(DEFAULT_FOREGROUND, settings->DefaultForeground());
}
void ControlCoreTests::InstantiateCore()
{
auto [settings, conn] = _createSettingsAndConnection();
Log::Comment(L"Create ControlCore object");
auto core = winrt::make_self<Control::implementation::ControlCore>(*settings, *conn);
VERIFY_IS_NOT_NULL(core);
}
void ControlCoreTests::TestInitialize()
{
auto [settings, conn] = _createSettingsAndConnection();
Log::Comment(L"Create ControlCore object");
auto core = winrt::make_self<Control::implementation::ControlCore>(*settings, *conn);
VERIFY_IS_NOT_NULL(core);
VERIFY_IS_FALSE(core->_initializedTerminal);
// "Consolas" ends up with an actual size of 9x21 at 96DPI. So
// let's just arbitrarily start with a 270x420px (30x20 chars) window
core->Initialize(270, 420, 1.0);
VERIFY_IS_TRUE(core->_initializedTerminal);
VERIFY_ARE_EQUAL(30, core->_terminal->GetViewport().Width());
}
void ControlCoreTests::TestAdjustAcrylic()
{
auto [settings, conn] = _createSettingsAndConnection();
settings->UseAcrylic(true);
settings->TintOpacity(0.5f);
Log::Comment(L"Create ControlCore object");
auto core = winrt::make_self<Control::implementation::ControlCore>(*settings, *conn);
VERIFY_IS_NOT_NULL(core);
// A callback to make sure that we're raising TransparencyChanged events
double expectedOpacity = 0.5;
auto opacityCallback = [&](auto&&, Control::TransparencyChangedEventArgs args) mutable {
VERIFY_ARE_EQUAL(expectedOpacity, args.Opacity());
VERIFY_ARE_EQUAL(expectedOpacity, settings->TintOpacity());
VERIFY_ARE_EQUAL(expectedOpacity, core->_settings.TintOpacity());
if (expectedOpacity < 1.0)
{
VERIFY_IS_TRUE(settings->UseAcrylic());
VERIFY_IS_TRUE(core->_settings.UseAcrylic());
}
VERIFY_ARE_EQUAL(expectedOpacity < 1.0, settings->UseAcrylic());
VERIFY_ARE_EQUAL(expectedOpacity < 1.0, core->_settings.UseAcrylic());
};
core->TransparencyChanged(opacityCallback);
VERIFY_IS_FALSE(core->_initializedTerminal);
// "Cascadia Mono" ends up with an actual size of 9x19 at 96DPI. So
// let's just arbitrarily start with a 270x380px (30x20 chars) window
core->Initialize(270, 380, 1.0);
VERIFY_IS_TRUE(core->_initializedTerminal);
Log::Comment(L"Increasing opacity till fully opaque");
expectedOpacity += 0.1; // = 0.6;
core->AdjustOpacity(0.1);
expectedOpacity += 0.1; // = 0.7;
core->AdjustOpacity(0.1);
expectedOpacity += 0.1; // = 0.8;
core->AdjustOpacity(0.1);
expectedOpacity += 0.1; // = 0.9;
core->AdjustOpacity(0.1);
expectedOpacity += 0.1; // = 1.0;
// cast to float because floating point numbers are mean
VERIFY_ARE_EQUAL(1.0f, base::saturated_cast<float>(expectedOpacity));
core->AdjustOpacity(0.1);
Log::Comment(L"Increasing opacity more doesn't actually change it to be >1.0");
expectedOpacity = 1.0;
core->AdjustOpacity(0.1);
Log::Comment(L"Decrease opacity");
expectedOpacity -= 0.25; // = 0.75;
core->AdjustOpacity(-0.25);
expectedOpacity -= 0.25; // = 0.5;
core->AdjustOpacity(-0.25);
expectedOpacity -= 0.25; // = 0.25;
core->AdjustOpacity(-0.25);
expectedOpacity -= 0.25; // = 0.05;
// cast to float because floating point numbers are mean
VERIFY_ARE_EQUAL(0.0f, base::saturated_cast<float>(expectedOpacity));
core->AdjustOpacity(-0.25);
Log::Comment(L"Decreasing opacity more doesn't actually change it to be < 0");
expectedOpacity = 0.0;
core->AdjustOpacity(-0.25);
}
void ControlCoreTests::TestFreeAfterClose()
{
{
auto [settings, conn] = _createSettingsAndConnection();
Log::Comment(L"Create ControlCore object");
auto core = winrt::make_self<Control::implementation::ControlCore>(*settings, *conn);
VERIFY_IS_NOT_NULL(core);
Log::Comment(L"Close the Core, like a TermControl would");
core->Close();
}
VERIFY_IS_TRUE(true, L"Make sure that the test didn't crash when the core when out of scope");
}
void ControlCoreTests::TestFontInitializedInCtor()
{
// This is to catch a dumb programming mistake I made while working on
// the core/control split. We want the font initialized in the ctor,
// before we even get to Core::Initialize.
auto [settings, conn] = _createSettingsAndConnection();
// Make sure to use something dumb like "Impact" as a font name here so
// that you don't default to Cascadia*
settings->FontFace(L"Impact");
Log::Comment(L"Create ControlCore object");
auto core = winrt::make_self<Control::implementation::ControlCore>(*settings, *conn);
VERIFY_IS_NOT_NULL(core);
VERIFY_ARE_EQUAL(L"Impact", std::wstring_view{ core->_actualFont.GetFaceName() });
}
}

View File

@ -0,0 +1,333 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalControl/EventArgs.h"
#include "../TerminalControl/ControlInteractivity.h"
#include "MockControlSettings.h"
#include "MockConnection.h"
using namespace ::Microsoft::Console;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
using namespace winrt;
using namespace winrt::Microsoft::Terminal;
using namespace ::Microsoft::Terminal::Core;
using namespace ::Microsoft::Console::VirtualTerminal;
namespace ControlUnitTests
{
class ControlInteractivityTests
{
BEGIN_TEST_CLASS(ControlInteractivityTests)
TEST_CLASS_PROPERTY(L"TestTimeout", L"0:0:10") // 10s timeout
END_TEST_CLASS()
TEST_METHOD(TestAdjustAcrylic);
TEST_METHOD(TestScrollWithMouse);
TEST_METHOD(CreateSubsequentSelectionWithDragging);
TEST_CLASS_SETUP(ClassSetup)
{
winrt::init_apartment(winrt::apartment_type::single_threaded);
return true;
}
TEST_CLASS_CLEANUP(ClassCleanup)
{
winrt::uninit_apartment();
return true;
}
std::tuple<winrt::com_ptr<MockControlSettings>,
winrt::com_ptr<MockConnection>>
_createSettingsAndConnection()
{
Log::Comment(L"Create settings object");
auto settings = winrt::make_self<MockControlSettings>();
VERIFY_IS_NOT_NULL(settings);
Log::Comment(L"Create connection object");
auto conn = winrt::make_self<MockConnection>();
VERIFY_IS_NOT_NULL(conn);
return { settings, conn };
}
std::tuple<winrt::com_ptr<Control::implementation::ControlCore>,
winrt::com_ptr<Control::implementation::ControlInteractivity>>
_createCoreAndInteractivity(Control::IControlSettings settings,
TerminalConnection::ITerminalConnection conn)
{
Log::Comment(L"Create ControlInteractivity object");
auto interactivity = winrt::make_self<Control::implementation::ControlInteractivity>(settings, conn);
VERIFY_IS_NOT_NULL(interactivity);
auto core = interactivity->_core;
VERIFY_IS_NOT_NULL(core);
return { core, interactivity };
}
void _standardInit(winrt::com_ptr<Control::implementation::ControlCore> core,
winrt::com_ptr<Control::implementation::ControlInteractivity> interactivity)
{
// "Consolas" ends up with an actual size of 9x21 at 96DPI. So
// let's just arbitrarily start with a 270x420px (30x20 chars) window
core->Initialize(270, 420, 1.0);
VERIFY_IS_TRUE(core->_initializedTerminal);
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
interactivity->Initialize();
}
};
void ControlInteractivityTests::TestAdjustAcrylic()
{
Log::Comment(L"Test that scrolling the mouse wheel with Ctrl+Shift changes opacity");
Log::Comment(L"(This test won't log as it goes, because it does some 200 verifications.)");
WEX::TestExecution::SetVerifyOutput verifyOutputScope{ WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures };
auto [settings, conn] = _createSettingsAndConnection();
settings->UseAcrylic(true);
settings->TintOpacity(0.5f);
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
// A callback to make sure that we're raising TransparencyChanged events
double expectedOpacity = 0.5;
auto opacityCallback = [&](auto&&, Control::TransparencyChangedEventArgs args) mutable {
VERIFY_ARE_EQUAL(expectedOpacity, args.Opacity());
VERIFY_ARE_EQUAL(expectedOpacity, settings->TintOpacity());
VERIFY_ARE_EQUAL(expectedOpacity, core->_settings.TintOpacity());
if (expectedOpacity < 1.0)
{
VERIFY_IS_TRUE(settings->UseAcrylic());
VERIFY_IS_TRUE(core->_settings.UseAcrylic());
}
VERIFY_ARE_EQUAL(expectedOpacity < 1.0, settings->UseAcrylic());
VERIFY_ARE_EQUAL(expectedOpacity < 1.0, core->_settings.UseAcrylic());
};
core->TransparencyChanged(opacityCallback);
const auto modifiers = ControlKeyStates(ControlKeyStates::RightCtrlPressed | ControlKeyStates::ShiftPressed);
Log::Comment(L"Scroll in the positive direction, increasing opacity");
// Scroll more than enough times to get to 1.0 from .5.
for (int i = 0; i < 55; i++)
{
// each mouse wheel only adjusts opacity by .01
expectedOpacity += 0.01;
if (expectedOpacity >= 1.0)
{
expectedOpacity = 1.0;
}
// The mouse location and buttons don't matter here.
interactivity->MouseWheel(modifiers,
30,
til::point{ 0, 0 },
{ false, false, false });
}
Log::Comment(L"Scroll in the negative direction, decreasing opacity");
// Scroll more than enough times to get to 0.0 from 1.0
for (int i = 0; i < 105; i++)
{
// each mouse wheel only adjusts opacity by .01
expectedOpacity -= 0.01;
if (expectedOpacity <= 0.0)
{
expectedOpacity = 0.0;
}
// The mouse location and buttons don't matter here.
interactivity->MouseWheel(modifiers,
30,
til::point{ 0, 0 },
{ false, false, false });
}
}
void ControlInteractivityTests::TestScrollWithMouse()
{
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
auto [settings, conn] = _createSettingsAndConnection();
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
_standardInit(core, interactivity);
// For the sake of this test, scroll one line at a time
interactivity->_rowsToScroll = 1;
int expectedTop = 0;
int expectedViewHeight = 20;
int expectedBufferHeight = 20;
auto scrollChangedHandler = [&](auto&&, const Control::ScrollPositionChangedArgs& args) mutable {
VERIFY_ARE_EQUAL(expectedTop, args.ViewTop());
VERIFY_ARE_EQUAL(expectedViewHeight, args.ViewHeight());
VERIFY_ARE_EQUAL(expectedBufferHeight, args.BufferSize());
};
core->ScrollPositionChanged(scrollChangedHandler);
interactivity->ScrollPositionChanged(scrollChangedHandler);
for (int i = 0; i < 40; ++i)
{
Log::Comment(NoThrowString().Format(L"Writing line #%d", i));
// The \r\n in the 19th loop will cause the view to start moving
if (i >= 19)
{
expectedTop++;
expectedBufferHeight++;
}
conn->WriteInput(L"Foo\r\n");
}
// We printed that 40 times, but the final \r\n bumped the view down one MORE row.
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
VERIFY_ARE_EQUAL(20, core->ViewHeight());
VERIFY_ARE_EQUAL(41, core->BufferHeight());
Log::Comment(L"Scroll up a line");
const auto modifiers = ControlKeyStates();
expectedBufferHeight = 41;
expectedTop = 20;
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
til::point{ 0, 0 },
{ false, false, false });
Log::Comment(L"Scroll up 19 more times, to the top");
for (int i = 0; i < 20; ++i)
{
expectedTop--;
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
til::point{ 0, 0 },
{ false, false, false });
}
Log::Comment(L"Scrolling up more should do nothing");
expectedTop = 0;
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
til::point{ 0, 0 },
{ false, false, false });
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
til::point{ 0, 0 },
{ false, false, false });
Log::Comment(L"Scroll down 21 more times, to the bottom");
for (int i = 0; i < 21; ++i)
{
expectedTop++;
interactivity->MouseWheel(modifiers,
-WHEEL_DELTA,
til::point{ 0, 0 },
{ false, false, false });
}
Log::Comment(L"Scrolling up more should do nothing");
expectedTop = 21;
interactivity->MouseWheel(modifiers,
-WHEEL_DELTA,
til::point{ 0, 0 },
{ false, false, false });
interactivity->MouseWheel(modifiers,
-WHEEL_DELTA,
til::point{ 0, 0 },
{ false, false, false });
}
void ControlInteractivityTests::CreateSubsequentSelectionWithDragging()
{
// This is a test for GH#9725
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
auto [settings, conn] = _createSettingsAndConnection();
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
_standardInit(core, interactivity);
// For this test, don't use any modifiers
const auto modifiers = ControlKeyStates();
const TerminalInput::MouseButtonState leftMouseDown{ true, false, false };
const TerminalInput::MouseButtonState noMouseDown{ false, false, false };
const til::size fontSize{ 9, 21 };
Log::Comment(L"Click on the terminal");
const til::point terminalPosition0{ 0, 0 };
const til::point cursorPosition0 = terminalPosition0 * fontSize;
interactivity->PointerPressed(leftMouseDown,
WM_LBUTTONDOWN, //pointerUpdateKind
0, // timestamp
modifiers,
cursorPosition0);
Log::Comment(L"Verify that there's not yet a selection");
VERIFY_IS_FALSE(core->HasSelection());
Log::Comment(L"Drag the mouse just a little");
// move not quite a whole cell, but enough to start a selection
const til::point terminalPosition1{ 0, 0 };
const til::point cursorPosition1{ 6, 0 };
interactivity->PointerMoved(leftMouseDown,
WM_LBUTTONDOWN, //pointerUpdateKind
modifiers,
true, // focused,
cursorPosition1);
Log::Comment(L"Verify that there's one selection");
VERIFY_IS_TRUE(core->HasSelection());
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
Log::Comment(L"Drag the mouse down a whole row");
const til::point terminalPosition2{ 1, 1 };
const til::point cursorPosition2 = terminalPosition2 * fontSize;
interactivity->PointerMoved(leftMouseDown,
WM_LBUTTONDOWN, //pointerUpdateKind
modifiers,
true, // focused,
cursorPosition2);
Log::Comment(L"Verify that there's now two selections (one on each row)");
VERIFY_IS_TRUE(core->HasSelection());
VERIFY_ARE_EQUAL(2u, core->_terminal->GetSelectionRects().size());
Log::Comment(L"Release the mouse");
interactivity->PointerReleased(noMouseDown,
WM_LBUTTONUP, //pointerUpdateKind
modifiers,
cursorPosition2);
Log::Comment(L"Verify that there's still two selections");
VERIFY_IS_TRUE(core->HasSelection());
VERIFY_ARE_EQUAL(2u, core->_terminal->GetSelectionRects().size());
Log::Comment(L"click outside the current selection");
const til::point terminalPosition3{ 2, 2 };
const til::point cursorPosition3 = terminalPosition3 * fontSize;
interactivity->PointerPressed(leftMouseDown,
WM_LBUTTONDOWN, //pointerUpdateKind
0, // timestamp
modifiers,
cursorPosition3);
Log::Comment(L"Verify that there's now no selection");
VERIFY_IS_FALSE(core->HasSelection());
VERIFY_ARE_EQUAL(0u, core->_terminal->GetSelectionRects().size());
Log::Comment(L"Drag the mouse");
const til::point terminalPosition4{ 3, 2 };
const til::point cursorPosition4 = terminalPosition4 * fontSize;
interactivity->PointerMoved(leftMouseDown,
WM_LBUTTONDOWN, //pointerUpdateKind
modifiers,
true, // focused,
cursorPosition4);
Log::Comment(L"Verify that there's now one selection");
VERIFY_IS_TRUE(core->HasSelection());
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// This is literally just the EchoConnection, but we can't use the
// EchoConnection because it's in TerminalConnection.dll and loading that in
// these tests is fraught with peril. Easier just to have a local copy.
#pragma once
#include "../cascadia/inc/cppwinrt_utils.h"
namespace ControlUnitTests
{
class MockConnection : public winrt::implements<MockConnection, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection>
{
public:
MockConnection() noexcept = default;
void Start() noexcept {};
void WriteInput(winrt::hstring const& data)
{
_TerminalOutputHandlers(data);
}
void Resize(uint32_t /*rows*/, uint32_t /*columns*/) noexcept {}
void Close() noexcept {}
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept { return winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::Connected; }
WINRT_CALLBACK(TerminalOutput, winrt::Microsoft::Terminal::TerminalConnection::TerminalOutputHandler);
TYPED_EVENT(StateChanged, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection, IInspectable);
};
}

View File

@ -0,0 +1,90 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
--*/
#pragma once
#include "../inc/cppwinrt_utils.h"
#include <DefaultSettings.h>
#include <conattrs.hpp>
namespace ControlUnitTests
{
class MockControlSettings : public winrt::implements<MockControlSettings, winrt::Microsoft::Terminal::Core::ICoreSettings, winrt::Microsoft::Terminal::Control::IControlSettings, winrt::Microsoft::Terminal::Core::ICoreAppearance, winrt::Microsoft::Terminal::Control::IControlAppearance>
{
public:
MockControlSettings() = default;
// --------------------------- Core Settings ---------------------------
// All of these settings are defined in ICoreSettings.
WINRT_PROPERTY(til::color, DefaultForeground, DEFAULT_FOREGROUND);
WINRT_PROPERTY(til::color, DefaultBackground, DEFAULT_BACKGROUND);
WINRT_PROPERTY(til::color, SelectionBackground, DEFAULT_FOREGROUND);
WINRT_PROPERTY(int32_t, HistorySize, DEFAULT_HISTORY_SIZE);
WINRT_PROPERTY(int32_t, InitialRows, 30);
WINRT_PROPERTY(int32_t, InitialCols, 80);
WINRT_PROPERTY(bool, SnapOnInput, true);
WINRT_PROPERTY(bool, AltGrAliasing, true);
WINRT_PROPERTY(til::color, CursorColor, DEFAULT_CURSOR_COLOR);
WINRT_PROPERTY(winrt::Microsoft::Terminal::Core::CursorStyle, CursorShape, winrt::Microsoft::Terminal::Core::CursorStyle::Vintage);
WINRT_PROPERTY(uint32_t, CursorHeight, DEFAULT_CURSOR_HEIGHT);
WINRT_PROPERTY(winrt::hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS);
WINRT_PROPERTY(bool, CopyOnSelect, false);
WINRT_PROPERTY(bool, InputServiceWarning, true);
WINRT_PROPERTY(bool, FocusFollowMouse, false);
WINRT_PROPERTY(winrt::Windows::Foundation::IReference<winrt::Microsoft::Terminal::Core::Color>, TabColor, nullptr);
WINRT_PROPERTY(winrt::Windows::Foundation::IReference<winrt::Microsoft::Terminal::Core::Color>, StartingTabColor, nullptr);
winrt::Microsoft::Terminal::Core::ICoreAppearance UnfocusedAppearance() { return {}; };
WINRT_PROPERTY(bool, TrimBlockSelection, false);
// ------------------------ End of Core Settings -----------------------
WINRT_PROPERTY(winrt::hstring, ProfileName);
WINRT_PROPERTY(bool, UseAcrylic, false);
WINRT_PROPERTY(double, TintOpacity, 0.5);
WINRT_PROPERTY(winrt::hstring, Padding, DEFAULT_PADDING);
WINRT_PROPERTY(winrt::hstring, FontFace, L"Consolas");
WINRT_PROPERTY(int32_t, FontSize, DEFAULT_FONT_SIZE);
WINRT_PROPERTY(winrt::Windows::UI::Text::FontWeight, FontWeight);
WINRT_PROPERTY(winrt::hstring, BackgroundImage);
WINRT_PROPERTY(double, BackgroundImageOpacity, 1.0);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode, winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::HorizontalAlignment, BackgroundImageHorizontalAlignment, winrt::Windows::UI::Xaml::HorizontalAlignment::Center);
WINRT_PROPERTY(winrt::Windows::UI::Xaml::VerticalAlignment, BackgroundImageVerticalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment::Center);
WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::IKeyBindings, KeyBindings, nullptr);
WINRT_PROPERTY(winrt::hstring, Commandline);
WINRT_PROPERTY(winrt::hstring, StartingDirectory);
WINRT_PROPERTY(winrt::hstring, StartingTitle);
WINRT_PROPERTY(bool, SuppressApplicationTitle);
WINRT_PROPERTY(winrt::hstring, EnvironmentVariables);
WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible);
WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale);
WINRT_PROPERTY(bool, RetroTerminalEffect, false);
WINRT_PROPERTY(bool, ForceFullRepaintRendering, false);
WINRT_PROPERTY(bool, SoftwareRendering, false);
WINRT_PROPERTY(bool, ForceVTInput, false);
WINRT_PROPERTY(winrt::hstring, PixelShaderPath);
private:
std::array<winrt::Microsoft::Terminal::Core::Color, COLOR_TABLE_SIZE> _ColorTable;
public:
winrt::Microsoft::Terminal::Core::Color GetColorTableEntry(int32_t index) noexcept { return _ColorTable.at(index); }
std::array<winrt::Microsoft::Terminal::Core::Color, 16> ColorTable() { return _ColorTable; }
void ColorTable(std::array<winrt::Microsoft::Terminal::Core::Color, 16> /*colors*/) {}
};
}

View File

@ -40,6 +40,10 @@ Licensed under the MIT license.
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Microsoft.Terminal.Core.h>
#include <winrt/Microsoft.Terminal.Control.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"

View File

@ -16,7 +16,7 @@
<Keyword>Win32Proj</Keyword>
<RootNamespace>RemotingUnitTests</RootNamespace>
<ProjectName>UnitTests_Remoting</ProjectName>
<TargetName>Remoting.UnitTests</TargetName>
<TargetName>Remoting.Unit.Tests</TargetName>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<WindowsTargetPlatformMinVersion>10.0.18362.0</WindowsTargetPlatformMinVersion>
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>

View File

@ -2166,6 +2166,7 @@ try
if (_antialiasingMode != antialiasingMode)
{
_antialiasingMode = antialiasingMode;
_recreateDeviceRequested = true;
LOG_IF_FAILED(InvalidateAll());
}
}

View File

@ -128,6 +128,8 @@ namespace Microsoft::Console::Render
void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept;
void SetDefaultTextBackgroundOpacity(const float opacity) noexcept;
wil::unique_handle _swapChainHandle;
void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept;
protected:

View File

@ -23,8 +23,8 @@ call %TAEF% ^
%OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\Types.Unit.Tests.dll ^
%OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\til.unit.tests.dll ^
%OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\UnitTests_TerminalApp\Terminal.App.Unit.Tests.dll ^
%OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\UnitTests_Remoting\Remoting.UnitTests.dll ^
%OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\UnitTests_Control\Control.UnitTests.dll ^
%OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\UnitTests_Remoting\Remoting.Unit.Tests.dll ^
%OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\UnitTests_Control\Control.Unit.Tests.dll ^
%_TestHostAppPath%\TerminalApp.LocalTests.dll ^
%_TestHostAppPath%\SettingsModel.LocalTests.dll ^
%*

View File

@ -6,8 +6,8 @@
<test name="terminalApp" type="unit" binary="UnitTests_TerminalApp\Terminal.App.Unit.Tests.dll" />
<test name="localTerminalApp" type="unit" runInHostApp="true" binary="TerminalApp.LocalTests.dll" />
<test name="localSettingsModel" type="unit" runInHostApp="true" binary="SettingsModel.LocalTests.dll" />
<test name="unitRemoting" type="unit" binary="Remoting.UnitTests.dll" />
<test name="unitControl" type="unit" binary="Control.UnitTests.dll" />
<test name="unitRemoting" type="unit" binary="Remoting.Unit.Tests.dll" />
<test name="unitControl" type="unit" binary="Control.Unit.Tests.dll" />
<test name="interactivityWin32" type="unit" binary="Conhost.Interactivity.Win32.Unit.Tests.dll" />
<test name="terminal" type="unit" binary="ConParser.Unit.Tests.dll" />
<test name="adapter" type="unit" binary="ConAdapter.Unit.Tests.dll" />