diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index dccb73ac43..682023fd96 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 24369e920e..9976442160 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; @@ -2429,6 +2435,10 @@ namespace winrt::TerminalApp::implementation term.WindowVisibilityChanged(_visible); } + 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 145fb3b79a..486cc2b393 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -316,6 +316,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { THROW_IF_FAILED(ConptyShowHidePseudoConsole(_hPC.get(), _initialVisibility)); } + if (_initialParentHwnd != 0) + { + THROW_IF_FAILED(ConptyReparentPseudoConsole(_hPC.get(), reinterpret_cast(_initialParentHwnd))); + } THROW_IF_FAILED(_LaunchAttachedClient()); } @@ -334,6 +338,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(); @@ -502,6 +507,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 75d0ebf6ae..5028bd89be 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -35,8 +35,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void Resize(uint32_t rows, uint32_t columns); void Close() noexcept; void ClearBuffer(); + void ShowHide(const bool show); + void ReparentWindow(const uint64_t newParent); + winrt::guid Guid() const noexcept; winrt::hstring Commandline() const; @@ -66,6 +69,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 1b05f5c661..82f51b70db 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.idl +++ b/src/cascadia/TerminalConnection/ConptyConnection.idl @@ -14,8 +14,11 @@ namespace Microsoft.Terminal.TerminalConnection String Commandline { get; }; void ClearBuffer(); + void ShowHide(Boolean show); + void ReparentWindow(UInt64 newParent); + static event NewConnectionHandler NewConnection; static void StartInboundListener(); static void StopInboundListener(); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index b04642028d..3a10a71d9c 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -265,6 +265,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 7afe41b8cc..79d08fda4c 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -170,6 +170,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation void WindowVisibilityChanged(const bool showOrHide); + // 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 ac9a2a3465..fd11bda8f1 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 13777ec7cf..850dd84f52 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -2807,4 +2807,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 d5409190ca..75fb8eddd1 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -61,6 +61,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 ca24266124..0d2912f209 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 537b19149a..dcd805dc52 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -23,6 +23,7 @@ public: 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 9ec11aeda9..0e991365ab 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: @@ -71,6 +75,12 @@ void PtySignalInputThread::ConnectConsole() noexcept { _DoShowHide(_initialShowHide->show); } + + // If we were given a owner HWND, then manually start the pseudo window now. + if (_earlyReparent) + { + _DoSetWindowParent(*_earlyReparent); + } } // Method Description: @@ -150,6 +160,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); @@ -183,6 +215,20 @@ void PtySignalInputThread::_DoShowHide(const bool show) _pConApi->ShowWindow(show); } +// 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 2d548ebaf6..4de50e53e9 100644 --- a/src/host/PtySignalInputThread.hpp +++ b/src/host/PtySignalInputThread.hpp @@ -42,6 +42,7 @@ namespace Microsoft::Console { ShowHideWindow = 1, ClearBuffer = 2, + SetParent = 3, ResizeWindow = 8 }; @@ -50,14 +51,21 @@ namespace Microsoft::Console unsigned short sx; unsigned short sy; }; + struct ShowHideData { unsigned short show; // used as a bool, but passed as a ushort }; + 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 _DoShowHide(const bool show); void _Shutdown(); @@ -69,5 +77,8 @@ namespace Microsoft::Console std::optional _earlyResize; std::optional _initialShowHide; std::unique_ptr _pConApi; + + public: + std::optional _earlyReparent; }; } diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index eba55aad4f..4c72b00dad 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -300,6 +300,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 9ca6d88c6c..1093a02ec2 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -905,3 +905,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 dfe5721f11..45f1b55291 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -117,6 +117,8 @@ public: 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 49e084ff0c..a23d971615 100644 --- a/src/inc/conpty-static.h +++ b/src/inc/conpty-static.h @@ -27,6 +27,8 @@ HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC); HRESULT WINAPI ConptyShowHidePseudoConsole(HPCON hPC, bool show); +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 66971fa264..299a62977a 100644 --- a/src/interactivity/base/InteractivityFactory.cpp +++ b/src/interactivity/base/InteractivityFactory.cpp @@ -289,9 +289,10 @@ 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; @@ -311,21 +312,30 @@ using namespace Microsoft::Console::Interactivity; pseudoClass.lpfnWndProc = s_PseudoWindowProc; RegisterClass(&pseudoClass); + // 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; const auto exStyles = WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_LAYERED; - // Attempt to create window + + // Attempt to create window. hwnd = CreateWindowExW(exStyles, PSEUDO_WINDOW_CLASS, nullptr, - windowStyle, //WS_CHILD, //WS_OVERLAPPEDWINDOW, + windowStyle, 0, 0, 0, 0, - HWND_DESKTOP, // owner + owner, nullptr, nullptr, - this); + nullptr); if (hwnd == nullptr) { @@ -335,7 +345,6 @@ using namespace Microsoft::Console::Interactivity; 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 75aae306c1..eb6a1a3fd6 100644 --- a/src/interactivity/base/InteractivityFactory.hpp +++ b/src/interactivity/base/InteractivityFactory.hpp @@ -26,7 +26,7 @@ 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); void SetPseudoWindowCallback(std::function func); // Wndproc diff --git a/src/interactivity/base/ServiceLocator.cpp b/src/interactivity/base/ServiceLocator.cpp index 4c2d96ac06..2c1fd1e4a9 100644 --- a/src/interactivity/base/ServiceLocator.cpp +++ b/src/interactivity/base/ServiceLocator.cpp @@ -309,10 +309,11 @@ void ServiceLocator::SetPseudoWindowCallback(std::function func) // 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) @@ -325,7 +326,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); } @@ -337,8 +338,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 9eca29f119..55ee5a5b8e 100644 --- a/src/interactivity/inc/IInteractivityFactory.hpp +++ b/src/interactivity/inc/IInteractivityFactory.hpp @@ -41,7 +41,7 @@ namespace Microsoft::Console::Interactivity [[nodiscard]] virtual NTSTATUS CreateSystemConfigurationProvider(_Inout_ std::unique_ptr& provider) = 0; virtual void SetPseudoWindowCallback(std::function func) = 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 c8ff5d6e8e..d0a2c3c452 100644 --- a/src/interactivity/inc/ServiceLocator.hpp +++ b/src/interactivity/inc/ServiceLocator.hpp @@ -85,7 +85,7 @@ namespace Microsoft::Console::Interactivity static Globals& LocateGlobals(); static void SetPseudoWindowCallback(std::function func); - 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 2eafa90877..1445de358a 100644 --- a/src/terminal/adapter/conGetSet.hpp +++ b/src/terminal/adapter/conGetSet.hpp @@ -112,5 +112,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 ad333e551a..2f388554aa 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -428,6 +428,11 @@ public: 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/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index 658ee04d41..3a3d79fb46 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -269,7 +269,6 @@ HRESULT _ShowHidePseudoConsole(_In_ const PseudoConsole* const pPty, const bool { return E_INVALIDARG; } - unsigned short signalPacket[2]; signalPacket[0] = PTY_SIGNAL_SHOWHIDE_WINDOW; signalPacket[1] = show; @@ -278,6 +277,36 @@ HRESULT _ShowHidePseudoConsole(_In_ const PseudoConsole* const pPty, const bool return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError()); } +// - 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, @@ -465,6 +494,22 @@ extern "C" HRESULT WINAPI ConptyShowHidePseudoConsole(_In_ HPCON hPC, bool show) return hr; } +// - 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 da71a8ceb4..44d1fdbfd7 100644 --- a/src/winconpty/winconpty.h +++ b/src/winconpty/winconpty.h @@ -19,6 +19,7 @@ typedef struct _PseudoConsole // the signal pipe. #define PTY_SIGNAL_SHOWHIDE_WINDOW (1u) #define PTY_SIGNAL_CLEAR_WINDOW (2u) +#define PTY_SIGNAL_REPARENT_WINDOW (3u) #define PTY_SIGNAL_RESIZE_WINDOW (8u) // CreatePseudoConsole Flags @@ -38,6 +39,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);