Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ShowWindow(GetConsoleWindow()) #13118

Merged
merged 16 commits into from May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/cascadia/TerminalApp/TerminalPage.cpp
Expand Up @@ -2431,7 +2431,10 @@ namespace winrt::TerminalApp::implementation
TermControl term{ settings.DefaultSettings(), settings.UnfocusedSettings(), connection };

// GH#12515: ConPTY assumes it's hidden at the start. If we're not, let it know now.
term.WindowVisibilityChanged(_visible);
if (_visible)
{
term.WindowVisibilityChanged(_visible);
}

if (_hostingHwnd.has_value())
{
Expand Down
8 changes: 6 additions & 2 deletions src/cascadia/TerminalConnection/ConptyConnection.cpp
Expand Up @@ -311,13 +311,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation

THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, flags, &_inPipe, &_outPipe, &_hPC));

// GH#12515: The conpty assumes it's hidden at the start. If we're visible, let it know now.
THROW_IF_FAILED(ConptyShowHidePseudoConsole(_hPC.get(), _initialVisibility));
if (_initialParentHwnd != 0)
{
THROW_IF_FAILED(ConptyReparentPseudoConsole(_hPC.get(), reinterpret_cast<HWND>(_initialParentHwnd)));
}

// GH#12515: The conpty assumes it's hidden at the start. If we're visible, let it know now.
if (_initialVisibility)
{
THROW_IF_FAILED(ConptyShowHidePseudoConsole(_hPC.get(), _initialVisibility));
}

THROW_IF_FAILED(_LaunchAttachedClient());
}
// But if it was an inbound handoff... attempt to synchronize the size of it with what our connection
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalConnection/ConptyConnection.h
Expand Up @@ -73,7 +73,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
hstring _commandline{};
hstring _startingDirectory{};
hstring _startingTitle{};
bool _initialVisibility{ false };
bool _initialVisibility{ true };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to admit, I am VERY worried that I read two comments that say "conpty assumes it's hidden" and then this banger that makes ConptyConnection assume it is visible

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it makes sense, it was just surprising

Windows::Foundation::Collections::ValueSet _environment{ nullptr };
guid _guid{}; // A unique session identifier for connected client
hstring _clientName{}; // The name of the process hosted by this ConPTY connection (as of launch).
Expand Down
16 changes: 11 additions & 5 deletions src/cascadia/TerminalControl/ControlCore.cpp
Expand Up @@ -1196,8 +1196,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation

void ControlCore::_terminalShowWindowChanged(bool showOrHide)
{
auto showWindow = winrt::make_self<implementation::ShowWindowArgs>(showOrHide);
_ShowWindowChangedHandlers(*this, *showWindow);
if (_initializedTerminal)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vague horror that we might be getting this callback before we have finished initialization...!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would actually bet that code is vestigial at this point. I'd guess leftover from me experimenting with the debouncing. That being said, I thought that 0dc6e0d wasn't needed, and it totally was.

{
auto showWindow = winrt::make_self<implementation::ShowWindowArgs>(showOrHide);
_ShowWindowChangedHandlers(*this, *showWindow);
}
}

bool ControlCore::HasSelection() const
Expand Down Expand Up @@ -1703,10 +1706,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - <none>
void ControlCore::WindowVisibilityChanged(const bool showOrHide)
{
// show is true, hide is false
if (auto conpty{ _connection.try_as<TerminalConnection::ConptyConnection>() })
if (_initializedTerminal)
{
conpty.ShowHide(showOrHide);
// show is true, hide is false
if (auto conpty{ _connection.try_as<TerminalConnection::ConptyConnection>() })
{
conpty.ShowHide(showOrHide);
}
}
}

Expand Down
36 changes: 29 additions & 7 deletions src/host/PtySignalInputThread.cpp
Expand Up @@ -75,11 +75,20 @@ 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);
}
// We should have successfully used the _earlyReparent message in CreatePseudoWindow.
}

// Method Description:
// - Create our pseudo window. We're doing this here, instead of in
// ConnectConsole, because the window is created in
// ConsoleInputThreadProcWin32, before ConnectConsole is first called. Doing
// this here ensures that the window is first created with the initial owner
// set up (if so specified).
// - Refer to GH#13066 for details.
void PtySignalInputThread::CreatePseudoWindow()
{
HWND owner = _earlyReparent.has_value() ? reinterpret_cast<HWND>((*_earlyReparent).handle) : HWND_DESKTOP;
ServiceLocator::LocatePseudoWindow(owner);
}

// Method Description:
Expand Down Expand Up @@ -227,15 +236,28 @@ void PtySignalInputThread::_DoShowHide(const bool show)
// - <none>
void PtySignalInputThread::_DoSetWindowParent(const SetParentData& data)
{
const auto owner{ reinterpret_cast<HWND>(data.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 pseudoHwnd{ ServiceLocator::LocatePseudoWindow(reinterpret_cast<HWND>(data.handle)) })
if (const auto pseudoHwnd{ ServiceLocator::LocatePseudoWindow(owner) })
{
LOG_LAST_ERROR_IF_NULL(::SetParent(pseudoHwnd, reinterpret_cast<HWND>(data.handle)));
// DO NOT USE SetParent HERE!
//
// Calling SetParent on a window that is WS_VISIBLE will cause the OS to
// hide the window, make it a _child_ window, then call SW_SHOW on the
// window to re-show it. SW_SHOW, however, will cause the OS to also set
// that window as the _foreground_ window, which would result in the
// pty's hwnd stealing the foreground away from the owning terminal
// window. That's bad.
//
// SetWindowLongPtr seems to do the job of changing who the window owner
// is, without all the other side effects of reparenting the window.
// See #13066
::SetWindowLongPtr(pseudoHwnd, GWLP_HWNDPARENT, reinterpret_cast<LONG_PTR>(owner));
}
}

Expand Down
1 change: 1 addition & 0 deletions src/host/PtySignalInputThread.hpp
Expand Up @@ -33,6 +33,7 @@ namespace Microsoft::Console
PtySignalInputThread& operator=(const PtySignalInputThread&) = delete;

void ConnectConsole() noexcept;
void CreatePseudoWindow();

private:
enum class PtySignal : unsigned short
Expand Down
26 changes: 21 additions & 5 deletions src/host/VtIo.cpp
Expand Up @@ -300,18 +300,34 @@ 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.
//
// Let the signal thread know that the console is connected
// By this point, the pseudo window should have already been created, by
// ConsoleInputThreadProcWin32. That thread has a message pump, which is
// needed to ensure that DPI change messages to the owning terminal
// window don't end up hanging because the pty didn't also process it.
_pPtySignalInputThread->ConnectConsole();
}

return S_OK;
}

// Method Description:
// - Create our pseudo window. This is exclusively called by
// ConsoleInputThreadProcWin32 on the console input thread.
// * It needs to be called on that thread, before any other calls to
// LocatePseudoWindow, to make sure that the input thread is the HWND's
Comment on lines +318 to +319
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any way to guarantee that this happens before LPW?

// message thread.
// * It needs to be plumbed through the signal thread, because the signal
// thread knows if someone should be marked as the window's owner. It's
// VERY IMPORTANT that any initial owners are set up when the window is
// first created.
// - Refer to GH#13066 for details.
void VtIo::CreatePseudoWindow()
{
_pPtySignalInputThread->CreatePseudoWindow();
}

// Method Description:
// - Create and start the signal thread. The signal thread can be created
// independent of the i/o threads, and doesn't require a client first
Expand Down
2 changes: 2 additions & 0 deletions src/host/VtIo.hpp
Expand Up @@ -52,6 +52,8 @@ namespace Microsoft::Console::VirtualTerminal

[[nodiscard]] HRESULT ManuallyClearScrollback() const noexcept;

