Propagate show/hide window calls against the ConPTY pseudo window to the Terminal (#12515)

Propagate show/hide window calls against the ConPTY pseudo window to the Terminal

## PR Checklist
* [x] Closes #12570 
* [x] I work here
* [x] Manual Tests passed
* [x] Spec Link: →[Doc Link](https://github.com/microsoft/terminal/blob/dev/miniksa/msgs/doc/specs/%2312570%20-%20Show%20Hide%20operations%20on%20GetConsoleWindow%20via%20PTY.md)←

## Detailed Description of the Pull Request / Additional comments
- See the spec. It's pretty much everything I went through deciding on this.

## Validation Steps Performed
- [x] Manual validation against scratch application calling all of the `::ShowWindow` commands against the pseudo console "fake window" and observing the real terminal window state
This commit is contained in:
Michael Niksa 2022-04-27 11:20:14 -07:00 committed by GitHub
parent d891e052f1
commit 6b936d9a74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 940 additions and 8 deletions

View File

@ -154,6 +154,7 @@ serializer
SETVERSION
SHELLEXECUTEINFOW
shobjidl
SHOWHIDE
SHOWMINIMIZED
SHOWTIP
SINGLEUSE

View File

@ -580,6 +580,7 @@ DEFFACE
defing
DEFPUSHBUTTON
defterm
deiconify
DELAYLOAD
deletable
DELETEONRELEASE
@ -670,6 +671,7 @@ dwl
DWLP
dwm
dwmapi
DWMWA
dword
dwrite
dwriteglyphrundescriptionclustermap
@ -1062,6 +1064,7 @@ ico
IComponent
ICONERROR
Iconified
Iconify
ICONINFORMATION
IConsole
ICONSTOP
@ -1200,6 +1203,7 @@ IWin
IWindow
IXaml
IXMP
IXP
ixx
jconcpp
JOBOBJECT
@ -1936,6 +1940,7 @@ qos
QRSTU
qsort
queryable
QUERYOPEN
QUESTIONMARK
quickedit
QUZ
@ -2191,8 +2196,10 @@ shlobj
shlwapi
SHORTPATH
SHOWCURSOR
SHOWDEFAULT
SHOWMAXIMIZED
SHOWMINNOACTIVE
SHOWNA
SHOWNOACTIVATE
SHOWNORMAL
SHOWWINDOW
@ -2424,10 +2431,12 @@ tofrom
tokenhelpers
tokenized
tokenizing
toolbar
toolbars
TOOLINFO
Toolset
tooltip
TOOLWINDOW
TOPDOWNDIB
TOPLEFT
toplevel
@ -2738,6 +2747,7 @@ windowsdeveloper
windowsinternalstring
WINDOWSIZE
windowsx
windowsterminal
WINDOWTEXT
windowtheme
WINDOWTITLE
@ -2880,6 +2890,7 @@ xutr
xvalue
XVIRTUALSCREEN
XWalk
XTWINOPS
Xzn
yact
YAML

View File

@ -0,0 +1,370 @@
---
author: Michael Niksa @miniksa
created on: 2022-02-24
last updated: 2022-02-24
issue id: 12570
---
# Show Hide operations on GetConsoleWindow via PTY
## Abstract
To maintain compatibility with command-line tools, utilities, and tests that desire to
manipulate the final presentation window of their output through retrieving the raw
console window handle and performing `user32` operations against it like [ShowWindow](https://docs.microsoft.com//windows/win32/api/winuser/nf-winuser-showwindow),
we will create a compatibility layer that captures this intent and translates it into
the nearest equivalent in the cross-platform virtual terminal language and implement the
understanding of these sequences in our own Windows Terminal.
## Inspiration
When attempting to enable the Windows Terminal as the default terminal application on Windows
(to supersede the execution of command-line utilities inside the classic console host window),
we discovered that there were a bunch of automated tests, tools, and utilities that relied on
showing and hiding the console window using the `::GetConsoleWindow()` API in conjunction with
`::ShowWindow()`.
When we initially invented the ConPTY, we worked to ensure that we built to the common
denominator that would work cross-platform in all scenarios, avoiding situations that were
dependent on Windows-isms like `user32k` including the full knowledge of how windowing occurs
specific to the Windows platform.
We also understood that on Windows, the [**CreateProcess**](https://docs.microsoft.com/windows/win32/procthread/process-creation-flags) API provides ample flags specifically
for command-line applications to command the need for (or lack thereof) a window on startup
such as `CREATE_NEW_CONSOLE`, `CREATE_NO_WINDOW`, and `DETACHED_PROCESS`. The understanding
was that people who didn't need or want a window, or otherwise needed to manipulate the
console session, would use those flags on process creation to dictate the session. Additionally,
the `::CreateProcess` call will accept information in `STARTUPINFO` or `STARTUPINFOEX` that
can dictate the placement, size, and visibility of a window... including some fields specific
to console sessions. We had accepted those as ways applications would specify their intent.
Those assumptions have proven incorrect. Because it was too easy to just `::CreateProcess` in
the default manner and then get access to the session after-the-fact and manipulate it with
APIs like `::GetConsoleWindow()`, tooling and tests organically grew to make use of this process.
Instead of requesting up front that they didn't need a window or the overhead of a console session,
they would create one anyway by default and then manipulate it afterward to hide it, move it off-
screen, or otherwise push it around. Overall, this is terrible for their performance and overall
reliability because they've obscured their intent by not asking for it upfront and impacted their
performance by having the entire subsystem spin up interactive work when they intend to not use it.
But Windows is the place for compatibility, so we must react and compensate for the existing
non-ideal situation.
We will implement a mechanism to compensate for these that attempts to capture the intent of the
requests from the calling applications against the ConPTY and translates them into the "universal"
Virtual Terminal language to the best of its ability to make the same effects as prior to the
change to the new PTY + Terminal platform.
## Solution Design
Overall, there are three processes involved in this situation:
1. The client command-line application utility, tool, or test that will manipulate the window.
1. The console host (`conhost.exe` or `openconsole.exe`) operating in PTY mode.
1. The terminal (`windowsterminal.exe` when it's Windows Terminal, but could be a third party).
The following diagram shows the components and how they will interact.
```txt
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────────┐
│ │ 1 │ │ │ │
│ Command-Line ├─────────────────► │ Console Host │ │ Windows Terminal │
│ Tool or │ │ as ConPTY │ │ Backend │
│ Utility │ 2 │ │ 6 │ │
│ │ ◄─────────────────┤ ├─────────────────► │ │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ 9 │ │
│ │ │ │ ◄─────────────────┤ │
│ │ │ │ │ │
└─────┬───────────┘ └───────────┬──────┘ └─────────────────┬────┘
│ ▲ │ ▲ │
│ │ │ │ │
│ │ │10 │ │7
│3 5│ │ │8 │
│ │ ▼ │ ▼
│ ┌───┴────┐ ┌──┴────┬───────┬─────────────────────────┐
▼ │ Hidden │ │ │ │ v^x│
┌─────────────────┐ │ Fake │ ├───────┴───────┴─────────────────────────┤
│ │ 4 │ PTY │ │ │
│ ├──────────────────────► │ Window │ │ │
│ user32.dll │ └────────┘ │ Windows Terminal │
│ Window APIs │ │ Displayed Window │
│ │ │ │
│ │ │ │
└─────────────────┘ │ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└─────────────────────────────────────────┘
```
1. The command-line tool calls `::GetConsoleWindow()` on the PTY host
2. The PTY host returns the raw `HWND` to the *Hidden Fake PTY Window* in its control
3. The command-line tool calls `::ShowWindow()` on the `user32.dll` API surface to manipulate that window.
4. `user32.dll` sends a message to the window message queue on the *Fake PTY Window*
5. The PTY host retrieves the message from the queue and translates it to a virtual terminal message
6. The Windows Terminal connection layer receives the virtual terminal message and decodes it into a window operation.
7. The true displayed *Windows Terminal Window* is told to change its status to show or hide.
8. The changed Show/Hide status is returned to the back-end on completion.
9. The Windows Terminal connection layer returns that information to the PTY host so it can remain in-the-know.
10. The PTY updates its *Fake PTY Window* status to match the real one so it continues to receive appropriate messages from `user32`.
This can be conceptually understood in a few phases:
- The client application grabs a handle and attempts to send a command via a back-channel through user32.
- User32 decides what message to send based on the window state of the handle.
- The message is translated by the PTY and propagated to the true visible window.
- The visible window state is returned back to the hidden/fake window to remain in synchronization so the next call to user32 can make the correct decision.
The communication between the PTY and the hosting terminal application occurs with a virtual terminal sequence.
Fortunately, *xterm* had already invented and implemented one for this behavior called **XTWINOPS** which means
we should be able to utilize that one and not worry about inventing our own Microsoft-specific thing. This ensures
that there is some precedence for what we're doing, guarantees a major third party terminal can support the same
sequence, and induces a high probability of other terminals already using it given *xterm* is the defacto standard
for terminal emulation.
Information about **XTWINOPS** can be found at [Xterm control sequences](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html). Search for *XTWINOPS*.
The sequence is **CSI** *Ps*; *Ps*; *Ps* **t**. It starts with the common "control sequence initiator" of `ESC [` (`0x1B 0x5B`).
Then between 1 and 3 numerical parameters are given, separated by semicolons (`0x3B`).
And finally, the sequence is terminated with `t` (`0x74`).
Specifically, the two parameter commands of `1` for *De-iconify window* and `2` for *Iconify window* appear relevant to our interests.
In `user32` parlance, "iconify" traditionally corresponds to minimize/restore state and is a good proxy for overall visibility of the window.
The theory then is to detect when the assorted calls to `::ShowWindow()` against the *Fake PTY Window* are asking for a command that
maps to either "iconify" or "deiconify" and translate them into the corresponding message over the VT channel to the attached terminal.
To detect this, we need to use some heuristics inside the [window procedure](https://docs.microsoft.com/windows/win32/winmsg/window-procedures) for the window owned by the PTY.
Unfortunately, calls to `::ShowWindow()` on research with the team that owns `user32` do not go straight into the window message queue. Instead, they're dispatched straight into `win32k` to be analyzed and then trigger an array of follow on window messages into the queue depending on the `HWND`'s current state. Most specifically, they vary based on the `WS_VISIBLE` state of the `HWND`. (See [Window Styles](https://docs.microsoft.com/windows/win32/winmsg/window-styles) for details on the `WS_VISIBLE` flag.)
I evaluated a handful of messages with the help of the IXP Essentials team to see which ones would telegraph the changes from `::ShowWindow()` into our window procedure:
- [WM_QUERYOPEN](https://docs.microsoft.com/windows/win32/winmsg/wm-queryopen) - This one allows us to accept/reject a minimize/restore call. Not really useful for finding out current state
- [WM_SYSCOMMAND](https://docs.microsoft.com/windows/win32/menurc/wm-syscommand) - This one is what is called when the minimize, maximize/restore, and exit buttons are called in the window toolbar. But apparently it is not generated for these requests coming from outside the window itself through the `user32` APIs.
- [WM_SHOWWINDOW](https://docs.microsoft.com/windows/win32/winmsg/wm-showwindow) - This one provides some insight in certain transitions, specifically around force hiding and showing. When the `lParam` is `0`, we're supposed to know that someone explicitly called `::ShowWindow()` to show or hide with the `wParam` being a `BOOL` where `TRUE` is "show" and `FALSE` is "hide". We can translate that into *de-iconify* and *iconify* respectively.
- [WM_WINDOWPOSCHANGING](https://docs.microsoft.com/windows/win32/winmsg/wm-windowposchanging) - This one I evaluated extensively as it looked to provide us insight into how the window was about to change before it did so and offered us the opportunity to veto some of those changes (for instance, if we wanted to remain invisible while propagating a "show" message). I'll detail more about this one in a sub-heading below.
- [WM_SIZE](https://docs.microsoft.com/windows/win32/winmsg/wm-size) - This one has a `wParam` that specifically sends `SIZE_MINIMIZED` (`1`) and `SIZE_RESTORED` (`0`) that should translate into *iconify* and *de-iconify respectively.
#### WM_WINDOWPOSCHANGING data
In investigating `WM_WINDOWPOSCHANGING`, I built a table of some of the states I observed while receiving messages from an external caller that was using `::ShowWindow()`:
|integer|constant|flags|Should Hide?|minimizing|maximizing|showing|hiding|activating|`0x8000`|`SWP_NOCOPYBITS`|`SWP_SHOWWINDOW`|`SWP_FRAMECHANGED`|`SWP_NOACTIVATE`|`SWP_NOZORDER`|`SWP_NOMOVE`|`SWP_NOSIZE`|
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|0|`SW_HIDE`|?|YES|?|?|?|?|?|?|?|?|?|?|?|?|?|
|1|`SW_NORMAL`|`0x43`|NO|F|F|T|F|T|||X||||X|X|
|2|`SW_SHOWMINIMIZED`|`0x8160`|YES|T|F|T|F|T|X|X|X|X|||||
|3|`SW_SHOWMAXIMIZED`|`0x8160`|NO|F|T|T|F|T|X|X|X|X|||||
|4|`SW_SHOWNOACTIVATE`|`0x8070`|NO|F|F|T|F|F|X||X|X|X||||
|5|`SW_SHOW`|`0x43`|NO|F|F|T|F|T|||X||||X|X|
|6|`SW_MINIMIZE`|`0x8170`|YES|T|F|T|F|F|X|X|X|X|X||||
|7|`SW_SHOWMINNOACTIVE`|`0x57`|YES|T|F|T|F|F|||X||X|X|X|X|
|8|`SW_SHOWNA`|`0x53`|NO|F|F|T|F|F|||X||X||X|X|
|9|`SW_RESTORE`|`0x8160`|NO|F|F|T|F|T|||X|X|||||
|10|`SW_SHOWDEFAULT`|`0x43`|NO|F|F|T|F|T|||X||||X|X|
|11|`SW_FORCEMINIMIZE`|?|YES|?|?|?|?|?|?|?|?|?|?|?|?|?|
The headings are as follows:
- integer - The value of the Show Window constant `SW_*` (see [ShowWindow](https://docs.microsoft.com/windows/win32/api/winuser/nf-winuser-showwindow))
- constant - The name of the Show Window constant
- flags - The `lParam` field is a pointer to a [**WINDOWPOS**](https://docs.microsoft.com/windows/win32/api/winuser/ns-winuser-windowpos) structure during this message. This the `UINT flags` field of that structure.
- Should Hide? - Whether or not I believe that the window should hide if this constant is seen. (Conversely, should show on the opposite.)
- minimizing - This is the `BOOL` response from a call to [**IsIconic()**](https://docs.microsoft.com/windows/win32/api/winuser/nf-winuser-isiconic) during this message.
- maximizing - This is the `BOOL` response from a call to [**IsZoomed()**](https://docs.microsoft.com/windows/win32/api/winuser/nf-winuser-iszoomed) during this message.
- showing - This is whether `SWP_SHOWWINDOW` is set on the `WINDOWPOS.flags` field during this message.
- hiding - This is whether `SWP_HIDEWINDOW` is set on the `WINDOWPOS.flags` field during this message.
- activating - This is the inverse of whether `SWP_NOACTIVATE` is set on the `WINDOWPOS.flags` field during this message.
- Remaining headings are `flags` values expanded to `X` is set and blank is unset. See [**SetWindowPos()**](https://docs.microsoft.com/windows/win32/api/winuser/nf-winuser-setwindowpos) for the definitions of all the flags.
From this data collection, I noticed a few things:
- The data in this table was unstable. The fields varied depending on the order in which I called the various constants against `ShowWindow()`. This is just one particular capture.
- Some of the states, I wouldn't see any message data at all (`SW_HIDE` and `SW_FORCEMINIMIZE`).
- There didn't seem to be a definitive way to use this data to reliably decide when to show or hide the window. I didn't have a reliable way of pulling this together with my *Should Hide?* column.
On further investigation, it became apparent that the values received were sometimes not coming through or varying because the `WS_VISIBLE` state of the `HWND` affected how `win32k` decided to dispatch messages and what values they contained. This is where I determined that steps #8-10 in the diagram above were going to be necessary: to report the state of the real window back to the *fake window* so it could report status to `user32` and `win32k` and receive state-appropriate messages.
For reporting back #8-10, I initially was going to use the `XTWINOPS` call with parameter `11`. The PTY could ask the attached terminal for its state and expect to hear back an answer of either `1` or `2` in the same format message depending on the state. However, on further consideration, I realized that the real window could change at a moments notice without prompting from the PTY, so I instead wrote the PTY to always listen for this and had the Windows Terminal send this back down unprompted.
#### Refined WM_SHOWWINDOW and WM_SIZE data
Upon setting up the synchronization for #8-10, I then tried again to build the table using just the two window messages that were giving me reliable data: `WM_SHOWWINDOW` and `WM_SIZE`:
|integer|constant|Should Hide?|`WM_SHOWWINDOW` OR `WM_SIZE` reported hide?|
|---|---|---|---|
|0|`SW_HIDE`|YES|YES|
|1|`SW_NORMAL`|NO|NO|
|2|`SW_SHOWMINIMIZED`|YES|YES|
|3|`SW_SHOWMAXIMIZED`|NO|NO|
|4|`SW_SHOWNOACTIVATE`|NO|NO|
|5|`SW_SHOW`|NO|NO|
|6|`SW_MINIMIZE`|YES|YES|
|7|`SW_SHOWMINNOACTIVE`|YES|YES|
|8|`SW_SHOWNA`|NO|NO|
|9|`SW_RESTORE`|NO|NO|
|10|`SW_SHOWDEFAULT`|NO|NO|
|11|`SW_FORCEMINIMIZE`|YES|YES|
Since this now matched up perfectly with what I was suspecting should happen *and* it was easier to implement than picking apart the `WM_WINDOWPOSCHANGING` message, it is what I believe the design should be.
Finally, with the *fake window* changing state to and from `WS_VISIBLE`... it was appearing on the screen and showing up in the taskbar and alt-tab. To resolve this, I utilized [**DWMWA_CLOAK**](https://docs.microsoft.com//windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute) which makes the window completely invisible even when in a normally `WS_VISIBLE` state. I then added the [**WS_EX_TOOLWINDOW**](https://docs.microsoft.com/windows/win32/winmsg/extended-window-styles) extended window style to hide it from alt-tab and taskbar.
With this setup, the PTY now has a completely invisible window with a synchronized `WS_VISIBLE` state with the real terminal window, a bidirectional signal channel to adjust the state between the terminal and PTY, and the ability to catch `user32` calls being made against the *fake window* that the PTY stands up for the client command-line application.
## UI/UX Design
The visible change in behavior is that a call to `::ShowWindow()` against the `::GetConsoleWindow()`
handle that is returned by the ConPTY will be propagated to the attached Terminal. As such, a
user will see the entire window be shown or hidden if one of the underlying attached
command-line applications requests a show or hide.
At the initial moment, the fact that the Terminal contains tabbed and/or paned sessions and
therefore multiple command-line clients on "different sessions" are attached to the same window
is partially ignored. If one attached client calls "show", the entire window will be shown with
all tabs. If another calls "hide", the entire window will be hidden including the other tab
that just requested a show. In the opposite direction, when the window is shown, all attached
PTYs for all tabs/panes will be alerted that they're now shown at once.
## Capabilities
### Accessibility
Users of assistive devices will have the same experience that they did with the legacy Windows
Console after this change. If a command-line application decides to show or hide the window
through the API without their consent, they will receive notification of the showing/hiding
window through our UIA framework.
Prior to this change, the window would have always remained visible and there would be no
action.
Overall, the experience will be consistent between what is happening on-screen and what is
presented through the UIA framework to assistive tools.
For third party terminals, it will be up to them to decide what their reaction and experience is.
### Security
We will maintain the security and integrity of the Terminal application chosen for presentation
by not revealing its true window handle information to the client process through the existing
`::GetConsoleWindow()` API. Through our design for default terminal applications, the final
presentation terminal could be Windows Terminal or it could be any third-party terminal that
meets the same specifications for communication. Giving raw access to its `HWND` to a client
application could disrupt its security.
By maintaining a level of separation with this feature by generating a "fake window" in the
ConPTY layer and only forwarding events, the attached terminal (whether ours or a 3rd party)
maintains the final level of control on whether or not it processes the message. This is
improved security over the legacy console host where the tool had full backdoor style access
to all `user32` based window APIs.
### Reliability
This test doesn't improve overall reliability in the system because utilities that are relying
on the behavior that this compatibility shim will restore are already introducing additional
layers of complexity and additional processes into their operation than were strictly necessary
simply by not stating their desires upfront at creation time.
In some capacity, you could argue it increases reliability of the existing tests that were
using this complex behavior in that they didn't work before and they will work now, but
the entire process is fragile. We're just restoring the fragile process instead of having
it not work at all.
### Compatibility
This change restores compatibility with existing applications that were relying on the behavior
we had excluded from our initial designs.
### Performance, Power, and Efficiency
The performance of tooling that is leveraging this process to create a console and then hide
or manipulate the session after the fact will be significantly worse when we enable the
default Windows Terminal than it was with the old Windows Console. This is because the
Terminal is significantly heavier weight (with its modern technologies like WinUI) and
will take more time to start and more committed memory. Additionally, more processes
will be in use because there will be the `conhost.exe` doing the ConPTY translation
and then the `windowsterminal.exe` doing the presentation.
However, this particular feature doesn't do anything to make that better or worse.
The appropriate solution for any tooling, test, or scenario that has a need for
performance and efficiency is to use the flags to `::CreateProcess` in the first place
to specify that they did not need a console window session at all, or to direct its
placement and visibility as a part of the creation call. We are working with
Microsoft's test automation tooling (TAEF) as well as the Windows performance
fundamentals (FUN) team to ensure that the test automation supports creating sessions
without a console window and that our internal performance test suite uses those
specifications on creation so we have accurate performance testing of the operating
system.
## Potential Issues
### Multiple clients sharing the same window host
With the initial design, multiple clients sharing the same window host will effectively
share the window state. Two different tabs or panes with two different client applications
could fight over the show/hide state of the window. In the initial revision, this is
ignored because this feature is being driven by a narrow failure scenario in the test gates.
In the reported scenario, a singular application is default-launched into a singular tab
in a terminal window and then the application expects to be able to hide it after the creation.
In the future, we may have to implement a conflict resolution or a graphical variance to
compensate for multiple tabs.
### Other verbs against the console window handle
This scenario initially focuses on just the `::ShowWindow()` call against the window handle
from `::GetConsoleWindow()`. Other functions from `user32` against the `HWND` will not
necessarily be captured and forwarded to the attached terminal application. And even more
specifically, we're focusing only on the Show and Hide state. Other state modifications that
are subtle related to z-ordering, activation, maximizing, snapping, and so on are not considered.
## Future considerations
### Multiple clients
If the multiple clients problem becomes more widespread, we may need to change the graphical
behavior of the Windows Terminal window to only hide certain tabs or panes when a command
comes in instead of hiding the entire window (unless of course there is only one tab/pane).
We may also need to adjust that once consensus is reached among tabs/panes that it can then
and only then propagate up to the entire window.
We will decide on this after we receive feedback that it is a necessary scenario. Otherwise,
we will hold for now.
### Other verbs
If it turns out that we discover tests/scenarios that need maximizing, activation, or other
properties of the `::ShowWindow()` call to be propagated to maintain compatibility, we will
be able to carry those through on the same channel and command. Most of them have an existing
equivalent in `XTWINOPS`. Those that do not, we would want to probably avoid as they will not
be implemented in any other terminal. We would extend the protocol as an absolute last resort
and only after receiving feedback from the greater worldwide terminal community.
### Z-ordering
The channel we're establishing here to communicate information about the window and its
placement may be useful for the z-ordering issues we have in #2988. In those scenarios,
a console client application is attempting to launch and position a window on top of the
terminal, wherever it is. Further synchronizing the state of the new fake-window in the
ConPTY with the real window on the terminal side may enable those tools to function as
they expect.
This is another circumstance we didn't expect: having command-line applications create windows
with a need for complex layout and ordering. These sorts of behaviors cannot be translated
to a universal language and will not be available off the singular machine, so encouraged
alternative methods like command-line based UI. However, for single-box scenarios, this
behavior is engrained in some Windows tooling due to its ease of use.
## Resources
- [Default Terminal spec](https://github.com/microsoft/terminal/pull/7414)
- [Z-ordering issue](https://github.com/microsoft/terminal/issues/2988)
- See all the embedded links in this document to Windows API resources

View File

@ -1123,6 +1123,22 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - Used to tell the PTY connection that the window visibility has changed.
// The underlying PTY might need to expose window visibility status to the
// client application for the `::GetConsoleWindow()` API.
// Arguments:
// - showOrHide - True is show; false is hide.
// Return Value:
// - <none>
void AppLogic::WindowVisibilityChanged(const bool showOrHide)
{
if (_root)
{
_root->WindowVisibilityChanged(showOrHide);
}
}
// Method Description:
// - Implements the F7 handler (per GH#638)
// - Implements the Alt handler (per GH#6421)

View File

@ -114,6 +114,7 @@ namespace winrt::TerminalApp::implementation
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
void CloseWindow(Microsoft::Terminal::Settings::Model::LaunchPosition position);
void WindowVisibilityChanged(const bool showOrHide);
winrt::TerminalApp::TaskbarState TaskbarState();
@ -207,6 +208,7 @@ namespace winrt::TerminalApp::implementation
FORWARDED_TYPED_EVENT(CloseRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, CloseRequested);
FORWARDED_TYPED_EVENT(OpenSystemMenu, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, OpenSystemMenu);
FORWARDED_TYPED_EVENT(QuitRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, QuitRequested);
FORWARDED_TYPED_EVENT(ShowWindowChanged, Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs, _root, ShowWindowChanged);
#ifdef UNIT_TESTING
friend class TerminalAppLocalTests::CommandlineTest;

View File

@ -91,6 +91,7 @@ namespace TerminalApp
Single CalcSnappedDimension(Boolean widthOrHeight, Single dimension);
void TitlebarClicked();
void CloseWindow(Microsoft.Terminal.Settings.Model.LaunchPosition position);
void WindowVisibilityChanged(Boolean showOrHide);
TaskbarState TaskbarState{ get; };
@ -132,5 +133,6 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
event Windows.Foundation.TypedEventHandler<Object, TerminalApp.SystemMenuChangeArgs> SystemMenuChangeRequested;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.ShowWindowArgs> ShowWindowChanged;
}
}

View File

@ -1,3 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
@ -1428,6 +1429,8 @@ namespace winrt::TerminalApp::implementation
term.SetTaskbarProgress({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler });
term.ConnectionStateChanged({ get_weak(), &TerminalPage::_ConnectionStateChangedHandler });
term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler });
}
// Method Description:
@ -2340,6 +2343,17 @@ namespace winrt::TerminalApp::implementation
_SetTaskbarProgressHandlers(*this, nullptr);
}
// Method Description:
// - Send an event (which will be caught by AppHost) to change the show window state of the entire hosting window
// Arguments:
// - sender (not used)
// - args: the arguments specifying how to set the display status to ShowWindow for our window handle
winrt::fire_and_forget TerminalPage::_ShowWindowChangedHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::ShowWindowArgs args)
{
co_await resume_foreground(Dispatcher());
_ShowWindowChangedHandlers(*this, args);
}
// Method Description:
// - Paste text from the Windows Clipboard to the focused terminal
void TerminalPage::_PasteText()
@ -2416,6 +2430,9 @@ namespace winrt::TerminalApp::implementation
// TermControl will copy the settings out of the settings passed to it.
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 (_hostingHwnd.has_value())
{
term.OwningHwnd(reinterpret_cast<uint64_t>(*_hostingHwnd));
@ -2848,6 +2865,33 @@ namespace winrt::TerminalApp::implementation
_DismissTabContextMenus();
}
// Method Description:
// - Notifies all attached console controls that the visibility of the
// hosting window has changed. The underlying PTYs may need to know this
// for the proper response to `::GetConsoleWindow()` from a Win32 console app.
// Arguments:
// - showOrHide: Show is true; hide is false.
// Return Value:
// - <none>
void TerminalPage::WindowVisibilityChanged(const bool showOrHide)
{
_visible = showOrHide;
for (const auto& tab : _tabs)
{
if (auto terminalTab{ _GetTerminalTabImpl(tab) })
{
// Manually enumerate the panes in each tab; this will let us recycle TerminalSettings
// objects but only have to iterate one time.
terminalTab->GetRootPane()->WalkTree([&](auto&& pane) {
if (auto control = pane->GetTerminalControl())
{
control.WindowVisibilityChanged(showOrHide);
}
});
}
}
}
// Method Description:
// - Called when the user tries to do a search using keybindings.
// This will tell the current focused terminal control to create

View File

@ -75,6 +75,7 @@ namespace winrt::TerminalApp::implementation
hstring Title();
void TitlebarClicked();
void WindowVisibilityChanged(const bool showOrHide);
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
@ -154,6 +155,7 @@ namespace winrt::TerminalApp::implementation
TYPED_EVENT(CloseRequested, IInspectable, IInspectable);
TYPED_EVENT(OpenSystemMenu, IInspectable, IInspectable);
TYPED_EVENT(QuitRequested, IInspectable, IInspectable);
TYPED_EVENT(ShowWindowChanged, IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs)
private:
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
@ -194,6 +196,7 @@ namespace winrt::TerminalApp::implementation
std::optional<int> _rearrangeFrom{};
std::optional<int> _rearrangeTo{};
bool _removing{ false };
bool _visible{ true };
std::vector<std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs>> _previouslyClosedPanesAndTabs{};
@ -439,6 +442,8 @@ namespace winrt::TerminalApp::implementation
static bool _IsMessageDismissed(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message);
static void _DismissMessage(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message);
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
#define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action);

View File

@ -59,5 +59,6 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.ShowWindowArgs> ShowWindowChanged;
}
}

View File

@ -311,6 +311,8 @@ 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)));
@ -489,6 +491,19 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
}
void ConptyConnection::ShowHide(const bool show)
{
// If we haven't connected yet, then stash for when we do connect.
if (_isConnected())
{
THROW_IF_FAILED(ConptyShowHidePseudoConsole(_hPC.get(), show));
}
else
{
_initialVisibility = show;
}
}
void ConptyConnection::ReparentWindow(const uint64_t newParent)
{
// If we haven't started connecting at all, stash this HWND to use once we have started.

View File

@ -35,6 +35,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void Resize(uint32_t rows, uint32_t columns);
void Close() noexcept;
void ClearBuffer();
void ShowHide(const bool show);
void ReparentWindow(const uint64_t newParent);
winrt::guid Guid() const noexcept;
@ -70,6 +73,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
hstring _commandline{};
hstring _startingDirectory{};
hstring _startingTitle{};
bool _initialVisibility{ false };
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).

View File

@ -12,7 +12,11 @@ namespace Microsoft.Terminal.TerminalConnection
ConptyConnection();
Guid Guid { get; };
String Commandline { get; };
void ClearBuffer();
void ShowHide(Boolean show);
void ReparentWindow(UInt64 newParent);
static event NewConnectionHandler NewConnection;

View File

@ -108,6 +108,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto pfnTerminalTaskbarProgressChanged = std::bind(&ControlCore::_terminalTaskbarProgressChanged, this);
_terminal->TaskbarProgressChangedCallback(pfnTerminalTaskbarProgressChanged);
auto pfnShowWindowChanged = std::bind(&ControlCore::_terminalShowWindowChanged, this, std::placeholders::_1);
_terminal->SetShowWindowCallback(pfnShowWindowChanged);
// MSFT 33353327: Initialize the renderer in the ctor instead of Initialize().
// We need the renderer to be ready to accept new engines before the SwapChainPanel is ready to go.
// If we wait, a screen reader may try to get the AutomationPeer (aka the UIA Engine), and we won't be able to attach
@ -1214,6 +1217,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_TaskbarProgressChangedHandlers(*this, nullptr);
}
void ControlCore::_terminalShowWindowChanged(bool showOrHide)
{
auto showWindow = winrt::make_self<implementation::ShowWindowArgs>(showOrHide);
_ShowWindowChangedHandlers(*this, *showWindow);
}
bool ControlCore::HasSelection() const
{
return _terminal->IsSelectionActive();
@ -1696,6 +1705,25 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
// Method Description:
// - Notifies the attached PTY that the window has changed visibility state
// - NOTE: Most VT commands are generated in `TerminalDispatch` and sent to this
// class as the target for transmission. But since this message isn't
// coming in via VT parsing (and rather from a window state transition)
// we generate and send it here.
// Arguments:
// - visible: True for visible; false for not visible.
// Return Value:
// - <none>
void ControlCore::WindowVisibilityChanged(const bool showOrHide)
{
// show is true, hide is false
if (auto conpty{ _connection.try_as<TerminalConnection::ConptyConnection>() })
{
conpty.ShowHide(showOrHide);
}
}
// Method Description:
// - When the control gains focus, it needs to tell ConPTY about this.
// Usually, these sequences are reserved for applications that

View File

@ -172,6 +172,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void AdjustOpacity(const double opacity, const bool relative);
void WindowVisibilityChanged(const bool showOrHide);
// TODO:GH#1256 - When a tab can be torn out or otherwise reparented to
// another window, this value will need a custom setter, so that we can
// also update the connection.
@ -201,6 +203,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(TransparencyChanged, IInspectable, Control::TransparencyChangedEventArgs);
TYPED_EVENT(ReceivedOutput, IInspectable, IInspectable);
TYPED_EVENT(FoundMatch, IInspectable, Control::FoundResultsArgs);
TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs);
// clang-format on
private:
@ -268,6 +271,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const int bufferSize);
void _terminalCursorPositionChanged();
void _terminalTaskbarProgressChanged();
void _terminalShowWindowChanged(bool showOrHide);
#pragma endregion
#pragma region RendererCallbacks

View File

@ -100,6 +100,7 @@ namespace Microsoft.Terminal.Control
String ReadEntireBuffer();
void AdjustOpacity(Double Opacity, Boolean relative);
void WindowVisibilityChanged(Boolean showOrHide);
event FontSizeChangedEventArgs FontSizeChanged;
@ -120,6 +121,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, TransparencyChangedEventArgs> TransparencyChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ReceivedOutput;
event Windows.Foundation.TypedEventHandler<Object, FoundResultsArgs> FoundMatch;
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
};
}

View File

@ -12,3 +12,4 @@
#include "RendererWarningArgs.g.cpp"
#include "TransparencyChangedEventArgs.g.cpp"
#include "FoundResultsArgs.g.cpp"
#include "ShowWindowArgs.g.cpp"

View File

@ -12,6 +12,7 @@
#include "RendererWarningArgs.g.h"
#include "TransparencyChangedEventArgs.g.h"
#include "FoundResultsArgs.g.h"
#include "ShowWindowArgs.g.h"
namespace winrt::Microsoft::Terminal::Control::implementation
{
@ -145,4 +146,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_PROPERTY(bool, FoundMatch);
};
struct ShowWindowArgs : public ShowWindowArgsT<ShowWindowArgs>
{
public:
ShowWindowArgs(const bool showOrHide) :
_ShowOrHide(showOrHide)
{
}
WINRT_PROPERTY(bool, ShowOrHide);
};
}

View File

@ -74,4 +74,9 @@ namespace Microsoft.Terminal.Control
{
Boolean FoundMatch { get; };
}
runtimeclass ShowWindowArgs
{
Boolean ShowOrHide { get; };
}
}

View File

@ -2078,6 +2078,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return cells * fontDimension + nonTerminalArea;
}
// Method Description:
// - Forwards window visibility changing event down into the control core
// to eventually let the hosting PTY know whether the window is visible or
// not (which can be relevant to `::GetConsoleWindow()` calls.)
// Arguments:
// - showOrHide: Show is true; hide is false.
// Return Value:
// - <none>
void TermControl::WindowVisibilityChanged(const bool showOrHide)
{
_core.WindowVisibilityChanged(showOrHide);
}
// Method Description:
// - Create XAML Thickness object based on padding props provided.
// Used for controlling the TermControl XAML Grid container's Padding prop.

View File

@ -42,6 +42,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Windows::Foundation::Size MinimumSize();
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension);
void WindowVisibilityChanged(const bool showOrHide);
#pragma region ICoreState
const uint64_t TaskbarState() const noexcept;
const uint64_t TaskbarProgress() const noexcept;
@ -124,6 +126,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
PROJECTED_FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable, _core, TabColorChanged);
PROJECTED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable, _core, TaskbarProgressChanged);
PROJECTED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable, _core, ConnectionStateChanged);
PROJECTED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs, _core, ShowWindowChanged);
PROJECTED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs, _interactivity, PasteFromClipboard);

