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

Use feature markers during terminal-by-default handoff #13160

Merged
4 commits merged into from
Jun 7, 2022

Conversation

lhecker
Copy link
Member

@lhecker lhecker commented May 24, 2022

If we want to make Windows Terminal the default terminal under Windows,
we'll have to make conhost "handoff" incoming connections by default.
But this poses a problem: How can the seldomly updated conhost know
whether the routinely updated Windows Terminal version is actually willing
to accept such handoffs by default (it might be unwilling due to bugs, etc.)?

This commit solves the issue by introducing:

  • A marker interface (IDefaultTerminalMarker): If it exists,
    Windows Terminal indicates its willingness to accept the handoff.
  • Turning the all-0 GUID from being synonymous for conhost,
    to being synonymous for "Let Windows decide". Without this we wouldn't
    be able to differentiate between users who consciously chose conhost
    as their default terminal, vs. users who want the standard behavior.

Validation Steps Performed

Testing fallback behavior:

  • Install "Terminal" 1.13
  • Delete the 2 keys below HKCU\Console\%%Startup
  • Enable Feature_AttemptHandoff in features.xml
    Return true from DefaultApp::CheckShouldTerminalBeDefault
  • Replace conhost.exe and console.dll with sfpcopy after building
  • Launching cmd.exe launches as a conhost window ✅
    (because "Terminal" 1.13 lacks the marker interface)
  • Open properties page in conhost.exe
    "Let Windows decide" is select by default ✅
  • Changing the selection writes the new value ✅

Testing the new behavior:

  • Delete the 2 keys below HKCU\Console\%%Startup
  • Enable Feature_AttemptHandoff in features.xml
    Return true from DefaultApp::CheckShouldTerminalBeDefault
  • Use CLSID_WindowsTerminalConsoleDev and CLSID_WindowsTerminalTerminalDev
    for the initialization of TerminalDelegationPair
  • Replace conhost.exe and console.dll with sfpcopy after building
  • Deploy the "Terminal Dev" package
  • Launching cmd.exe launches "Terminal Dev" ✅
    (because "Terminal Dev" has the marker interface)
  • Open the settings tab
    "Let Windows decide" is select by default ✅
  • Changing the selection and saving writes the new value ✅

@lhecker lhecker force-pushed the dev/lhecker/wt-as-default branch from 59d45c9 to afec10f Compare May 24, 2022 19:34
@lhecker lhecker added Product-Terminal The new Windows Terminal. Priority-1 A description (P1) Area-DefApp labels May 24, 2022
src/host/srvinit.cpp Outdated Show resolved Hide resolved
src/server/IoDispatchers.cpp Outdated Show resolved Hide resolved
zadjii-msft
zadjii-msft previously approved these changes May 26, 2022
Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

Okay, this all looks like it'll work Terminal-side, and conhost side. My only recommendation would be add more comments. A lot of this defterm stuff is hard to parse, so more comments never hurt.

The code is good enough though, so :shipit:

src/propslib/DelegationConfig.cpp Outdated Show resolved Hide resolved
src/propslib/DelegationConfig.cpp Outdated Show resolved Hide resolved
src/propslib/DelegationConfig.cpp Outdated Show resolved Hide resolved
src/host/srvinit.cpp Outdated Show resolved Hide resolved
src/server/IoDispatchers.cpp Outdated Show resolved Hide resolved
carlos-zamora
carlos-zamora previously approved these changes May 31, 2022
Copy link
Member

@carlos-zamora carlos-zamora left a comment

Choose a reason for hiding this comment

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

Looks good to me. Thanks!

src/propslib/DelegationConfig.cpp Outdated Show resolved Hide resolved
@carlos-zamora
Copy link
Member

Dustin wanted to take a look at this, so I'm leaving it unmerged for now :)

@carlos-zamora
Copy link
Member

@msftbot make sure @DHowett signs off on this

@ghost ghost added the AutoMerge Marked for automatic merge by the bot when requirements are met label May 31, 2022
@ghost
Copy link

ghost commented May 31, 2022

Hello @carlos-zamora!

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:

  • I'll only merge this pull request if it's approved by @DHowett

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".

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.

Chatted with Leonard & we need a slightly different solution :)

@ghost ghost added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jun 1, 2022
@ghost ghost removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jun 2, 2022
@lhecker lhecker dismissed stale reviews from zadjii-msft and carlos-zamora June 2, 2022 23:58

I redid the entire thing.

@lhecker lhecker marked this pull request as draft June 2, 2022 23:59
@lhecker
Copy link
Member Author

lhecker commented Jun 3, 2022

I've rewritten this PR, but haven't tested it yet and so converted this PR to a Draft.
I think it should work however, so please feel free to review if you have time to spare. 🙂
I'm updating the PR message to reflect the new approach.

@github-actions

This comment was marked as outdated.

@lhecker
Copy link
Member Author

lhecker commented Jun 3, 2022

