Add support for moving panes and tabs between windows (#14866)

_Lo! Harken to me, for I shall divulge the heart of the tab tear-out
saga. Verily, this PR shall bestow upon thee the power to move tabs and
panes between windows by means of pre-defined actions. Though be warned,
it does not yet grant thee the power to drag and drop them as thou
mayest desire. Yet, the same plumbing that underpins this work shall
remain steadfast. Behold, the majority of this undertaking concerns the
elevation of the RequestMoveContent event from the TerminalPage to the
very summit of the Monarch. From thence, a great AttachContent method
shall descend back to the lowest depths. Furthermore, there are minor
revisions to TermControl that shall enable thee to better detach the
content and attach it to a new one._

This is the most important part of the tab tear-out saga. This PR
enables the user to move tabs and panes between windows using
pre-defined actions. It does _not_ enable the user to drag/drop them
yet, but the same fundamental plumbing will still apply. Most of the PR
is plumbing the `RequestMoveContent` event up from the `TerminalPage` up
to the `Monarch`, and then plumbing an `AttachContent` method back down.
There are also small changes to `TermControl` to better support
detaching the content and attaching to a new one.

For testing, I recommend:

```json
        { "keys": "f1", "command": { "action": "moveTab", "window": "1" } },
        { "keys": "f2", "command": { "action": "moveTab", "window": "2" } },

        { "keys": "f3", "command": { "action": "movePane", "window": "1" } },
        { "keys": "f4", "command": { "action": "movePane", "window": "2" } },

        { "keys": "shift+f3", "command": { "action": "movePane", "window": "1", "index": 3 } },
        { "keys": "shift+f4", "command": { "action": "movePane", "window": "2", "index": 3 } },
```

* Related to #1256
* Related to #5000

---------

Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com>
This commit is contained in:
Mike Griese
2023-03-30 09:37:53 -05:00
committed by GitHub
parent 34aa6aa0d4
commit 17a5b77335
52 changed files with 944 additions and 169 deletions

View File

@@ -1055,4 +1055,58 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
return winrt::single_threaded_vector(std::move(vec));
}
void Monarch::RequestMoveContent(winrt::hstring window,
winrt::hstring content,
uint32_t tabIndex)
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_Requested",
TraceLoggingWideString(window.c_str(), "window", "The name of the window we tried to move to"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
uint64_t windowId = _lookupPeasantIdForName(window);
if (windowId == 0)
{
// Try the name as an integer ID
uint32_t temp;
if (!Utils::StringToUint(window.c_str(), temp))
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_FailedToParseId",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
windowId = temp;
}
}
if (auto targetPeasant{ _getPeasant(windowId) })
{
auto request = winrt::make_self<implementation::AttachRequest>(content, tabIndex);
targetPeasant.AttachContentToWindow(*request);
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_Completed",
TraceLoggingInt64(windowId, "windowId", "The ID of the peasant which we sent the content to"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_NoWindow",
TraceLoggingInt64(windowId, "windowId", "We could not find a peasant with this ID"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
// TODO GH#5000
//
// In the case where window couldn't be found, then create a window
// for that name / ID. Do this as a part of tear-out (different than
// drag/drop)
}
}
}

View File

@@ -81,6 +81,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
void RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);

View File

@@ -70,6 +70,8 @@ namespace Microsoft.Terminal.Remoting
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos { get; };
Windows.Foundation.Collections.IVector<String> GetAllWindowLayouts();
void RequestMoveContent(String window, String content, UInt32 tabIndex);
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;

View File

@@ -8,6 +8,7 @@
#include "GetWindowLayoutArgs.h"
#include "Peasant.g.cpp"
#include "../../types/inc/utils.hpp"
#include "AttachRequest.g.cpp"
using namespace winrt;
using namespace winrt::Microsoft::Terminal;
@@ -275,6 +276,22 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::AttachContentToWindow(Remoting::AttachRequest request)
{
try
{
_AttachRequestedHandlers(*this, request);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_AttachContentToWindow",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::Quit()
{
try

View File

@@ -5,6 +5,7 @@
#include "Peasant.g.h"
#include "RenameRequestArgs.h"
#include "AttachRequest.g.h"
namespace RemotingUnitTests
{
@@ -12,6 +13,18 @@ namespace RemotingUnitTests
};
namespace winrt::Microsoft::Terminal::Remoting::implementation
{
struct AttachRequest : public AttachRequestT<AttachRequest>
{
WINRT_PROPERTY(winrt::hstring, Content);
WINRT_PROPERTY(uint32_t, TabIndex);
public:
AttachRequest(winrt::hstring content,
uint32_t tabIndex) :
_Content{ content },
_TabIndex{ tabIndex } {};
};
struct Peasant : public PeasantT<Peasant>
{
Peasant();
@@ -32,6 +45,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void RequestQuitAll();
void Quit();
void AttachContentToWindow(Remoting::AttachRequest request);
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
@@ -47,12 +62,15 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RenameRequestArgs);
TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior);
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(GetWindowLayoutRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::GetWindowLayoutArgs);
TYPED_EVENT(AttachRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::AttachRequest);
private:
Peasant(const uint64_t testPID);
uint64_t _ourPID;

View File

@@ -51,6 +51,11 @@ namespace Microsoft.Terminal.Remoting
MonitorBehavior ToMonitor;
}
[default_interface] runtimeclass AttachRequest {
String Content { get; };
UInt32 TabIndex { get; };
}
interface IPeasant
{
CommandlineArgs InitialArgs { get; };
@@ -69,23 +74,30 @@ namespace Microsoft.Terminal.Remoting
void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested
void Summon(SummonWindowBehavior behavior);
void RequestShowNotificationIcon();
void RequestHideNotificationIcon();
void RequestQuitAll();
void Quit();
String GetWindowLayout();
void AttachContentToWindow(AttachRequest request);
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> DisplayWindowIdRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameRequestArgs> RenameRequested;
event Windows.Foundation.TypedEventHandler<Object, SummonWindowBehavior> SummonRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, GetWindowLayoutArgs> GetWindowLayoutRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitAllRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
event Windows.Foundation.TypedEventHandler<Object, AttachRequest> AttachRequested;
};
[default_interface] runtimeclass Peasant : IPeasant

View File

@@ -413,4 +413,13 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
return nullptr;
}
winrt::fire_and_forget WindowManager::RequestMoveContent(winrt::hstring window,
winrt::hstring content,
uint32_t tabIndex)
{
co_await winrt::resume_background();
_monarch.RequestMoveContent(window, content, tabIndex);
}
}

View File

@@ -35,13 +35,17 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
uint64_t GetNumberOfPeasants();
static winrt::fire_and_forget RequestQuitAll(Remoting::Peasant peasant);
void UpdateActiveTabTitle(const winrt::hstring& title, const Remoting::Peasant& peasant);
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
bool DoesQuakeWindowExist();
winrt::fire_and_forget RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);

View File

@@ -26,6 +26,8 @@ namespace Microsoft.Terminal.Remoting
Boolean DoesQuakeWindowExist();
void RequestMoveContent(String window, String content, UInt32 tabIndex);
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;

View File

@@ -24,27 +24,5 @@ namespace winrt::TerminalApp::implementation
Name(command.Name());
KeyChordText(command.KeyChordText());
Icon(command.IconPath());
_commandChangedRevoker = command.PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](auto& sender, auto& e) {
auto item{ weakThis.get() };
auto senderCommand{ sender.try_as<Microsoft::Terminal::Settings::Model::Command>() };
if (item && senderCommand)
{
auto changedProperty = e.PropertyName();
if (changedProperty == L"Name")
{
item->Name(senderCommand.Name());
}
else if (changedProperty == L"KeyChordText")
{
item->KeyChordText(senderCommand.KeyChordText());
}
else if (changedProperty == L"IconPath")
{
item->Icon(senderCommand.IconPath());
}
}
});
}
}

View File

@@ -209,7 +209,7 @@ namespace winrt::TerminalApp::implementation
}
else if (const auto& realArgs = args.ActionArgs().try_as<MovePaneArgs>())
{
auto moved = _MovePane(realArgs.TabIndex());
auto moved = _MovePane(realArgs);
args.Handled(moved);
}
}
@@ -811,17 +811,8 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = actionArgs.ActionArgs().try_as<MoveTabArgs>())
{
auto direction = realArgs.Direction();
if (direction != MoveTabDirection::None)
{
if (auto focusedTabIndex = _GetFocusedTabIndex())
{
auto currentTabIndex = focusedTabIndex.value();
auto delta = direction == MoveTabDirection::Forward ? 1 : -1;
_TryMoveTab(currentTabIndex, currentTabIndex + delta);
}
}
actionArgs.Handled(true);
auto moved = _MoveTab(realArgs);
actionArgs.Handled(moved);
}
}

View File

@@ -340,7 +340,7 @@ void AppCommandlineArgs::_buildMovePaneParser()
if (_movePaneTabIndex >= 0)
{
movePaneAction.Action(ShortcutAction::MovePane);
MovePaneArgs args{ static_cast<unsigned int>(_movePaneTabIndex) };
MovePaneArgs args{ static_cast<unsigned int>(_movePaneTabIndex), L"" };
movePaneAction.Args(args);
_startupActions.push_back(movePaneAction);
}

View File

