Introduce a ContentManager helper (#14851)

## Summary

_Thus we come to the introduction of a new servant, the
`ContentManager`, a singular entity that serves at the behest of the
`emperor`. It is its charge to keep track of all `TermControl` instances
created by the windows, for each window must seek its blessing before
calling forth such an instance._
_With the aid of the `ContentManager`, the `TermControl` shall now be
traced by the hand of fate through the use of unique identifying marks,
known as `GUID`s. Yet, its purpose remains yet unknown, for it is merely
a waypoint upon the journey yet to come._
_This act of bridging also brings a change to the handling of events
within the `TermControl`. This change shall see the addition of a
revoker, similar to the manner in which the `AppHost` hath employed it,
to the `TermControl`. Additionally, there is a new layer of indirection
between the `ControlCore` and the `App` layer, making ready for the day
when the `TermControl` may be repositioned and re-parented with ease._
_Consider this but a trivial act, a mere shadow of things yet to come,
for its impact shall be felt but briefly, like the passing of a gentle
breeze._

Related to #5000
Related to #1256

# Detailed description

This PR is another small bridge PR between the big work in #14843, and
the PR that will enable panes to move between windows.

This introduces a new class, called `ContentManager`. This is a global
singleton object, owned by the emperor. Whenever a window wants to
instantiate a new `TermControl`, it must ask the ContentManager to give
it one. This allows the ContentManager to track each "content" by GUID.
That's it. We don't do anything with them in this PR by itself, we just
track them.

This also includes a small change to the way TermControl events are
handled. It adds an `AppHost`-like revoker struct, and weak_ref's all
the handlers. We also add a layer of indirection between the
ControlCore's raising of events and the App layer's handling. This will
make reparenting content easier in the future.

This is a pretty trivial change which shouldn't have any major side
effects. Consider it exposition of the things to come. It's
intentionally small to try and keep the reviews more managable.
This commit is contained in:
Mike Griese 2023-03-22 12:11:44 -05:00 committed by GitHub
parent e0046a4cca
commit f3a722e0e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 297 additions and 46 deletions

View File

@ -10,6 +10,7 @@
#include "../TerminalApp/ShortcutActionDispatch.h"
#include "../TerminalApp/TerminalTab.h"
#include "../TerminalApp/CommandPalette.h"
#include "../TerminalApp/ContentManager.h"
#include "CppWinrtTailored.h"
using namespace Microsoft::Console;
@ -112,6 +113,7 @@ namespace TerminalAppLocalTests
CascadiaSettings initialSettings);
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> _commonSetup();
winrt::com_ptr<winrt::TerminalApp::implementation::WindowProperties> _windowProperties;
winrt::com_ptr<winrt::TerminalApp::implementation::ContentManager> _contentManager;
};
template<typename TFunction>
@ -199,8 +201,11 @@ namespace TerminalAppLocalTests
_windowProperties = winrt::make_self<winrt::TerminalApp::implementation::WindowProperties>();
winrt::TerminalApp::WindowProperties props = *_windowProperties;
auto result = RunOnUIThread([&page, props]() {
page = winrt::make_self<winrt::TerminalApp::implementation::TerminalPage>(props);
_contentManager = winrt::make_self<winrt::TerminalApp::implementation::ContentManager>();
winrt::TerminalApp::ContentManager contentManager = *_contentManager;
auto result = RunOnUIThread([&page, props, contentManager]() {
page = winrt::make_self<winrt::TerminalApp::implementation::TerminalPage>(props, contentManager);
VERIFY_IS_NOT_NULL(page);
});
VERIFY_SUCCEEDED(result);
@ -246,9 +251,11 @@ namespace TerminalAppLocalTests
_windowProperties = winrt::make_self<winrt::TerminalApp::implementation::WindowProperties>();
winrt::TerminalApp::WindowProperties props = *_windowProperties;
_contentManager = winrt::make_self<winrt::TerminalApp::implementation::ContentManager>();
winrt::TerminalApp::ContentManager contentManager = *_contentManager;
Log::Comment(NoThrowString().Format(L"Construct the TerminalPage"));
auto result = RunOnUIThread([&projectedPage, &page, initialSettings, props]() {
projectedPage = winrt::TerminalApp::TerminalPage(props);
auto result = RunOnUIThread([&projectedPage, &page, initialSettings, props, contentManager]() {
projectedPage = winrt::TerminalApp::TerminalPage(props, contentManager);
page.copy_from(winrt::get_self<winrt::TerminalApp::implementation::TerminalPage>(projectedPage));
page->_settings = initialSettings;
});

View File

@ -721,7 +721,7 @@ namespace winrt::TerminalApp::implementation
warnings,
_settings);
auto window = winrt::make_self<implementation::TerminalWindow>(*ev);
auto window = winrt::make_self<implementation::TerminalWindow>(*ev, _contentManager);
this->SettingsChanged({ window->get_weak(), &implementation::TerminalWindow::UpdateSettingsHandler });
if (_hasSettingsStartupActions)
@ -731,6 +731,11 @@ namespace winrt::TerminalApp::implementation
return *window;
}
winrt::TerminalApp::ContentManager AppLogic::ContentManager()
{
return _contentManager;
}
bool AppLogic::ShouldUsePersistedLayout() const
{
return _settings.GlobalSettings().ShouldUsePersistedLayout();

View File

@ -10,6 +10,7 @@
#include "LanguageProfileNotifier.h"
#include "AppCommandlineArgs.h"
#include "TerminalWindow.h"
#include "ContentManager.h"
#include <inc/cppwinrt_utils.h>
#include <ThrottledFunc.h>
@ -70,6 +71,8 @@ namespace winrt::TerminalApp::implementation
TerminalApp::TerminalWindow CreateNewWindow();
winrt::TerminalApp::ContentManager ContentManager();
TerminalApp::ParseCommandlineResult GetParseCommandlineMessage(array_view<const winrt::hstring> args);
TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SettingsLoadEventArgs);
@ -98,6 +101,8 @@ namespace winrt::TerminalApp::implementation
winrt::com_ptr<LanguageProfileNotifier> _languageProfileNotifier;
wil::unique_folder_change_reader_nothrow _reader;
TerminalApp::ContentManager _contentManager{ winrt::make<implementation::ContentManager>() };
static TerminalApp::FindTargetWindowResult _doFindTargetWindow(winrt::array_view<const hstring> args,
const Microsoft::Terminal::Settings::Model::WindowingMode& windowingBehavior);

View File

@ -34,6 +34,8 @@ namespace TerminalApp
Boolean IsRunningElevated();
Boolean CanDragDrop();
ContentManager ContentManager { get; };
Boolean HasSettingsStartupActions();
Boolean ShouldUsePersistedLayout();

View File

@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ContentManager.h"
#include "ContentManager.g.cpp"
#include <wil/token_helpers.h>
#include "../../types/inc/utils.hpp"
using namespace winrt::Windows::ApplicationModel;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::System;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Control;
using namespace winrt::Microsoft::Terminal::Settings::Model;
namespace winrt::TerminalApp::implementation
{
ControlInteractivity ContentManager::CreateCore(const Microsoft::Terminal::Control::IControlSettings& settings,
const IControlAppearance& unfocusedAppearance,
const TerminalConnection::ITerminalConnection& connection)
{
ControlInteractivity content{ settings, unfocusedAppearance, connection };
content.Closed({ get_weak(), &ContentManager::_closedHandler });
_content.emplace(content.Id(), content);
return content;
}
ControlInteractivity ContentManager::TryLookupCore(uint64_t id)
{
const auto it = _content.find(id);
return it != _content.end() ? it->second : ControlInteractivity{ nullptr };
}
void ContentManager::_closedHandler(winrt::Windows::Foundation::IInspectable sender,
winrt::Windows::Foundation::IInspectable e)
{
if (const auto& content{ sender.try_as<winrt::Microsoft::Terminal::Control::ControlInteractivity>() })
{
const auto& contentId{ content.Id() };
_content.erase(contentId);
}
}
}

View File

@ -0,0 +1,44 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Class Name:
- ContentManager.h
Abstract:
- This is a helper class for tracking all of the terminal "content" instances of
the Terminal. These are all the ControlInteractivity & ControlCore's of each
of our TermControls. These are each assigned a GUID on creation, and stored in
a map for later lookup.
- This is used to enable moving panes between windows. TermControl's are not
thread-agile, so they cannot be reused on other threads. However, the content
is. This helper, which exists as a singleton across all the threads in the
Terminal app, allows each thread to create content, assign it to a
TermControl, detach it from that control, and reattach to new controls on
other threads.
- When you want to create a new TermControl, call CreateCore to instantiate a
new content with a GUID for later reparenting.
--*/
#pragma once
#include "ContentManager.g.h"
#include <inc/cppwinrt_utils.h>
namespace winrt::TerminalApp::implementation
{
struct ContentManager : ContentManagerT<ContentManager>
{
public:
ContentManager() = default;
Microsoft::Terminal::Control::ControlInteractivity CreateCore(const Microsoft::Terminal::Control::IControlSettings& settings,
const Microsoft::Terminal::Control::IControlAppearance& unfocusedAppearance,
const Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection);
Microsoft::Terminal::Control::ControlInteractivity TryLookupCore(uint64_t id);
private:
std::unordered_map<uint64_t, Microsoft::Terminal::Control::ControlInteractivity> _content;
void _closedHandler(winrt::Windows::Foundation::IInspectable sender,
winrt::Windows::Foundation::IInspectable e);
};
}

View File

@ -138,6 +138,9 @@
<ClInclude Include="AppLogic.h">
<DependentUpon>AppLogic.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ContentManager.h">
<DependentUpon>TerminalPage.idl</DependentUpon>
</ClInclude>
<ClInclude Include="TerminalWindow.h">
<DependentUpon>TerminalWindow.idl</DependentUpon>
</ClInclude>
@ -237,6 +240,9 @@
<ClCompile Include="AppLogic.cpp">
<DependentUpon>AppLogic.idl</DependentUpon>
</ClCompile>
<ClCompile Include="ContentManager.cpp">
<DependentUpon>TerminalPage.idl</DependentUpon>
</ClCompile>
<ClCompile Include="TerminalWindow.cpp">
<DependentUpon>TerminalWindow.idl</DependentUpon>
</ClCompile>

View File

@ -54,10 +54,11 @@ namespace winrt
namespace winrt::TerminalApp::implementation
{
TerminalPage::TerminalPage(TerminalApp::WindowProperties properties) :
TerminalPage::TerminalPage(TerminalApp::WindowProperties properties, const TerminalApp::ContentManager& manager) :
_tabs{ winrt::single_threaded_observable_vector<TerminalApp::TabBase>() },
_mruTabs{ winrt::single_threaded_observable_vector<TerminalApp::TabBase>() },
_startupActions{ winrt::single_threaded_vector<ActionAndArgs>() },
_manager{ manager },
_hostingHwnd{},
_WindowProperties{ std::move(properties) }
{
@ -2593,7 +2594,10 @@ namespace winrt::TerminalApp::implementation
// Do any initialization that needs to apply to _every_ TermControl we
// create here.
// TermControl will copy the settings out of the settings passed to it.
TermControl term{ settings.DefaultSettings(), settings.UnfocusedSettings(), connection };
const auto content = _manager.CreateCore(settings.DefaultSettings(), settings.UnfocusedSettings(), connection);
TermControl term{ content };
// GH#12515: ConPTY assumes it's hidden at the start. If we're not, let it know now.
if (_visible)

View File

@ -63,7 +63,7 @@ namespace winrt::TerminalApp::implementation
struct TerminalPage : TerminalPageT<TerminalPage>
{
public:
TerminalPage(TerminalApp::WindowProperties properties);
TerminalPage(TerminalApp::WindowProperties properties, const TerminalApp::ContentManager& manager);
// This implements shobjidl's IInitializeWithWindow, but due to a XAML Compiler bug we cannot
// put it in our inheritance graph. https://github.com/microsoft/microsoft-ui-xaml/issues/3331
@ -224,9 +224,10 @@ namespace winrt::TerminalApp::implementation
bool _renamerPressedEnter{ false };
TerminalApp::WindowProperties _WindowProperties{ nullptr };
PaneResources _paneResources;
TerminalApp::ContentManager _manager{ nullptr };
winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker;
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowDialogHelper(const std::wstring_view& name);

View File

@ -5,6 +5,13 @@ import "IDirectKeyListener.idl";
namespace TerminalApp
{
[default_interface] runtimeclass ContentManager
{
Microsoft.Terminal.Control.ControlInteractivity CreateCore(Microsoft.Terminal.Control.IControlSettings settings,
Microsoft.Terminal.Control.IControlAppearance unfocusedAppearance,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
Microsoft.Terminal.Control.ControlInteractivity TryLookupCore(UInt64 id);
}
[default_interface] runtimeclass LastTabClosedEventArgs
{
@ -33,7 +40,7 @@ namespace TerminalApp
[default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged, IDirectKeyListener
{
TerminalPage(WindowProperties properties);
TerminalPage(WindowProperties properties, ContentManager manager);
// XAML bound properties
String ApplicationDisplayName { get; };

View File

@ -118,8 +118,10 @@ static Documents::Run _BuildErrorRun(const winrt::hstring& text, const ResourceD
namespace winrt::TerminalApp::implementation
{
TerminalWindow::TerminalWindow(const TerminalApp::SettingsLoadEventArgs& settingsLoadedResult) :
TerminalWindow::TerminalWindow(const TerminalApp::SettingsLoadEventArgs& settingsLoadedResult,
const TerminalApp::ContentManager& manager) :
_settings{ settingsLoadedResult.NewSettings() },
_manager{ manager },
_initialLoadResult{ settingsLoadedResult },
_WindowProperties{ winrt::make_self<TerminalApp::implementation::WindowProperties>() }
{
@ -138,7 +140,7 @@ namespace winrt::TerminalApp::implementation
HRESULT TerminalWindow::Initialize(HWND hwnd)
{
// Now that we know we can do XAML, build our page.
_root = winrt::make_self<TerminalPage>(*_WindowProperties);
_root = winrt::make_self<TerminalPage>(*_WindowProperties, _manager);
_dialog = ContentDialog{};
// Pass commandline args into the TerminalPage. If we were supposed to

View File

@ -58,7 +58,7 @@ namespace winrt::TerminalApp::implementation
struct TerminalWindow : TerminalWindowT<TerminalWindow, IInitializeWithWindow>
{
public:
TerminalWindow(const TerminalApp::SettingsLoadEventArgs& settingsLoadedResult);
TerminalWindow(const TerminalApp::SettingsLoadEventArgs& settingsLoadedResult, const TerminalApp::ContentManager& manager);
~TerminalWindow() = default;
STDMETHODIMP Initialize(HWND hwnd);
@ -177,6 +177,8 @@ namespace winrt::TerminalApp::implementation
Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
TerminalApp::SettingsLoadEventArgs _initialLoadResult{ nullptr };
TerminalApp::ContentManager _manager{ nullptr };
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey,
const winrt::hstring& contentKey,
HRESULT settingsLoadedResult,

View File

@ -42,7 +42,7 @@ namespace TerminalApp
// information.
[default_interface] runtimeclass TerminalWindow : IDirectKeyListener, IDialogPresenter, Windows.UI.Xaml.Data.INotifyPropertyChanged
{
TerminalWindow(SettingsLoadEventArgs result);
TerminalWindow(SettingsLoadEventArgs result, ContentManager manager);
// For your own sanity, it's better to do setup outside the ctor.
// If you do any setup in the ctor that ends up throwing an exception,

View File

@ -126,7 +126,6 @@ namespace Microsoft.Terminal.Control
String HoveredUriText { get; };
Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> HoveredCell { get; };
void Close();
void BlinkCursor();
Boolean IsInReadOnlyMode { get; };
Boolean CursorOn;

View File

@ -27,6 +27,8 @@ static constexpr unsigned int MAX_CLICK_COUNT = 3;
namespace winrt::Microsoft::Terminal::Control::implementation
{
std::atomic<uint64_t> ControlInteractivity::_nextId{ 1 };
static constexpr TerminalInput::MouseButtonState toInternalMouseState(const Control::MouseButtonState& state)
{
return TerminalInput::MouseButtonState{
@ -44,9 +46,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_lastMouseClickPos{},
_selectionNeedsToBeCopied{ false }
{
_id = _nextId.fetch_add(1, std::memory_order_relaxed);
_core = winrt::make_self<ControlCore>(settings, unfocusedAppearance, connection);
}
uint64_t ControlInteractivity::Id()
{
return _id;
}
// Method Description:
// - Updates our internal settings. These settings should be
// interactivity-specific. Right now, we primarily update _rowsToScroll
@ -73,6 +82,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return *_core;
}
void ControlInteractivity::Close()
{
_ClosedHandlers(*this, nullptr);
if (_core)
{
_core->Close();
}
}
// Method Description:
// - Returns the number of clicks that occurred (double and triple click support).
// Every call to this function registers a click.

View File

@ -44,6 +44,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void Initialize();
Control::ControlCore Core();
void Close();
Control::InteractivityAutomationPeer OnCreateAutomationPeer();
::Microsoft::Console::Render::IRenderData* GetRenderData() const;
@ -85,11 +87,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void SetEndSelectionPoint(const Core::Point pixelPosition);
bool ManglePathsForWsl();
uint64_t Id();
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
TYPED_EVENT(ContextMenuRequested, IInspectable, Control::ContextMenuRequestedEventArgs);
TYPED_EVENT(Closed, IInspectable, IInspectable);
private:
// NOTE: _uiaEngine must be ordered before _core.
//
@ -130,6 +136,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> _lastHoveredInterval{ std::nullopt };
uint64_t _id;
static std::atomic<uint64_t> _nextId;
unsigned int _numberOfClicks(Core::Point clickPos, Timestamp clickTime);
void _updateSystemParameterSettings() noexcept;

View File

@ -23,6 +23,10 @@ namespace Microsoft.Terminal.Control
void GotFocus();
void LostFocus();
UInt64 Id { get; };
void Close();
InteractivityAutomationPeer OnCreateAutomationPeer();
Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
@ -65,6 +69,8 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;
event Windows.Foundation.TypedEventHandler<Object, Object> Closed;
// Used to communicate to the TermControl, but not necessarily higher up in the stack
event Windows.Foundation.TypedEventHandler<Object, ContextMenuRequestedEventArgs> ContextMenuRequested;

View File

@ -50,6 +50,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TermControl::TermControl(IControlSettings settings,
Control::IControlAppearance unfocusedAppearance,
TerminalConnection::ITerminalConnection connection) :
TermControl{ winrt::make<implementation::ControlInteractivity>(settings, unfocusedAppearance, connection) }
{
}
TermControl::TermControl(Control::ControlInteractivity content) :
_interactivity{ content },
_isInternalScrollBarUpdate{ false },
_autoScrollVelocity{ 0 },
_autoScrollingPointerPoint{ std::nullopt },
@ -61,32 +67,42 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
InitializeComponent();
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, unfocusedAppearance, connection);
_core = _interactivity.Core();
// These events might all be triggered by the connection, but that
// should be drained and closed before we complete destruction. So these
// are safe.
_core.ScrollPositionChanged({ this, &TermControl::_ScrollPositionChanged });
_core.WarningBell({ this, &TermControl::_coreWarningBell });
_core.CursorPositionChanged({ this, &TermControl::_CursorPositionChanged });
_revokers.coreScrollPositionChanged = _core.ScrollPositionChanged(winrt::auto_revoke, { get_weak(), &TermControl::_ScrollPositionChanged });
_revokers.WarningBell = _core.WarningBell(winrt::auto_revoke, { get_weak(), &TermControl::_coreWarningBell });
_revokers.CursorPositionChanged = _core.CursorPositionChanged(winrt::auto_revoke, { get_weak(), &TermControl::_CursorPositionChanged });
// This event is specifically triggered by the renderer thread, a BG thread. Use a weak ref here.
_core.RendererEnteredErrorState({ get_weak(), &TermControl::_RendererEnteredErrorState });
_revokers.RendererEnteredErrorState = _core.RendererEnteredErrorState(winrt::auto_revoke, { get_weak(), &TermControl::_RendererEnteredErrorState });
// These callbacks can only really be triggered by UI interactions. So
// they don't need weak refs - they can't be triggered unless we're
// alive.
_core.BackgroundColorChanged({ this, &TermControl::_coreBackgroundColorChanged });
_core.FontSizeChanged({ this, &TermControl::_coreFontSizeChanged });
_core.TransparencyChanged({ this, &TermControl::_coreTransparencyChanged });
_core.RaiseNotice({ this, &TermControl::_coreRaisedNotice });
_core.HoveredHyperlinkChanged({ this, &TermControl::_hoveredHyperlinkChanged });
_core.FoundMatch({ this, &TermControl::_coreFoundMatch });
_core.UpdateSelectionMarkers({ this, &TermControl::_updateSelectionMarkers });
_core.OpenHyperlink({ this, &TermControl::_HyperlinkHandler });
_interactivity.OpenHyperlink({ this, &TermControl::_HyperlinkHandler });
_interactivity.ScrollPositionChanged({ this, &TermControl::_ScrollPositionChanged });
_revokers.BackgroundColorChanged = _core.BackgroundColorChanged(winrt::auto_revoke, { get_weak(), &TermControl::_coreBackgroundColorChanged });
_revokers.FontSizeChanged = _core.FontSizeChanged(winrt::auto_revoke, { get_weak(), &TermControl::_coreFontSizeChanged });
_revokers.TransparencyChanged = _core.TransparencyChanged(winrt::auto_revoke, { get_weak(), &TermControl::_coreTransparencyChanged });
_revokers.RaiseNotice = _core.RaiseNotice(winrt::auto_revoke, { get_weak(), &TermControl::_coreRaisedNotice });
_revokers.HoveredHyperlinkChanged = _core.HoveredHyperlinkChanged(winrt::auto_revoke, { get_weak(), &TermControl::_hoveredHyperlinkChanged });
_revokers.FoundMatch = _core.FoundMatch(winrt::auto_revoke, { get_weak(), &TermControl::_coreFoundMatch });
_revokers.UpdateSelectionMarkers = _core.UpdateSelectionMarkers(winrt::auto_revoke, { get_weak(), &TermControl::_updateSelectionMarkers });
_revokers.coreOpenHyperlink = _core.OpenHyperlink(winrt::auto_revoke, { get_weak(), &TermControl::_HyperlinkHandler });
_revokers.interactivityOpenHyperlink = _interactivity.OpenHyperlink(winrt::auto_revoke, { get_weak(), &TermControl::_HyperlinkHandler });
_revokers.interactivityScrollPositionChanged = _interactivity.ScrollPositionChanged(winrt::auto_revoke, { get_weak(), &TermControl::_ScrollPositionChanged });
// "Bubbled" events - ones we want to handle, by raising our own event.
_revokers.CopyToClipboard = _core.CopyToClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCopyToClipboard });
_revokers.TitleChanged = _core.TitleChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleTitleChanged });
_revokers.TabColorChanged = _core.TabColorChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleTabColorChanged });
_revokers.TaskbarProgressChanged = _core.TaskbarProgressChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleSetTaskbarProgress });
_revokers.ConnectionStateChanged = _core.ConnectionStateChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleConnectionStateChanged });
_revokers.ShowWindowChanged = _core.ShowWindowChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleShowWindowChanged });
_revokers.CloseTerminalRequested = _core.CloseTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleCloseTerminalRequested });
_revokers.PasteFromClipboard = _interactivity.PasteFromClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubblePasteFromClipboard });
_interactivity.ContextMenuRequested({ this, &TermControl::_contextMenuHandler });
@ -134,7 +150,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
_autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll });
_autoScrollTimer.Tick({ get_weak(), &TermControl::_UpdateAutoScroll });
_ApplyUISettings();
@ -888,7 +904,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// after Enable, then it'll be possible to paint the frame once
// _before_ the warning handler is set up, and then warnings from
// the first paint will be ignored!
_core.RendererWarning({ get_weak(), &TermControl::_RendererWarning });
_revokers.RendererWarning = _core.RendererWarning(winrt::auto_revoke, { get_weak(), &TermControl::_RendererWarning });
const auto coreInitialized = _core.Initialize(panelWidth,
panelHeight,
@ -899,7 +915,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
_interactivity.Initialize();
_core.SwapChainChanged({ get_weak(), &TermControl::RenderEngineSwapChainChanged });
_revokers.SwapChainChanged = _core.SwapChainChanged(winrt::auto_revoke, { get_weak(), &TermControl::RenderEngineSwapChainChanged });
_core.EnablePainting();
auto bufferHeight = _core.BufferHeight();
@ -2054,11 +2070,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_RestorePointerCursorHandlers(*this, nullptr);
_revokers = {};
// Disconnect the TSF input control so it doesn't receive EditContext events.
TSFInputControl().Close();
_autoScrollTimer.Stop();
_core.Close();
_interactivity.Close();
}
}
@ -2995,9 +3013,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Ensure the marker is oriented properly
// (i.e. if start is at the beginning of the buffer, it should be flipped)
auto transform{ marker.RenderTransform().as<Windows::UI::Xaml::Media::ScaleTransform>() };
transform.ScaleX(std::abs(transform.ScaleX()) * (flipMarker ? -1.0 : 1.0));
marker.RenderTransform(transform);
//
// Note - This RenderTransform might not be a
// ScaleTransform, if we haven't had a _coreFontSizeChanged
// handled yet, because that's the first place we set the
// RenderTransform
if (const auto& transform{ marker.RenderTransform().try_as<Windows::UI::Xaml::Media::ScaleTransform>() })
{
transform.ScaleX(std::abs(transform.ScaleX()) * (flipMarker ? -1.0 : 1.0));
marker.RenderTransform(transform);
}
// Compute the location of the top left corner of the cell in DIPS
auto terminalPos{ targetEnd ? markerData.EndPos : markerData.StartPos };

