-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Conversation
…ntrol doesn't render right, but I think that's because the actual HEAD of all this work is in `connection-factory`
…ere are many issues.
… just going to disable this for now
This reverts commit 64533c8.
…sn't going to work
… 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_
// 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>()) |
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.
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
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.
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 |
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.
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"
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.
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)
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.
Probably want to bring fadd028 with it
} | ||
|
||
// Method Description: | ||
// - Duplicate the swap chain handle to the provided process. |
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.
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.
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.
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...
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.
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
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.
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.
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.
bad first attempt in dev/migrie/oop/2/COM-ISwapChainProvider-attempt-1
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'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. |
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.
torn state opportunity?
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.
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"
|
||
// Stash away that weak ref for future callers. | ||
g_weak = weak; | ||
return strong.as(iid, result); |
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.
wait, as
returns an hresult we can use? it doesn't throw? what if it throws...
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); | ||
} | ||
} |
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.
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?
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.
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.
// Sometimes, RoActivateInstance is weird and fails with errors like the | ||
// following |
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.
That sounds weird? It shouldn't be doing that. 😰
Do we have to do that?
try | ||
{ |
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 don't quite see what could throw here? 🤔
|
||
waits[0] = hContent.get(); | ||
|
||
switch (WaitForMultipleObjects(2, waits, FALSE, INFINITE)) |
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.
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);
}
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? |
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. |
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 towt
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 withThis 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 indev/migrie/oop/2/cptn-marvel
, but that can definitely wait. Best to get this section of the merge conflicts done with now.Control.ContentProcess
class, which is responsible for owning a singleControlInteractivity
bound to a GUID. You can useContentProcess.RequestSwapChainHandle
to get the swapchain of that content process.TermControlContentManagement.cpp
, which handles theTermControl
side of connecting to one of these.InteractivityAutomationPeer
methods, because we couldn't have a XAMLAutomationPeer
in the content process without XAML. I've checked that this still works in narrator and accessibility insights.WindowsTerminal
forContentProcessMain
, for handling all the weird lifetime semantics of the content process.Validation Steps Performed
It works pretty cool in the scratch sln.