@@ -39,8 +39,17 @@ namespace winrt::TerminalApp::implementation
return it != _content.end() ? it->second : ControlInteractivity{ nullptr };
}
void ContentManager::_closedHandler(winrt::Windows::Foundation::IInspectable sender,
winrt::Windows::Foundation::IInspectable e)
void ContentManager::Detach(const Microsoft::Terminal::Control::TermControl& control)
{
const auto contentId{ control.ContentId() };
if (const auto& content{ TryLookupCore(contentId) })
{
control.Detach();
}
}
void ContentManager::_closedHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable&)
{
if (const auto& content{ sender.try_as<winrt::Microsoft::Terminal::Control::ControlInteractivity>() })
{

View File

@@ -18,6 +18,9 @@ Abstract:
other threads.
- When you want to create a new TermControl, call CreateCore to instantiate a
new content with a GUID for later reparenting.
- Detach can be used to temporarily remove a content from its hosted
TermControl. After detaching, you can still use LookupCore &
TermControl::AttachContent to re-attach to the content.
--*/
#pragma once
@@ -35,10 +38,12 @@ namespace winrt::TerminalApp::implementation
const Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection);
Microsoft::Terminal::Control::ControlInteractivity TryLookupCore(uint64_t id);
void Detach(const Microsoft::Terminal::Control::TermControl& control);
private:
std::unordered_map<uint64_t, Microsoft::Terminal::Control::ControlInteractivity> _content;
void _closedHandler(winrt::Windows::Foundation::IInspectable sender,
winrt::Windows::Foundation::IInspectable e);
void _closedHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& e);
};
}

View File

@@ -108,10 +108,12 @@ Pane::Pane(std::shared_ptr<Pane> first,
// - Extract the terminal settings from the current (leaf) pane's control
// to be used to create an equivalent control
// Arguments:
// - <none>
// - asContent: when true, we're trying to serialize this pane for moving across
// windows. In that case, we'll need to fill in the content guid for our new
// terminal args.
// Return Value:
// - Arguments appropriate for a SplitPane or NewTab action
NewTerminalArgs Pane::GetTerminalArgsForPane() const
NewTerminalArgs Pane::GetTerminalArgsForPane(const bool asContent) const
{
// Leaves are the only things that have controls
assert(_IsLeaf());
@@ -156,6 +158,14 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const
// object. That would work for schemes set by the Terminal, but not ones set
// by VT, but that seems good enough.
// Only fill in the ContentId if absolutely needed. If you fill in a number
// here (even 0), we'll serialize that number, AND treat that action as an
// "attach existing" rather than a "create"
if (asContent)
{
args.ContentId(_control.ContentId());
}
return args;
}
@@ -167,36 +177,62 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const
// Arguments:
// - currentId: the id to use for the current/first pane
// - nextId: the id to use for a new pane if we split
// - asContent: We're serializing this set of actions as content actions for
// moving to other windows, so we need to make sure to include ContentId's
// in the final actions.
// - asMovePane: only used with asContent. When this is true, we're building
// these actions as a part of moving the pane to another window, but without
// the context of the hosting tab. In that case, we'll want to build a
// splitPane action even if we're just a single leaf, because there's no other
// parent to try and build an action for us.
// Return Value:
// - The state from building the startup actions, includes a vector of commands,
// the original root pane, the id of the focused pane, and the number of panes
// created.
Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t nextId)
Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId,
uint32_t nextId,
const bool asContent,
const bool asMovePane)
{
// if we are a leaf then all there is to do is defer to the parent.
if (_IsLeaf())
// Normally, if we're a leaf, return an empt set of actions, because the
// parent pane will build the SplitPane action for us. If we're building
// actions for a movePane action though, we'll still need to include
// ourselves.
if (!asMovePane && _IsLeaf())
{
if (_lastActive)
{
return { {}, shared_from_this(), currentId, 0 };
// empty args, this is the first pane, currentId is
return { .args = {}, .firstPane = shared_from_this(), .focusedPaneId = currentId, .panesCreated = 0 };
}
return { {}, shared_from_this(), std::nullopt, 0 };
return { .args = {}, .firstPane = shared_from_this(), .focusedPaneId = std::nullopt, .panesCreated = 0 };
}
auto buildSplitPane = [&](auto newPane) {
ActionAndArgs actionAndArgs;
actionAndArgs.Action(ShortcutAction::SplitPane);
const auto terminalArgs{ newPane->GetTerminalArgsForPane() };
const auto terminalArgs{ newPane->GetTerminalArgsForPane(asContent) };
// When creating a pane the split size is the size of the new pane
// and not position.
const auto splitDirection = _splitState == SplitState::Horizontal ? SplitDirection::Down : SplitDirection::Right;
SplitPaneArgs args{ SplitType::Manual, splitDirection, 1. - _desiredSplitPosition, terminalArgs };
const auto splitSize = (asContent && _IsLeaf() ? .5 : 1. - _desiredSplitPosition);
SplitPaneArgs args{ SplitType::Manual, splitDirection, splitSize, terminalArgs };
actionAndArgs.Args(args);
return actionAndArgs;
};
if (asContent && _IsLeaf())
{
return {
.args = { buildSplitPane(shared_from_this()) },
.firstPane = shared_from_this(),
.focusedPaneId = currentId,
.panesCreated = 1
};
}
auto buildMoveFocus = [](auto direction) {
MoveFocusArgs args{ direction };
@@ -223,7 +259,12 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n
focusedPaneId = nextId;
}
return { { actionAndArgs }, _firstChild, focusedPaneId, 1 };
return {
.args = { actionAndArgs },
.firstPane = _firstChild,
.focusedPaneId = focusedPaneId,
.panesCreated = 1
};
}
// We now need to execute the commands for each side of the tree
@@ -260,7 +301,12 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n
// mutually exclusive.
const auto focusedPaneId = firstState.focusedPaneId.has_value() ? firstState.focusedPaneId : secondState.focusedPaneId;
return { actions, firstState.firstPane, focusedPaneId, firstState.panesCreated + secondState.panesCreated + 1 };
return {
.args = { actions },
.firstPane = firstState.firstPane,
.focusedPaneId = focusedPaneId,
.panesCreated = firstState.panesCreated + secondState.panesCreated + 1
};
}
// Method Description:

View File

@@ -97,8 +97,8 @@ public:
std::optional<uint32_t> focusedPaneId;
uint32_t panesCreated;
};
BuildStartupState BuildStartupActions(uint32_t currentId, uint32_t nextId);
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetTerminalArgsForPane() const;
BuildStartupState BuildStartupActions(uint32_t currentId, uint32_t nextId, const bool asContent = false, const bool asMovePane = false);
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetTerminalArgsForPane(const bool asContent = false) const;
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::Settings::Model::Profile& profile);

View File

@@ -762,6 +762,10 @@
<value>The "newTabMenu" field contains more than one entry of type "remainingProfiles". Only the first one will be considered.</value>
<comment>{Locked="newTabMenu"} {Locked="remainingProfiles"}</comment>
</data>
<data name="InvalidUseOfContent" xml:space="preserve">
<value>The "__content" property is reserved for internal use</value>
<comment>{Locked="__content"}</comment>
</data>
<data name="AboutToolTip" xml:space="preserve">
<value>Open a dialog containing product information</value>
</data>

View File

@@ -47,10 +47,13 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Creates a list of actions that can be run to recreate the state of this tab
// Arguments:
// - <none>
// Return Value:
// - asContent: unused. There's nothing different we need to do when
// serializing the settings tab for moving to another window. If we ever
// really want to support opening the SUI to a specific page, we can
// re-evaluate including that arg in this action then.
// Return Value:
// - The list of actions.
std::vector<ActionAndArgs> SettingsTab::BuildStartupActions() const
std::vector<ActionAndArgs> SettingsTab::BuildStartupActions(const bool /*asContent*/) const
{
ActionAndArgs action;
action.Action(ShortcutAction::OpenSettings);

View File

@@ -30,7 +30,7 @@ namespace winrt::TerminalApp::implementation
void UpdateSettings(Microsoft::Terminal::Settings::Model::CascadiaSettings settings);
void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override;
std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions() const override;
std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions(const bool asContent = false) const override;
private:
winrt::Windows::UI::Xaml::ElementTheme _requestedTheme;

View File

@@ -23,7 +23,7 @@ namespace winrt::TerminalApp::implementation
void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs);
void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap);
virtual std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions() const = 0;
virtual std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions(const bool asContent = false) const = 0;
virtual std::optional<winrt::Windows::UI::Color> GetTabColor();
void ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused,

View File

