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

Avoid focus loops in ConPTY #17829

Merged
merged 3 commits into from
Oct 8, 2024
Merged

Avoid focus loops in ConPTY #17829

merged 3 commits into from
Oct 8, 2024

Conversation

lhecker
Copy link
Member

@lhecker lhecker commented Aug 29, 2024

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:
    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 ✅

@lhecker lhecker added Product-Conpty For console issues specifically related to conpty Issue-Bug It either shouldn't be doing this or needs an investigation. labels Aug 29, 2024
@lhecker lhecker force-pushed the dev/lhecker/conpty-message-window branch from 16e54e9 to a3c12bf Compare August 29, 2024 23:18
Comment on lines +206 to +213
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);
}
});
Copy link
Member Author

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));
Copy link
Member Author

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.

Comment on lines +211 to +218
// 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;
}
Copy link
Member Author

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,
Copy link
Member Author

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.

Copy link
Member

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);
Copy link
Member Author

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.

Copy link
Member

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.

Comment on lines +403 to +408
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);
}
Copy link
Member Author

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.

Comment on lines +165 to +166
WI_UpdateFlag(gci.Flags, CONSOLE_HAS_FOCUS, focused);
gci.ProcessHandleList.ModifyConsoleProcessFocus(focused);
Copy link
Member Author

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))
Copy link
Member Author

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.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, this is great

Copy link
Member

@carlos-zamora carlos-zamora left a 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>
Copy link
Member

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);
Copy link
Member

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,
Copy link
Member

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);
Copy link
Member

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?

Copy link
Member

Choose a reason for hiding this comment

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

open question

Copy link
Member Author

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.

Copy link
Member

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))
Copy link
Member

Choose a reason for hiding this comment

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

Oh, this is great

@DHowett
Copy link
Member

DHowett commented Oct 3, 2024

Should this one demote to Draft?

@lhecker
Copy link
Member Author

lhecker commented Oct 4, 2024

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);
Copy link
Member

Choose a reason for hiding this comment

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

open question

@DHowett DHowett merged commit 4386bf0 into main Oct 8, 2024
20 checks passed
@DHowett DHowett deleted the dev/lhecker/conpty-message-window branch October 8, 2024 16:37
DHowett pushed a commit that referenced this pull request Oct 9, 2024
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
Copy link

@Jihad85-D Jihad85-D left a comment

Choose a reason for hiding this comment

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

Copy link

@Jihad85-D Jihad85-D left a comment

Choose a reason for hiding this comment

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

Should do

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Conpty For console issues specifically related to conpty
Projects
Status: Rejected
Development

Successfully merging this pull request may close these issues.

4 participants