diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index b5fa445ab77..44f1a9c2db8 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -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 _commonSetup(); winrt::com_ptr _windowProperties; + winrt::com_ptr _contentManager; }; template @@ -199,8 +201,11 @@ namespace TerminalAppLocalTests _windowProperties = winrt::make_self(); winrt::TerminalApp::WindowProperties props = *_windowProperties; - auto result = RunOnUIThread([&page, props]() { - page = winrt::make_self(props); + _contentManager = winrt::make_self(); + winrt::TerminalApp::ContentManager contentManager = *_contentManager; + + auto result = RunOnUIThread([&page, props, contentManager]() { + page = winrt::make_self(props, contentManager); VERIFY_IS_NOT_NULL(page); }); VERIFY_SUCCEEDED(result); @@ -246,9 +251,11 @@ namespace TerminalAppLocalTests _windowProperties = winrt::make_self(); winrt::TerminalApp::WindowProperties props = *_windowProperties; + _contentManager = winrt::make_self(); + 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(projectedPage)); page->_settings = initialSettings; }); diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 85033debee6..2f7a49ef0ab 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -721,7 +721,7 @@ namespace winrt::TerminalApp::implementation warnings, _settings); - auto window = winrt::make_self(*ev); + auto window = winrt::make_self(*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(); diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 0e5bfd60604..f5a206b1826 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -10,6 +10,7 @@ #include "LanguageProfileNotifier.h" #include "AppCommandlineArgs.h" #include "TerminalWindow.h" +#include "ContentManager.h" #include #include @@ -70,6 +71,8 @@ namespace winrt::TerminalApp::implementation TerminalApp::TerminalWindow CreateNewWindow(); + winrt::TerminalApp::ContentManager ContentManager(); + TerminalApp::ParseCommandlineResult GetParseCommandlineMessage(array_view args); TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SettingsLoadEventArgs); @@ -98,6 +101,8 @@ namespace winrt::TerminalApp::implementation winrt::com_ptr _languageProfileNotifier; wil::unique_folder_change_reader_nothrow _reader; + TerminalApp::ContentManager _contentManager{ winrt::make() }; + static TerminalApp::FindTargetWindowResult _doFindTargetWindow(winrt::array_view args, const Microsoft::Terminal::Settings::Model::WindowingMode& windowingBehavior); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index bc903ff150f..5eac395390f 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -34,6 +34,8 @@ namespace TerminalApp Boolean IsRunningElevated(); Boolean CanDragDrop(); + ContentManager ContentManager { get; }; + Boolean HasSettingsStartupActions(); Boolean ShouldUsePersistedLayout(); diff --git a/src/cascadia/TerminalApp/ContentManager.cpp b/src/cascadia/TerminalApp/ContentManager.cpp new file mode 100644 index 00000000000..3fb5bdb21ee --- /dev/null +++ b/src/cascadia/TerminalApp/ContentManager.cpp @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "ContentManager.h" +#include "ContentManager.g.cpp" + +#include + +#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() }) + { + const auto& contentId{ content.Id() }; + _content.erase(contentId); + } + } +} diff --git a/src/cascadia/TerminalApp/ContentManager.h b/src/cascadia/TerminalApp/ContentManager.h new file mode 100644 index 00000000000..ce7ba807045 --- /dev/null +++ b/src/cascadia/TerminalApp/ContentManager.h @@ -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 +namespace winrt::TerminalApp::implementation +{ + struct ContentManager : ContentManagerT + { + 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 _content; + + void _closedHandler(winrt::Windows::Foundation::IInspectable sender, + winrt::Windows::Foundation::IInspectable e); + }; +} diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index d72908d5b2a..bac8780ef0c 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -138,6 +138,9 @@ AppLogic.idl + + TerminalPage.idl + TerminalWindow.idl @@ -237,6 +240,9 @@ AppLogic.idl + + TerminalPage.idl + TerminalWindow.idl diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 25b89261c51..9bfaa4ef381 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -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() }, _mruTabs{ winrt::single_threaded_observable_vector() }, _startupActions{ winrt::single_threaded_vector() }, + _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) diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 1b261f71ec1..a3369f05247 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -63,7 +63,7 @@ namespace winrt::TerminalApp::implementation struct TerminalPage : TerminalPageT { 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 _ShowDialogHelper(const std::wstring_view& name); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 2fc333afe12..1e5fc3415d0 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -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; }; diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index fdee09a38c6..d1ea0cc37e2 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -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() } { @@ -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(*_WindowProperties); + _root = winrt::make_self(*_WindowProperties, _manager); _dialog = ContentDialog{}; // Pass commandline args into the TerminalPage. If we were supposed to diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 61c51844fb9..488c1d167ae 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -58,7 +58,7 @@ namespace winrt::TerminalApp::implementation struct TerminalWindow : TerminalWindowT { 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, diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index 0dc6550e7b8..452d8e00210 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -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, diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index a8df501469b..3a09dc5f747 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -126,7 +126,6 @@ namespace Microsoft.Terminal.Control String HoveredUriText { get; }; Windows.Foundation.IReference HoveredCell { get; }; - void Close(); void BlinkCursor(); Boolean IsInReadOnlyMode { get; }; Boolean CursorOn; diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index b4b6915c7db..3d38c624036 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -27,6 +27,8 @@ static constexpr unsigned int MAX_CLICK_COUNT = 3; namespace winrt::Microsoft::Terminal::Control::implementation { + std::atomic 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(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. diff --git a/src/cascadia/TerminalControl/ControlInteractivity.h b/src/cascadia/TerminalControl/ControlInteractivity.h index 15fee5574c5..b22c02bcc94 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.h +++ b/src/cascadia/TerminalControl/ControlInteractivity.h @@ -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> _lastHoveredInterval{ std::nullopt }; + uint64_t _id; + static std::atomic _nextId; + unsigned int _numberOfClicks(Core::Point clickPos, Timestamp clickTime); void _updateSystemParameterSettings() noexcept; diff --git a/src/cascadia/TerminalControl/ControlInteractivity.idl b/src/cascadia/TerminalControl/ControlInteractivity.idl index 6906999aa38..81d5e082a0a 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.idl +++ b/src/cascadia/TerminalControl/ControlInteractivity.idl @@ -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 formats); @@ -65,6 +69,8 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler ScrollPositionChanged; event Windows.Foundation.TypedEventHandler PasteFromClipboard; + event Windows.Foundation.TypedEventHandler Closed; + // Used to communicate to the TermControl, but not necessarily higher up in the stack event Windows.Foundation.TypedEventHandler ContextMenuRequested; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 40081c30b62..08cf3cadc97 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -50,6 +50,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation TermControl::TermControl(IControlSettings settings, Control::IControlAppearance unfocusedAppearance, TerminalConnection::ITerminalConnection connection) : + TermControl{ winrt::make(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(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(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() }; - 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() }) + { + 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 }; diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index e8f0bb8734d..a6bfb4e4d3e 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -25,6 +25,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation { struct TermControl : TermControlT { + 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{}; }; } diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 3c9f794f42c..4feecb33918 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -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); diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index 1e9ccb1a0d0..fd8aff723bc 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -102,6 +102,27 @@ public: winrt::event_token name(const Windows::Foundation::TypedEventHandler& 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.