View File

@ -46,6 +46,8 @@ namespace Microsoft.Terminal.Control
// We expose this and ConnectionState here so that it might eventually be data bound.
event Windows.Foundation.TypedEventHandler<Object, IInspectable> ConnectionStateChanged;
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
void PasteTextFromClipboard();
void ClearBuffer(ClearBufferType clearType);
@ -54,6 +56,8 @@ namespace Microsoft.Terminal.Control
Windows.Foundation.Size MinimumSize { get; };
Single SnapDimensionToGrid(Boolean widthOrHeight, Single dimension);
void WindowVisibilityChanged(Boolean showOrHide);
void ScrollViewport(Int32 viewTop);
void CreateSearchBoxControl();

View File

@ -69,6 +69,8 @@ namespace Microsoft::Terminal::Core
virtual void PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::VTParameters options) = 0;
virtual void PopGraphicsRendition() = 0;
virtual void ShowWindow(bool showOrHide) = 0;
virtual void UseAlternateScreenBuffer() = 0;
virtual void UseMainScreenBuffer() = 0;

View File

@ -1290,6 +1290,15 @@ void Microsoft::Terminal::Core::Terminal::TaskbarProgressChangedCallback(std::fu
_pfnTaskbarProgressChanged.swap(pfn);
}
// Method Description:
// - Propagates an incoming set window visibility call from the PTY up into our window control layers
// Arguments:
// - pfn: a function callback that accepts true as "make window visible" and false as "hide window"
void Terminal::SetShowWindowCallback(std::function<void(bool)> pfn) noexcept
{
_pfnShowWindowChanged.swap(pfn);
}
// Method Description:
// - Sets the cursor to be currently on. On/Off is tracked independently of
// cursor visibility (hidden/visible). On/off is controlled by the cursor

