Enable dragging tabs between windows (#14901)

_Behold, the penultimate chapter in the saga of tear-out! This
significant update bestows upon the user the power to transport tabs
betwixt Terminal windows. Alas, the drag and drop capabilities of
TabView are not yet refined, so this PR primarily concerns itself with
the intricacies of plumbing. When a tab is extracted and deposited
elsewhere, it is necessary to have the recipient make an inquiry to the
Monarch, who in turn will beseech the sender to transmit the tab
content, akin to the act of moving a tab. Curious it may seem, but the
method has proven effective._

The penultimate tear-out PR. This PR enables the user to move tabs from
one Terminal window to another. The TabView drag/drop APIs have some
rough edges, so this PR is mostly plumbing. When a tab is drag/dropped,
we need to get the recipient to ask the Monarch to ask the sender to
send the tab content, like a MoveTab action. Wacky, but it works.

There's a LONG tail of UX gaps. Those I'm going to track in #14900. It
is more valuable for us to merge this now than to figure out workarounds
immediately.

The next PR will be the last main PR in this saga - in which we enable
dragging a tab out of the window and dropping to create a new window.


* Closes #1256
* Related to #5000
* Follow-ups get to go in #14900 


## Detailed description

As I mentioned, it's mostly plumbing. The order that we get tab drag
events is... unfortunate... for our use case. So we do a lot of sending
`RequestReceiveContentArgs` up and down between windows, just to
communicate who the tab was dropped on to whomever the tab was dragged
from.

There's a diagram for this that I originally put in
https://github.com/microsoft/terminal/issues/5000#issuecomment-1435328038:

```mermaid  
sequenceDiagram
    participant Source
    participant Target
    participant Monarch

    Note Left of Source: _onTabDragStarting
    Source --> Source: stash dragged content
    Source --> Source: pack window ID into DataPackage

    Source ->> Target: Drag tab
    Note right of Target: _onTabStripDragOver
    Target ->> Target: AcceptedOperation(DataPackageOperation::Move)
    
    Source --> Target: Release mouse (to drop)
    
    Note right of Target: _onTabStripDrop
    Target --> Target: get WindowID from DataPackage
    Target -) Monarch: Request that WindowID sends content to us<br>RequestRecieveContent
    Monarch -) Source: Tell to send content to Target.Id<br>RequestSendContent, SendContent
    Source --> Source: detach our content
    Source -) Monarch: RequestMoveContent(stashed, target.id)
    Monarch -) Target: AttachContent(stashed)

    # Target -->> Source: 
    # Note Left of Source: TabViewTabDragStartingEventArgs<br>.OperationCompleted
    # Note Left of Source: _onTabDroppedCompleted
```

Really really though, let's try to avoid nits about the UX at this time.
This PR works with what we've got. Mail threads are percolating. I've
got 19 chapters worth of Hobbit branch names to use for those follow
ups.
This commit is contained in:
Mike Griese 2023-03-30 10:20:23 -05:00 committed by GitHub
parent 17a5b77335
commit 9514c1191a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 364 additions and 14 deletions

View File

@ -1109,4 +1109,39 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// drag/drop)
}
}
// Very similar to the above. Someone came and told us that they were the target of a drag/drop, and they know who started it.
// We will go tell the person who started it that they should send that target the content which was dragged.
void Monarch::RequestSendContent(const Remoting::RequestReceiveContentArgs& args)
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SendContent_Requested",
TraceLoggingUInt64(args.SourceWindow(), "source", "The window which started the drag"),
TraceLoggingUInt64(args.TargetWindow(), "target", "The window which was the target of the drop"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
if (auto senderPeasant{ _getPeasant(args.SourceWindow()) })
{
senderPeasant.SendContent(args);
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SendContent_Completed",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SendContent_NoWindow",
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

@ -82,6 +82,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
void RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex);
void RequestSendContent(const Remoting::RequestReceiveContentArgs& args);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);

View File