@@ -7,6 +7,7 @@
#include "TerminalPage.g.cpp"
#include "LastTabClosedEventArgs.g.cpp"
#include "RenameWindowRequestedArgs.g.cpp"
#include "RequestMoveContentArgs.g.cpp"
#include <filesystem>
@@ -1883,12 +1884,15 @@ namespace winrt::TerminalApp::implementation
// is the last remaining pane on a tab, that tab will be closed upon moving.
// - No move will occur if the tabIdx is the same as the current tab, or if
// the specified tab is not a host of terminals (such as the settings tab).
// Arguments:
// - tabIdx: The target tab index.
// - If the Window is specified, the pane will instead be detached and moved
// to the window with the given name/id.
// Return Value:
// - true if the pane was successfully moved to the new tab.
bool TerminalPage::_MovePane(const uint32_t tabIdx)
bool TerminalPage::_MovePane(MovePaneArgs args)
{
const auto tabIdx{ args.TabIndex() };
const auto windowId{ args.Window() };
auto focusedTab{ _GetFocusedTabImpl() };
if (!focusedTab)
@@ -1896,6 +1900,23 @@ namespace winrt::TerminalApp::implementation
return false;
}
// If there was a windowId in the action, try to move it to the
// specified window instead of moving it in our tab row.
if (!windowId.empty())
{
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
if (const auto pane{ terminalTab->GetActivePane() })
{
auto startupActions = pane->BuildStartupActions(0, 1, true, true);
_DetachPaneFromWindow(pane);
_MoveContent(std::move(startupActions.args), args.Window(), args.TabIndex());
focusedTab->DetachPane();
return true;
}
}
}
// If we are trying to move from the current tab to the current tab do nothing.
if (_GetFocusedTabIndex() == tabIdx)
{
@@ -1926,6 +1947,134 @@ namespace winrt::TerminalApp::implementation
return true;
}
// Detach a tree of panes from this terminal. Helper used for moving panes
// and tabs to other windows.
void TerminalPage::_DetachPaneFromWindow(std::shared_ptr<Pane> pane)
{
pane->WalkTree([&](auto p) {
if (const auto& control{ p->GetTerminalControl() })
{
_manager.Detach(control);
}
});
}
void TerminalPage::_DetachTabFromWindow(const winrt::com_ptr<TerminalTab>& terminalTab)
{
// Detach the root pane, which will act like the whole tab got detached.
if (const auto rootPane = terminalTab->GetRootPane())
{
_DetachPaneFromWindow(rootPane);
}
}
// Method Description:
// - Serialize these actions to json, and raise them as a RequestMoveContent
// event. Our Window will raise that to the window manager / monarch, who
// will dispatch this blob of json back to the window that should handle
// this.
// - `actions` will be emptied into a winrt IVector as a part of this method
// and should be expected to be empty after this call.
void TerminalPage::_MoveContent(std::vector<Settings::Model::ActionAndArgs>&& actions,
const winrt::hstring& windowName,
const uint32_t tabIndex)
{
const auto winRtActions{ winrt::single_threaded_vector<ActionAndArgs>(std::move(actions)) };
const auto str{ ActionAndArgs::Serialize(winRtActions) };
const auto request = winrt::make_self<RequestMoveContentArgs>(windowName,
str,
tabIndex);
_RequestMoveContentHandlers(*this, *request);
}
bool TerminalPage::_MoveTab(MoveTabArgs args)
{
// If there was a windowId in the action, try to move it to the
// specified window instead of moving it in our tab row.
const auto windowId{ args.Window() };
if (!windowId.empty())
{
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
auto startupActions = terminalTab->BuildStartupActions(true);
_DetachTabFromWindow(terminalTab);
_MoveContent(std::move(startupActions), args.Window(), 0);
_RemoveTab(*terminalTab);
return true;
}
}
const auto direction = args.Direction();
if (direction != MoveTabDirection::None)
{
if (auto focusedTabIndex = _GetFocusedTabIndex())
{
const auto currentTabIndex = focusedTabIndex.value();
const auto delta = direction == MoveTabDirection::Forward ? 1 : -1;
_TryMoveTab(currentTabIndex, currentTabIndex + delta);
}
}
return true;
}
// Method Description:
// - Called when it is determined that an existing tab or pane should be
// attached to our window. content represents a blob of JSON describing
// some startup actions for rebuilding the specified panes. They will
// include `__content` properties with the GUID of the existing
// ControlInteractivity's we should use, rather than starting new ones.
// - _MakePane is already enlightened to use the ContentId property to
// reattach instead of create new content, so this method simply needs to
// parse the JSON and pump it into our action handler. Almost the same as
// doing something like `wt -w 0 nt`.
winrt::fire_and_forget TerminalPage::AttachContent(winrt::hstring content,
uint32_t tabIndex)
{
auto args = ActionAndArgs::Deserialize(content);
if (args == nullptr ||
args.Size() == 0)
{
co_return;
}
// Switch to the UI thread before selecting a tab or dispatching actions.
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::High);
const auto& firstAction = args.GetAt(0);
const bool firstIsSplitPane{ firstAction.Action() == ShortcutAction::SplitPane };
// `splitPane` allows the user to specify which tab to split. In that
// case, split specifically the requested pane.
//
// If there's not enough tabs, then just turn this pane into a new tab.
//
// If the first action is `newTab`, the index is always going to be 0,
// so don't do anything in that case.
if (firstIsSplitPane && tabIndex < _tabs.Size())
{
_SelectTab(tabIndex);
}
else
{
if (firstIsSplitPane)
{
// Create the equivalent NewTab action.
const auto newAction = Settings::Model::ActionAndArgs{ Settings::Model::ShortcutAction::NewTab,
Settings::Model::NewTabArgs(firstAction.Args() ?
firstAction.Args().try_as<Settings::Model::SplitPaneArgs>().TerminalArgs() :
nullptr) };
args.SetAt(0, newAction);
}
}
for (const auto& action : args)
{
_actionDispatch->DoAction(action);
}
}
// Method Description:
// - Split the focused pane either horizontally or vertically, and place the
// given pane accordingly in the tree
@@ -2589,26 +2738,46 @@ namespace winrt::TerminalApp::implementation
}
}
TermControl TerminalPage::_InitControl(const TerminalSettingsCreateResult& settings, const ITerminalConnection& connection)
TermControl TerminalPage::_CreateNewControlAndContent(const TerminalSettingsCreateResult& settings, const ITerminalConnection& connection)
{
// 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.
const auto content = _manager.CreateCore(settings.DefaultSettings(), settings.UnfocusedSettings(), connection);
return _SetupControl(TermControl{ content });
}
TermControl term{ content };
TermControl TerminalPage::_AttachControlToContent(const uint64_t& contentId)
{
if (const auto& content{ _manager.TryLookupCore(contentId) })
{
// We have to pass in our current keybindings, because that's an
// object that belongs to this TerminalPage, on this thread. If we
// don't, then when we move the content to another thread, and it
// tries to handle a key, it'll callback on the original page's
// stack, inevitably resulting in a wrong_thread
return _SetupControl(TermControl::NewControlByAttachingContent(content, *_bindings));
}
return nullptr;
}
TermControl TerminalPage::_SetupControl(const TermControl& term)
{
// GH#12515: ConPTY assumes it's hidden at the start. If we're not, let it know now.
if (_visible)
{
term.WindowVisibilityChanged(_visible);
}
// Even in the case of re-attaching content from another window, this
// will correctly update the control's owning HWND
if (_hostingHwnd.has_value())
{
term.OwningHwnd(reinterpret_cast<uint64_t>(*_hostingHwnd));
}
_RegisterTerminalEvents(term);
return term;
}
@@ -2632,6 +2801,19 @@ namespace winrt::TerminalApp::implementation
const winrt::TerminalApp::TabBase& sourceTab,
TerminalConnection::ITerminalConnection existingConnection)
{
// First things first - Check for making a pane from content ID.
if (newTerminalArgs &&
newTerminalArgs.ContentId() != 0)
{
// Don't need to worry about duplicating or anything - we'll
// serialize the actual profile's GUID along with the content guid.
const auto& profile = _settings.GetProfileForArgs(newTerminalArgs);
const auto control = _AttachControlToContent(newTerminalArgs.ContentId());
return std::make_shared<Pane>(profile, control);
}
TerminalSettingsCreateResult controlSettings{ nullptr };
Profile profile{ nullptr };
@@ -2683,15 +2865,13 @@ namespace winrt::TerminalApp::implementation
}
}
const auto control = _InitControl(controlSettings, connection);
_RegisterTerminalEvents(control);
const auto control = _CreateNewControlAndContent(controlSettings, connection);
auto resultPane = std::make_shared<Pane>(profile, control);
if (debugConnection) // this will only be set if global debugging is on and tap is active
{
auto newControl = _InitControl(controlSettings, debugConnection);
_RegisterTerminalEvents(newControl);
auto newControl = _CreateNewControlAndContent(controlSettings, debugConnection);
// Split (auto) with the debug tap.
auto debugPane = std::make_shared<Pane>(profile, newControl);
@@ -4130,7 +4310,10 @@ namespace winrt::TerminalApp::implementation
if (auto terminalTab{ _GetTerminalTabImpl(tab) })
{
// The root pane will propagate the theme change to all its children.
terminalTab->GetRootPane()->UpdateResources(_paneResources);
if (const auto& rootPane{ terminalTab->GetRootPane() })
{
rootPane->UpdateResources(_paneResources);
}
}
}
}

View File