View File

@ -138,8 +138,11 @@ public:
void PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::VTParameters options) override;
void PopGraphicsRendition() override;
void ShowWindow(bool showOrHide) override;
void UseAlternateScreenBuffer() override;
void UseMainScreenBuffer() override;
#pragma endregion
#pragma region ITerminalInput
@ -212,6 +215,7 @@ public:
void SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept;
void SetBackgroundCallback(std::function<void(const til::color)> pfn) noexcept;
void TaskbarProgressChangedCallback(std::function<void()> pfn) noexcept;
void SetShowWindowCallback(std::function<void(bool)> pfn) noexcept;
void SetCursorOn(const bool isOn);
bool IsCursorBlinkingAllowed() const noexcept;
@ -279,6 +283,7 @@ private:
std::function<void()> _pfnCursorPositionChanged;
std::function<void(const std::optional<til::color>)> _pfnTabColorChanged;
std::function<void()> _pfnTaskbarProgressChanged;
std::function<void(bool)> _pfnShowWindowChanged;
RenderSettings _renderSettings;
std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;

View File

@ -684,3 +684,17 @@ void Terminal::UseMainScreenBuffer()
}
CATCH_LOG();
}
// Method Description:
// - Reacts to a client asking us to show or hide the window.
// Arguments:
// - showOrHide - True for show. False for hide.
// Return Value:
// - <none>
void Terminal::ShowWindow(bool showOrHide)
{
if (_pfnShowWindowChanged)
{
_pfnShowWindowChanged(showOrHide);
}
}

