diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index daf1feb13af..c3f4e26accc 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -144,6 +144,7 @@ REGCLS RETURNCMD rfind roundf +ROOTOWNER RSHIFT SACL schandle diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 62fa438ce9d..f56e399affb 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -65,6 +65,8 @@ namespace winrt::TerminalApp::implementation void LoadSettings(); [[nodiscard]] Microsoft::Terminal::Settings::Model::CascadiaSettings GetSettings() const noexcept; + void SetOwnerHwnd(uint64_t owner) { _root->SetOwnerHwnd(owner); } + void Quit(); bool HasCommandlineArguments() const noexcept; diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index ae5fdbc319f..70e2f9018c9 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -46,6 +46,8 @@ namespace TerminalApp // registered?" when it definitely is. void Create(); + void SetOwnerHwnd(UInt64 owner); + Boolean IsUwp(); void RunAsUwp(); Boolean IsElevated(); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 008fa1ebe88..724d041ad68 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -2426,6 +2426,9 @@ 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(_ownerHwnd); + // term.OwningHwnd(reinterpret_cast(_ownerHwnd)); return term; } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 0c65cb2837a..d67da870c30 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -63,6 +63,9 @@ namespace winrt::TerminalApp::implementation void Create(); + uint64_t _ownerHwnd{ 0 }; + void SetOwnerHwnd(uint64_t owner) { _ownerHwnd = owner; }; + bool ShouldUsePersistedLayout(Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const; bool ShouldImmediatelyHandoffToElevated(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const; void HandoffToElevated(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index b4e5978440b..f25ae642b4a 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -296,6 +296,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation if (!_inPipe) { THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, PSEUDOCONSOLE_RESIZE_QUIRK | PSEUDOCONSOLE_WIN32_INPUT_MODE, &_inPipe, &_outPipe, &_hPC)); + 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 @@ -313,6 +314,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(); @@ -468,6 +470,20 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } } + void ConptyConnection::ReparentWindow(const uint64_t newParent) + { + // If we haven't started connecting at all, TODO! + if (!_isStateAtOrBeyond(ConnectionState::Connecting)) + { + _initialParentHwnd = newParent; + } + // Otherwise, TODO! + 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 4089a944675..65037e922ee 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 55502b8cf5b..34c1ee574e6 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -264,6 +264,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto height = vp.Height(); _connection.Resize(height, width); + 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 7afe41b8ccf..b20d2f5c162 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -170,6 +170,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void WindowVisibilityChanged(const bool showOrHide); + 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 5df2d2eb45b..4a043181b47 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -2790,4 +2790,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 d5409190ca0..75fb8eddd12 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/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index efa98781803..d6e03285e35 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -335,6 +335,8 @@ void AppHost::Initialize() if (auto withWindow{ _logic.try_as() }) { withWindow->Initialize(_window->GetHandle()); + // _logic.SetOwnerHwnd(reinterpret_cast(_window->GetInteropHandle())); + _logic.SetOwnerHwnd(reinterpret_cast(_window->GetHandle())); } if (_useNonClientArea) diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index df92afbc8a9..dc63551c4f7 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 537b19149af..dcd805dc52f 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..3bb20c6fa68 100644 --- a/src/host/PtySignalInputThread.cpp +++ b/src/host/PtySignalInputThread.cpp @@ -67,6 +67,10 @@ void PtySignalInputThread::ConnectConsole() noexcept { _DoResizeWindow(*_earlyResize); } + if (_earlyReparent) + { + _DoSetWindowParent(*_earlyReparent); + } } // Method Description: @@ -119,6 +123,26 @@ void PtySignalInputThread::ConnectConsole() noexcept break; } + case PtySignal::SetParent: + { + SetParentData reparentMessage = { 0 }; + _GetData(&reparentMessage, sizeof(reparentMessage)); + + LockConsole(); + auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); + + // todo + if (!_consoleConnected) + { + _earlyReparent = reparentMessage; + } + else + { + _DoSetWindowParent(reparentMessage); + } + + break; + } default: { THROW_HR(E_UNEXPECTED); @@ -147,6 +171,11 @@ void PtySignalInputThread::_DoClearBuffer() _pConApi->ClearBuffer(); } +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 86899eb59c6..8bf952594d1 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -258,6 +258,16 @@ 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 parent hwnd will + // cause us to get a dpi change as well, which we'll never deque and + // handle, effectively HANGING THE PARENT HWND. super bad. + // + // TODO! clean this up + if (_pPtySignalInputThread->_earlyReparent.has_value()) + { + ServiceLocator::LocatePseudoWindow(reinterpret_cast(_pPtySignalInputThread->_earlyReparent.value().handle)); + } // 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 4a3990c040b..35da15184f2 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -905,3 +905,13 @@ 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 (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 dfe5721f11b..45f1b552915 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -117,6 +117,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 052708a6bd5..8bba2750b56 100644 --- a/src/inc/conpty-static.h +++ b/src/inc/conpty-static.h @@ -24,6 +24,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 519f42cccb2..6cffb975696 100644 --- a/src/interactivity/base/InteractivityFactory.cpp +++ b/src/interactivity/base/InteractivityFactory.cpp @@ -293,7 +293,7 @@ using namespace Microsoft::Console::Interactivity; // - hwnd: Receives the value of the newly created window's HWND. // 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; @@ -313,9 +313,25 @@ using namespace Microsoft::Console::Interactivity; pseudoClass.lpfnWndProc = s_PseudoWindowProc; RegisterClass(&pseudoClass); + // const auto windowStyle = (owner == HWND_DESKTOP) ? WS_OVERLAPPEDWINDOW : WS_CHILD; + const auto windowStyle = WS_OVERLAPPEDWINDOW; + // Attempt to create window hwnd = CreateWindowExW( - WS_EX_TOOLWINDOW, PSEUDO_WINDOW_CLASS, nullptr, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, HWND_DESKTOP, nullptr, nullptr, this); + WS_EX_TOOLWINDOW, + PSEUDO_WINDOW_CLASS, + nullptr, + windowStyle, //WS_CHILD, //WS_OVERLAPPEDWINDOW, + 0, + 0, + 0, + 0, + // Niksa just had the parent as HWND_DESKTOP always, + // This branch tests a merged version of the prototypes + owner /*(HWND)0x00070C6A*/ /*HWND_DESKTOP*/, // parent + nullptr, + nullptr, + this); if (hwnd == nullptr) { DWORD const gle = GetLastError(); diff --git a/src/interactivity/base/InteractivityFactory.hpp b/src/interactivity/base/InteractivityFactory.hpp index 75aae306c18..eb6a1a3fd6c 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 4c2d96ac06a..68e405d8ea5 100644 --- a/src/interactivity/base/ServiceLocator.cpp +++ b/src/interactivity/base/ServiceLocator.cpp @@ -312,7 +312,7 @@ void ServiceLocator::SetPseudoWindowCallback(std::function func) // - // 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 +325,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); } @@ -335,7 +335,14 @@ HWND ServiceLocator::LocatePseudoWindow() return s_pseudoWindow.get(); } -#pragma endregion +// void ServiceLocator::ReparentPseudoHwnd(HWND newParent) +// { +// // This will initialize s_interactivityFactory for us. It will also conveniently return 0 when we're on OneCore. +// if (const auto psuedoHwnd{ LocatePseudoWindow() }) +// { +// ::SetParent(psuedoHwnd, newParent); +// } +// } #pragma endregion diff --git a/src/interactivity/inc/IInteractivityFactory.hpp b/src/interactivity/inc/IInteractivityFactory.hpp index 9eca29f1195..55ee5a5b8ea 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 c8ff5d6e8e6..d0a2c3c4524 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 2eafa90877d..1445de358a5 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 ad333e551a8..d05dd37aa01 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -428,6 +428,11 @@ class TestGetSet final : public ConGetSet VERIFY_ARE_EQUAL(_expectedCellSize.cy, cellSize.cy); } + void ReparentWindow(const uint64_t /*handle*/) + { + Log::Comment(L"UpdateSoftFont 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/tools/scratch/main.cpp b/src/tools/scratch/main.cpp index c6f9cc901a2..26bcbec1acd 100644 --- a/src/tools/scratch/main.cpp +++ b/src/tools/scratch/main.cpp @@ -1,10 +1,216 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +#include "../../inc/LibraryIncludes.h" #include +// Register the window class. +const wchar_t CLASS_NAME[] = L"Sample Window Class"; + +struct handle_data +{ + unsigned long process_id; + HWND window_handle; +}; + +BOOL is_main_window(HWND handle) +{ + auto owner{ GetWindow(handle, GW_OWNER) }; + // wprintf(fmt::format(L"\t\towner: {}\n", reinterpret_cast(owner)).c_str()); + return owner == (HWND)0 && IsWindowVisible(handle); +} + +BOOL CALLBACK enum_windows_callback(HWND handle, LPARAM lParam) +{ + // wprintf(fmt::format(L"enum_windows_callback\n").c_str()); + handle_data& data = *(handle_data*)lParam; + unsigned long process_id = 0; + GetWindowThreadProcessId(handle, &process_id); + const bool pidDidntMatch{ data.process_id != process_id }; + // wprintf(fmt::format(L"\tpidDidntMatch: {}\n", pidDidntMatch).c_str()); + const bool wasntMainWindow{ !is_main_window(handle) }; + // wprintf(fmt::format(L"\twasntMainWindow: {}\n", wasntMainWindow).c_str()); + if (pidDidntMatch || wasntMainWindow) + { + return TRUE; + } + + wprintf(fmt::format(L"\tpidDidntMatch: {}\n", pidDidntMatch).c_str()); + wprintf(fmt::format(L"\twasntMainWindow: {}\n", wasntMainWindow).c_str()); + + auto owner{ GetWindow(handle, GW_OWNER) }; + wprintf(fmt::format(L"\t\thandle: {}\n", reinterpret_cast(handle)).c_str()); + wprintf(fmt::format(L"\t\towner: {}\n", reinterpret_cast(owner)).c_str()); + + data.window_handle = handle; + return FALSE; +} + +HWND find_main_window(unsigned long process_id) +{ + handle_data data; + data.process_id = process_id; + data.window_handle = 0; + EnumWindows(enum_windows_callback, (LPARAM)&data); + return data.window_handle; +} + +static LRESULT __stdcall WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept +{ + static bool gotKeyDown{ false }; + switch (message) + { + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + case WM_KEYDOWN: + gotKeyDown = true; + return 0; + case WM_KEYUP: + if (gotKeyDown) + DestroyWindow(window); + return 0; + } + return DefWindowProc(window, message, wparam, lparam); +} + +int doTheWindowThing(HWND hwndToUseAsParent) +{ + const auto hInst{ GetModuleHandle(NULL) }; + wprintf(fmt::format(L"=====Creating a Window, then a MessageBox, using {} as the parent HWND=====\n", reinterpret_cast(hwndToUseAsParent)).c_str()); + + auto doWindowCreateLoop = [&](bool child) { + // Create the window. + HWND hwnd = CreateWindowEx( + 0, // Optional window styles. + CLASS_NAME, // Window class + L"Learn to Program Windows", // Window text + WS_OVERLAPPEDWINDOW | (child ? WS_CHILD : 0), // Window style + + // Size and position + 200, + 200, + 200, + 200, + + hwndToUseAsParent, // Parent window + NULL, // Menu + hInst, // Instance handle + NULL // Additional application data + ); + + // wprintf(fmt::format(L"hwnd: {}\n", reinterpret_cast(hwnd)).c_str()); + + // const auto newHwnd{ find_main_window(pid) }; + // wprintf(fmt::format(L"newHwnd: {}\n", reinterpret_cast(newHwnd)).c_str()); + + if (hwnd == NULL) + { + return 0; + } + wprintf(fmt::format(L" created window\n").c_str()); + + ShowWindow(hwnd, SW_SHOW); + MSG msg = {}; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + wprintf(fmt::format(L" window was closed\n").c_str()); + return 0; + }; + + wprintf(fmt::format(L" create an unowned window...\n").c_str()); + doWindowCreateLoop(false); + + wprintf(fmt::format(L" create a child window...\n").c_str()); + doWindowCreateLoop(true); + + wprintf(fmt::format(L" Opening a MessageBoxW...\n").c_str()); + MessageBoxW(hwndToUseAsParent, L"foo", L"bar", MB_OK); + wprintf(fmt::format(L" closed a MessageBoxW\n").c_str()); + return 0; +} + +int createSomeWindows(const HWND& consoleHwnd) +{ + WNDCLASS wc = {}; + const auto hInst{ GetModuleHandle(NULL) }; + wc.lpfnWndProc = WndProc; + wc.hInstance = hInst; + wc.lpszClassName = CLASS_NAME; + + RegisterClass(&wc); + + wprintf(fmt::format(L"Make some windows, using NULL as the parent.\n").c_str()); + + doTheWindowThing(nullptr); + wprintf(fmt::format(L"Now, with the console window handle.\n").c_str()); + doTheWindowThing(consoleHwnd); + + return 0; +} + +int doDefaultOutput() +{ + const auto pid{ GetCurrentProcessId() }; + const auto consoleWindow{ GetConsoleWindow() }; + + wprintf(fmt::format(L"pid: {}\n", pid).c_str()); + wprintf(fmt::format(L"consoleWindow: {0:#010x}\n", reinterpret_cast(consoleWindow)).c_str()); + + const auto mainHwnd{ find_main_window(pid) }; + wprintf(fmt::format(L"mainHwnd: {0:#010x}\n", reinterpret_cast(mainHwnd)).c_str()); + + const auto consoleParent{ GetParent(consoleWindow) }; + wprintf(fmt::format(L"parent: {0:#010x}\n", reinterpret_cast(consoleParent)).c_str()); + const auto consoleOwner{ GetWindow(consoleWindow, GW_OWNER) }; + wprintf(fmt::format(L"owner: {0:#010x}\n", reinterpret_cast(consoleOwner)).c_str()); + const auto consoleAncestor_PARENT{ GetAncestor(consoleWindow, GA_PARENT) }; + const auto consoleAncestor_ROOT{ GetAncestor(consoleWindow, GA_ROOT) }; + const auto consoleAncestor_ROOTOWNER{ GetAncestor(consoleWindow, GA_ROOTOWNER) }; + wprintf(fmt::format(L"Ancestor_PARENT: {0:#010x}\n", reinterpret_cast(consoleAncestor_PARENT)).c_str()); + wprintf(fmt::format(L"Ancestor_ROOT: {0:#010x}\n", reinterpret_cast(consoleAncestor_ROOT)).c_str()); + wprintf(fmt::format(L"Ancestor_ROOTOWNER: {0:#010x}\n", reinterpret_cast(consoleAncestor_ROOTOWNER)).c_str()); + + return 0; +} + // This wmain exists for help in writing scratch programs while debugging. -int __cdecl wmain(int /*argc*/, WCHAR* /*argv[]*/) +int __cdecl wmain(int argc, WCHAR* argv[]) { + doDefaultOutput(); + + const auto consoleWindow{ GetConsoleWindow() }; + + if (argc <= 1) + { + return 0; + } + + HWND target = consoleWindow; + std::wstring arg{ argv[1] }; + if (arg == L"--parent" && argc > 2) + { + target = GetAncestor(consoleWindow, GA_ROOT); + arg = argv[2]; + } + + if (arg == L"messagebox") + { + MessageBoxW(target, L"foo", L"bar", MB_OK); + } + else if (arg == L"windows") + { + createSomeWindows(target); + } + else if (arg == L"hide") + { + ShowWindow(target, SW_HIDE); + } + return 0; } diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index 7653ce6bd5c..2feae841ff7 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -252,6 +252,37 @@ HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty) return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError()); } +// Function Description: +// - TODO! +HRESULT _ReparentPseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const HWND newParent) +{ + if (pPty == nullptr) + { + return E_INVALIDARG; + } + +#pragma pack(push, 1) + struct _signal + { + const unsigned short id; // = PTY_SIGNAL_RESIZE_WINDOW; + const uint64_t hwnd; // = reinterpret_cast(newParent); + } data{ PTY_SIGNAL_REPARENT_WINDOW, reinterpret_cast(newParent) }; +#pragma pack(pop) + + // unsigned short signalPacket[5]; + // signalPacket[0] = PTY_SIGNAL_RESIZE_WINDOW; + // // hwnd is a uint64_t. + // signalPacket[1] = size.X; + // signalPacket[2] = size.Y; + // signalPacket[3] = size.Y; + // signalPacket[4] = size.Y; + // static_assert(sizeof(data) == (10)); + // static_assert(sizeof(data) == (sizeof(unsigned short) + sizeof(HWND))); + + 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, @@ -423,6 +454,19 @@ extern "C" HRESULT WINAPI ConptyClearPseudoConsole(_In_ HPCON hPC) return hr; } +// Function Description: +// - TODO! +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 f2a7ff429ee..32458634c15 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 @@ -36,6 +37,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);