View File

@ -25,6 +25,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
struct TermControl : TermControlT<TermControl>
{
TermControl(Control::ControlInteractivity content);
TermControl(IControlSettings settings,
Control::IControlAppearance unfocusedAppearance,
TerminalConnection::ITerminalConnection connection);
@ -137,20 +139,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void AdjustOpacity(const double opacity, const bool relative);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
// -------------------------------- WinRT Events ---------------------------------
// clang-format off
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
PROJECTED_FORWARDED_TYPED_EVENT(CopyToClipboard, IInspectable, Control::CopyToClipboardEventArgs, _core, CopyToClipboard);
PROJECTED_FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs, _core, TitleChanged);
PROJECTED_FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable, _core, TabColorChanged);
PROJECTED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable, _core, TaskbarProgressChanged);
PROJECTED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable, _core, ConnectionStateChanged);
PROJECTED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs, _core, ShowWindowChanged);
PROJECTED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable, _core, CloseTerminalRequested);
// UNDER NO CIRCUMSTANCES SHOULD YOU ADD A (PROJECTED_)FORWARDED_TYPED_EVENT HERE
// Those attach the handler to the core directly, and will explode if
// the core ever gets detached & reattached to another window.
BUBBLED_FORWARDED_TYPED_EVENT(CopyToClipboard, IInspectable, Control::CopyToClipboardEventArgs);
BUBBLED_FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs);
BUBBLED_FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable);
BUBBLED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable);
BUBBLED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable);
BUBBLED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs);
BUBBLED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable);
PROJECTED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs, _interactivity, PasteFromClipboard);
BUBBLED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(RaiseNotice, IInspectable, Control::NoticeEventArgs);
@ -327,6 +331,36 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _PasteCommandHandler(const IInspectable& sender, const IInspectable& args);
void _CopyCommandHandler(const IInspectable& sender, const IInspectable& args);
void _SearchCommandHandler(const IInspectable& sender, const IInspectable& args);
struct Revokers
{
Control::ControlCore::ScrollPositionChanged_revoker coreScrollPositionChanged;
Control::ControlCore::WarningBell_revoker WarningBell;
Control::ControlCore::CursorPositionChanged_revoker CursorPositionChanged;
Control::ControlCore::RendererEnteredErrorState_revoker RendererEnteredErrorState;
Control::ControlCore::BackgroundColorChanged_revoker BackgroundColorChanged;
Control::ControlCore::FontSizeChanged_revoker FontSizeChanged;
Control::ControlCore::TransparencyChanged_revoker TransparencyChanged;
Control::ControlCore::RaiseNotice_revoker RaiseNotice;
Control::ControlCore::HoveredHyperlinkChanged_revoker HoveredHyperlinkChanged;
Control::ControlCore::FoundMatch_revoker FoundMatch;
Control::ControlCore::UpdateSelectionMarkers_revoker UpdateSelectionMarkers;
Control::ControlCore::OpenHyperlink_revoker coreOpenHyperlink;
Control::ControlCore::CopyToClipboard_revoker CopyToClipboard;
Control::ControlCore::TitleChanged_revoker TitleChanged;
Control::ControlCore::TabColorChanged_revoker TabColorChanged;
Control::ControlCore::TaskbarProgressChanged_revoker TaskbarProgressChanged;
Control::ControlCore::ConnectionStateChanged_revoker ConnectionStateChanged;
Control::ControlCore::ShowWindowChanged_revoker ShowWindowChanged;
Control::ControlCore::CloseTerminalRequested_revoker CloseTerminalRequested;
// These are set up in _InitializeTerminal
Control::ControlCore::RendererWarning_revoker RendererWarning;
Control::ControlCore::SwapChainChanged_revoker SwapChainChanged;
Control::ControlInteractivity::OpenHyperlink_revoker interactivityOpenHyperlink;
Control::ControlInteractivity::ScrollPositionChanged_revoker interactivityScrollPositionChanged;
Control::ControlInteractivity::PasteFromClipboard_revoker PasteFromClipboard;
} _revokers{};
};
}