@ -41,8 +41,7 @@ namespace Microsoft.Terminal.Remoting
Windows.Foundation.IReference<UInt64> WindowID;
}
[default_interface] runtimeclass QuitAllRequestedArgs
{
[default_interface] runtimeclass QuitAllRequestedArgs {
QuitAllRequestedArgs();
Windows.Foundation.IAsyncAction BeforeQuitAllAction;
}
@ -71,6 +70,7 @@ namespace Microsoft.Terminal.Remoting
Windows.Foundation.Collections.IVector<String> GetAllWindowLayouts();
void RequestMoveContent(String window, String content, UInt32 tabIndex);
void RequestSendContent(RequestReceiveContentArgs args);
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;

View File

@ -9,6 +9,7 @@
#include "Peasant.g.cpp"
#include "../../types/inc/utils.hpp"
#include "AttachRequest.g.cpp"
#include "RequestReceiveContentArgs.g.cpp"
using namespace winrt;
using namespace winrt::Microsoft::Terminal;
@ -327,4 +328,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
return args->WindowLayoutJson();
}
void Peasant::SendContent(const Remoting::RequestReceiveContentArgs& args)
{
_SendContentRequestedHandlers(*this, args);
}
}

View File

@ -6,6 +6,7 @@
#include "Peasant.g.h"
#include "RenameRequestArgs.h"
#include "AttachRequest.g.h"
#include "RequestReceiveContentArgs.g.h"
namespace RemotingUnitTests
{
@ -25,6 +26,19 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_TabIndex{ tabIndex } {};
};
struct RequestReceiveContentArgs : RequestReceiveContentArgsT<RequestReceiveContentArgs>
{
WINRT_PROPERTY(uint64_t, SourceWindow);
WINRT_PROPERTY(uint64_t, TargetWindow);
WINRT_PROPERTY(uint32_t, TabIndex);
public:
RequestReceiveContentArgs(const uint64_t src, const uint64_t tgt, const uint32_t tabIndex) :
_SourceWindow{ src },
_TargetWindow{ tgt },
_TabIndex{ tabIndex } {};
};
struct Peasant : public PeasantT<Peasant>
{
Peasant();
@ -52,6 +66,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
winrt::hstring GetWindowLayout();
void SendContent(const winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs& args);
WINRT_PROPERTY(winrt::hstring, WindowName);
WINRT_PROPERTY(winrt::hstring, ActiveTabTitle);
@ -70,6 +85,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(GetWindowLayoutRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::GetWindowLayoutArgs);
TYPED_EVENT(AttachRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::AttachRequest);
TYPED_EVENT(SendContentRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs);
private:
Peasant(const uint64_t testPID);
@ -87,4 +103,5 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
{
BASIC_FACTORY(Peasant);
BASIC_FACTORY(RequestReceiveContentArgs);
}

View File

@ -55,6 +55,13 @@ namespace Microsoft.Terminal.Remoting
String Content { get; };
UInt32 TabIndex { get; };
}
[default_interface] runtimeclass RequestReceiveContentArgs {
RequestReceiveContentArgs(UInt64 src, UInt64 tgt, UInt32 tabIndex);
UInt64 SourceWindow { get; };
UInt64 TargetWindow { get; };
UInt32 TabIndex { get; };
};
interface IPeasant
{
@ -82,7 +89,7 @@ namespace Microsoft.Terminal.Remoting
String GetWindowLayout();
void AttachContentToWindow(AttachRequest request);
void SendContent(RequestReceiveContentArgs args);
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
@ -98,6 +105,8 @@ namespace Microsoft.Terminal.Remoting
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
event Windows.Foundation.TypedEventHandler<Object, AttachRequest> AttachRequested;
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> SendContentRequested;
};
[default_interface] runtimeclass Peasant : IPeasant

View File

@ -422,4 +422,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_monarch.RequestMoveContent(window, content, tabIndex);
}
winrt::fire_and_forget WindowManager::RequestSendContent(Remoting::RequestReceiveContentArgs args)
{
co_await winrt::resume_background();
_monarch.RequestSendContent(args);
}
}

View File

@ -45,6 +45,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
bool DoesQuakeWindowExist();
winrt::fire_and_forget RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex);
winrt::fire_and_forget RequestSendContent(Remoting::RequestReceiveContentArgs args);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);

View File

@ -27,6 +27,7 @@ namespace Microsoft.Terminal.Remoting
Boolean DoesQuakeWindowExist();
void RequestMoveContent(String window, String content, UInt32 tabIndex);
void RequestSendContent(RequestReceiveContentArgs args);
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;

View File

@ -264,6 +264,19 @@ namespace winrt::TerminalApp::implementation
tab->_RequestFocusActiveControlHandlers();
}
});
// BODGY: When the tab is drag/dropped, the TabView gets a
// TabDragStarting. However, the way it is implemented[^1], the
// TabViewItem needs either an Item or a Content for the event to
// include the correct TabViewItem. Otherwise, it will just return the
// first TabViewItem in the TabView with the same Content as the dragged
// tab (which, if the Content is null, will be the _first_ tab).
//
// So here, we'll stick an empty border in, just so that every tab has a
// Content which is not equal to the others.
//
// [^1]: microsoft-ui-xaml/blob/92fbfcd55f05c92ac65569f5d284c5b36492091e/dev/TabView/TabView.cpp#L751-L758
TabViewItem().Content(winrt::WUX::Controls::Border{});
}
std::optional<winrt::Windows::UI::Color> TabBase::GetTabColor()