@@ -9,6 +9,7 @@
#include "AppCommandlineArgs.h"
#include "LastTabClosedEventArgs.g.h"
#include "RenameWindowRequestedArgs.g.h"
#include "RequestMoveContentArgs.g.h"
#include "Toast.h"
#define DECLARE_ACTION_HANDLER(action) void _Handle##action(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
@@ -60,6 +61,19 @@ namespace winrt::TerminalApp::implementation
_ProposedName{ name } {};
};
struct RequestMoveContentArgs : RequestMoveContentArgsT<RequestMoveContentArgs>
{
WINRT_PROPERTY(winrt::hstring, Window);
WINRT_PROPERTY(winrt::hstring, Content);
WINRT_PROPERTY(uint32_t, TabIndex);
public:
RequestMoveContentArgs(const winrt::hstring window, const winrt::hstring content, uint32_t tabIndex) :
_Window{ window },
_Content{ content },
_TabIndex{ tabIndex } {};
};
struct TerminalPage : TerminalPageT<TerminalPage>
{
public:
@@ -134,6 +148,8 @@ namespace winrt::TerminalApp::implementation
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
winrt::fire_and_forget AttachContent(winrt::hstring content, uint32_t tabIndex);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
// -------------------------------- WinRT Events ---------------------------------
@@ -150,11 +166,14 @@ namespace winrt::TerminalApp::implementation
TYPED_EVENT(IdentifyWindowsRequested, IInspectable, IInspectable);
TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs);
TYPED_EVENT(SummonWindowRequested, IInspectable, IInspectable);
TYPED_EVENT(CloseRequested, IInspectable, IInspectable);
TYPED_EVENT(OpenSystemMenu, IInspectable, IInspectable);
TYPED_EVENT(QuitRequested, IInspectable, IInspectable);
TYPED_EVENT(ShowWindowChanged, IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs)
TYPED_EVENT(RequestMoveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestMoveContentArgs);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, _PropertyChangedHandlers, nullptr);
private:
@@ -297,7 +316,8 @@ namespace winrt::TerminalApp::implementation
bool _SelectTab(uint32_t tabIndex);
bool _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _MovePane(const uint32_t tabIdx);
bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args);
bool _MoveTab(const Microsoft::Terminal::Settings::Model::MoveTabArgs args);
template<typename F>
bool _ApplyToActiveControls(F f)
@@ -386,8 +406,10 @@ namespace winrt::TerminalApp::implementation
void _Find(const TerminalTab& tab);
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection);
winrt::Microsoft::Terminal::Control::TermControl _CreateNewControlAndContent(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection);
winrt::Microsoft::Terminal::Control::TermControl _SetupControl(const winrt::Microsoft::Terminal::Control::TermControl& term);
winrt::Microsoft::Terminal::Control::TermControl _AttachControlToContent(const uint64_t& contentGuid);
std::shared_ptr<Pane> _MakePane(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr,
const winrt::TerminalApp::TabBase& sourceTab = nullptr,
@@ -460,6 +482,10 @@ namespace winrt::TerminalApp::implementation
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
void _DetachPaneFromWindow(std::shared_ptr<Pane> pane);
void _DetachTabFromWindow(const winrt::com_ptr<TerminalTab>& terminalTab);
void _MoveContent(std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>&& actions, const winrt::hstring& windowName, const uint32_t tabIndex);
void _ContextMenuOpened(const IInspectable& sender, const IInspectable& args);
void _SelectionMenuOpened(const IInspectable& sender, const IInspectable& args);
void _PopulateContextMenu(const IInspectable& sender, const bool withSelection);

View File

@@ -10,7 +10,9 @@ namespace TerminalApp
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);
void Detach(Microsoft.Terminal.Control.TermControl control);
}
[default_interface] runtimeclass LastTabClosedEventArgs
@@ -22,6 +24,12 @@ namespace TerminalApp
{
String ProposedName { get; };
};
[default_interface] runtimeclass RequestMoveContentArgs
{
String Window { get; };
String Content { get; };
UInt32 TabIndex { get; };
};
interface IDialogPresenter
{
@@ -62,6 +70,7 @@ namespace TerminalApp
String KeyboardServiceDisabledText { get; };
TaskbarState TaskbarState{ get; };
void AttachContent(String content, UInt32 tabIndex);
Windows.UI.Xaml.Media.Brush TitlebarBrush { get; };
void WindowActivated(Boolean activated);
@@ -77,8 +86,12 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> IdentifyWindowsRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameWindowRequestedArgs> RenameWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.ShowWindowArgs> ShowWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, RequestMoveContentArgs> RequestMoveContent;
}
}

View File

@@ -438,16 +438,16 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - A vector of commands
std::vector<ActionAndArgs> TerminalTab::BuildStartupActions() const
std::vector<ActionAndArgs> TerminalTab::BuildStartupActions(const bool asContent) const
{
// Give initial ids (0 for the child created with this tab,
// 1 for the child after the first split.
auto state = _rootPane->BuildStartupActions(0, 1);
auto state = _rootPane->BuildStartupActions(0, 1, asContent);
{
ActionAndArgs newTabAction{};
newTabAction.Action(ShortcutAction::NewTab);
NewTabArgs newTabArgs{ state.firstPane->GetTerminalArgsForPane() };
NewTabArgs newTabArgs{ state.firstPane->GetTerminalArgsForPane(asContent) };
newTabAction.Args(newTabArgs);
state.args.emplace(state.args.begin(), std::move(newTabAction));
@@ -783,6 +783,10 @@ namespace winrt::TerminalApp::implementation
bool TerminalTab::FocusPane(const uint32_t id)
{
if (_rootPane == nullptr)
{
return false;
}
_changingActivePane = true;
const auto res = _rootPane->FocusPane(id);
_changingActivePane = false;

View File

@@ -82,7 +82,7 @@ namespace winrt::TerminalApp::implementation
void EnterZoom();
void ExitZoom();
std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions() const override;
std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions(const bool asContent = false) const override;
int GetLeafPaneCount() const noexcept;

View File

@@ -52,6 +52,7 @@ static const std::array settingsLoadWarningsLabels{
USES_RESOURCE(L"FailedToParseSubCommands"),
USES_RESOURCE(L"UnknownTheme"),
USES_RESOURCE(L"DuplicateRemainingProfilesEntry"),
USES_RESOURCE(L"InvalidUseOfContent"),
};
static_assert(settingsLoadWarningsLabels.size() == static_cast<size_t>(SettingsLoadWarnings::WARNINGS_SIZE));
@@ -1174,6 +1175,14 @@ namespace winrt::TerminalApp::implementation
_WindowProperties->WindowId(id);
}
void TerminalWindow::AttachContent(winrt::hstring content, uint32_t tabIndex)
{
if (_root)
{
_root->AttachContent(content, tabIndex);
}
}
bool TerminalWindow::ShouldImmediatelyHandoffToElevated()
{
return _root != nullptr ? _root->ShouldImmediatelyHandoffToElevated(_settings) : false;

View File

@@ -142,6 +142,8 @@ namespace winrt::TerminalApp::implementation
bool IsQuakeWindow() const noexcept { return _WindowProperties->IsQuakeWindow(); }
TerminalApp::WindowProperties WindowProperties() { return *_WindowProperties; }
void AttachContent(winrt::hstring content, uint32_t tabIndex);
// -------------------------------- WinRT Events ---------------------------------
// PropertyChanged is surprisingly not a typed event, so we'll define that one manually.
// Usually we'd just do
@@ -216,6 +218,8 @@ namespace winrt::TerminalApp::implementation
TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SettingsLoadEventArgs);
FORWARDED_TYPED_EVENT(RequestMoveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestMoveContentArgs, _root, RequestMoveContent);
#ifdef UNIT_TESTING
friend class TerminalAppLocalTests::CommandlineTest;
#endif

View File

@@ -136,5 +136,8 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, SettingsLoadEventArgs> SettingsChanged;
event Windows.Foundation.TypedEventHandler<Object, RequestMoveContentArgs> RequestMoveContent;
void AttachContent(String content, UInt32 tabIndex);
}
}

View File

