diff --git a/src/cascadia/Remoting/Monarch.cpp b/src/cascadia/Remoting/Monarch.cpp index ee0e1b1e3bd..a62493ecb4a 100644 --- a/src/cascadia/Remoting/Monarch.cpp +++ b/src/cascadia/Remoting/Monarch.cpp @@ -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) + } + } } diff --git a/src/cascadia/Remoting/Monarch.h b/src/cascadia/Remoting/Monarch.h index 33a7d6711f7..54c31e98e27 100644 --- a/src/cascadia/Remoting/Monarch.h +++ b/src/cascadia/Remoting/Monarch.h @@ -82,6 +82,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation Windows::Foundation::Collections::IVector 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); diff --git a/src/cascadia/Remoting/Monarch.idl b/src/cascadia/Remoting/Monarch.idl index 0bbc337ca70..44ebf7b0f1c 100644 --- a/src/cascadia/Remoting/Monarch.idl +++ b/src/cascadia/Remoting/Monarch.idl @@ -41,8 +41,7 @@ namespace Microsoft.Terminal.Remoting Windows.Foundation.IReference 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 GetAllWindowLayouts(); void RequestMoveContent(String window, String content, UInt32 tabIndex); + void RequestSendContent(RequestReceiveContentArgs args); event Windows.Foundation.TypedEventHandler FindTargetWindowRequested; event Windows.Foundation.TypedEventHandler ShowNotificationIconRequested; diff --git a/src/cascadia/Remoting/Peasant.cpp b/src/cascadia/Remoting/Peasant.cpp index 659de9b72fc..94f8406c00c 100644 --- a/src/cascadia/Remoting/Peasant.cpp +++ b/src/cascadia/Remoting/Peasant.cpp @@ -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); + } } diff --git a/src/cascadia/Remoting/Peasant.h b/src/cascadia/Remoting/Peasant.h index a0fb55dce73..26a82a17fef 100644 --- a/src/cascadia/Remoting/Peasant.h +++ b/src/cascadia/Remoting/Peasant.h @@ -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 + { + 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(); @@ -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); } diff --git a/src/cascadia/Remoting/Peasant.idl b/src/cascadia/Remoting/Peasant.idl index a26604e4bf6..2b8780cae8d 100644 --- a/src/cascadia/Remoting/Peasant.idl +++ b/src/cascadia/Remoting/Peasant.idl @@ -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 WindowActivated; event Windows.Foundation.TypedEventHandler ExecuteCommandlineRequested; @@ -98,6 +105,8 @@ namespace Microsoft.Terminal.Remoting event Windows.Foundation.TypedEventHandler QuitRequested; event Windows.Foundation.TypedEventHandler AttachRequested; + + event Windows.Foundation.TypedEventHandler SendContentRequested; }; [default_interface] runtimeclass Peasant : IPeasant diff --git a/src/cascadia/Remoting/WindowManager.cpp b/src/cascadia/Remoting/WindowManager.cpp index 5fdb1830c25..afa8403790e 100644 --- a/src/cascadia/Remoting/WindowManager.cpp +++ b/src/cascadia/Remoting/WindowManager.cpp @@ -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); + } } diff --git a/src/cascadia/Remoting/WindowManager.h b/src/cascadia/Remoting/WindowManager.h index 89dcf0962cb..e42d6b4ba27 100644 --- a/src/cascadia/Remoting/WindowManager.h +++ b/src/cascadia/Remoting/WindowManager.h @@ -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); diff --git a/src/cascadia/Remoting/WindowManager.idl b/src/cascadia/Remoting/WindowManager.idl index d01670f8bd0..ca9d78a5d1a 100644 --- a/src/cascadia/Remoting/WindowManager.idl +++ b/src/cascadia/Remoting/WindowManager.idl @@ -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 FindTargetWindowRequested; diff --git a/src/cascadia/TerminalApp/TabBase.cpp b/src/cascadia/TerminalApp/TabBase.cpp index 52734c9f31e..54b25c48b08 100644 --- a/src/cascadia/TerminalApp/TabBase.cpp +++ b/src/cascadia/TerminalApp/TabBase.cpp @@ -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 TabBase::GetTabColor() diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 511c4925fe9..560a531650b 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -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); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 013e766cec1..efe73161ec0 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -8,6 +8,7 @@ #include "LastTabClosedEventArgs.g.cpp" #include "RenameWindowRequestedArgs.g.cpp" #include "RequestMoveContentArgs.g.cpp" +#include "RequestReceiveContentArgs.g.cpp" #include @@ -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) + void TerminalPage::_DetachTabFromWindow(const winrt::com_ptr& 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()) { - _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 tabImpl; + tabImpl.copy_from(winrt::get_self(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(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(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(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(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() }) + { + 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(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); + } + } + } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 98bc0c79806..46cf9c11c8c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -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 + { + 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 { 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 _stashedDraggedTab{ nullptr }; + winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker; winrt::Windows::Foundation::IAsyncOperation _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); - void _DetachTabFromWindow(const winrt::com_ptr& terminalTab); + void _DetachTabFromWindow(const winrt::com_ptr& terminalTab); void _MoveContent(std::vector&& 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); } diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 6832fc6e12b..a23203046dd 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -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 TitleChanged; event Windows.Foundation.TypedEventHandler LastTabClosed; @@ -92,6 +100,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler ShowWindowChanged; event Windows.Foundation.TypedEventHandler RequestMoveContent; - + event Windows.Foundation.TypedEventHandler RequestReceiveContent; } } diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 2d7d59bc604..3db175daec8 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -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() { diff --git a/src/cascadia/TerminalApp/TerminalWindow.h b/src/cascadia/TerminalApp/TerminalWindow.h index 5412da866a9..eda5c9e84ab 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.h +++ b/src/cascadia/TerminalApp/TerminalWindow.h @@ -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; diff --git a/src/cascadia/TerminalApp/TerminalWindow.idl b/src/cascadia/TerminalApp/TerminalWindow.idl index 2307336c39b..770289895e2 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.idl +++ b/src/cascadia/TerminalApp/TerminalWindow.idl @@ -137,7 +137,9 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler SettingsChanged; event Windows.Foundation.TypedEventHandler RequestMoveContent; - void AttachContent(String content, UInt32 tabIndex); + event Windows.Foundation.TypedEventHandler RequestReceiveContent; + void AttachContent(String content, UInt32 tabIndex); + void SendContentToOther(RequestReceiveContentArgs args); } } diff --git a/src/cascadia/TerminalControl/ControlInteractivity.idl b/src/cascadia/TerminalControl/ControlInteractivity.idl index 47a2920f0eb..086b009ba09 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.idl +++ b/src/cascadia/TerminalControl/ControlInteractivity.idl @@ -72,10 +72,10 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler ScrollPositionChanged; event Windows.Foundation.TypedEventHandler PasteFromClipboard; - event Windows.Foundation.TypedEventHandler Attached; - event Windows.Foundation.TypedEventHandler Closed; + event Windows.Foundation.TypedEventHandler Attached; + // Used to communicate to the TermControl, but not necessarily higher up in the stack event Windows.Foundation.TypedEventHandler ContextMenuRequested; diff --git a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp index 111dfb5877a..058a6c0793c 100644 --- a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp +++ b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp @@ -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 GetPeasantInfos() DIE; winrt::Windows::Foundation::Collections::IVector 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); diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index f190940e837..b4958ece432 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -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. diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index a9b8543ffbe..3521d984b6a 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -127,6 +127,14 @@ class AppHost 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 @@ class AppHost 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{}; };