View File

@ -512,6 +512,11 @@ namespace winrt::TerminalApp::implementation
_mruTabs.RemoveAt(mruIndex);
}
if (_stashedDraggedTab && *_stashedDraggedTab == tab)
{
_stashedDraggedTab = nullptr;
}
_tabs.RemoveAt(tabIndex);
_tabView.TabItems().RemoveAt(tabIndex);
_UpdateTabIndices();
@ -703,7 +708,8 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::TabBase TerminalPage::_GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept
{
uint32_t tabIndexFromControl{};
if (_tabView.TabItems().IndexOf(tabViewItem, tabIndexFromControl))
const auto items{ _tabView.TabItems() };
if (items.IndexOf(tabViewItem, tabIndexFromControl))
{
// If IndexOf returns true, we've actually got an index
return _tabs.GetAt(tabIndexFromControl);

View File

@ -8,6 +8,7 @@
#include "LastTabClosedEventArgs.g.cpp"
#include "RenameWindowRequestedArgs.g.cpp"
#include "RequestMoveContentArgs.g.cpp"
#include "RequestReceiveContentArgs.g.cpp"
#include <filesystem>
@ -246,6 +247,10 @@ namespace winrt::TerminalApp::implementation
_tabView.TabCloseRequested({ this, &TerminalPage::_OnTabCloseRequested });
_tabView.TabItemsChanged({ this, &TerminalPage::_OnTabItemsChanged });
_tabView.TabDragStarting({ this, &TerminalPage::_onTabDragStarting });
_tabView.TabStripDragOver({ this, &TerminalPage::_onTabStripDragOver });
_tabView.TabStripDrop({ this, &TerminalPage::_onTabStripDrop });
_CreateNewTabFlyout();
_UpdateTabWidthMode();
@ -1959,12 +1964,15 @@ namespace winrt::TerminalApp::implementation
});
}
void TerminalPage::_DetachTabFromWindow(const winrt::com_ptr<TerminalTab>& terminalTab)
void TerminalPage::_DetachTabFromWindow(const winrt::com_ptr<TabBase>& tab)
{
// Detach the root pane, which will act like the whole tab got detached.
if (const auto rootPane = terminalTab->GetRootPane())
if (const auto terminalTab = tab.try_as<TerminalTab>())
{
_DetachPaneFromWindow(rootPane);
// Detach the root pane, which will act like the whole tab got detached.
if (const auto rootPane = terminalTab->GetRootPane())
{
_DetachPaneFromWindow(rootPane);
}
}
}
@ -2073,6 +2081,25 @@ namespace winrt::TerminalApp::implementation
{
_actionDispatch->DoAction(action);
}
// After handling all the actions, then re-check the tabIndex. We might
// have been called as a part of a tab drag/drop. In that case, the
// tabIndex is actually relevant, and we need to move the tab we just
// made into position.
if (!firstIsSplitPane && tabIndex != -1)
{
// Move the currently active tab to the requested index Use the
// currently focused tab index, because we don't know if the new tab
// opened at the end of the list, or adjacent to the previously
// active tab. This is affected by the user's "newTabPosition"
// setting.
if (const auto focusedTabIndex = _GetFocusedTabIndex())
{
const auto source = *focusedTabIndex;
_TryMoveTab(source, tabIndex);
}
// else: This shouldn't really be possible, because the tab we _just_ opened should be active.
}
}
// Method Description:
@ -4572,4 +4599,160 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_onTabDragStarting(const winrt::Microsoft::UI::Xaml::Controls::TabView&,
const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs& e)
{
// Get the tab impl from this event.
const auto eventTab = e.Tab();
const auto tabBase = _GetTabByTabViewItem(eventTab);
winrt::com_ptr<TabBase> tabImpl;
tabImpl.copy_from(winrt::get_self<TabBase>(tabBase));
if (tabImpl)
{
// First: stash the tab we started dragging.
// We're going to be asked for this.
_stashedDraggedTab = tabImpl;
// Into the DataPackage, let's stash our own window ID.
const auto id{ _WindowProperties.WindowId() };
// Get our PID
const auto pid{ GetCurrentProcessId() };
e.Data().Properties().Insert(L"windowId", winrt::box_value(id));
e.Data().Properties().Insert(L"pid", winrt::box_value<uint32_t>(pid));
e.Data().RequestedOperation(DataPackageOperation::Move);
// The next thing that will happen:
// * Another TerminalPage will get a TabStripDragOver, then get a
// TabStripDrop
// * This will be handled by the _other_ page asking the monarch
// to ask us to send our content to them.
// * We'll get a TabDroppedOutside to indicate that this tab was
// dropped _not_ on a TabView.
// * This we can't handle yet, and is the last point of TODO GH#5000
}
}
void TerminalPage::_onTabStripDragOver(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::UI::Xaml::DragEventArgs& e)
{
// We must mark that we can accept the drag/drop. The system will never
// call TabStripDrop on us if we don't indicate that we're willing.
const auto& props{ e.DataView().Properties() };
if (props.HasKey(L"windowId") &&
props.HasKey(L"pid") &&
(winrt::unbox_value_or<uint32_t>(props.TryLookup(L"pid"), 0u) == GetCurrentProcessId()))
{
e.AcceptedOperation(DataPackageOperation::Move);
}
// You may think to yourself, this is a great place to increase the
// width of the TabView artificially, to make room for the new tab item.
// However, we'll never get a message that the tab left the tab view
// (without being dropped). So there's no good way to resize back down.
}
// Method Description:
// - Called on the TARGET of a tab drag/drop. We'll unpack the DataPackage
// to find who the tab came from. We'll then ask the Monarch to ask the
// sender to move that tab to us.
winrt::fire_and_forget TerminalPage::_onTabStripDrop(winrt::Windows::Foundation::IInspectable /*sender*/,
winrt::Windows::UI::Xaml::DragEventArgs e)
{
// Get the PID and make sure it is the same as ours.
if (const auto& pidObj{ e.DataView().Properties().TryLookup(L"pid") })
{
const auto pid{ winrt::unbox_value_or<uint32_t>(pidObj, 0u) };
if (pid != GetCurrentProcessId())
{
// The PID doesn't match ours. We can't handle this drop.
co_return;
}
}
else
{
// No PID? We can't handle this drop. Bail.
co_return;
}
const auto& windowIdObj{ e.DataView().Properties().TryLookup(L"windowId") };
if (windowIdObj == nullptr)
{
// No windowId? Bail.
co_return;
}
const uint64_t src{ winrt::unbox_value<uint64_t>(windowIdObj) };
// Figure out where in the tab strip we're dropping this tab. Add that
// index to the request. This is largely taken from the WinUI sample
// app.
// We need to be on OUR UI thread to figure out where we dropped
auto weakThis{ get_weak() };
co_await wil::resume_foreground(Dispatcher());
if (const auto& page{ weakThis.get() })
{
// First we need to get the position in the List to drop to
auto index = -1;
// Determine which items in the list our pointer is between.
for (auto i = 0u; i < _tabView.TabItems().Size(); i++)
{
if (const auto& item{ _tabView.ContainerFromIndex(i).try_as<winrt::MUX::Controls::TabViewItem>() })
{
const auto posX{ e.GetPosition(item).X }; // The point of the drop, relative to the tab
const auto itemWidth{ item.ActualWidth() }; // The right of the tab
// If the drag point is on the left half of the tab, then insert here.
if (posX < itemWidth / 2)
{
index = i;
break;
}
}
}
// `this` is safe to use
const auto request = winrt::make_self<RequestReceiveContentArgs>(src, _WindowProperties.WindowId(), index);
// This will go up to the monarch, who will then dispatch the request
// back down to the source TerminalPage, who will then perform a
// RequestMoveContent to move their tab to us.
_RequestReceiveContentHandlers(*this, *request);
}
}
// Method Description:
// - This is called on the drag/drop SOURCE TerminalPage, when the monarch has
// requested that we send our tab to another window. We'll need to
// serialize the tab, and send it to the monarch, who will then send it to
// the destination window.
// - Fortunately, sending the tab is basically just a MoveTab action, so we
// can largely reuse that.
winrt::fire_and_forget TerminalPage::SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args)
{
// validate that we're the source window of the tab in this request
if (args.SourceWindow() != _WindowProperties.WindowId())
{
co_return;
}
if (!_stashedDraggedTab)
{
co_return;
}
// must do the work of adding/removing tabs on the UI thread.
auto weakThis{ get_weak() };
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
if (const auto& page{ weakThis.get() })
{
// `this` is safe to use in here.
auto startupActions = _stashedDraggedTab->BuildStartupActions(true);
_DetachTabFromWindow(_stashedDraggedTab);
_MoveContent(std::move(startupActions), winrt::hstring{ fmt::format(L"{}", args.TargetWindow()) }, args.TabIndex());
// _RemoveTab will make sure to null out the _stashedDraggedTab
_RemoveTab(*_stashedDraggedTab);
}
}
}