View File

@ -3,6 +3,7 @@
import "IMouseWheelListener.idl";
import "IControlSettings.idl";
import "ControlInteractivity.idl";
import "IDirectKeyListener.idl";
import "EventArgs.idl";
import "ICoreState.idl";
@ -17,6 +18,8 @@ namespace Microsoft.Terminal.Control
ICoreState,
Windows.UI.Xaml.Data.INotifyPropertyChanged
{
TermControl(ControlInteractivity content);
TermControl(IControlSettings settings,
IControlAppearance unfocusedAppearance,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);

View File

@ -102,6 +102,27 @@ public:
winrt::event_token name(const Windows::Foundation::TypedEventHandler<sender, args>& h) { return handler.handlerName(h); } \
void name(const winrt::event_token& token) noexcept { handler.handlerName(token); }
// This is a bit like *FORWARDED_TYPED_EVENT. When you use a forwarded event,
// the handler gets added to the object that's raising the event. For example,
// the TerminalPage might be the handler for the TermControl's
// BackgroundColorChanged event, which is actually implemented by the
// ControlCore. So when Core raises an event, it immediately calls the handler
// on the Page.
//
// Instead, the BUBBLED event introduces an indirection layer. In the above
// example, the Core would raise the event, but now the Control would handle it,
// and raise an event with each of its own handlers.
//
// This allows us to detach the core from the control safely, without needing to
// re-wire all the event handlers from page->control again.
//
// Implement like:
//
// _core.TitleChanged({ get_weak(), &TermControl::_bubbleTitleChanged });
#define BUBBLED_FORWARDED_TYPED_EVENT(name, sender, args) \
TYPED_EVENT(name, sender, args) \
void _bubble##name(const sender& s, const args& a) { _##name##Handlers(s, a); }
// Use this macro to quick implement both the getter and setter for a property.
// This should only be used for simple types where there's no logic in the
// getter/setter beyond just accessing/updating the value.