View File

@ -794,6 +794,23 @@ bool TerminalDispatch::HardReset()
return true;
}
bool TerminalDispatch::WindowManipulation(const DispatchTypes::WindowManipulationType function,
const VTParameter /*parameter1*/,
const VTParameter /*parameter2*/)
{
switch (function)
{
case DispatchTypes::WindowManipulationType::DeIconifyWindow:
_terminalApi.ShowWindow(true);
return true;
case DispatchTypes::WindowManipulationType::IconifyWindow:
_terminalApi.ShowWindow(false);
return true;
default:
return false;
}
}
// Routine Description:
// - DECSC - Saves the current "cursor state" into a memory buffer. This
// includes the cursor position, origin mode, graphic rendition, and

View File

@ -69,6 +69,11 @@ public:
bool SoftReset() override; // DECSTR
bool HardReset() override; // RIS
// DTTERM_WindowManipulation
bool WindowManipulation(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::WindowManipulationType /*function*/,
const ::Microsoft::Console::VirtualTerminal::VTParameter /*parameter1*/,
const ::Microsoft::Console::VirtualTerminal::VTParameter /*parameter2*/) override;
bool EnableVT200MouseMode(const bool enabled) override; // ?1000
bool EnableUTF8ExtendedMouseMode(const bool enabled) override; // ?1005
bool EnableSGRExtendedMouseMode(const bool enabled) override; // ?1006