@@ -154,7 +154,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
}
_setupDispatcherAndCallbacks();
UpdateSettings(settings, unfocusedAppearance);
}
void ControlCore::_setupDispatcherAndCallbacks()
{
// Get our dispatcher. If we're hosted in-proc with XAML, this will get
// us the same dispatcher as TermControl::Dispatcher(). If we're out of
// proc, this'll return null. We'll need to instead make a new
@@ -211,8 +217,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
core->_ScrollPositionChangedHandlers(*core, update);
}
});
UpdateSettings(settings, unfocusedAppearance);
}
ControlCore::~ControlCore()
@@ -225,6 +229,33 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
void ControlCore::Detach()
{
// Disable the renderer, so that it doesn't try to start any new frames
// for our engines while we're not attached to anything.
_renderer->WaitForPaintCompletionAndDisable(INFINITE);
// Clear out any throttled funcs that we had wired up to run on this UI
// thread. These will be recreated in _setupDispatcherAndCallbacks, when
// we're re-attached to a new control (on a possibly new UI thread).
_tsfTryRedrawCanvas.reset();
_updatePatternLocations.reset();
_updateScrollBar.reset();
}
void ControlCore::AttachToNewControl(const Microsoft::Terminal::Control::IKeyBindings& keyBindings)
{
_settings->KeyBindings(keyBindings);
_setupDispatcherAndCallbacks();
const auto actualNewSize = _actualFont.GetSize();
// Bubble this up, so our new control knows how big we want the font.
_FontSizeChangedHandlers(actualNewSize.width, actualNewSize.height, true);
// Turn the rendering back on now that we're ready to go.
_renderer->EnablePainting();
_AttachedHandlers(*this, nullptr);
}
bool ControlCore::Initialize(const double actualWidth,
const double actualHeight,
const double compositionScale)
@@ -304,9 +335,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// the first paint will be ignored!
_renderEngine->SetWarningCallback(std::bind(&ControlCore::_rendererWarning, this, std::placeholders::_1));
// Tell the DX Engine to notify us when the swap chain changes.
// We do this after we initially set the swapchain so as to avoid unnecessary callbacks (and locking problems)
_renderEngine->SetCallback([this](auto handle) { _renderEngineSwapChainChanged(handle); });
// Tell the render engine to notify us when the swap chain changes.
// We do this after we initially set the swapchain so as to avoid
// unnecessary callbacks (and locking problems)
_renderEngine->SetCallback([this](HANDLE handle) {
_renderEngineSwapChainChanged(handle);
});
_renderEngine->SetRetroTerminalEffect(_settings->RetroTerminalEffect());
_renderEngine->SetPixelShaderPath(_settings->PixelShaderPath());
@@ -574,7 +608,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// itself - it was initiated by the mouse wheel, or the scrollbar.
_terminal->UserScrollViewport(viewTop);
(*_updatePatternLocations)();
if (_updatePatternLocations)
{
(*_updatePatternLocations)();
}
}
void ControlCore::AdjustOpacity(const double adjustment)
@@ -971,18 +1008,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ControlCore::SizeChanged(const double width,
const double height)
{
// _refreshSizeUnderLock redraws the entire terminal.
// Don't call it if we don't have to.
if (_panelWidth == width && _panelHeight == height)
{
return;
}
_panelWidth = width;
_panelHeight = height;
auto lock = _terminal->LockForWriting();
_refreshSizeUnderLock();
SizeOrScaleChanged(width, height, _compositionScale);
}
void ControlCore::ScaleChanged(const double scale)
@@ -991,19 +1017,31 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
return;
}
SizeOrScaleChanged(_panelWidth, _panelHeight, scale);
}
void ControlCore::SizeOrScaleChanged(const double width,
const double height,
const double scale)
{
// _refreshSizeUnderLock redraws the entire terminal.
// Don't call it if we don't have to.
if (_compositionScale == scale)
if (_panelWidth == width && _panelHeight == height && _compositionScale == scale)
{
return;
}
const auto oldScale = _compositionScale;
_panelWidth = width;
_panelHeight = height;
_compositionScale = scale;
auto lock = _terminal->LockForWriting();
// _updateFont relies on the new _compositionScale set above
_updateFont();
if (oldScale != scale)
{
// _updateFont relies on the new _compositionScale set above
_updateFont();
}
_refreshSizeUnderLock();
}
@@ -1364,7 +1402,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto update{ winrt::make<ScrollPositionChangedArgs>(viewTop,
viewHeight,
bufferSize) };
if (!_inUnitTests)
if (!_inUnitTests && _updateScrollBar)
{
_updateScrollBar->Run(update);
}
@@ -1374,14 +1412,21 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
// Additionally, start the throttled update of where our links are.
(*_updatePatternLocations)();
if (_updatePatternLocations)
{
(*_updatePatternLocations)();
}
}
void ControlCore::_terminalCursorPositionChanged()
{
// When the buffer's cursor moves, start the throttled func to
// eventually dispatch a CursorPositionChanged event.
_tsfTryRedrawCanvas->Run();
if (_tsfTryRedrawCanvas)
{
_tsfTryRedrawCanvas->Run();
}
}
void ControlCore::_terminalTaskbarProgressChanged()
@@ -1409,7 +1454,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// The UI thread might try to acquire the console lock from time to time.
// --> Unlock it, so the UI doesn't hang while we're busy.
const auto suspension = _terminal->SuspendLock();
// This call will block for the duration, unless shutdown early.
_midiAudio.PlayNote(reinterpret_cast<HWND>(_owningHwnd), noteNumber, velocity, std::chrono::duration_cast<std::chrono::milliseconds>(duration));
}
@@ -1511,9 +1555,32 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_RendererWarningHandlers(*this, winrt::make<RendererWarningArgs>(hr));
}
void ControlCore::_renderEngineSwapChainChanged(const HANDLE handle)
winrt::fire_and_forget ControlCore::_renderEngineSwapChainChanged(const HANDLE sourceHandle)
{
_SwapChainChangedHandlers(*this, winrt::box_value<uint64_t>(reinterpret_cast<uint64_t>(handle)));
// `sourceHandle` is a weak ref to a HANDLE that's ultimately owned by the
// render engine's own unique_handle. We'll add another ref to it here.
// This will make sure that we always have a valid HANDLE to give to
// callers of our own SwapChainHandle method, even if the renderer is
// currently in the process of discarding this value and creating a new
// one. Callers should have already set up the SwapChainChanged
// callback, so this all works out.
winrt::handle duplicatedHandle;
const auto processHandle = GetCurrentProcess();
THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(processHandle, sourceHandle, processHandle, duplicatedHandle.put(), 0, FALSE, DUPLICATE_SAME_ACCESS));
const auto weakThis{ get_weak() };
co_await wil::resume_foreground(_dispatcher);
if (auto core{ weakThis.get() })
{
// `this` is safe to use now
_lastSwapChainHandle = std::move(duplicatedHandle);
// Now bubble the event up to the control.
_SwapChainChangedHandlers(*this, winrt::box_value<uint64_t>(reinterpret_cast<uint64_t>(_lastSwapChainHandle.get())));
}
}
void ControlCore::_rendererBackgroundColorChanged()
@@ -1660,6 +1727,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// _renderer will always exist since it's introduced in the ctor
_renderer->AddRenderEngine(pEngine);
}
void ControlCore::DetachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine)
{
_renderer->RemoveRenderEngine(pEngine);
}
bool ControlCore::IsInReadOnlyMode() const
{
@@ -1688,7 +1759,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->Write(hstr);
// Start the throttled update of where our hyperlinks are.
(*_updatePatternLocations)();
if (_updatePatternLocations)
{
(*_updatePatternLocations)();
}
}
catch (...)
{
@@ -1697,6 +1771,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
uint64_t ControlCore::SwapChainHandle() const
{
// This is only ever called by TermControl::AttachContent, which occurs
// when we're taking an existing core and moving it to a new control.
// Otherwise, we only ever use the value from the SwapChainChanged
// event.
return reinterpret_cast<uint64_t>(_lastSwapChainHandle.get());
}
// Method Description:
// - Clear the contents of the buffer. The region cleared is given by
// clearType:

View File

@@ -63,6 +63,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const double compositionScale);
void EnablePainting();
void Detach();
void UpdateSettings(const Control::IControlSettings& settings, const IControlAppearance& newAppearance);
void ApplyAppearance(const bool& focused);
Control::IControlSettings Settings() { return *_settings; };
@@ -73,8 +75,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::Microsoft::Terminal::Core::Scheme ColorScheme() const noexcept;
void ColorScheme(const winrt::Microsoft::Terminal::Core::Scheme& scheme);
uint64_t SwapChainHandle() const;
void AttachToNewControl(const Microsoft::Terminal::Control::IKeyBindings& keyBindings);
void SizeChanged(const double width, const double height);
void ScaleChanged(const double scale);
void SizeOrScaleChanged(const double width, const double height, const double scale);
void AdjustFontSize(float fontSizeDelta);
void ResetFontSize();
@@ -190,6 +196,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool& selectionNeedsToBeCopied);
void AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine);
void DetachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine);
bool IsInReadOnlyMode() const;
void ToggleReadOnlyMode();
@@ -234,6 +241,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(UpdateSelectionMarkers, IInspectable, Control::UpdateSelectionMarkersEventArgs);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable);
TYPED_EVENT(Attached, IInspectable, IInspectable);
// clang-format on
private:
@@ -256,6 +265,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
std::unique_ptr<::Microsoft::Console::Render::IRenderEngine> _renderEngine{ nullptr };
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer{ nullptr };
winrt::handle _lastSwapChainHandle{ nullptr };
FontInfoDesired _desiredFont;
FontInfo _actualFont;
winrt::hstring _actualFontFaceName;
@@ -286,6 +297,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
std::unique_ptr<til::throttled_func_trailing<>> _updatePatternLocations;
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> _updateScrollBar;
void _setupDispatcherAndCallbacks();
bool _setFontSizeUnderLock(float fontSize);
void _updateFont(const bool initialUpdate = false);
void _refreshSizeUnderLock();
@@ -315,7 +328,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
#pragma region RendererCallbacks
void _rendererWarning(const HRESULT hr);
void _renderEngineSwapChainChanged(const HANDLE handle);
winrt::fire_and_forget _renderEngineSwapChainChanged(const HANDLE handle);
void _rendererBackgroundColorChanged();
void _rendererTabColorChanged();
#pragma endregion

View File

@@ -76,6 +76,8 @@ namespace Microsoft.Terminal.Control
IControlAppearance UnfocusedAppearance { get; };
Boolean HasUnfocusedAppearance();
UInt64 SwapChainHandle { get; };
Windows.Foundation.Size FontSize { get; };
String FontFaceName { get; };
UInt16 FontWeight { get; };
@@ -108,6 +110,7 @@ namespace Microsoft.Terminal.Control
void AdjustFontSize(Single fontSizeDelta);
void SizeChanged(Double width, Double height);
void ScaleChanged(Double scale);
void SizeOrScaleChanged(Double width, Double height, Double scale);
void ToggleShaderEffects();
void ToggleReadOnlyMode();
@@ -162,5 +165,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, UpdateSelectionMarkersEventArgs> UpdateSelectionMarkers;
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> Attached;
};
}

View File

