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

Fix ShowWindow(GetConsoleWindow()) #13118

merged 16 commits into from May 18, 2022

Conversation

zadjii-msft
Copy link
Member

@zadjii-msft zadjii-msft commented May 17, 2022

A bad merge, that actually revealed a horrible bug.

There was a secret conflict between the code in #12526 and #12515. 69b77ca was a bad merge that hid just how bad the issue was. Fixing the one line nullptr->this in InteractivityFactory resulted in a window that would flash uncontrollably, as it minimized and restored itself in a loop. Great.

This can seemingly be fixed by making sure that the conpty window is initially created with the owner already set, rather than relying on a SetParent call in post. This does pose some complications for the #1256 future we're approaching. However, this is a blocking bug now, and we can figure out the tearout/SetParent thing in post.

  • fixes [1.14] ShowWindow(SW_MINIMIZE) doesn't work anymore #13066.
  • Tested with the script in that issue.
  • Window doesn't flash uncontrollably.
  • gci | ogv still works right
  • I work here.
  • Opening a new tab doesn't spontaneously cause the window to minimize
  • Restoring from minimized doesn't yeet focus to an invisible window
  • Opening a new tab doesn't yeet focus to an invisible window
  • There is a viable way to call GetAncestor s.t. it returns the Terminal's hwnd in Terminal, and the console's in Conhost

The SW_SHOWNOACTIVATE change is also quite load bearing. With just SW_NORMAL, the pseudo window (which is invisible!) gets activated whenever the terminal window is restored from minimized. That's BAD.

There's actually more to this as well.

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.

Without SetParent, however, the pty HWND is no longer a descendant of the Terminal HWND, so that means GA_ROOT can no longer be used to find the owner's hwnd. For even more insanity, without WS_POPUP, none of the values of GetAncestor will actually get the terminal HWND. So, now we also need WS_POPUP on the pty hwnd. To get at the Terminal hwnd, you'll need

GetAncestor(GetConsoleWindow(), GA_ROOTOWNER)
Reparenting

For #1256.

Although not tested in the scope of the Terminal, I'm fairly confident that SetWindowLongPtr will work for this.
image

In that screenshot, I reparented the black terminal to be owned by the blue one. API calls all seemed to confirm that worked, and the hidden pty window wasn't activated as a middle step.

Helper Scripts

Guys TIL how handy the Win32 interop of powershell is. Look at this. This is so much easier.

$NativeFunctions=@"
using System;
using System.Runtime.InteropServices;
public class Native {

    [DllImport("user32.dll")]
    public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

    [DllImport("user32.dll")]
    public static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags);

    [DllImport("user32.dll")]
    public static extern IntPtr GetParent(IntPtr hWnd);

    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);

    [DllImport("user32.dll")]
    public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr GetConsoleWindow();
}
"@
Add-Type -TypeDefinition $NativeFunctions


# [Native]::ShowWindow([Native]::GetConsoleWindow(), 0)

function hwnds {
  $console = [Native]::GetConsoleWindow()

  $GA_PARENT = [Native]::GetAncestor($console, 1) # GA_PARENT
  $GA_ROOT = [Native]::GetAncestor($console, 2) # GA_ROOT
  $GA_ROOTOWNER = [Native]::GetAncestor($console, 3) # GA_ROOTOWNER
  $Parent = [Native]::GetParent($console)
  $GWLP_HWNDPARENT = [Native]::GetWindowLongPtr($console, -8)

  Write-Host ("Console`t`t{0:x}`nGA_PARENT`t{1:x}`nGA_ROOT`t`t{2:x}`nGA_ROOTOWNER`t{3:x}`nParent`t`t{4:x}`nGWLP_HWNDPARENT`t{5:x}" -f $console,$GA_PARENT,$GA_ROOT,$GA_ROOTOWNER,$Parent,$GWLP_HWNDPARENT)

  if ($console -eq $GA_ROOTOWNER) { Write-Host "`e[31mThis is bad`e[m" }
  else {
    if ($console -eq $GA_ROOT) { Write-Host "`e[33mThis is okay`e[m - we told folks that they should use ROOT. They probably will need ROOTOWNER`e[m" }
    else { Write-Host "`e[32mThis is good`e[m" }
  }
}

@ghost ghost added Area-Windowing Window frame, quake mode, tearout Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-0 Bugs that we consider release-blocking/recall-class (P0) Product-Conpty For console issues specifically related to conpty Product-Terminal The new Windows Terminal. Severity-Blocking We won't ship a release like this! No-siree. labels May 17, 2022
@@ -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.

@github-actions

This comment was marked as outdated.

