terminal/src/cascadia/TerminalApp/TerminalPage.h
Mike Griese 8910a16fd0
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.
2021-04-27 15:50:45 +00:00

360 lines
19 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "TerminalPage.g.h"
#include "TerminalTab.h"
#include "AppKeyBindings.h"
#include "AppCommandlineArgs.h"
#include "RenameWindowRequestedArgs.g.h"
#include "Toast.h"
#define DECLARE_ACTION_HANDLER(action) void _Handle##action(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
static constexpr uint32_t DefaultRowsToScroll{ 3 };
static constexpr std::wstring_view TabletInputServiceKey{ L"TabletInputService" };
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class TabTests;
class SettingsTests;
};
namespace winrt::TerminalApp::implementation
{
enum StartupState : int
{
NotInitialized = 0,
InStartup = 1,
Initialized = 2
};
enum ScrollDirection : int
{
ScrollUp = 0,
ScrollDown = 1
};
struct RenameWindowRequestedArgs : RenameWindowRequestedArgsT<RenameWindowRequestedArgs>
{
WINRT_PROPERTY(winrt::hstring, ProposedName);
public:
RenameWindowRequestedArgs(const winrt::hstring& name) :
_ProposedName{ name } {};
};
struct TerminalPage : TerminalPageT<TerminalPage>
{
public:
TerminalPage();
// 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
STDMETHODIMP Initialize(HWND hwnd);
winrt::fire_and_forget SetSettings(Microsoft::Terminal::Settings::Model::CascadiaSettings settings, bool needRefreshUI);
void Create();
hstring Title();
void TitlebarClicked();
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
winrt::hstring ApplicationDisplayName();
winrt::hstring ApplicationVersion();
winrt::hstring ThirdPartyNoticesLink();
winrt::fire_and_forget CloseWindow();
void ToggleFocusMode();
void ToggleFullscreen();
void ToggleAlwaysOnTop();
bool FocusMode() const;
bool Fullscreen() const;
bool AlwaysOnTop() const;
void SetStartupActions(std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);
void SetInboundListener();
static std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> ConvertExecuteCommandlineToActions(const Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args);
winrt::TerminalApp::IDialogPresenter DialogPresenter() const;
void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter);
size_t GetLastActiveControlTaskbarState();
size_t GetLastActiveControlTaskbarProgress();
void ShowKeyboardServiceWarning();
winrt::hstring KeyboardServiceDisabledText();
winrt::fire_and_forget IdentifyWindow();
winrt::fire_and_forget RenameFailed();
winrt::fire_and_forget ProcessStartupActions(Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::ActionAndArgs> actions,
const bool initial,
const winrt::hstring cwd = L"");
// Normally, WindowName and WindowId would be
// WINRT_OBSERVABLE_PROPERTY's, but we want them to raise
// WindowNameForDisplay and WindowIdForDisplay instead
winrt::hstring WindowName() const noexcept;
winrt::fire_and_forget WindowName(const winrt::hstring& value);
uint64_t WindowId() const noexcept;
void WindowId(const uint64_t& value);
winrt::hstring WindowIdForDisplay() const noexcept;
winrt::hstring WindowNameForDisplay() const noexcept;
bool IsQuakeWindow() const noexcept;
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
// -------------------------------- WinRT Events ---------------------------------
TYPED_EVENT(TitleChanged, IInspectable, winrt::hstring);
TYPED_EVENT(LastTabClosed, IInspectable, winrt::TerminalApp::LastTabClosedEventArgs);
TYPED_EVENT(SetTitleBarContent, IInspectable, winrt::Windows::UI::Xaml::UIElement);
TYPED_EVENT(FocusModeChanged, IInspectable, IInspectable);
TYPED_EVENT(FullscreenChanged, IInspectable, IInspectable);
TYPED_EVENT(AlwaysOnTopChanged, IInspectable, IInspectable);
TYPED_EVENT(RaiseVisualBell, IInspectable, IInspectable);
TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable);
TYPED_EVENT(Initialized, IInspectable, winrt::Windows::UI::Xaml::RoutedEventArgs);
TYPED_EVENT(IdentifyWindowsRequested, IInspectable, IInspectable);
TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs);
TYPED_EVENT(IsQuakeWindowChanged, IInspectable, IInspectable);
private:
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
std::optional<HWND> _hostingHwnd;
// If you add controls here, but forget to null them either here or in
// the ctor, you're going to have a bad time. It'll mysteriously fail to
// activate the app.
// ALSO: If you add any UIElements as roots here, make sure they're
// updated in App::_ApplyTheme. The roots currently is _tabRow
// (which is a root when the tabs are in the titlebar.)
Microsoft::UI::Xaml::Controls::TabView _tabView{ nullptr };
TerminalApp::TabRowControl _tabRow{ nullptr };
Windows::UI::Xaml::Controls::Grid _tabContent{ nullptr };
Microsoft::UI::Xaml::Controls::SplitButton _newTabButton{ nullptr };
Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
Windows::Foundation::Collections::IObservableVector<TerminalApp::TabBase> _tabs;
Windows::Foundation::Collections::IObservableVector<TerminalApp::TabBase> _mruTabs;
static winrt::com_ptr<TerminalTab> _GetTerminalTabImpl(const TerminalApp::TabBase& tab);
void _UpdateTabIndices();
TerminalApp::SettingsTab _settingsTab{ nullptr };
bool _isInFocusMode{ false };
bool _isFullscreen{ false };
bool _isAlwaysOnTop{ false };
winrt::hstring _WindowName{};
uint64_t _WindowId{ 0 };
bool _rearranging;
std::optional<int> _rearrangeFrom;
std::optional<int> _rearrangeTo;
bool _removing{ false };
uint32_t _systemRowsToScroll{ DefaultRowsToScroll };
// use a weak reference to prevent circular dependency with AppLogic
winrt::weak_ref<winrt::TerminalApp::IDialogPresenter> _dialogPresenter;
winrt::com_ptr<AppKeyBindings> _bindings{ winrt::make_self<implementation::AppKeyBindings>() };
winrt::com_ptr<ShortcutActionDispatch> _actionDispatch{ winrt::make_self<implementation::ShortcutActionDispatch>() };
winrt::Windows::UI::Xaml::Controls::Grid::LayoutUpdated_revoker _layoutUpdatedRevoker;
StartupState _startupState{ StartupState::NotInitialized };
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::ActionAndArgs> _startupActions;
bool _shouldStartInboundListener{ false };
std::shared_ptr<Toast> _windowIdToast{ nullptr };
std::shared_ptr<Toast> _windowRenameFailedToast{ nullptr };
void _ShowAboutDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowCloseWarningDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowCloseReadOnlyDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowMultiLinePasteWarningDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowLargePasteWarningDialog();
void _CreateNewTabFlyout();
void _OpenNewTabDropdown();
void _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
void _CreateNewTabFromSettings(GUID profileGuid, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(GUID profileGuid, Microsoft::Terminal::Settings::Model::TerminalSettings settings);
winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
bool _displayingCloseDialog{ false };
void _SettingsButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _FeedbackButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _AboutButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _ThirdPartyNoticesOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _SUIPreviewKeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap) noexcept;
void _RegisterActionCallbacks();
void _UpdateTitle(const TerminalTab& tab);
void _UpdateTabIcon(TerminalTab& tab);
void _UpdateTabView();
void _UpdateTabWidthMode();
void _UpdateCommandsForPalette();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, Microsoft::Terminal::Settings::Model::Command> _ExpandCommands(Windows::Foundation::Collections::IMapView<winrt::hstring, Microsoft::Terminal::Settings::Model::Command> commandsToExpand,
Windows::Foundation::Collections::IVectorView<Microsoft::Terminal::Settings::Model::Profile> profiles,
Windows::Foundation::Collections::IMapView<winrt::hstring, Microsoft::Terminal::Settings::Model::ColorScheme> schemes);
void _DuplicateFocusedTab();
void _DuplicateTab(const TerminalTab& tab);
winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::TabBase tab);
void _RemoveTab(const winrt::TerminalApp::TabBase& tab);
winrt::fire_and_forget _RemoveTabs(const std::vector<winrt::TerminalApp::TabBase> tabs);
void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term, TerminalTab& hostingTab);
void _DismissTabContextMenus();
void _FocusCurrentTab(const bool focusAlways);
bool _HasMultipleTabs() const;
void _RemoveAllTabs();
void _ResizeTabContent(const winrt::Windows::Foundation::Size& newSize);
void _SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode);
bool _SelectTab(const uint32_t tabIndex);
void _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl();
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
TerminalApp::TabBase _GetFocusedTab() const noexcept;
winrt::com_ptr<TerminalTab> _GetFocusedTabImpl() const noexcept;
TerminalApp::TabBase _GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept;
winrt::fire_and_forget _SetFocusedTab(const winrt::TerminalApp::TabBase tab);
void _CloseFocusedTab();
winrt::fire_and_forget _CloseFocusedPane();
winrt::fire_and_forget _RemoveOnCloseRoutine(Microsoft::UI::Xaml::Controls::TabViewItem tabViewItem, winrt::com_ptr<TerminalPage> page);
void _Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference<uint32_t>& rowsToScroll);
void _SplitPane(const Microsoft::Terminal::Settings::Model::SplitState splitType,
const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual,
const float splitSize = 0.5f,
const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr);
void _ResizePane(const Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
void _ScrollPage(ScrollDirection scrollDirection);
void _ScrollToBufferEdge(ScrollDirection scrollDirection);
void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Control::KeyChord& keyChord);
winrt::fire_and_forget _CopyToClipboardHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::CopyToClipboardEventArgs copiedData);
winrt::fire_and_forget _PasteFromClipboardHandler(const IInspectable sender,
const Microsoft::Terminal::Control::PasteFromClipboardEventArgs eventArgs);
void _OpenHyperlinkHandler(const IInspectable sender, const Microsoft::Terminal::Control::OpenHyperlinkEventArgs eventArgs);
bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri);
void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri);
bool _CopyText(const bool singleLine, const Windows::Foundation::IReference<Microsoft::Terminal::Control::CopyFormat>& formats);
winrt::fire_and_forget _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs);
void _PasteText();
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);
void _TabDragStarted(const IInspectable& sender, const IInspectable& eventArgs);
void _TabDragCompleted(const IInspectable& sender, const IInspectable& eventArgs);
void _OnTabClick(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
void _OnTabSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& eventArgs);
void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs);
void _OnContentSizeChanged(const IInspectable& /*sender*/, Windows::UI::Xaml::SizeChangedEventArgs const& e);
void _OnTabCloseRequested(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabCloseRequestedEventArgs& eventArgs);
void _OnFirstLayout(const IInspectable& sender, const IInspectable& eventArgs);
void _UpdatedSelectedTab(const winrt::TerminalApp::TabBase& tab);
void _OnDispatchCommandRequested(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::Command& command);
void _OnCommandLineExecutionRequested(const IInspectable& sender, const winrt::hstring& commandLine);
void _OnSwitchToTabRequested(const IInspectable& sender, const winrt::TerminalApp::TabBase& tab);
void _Find();
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection);
winrt::fire_and_forget _RefreshUIForSettingsReload();
void _SetNonClientAreaColors(const Windows::UI::Color& selectedTabColor);
void _ClearNonClientAreaColors();
void _SetNewTabButtonColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor);
void _ClearNewTabButtonColor();
void _CompleteInitialization();
void _FocusActiveControl(IInspectable sender, IInspectable eventArgs);
void _UnZoomIfNeeded();
void _OpenSettingsUI();
static int _ComputeScrollDelta(ScrollDirection scrollDirection, const uint32_t rowsToScroll);
static uint32_t _ReadSystemRowsToScroll();
void _UpdateMRUTab(const winrt::TerminalApp::TabBase& tab);
void _TryMoveTab(const uint32_t currentTabIndex, const int32_t suggestedNewTabIndex);
bool _shouldMouseVanish{ false };
bool _isMouseHidden{ false };
Windows::UI::Core::CoreCursor _defaultPointerCursor{ nullptr };
void _HidePointerCursorHandler(const IInspectable& sender, const IInspectable& eventArgs);
void _RestorePointerCursorHandler(const IInspectable& sender, const IInspectable& eventArgs);
void _PreviewActionHandler(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::Command& args);
void _EndPreview();
void _EndPreviewColorScheme();
void _PreviewColorScheme(const Microsoft::Terminal::Settings::Model::SetColorSchemeArgs& args);
winrt::Microsoft::Terminal::Settings::Model::Command _lastPreviewedCommand{ nullptr };
winrt::Microsoft::Terminal::Settings::Model::TerminalSettings _originalSettings{ nullptr };
void _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection);
void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
void _WindowRenamerActionClick(const IInspectable& sender, const IInspectable& eventArgs);
void _RequestWindowRename(const winrt::hstring& newName);
void _WindowRenamerKeyUp(const IInspectable& sender, winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _UpdateTeachingTipTheme(winrt::Windows::UI::Xaml::FrameworkElement element);
void _SetFocusMode(const bool inFocusMode);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
#define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action);
ALL_SHORTCUT_ACTIONS
#undef ON_ALL_ACTIONS
#pragma endregion
friend class TerminalAppLocalTests::TabTests;
friend class TerminalAppLocalTests::SettingsTests;
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(TerminalPage);
}