@@ -49,6 +49,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_id = _nextId.fetch_add(1, std::memory_order_relaxed);
_core = winrt::make_self<ControlCore>(settings, unfocusedAppearance, connection);
_core->Attached([weakThis = get_weak()](auto&&, auto&&) {
if (auto self{ weakThis.get() })
{
self->_AttachedHandlers(*self, nullptr);
}
});
}
uint64_t ControlInteractivity::Id()
@@ -56,6 +63,33 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _id;
}
void ControlInteractivity::Detach()
{
if (_uiaEngine)
{
// There's a potential race here where we've removed the TermControl
// from the UI tree, but the UIA engine is in the middle of a paint,
// and the UIA engine will try to dispatch to the
// TermControlAutomationPeer, which (is now)/(will very soon be) gone.
//
// To alleviate, make sure to disable the UIA engine and remove it,
// and ALSO disable the renderer. Core.Detach will take care of the
// WaitForPaintCompletionAndDisable (which will stop the renderer
// after all current engines are done painting).
//
// Simply disabling the UIA engine is not enough, because it's
// possible that it had already started presenting here.
LOG_IF_FAILED(_uiaEngine->Disable());
_core->DetachUiaEngine(_uiaEngine.get());
}
_core->Detach();
}
void ControlInteractivity::AttachToNewControl(const Microsoft::Terminal::Control::IKeyBindings& keyBindings)
{
_core->AttachToNewControl(keyBindings);
}
// Method Description:
// - Updates our internal settings. These settings should be
// interactivity-specific. Right now, we primarily update _rowsToScroll
@@ -676,7 +710,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
try
{
const auto autoPeer = winrt::make_self<implementation::InteractivityAutomationPeer>(this);
if (_uiaEngine)
{
_core->DetachUiaEngine(_uiaEngine.get());
}
_uiaEngine = std::make_unique<::Microsoft::Console::Render::UiaEngine>(autoPeer.get());
_core->AttachUiaEngine(_uiaEngine.get());
return *autoPeer;

View File

@@ -45,6 +45,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Control::ControlCore Core();
void Close();
void Detach();
Control::InteractivityAutomationPeer OnCreateAutomationPeer();
::Microsoft::Console::Render::IRenderData* GetRenderData() const;
@@ -88,12 +89,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool ManglePathsForWsl();
uint64_t Id();
void AttachToNewControl(const Microsoft::Terminal::Control::IKeyBindings& keyBindings);
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(Attached, IInspectable, IInspectable);
TYPED_EVENT(Closed, IInspectable, IInspectable);
private:

View File

@@ -25,6 +25,9 @@ namespace Microsoft.Terminal.Control
UInt64 Id { get; };
void AttachToNewControl(Microsoft.Terminal.Control.IKeyBindings keyBindings);
void Detach();
void Close();
InteractivityAutomationPeer OnCreateAutomationPeer();
@@ -69,11 +72,12 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;
event Windows.Foundation.TypedEventHandler<Object, Object> Attached;
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

@@ -69,16 +69,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_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.
_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.
_revokers.RendererEnteredErrorState = _core.RendererEnteredErrorState(winrt::auto_revoke, { get_weak(), &TermControl::_RendererEnteredErrorState });
// IMPORTANT! Set this callback up sooner rather than later. If we do it
// 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!
_revokers.RendererWarning = _core.RendererWarning(winrt::auto_revoke, { get_weak(), &TermControl::_RendererWarning });
// ALSO IMPORTANT: Make sure to set this callback up in the ctor, so
// that we won't miss any swap chain changes.
_revokers.SwapChainChanged = _core.SwapChainChanged(winrt::auto_revoke, { get_weak(), &TermControl::RenderEngineSwapChainChanged });
// 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.
@@ -113,8 +115,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// in any layout change chain. That gives us great flexibility in finding the right point
// at which to initialize our renderer (and our terminal).
// Any earlier than the last layout update and we may not know the terminal's starting size.
if (_InitializeTerminal())
if (_InitializeTerminal(InitializeReason::Create))
{
// Only let this succeed once.
_layoutUpdatedRevoker.revoke();
@@ -148,6 +149,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
});
// These events might all be triggered by the connection, but that
// should be drained and closed before we complete destruction. So these
// are safe.
//
// NOTE: _ScrollPositionChanged has to be registered after we set up the
// _updateScrollBar func. Otherwise, we could get a callback from an
// attached content before we set up the throttled func, and that'll A/V
_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 });
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
_autoScrollTimer.Tick({ get_weak(), &TermControl::_UpdateAutoScroll });
@@ -208,6 +220,49 @@ namespace winrt::Microsoft::Terminal::Control::implementation
});
}
// Function Description:
// - Static helper for building a new TermControl from an already existing
// content. We'll attach the existing swapchain to this new control's
// SwapChainPanel. The IKeyBindings might belong to a non-agile object on
// a new thread, so we'll hook up the core to these new bindings.
// Arguments:
// - content: The preexisting ControlInteractivity to connect to.
// - keybindings: The new IKeyBindings instance to use for this control.
// Return Value:
// - The newly constructed TermControl.
Control::TermControl TermControl::NewControlByAttachingContent(Control::ControlInteractivity content,
const Microsoft::Terminal::Control::IKeyBindings& keyBindings)
{
const auto term{ winrt::make_self<TermControl>(content) };
term->_initializeForAttach(keyBindings);
return *term;
}
void TermControl::_initializeForAttach(const Microsoft::Terminal::Control::IKeyBindings& keyBindings)
{
_AttachDxgiSwapChainToXaml(reinterpret_cast<HANDLE>(_core.SwapChainHandle()));
_interactivity.AttachToNewControl(keyBindings);
// Initialize the terminal only once the swapchainpanel is loaded - that
// way, we'll be able to query the real pixel size it got on layout
auto r = SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
// Replace the normal initialize routine with one that will allow up
// to complete initialization even though the Core was already
// initialized.
if (_InitializeTerminal(InitializeReason::Reattach))
{
// Only let this succeed once.
_layoutUpdatedRevoker.revoke();
}
});
_layoutUpdatedRevoker.swap(r);
}
uint64_t TermControl::ContentId() const
{
return _interactivity.Id();
}
void TermControl::_throttledUpdateScrollbar(const ScrollBarUpdate& update)
{
// Assumptions:
@@ -811,25 +866,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core.ConnectionState();
}
winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged(IInspectable /*sender*/, IInspectable args)
void TermControl::RenderEngineSwapChainChanged(IInspectable /*sender*/, IInspectable args)
{
// This event is only registered during terminal initialization,
// so we don't need to check _initializedTerminal.
const auto weakThis{ get_weak() };
// Create a copy of the swap chain HANDLE in args, since we don't own that parameter.
// By the time we return from the co_await below, it might be deleted already.
winrt::handle handle;
const auto processHandle = GetCurrentProcess();
const auto sourceHandle = reinterpret_cast<HANDLE>(winrt::unbox_value<uint64_t>(args));
THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(processHandle, sourceHandle, processHandle, handle.put(), 0, FALSE, DUPLICATE_SAME_ACCESS));
co_await wil::resume_foreground(Dispatcher());
if (auto control{ weakThis.get() })
{
_AttachDxgiSwapChainToXaml(handle.get());
}
// This event comes in on the UI thread
HANDLE h = reinterpret_cast<HANDLE>(winrt::unbox_value<uint64_t>(args));
_AttachDxgiSwapChainToXaml(h);
}
// Method Description:
@@ -880,7 +921,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
nativePanel->SetSwapChainHandle(swapChainHandle);
}
bool TermControl::_InitializeTerminal()
bool TermControl::_InitializeTerminal(const InitializeReason reason)
{
if (_initializedTerminal)
{
@@ -900,22 +941,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return false;
}
// IMPORTANT! Set this callback up sooner rather than later. If we do it
// 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!
_revokers.RendererWarning = _core.RendererWarning(winrt::auto_revoke, { get_weak(), &TermControl::_RendererWarning });
const auto coreInitialized = _core.Initialize(panelWidth,
panelHeight,
panelScaleX);
if (!coreInitialized)
// If we're re-attaching an existing content, then we want to proceed even though the Terminal was already initialized.
if (reason == InitializeReason::Create)
{
return false;
const auto coreInitialized = _core.Initialize(panelWidth,
panelHeight,
panelScaleX);
if (!coreInitialized)
{
return false;
}
_interactivity.Initialize();
}
else
{
_core.SizeOrScaleChanged(panelWidth, panelHeight, panelScaleX);
}
_interactivity.Initialize();
_revokers.SwapChainChanged = _core.SwapChainChanged(winrt::auto_revoke, { get_weak(), &TermControl::RenderEngineSwapChainChanged });
_core.EnablePainting();
auto bufferHeight = _core.BufferHeight();
@@ -1759,7 +1801,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// GH#5421: Enable the UiaEngine before checking for the SearchBox
// That way, new selections are notified to automation clients.
// The _uiaEngine lives in _interactivity, so call into there to enable it.
_interactivity.GotFocus();
if (_interactivity)
{
_interactivity.GotFocus();
}
// If the searchbox is focused, we don't want TSFInputControl to think
// it has focus so it doesn't intercept IME input. We also don't want the
@@ -1814,7 +1860,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// This will disable the accessibility notifications, because the
// UiaEngine lives in ControlInteractivity
_interactivity.LostFocus();
if (_interactivity)
{
_interactivity.LostFocus();
}
if (TSFInputControl() != nullptr)
{
@@ -2076,9 +2125,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TSFInputControl().Close();
_autoScrollTimer.Stop();
_interactivity.Close();
if (!_detached)
{
_interactivity.Close();
}
}
}
void TermControl::Detach()
{
_revokers = {};
Control::ControlInteractivity old{ nullptr };
std::swap(old, _interactivity);
old.Detach();
_detached = true;
}
// Method Description:
// - Scrolls the viewport of the terminal and updates the scroll bar accordingly

View File

@@ -27,14 +27,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
TermControl(Control::ControlInteractivity content);
TermControl(IControlSettings settings,
Control::IControlAppearance unfocusedAppearance,
TerminalConnection::ITerminalConnection connection);
TermControl(IControlSettings settings, Control::IControlAppearance unfocusedAppearance, TerminalConnection::ITerminalConnection connection);
static Control::TermControl NewControlByAttachingContent(Control::ControlInteractivity content, const Microsoft::Terminal::Control::IKeyBindings& keyBindings);
winrt::fire_and_forget UpdateControlSettings(Control::IControlSettings settings);
winrt::fire_and_forget UpdateControlSettings(Control::IControlSettings settings, Control::IControlAppearance unfocusedAppearance);
IControlSettings Settings() const;
uint64_t ContentId() const;
hstring GetProfileName() const;
bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
@@ -93,7 +95,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ToggleShaderEffects();
winrt::fire_and_forget RenderEngineSwapChainChanged(IInspectable sender, IInspectable args);
void RenderEngineSwapChainChanged(IInspectable sender, IInspectable args);
void _AttachDxgiSwapChainToXaml(HANDLE swapChainHandle);
winrt::fire_and_forget _RendererEnteredErrorState(IInspectable sender, IInspectable args);
@@ -139,6 +141,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void AdjustOpacity(const double opacity, const bool relative);
void Detach();
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
// -------------------------------- WinRT Events ---------------------------------
// clang-format off
@@ -224,6 +228,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool _showMarksInScrollbar{ false };
bool _isBackgroundLight{ false };
bool _detached{ false };
winrt::Windows::Foundation::Collections::IObservableVector<winrt::Windows::UI::Xaml::Controls::ICommandBarElement> _originalPrimaryElements{ nullptr };
winrt::Windows::Foundation::Collections::IObservableVector<winrt::Windows::UI::Xaml::Controls::ICommandBarElement> _originalSecondaryElements{ nullptr };
@@ -242,6 +247,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _closing;
}
void _initializeForAttach(const Microsoft::Terminal::Control::IKeyBindings& keyBindings);
void _UpdateSettingsFromUIThread();
void _UpdateAppearanceFromUIThread(Control::IControlAppearance newAppearance);
void _ApplyUISettings();
@@ -254,7 +261,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
static bool _isColorLight(til::color bg) noexcept;
void _changeBackgroundOpacity();
bool _InitializeTerminal();
enum InitializeReason : bool
{
Create,
Reattach
};
bool _InitializeTerminal(const InitializeReason reason);
void _SetFontSize(int fontSize);
void _TappedHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& e);
void _KeyDownHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);