This is what the new option looks like:
image

image

@lhecker lhecker force-pushed the dev/lhecker/wt-as-default branch from 79ddc66 to 86a1863 Compare June 3, 2022 19:10
@lhecker lhecker marked this pull request as ready for review June 3, 2022 19:10
@lhecker lhecker force-pushed the dev/lhecker/wt-as-default branch 2 times, most recently from fe26551 to 7c92e62 Compare June 3, 2022 20:07
@lhecker lhecker force-pushed the dev/lhecker/wt-as-default branch from 7c92e62 to 89b4739 Compare June 3, 2022 20:11
@microsoft microsoft deleted a comment from github-actions bot Jun 3, 2022
@lhecker lhecker force-pushed the dev/lhecker/wt-as-default branch from d754068 to 33a69fe Compare June 6, 2022 15:10
Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

Okay, I read over this all, it makes sense to me. I again left notes as I went - my nit again is that all this defterm stuff is already hard to parse so more comments is always better.

@@ -93,7 +93,7 @@

<Grid.RowDefinitions>
<!-- profile name -->
<RowDefinition Height="*" />
<RowDefinition Height="20" />
Copy link
Member

Choose a reason for hiding this comment

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

nit: is this redundant with the Height="20" on the Name TextBlock?

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately we only get the layout in the screenshot above, by specifying the height explicitly twice. The reason is that the flyout (and only the flyout) seemingly doesn't respect the visibility state of the two TextBlocks. Due to that the dropdown box will have the correct height (1 line high) but the flyout list will be 2 lines high, even with both TextBlocks collapsed. I'm not entirely sure how such a bug can occur, but it does. 😄

std::pair<std::vector<Model::DefaultTerminal>, Model::DefaultTerminal> result{ {}, nullptr };
til::latch latch{ 1 };