void CreatePseudoWindow();

private:
// After CreateIoHandlers is called, these will be invalid.
wil::unique_hfile _hInput;
Expand Down
2 changes: 1 addition & 1 deletion src/host/outputStream.cpp
Expand Up @@ -285,7 +285,7 @@ void ConhostInternalGetSet::ShowWindow(bool showOrHide)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto hwnd = gci.IsInVtIoMode() ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle();
::ShowWindow(hwnd, showOrHide ? SW_NORMAL : SW_MINIMIZE);
::ShowWindow(hwnd, showOrHide ? SW_SHOWNOACTIVATE : SW_MINIMIZE);
}

// Routine Description:
Expand Down
12 changes: 9 additions & 3 deletions src/interactivity/base/InteractivityFactory.cpp
Expand Up @@ -320,8 +320,14 @@ using namespace Microsoft::Console::Interactivity;
// 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;
//
// GH#13066: Load-bearing: Make sure to set WS_POPUP. If you
// don't, then GetAncestor(GetConsoleWindow(), GA_ROOTOWNER)
// will return the console handle again, not the owning
// terminal's handle. It's not entirely clear why, but WS_POPUP
// is absolutely vital for this to work correctly.
const auto windowStyle = WS_OVERLAPPEDWINDOW | WS_POPUP;
const auto exStyles = WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOACTIVATE;

// Attempt to create window.
hwnd = CreateWindowExW(exStyles,
Expand All @@ -335,7 +341,7 @@ using namespace Microsoft::Console::Interactivity;
owner,
nullptr,
nullptr,
nullptr);
this);

if (hwnd == nullptr)
{
Expand Down
15 changes: 12 additions & 3 deletions src/interactivity/win32/windowio.cpp
Expand Up @@ -1036,9 +1036,18 @@ DWORD WINAPI ConsoleInputThreadProcWin32(LPVOID /*lpParameter*/)
// If we are headless (because we're a pseudo console), we
// will still need a window handle in the win32 environment
// in case anyone sends messages at that HWND (vim.exe is an example.)
// We have to CreateWindow on the same thread that will pump the messages
// which is this thread.
ServiceLocator::LocatePseudoWindow();
//
// IMPORTANT! We have to CreateWindow on the same thread that will pump
// the messages, which is this thread. 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.
// ServiceLocator::LocatePseudoWindow();
//
// Instead of just calling LocatePseudoWindow, make sure to go through
// VtIo's CreatePseudoWindow, which will make sure that the window is
// successfully created with the owner configured when the window is
// first created. See GH#13066 for details.
ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CreatePseudoWindow();
}

UnlockConsole();
Expand Down
18 changes: 13 additions & 5 deletions src/terminal/adapter/InteractDispatch.cpp
Expand Up @@ -199,11 +199,19 @@ bool InteractDispatch::FocusChanged(const bool focused) const
{
// They want focus, we found a pseudo hwnd.

// Note: ::GetParent(pseudoHwnd) will return 0. GetAncestor works though.
// GA_PARENT and GA_ROOT seemingly return the same thing for
// Terminal. We're going with GA_ROOT since it seems
// semantically more correct here.
if (const auto ownerHwnd{ ::GetAncestor(pseudoHwnd, GA_ROOT) })
// BODGY
//
// This needs to be GA_ROOTOWNER here. Not GA_ROOT, GA_PARENT,
// or GetParent. The ConPTY hwnd is an owned, top-level, popup,
// non-parented window. It does not have a parent set. It does
// have an owner set. It is not a WS_CHILD window. This
// combination of things allows us to find the owning window
// with GA_ROOTOWNER. GA_ROOT will get us ourselves, and
// GA_PARENT will return the desktop HWND.
//
// See GH#13066

if (const auto ownerHwnd{ ::GetAncestor(pseudoHwnd, GA_ROOTOWNER) })
{
// We have an owner from a previous call to ReparentWindow

Expand Down