-
Notifications
You must be signed in to change notification settings - Fork 8.4k
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
Avoid focus loops in ConPTY #17829
Avoid focus loops in ConPTY #17829
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -96,6 +96,7 @@ IGraphics | |
IImage | ||
IInheritable | ||
IMap | ||
imm | ||
IMonarch | ||
IObject | ||
iosfwd | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -74,8 +74,6 @@ void PtySignalInputThread::ConnectConsole() noexcept | |
{ | ||
_DoShowHide(*_initialShowHide); | ||
} | ||
|
||
// We should have successfully used the _earlyReparent message in CreatePseudoWindow. | ||
} | ||
|
||
// Method Description: | ||
|
@@ -87,8 +85,7 @@ void PtySignalInputThread::ConnectConsole() noexcept | |
// - Refer to GH#13066 for details. | ||
void PtySignalInputThread::CreatePseudoWindow() | ||
{ | ||
HWND owner = _earlyReparent.has_value() ? reinterpret_cast<HWND>((*_earlyReparent).handle) : HWND_DESKTOP; | ||
ServiceLocator::LocatePseudoWindow(owner); | ||
ServiceLocator::LocatePseudoWindow(); | ||
} | ||
|
||
// Method Description: | ||
|
@@ -220,7 +217,7 @@ void PtySignalInputThread::_DoShowHide(const ShowHideData& data) | |
return; | ||
} | ||
|
||
_api.ShowWindow(data.show); | ||
ServiceLocator::SetPseudoWindowVisibility(data.show); | ||
} | ||
|
||
// Method Description: | ||
|
@@ -237,44 +234,7 @@ void PtySignalInputThread::_DoSetWindowParent(const SetParentData& data) | |
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 = data; | ||
return; | ||
} | ||
|
||
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(owner) }) | ||
{ | ||
// SetWindowLongPtrW may call back into the message handler and wait for it to finish, | ||
// similar to SendMessageW(). If the conhost message handler is already processing and | ||
// waiting to acquire the console lock, which we're currently holding, we'd deadlock. | ||
// --> Release the lock now. | ||
Unlock.reset(); | ||
|
||
// 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 | ||
::SetWindowLongPtrW(pseudoHwnd, GWLP_HWNDPARENT, reinterpret_cast<LONG_PTR>(owner)); | ||
} | ||
ServiceLocator::SetPseudoWindowOwner(reinterpret_cast<HWND>(data.handle)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Azure PowerShell extension crashes the PowerShell process hard (not even an exception or anything) if you give it a I left these changes in, because it IMO still makes the code easier to maintain: Now all of the parent/owner code is centralized in a single file. |
||
} | ||
|
||
// Method Description: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,7 +86,7 @@ | |
</ClCompile> | ||
<Link> | ||
<AllowIsolation>true</AllowIsolation> | ||
<AdditionalDependencies>WinMM.Lib;%(AdditionalDependencies)</AdditionalDependencies> | ||
<AdditionalDependencies>winmm.lib;imm32.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which API do we need from this? If it is part of an APIset, we should prefer apiset linkage. We may also need to update |
||
</Link> | ||
</ItemDefinitionGroup> | ||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. --> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -208,15 +208,22 @@ CursorType ConhostInternalGetSet::GetUserDefaultCursorStyle() const | |
// - <none> | ||
void ConhostInternalGetSet::ShowWindow(bool showOrHide) | ||
{ | ||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); | ||
const auto hwnd = gci.IsInVtIoMode() ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle(); | ||
// ConPTY is supposed to be "transparent" to the VT application. Any VT it processes is given to the terminal. | ||
// As such, it must not react to this "CSI 1 t" or "CSI 2 t" sequence. That's the job of the terminal. | ||
// If the terminal encounters such a sequence, it can show/hide itself and let ConPTY know via its signal API. | ||
const auto window = ServiceLocator::LocateConsoleWindow(); | ||
if (!window) | ||
{ | ||
return; | ||
} | ||
Comment on lines
+211
to
+218
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
// GH#13301 - When we send this ShowWindow message, if we send it to the | ||
// conhost HWND, it's going to need to get processed by the window message | ||
// thread before returning. | ||
// However, ShowWindowAsync doesn't have this problem. It'll post the | ||
// message to the window thread, then immediately return, so we don't have | ||
// to worry about deadlocking. | ||
const auto hwnd = window->GetWindowHandle(); | ||
::ShowWindowAsync(hwnd, showOrHide ? SW_SHOWNOACTIVATE : SW_MINIMIZE); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -294,7 +294,7 @@ using namespace Microsoft::Console::Interactivity; | |
// - 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, const HWND owner) | ||
[[nodiscard]] NTSTATUS InteractivityFactory::CreatePseudoWindow(HWND& hwnd) | ||
{ | ||
hwnd = nullptr; | ||
ApiLevel level; | ||
|
@@ -310,6 +310,11 @@ using namespace Microsoft::Console::Interactivity; | |
{ | ||
case ApiLevel::Win32: | ||
{ | ||
// We don't need an "Default IME" window for ConPTY. That's the terminal's job. | ||
// -1, aka DWORD_MAX, tells the function to disable it for the entire process. | ||
// Must be called before creating any window. | ||
ImmDisableIME(DWORD_MAX); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This avoids spawning the entire IME machinery inside ConPTY. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, this answers my question about For That should be sufficient. |
||
|
||
pseudoClass.cbSize = sizeof(WNDCLASSEXW); | ||
pseudoClass.lpszClassName = PSEUDO_WINDOW_CLASS; | ||
pseudoClass.lpfnWndProc = s_PseudoWindowProc; | ||
|
@@ -330,19 +335,15 @@ using namespace Microsoft::Console::Interactivity; | |
// 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, | ||
hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, | ||
reinterpret_cast<LPCWSTR>(windowClassAtom), | ||
nullptr, | ||
windowStyle, | ||
WS_POPUP, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wonder if this fixes the "openconsole has a 0x0 window at 0x0" problem |
||
0, | ||
0, | ||
0, | ||
0, | ||
owner, | ||
_owner.load(std::memory_order_relaxed), | ||
nullptr, | ||
nullptr, | ||
this); | ||
|
@@ -375,6 +376,38 @@ using namespace Microsoft::Console::Interactivity; | |
return status; | ||
} | ||
|
||
void InteractivityFactory::SetOwner(HWND owner) noexcept | ||
{ | ||
_owner.store(owner, std::memory_order_relaxed); | ||
|
||
if (_pseudoConsoleWindowHwnd) | ||
{ | ||
// 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 | ||
::SetWindowLongPtrW(_pseudoConsoleWindowHwnd, GWLP_HWNDPARENT, reinterpret_cast<LONG_PTR>(owner)); | ||
} | ||
} | ||
|
||
void InteractivityFactory::SetVisibility(const bool isVisible) noexcept | ||
{ | ||
if (_pseudoConsoleWindowHwnd && IsIconic(_pseudoConsoleWindowHwnd) != static_cast<BOOL>(isVisible)) | ||
{ | ||
_suppressVisibilityChange.store(true, std::memory_order_relaxed); | ||
ShowWindow(_pseudoConsoleWindowHwnd, isVisible ? SW_SHOWNOACTIVATE : SW_MINIMIZE); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we certain that this is processed in order on all versions of Windows we care about? Alternatively... should we do the management of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. open question There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I overlooked this one. When you say in-order, what do you mean? If it's what I think it is, then yes, this will work: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perfect. thanks. |
||
_suppressVisibilityChange.store(false, std::memory_order_relaxed); | ||
} | ||
Comment on lines
+403
to
+408
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
// Method Description: | ||
// - Static window procedure for pseudo console windows | ||
// - Processes set-up on create to stow the "this" pointer to specific instantiations and routes | ||
|
@@ -446,7 +479,7 @@ using namespace Microsoft::Console::Interactivity; | |
{ | ||
_WritePseudoWindowCallback(false); | ||
} | ||
break; | ||
return 0; | ||
} | ||
// case WM_WINDOWPOSCHANGING: | ||
// As long as user32 didn't eat the `ShowWindow` call because the window state requested | ||
|
@@ -479,12 +512,12 @@ using namespace Microsoft::Console::Interactivity; | |
} | ||
case WM_ACTIVATE: | ||
{ | ||
if (const auto ownerHwnd{ ::GetAncestor(hWnd, GA_ROOTOWNER) }) | ||
if (const auto ownerHwnd = _owner.load(std::memory_order_relaxed)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The benefit of this change is that we now assign focus to the exact HWND that we were told is our owning window and not any window up the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, this is great |
||
{ | ||
SetFocus(ownerHwnd); | ||
return 0; | ||
} | ||
break; | ||
return 0; | ||
} | ||
} | ||
// If we get this far, call the default window proc | ||
|
@@ -501,6 +534,11 @@ using namespace Microsoft::Console::Interactivity; | |
// - <none> | ||
void InteractivityFactory::_WritePseudoWindowCallback(bool showOrHide) | ||
{ | ||
if (_suppressVisibilityChange.load(std::memory_order_relaxed)) | ||
{ | ||
return; | ||
} | ||
|
||
// IMPORTANT! | ||
// | ||
// A hosting terminal window should only "restore" itself in response to | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.