std::ignore = [&]() -> winrt::fire_and_forget {
Copy link
Member

Choose a reason for hiding this comment

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

I low key really love this "go do something on a backgrouund thread" lambda, without needing to make a whole function declaration for it.

It's like a sneaky way of doing a IAsyncOperation without also making this method async, and letting us return a non winrt type. neato.

Copy link
Member

Choose a reason for hiding this comment

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

But we're not using extractValueFromTaskWithoutMainThreadAwait... because... it's just a static helper in the other file?

Copy link
Member Author

Choose a reason for hiding this comment

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

extractValueFromTaskWithoutMainThreadAwait expects an (asynchronous) Task which can be co_awaited, but this code executes a synchronous function on a background task instead.

@@ -61,9 +61,11 @@ static auto extractValueFromTaskWithoutMainThreadAwait(TTask&& task) -> decltype
til::latch latch{ 1 };

const auto _ = [&]() -> winrt::fire_and_forget {
const auto cleanup = wil::scope_exit([&]() {
Copy link
Member

Choose a reason for hiding this comment

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

not sure I understand why this moved. Just in case task throws?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I realized this mistake when I wrote the code for CascadiaSettings.cpp and wanted to fix extractValueFromTaskWithoutMainThreadAwait as well.

@@ -47,7 +47,7 @@ namespace Microsoft::Console::Internal

namespace DefaultApp
{
[[nodiscard]] HRESULT CheckDefaultAppPolicy(bool& isEnabled) noexcept;
[[nodiscard]] HRESULT CheckShouldTerminalBeDefault(bool& isEnabled) noexcept;
[[nodiscard]] bool CheckDefaultAppPolicy() noexcept;
Copy link
Member

Choose a reason for hiding this comment

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

IIRC if we change the signature of these methods, we'll have to update the internal-only versions as well, FYI.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep! I felt like this made writing the code more comfortable and noticed that the internal code never returns anything but S_OK anyways.

Comment on lines +288 to +297
if (Globals.defaultTerminalMarkerCheckRequired)
{
::Microsoft::WRL::ComPtr<IDefaultTerminalMarker> marker;
if (FAILED(handoff.As(&marker)))
{
Globals.delegationPair = DelegationConfig::ConhostDelegationPair;
Globals.defaultTerminalMarkerCheckRequired = false;
return;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This is the new section

src/propslib/DelegationConfig.cpp Show resolved Hide resolved
&bytesNeeded);

if (NTSTATUS_FROM_WIN32(ERROR_SUCCESS) != result)
if (values[0] == CLSID_Default || values[1] == CLSID_Default)
Copy link
Member

Choose a reason for hiding this comment

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

notes: if either was missing or failed to parse, use {default, 0, 0}

RETURN_IF_FAILED(IIDFromString(buffer.get(), &iid));

return S_OK;
if (values[0] == CLSID_Conhost || values[1] == CLSID_Conhost)
Copy link
Member

Choose a reason for hiding this comment

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

Was the saved value SPECIFICALLY conhost? then return that

oh ho ho so I see, this is where we're being tricky now. Now conhost isn't {0}, it's got it's own GUID now. That makes sense.

src/propslib/DelegationConfig.cpp Outdated Show resolved Hide resolved

vec.push_back(useInbox);

auto coinit = wil::CoInitializeEx(COINIT_APARTMENTTHREADED);
Copy link
Member

Choose a reason for hiding this comment

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

We pretty sure the propsheet initializes COM itself? Note that the propsheet can get loaded outside of the context of conhost, just by right-clicking on a .lnk to a commandline application.

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 just moved the initialization one call up so that we don't have to do this twice.

@ghost ghost merged commit 1b81c65 into main Jun 7, 2022
@ghost ghost deleted the dev/lhecker/wt-as-default branch June 7, 2022 19:31
ghost pushed a commit that referenced this pull request Jun 9, 2022
When #13160 introduced a new interface to the IConsoleHandoff idl, it
changed midl's RPC proxy stub lookup algorithm from a direct GUID
comparison to an unrolled binary search. Now, that would ordinarily not
be a problem...

However, in #11610, we took a shortcut and replaced `memcmp` -- used
only by RPC for GUID comparison -- with a direct GUID-only equality
comparator. This worked totally fine, and ordinarily would not be a
problem...

The unrolled binary search unfortunately _relies on memcmp's contract_:
it uses memcmp to match against a fully sorted set. Our memcmp only
returned 0 or 1 (equal or not), and it knew nothing about ordering.

When a package that contains a PackagedCOM proxy stub is installed, it
is selected as the primary proxy stub for any interfaces it can proxy.
After all, interfaces are immutable, so it doesn't matter whose proxy
you're using. Now, given that we installed a *broken* proxy... *all*
IIDs that got whacked by our memcmp issue broke for every consumer.

To fix it: instead of implementing memcmp ourselves, we're just going to
take a page out of WinAppSDK's book and link this binary using the
"Hybrid CRT" model. It will statically link any parts of the STL it uses
(none) and dynamically link the ucrt (which is guaranteed to be present
on Windows.)

Sure, the binary size goes up from 8k to 24k, but... the cost is never
having to worry about this again.

Closes #13251
DHowett pushed a commit that referenced this pull request Jun 24, 2022
If we want to make Windows Terminal the default terminal under Windows,
we'll have to make conhost "handoff" incoming connections by default.
But this poses a problem: How can the seldomly updated conhost know
whether the routinely updated Windows Terminal version is actually willing
to accept such handoffs by default (it might be unwilling due to bugs, etc.)?

This commit solves the issue by introducing:
* A marker interface (`IDefaultTerminalMarker`): If it exists,
  Windows Terminal indicates its willingness to accept the handoff.
* Turning the all-0 GUID from being synonymous for conhost,
  to being synonymous for "Let Windows decide". Without this we wouldn't
  be able to differentiate between users who consciously chose conhost
  as their default terminal, vs. users who want the standard behavior.

Testing fallback behavior:
* Install "Terminal" 1.13
* Delete the 2 keys below `HKCU\Console\%%Startup`
* Enable `Feature_AttemptHandoff` in `features.xml`
  Return `true` from `DefaultApp::CheckShouldTerminalBeDefault`
* Replace `conhost.exe` and `console.dll` with `sfpcopy` after building
* Launching `cmd.exe` launches as a conhost window ✅
  (because "Terminal" 1.13 lacks the marker interface)
* Open properties page in `conhost.exe`
  "Let Windows decide" is select by default ✅
* Changing the selection writes the new value ✅

Testing the new behavior:
* Delete the 2 keys below `HKCU\Console\%%Startup`
* Enable `Feature_AttemptHandoff` in `features.xml`
  Return `true` from `DefaultApp::CheckShouldTerminalBeDefault`
* Use `CLSID_WindowsTerminalConsoleDev` and `CLSID_WindowsTerminalTerminalDev`
  for the initialization of `TerminalDelegationPair`
* Replace `conhost.exe` and `console.dll` with `sfpcopy` after building
* Deploy the "Terminal Dev" package
* Launching `cmd.exe` launches "Terminal Dev" ✅
  (because "Terminal Dev" has the marker interface)
* Open the settings tab
  "Let Windows decide" is select by default ✅
* Changing the selection and saving writes the new value ✅

(cherry picked from commit 1b81c65)
Service-Card-Id: 82925080
Service-Version: Inbox
DHowett added a commit that referenced this pull request Jun 30, 2022
@ghost
Copy link

ghost commented Jul 6, 2022

🎉Windows Terminal v1.14.186 has been released which incorporates this pull request.:tada:

Handy links:

@ghost
Copy link

ghost commented Jul 6, 2022

🎉Windows Terminal Preview v1.15.186 has been released which incorporates this pull request.:tada:

Handy links:

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-DefApp AutoMerge Marked for automatic merge by the bot when requirements are met Priority-1 A description (P1) Product-Terminal The new Windows Terminal.
Projects
Development

Successfully merging this pull request may close these issues.

4 participants