View File

@@ -24,6 +24,8 @@ namespace Microsoft.Terminal.Control
IControlAppearance unfocusedAppearance,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
static TermControl NewControlByAttachingContent(ControlInteractivity content, Microsoft.Terminal.Control.IKeyBindings keyBindings);
static Windows.Foundation.Size GetProposedDimensions(IControlSettings settings,
UInt32 dpi,
Int32 commandlineCols,
@@ -32,6 +34,8 @@ namespace Microsoft.Terminal.Control
void UpdateControlSettings(IControlSettings settings);
void UpdateControlSettings(IControlSettings settings, IControlAppearance unfocusedAppearance);
UInt64 ContentId{ get; };
Microsoft.Terminal.Control.IControlSettings Settings { get; };
event FontSizeChangedEventArgs FontSizeChanged;
@@ -107,5 +111,7 @@ namespace Microsoft.Terminal.Control
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
void ColorSelection(SelectionColor fg, SelectionColor bg, Microsoft.Terminal.Core.MatchMode matchMode);
void Detach();
}
}

View File

@@ -427,4 +427,30 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
const auto found = GeneratedActionNames.find(_Action);
return found != GeneratedActionNames.end() ? found->second : L"";
}
winrt::hstring ActionAndArgs::Serialize(const winrt::Windows::Foundation::Collections::IVector<Model::ActionAndArgs>& args)
{
Json::Value json{ Json::objectValue };
JsonUtils::SetValueForKey(json, "actions", args);
Json::StreamWriterBuilder wbuilder;
auto str = Json::writeString(wbuilder, json);
return winrt::to_hstring(str);
}
winrt::Windows::Foundation::Collections::IVector<Model::ActionAndArgs> ActionAndArgs::Deserialize(winrt::hstring content)
{
auto data = winrt::to_string(content);
std::string errs;
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
Json::Value root;
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
{
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
}
winrt::Windows::Foundation::Collections::IVector<Model::ActionAndArgs> result{ nullptr };
JsonUtils::GetValueForKey(root, "actions", result);
return result;
}
}

View File

@@ -16,6 +16,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
std::vector<SettingsLoadWarnings>& warnings);
static Json::Value ToJson(const Model::ActionAndArgs& val);
static winrt::hstring Serialize(const winrt::Windows::Foundation::Collections::IVector<Model::ActionAndArgs>& args);
static winrt::Windows::Foundation::Collections::IVector<Model::ActionAndArgs> Deserialize(winrt::hstring content);
ActionAndArgs() = default;
ActionAndArgs(ShortcutAction action);
ActionAndArgs(ShortcutAction action, IActionArgs args) :

View File

@@ -13,6 +13,7 @@
#include "ResizePaneArgs.g.cpp"
#include "MoveFocusArgs.g.cpp"
#include "MovePaneArgs.g.cpp"
#include "MoveTabArgs.g.cpp"
#include "SwapPaneArgs.g.cpp"
#include "AdjustFontSizeArgs.g.cpp"
#include "SendInputArgs.g.cpp"
@@ -28,7 +29,6 @@
#include "CloseOtherTabsArgs.g.cpp"
#include "CloseTabsAfterArgs.g.cpp"
#include "CloseTabArgs.g.cpp"
#include "MoveTabArgs.g.cpp"
#include "ScrollToMarkArgs.g.cpp"
#include "AddMarkArgs.g.cpp"
#include "FindMatchArgs.g.cpp"
@@ -245,6 +245,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
winrt::hstring MovePaneArgs::GenerateName() const
{
if (!Window().empty())
{
return winrt::hstring{
fmt::format(L"{}, window:{}, tab index:{}", RS_(L"MovePaneCommandKey"), Window(), TabIndex())
};
}
return winrt::hstring{
fmt::format(L"{}, tab index:{}", RS_(L"MovePaneCommandKey"), TabIndex())
};
@@ -648,6 +654,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
winrt::hstring MoveTabArgs::GenerateName() const
{
if (!Window().empty())
{
return winrt::hstring{
fmt::format(std::wstring_view(RS_(L"MoveTabToWindowCommandKey")),
Window())
};
}
winrt::hstring directionString;
switch (Direction())
{

View File

@@ -96,8 +96,9 @@ private:
X(Windows::Foundation::IReference<Control::CopyFormat>, CopyFormatting, "copyFormatting", false, nullptr)
////////////////////////////////////////////////////////////////////////////////
#define MOVE_PANE_ARGS(X) \
X(uint32_t, TabIndex, "index", false, 0)
#define MOVE_PANE_ARGS(X) \
X(uint32_t, TabIndex, "index", false, 0) \
X(winrt::hstring, Window, "window", false, L"")
////////////////////////////////////////////////////////////////////////////////
#define SWITCH_TO_TAB_ARGS(X) \
@@ -172,8 +173,15 @@ private:
X(Windows::Foundation::IReference<uint32_t>, Index, "index", false, nullptr)
////////////////////////////////////////////////////////////////////////////////
#define MOVE_TAB_ARGS(X) \
X(MoveTabDirection, Direction, "direction", args->Direction() == MoveTabDirection::None, MoveTabDirection::None)
// Interestingly, the order MATTERS here. Window has to be BEFORE Direction,
// because otherwise we won't have parsed the Window yet when we validate the
// Direction.
#define MOVE_TAB_ARGS(X) \
X(winrt::hstring, Window, "window", false, L"") \
X(MoveTabDirection, Direction, "direction", (args->Direction() == MoveTabDirection::None) && (args->Window().empty()), MoveTabDirection::None)
// Other ideas:
// X(uint32_t, TabIndex, "index", false, 0) \ // target? source?
////////////////////////////////////////////////////////////////////////////////
#define SCROLL_UP_ARGS(X) \
@@ -276,6 +284,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
ACTION_ARG(Windows::Foundation::IReference<bool>, SuppressApplicationTitle, nullptr);
ACTION_ARG(winrt::hstring, ColorScheme);
ACTION_ARG(Windows::Foundation::IReference<bool>, Elevate, nullptr);
ACTION_ARG(uint64_t, ContentId);
static constexpr std::string_view CommandlineKey{ "commandline" };
static constexpr std::string_view StartingDirectoryKey{ "startingDirectory" };
@@ -286,6 +295,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static constexpr std::string_view SuppressApplicationTitleKey{ "suppressApplicationTitle" };
static constexpr std::string_view ColorSchemeKey{ "colorScheme" };
static constexpr std::string_view ElevateKey{ "elevate" };
static constexpr std::string_view ContentKey{ "__content" };
public:
hstring GenerateName() const;
@@ -304,7 +314,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
otherAsUs->_Profile == _Profile &&
otherAsUs->_SuppressApplicationTitle == _SuppressApplicationTitle &&
otherAsUs->_ColorScheme == _ColorScheme &&
otherAsUs->_Elevate == _Elevate;
otherAsUs->_Elevate == _Elevate &&
otherAsUs->_ContentId == _ContentId;
}
return false;
};
@@ -321,6 +332,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
JsonUtils::GetValueForKey(json, SuppressApplicationTitleKey, args->_SuppressApplicationTitle);
JsonUtils::GetValueForKey(json, ColorSchemeKey, args->_ColorScheme);
JsonUtils::GetValueForKey(json, ElevateKey, args->_Elevate);
JsonUtils::GetValueForKey(json, ContentKey, args->_ContentId);
return *args;
}
static Json::Value ToJson(const Model::NewTerminalArgs& val)
@@ -340,6 +352,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
JsonUtils::SetValueForKey(json, SuppressApplicationTitleKey, args->_SuppressApplicationTitle);
JsonUtils::SetValueForKey(json, ColorSchemeKey, args->_ColorScheme);
JsonUtils::SetValueForKey(json, ElevateKey, args->_Elevate);
JsonUtils::SetValueForKey(json, ContentKey, args->_ContentId);
return json;
}
Model::NewTerminalArgs Copy() const
@@ -354,6 +367,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
copy->_SuppressApplicationTitle = _SuppressApplicationTitle;
copy->_ColorScheme = _ColorScheme;
copy->_Elevate = _Elevate;
copy->_ContentId = _ContentId;
return *copy;
}
size_t Hash() const
@@ -373,6 +387,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
h.write(SuppressApplicationTitle());
h.write(ColorScheme());
h.write(Elevate());
h.write(ContentId());
}
};
}
@@ -436,6 +451,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<NewTabArgs>();
args->_TerminalArgs = NewTerminalArgs::FromJson(json);
// Don't let the user specify the __content property in their
// settings. That's an internal-use-only property.
if (args->_TerminalArgs.ContentId())
{
return { *args, { SettingsLoadWarnings::InvalidUseOfContent } };
}
return { *args, {} };
}
static Json::Value ToJson(const IActionArgs& val)
@@ -515,6 +537,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
return { nullptr, { SettingsLoadWarnings::InvalidSplitSize } };
}
// Don't let the user specify the __content property in their
// settings. That's an internal-use-only property.
if (args->_TerminalArgs.ContentId())
{
return { *args, { SettingsLoadWarnings::InvalidUseOfContent } };
}
return { *args, {} };
}
static Json::Value ToJson(const IActionArgs& val)
@@ -574,6 +604,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<NewWindowArgs>();
args->_TerminalArgs = NewTerminalArgs::FromJson(json);
// Don't let the user specify the __content property in their
// settings. That's an internal-use-only property.
if (args->_TerminalArgs.ContentId())
{
return { *args, { SettingsLoadWarnings::InvalidUseOfContent } };
}
return { *args, {} };
}
static Json::Value ToJson(const IActionArgs& val)

View File

@@ -132,6 +132,8 @@ namespace Microsoft.Terminal.Settings.Model
// not modify whatever the profile's value is (either true or false)
Windows.Foundation.IReference<Boolean> Elevate;
UInt64 ContentId{ get; set; };
Boolean Equals(NewTerminalArgs other);
String GenerateName();
String ToCommandline();
@@ -158,8 +160,9 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass MovePaneArgs : IActionArgs
{
MovePaneArgs(UInt32 tabIndex);
MovePaneArgs(UInt32 tabIndex, String Window);
UInt32 TabIndex;
String Window;
};
[default_interface] runtimeclass SwitchToTabArgs : IActionArgs
@@ -276,8 +279,9 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass MoveTabArgs : IActionArgs
{
MoveTabArgs(MoveTabDirection direction);
MoveTabArgs(String window, MoveTabDirection direction);
MoveTabDirection Direction { get; };
String Window { get; };
};
[default_interface] runtimeclass ScrollUpArgs : IActionArgs

View File

@@ -66,9 +66,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
hstring IconPath() const noexcept;
void IconPath(const hstring& val);
winrt::Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker propertyChangedRevoker;
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None);
WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs);

View File

@@ -24,11 +24,14 @@ namespace Microsoft.Terminal.Settings.Model
ActionAndArgs();
ActionAndArgs(ShortcutAction action, IActionArgs args);
static String Serialize(IVector<ActionAndArgs> args);
static IVector<ActionAndArgs> Deserialize(String content);
IActionArgs Args;
ShortcutAction Action;
};
[default_interface] runtimeclass Command : Windows.UI.Xaml.Data.INotifyPropertyChanged
[default_interface] runtimeclass Command
{
Command();

View File

@@ -692,6 +692,30 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
}
};
template<>
struct ConversionTrait<uint64_t>
{
unsigned int FromJson(const Json::Value& json)
{
return json.asUInt();
}
bool CanConvert(const Json::Value& json)
{
return json.isUInt();
}
Json::Value ToJson(const uint64_t& val)
{
return val;
}
std::string TypeDescription() const
{
return "number (>= 0)";
}
};
template<>
struct ConversionTrait<float>
{

View File

@@ -166,6 +166,10 @@
<value>Move tab {0}</value>
<comment>{0} will be replaced with a "forward" / "backward"</comment>
</data>
<data name="MoveTabToWindowCommandKey" xml:space="preserve">
<value>Move tab to window "{0}"</value>
<comment>{0} will be replaced with a user-specified name of a window</comment>
</data>
<data name="MoveTabDirectionForward" xml:space="preserve">
<value>forward</value>
</data>

View File

@@ -23,6 +23,7 @@ namespace Microsoft.Terminal.Settings.Model
FailedToParseSubCommands,
UnknownTheme,
DuplicateRemainingProfilesEntry,
InvalidUseOfContent,
WARNINGS_SIZE // IMPORTANT: This MUST be the last value in this enum. It's an unused placeholder.
};

View File

@@ -85,6 +85,7 @@ namespace RemotingUnitTests
winrt::hstring GetWindowLayout() DIE;
void RequestQuitAll() DIE;
void Quit() DIE;
void AttachContentToWindow(Remoting::AttachRequest) DIE;
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, Remoting::WindowActivatedArgs);
TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, Remoting::CommandlineArgs);
TYPED_EVENT(IdentifyWindowsRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
@@ -96,6 +97,7 @@ namespace RemotingUnitTests
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(GetWindowLayoutRequested, winrt::Windows::Foundation::IInspectable, Remoting::GetWindowLayoutArgs);
TYPED_EVENT(AttachRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::AttachRequest);
};
// Same idea.
@@ -114,6 +116,7 @@ namespace RemotingUnitTests
bool DoesQuakeWindowExist() DIE;
winrt::Windows::Foundation::Collections::IVectorView<Remoting::PeasantInfo> GetPeasantInfos() DIE;
winrt::Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts() DIE;
void RequestMoveContent(winrt::hstring, winrt::hstring, uint32_t) DIE;
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);

View File

@@ -272,6 +272,8 @@ void AppHost::_HandleCommandlineArgs()
_windowLogic.WindowName(_peasant.WindowName());
_windowLogic.WindowId(_peasant.GetID());
_revokers.AttachRequested = _peasant.AttachRequested(winrt::auto_revoke, { this, &AppHost::_handleAttach });
}
}
@@ -381,6 +383,7 @@ void AppHost::Initialize()
_revokers.OpenSystemMenu = _windowLogic.OpenSystemMenu(winrt::auto_revoke, { this, &AppHost::_OpenSystemMenu });
_revokers.QuitRequested = _windowLogic.QuitRequested(winrt::auto_revoke, { this, &AppHost::_RequestQuitAll });
_revokers.ShowWindowChanged = _windowLogic.ShowWindowChanged(winrt::auto_revoke, { this, &AppHost::_ShowWindowChanged });
_revokers.RequestMoveContent = _windowLogic.RequestMoveContent(winrt::auto_revoke, { this, &AppHost::_handleMoveContent });
// BODGY
// On certain builds of Windows, when Terminal is set as the default
@@ -1217,6 +1220,18 @@ winrt::TerminalApp::TerminalWindow AppHost::Logic()
return _windowLogic;
}
void AppHost::_handleMoveContent(const winrt::Windows::Foundation::IInspectable& /*sender*/,
winrt::TerminalApp::RequestMoveContentArgs args)
{
_windowManager.RequestMoveContent(args.Window(), args.Content(), args.TabIndex());
}
void AppHost::_handleAttach(const winrt::Windows::Foundation::IInspectable& /*sender*/,
winrt::Microsoft::Terminal::Remoting::AttachRequest args)
{
_windowLogic.AttachContent(args.Content(), args.TabIndex());
}
// Bubble the update settings request up to the emperor. We're being called on
// the Window thread, but the Emperor needs to update the settings on the _main_
// thread.

View File

@@ -110,9 +110,6 @@ private:
void _CloseRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
void _QuitAllRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs& args);
void _ShowWindowChanged(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Control::ShowWindowArgs& args);
@@ -123,6 +120,11 @@ private:
void _initialResizeAndRepositionWindow(const HWND hwnd, RECT proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode);
void _handleMoveContent(const winrt::Windows::Foundation::IInspectable& sender,
winrt::TerminalApp::RequestMoveContentArgs args);
void _handleAttach(const winrt::Windows::Foundation::IInspectable& sender,
winrt::Microsoft::Terminal::Remoting::AttachRequest args);
void _requestUpdateSettings();
winrt::event_token _GetWindowLayoutRequestedToken;
@@ -138,6 +140,8 @@ private:
winrt::Microsoft::Terminal::Remoting::Peasant::SummonRequested_revoker peasantSummonRequested;
winrt::Microsoft::Terminal::Remoting::Peasant::DisplayWindowIdRequested_revoker peasantDisplayWindowIdRequested;
winrt::Microsoft::Terminal::Remoting::Peasant::QuitRequested_revoker peasantQuitRequested;
winrt::Microsoft::Terminal::Remoting::Peasant::AttachRequested_revoker AttachRequested;
winrt::TerminalApp::TerminalWindow::CloseRequested_revoker CloseRequested;
winrt::TerminalApp::TerminalWindow::RequestedThemeChanged_revoker RequestedThemeChanged;
winrt::TerminalApp::TerminalWindow::FullscreenChanged_revoker FullscreenChanged;
@@ -156,6 +160,7 @@ private:
winrt::TerminalApp::TerminalWindow::OpenSystemMenu_revoker OpenSystemMenu;
winrt::TerminalApp::TerminalWindow::QuitRequested_revoker QuitRequested;
winrt::TerminalApp::TerminalWindow::ShowWindowChanged_revoker ShowWindowChanged;
winrt::TerminalApp::TerminalWindow::RequestMoveContent_revoker RequestMoveContent;
winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged;
winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged;

View File

@@ -1306,6 +1306,20 @@ void Renderer::AddRenderEngine(_In_ IRenderEngine* const pEngine)
THROW_HR_MSG(E_UNEXPECTED, "engines array is full");
}
void Renderer::RemoveRenderEngine(_In_ IRenderEngine* const pEngine)
{
THROW_HR_IF_NULL(E_INVALIDARG, pEngine);
for (auto& p : _engines)
{
if (p == pEngine)
{
p = nullptr;
return;
}
}
}
// Method Description:
// - Registers a callback for when the background color is changed
// Arguments:

View File

@@ -82,6 +82,7 @@ namespace Microsoft::Console::Render
void WaitUntilCanRender();
void AddRenderEngine(_In_ IRenderEngine* const pEngine);
void RemoveRenderEngine(_In_ IRenderEngine* const pEngine);
void SetBackgroundColorChangedCallback(std::function<void()> pfn);
void SetFrameColorChangedCallback(std::function<void()> pfn);