-
Notifications
You must be signed in to change notification settings - Fork 8.5k
Enable the Terminal to tell ConPTY who the owner is #12526
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
Conversation
…y with the actual terminal hwnd too
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
// When merging with #12515, we're going to need to adjust these styles. | ||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah watch out for this.
@msftbot make sure @DHowett signs off |
Hello @DHowett! Because you've given me some instructions on how to help merge this pull request, I'll be modifying my merge approach. Here's how I understand your requirements for merging this pull request:
If this doesn't seem right to you, you can tell me to cancel these instructions and use the auto-merge policy that has been configured for this repository. Try telling me "forget everything I just told you". |
Nits: rework the PR description -- we should be careful about merging things that say "prototype" and then calling them stable and done :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I generally wish the uint64_t
would get converted to a HWND
as early as possible (if possible). Otherwise this looks good to me.
struct SetParentData | ||
{ | ||
uint64_t handle; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why's this a struct
? Will there be more members in the future?
Why is handle
not a HWND
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meh, they're all structs? I thought it was a nice abstraction to indicate "this is the packet of data that's sent". I don't expect to add more members in the future, but who knows.
As far as the uint64_t
vs HWND
- IIRC HWND
is technically just a void*
, so I wanted to be explicit with the size of data that's getting passed on the wire here. It's the same idea as like, every time we use a uint64_t
for a HWND
in any WinRT things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think transmitting it as a unintptr_t
would've been more consistent with our other Win32 APIs (which this API is closest to IMO).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, this is a wire format. 32-bit VSCode might be using 64-bit conhost (in fact: it is using 64-bit conhost). uintptr_t
changes size. uint64_t
does not.
@@ -13,6 +13,7 @@ namespace Microsoft.Terminal.TerminalConnection | |||
Guid Guid { get; }; | |||
String Commandline { get; }; | |||
void ClearBuffer(); | |||
void ReparentWindow(UInt64 newParent); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mind filing a followup to make this and the following ones about focus part of a new interface? ITerminalConnectionWindowHosting
or something
@@ -262,6 +262,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |||
const auto height = vp.Height(); | |||
_connection.Resize(height, width); | |||
|
|||
if (_OwningHwnd != 0) | |||
{ | |||
if (auto conpty{ _connection.try_as<TerminalConnection::ConptyConnection>() }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't love this coupling, thus the interface :)
@@ -2794,4 +2794,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |||
} | |||
} | |||
|
|||
void TermControl::OwningHwnd(uint64_t owner) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: vaguely prefer the HostingWindow
parlance; we can take that as a followup (look at TSE's IHostedInWindow
)
// If the window hasn't been created yet, by some other call to | ||
// LocatePseudoWindow, then this will also initialize the owner of the | ||
// window. | ||
if (const auto psuedoHwnd{ ServiceLocator::LocatePseudoWindow(reinterpret_cast<HWND>(handle)) }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
psuedo
-> pseudo
@@ -892,3 +892,17 @@ void ConhostInternalGetSet::UpdateSoftFont(const gsl::span<const uint16_t> bitPa | |||
pRender->UpdateSoftFont(bitPattern, cellSize, centeringHint); | |||
} | |||
} | |||
|
|||
void ConhostInternalGetSet::ReparentWindow(const uint64_t handle) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting -- why did we keep it a uint64_t
this deep in the stack? We should decap it as early as we can
// window. | ||
if (const auto psuedoHwnd{ ServiceLocator::LocatePseudoWindow(reinterpret_cast<HWND>(handle)) }) | ||
{ | ||
LOG_LAST_ERROR_IF_NULL(::SetParent(psuedoHwnd, reinterpret_cast<HWND>(handle))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just making sure . . . so this sets the OWNER, not the PARENT, for the same reason as below (no CHILDWINDOW
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exactly. There's no ::SetOwner
, SetParent
sets the owner/parent based on if WS_CHILD
is set.
I'm not bullish on the spelling fix, but I am curious about decapping the uint64 early so that we don't pass it around as an unguarded integer for too long. |
TODOS:
|
If you need to resolve a merge conflict in a stacked PR, merge 26d67d9 into your branch. You can fetch it with |
…12799) ## Window shenanigans, part the third: Hooks the Terminal's focus state up to the underlying ConPTY. This is LOAD BEARING for allowing windows created by console applications to bring themselves to the foreground. We're using the [FocusIn/FocusOut](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-FocusIn_FocusOut) sequences to communicate to ConPTY when a control gains/loses focus. Theoretically, other terminals could do this as well. ## References #11682 tracks _real_ support for this sequence in Console & conpty. When we do that, we should consider even if a client application disables this mode, the Terminal & conpty should always request this from the hosting terminal (and just ignore internally to ConPTY). See also #12515, #12526, which are the other two parts of this effort. This was tested with all three merged together, and they worked beautifully for all our scenarios. They are kept separate for ease of review. ## PR Checklist * [x] This is prototype 3 for #2988 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments This allows windows spawned by console processes to bring themselves to the foreground _when the console is focused_. (Historically, this is also called in the WndProc, when focus changes). Notably, before this, ConPTY was _never_ focused, so windows could never bring themselves to the foreground when run from a ConPTY console. We're not blanket granting the SetForeground right to all console apps when run in ConPTY. It's the responsibility of the hosting terminal emulator to always tell ConPTY when a particular instance is focused. ## Validation Steps Performed (gif below)
####⚠️ _Targets #12799_⚠️ This is an atomic bit of code that partners with #12799. It's separated as an individual PR to keep diffs more simple. This ensures that when a terminal tells ConPTY that it's focused, that ConPTY doesn't do the `ConsoleControl(CONSOLE_FOREGROUND` thing unless the terminal application is actually in the foreground. This prevents a trivial exploit whereby a `malicious.exe` could create a PTY, tell ConPTY it has focus (when it doesn't), then use this mechanism to launch an instance of itself into the foreground. When the terminal tells us it's in the foreground, we're gonna look at the owner of the ConPTY window handle. If that owner has focus, then cool, this is allowed. Otherwise, we won't grant them the FG right. For this to work, the terminal just have already called `ReparentPseudoConsole`. * built on top of #12799 and #12526 * [x] Part of #2988 * [x] Tested manually.
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) ```
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
🎉 Handy links: |
Window shenanigans, part the first:
This PR enables terminals to tell ConPTY what the owning window for the pseudo window should be. This allows thigs like MessageBoxes created by console applications to work. It also enables console apps to use
GetAncestor(GetConsoleWindow(), GA_ROOT)
to get directly at the HWND of the Terminal (but don't please).This is tested with our internal partners and seems to work for their scenario.
See #2988, #12799, #12515, #12570.
PR Checklist
GetConsoleWindow
) #2988.TODOS: