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

Add support for running the terminal core as a content process #12938

Closed
wants to merge 62 commits into from

Conversation

zadjii-msft
Copy link
Member

@zadjii-msft zadjii-msft commented Apr 19, 2022

Summary of the Pull Request

This is 1000% WIP work. This PR, however, can go in now before the rest of tear out is ready. It's gated behind a new velocity flag that only enables it for Dev builds. Terminal the app also provides no way to leverage this quite yet, but it is hooked up for the sample app.

Adds an undocumented --content parameter to wt which allows you to pass a content GUID on the commandline. When you do that, wt will start up as a content process, instead of as a window process. WT will serve a singular terminal control instance, which can be accessed with

winrt::create_instance<winrt::Microsoft::Terminal::Control::ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);

This content process will hang around until the last outstanding reference to it is closed.

PR Checklist

Detailed Description of the Pull Request / Additional comments

Multiple different clients can access the same content process by simply communicating the GUID. That's obviously not recommended to do simultaneously, cause you'd have to keep the controls exactly the same size, but it's something that's possible.

Theoretically, we need to gate access to _interactivity and to _core behind some sort of safer accessor that won't just throw when the content process has died unexpectedly. A prototype was started in dev/migrie/oop/2/cptn-marvel, but that can definitely wait. Best to get this section of the merge conflicts done with now.

  • We've added a Control.ContentProcess class, which is responsible for owning a single ControlInteractivity bound to a GUID. You can use ContentProcess.RequestSwapChainHandle to get the swapchain of that content process.
    • this includes the TermControlContentManagement.cpp, which handles the TermControl side of connecting to one of these.
  • We've had to re-wire some InteractivityAutomationPeer methods, because we couldn't have a XAML AutomationPeer in the content process without XAML. I've checked that this still works in narrator and accessibility insights.
  • There's another file in WindowsTerminal for ContentProcessMain, for handling all the weird lifetime semantics of the content process.

Validation Steps Performed

It works pretty cool in the scratch sln.

…ntrol doesn't render right, but I think that's because the actual HEAD of all this work is in `connection-factory`
… thing isn't going to work"

This reverts commit fd364db.
…ng for the actual text ranges. That's not what you want.
…he core. I think every x-proc call in TermControl logs that message, so _crap_
@zadjii-msft zadjii-msft added this to the Terminal v1.15 milestone Apr 28, 2022
// When we get to extensions (GH#4000), we may want to revisit.
if (LOG_IF_FAILED(RoActivateInstance(name, raw)))
// So to avoid those, we'll manually instantiate these
if (info.ClassName() == winrt::name_of<TerminalConnection::ConptyConnection>())
Copy link
Member

Choose a reason for hiding this comment

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

notes from discussion: it is pretty cheap to try to activate a class from our own DLL, so we may just want to do that

Copy link
Member

Choose a reason for hiding this comment

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

barring that, can we use the same reverse compare optimization that cppwinrt does?

// get destructed. This eliminates the need to do any sort of
// ref-counting weirdness. This entire process exists to host one
// singular ContentProcess instance. When we're destructed, it's because
// every other window process was done with us. We can die now, knowing
Copy link
Member

Choose a reason for hiding this comment

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

You might want to move this into a final_release block. Otherwise, COM might consider it a reportable RPC failure when the final ->Release() call results in the RPC server "crashing"

Copy link
Member Author

Choose a reason for hiding this comment

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

Note to self: I did this in e9375d6...089c015. Probably explodes when merged with #13882. Right now those commits are on wandavision, which is definitely after endgame (and maybe an additional black-widow PR for preemptive cleanup)

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably want to bring fadd028 with it

}

// Method Description:
// - Duplicate the swap chain handle to the provided process.
Copy link
Member

Choose a reason for hiding this comment

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

Danger, Will Robinson!

Michael got system_handle published for consumption on the promise that it would stop app developers from having to roll their own handle security and ownership management!

There must be some way for us to leverage that here.

Copy link
Member

Choose a reason for hiding this comment

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

so I see you mentioned it below. I'd still like to see us dig further into making it a COM interface and doing it "right" if possible...

Copy link
Member Author

Choose a reason for hiding this comment

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

It does seem like this counts as system_handle(sh_composition). Trick is plumbing it all through.

  • We've got a WinRT object, the ContentProcess.
  • The window needs to ask ^ that thing ^ for a COM type, a ISwapChainProvider (whatever).
  • The Control dll needs to also expose COM type information?
  • But this sample on MSDN makes it look... really easy: A simple example of a COM component

Copy link
Member Author

Choose a reason for hiding this comment

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

Based on my prior experience with COM, I'm certain that it's not gonna be that easy. It doesn't seem like it's something trivial to add a COM interfact to ContentProcess, via just a c++ virtual class like in that example. Plus, that doesn't invoke the system_handle magic that's important here.

How much do we hate filing this as a code-health follow up? I don't want to burn a week investigating how to re-jigger ContentProcessMain to serve as a factory for two interfaces, the second being the ISwapChainProvider one.

Copy link
Member Author

Choose a reason for hiding this comment

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