View File

@ -10,6 +10,7 @@
#include "LastTabClosedEventArgs.g.h"
#include "RenameWindowRequestedArgs.g.h"
#include "RequestMoveContentArgs.g.h"
#include "RequestReceiveContentArgs.g.h"
#include "Toast.h"
#define DECLARE_ACTION_HANDLER(action) void _Handle##action(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
@ -74,6 +75,19 @@ namespace winrt::TerminalApp::implementation
_TabIndex{ tabIndex } {};
};
struct RequestReceiveContentArgs : RequestReceiveContentArgsT<RequestReceiveContentArgs>
{
WINRT_PROPERTY(uint64_t, SourceWindow);
WINRT_PROPERTY(uint64_t, TargetWindow);
WINRT_PROPERTY(uint32_t, TabIndex);
public:
RequestReceiveContentArgs(const uint64_t src, const uint64_t tgt, const uint32_t tabIndex) :
_SourceWindow{ src },
_TargetWindow{ tgt },
_TabIndex{ tabIndex } {};
};
struct TerminalPage : TerminalPageT<TerminalPage>
{
public:
@ -149,6 +163,7 @@ 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::fire_and_forget SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
@ -173,6 +188,7 @@ namespace winrt::TerminalApp::implementation
TYPED_EVENT(ShowWindowChanged, IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs)
TYPED_EVENT(RequestMoveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestMoveContentArgs);
TYPED_EVENT(RequestReceiveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, _PropertyChangedHandlers, nullptr);
@ -247,6 +263,8 @@ namespace winrt::TerminalApp::implementation
TerminalApp::ContentManager _manager{ nullptr };
winrt::com_ptr<winrt::TerminalApp::implementation::TabBase> _stashedDraggedTab{ nullptr };
winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker;
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowDialogHelper(const std::wstring_view& name);
@ -482,8 +500,12 @@ 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 _onTabDragStarting(const winrt::Microsoft::UI::Xaml::Controls::TabView& sender, const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs& e);
void _onTabStripDragOver(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::DragEventArgs& e);
winrt::fire_and_forget _onTabStripDrop(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::DragEventArgs e);
void _DetachPaneFromWindow(std::shared_ptr<Pane> pane);
void _DetachTabFromWindow(const winrt::com_ptr<TerminalTab>& terminalTab);
void _DetachTabFromWindow(const winrt::com_ptr<TabBase>& 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);
@ -505,4 +527,5 @@ namespace winrt::TerminalApp::implementation
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(TerminalPage);
BASIC_FACTORY(RequestReceiveContentArgs);
}