Comment on lines +318 to +319
// * 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
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?

@DHowett DHowett added this to To Cherry Pick in 1.14 Servicing Pipeline via automation May 17, 2022
@DHowett
Copy link
Member

DHowett commented May 17, 2022

@carlos-zamora FYI last blocker for 1.14!

@DHowett
Copy link
Member

DHowett commented May 17, 2022

What horrors happen if a console app uses SW_NORMAL on its own window handle inside Terminal? Is there a way for us to suppress activation in the message loop?

@zadjii-msft zadjii-msft marked this pull request as draft May 17, 2022 19:23
@zadjii-msft
Copy link
Member Author

What horrors happen if a console app uses SW_NORMAL on its own window handle inside Terminal? Is there a way for us to suppress activation in the message loop?

[Native]::ShowWindow([Native]::GetConsoleWindow(), 0) ; sleep 1 ; [Native]::ShowWindow([Native]::GetConsoleWindow(), 1) does seem to work correctly in the terminal, so I suppose this works fine?

@zadjii-msft
Copy link
Member Author

zadjii-msft commented May 17, 2022

🛑 converted to draft for a sec while I make sure that the parenting / ownership stuff didn't regress more than expected.

@github-actions

This comment was marked as resolved.

@zadjii-msft zadjii-msft marked this pull request as ready for review May 18, 2022 20:54
@@ -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

Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

Okay, let's do this. It's dark magic, sure, but so was what we had before. If you worked with @ekoschik on it I have to assume that it could take a bullet and keep going...

@@ -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 think it makes sense, it was just surprising

@DHowett DHowett removed this from To Cherry Pick in 1.14 Servicing Pipeline May 18, 2022
@DHowett DHowett merged commit 77215d9 into main May 18, 2022
@DHowett DHowett deleted the dev/migrie/b/13066-SW_FLASH-2 branch May 18, 2022 22:25
@DHowett
Copy link
Member

DHowett commented May 19, 2022

This doesn't work with DefTerm until the window is minimized and restored once. :|

@DHowett DHowett added this to To Cherry Pick in 1.14 Servicing Pipeline via automation May 19, 2022
@DHowett DHowett moved this from To Cherry Pick to Cherry Picked in 1.14 Servicing Pipeline May 19, 2022
DHowett pushed a commit that referenced this pull request May 19, 2022
A bad merge, that actually revealed a horrible bug.

There was a secret conflict between the code in #12526 and #12515. 69b77ca was a bad merge that hid just how bad the issue was. Fixing the one line `nullptr`->`this` in `InteractivityFactory` resulted in a window that would flash uncontrollably, as it minimized and restored itself in a loop. Great.

This can seemingly be fixed by making sure that the conpty window is initially created with the owner already set, rather than relying on a `SetParent` call in post. This does pose some complications for the #1256 future we're approaching. However, this is a blocking bug _now_, and we can figure out the tearout/`SetParent` thing in post.

* fixes #13066.
* Tested with the script in that issue.
* Window doesn't flash uncontrollably.
* `gci | ogv` still works right
* I work here.
* Opening a new tab doesn't spontaneously cause the window to minimize
* Restoring from minimized doesn't yeet focus to an invisible window
* Opening a new tab doesn't yeet focus to an invisible window
* There _is_ a viable way to call `GetAncestor` s.t. it returns the Terminal's hwnd in Terminal, and the console's in Conhost

The `SW_SHOWNOACTIVATE` change is also quite load bearing. With just `SW_NORMAL`, the pseudo window (which is invisible!) gets activated whenever the terminal window is restored from minimized. That's BAD.

There's actually more to this as well.

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.

Without `SetParent`, however, the pty HWND is no longer a descendant of the Terminal HWND, so that means `GA_ROOT` can no longer be used to find the owner's hwnd. For even more insanity, without `WS_POPUP`, none of the values of `GetAncestor` will actually get the terminal HWND. So, now we also need `WS_POPUP` on the pty hwnd. To get at the Terminal hwnd, you'll need

```c++
GetAncestor(GetConsoleWindow(), GA_ROOTOWNER)
```

(cherry picked from commit 77215d9)
Service-Card-Id: 82170678
Service-Version: 1.14
@DHowett DHowett moved this from Cherry Picked to Validated in 1.14 Servicing Pipeline May 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Windowing Window frame, quake mode, tearout Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-0 Bugs that we consider release-blocking/recall-class (P0) Product-Conpty For console issues specifically related to conpty Product-Terminal The new Windows Terminal. Severity-Blocking We won't ship a release like this! No-siree.
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

[1.14] ShowWindow(SW_MINIMIZE) doesn't work anymore
3 participants