bad first attempt in dev/migrie/oop/2/COM-ISwapChainProvider-attempt-1

Copy link
Member

Choose a reason for hiding this comment

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

I'd ask BriAl for help early -- I don't want to break into jail handling our own handle security unless we absolutely need to.

// If we were being used in a process that had a CoreWindow, we could just call
// DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
//
// We won't always be though, so instead, ask our model what the DPI is.
Copy link
Member

Choose a reason for hiding this comment

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

torn state opportunity?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yea, that's definitely a torn state opportunity. Like, especially those few frames where the Terminal doesn't seem like it reacted to the DPI change. Lemme see if we can wrap that up in a try/catch - at least the default behavior is "definitely the right thing", and it falls back to "hey what did you think it was"

src/cascadia/WindowsTerminal/ContentProcessMain.cpp Outdated Show resolved Hide resolved
src/cascadia/WindowsTerminal/ContentProcessMain.cpp Outdated Show resolved Hide resolved

// Stash away that weak ref for future callers.
g_weak = weak;
return strong.as(iid, result);
Copy link
Member

Choose a reason for hiding this comment

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

wait, as returns an hresult we can use? it doesn't throw? what if it throws...

Comment on lines +88 to +95
wil::unique_any<LPWSTR*, decltype(&::LocalFree), ::LocalFree> argv{ CommandLineToArgvW(commandline, &argc) };
if (argv)
{
for (auto& elem : wil::make_range(argv.get(), argc))
{
args.emplace_back(elem);
}
}
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 expensive to do just to throw away and then do again later, isn't it? Can we just strictly check for --content in an expected location and not do a full deep string split?

Copy link
Member Author

Choose a reason for hiding this comment

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

note: I might just leave this, cause I couldn't find a good way to handle wt --content "foo --signal" this-is-dumb-but-you-get-it without CommandLineToArgvW, and splitting the commandline args won't be that expensive, in the big picture.

src/cascadia/WindowsTerminal/main.cpp Show resolved Hide resolved
scratch/ScratchIslandApp/SampleApp/MyPage.cpp Outdated Show resolved Hide resolved
scratch/ScratchIslandApp/SampleApp/MyPage.cpp Outdated Show resolved Hide resolved
Comment on lines 42 to 43
// Sometimes, RoActivateInstance is weird and fails with errors like the
// following
Copy link
Member

Choose a reason for hiding this comment

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

That sounds weird? It shouldn't be doing that. 😰
Do we have to do that?

src/cascadia/TerminalControl/ContentProcess.cpp Outdated Show resolved Hide resolved
Comment on lines +39 to +40
try
{
Copy link
Member

Choose a reason for hiding this comment

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

I don't quite see what could throw here? 🤔


waits[0] = hContent.get();

switch (WaitForMultipleObjects(2, waits, FALSE, INFINITE))
Copy link
Member

@lhecker lhecker May 25, 2022

Choose a reason for hiding this comment

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

Technically we can use RegisterWaitForSingleObject here.

This differs from WaitForSingleObject because the wait is performed by a different thread that combines several such calls for efficiency.

Something like that:

class TermControl
{
    // Can't use wil::unique_event, because RegisterWaitForSingleObject
    // requires UnregisterWaitEx and not CloseHandle.
    HANDLE contentWaitHandle = nullptr;
};

void TermControl::~TermControl() {
    if (const auto handle = std::exchange(self->contentWaitHandle, nullptr))
    {
        // INVALID_HANDLE_VALUE = block until currently running callbacks are finished.
        UnregisterWaitEx(handle, INVALID_HANDLE_VALUE);
    }
}

void TermControl::waitCallback(_In_ PVOID lpParameter, _In_ BOOLEAN TimerOrWaitFired)
try
{
    const auto self = static_cast<TermControl*>(lpParameter);
    if (const auto handle = std::exchange(self->contentWaitHandle, nullptr))
    {
        UnregisterWaitEx(handle, nullptr);
        self->_raiseContentDied();
    }
}
CATCH_LOG()

void TermControl::s_waitOnContentProcess()
{
    RegisterWaitForSingleObject(&contentWaitHandle, _contentWaitInterrupt, &TermControl::waitCallback, static_cast<void*>(this), INFINITE, WT_EXECUTEONLYONCE);
}

src/cascadia/WindowsTerminal/ContentProcessMain.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalControl/TermControl.cpp Outdated Show resolved Hide resolved
@zadjii-msft
Copy link
Member Author

I'm putting this in 1.17 but let's be real - I won't be around for most of 1.17 and 1.18. So maybe we merge this at the start of 1.17 just to clean it off the PR backlog, but it won't be used by the real Terminal so maybe it's okay?

@zadjii-msft zadjii-msft removed this from the Terminal v1.17 milestone Dec 12, 2022
@zadjii-msft
Copy link
Member Author

We're gonna pursue another solution going into 2023. Some recent research showed that we might be able to do this all much, much easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Remoting Communication layer between windows. Often for windowing behavior, quake mode, etc. Area-TerminalControl Issues pertaining to the terminal control (input, selection, keybindings, mouse interaction, etc.)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants