From 26d67d9c0af0a05b0e65aa76537006862d39535b Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 12 Apr 2022 17:44:01 -0500 Subject: [PATCH] Enable the Terminal to tell ConPTY who the owner is (#12526) ## Window shenanigans, part the first: This PR enables terminals to tell ConPTY what the owning window for the pseudo window should be. This allows thigs like MessageBoxes created by console applications to work. It also enables console apps to use `GetAncestor(GetConsoleWindow(), GA_ROOT)` to get directly at the HWND of the Terminal (but _don't please_). This is tested with our internal partners and seems to work for their scenario. See #2988, #12799, #12515, #12570. ## PR Checklist This is 1/3 of #2988. --- .github/actions/spelling/allow/apis.txt | 1 + src/cascadia/TerminalApp/TerminalPage.cpp | 11 ++++ .../TerminalConnection/ConptyConnection.cpp | 23 +++++++++ .../TerminalConnection/ConptyConnection.h | 2 + .../TerminalConnection/ConptyConnection.idl | 1 + src/cascadia/TerminalControl/ControlCore.cpp | 8 +++ src/cascadia/TerminalControl/ControlCore.h | 5 ++ src/cascadia/TerminalControl/ICoreState.idl | 3 ++ src/cascadia/TerminalControl/TermControl.cpp | 10 ++++ src/cascadia/TerminalControl/TermControl.h | 3 ++ src/cascadia/WindowsTerminal/IslandWindow.cpp | 5 ++ src/cascadia/WindowsTerminal/IslandWindow.h | 1 + src/host/PtySignalInputThread.cpp | 46 +++++++++++++++++ src/host/PtySignalInputThread.hpp | 9 ++++ src/host/VtIo.cpp | 5 ++ src/host/outputStream.cpp | 14 ++++++ src/host/outputStream.hpp | 2 + src/inc/conpty-static.h | 2 + .../base/InteractivityFactory.cpp | 39 ++++++++++++--- .../base/InteractivityFactory.hpp | 2 +- src/interactivity/base/ServiceLocator.cpp | 9 ++-- .../inc/IInteractivityFactory.hpp | 2 +- src/interactivity/inc/ServiceLocator.hpp | 2 +- src/terminal/adapter/conGetSet.hpp | 2 + .../adapter/ut_adapter/adapterTest.cpp | 5 ++ src/tools/scratch/Scratch.vcxproj | 10 +++- src/winconpty/winconpty.cpp | 50 +++++++++++++++++++ src/winconpty/winconpty.h | 2 + 28 files changed, 259 insertions(+), 15 deletions(-) diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 3045f47721c..ad2ce887717 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -145,6 +145,7 @@ REGCLS RETURNCMD rfind roundf +ROOTOWNER RSHIFT SACL schandle diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 62c8885fffa..401f3b41b98 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -60,6 +60,12 @@ namespace winrt::TerminalApp::implementation // Method Description: // - implements the IInitializeWithWindow interface from shobjidl_core. + // - We're going to use this HWND as the owner for the ConPTY windows, via + // ConptyConnection::ReparentWindow. We need this for applications that + // call GetConsoleWindow, and attempt to open a MessageBox for the + // console. By marking the conpty windows as owned by the Terminal HWND, + // the message box will be owned by the Terminal window as well. + // - see GH#2988 HRESULT TerminalPage::Initialize(HWND hwnd) { _hostingHwnd = hwnd; @@ -2409,6 +2415,11 @@ namespace winrt::TerminalApp::implementation // create here. // TermControl will copy the settings out of the settings passed to it. TermControl term{ settings.DefaultSettings(), settings.UnfocusedSettings(), connection }; + + if (_hostingHwnd.has_value()) + { + term.OwningHwnd(reinterpret_cast(*_hostingHwnd)); + } return term; } diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 762d7b4f90a..c7677d288d5 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -310,6 +310,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, flags, &_inPipe, &_outPipe, &_hPC)); + + if (_initialParentHwnd != 0) + { + THROW_IF_FAILED(ConptyReparentPseudoConsole(_hPC.get(), reinterpret_cast(_initialParentHwnd))); + } + THROW_IF_FAILED(_LaunchAttachedClient()); } // But if it was an inbound handoff... attempt to synchronize the size of it with what our connection @@ -327,6 +333,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), dimensions)); + THROW_IF_FAILED(ConptyReparentPseudoConsole(_hPC.get(), reinterpret_cast(_initialParentHwnd))); } _startTime = std::chrono::high_resolution_clock::now(); @@ -482,6 +489,22 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } } + void ConptyConnection::ReparentWindow(const uint64_t newParent) + { + // If we haven't started connecting at all, stash this HWND to use once we have started. + if (!_isStateAtOrBeyond(ConnectionState::Connecting)) + { + _initialParentHwnd = newParent; + } + // Otherwise, just inform the conpty of the new owner window handle. + // This shouldn't be hittable until GH#5000 / GH#1256, when it's + // possible to reparent terminals to different windows. + else if (_isConnected()) + { + THROW_IF_FAILED(ConptyReparentPseudoConsole(_hPC.get(), reinterpret_cast(newParent))); + } + } + void ConptyConnection::Close() noexcept try { diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index fc4ed55c08e..db6e1650e57 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -35,6 +35,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void Resize(uint32_t rows, uint32_t columns); void Close() noexcept; void ClearBuffer(); + void ReparentWindow(const uint64_t newParent); winrt::guid Guid() const noexcept; winrt::hstring Commandline() const; @@ -65,6 +66,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation uint32_t _initialRows{}; uint32_t _initialCols{}; + uint64_t _initialParentHwnd{ 0 }; hstring _commandline{}; hstring _startingDirectory{}; hstring _startingTitle{}; diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl index 4c3df03ce63..7d495cb638c 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.idl +++ b/src/cascadia/TerminalConnection/ConptyConnection.idl @@ -13,6 +13,7 @@ namespace Microsoft.Terminal.TerminalConnection Guid Guid { get; }; String Commandline { get; }; void ClearBuffer(); + void ReparentWindow(UInt64 newParent); static event NewConnectionHandler NewConnection; static void StartInboundListener(); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 820686d322b..05d022ef254 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -262,6 +262,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto height = vp.Height(); _connection.Resize(height, width); + if (_OwningHwnd != 0) + { + if (auto conpty{ _connection.try_as() }) + { + conpty.ReparentWindow(_OwningHwnd); + } + } + // Override the default width and height to match the size of the swapChainPanel _settings->InitialCols(width); _settings->InitialRows(height); diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index a8cd204f1ef..592dc76aa46 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -168,6 +168,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation void AdjustOpacity(const double opacity, const bool relative); + // TODO:GH#1256 - When a tab can be torn out or otherwise reparented to + // another window, this value will need a custom setter, so that we can + // also update the connection. + WINRT_PROPERTY(uint64_t, OwningHwnd, 0); + RUNTIME_SETTING(double, Opacity, _settings->Opacity()); RUNTIME_SETTING(bool, UseAcrylic, _settings->UseAcrylic()); diff --git a/src/cascadia/TerminalControl/ICoreState.idl b/src/cascadia/TerminalControl/ICoreState.idl index ac9a2a3465c..fd11bda8f14 100644 --- a/src/cascadia/TerminalControl/ICoreState.idl +++ b/src/cascadia/TerminalControl/ICoreState.idl @@ -24,5 +24,8 @@ namespace Microsoft.Terminal.Control Microsoft.Terminal.TerminalConnection.ConnectionState ConnectionState { get; }; Microsoft.Terminal.Core.Scheme ColorScheme { get; set; }; + + UInt64 OwningHwnd; + }; } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 818a10c9b4b..f45e05cb56f 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -2794,4 +2794,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } + void TermControl::OwningHwnd(uint64_t owner) + { + _core.OwningHwnd(owner); + } + + uint64_t TermControl::OwningHwnd() + { + return _core.OwningHwnd(); + } + } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 95aa82cdb92..512ab1476b6 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -59,6 +59,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool BracketedPasteEnabled() const noexcept; double BackgroundOpacity() const; + + uint64_t OwningHwnd(); + void OwningHwnd(uint64_t owner); #pragma endregion void ScrollViewport(int viewTop); diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index b336c60a7f9..e190eb672f5 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -41,6 +41,11 @@ IslandWindow::~IslandWindow() _source.Close(); } +HWND IslandWindow::GetInteropHandle() const +{ + return _interopWindowHandle; +} + // Method Description: // - Create the actual window that we'll use for the application. // Arguments: diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index 53c00121d15..a39af64fc87 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -23,6 +23,7 @@ class IslandWindow : virtual void MakeWindow() noexcept; void Close(); virtual void OnSize(const UINT width, const UINT height); + HWND GetInteropHandle() const; [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; void OnResize(const UINT width, const UINT height) override; diff --git a/src/host/PtySignalInputThread.cpp b/src/host/PtySignalInputThread.cpp index d480e135617..cf564d0c9fb 100644 --- a/src/host/PtySignalInputThread.cpp +++ b/src/host/PtySignalInputThread.cpp @@ -56,6 +56,10 @@ DWORD WINAPI PtySignalInputThread::StaticThreadProc(_In_ LPVOID lpParameter) // (in and screen buffers) haven't yet been initialized. // - NOTE: Call under LockConsole() to ensure other threads have an opportunity // to set early-work state. +// - We need to do this specifically on the thread with the message pump. If the +// window is created on another thread, then the window won't have a message +// pump associated with it, and a DPI change in the connected terminal could +// end up HANGING THE CONPTY (for example). // Arguments: // - // Return Value: @@ -67,6 +71,12 @@ void PtySignalInputThread::ConnectConsole() noexcept { _DoResizeWindow(*_earlyResize); } + + // If we were given a owner HWND, then manually start the pseudo window now. + if (_earlyReparent) + { + _DoSetWindowParent(*_earlyReparent); + } } // Method Description: @@ -119,6 +129,28 @@ void PtySignalInputThread::ConnectConsole() noexcept break; } + case PtySignal::SetParent: + { + SetParentData reparentMessage = { 0 }; + _GetData(&reparentMessage, sizeof(reparentMessage)); + + LockConsole(); + auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); + + // If the client app hasn't yet connected, stash the new owner. + // We'll later (PtySignalInputThread::ConnectConsole) use the value + // to set up the owner of the conpty window. + if (!_consoleConnected) + { + _earlyReparent = reparentMessage; + } + else + { + _DoSetWindowParent(reparentMessage); + } + + break; + } default: { THROW_HR(E_UNEXPECTED); @@ -147,6 +179,20 @@ void PtySignalInputThread::_DoClearBuffer() _pConApi->ClearBuffer(); } +// Method Description: +// - Update the owner of the pseudo-window we're using for the conpty HWND. This +// allows to mark the pseudoconsole windows as "owner" by the terminal HWND +// that's actually hosting them. +// - Refer to GH#2988 +// Arguments: +// - data - Packet information containing owner HWND information +// Return Value: +// - +void PtySignalInputThread::_DoSetWindowParent(const SetParentData& data) +{ + _pConApi->ReparentWindow(data.handle); +} + // Method Description: // - Retrieves bytes from the file stream and exits or throws errors should the pipe state // be compromised. diff --git a/src/host/PtySignalInputThread.hpp b/src/host/PtySignalInputThread.hpp index 74ffa8e022b..71d1ed27c83 100644 --- a/src/host/PtySignalInputThread.hpp +++ b/src/host/PtySignalInputThread.hpp @@ -41,6 +41,7 @@ namespace Microsoft::Console enum class PtySignal : unsigned short { ClearBuffer = 2, + SetParent = 3, ResizeWindow = 8 }; @@ -49,10 +50,15 @@ namespace Microsoft::Console unsigned short sx; unsigned short sy; }; + struct SetParentData + { + uint64_t handle; + }; [[nodiscard]] HRESULT _InputThread(); bool _GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, const DWORD cbBuffer); void _DoResizeWindow(const ResizeWindowData& data); + void _DoSetWindowParent(const SetParentData& data); void _DoClearBuffer(); void _Shutdown(); @@ -62,5 +68,8 @@ namespace Microsoft::Console bool _consoleConnected; std::optional _earlyResize; std::unique_ptr _pConApi; + + public: + std::optional _earlyReparent; }; } diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 14271a4fb95..428480e702a 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -296,6 +296,11 @@ bool VtIo::IsUsingVt() const if (_pPtySignalInputThread) { + // IMPORTANT! Start the pseudo window on this thread. This thread has a + // message pump. If you DON'T, then a DPI change in the owning hwnd will + // cause us to get a dpi change as well, which we'll never deque and + // handle, effectively HANGING THE OWNER HWND. + // // Let the signal thread know that the console is connected _pPtySignalInputThread->ConnectConsole(); } diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 1b3ba0a2608..6a4244926e6 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -892,3 +892,17 @@ void ConhostInternalGetSet::UpdateSoftFont(const gsl::span bitPa pRender->UpdateSoftFont(bitPattern, cellSize, centeringHint); } } + +void ConhostInternalGetSet::ReparentWindow(const uint64_t handle) +{ + // This will initialize s_interactivityFactory for us. It will also + // conveniently return 0 when we're on OneCore. + // + // If the window hasn't been created yet, by some other call to + // LocatePseudoWindow, then this will also initialize the owner of the + // window. + if (const auto psuedoHwnd{ ServiceLocator::LocatePseudoWindow(reinterpret_cast(handle)) }) + { + LOG_LAST_ERROR_IF_NULL(::SetParent(psuedoHwnd, reinterpret_cast(handle))); + } +} diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index 458e1ab550f..75db5f59076 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -116,6 +116,8 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal:: const SIZE cellSize, const size_t centeringHint) override; + void ReparentWindow(const uint64_t handle); + private: void _modifyLines(const size_t count, const bool insert); diff --git a/src/inc/conpty-static.h b/src/inc/conpty-static.h index b745a8b1631..fc01dc0659a 100644 --- a/src/inc/conpty-static.h +++ b/src/inc/conpty-static.h @@ -25,6 +25,8 @@ HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size); HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC); +HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent); + VOID WINAPI ConptyClosePseudoConsole(HPCON hPC); HRESULT WINAPI ConptyPackPseudoConsole(HANDLE hServerProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC); diff --git a/src/interactivity/base/InteractivityFactory.cpp b/src/interactivity/base/InteractivityFactory.cpp index fd93f6ec158..810f41d44ef 100644 --- a/src/interactivity/base/InteractivityFactory.cpp +++ b/src/interactivity/base/InteractivityFactory.cpp @@ -289,14 +289,15 @@ using namespace Microsoft::Console::Interactivity; // that GetConsoleWindow returns a real value. // Arguments: // - hwnd: Receives the value of the newly created window's HWND. +// - owner: the HWND that should be the initial owner of the pseudo window. // Return Value: // - STATUS_SUCCESS on success, otherwise an appropriate error. -[[nodiscard]] NTSTATUS InteractivityFactory::CreatePseudoWindow(HWND& hwnd) +[[nodiscard]] NTSTATUS InteractivityFactory::CreatePseudoWindow(HWND& hwnd, const HWND owner) { hwnd = nullptr; ApiLevel level; NTSTATUS status = ApiDetector::DetectNtUserWindow(&level); - ; + if (NT_SUCCESS(status)) { try @@ -306,19 +307,45 @@ using namespace Microsoft::Console::Interactivity; switch (level) { case ApiLevel::Win32: + { pseudoClass.lpszClassName = PSEUDO_WINDOW_CLASS; pseudoClass.lpfnWndProc = DefWindowProc; RegisterClass(&pseudoClass); - // Attempt to create window - hwnd = CreateWindowExW( - 0, PSEUDO_WINDOW_CLASS, nullptr, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, HWND_DESKTOP, nullptr, nullptr, nullptr); + + // When merging with #12515, we're going to need to adjust these styles. + // + // Note that because we're not specifying WS_CHILD, this window + // will become an _owned_ window, not a _child_ window. This is + // important - child windows report their position as relative + // to their parent window, while owned windows are still + // relative to the desktop. (there are other subtleties as well + // as far as the difference between parent/child and owner/owned + // windows). Evan K said we should do it this way, and he + // definitely knows. + const auto windowStyle = WS_OVERLAPPEDWINDOW; + + // Attempt to create window. + hwnd = CreateWindowExW(0, + PSEUDO_WINDOW_CLASS, + nullptr, + windowStyle, + 0, + 0, + 0, + 0, + owner, + nullptr, + nullptr, + nullptr); + if (hwnd == nullptr) { DWORD const gle = GetLastError(); status = NTSTATUS_FROM_WIN32(gle); } - break; + break; + } #ifdef BUILD_ONECORE_INTERACTIVITY case ApiLevel::OneCore: hwnd = 0; diff --git a/src/interactivity/base/InteractivityFactory.hpp b/src/interactivity/base/InteractivityFactory.hpp index 97ac10d3b9f..597f32515a7 100644 --- a/src/interactivity/base/InteractivityFactory.hpp +++ b/src/interactivity/base/InteractivityFactory.hpp @@ -26,6 +26,6 @@ namespace Microsoft::Console::Interactivity [[nodiscard]] NTSTATUS CreateAccessibilityNotifier(_Inout_ std::unique_ptr& notifier); [[nodiscard]] NTSTATUS CreateSystemConfigurationProvider(_Inout_ std::unique_ptr& provider); - [[nodiscard]] NTSTATUS CreatePseudoWindow(HWND& hwnd); + [[nodiscard]] NTSTATUS CreatePseudoWindow(HWND& hwnd, const HWND owner); }; } diff --git a/src/interactivity/base/ServiceLocator.cpp b/src/interactivity/base/ServiceLocator.cpp index 8fc25fb4f9d..ee86679a1f2 100644 --- a/src/interactivity/base/ServiceLocator.cpp +++ b/src/interactivity/base/ServiceLocator.cpp @@ -288,10 +288,11 @@ Globals& ServiceLocator::LocateGlobals() // Method Description: // - Retrieves the pseudo console window, or attempts to instantiate one. // Arguments: -// - +// - owner: (defaults to 0 `HWND_DESKTOP`) the HWND that should be the initial +// owner of the pseudo window. // Return Value: // - a reference to the pseudoconsole window. -HWND ServiceLocator::LocatePseudoWindow() +HWND ServiceLocator::LocatePseudoWindow(const HWND owner) { NTSTATUS status = STATUS_SUCCESS; if (!s_pseudoWindowInitialized) @@ -304,7 +305,7 @@ HWND ServiceLocator::LocatePseudoWindow() if (NT_SUCCESS(status)) { HWND hwnd; - status = s_interactivityFactory->CreatePseudoWindow(hwnd); + status = s_interactivityFactory->CreatePseudoWindow(hwnd, owner); s_pseudoWindow.reset(hwnd); } s_pseudoWindowInitialized = true; @@ -315,8 +316,6 @@ HWND ServiceLocator::LocatePseudoWindow() #pragma endregion -#pragma endregion - #pragma region Private Methods [[nodiscard]] NTSTATUS ServiceLocator::LoadInteractivityFactory() diff --git a/src/interactivity/inc/IInteractivityFactory.hpp b/src/interactivity/inc/IInteractivityFactory.hpp index 65c28428b4f..860a77cc3d7 100644 --- a/src/interactivity/inc/IInteractivityFactory.hpp +++ b/src/interactivity/inc/IInteractivityFactory.hpp @@ -40,7 +40,7 @@ namespace Microsoft::Console::Interactivity [[nodiscard]] virtual NTSTATUS CreateAccessibilityNotifier(_Inout_ std::unique_ptr& notifier) = 0; [[nodiscard]] virtual NTSTATUS CreateSystemConfigurationProvider(_Inout_ std::unique_ptr& provider) = 0; - [[nodiscard]] virtual NTSTATUS CreatePseudoWindow(HWND& hwnd) = 0; + [[nodiscard]] virtual NTSTATUS CreatePseudoWindow(HWND& hwnd, const HWND owner) = 0; }; inline IInteractivityFactory::~IInteractivityFactory() {} diff --git a/src/interactivity/inc/ServiceLocator.hpp b/src/interactivity/inc/ServiceLocator.hpp index d930b0d9938..ff0bde14270 100644 --- a/src/interactivity/inc/ServiceLocator.hpp +++ b/src/interactivity/inc/ServiceLocator.hpp @@ -84,7 +84,7 @@ namespace Microsoft::Console::Interactivity static Globals& LocateGlobals(); - static HWND LocatePseudoWindow(); + static HWND LocatePseudoWindow(const HWND owner = 0 /*HWND_DESKTOP*/); protected: ServiceLocator(ServiceLocator const&) = delete; diff --git a/src/terminal/adapter/conGetSet.hpp b/src/terminal/adapter/conGetSet.hpp index 1cfe0d2613b..6dd127c2825 100644 --- a/src/terminal/adapter/conGetSet.hpp +++ b/src/terminal/adapter/conGetSet.hpp @@ -111,5 +111,7 @@ namespace Microsoft::Console::VirtualTerminal virtual void UpdateSoftFont(const gsl::span bitPattern, const SIZE cellSize, const size_t centeringHint) = 0; + + virtual void ReparentWindow(const uint64_t handle) = 0; }; } diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index c75be3a58b2..5fb239682a4 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -422,6 +422,11 @@ class TestGetSet final : public ConGetSet VERIFY_ARE_EQUAL(_expectedCellSize.cy, cellSize.cy); } + void ReparentWindow(const uint64_t /*handle*/) + { + Log::Comment(L"ReparentWindow MOCK called..."); + } + void PrepData() { PrepData(CursorDirection::UP); // if called like this, the cursor direction doesn't matter. diff --git a/src/tools/scratch/Scratch.vcxproj b/src/tools/scratch/Scratch.vcxproj index f2d0141596f..f2493136a37 100644 --- a/src/tools/scratch/Scratch.vcxproj +++ b/src/tools/scratch/Scratch.vcxproj @@ -6,7 +6,7 @@ Scratch Scratch Scratch - Application + Application @@ -21,6 +21,14 @@ Console + + + + + {6bae5851-50d5-4934-8d5e-30361a8a40f3} + + + diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index 0292c0f47d2..88f1549e00b 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -254,6 +254,39 @@ HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty) return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError()); } +// Function Description: +// - Sends a message to the pseudoconsole informing it that it should use the +// given window handle as the owner for the conpty's pseudo window. This +// allows the response given to GetConsoleWindow() to be a HWND that's owned +// by the actual hosting terminal's HWND. +// Arguments: +// - pPty: A pointer to a PseudoConsole struct. +// - newParent: The new owning window +// Return Value: +// - S_OK if the call succeeded, else an appropriate HRESULT for failing to +// write the resize message to the pty. +#pragma warning(suppress : 26461) +// an HWND is technically a void*, but that confuses static analysis here. +HRESULT _ReparentPseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const HWND newParent) +{ + if (pPty == nullptr) + { + return E_INVALIDARG; + } + + // sneaky way to pack a short and a uint64_t in a relatively literal way. +#pragma pack(push, 1) + struct _signal + { + const unsigned short id; + const uint64_t hwnd; + } data{ PTY_SIGNAL_REPARENT_WINDOW, (uint64_t)(newParent) }; +#pragma pack(pop) + + const BOOL fSuccess = WriteFile(pPty->hSignal, &data, sizeof(data), nullptr, nullptr); + return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError()); +} + // Function Description: // - This closes each of the members of a PseudoConsole. It does not free the // data associated with the PseudoConsole. This is helpful for testing, @@ -425,6 +458,23 @@ extern "C" HRESULT WINAPI ConptyClearPseudoConsole(_In_ HPCON hPC) return hr; } +// Function Description: +// - Sends a message to the pseudoconsole informing it that it should use the +// given window handle as the owner for the conpty's pseudo window. This +// allows the response given to GetConsoleWindow() to be a HWND that's owned +// by the actual hosting terminal's HWND. +// - Used to support GH#2988 +extern "C" HRESULT WINAPI ConptyReparentPseudoConsole(_In_ HPCON hPC, HWND newParent) +{ + const PseudoConsole* const pPty = (PseudoConsole*)hPC; + HRESULT hr = pPty == nullptr ? E_INVALIDARG : S_OK; + if (SUCCEEDED(hr)) + { + hr = _ReparentPseudoConsole(pPty, newParent); + } + return hr; +} + // Function Description: // Closes the conpty and all associated state. // Client applications attached to the conpty will also behave as though the diff --git a/src/winconpty/winconpty.h b/src/winconpty/winconpty.h index 5c6a2f501c0..80a7c172a58 100644 --- a/src/winconpty/winconpty.h +++ b/src/winconpty/winconpty.h @@ -18,6 +18,7 @@ typedef struct _PseudoConsole // These are not defined publicly, but are used for controlling the conpty via // the signal pipe. #define PTY_SIGNAL_CLEAR_WINDOW (2u) +#define PTY_SIGNAL_REPARENT_WINDOW (3u) #define PTY_SIGNAL_RESIZE_WINDOW (8u) // CreatePseudoConsole Flags @@ -37,6 +38,7 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken, HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const COORD size); HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty); +HRESULT _ReparentPseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const HWND newParent); void _ClosePseudoConsoleMembers(_In_ PseudoConsole* pPty); VOID _ClosePseudoConsole(_In_ PseudoConsole* pPty);