-
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
Conversation
16e54e9
to
a3c12bf
Compare
shared->focusChanged = std::make_unique<til::debounced_func_trailing<bool>>( | ||
std::chrono::milliseconds{ 25 }, | ||
[weakThis = get_weak()](const bool focused) { | ||
if (const auto core{ weakThis.get() }) | ||
{ | ||
core->_focusChanged(focused); | ||
} | ||
}); |
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.
- Avoid emitting multiple such sequences when rapidly un-/focusing 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 comment
The 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 HWND_MESSAGE
. But my entire concept was based around that! That's why I moved all of the parent/owner code to InteractivityFactory
too (below in this PR): A HWND_MESSAGE
window cannot have a custom parent like this and so I had to store the owner in a member. I felt like InteractivityFactory
should store it.
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.
// 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; | ||
} |
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.
- Avoid emitting another de-/iconify VT sequence when we encounter a (de)iconify VT sequence during parsing.
reinterpret_cast<LPCWSTR>(windowClassAtom), | ||
nullptr, | ||
windowStyle, | ||
WS_POPUP, |
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.
No WS_OVERLAPPEDWINDOW
= no DWM, no border, no size. Makes it cheaper and easier. Seems to still work too.
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.
Wonder if this fixes the "openconsole has a 0x0 window at 0x0" problem
// 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this answers my question about imm32
.
For sources
, can you instead link ext-ms-win-imm-l1-1-0
? It is an extension APIset and can be delay-loaded.
That should be sufficient.
No need to change the link line for the vcxproj.
if (_pseudoConsoleWindowHwnd && IsIconic(_pseudoConsoleWindowHwnd) != static_cast<BOOL>(isVisible)) | ||
{ | ||
_suppressVisibilityChange.store(true, std::memory_order_relaxed); | ||
ShowWindow(_pseudoConsoleWindowHwnd, isVisible ? SW_SHOWNOACTIVATE : SW_MINIMIZE); | ||
_suppressVisibilityChange.store(false, std::memory_order_relaxed); | ||
} |
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.
- Avoid emitting a de-/iconify VT sequence when a focus event is received on the signal pipe.
WI_UpdateFlag(gci.Flags, CONSOLE_HAS_FOCUS, focused); | ||
gci.ProcessHandleList.ModifyConsoleProcessFocus(focused); |
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.
This is the security related change I mentioned.
@@ -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 comment
The 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 GA_ROOTOWNER
chain.
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.
Oh, this is great
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.
Nice work!
@@ -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 comment
The 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 sources
.
// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this answers my question about imm32
.
For sources
, can you instead link ext-ms-win-imm-l1-1-0
? It is an extension APIset and can be delay-loaded.
That should be sufficient.
No need to change the link line for the vcxproj.
reinterpret_cast<LPCWSTR>(windowClassAtom), | ||
nullptr, | ||
windowStyle, | ||
WS_POPUP, |
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.
Wonder if this fixes the "openconsole has a 0x0 window at 0x0" problem
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 comment
The 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 suppress
in a custom window message?
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.
open question
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.
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: ShowWindow
is synchronous by spec. There's an alternative. ShowWindowAsync
.
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.
perfect. thanks.
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this is great
Should this one demote to Draft? |
No, I'll dedicate next week to doing WT work. :) |
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 comment
The reason will be displayed to describe this comment to others. Learn more.
open question
This fixes a lot of subtle issues: * Avoid emitting another de-/iconify VT sequence when we encounter a (de)iconify VT sequence during parsing. * Avoid emitting a de-/iconify VT sequence when a focus event is received on the signal pipe. * Avoid emitting such sequences on startup. * Avoid emitting multiple such sequences when rapidly un-/focusing the window. It's also a minor cleanup, because the `GA_ROOTOWNER` is not security relevant. It was added because there was concern that someone can just spawn a ConPTY session, tell it that it's focused, and spawn a child which is now focused. But why would someone do that, when the console IOCTLs to do so are not just publicly available but also documented? I also disabled the IME window. ## Validation Steps Performed * First: ```cpp int main() { for (bool show = false;; show = !show) { printf(show ? "Show in 3s...\n" : "Hide in 3s...\n"); Sleep(3000); ShowWindow(GetConsoleWindow(), show ? SW_SHOW : SW_HIDE); } } ``` * PowerShell 5's `Get-Credential` gains focus ✅ * `sleep 5; Get-Credential` and focus another app. WT should start blinking in the taskbar. Restore it. The popup has focus ✅ * Run `:hardcopy` in vim: Window is shown centered at (0,0) ✖️ But that's okay because it does that already anyway ✅ * `Connect-AzAccount` doesn't crash PowerShell ✅ (cherry picked from commit 4386bf0) Service-Card-Id: PVTI_lADOAF3p4s4AmhmQzgTs61k Service-Version: 1.22
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.
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.
Should do
This fixes a lot of subtle issues:
we encounter a (de)iconify VT sequence during parsing.
a focus event is received on the signal pipe.
when rapidly un-/focusing the window.
It's also a minor cleanup, because the
GA_ROOTOWNER
is not securityrelevant. It was added because there was concern that someone can just
spawn a ConPTY session, tell it that it's focused, and spawn a child
which is now focused. But why would someone do that, when the console
IOCTLs to do so are not just publicly available but also documented?
I also disabled the IME window.
Validation Steps Performed
Get-Credential
gains focus ✅sleep 5; Get-Credential
and focus another app. WT should startblinking in the taskbar. Restore it. The popup has focus ✅
:hardcopy
in vim: Window is shown centered at (0,0) ✖️But that's okay because it does that already anyway ✅
Connect-AzAccount
doesn't crash PowerShell ✅