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 851aa47a746..37b0d2a1d28 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; @@ -2424,6 +2430,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 6863ef080a6..3c44c132643 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);