View File

@ -30,6 +30,13 @@ namespace TerminalApp
String Content { get; };
UInt32 TabIndex { get; };
};
[default_interface] runtimeclass RequestReceiveContentArgs {
RequestReceiveContentArgs(UInt64 src, UInt64 tgt, UInt32 tabIndex);
UInt64 SourceWindow { get; };
UInt64 TargetWindow { get; };
UInt32 TabIndex { get; };
};
interface IDialogPresenter
{
@ -74,6 +81,7 @@ namespace TerminalApp
Windows.UI.Xaml.Media.Brush TitlebarBrush { get; };
void WindowActivated(Boolean activated);
void SendContentToOther(RequestReceiveContentArgs args);
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
@ -92,6 +100,6 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.ShowWindowArgs> ShowWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, RequestMoveContentArgs> RequestMoveContent;
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
}
}

View File

@ -1182,6 +1182,13 @@ namespace winrt::TerminalApp::implementation
_root->AttachContent(content, tabIndex);
}
}
void TerminalWindow::SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args)
{
if (_root)
{
_root->SendContentToOther(args);
}
}
bool TerminalWindow::ShouldImmediatelyHandoffToElevated()
{

View File

@ -143,6 +143,7 @@ namespace winrt::TerminalApp::implementation
TerminalApp::WindowProperties WindowProperties() { return *_WindowProperties; }
void AttachContent(winrt::hstring content, uint32_t tabIndex);
void SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args);
// -------------------------------- WinRT Events ---------------------------------
// PropertyChanged is surprisingly not a typed event, so we'll define that one manually.
@ -219,6 +220,7 @@ 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);
FORWARDED_TYPED_EVENT(RequestReceiveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs, _root, RequestReceiveContent);
#ifdef UNIT_TESTING
friend class TerminalAppLocalTests::CommandlineTest;

View File

@ -137,7 +137,9 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, SettingsLoadEventArgs> SettingsChanged;
event Windows.Foundation.TypedEventHandler<Object, RequestMoveContentArgs> RequestMoveContent;
void AttachContent(String content, UInt32 tabIndex);
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
void AttachContent(String content, UInt32 tabIndex);
void SendContentToOther(RequestReceiveContentArgs args);
}
}

View File

@ -72,10 +72,10 @@ 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;
event Windows.Foundation.TypedEventHandler<Object, Object> Attached;
// Used to communicate to the TermControl, but not necessarily higher up in the stack
event Windows.Foundation.TypedEventHandler<Object, ContextMenuRequestedEventArgs> ContextMenuRequested;

View File

@ -86,6 +86,7 @@ namespace RemotingUnitTests
void RequestQuitAll() DIE;
void Quit() DIE;
void AttachContentToWindow(Remoting::AttachRequest) DIE;
void SendContent(winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs) 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);
@ -98,6 +99,7 @@ namespace RemotingUnitTests
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);
TYPED_EVENT(SendContentRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs);
};
// Same idea.
@ -117,6 +119,7 @@ namespace RemotingUnitTests
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;
void RequestSendContent(Remoting::RequestReceiveContentArgs) DIE;
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);

View File

@ -384,7 +384,8 @@ void AppHost::Initialize()
_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 });
_revokers.RequestReceiveContent = _windowLogic.RequestReceiveContent(winrt::auto_revoke, { this, &AppHost::_handleReceiveContent });
_revokers.SendContentRequested = _peasant.SendContentRequested(winrt::auto_revoke, { this, &AppHost::_handleSendContent });
// BODGY
// On certain builds of Windows, when Terminal is set as the default
// it will accumulate an unbounded amount of queued animations while
@ -1232,6 +1233,23 @@ void AppHost::_handleAttach(const winrt::Windows::Foundation::IInspectable& /*se
_windowLogic.AttachContent(args.Content(), args.TabIndex());
}
// Page -> us -> manager -> monarch
// The page wants to tell the monarch that it was the drop target for a drag drop.
// The manager will tell the monarch to tell the _other_ window to send its content to us.
void AppHost::_handleReceiveContent(const winrt::Windows::Foundation::IInspectable& /* sender */,
winrt::TerminalApp::RequestReceiveContentArgs args)
{
_windowManager.RequestSendContent(winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs{ args.SourceWindow(), args.TargetWindow(), args.TabIndex() });
}
// monarch -> Peasant -> us -> Page
// The Monarch was told to tell us to send our dragged content to someone else.
void AppHost::_handleSendContent(const winrt::Windows::Foundation::IInspectable& /* sender */,
winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs args)
{
_windowLogic.SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs{ args.SourceWindow(), args.TargetWindow(), 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

@ -127,6 +127,14 @@ private:
void _requestUpdateSettings();
// Page -> us -> monarch
void _handleReceiveContent(const winrt::Windows::Foundation::IInspectable& sender,
winrt::TerminalApp::RequestReceiveContentArgs args);
// monarch -> us -> Page
void _handleSendContent(const winrt::Windows::Foundation::IInspectable& sender,
winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs args);
winrt::event_token _GetWindowLayoutRequestedToken;
// Helper struct. By putting these all into one struct, we can revoke them
@ -161,9 +169,11 @@ private:
winrt::TerminalApp::TerminalWindow::QuitRequested_revoker QuitRequested;
winrt::TerminalApp::TerminalWindow::ShowWindowChanged_revoker ShowWindowChanged;
winrt::TerminalApp::TerminalWindow::RequestMoveContent_revoker RequestMoveContent;
winrt::TerminalApp::TerminalWindow::RequestReceiveContent_revoker RequestReceiveContent;
winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged;
winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged;
winrt::Microsoft::Terminal::Remoting::WindowManager::QuitAllRequested_revoker QuitAllRequested;
winrt::Microsoft::Terminal::Remoting::Peasant::SendContentRequested_revoker SendContentRequested;
} _revokers{};
};