View File

@ -373,6 +373,8 @@ void AppHost::Initialize()
// application layer.
_window->DragRegionClicked([this]() { _logic.TitlebarClicked(); });
_window->WindowVisibilityChanged([this](bool showOrHide) { _logic.WindowVisibilityChanged(showOrHide); });
_revokers.RequestedThemeChanged = _logic.RequestedThemeChanged(winrt::auto_revoke, { this, &AppHost::_UpdateTheme });
_revokers.FullscreenChanged = _logic.FullscreenChanged(winrt::auto_revoke, { this, &AppHost::_FullscreenChanged });
_revokers.FocusModeChanged = _logic.FocusModeChanged(winrt::auto_revoke, { this, &AppHost::_FocusModeChanged });
@ -400,6 +402,7 @@ void AppHost::Initialize()
_revokers.SummonWindowRequested = _logic.SummonWindowRequested(winrt::auto_revoke, { this, &AppHost::_SummonWindowRequested });
_revokers.OpenSystemMenu = _logic.OpenSystemMenu(winrt::auto_revoke, { this, &AppHost::_OpenSystemMenu });
_revokers.QuitRequested = _logic.QuitRequested(winrt::auto_revoke, { this, &AppHost::_RequestQuitAll });
_revokers.ShowWindowChanged = _logic.ShowWindowChanged(winrt::auto_revoke, { this, &AppHost::_ShowWindowChanged });
// BODGY
// On certain builds of Windows, when Terminal is set as the default
@ -1391,6 +1394,12 @@ void AppHost::_QuitAllRequested(const winrt::Windows::Foundation::IInspectable&,
args.BeforeQuitAllAction(_SaveWindowLayouts());
}
void AppHost::_ShowWindowChanged(const winrt::Windows::Foundation::IInspectable&,
const winrt::Microsoft::Terminal::Control::ShowWindowArgs& args)
{
_window->ShowWindowChanged(args.ShowOrHide());
}
void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable&)
{

View File

@ -112,6 +112,9 @@ private:
void _QuitAllRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs& args);
void _ShowWindowChanged(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Control::ShowWindowArgs& args);
void _CreateNotificationIcon();
void _DestroyNotificationIcon();
void _ShowNotificationIconRequested(const winrt::Windows::Foundation::IInspectable& sender,
@ -157,6 +160,7 @@ private:
winrt::TerminalApp::AppLogic::SummonWindowRequested_revoker SummonWindowRequested;
winrt::TerminalApp::AppLogic::OpenSystemMenu_revoker OpenSystemMenu;
winrt::TerminalApp::AppLogic::QuitRequested_revoker QuitRequested;
winrt::TerminalApp::AppLogic::ShowWindowChanged_revoker ShowWindowChanged;
winrt::Microsoft::Terminal::Remoting::WindowManager::ShowNotificationIconRequested_revoker ShowNotificationIconRequested;
winrt::Microsoft::Terminal::Remoting::WindowManager::HideNotificationIconRequested_revoker HideNotificationIconRequested;
winrt::Microsoft::Terminal::Remoting::WindowManager::QuitAllRequested_revoker QuitAllRequested;

View File

@ -476,14 +476,19 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
{
if (wparam == SIZE_RESTORED || wparam == SIZE_MAXIMIZED)
{
_WindowVisibilityChangedHandlers(true);
_MaximizeChangedHandlers(wparam == SIZE_MAXIMIZED);
}
if (wparam == SIZE_MINIMIZED && _isQuakeWindow)
if (wparam == SIZE_MINIMIZED)
{
_WindowVisibilityChangedHandlers(false);
if (_isQuakeWindow)
{
ShowWindow(GetHandle(), SW_HIDE);
return 0;
}
}
break;
}
case WM_MOVING:
@ -820,6 +825,22 @@ void IslandWindow::SetAlwaysOnTop(const bool alwaysOnTop)
}
}
// Method Description:
// - Posts a message to the window message queue that the window visibility has changed
// and should then be minimized or restored.
// Arguments:
// - showOrHide: True for show; false for hide.
// Return Value:
// - <none>
void IslandWindow::ShowWindowChanged(const bool showOrHide)
{
const auto hwnd = GetHandle();
if (hwnd)
{
PostMessage(hwnd, WM_SYSCOMMAND, showOrHide ? SC_RESTORE : SC_MINIMIZE, 0);
}
}
// Method Description
// - Flash the taskbar icon, indicating to the user that something needs their attention
void IslandWindow::FlashTaskbar()

View File

@ -43,6 +43,7 @@ public:
void FocusModeChanged(const bool focusMode);
void FullscreenChanged(const bool fullscreen);
void SetAlwaysOnTop(const bool alwaysOnTop);
void ShowWindowChanged(const bool showOrHide);
void FlashTaskbar();
void SetTaskbarProgress(const size_t state, const size_t progress);
@ -77,6 +78,7 @@ public:
WINRT_CALLBACK(MaximizeChanged, winrt::delegate<void(bool)>);
WINRT_CALLBACK(WindowMoved, winrt::delegate<void()>);
WINRT_CALLBACK(WindowVisibilityChanged, winrt::delegate<void(bool)>);
protected:
void ForceResize()

View File

@ -71,6 +71,10 @@ void PtySignalInputThread::ConnectConsole() noexcept
{
_DoResizeWindow(*_earlyResize);
}
if (_initialShowHide)
{
_DoShowHide(_initialShowHide->show);
}
// If we were given a owner HWND, then manually start the pseudo window now.
if (_earlyReparent)
@ -91,6 +95,33 @@ void PtySignalInputThread::ConnectConsole() noexcept
{
switch (signalId)
{
case PtySignal::ShowHideWindow:
{
ShowHideData msg = { 0 };
_GetData(&msg, sizeof(msg));
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
// If the client app hasn't yet connected, stash our initial
// visibility for when we do. We default to not being visible - if a
// terminal wants the ConPTY windows to start "visible", then they
// should send a ShowHidePseudoConsole(..., true) to tell us to
// initially be visible.
//
// Notably, if they don't, then a ShowWindow(SW_HIDE) on the ConPTY
// HWND will initially do _nothing_, because the OS will think that
// the window is already hidden.
if (!_consoleConnected)
{
_initialShowHide = msg;
}
else
{
_DoShowHide(msg.show);
}
break;
}
case PtySignal::ClearBuffer:
{
LockConsole();
@ -181,6 +212,11 @@ void PtySignalInputThread::_DoClearBuffer()
THROW_IF_FAILED(gci.GetActiveOutputBuffer().ClearBuffer());
}
void PtySignalInputThread::_DoShowHide(const bool show)
{
_pConApi->ShowWindow(show);
}
// Method Description:
// - Update the owner of the pseudo-window we're using for the conpty HWND. This
// allows to mark the pseudoconsole windows as "owner" by the terminal HWND

View File

@ -40,6 +40,7 @@ namespace Microsoft::Console
private:
enum class PtySignal : unsigned short
{
ShowHideWindow = 1,
ClearBuffer = 2,
SetParent = 3,
ResizeWindow = 8
@ -50,6 +51,12 @@ namespace Microsoft::Console
unsigned short sx;
unsigned short sy;
};
struct ShowHideData
{
unsigned short show; // used as a bool, but passed as a ushort
};
struct SetParentData
{
uint64_t handle;
@ -60,6 +67,7 @@ namespace Microsoft::Console
void _DoResizeWindow(const ResizeWindowData& data);
void _DoSetWindowParent(const SetParentData& data);
void _DoClearBuffer();
void _DoShowHide(const bool show);
void _Shutdown();
wil::unique_hfile _hFile;
@ -67,6 +75,7 @@ namespace Microsoft::Console
DWORD _dwThreadId;
bool _consoleConnected;
std::optional<ResizeWindowData> _earlyResize;
std::optional<ShowHideData> _initialShowHide;
std::unique_ptr<Microsoft::Console::VirtualTerminal::ConGetSet> _pConApi;
public:

View File

@ -257,6 +257,10 @@ bool VtIo::IsUsingVt() const
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(_pVtRenderEngine.get());
g.getConsoleInformation().GetActiveInputBuffer()->SetTerminalConnection(_pVtRenderEngine.get());
ServiceLocator::SetPseudoWindowCallback([&](bool showOrHide) -> void {
// Set the remote window visibility to the request
LOG_IF_FAILED(_pVtRenderEngine->SetWindowVisibility(showOrHide));
});
}
CATCH_RETURN();
}

View File

@ -258,6 +258,19 @@ CursorType ConhostInternalGetSet::GetUserDefaultCursorStyle() const
return gci.GetCursorType();
}
// Routine Description:
// - Shows or hides the active window when asked.
// Arguments:
// - showOrHide - True for show, False for hide. Matching WM_SHOWWINDOW lParam.
// Return Value:
// - <none>
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);
}
// Routine Description:
// - Connects the SetConsoleOutputCP API call directly into our Driver Message servicing call inside Conhost.exe
// Arguments:

View File

@ -57,6 +57,8 @@ public:
CursorType GetUserDefaultCursorStyle() const override;
void ShowWindow(bool showOrHide) override;
bool ResizeWindow(const size_t width, const size_t height) override;
void SetConsoleOutputCP(const unsigned int codepage) override;

View File

@ -25,6 +25,8 @@ HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size);
HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC);
HRESULT WINAPI ConptyShowHidePseudoConsole(HPCON hPC, bool show);
HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent);
VOID WINAPI ConptyClosePseudoConsole(HPCON hPC);

View File

@ -309,11 +309,9 @@ using namespace Microsoft::Console::Interactivity;
case ApiLevel::Win32:
{
pseudoClass.lpszClassName = PSEUDO_WINDOW_CLASS;
pseudoClass.lpfnWndProc = DefWindowProc;
pseudoClass.lpfnWndProc = s_PseudoWindowProc;
RegisterClass(&pseudoClass);
// When merging with #12515, we're going to need to adjust these styles.
//
// Note that because we're not specifying WS_CHILD, this window
// will become an _owned_ window, not a _child_ window. This is
// important - child windows report their position as relative
@ -323,9 +321,10 @@ using namespace Microsoft::Console::Interactivity;
// 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;
// Attempt to create window.
hwnd = CreateWindowExW(0,
hwnd = CreateWindowExW(exStyles,
PSEUDO_WINDOW_CLASS,
nullptr,
windowStyle,
@ -365,4 +364,118 @@ using namespace Microsoft::Console::Interactivity;
return status;
}
// Method Description:
// - Gives the pseudo console window a target to relay show/hide window messages
// Arguments:
// - func - A function that will take a true for "show" and false for "hide" and
// relay that information to the attached terminal to adjust its window state.
// Return Value:
// - <none>
void InteractivityFactory::SetPseudoWindowCallback(std::function<void(bool)> func)
{
_pseudoWindowMessageCallback = func;
}
// Method Description:
// - Static window procedure for pseudo console windows
// - Processes set-up on create to stow the "this" pointer to specific instantiations and routes
// to the specific object on future calls.
// Arguments:
// - hWnd - Associated window handle from message
// - Message - ID of message in queue
// - wParam - Variable wParam depending on message type
// - lParam - Variable lParam depending on message type
// Return Value:
// - 0 if we processed this message. See details on how a WindowProc is implemented.
[[nodiscard]] LRESULT CALLBACK InteractivityFactory::s_PseudoWindowProc(_In_ HWND hWnd, _In_ UINT Message, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
// Save the pointer here to the specific window instance when one is created
if (Message == WM_CREATE)
{
const CREATESTRUCT* const pCreateStruct = reinterpret_cast<CREATESTRUCT*>(lParam);
InteractivityFactory* const pFactory = reinterpret_cast<InteractivityFactory*>(pCreateStruct->lpCreateParams);
SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pFactory));
}
// Dispatch the message to the specific class instance
InteractivityFactory* const pFactory = reinterpret_cast<InteractivityFactory*>(GetWindowLongPtrW(hWnd, GWLP_USERDATA));
if (pFactory != nullptr)
{
return pFactory->PseudoWindowProc(hWnd, Message, wParam, lParam);
}
// If we get this far, call the default window proc
return DefWindowProcW(hWnd, Message, wParam, lParam);
}
// Method Description:
// - Per-object window procedure for pseudo console windows
// Arguments:
// - hWnd - Associated window handle from message
// - Message - ID of message in queue
// - wParam - Variable wParam depending on message type
// - lParam - Variable lParam depending on message type
// Return Value:
// - 0 if we processed this message. See details on how a WindowProc is implemented.
[[nodiscard]] LRESULT CALLBACK InteractivityFactory::PseudoWindowProc(_In_ HWND hWnd, _In_ UINT Message, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
switch (Message)
{
// NOTE: To the future reader, all window messages that are talked about but unused were tested
// during prototyping and didn't give quite the results needed to determine show/hide window
// state. The notes are left here for future expeditions into message queues.
// case WM_QUERYOPEN:
// It can be fun to toggle WM_QUERYOPEN but DefWindowProc returns TRUE.
case WM_SIZE:
{
if (wParam == SIZE_RESTORED)
{
_WritePseudoWindowCallback(true);
}
if (wParam == SIZE_MINIMIZED)
{
_WritePseudoWindowCallback(false);
}
break;
}
// case WM_WINDOWPOSCHANGING:
// As long as user32 didn't eat the `ShowWindow` call because the window state requested
// matches the existing WS_VISIBLE state of the HWND... we should hear from it in WM_WINDOWPOSCHANGING.
// WM_WINDOWPOSCHANGING can tell us a bunch through the flags fields.
// We can also check IsIconic/IsZoomed on the HWND during the message
// and we could suppress the change to prevent things from happening.
// case WM_SYSCOMMAND:
// WM_SYSCOMMAND will not come through. Don't try.
case WM_SHOWWINDOW:
// WM_SHOWWINDOW comes through on some of the messages.
{
if (0 == lParam) // Someone explicitly called ShowWindow on us.
{
_WritePseudoWindowCallback((bool)wParam);
}
}
}
// If we get this far, call the default window proc
return DefWindowProcW(hWnd, Message, wParam, lParam);
}
// Method Description:
// - Helper for the pseudo console message loop to send a notification
// when it realizes we should be showing or hiding the window.
// - Simply skips if no callback is installed.
// Arguments:
// - showOrHide: Show is true; hide is false.
// Return Value:
// - <none>
void InteractivityFactory::_WritePseudoWindowCallback(bool showOrHide)
{
if (_pseudoWindowMessageCallback)
{
_pseudoWindowMessageCallback(showOrHide);
}
}
#pragma endregion

View File

@ -27,5 +27,20 @@ namespace Microsoft::Console::Interactivity
[[nodiscard]] NTSTATUS CreateSystemConfigurationProvider(_Inout_ std::unique_ptr<ISystemConfigurationProvider>& provider);
[[nodiscard]] NTSTATUS CreatePseudoWindow(HWND& hwnd, const HWND owner);
void SetPseudoWindowCallback(std::function<void(bool)> func);
// Wndproc
[[nodiscard]] static LRESULT CALLBACK s_PseudoWindowProc(_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam);
[[nodiscard]] LRESULT CALLBACK PseudoWindowProc(_In_ HWND,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam);
private:
void _WritePseudoWindowCallback(bool showOrHide);
std::function<void(bool)> _pseudoWindowMessageCallback;
};
}

View File

@ -285,6 +285,27 @@ Globals& ServiceLocator::LocateGlobals()
return s_globals;
}
// Method Description:
// - Installs a callback method to receive notifications when the pseudo console
// window is shown or hidden by an attached client application (so we can
// translate it and forward it to the attached terminal, in case it would like
// to react accordingly.)
// Arguments:
// - func - Callback function that takes True as Show and False as Hide.
// Return Value:
// - <none>
void ServiceLocator::SetPseudoWindowCallback(std::function<void(bool)> func)
{
// Force the whole window to be put together first.
// We don't really need the handle, we just want to leverage the setup steps.
(void)LocatePseudoWindow();
if (s_interactivityFactory)
{
s_interactivityFactory->SetPseudoWindowCallback(func);
}
}
// Method Description:
// - Retrieves the pseudo console window, or attempts to instantiate one.
// Arguments:
@ -308,6 +329,7 @@ HWND ServiceLocator::LocatePseudoWindow(const HWND owner)
status = s_interactivityFactory->CreatePseudoWindow(hwnd, owner);
s_pseudoWindow.reset(hwnd);
}
s_pseudoWindowInitialized = true;
}
LOG_IF_NTSTATUS_FAILED(status);

View File

@ -40,6 +40,7 @@ namespace Microsoft::Console::Interactivity
[[nodiscard]] virtual NTSTATUS CreateAccessibilityNotifier(_Inout_ std::unique_ptr<IAccessibilityNotifier>& notifier) = 0;
[[nodiscard]] virtual NTSTATUS CreateSystemConfigurationProvider(_Inout_ std::unique_ptr<ISystemConfigurationProvider>& provider) = 0;
virtual void SetPseudoWindowCallback(std::function<void(bool)> func) = 0;
[[nodiscard]] virtual NTSTATUS CreatePseudoWindow(HWND& hwnd, const HWND owner) = 0;
};

View File

@ -84,6 +84,7 @@ namespace Microsoft::Console::Interactivity
static Globals& LocateGlobals();
static void SetPseudoWindowCallback(std::function<void(bool)> func);
static HWND LocatePseudoWindow(const HWND owner = nullptr /*HWND_DESKTOP = 0*/);
protected:

View File

@ -540,6 +540,25 @@ CATCH_RETURN();
return _Flush();
}
// Method Description:
// - Sends a command to set the terminal's window to visible or hidden
// Arguments:
// - showOrHide - True if show; false if hide.
// Return Value:
// - S_OK or suitable HRESULT error from either conversion or writing pipe.
[[nodiscard]] HRESULT XtermEngine::SetWindowVisibility(const bool showOrHide) noexcept
{
if (showOrHide)
{
RETURN_IF_FAILED(_Write("\x1b[1t"));
}
else
{
RETURN_IF_FAILED(_Write("\x1b[2t"));
}
return _Flush();
}
// Method Description:
// - Updates the window's title string. Emits the VT sequence to SetWindowTitle.
// Arguments:

View File

@ -53,6 +53,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT WriteTerminalW(const std::wstring_view str) noexcept override;
[[nodiscard]] HRESULT SetWindowVisibility(const bool showOrHide) noexcept override;
protected:
// I'm using a non-class enum here, so that the values
// are trivially convertible and comparable to bool.

View File

@ -86,6 +86,7 @@ namespace Microsoft::Console::Render
void SetTerminalCursorTextPosition(const COORD coordCursor) noexcept;
[[nodiscard]] virtual HRESULT ManuallyClearScrollback() noexcept;
[[nodiscard]] HRESULT RequestWin32Input() noexcept;
[[nodiscard]] virtual HRESULT SetWindowVisibility(const bool showOrHide) noexcept = 0;
[[nodiscard]] HRESULT SwitchScreenBuffer(const bool useAltBuffer) noexcept;
protected:

View File

@ -405,6 +405,8 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
enum WindowManipulationType : VTInt
{
Invalid = 0,
DeIconifyWindow = 1,
IconifyWindow = 2,
RefreshWindow = 7,
ResizeWindowInCharacters = 8,
};

View File

@ -109,6 +109,12 @@ bool InteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulatio
// MSFT:13271146 - QueryScreenSize
switch (function)
{
case DispatchTypes::WindowManipulationType::DeIconifyWindow:
_pConApi->ShowWindow(true);
return true;
case DispatchTypes::WindowManipulationType::IconifyWindow:
_pConApi->ShowWindow(false);
return true;
case DispatchTypes::WindowManipulationType::RefreshWindow:
_pConApi->GetTextBuffer().TriggerRedrawAll();
return true;

View File

@ -2313,6 +2313,12 @@ bool AdaptDispatch::WindowManipulation(const DispatchTypes::WindowManipulationTy
// MSFT:13271146 - QueryScreenSize
switch (function)
{
case DispatchTypes::WindowManipulationType::DeIconifyWindow:
_pConApi->ShowWindow(true);
return true;
case DispatchTypes::WindowManipulationType::IconifyWindow:
_pConApi->ShowWindow(false);
return true;
case DispatchTypes::WindowManipulationType::RefreshWindow:
_pConApi->GetTextBuffer().TriggerRedrawAll();
return true;

View File

@ -59,6 +59,8 @@ namespace Microsoft::Console::VirtualTerminal
virtual CursorType GetUserDefaultCursorStyle() const = 0;
virtual void ShowWindow(bool showOrHide) = 0;
virtual void SetConsoleOutputCP(const unsigned int codepage) = 0;
virtual unsigned int GetConsoleOutputCP() const = 0;

View File

@ -181,6 +181,12 @@ public:
return CursorType::Legacy;
}
void ShowWindow(bool showOrHide) override
{
Log::Comment(L"ShowWindow MOCK called...");
VERIFY_ARE_EQUAL(_expectedShowWindow, showOrHide);
}
bool ResizeWindow(const size_t /*width*/, const size_t /*height*/) override
{
Log::Comment(L"ResizeWindow MOCK called...");
@ -397,6 +403,7 @@ public:
std::wstring_view _expectedWindowTitle{};
bool _setConsoleOutputCPResult = false;
bool _getConsoleOutputCPResult = false;
bool _expectedShowWindow = false;
private:
HANDLE _hCon;

View File

@ -255,6 +255,28 @@ HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty)
}
// Function Description:
// - Shows or hides the internal HWND used by ConPTY. This should be kept in
// sync with the hosting application's window.
// Arguments:
// - hSignal: A signal pipe as returned by CreateConPty.
// - show: true if the window should be shown, false to mark it as iconic.
// Return Value:
// - S_OK if the call succeeded, else an appropriate HRESULT for failing to
// write the clear message to the pty.
HRESULT _ShowHidePseudoConsole(_In_ const PseudoConsole* const pPty, const bool show)
{
if (pPty == nullptr)
{
return E_INVALIDARG;
}
unsigned short signalPacket[2];
signalPacket[0] = PTY_SIGNAL_SHOWHIDE_WINDOW;
signalPacket[1] = show;
const BOOL fSuccess = WriteFile(pPty->hSignal, signalPacket, sizeof(signalPacket), nullptr, nullptr);
return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
// - Sends a message to the pseudoconsole informing it that it should use the
// given window handle as the owner for the conpty's pseudo window. This
// allows the response given to GetConsoleWindow() to be a HWND that's owned
@ -273,7 +295,6 @@ HRESULT _ReparentPseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const
{
return E_INVALIDARG;
}
// sneaky way to pack a short and a uint64_t in a relatively literal way.
#pragma pack(push, 1)
struct _signal
@ -284,6 +305,7 @@ HRESULT _ReparentPseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const
#pragma pack(pop)
const auto fSuccess = WriteFile(pPty->hSignal, &data, sizeof(data), nullptr, nullptr);
return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
@ -459,6 +481,16 @@ extern "C" HRESULT WINAPI ConptyClearPseudoConsole(_In_ HPCON hPC)
}
// Function Description:
// - Tell the ConPTY about the state of the hosting window. This should be used
// to keep ConPTY's internal HWND state in sync with the state of whatever the
// hosting window is.
// - For more information, refer to GH#12515.
extern "C" HRESULT WINAPI ConptyShowHidePseudoConsole(_In_ HPCON hPC, bool show)
{
// _ShowHidePseudoConsole will return E_INVALIDARG for us if the hPC is nullptr.
return _ShowHidePseudoConsole((PseudoConsole*)hPC, show);
}
// - Sends a message to the pseudoconsole informing it that it should use the
// given window handle as the owner for the conpty's pseudo window. This
// allows the response given to GetConsoleWindow() to be a HWND that's owned

View File

@ -17,6 +17,7 @@ typedef struct _PseudoConsole
// Signals
// These are not defined publicly, but are used for controlling the conpty via
// the signal pipe.
#define PTY_SIGNAL_SHOWHIDE_WINDOW (1u)
#define PTY_SIGNAL_CLEAR_WINDOW (2u)
#define PTY_SIGNAL_REPARENT_WINDOW (3u)
#define PTY_SIGNAL_RESIZE_WINDOW (8u)
@ -38,6 +39,7 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken,
HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const COORD size);
HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty);
HRESULT _ShowHidePseudoConsole(_In_ const PseudoConsole* const pPty, const bool show);
HRESULT _ReparentPseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const HWND newParent);
void _ClosePseudoConsoleMembers(_In_ PseudoConsole* pPty);
VOID _ClosePseudoConsole(_In_ PseudoConsole* pPty);