Add a signal that the content can use to tell the window it's ready

This commit is contained in:
Mike Griese 2021-08-12 08:45:43 -05:00
parent 2f64db2765
commit 9331cc8e59
6 changed files with 82 additions and 32 deletions

View file

@ -28,7 +28,7 @@ namespace winrt::SampleApp::implementation
void MyPage::Create()
{
auto settings = winrt::make_self<ControlUnitTests::MySettings>();
auto settings = winrt::make_self<MySettings>();
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted in-proc...",
winrt::hstring{},
@ -56,7 +56,22 @@ namespace winrt::SampleApp::implementation
static wil::unique_process_information _createHostClassProcess(const winrt::guid& g)
{
auto guidStr{ ::Microsoft::Console::Utils::GuidToString(g) };
std::wstring commandline{ fmt::format(L"windowsterminal.exe --content {}", guidStr) };
// Create an event that the content process will use to signal it is
// ready to go. We won't need the event after this function, so the
// unique_event will clean up our handle when we leave this scope. The
// ContentProcess is responsible for cleaning up its own handle.
wil::unique_event ev{ CreateEvent(nullptr, true, false, L"contentProcessStarted") };
// Make sure to mark this handle as inheritable! Even with
// bInheritHandles=true, this is only inherited when it's explicitly
// allowed to be.
SetHandleInformation(ev.get(), HANDLE_FLAG_INHERIT, 1);
// god bless, fmt::format will format a HANDLE like `0xa80`
std::wstring commandline{
fmt::format(L"windowsterminal.exe --content {} --signal {}", guidStr, ev.get())
};
STARTUPINFO siOne{ 0 };
siOne.cb = sizeof(STARTUPINFOW);
wil::unique_process_information piOne;
@ -65,7 +80,7 @@ namespace winrt::SampleApp::implementation
commandline.data(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
false, // bInheritHandles
true, // bInheritHandles
CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // startingDirectory
@ -73,28 +88,23 @@ namespace winrt::SampleApp::implementation
&piOne // lpProcessInformation
);
THROW_IF_WIN32_BOOL_FALSE(succeeded);
// if (!succeeded)
// {
// printf("Failed to create host process\n");
// return;
// }
// Ooof this is dumb, but we need a sleep here to make the server starts.
// That's _sub par_. Maybe we could use the host's stdout to have them emit
// a byte when they're set up?
Sleep(2000);
// TODO MONDAY - It seems like it takes conhost too long to start up to
// host the ScratchWinRTServer that even a sleep 100 is too short. However,
// any longer, and XAML will just crash, because some frame took too long.
// So we _need_ to do the "have the server explicitly tell us it's ready"
// thing, and maybe also do it on a bg thread (and signal to the UI thread
// that it can attach now)
// Wait for the child process to signal that they're ready.
// TODO!: The _first_ time we run, we always fail to init the ContentProcess. What the heck?
WaitForSingleObject(ev.get(), INFINITE);
return std::move(piOne);
}
winrt::fire_and_forget MyPage::CreateClicked(const IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& eventArgs)
void MyPage::_writeToLog(std::wstring_view str)
{
winrt::WUX::Controls::TextBlock block;
block.Text(str);
Log().Children().Append(block);
}
winrt::fire_and_forget MyPage::CreateClicked(const IInspectable& sender,
const WUX::Input::TappedRoutedEventArgs& eventArgs)
{
auto guidString = GuidInput().Text();
@ -102,7 +112,9 @@ namespace winrt::SampleApp::implementation
winrt::apartment_context ui_thread;
co_await winrt::resume_background();
auto canConvert = guidString.size() == 38 && guidString.front() == '{' && guidString.back() == '}';
auto canConvert = guidString.size() == 38 &&
guidString.front() == '{' &&
guidString.back() == '}';
bool tryingToAttach = false;
winrt::guid contentGuid{ ::Microsoft::Console::Utils::CreateGuid() };
@ -128,6 +140,13 @@ namespace winrt::SampleApp::implementation
// object.
// * If we're attaching, then that process already exists.
Control::ContentProcess content = create_instance<Control::ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
if (content == nullptr)
{
// Switch back to the UI thread.
co_await ui_thread;
_writeToLog(L"Failed to connect to the ContentProces object. It may not have been started fast enough.");
co_return; // be sure to co_return or we'll fall through to the part where we clear the log
}
TerminalConnection::ConnectionInformation connectInfo{ nullptr };
Control::IControlSettings settings{ *winrt::make_self<implementation::MySettings>() };
@ -153,7 +172,10 @@ namespace winrt::SampleApp::implementation
if (!content.Initialize(settings, connectInfo))
{
co_return;
// Switch back to the UI thread.
co_await ui_thread;
_writeToLog(L"Failed to Initialize the ContentProces object.");
co_return; // be sure to co_return or we'll fall through to the part where we clear the log
}
}
else
@ -168,6 +190,7 @@ namespace winrt::SampleApp::implementation
// We're not passing in a connection, because the contentGuid will be used instead.
Control::TermControl control{ contentGuid, settings, nullptr };
Log().Children().Clear();
OutOfProcContent().Children().Append(control);
if (!tryingToAttach)

View file

@ -14,14 +14,14 @@ namespace winrt::SampleApp::implementation
MyPage();
void Create();
hstring Title();
// winrt::fire_and_forget CreateOutOfProcTerminal();
winrt::fire_and_forget CreateClicked(const IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& eventArgs);
private:
friend struct MyPageT<MyPage>; // for Xaml to bind events
void _writeToLog(std::wstring_view str);
};
}

View file

@ -53,7 +53,12 @@
Padding="16"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="#0000ff" />
Background="#0000ff">
<StackPanel x:Name="Log"
Orientation="Vertical" />
</Grid>
</Grid>

View file

@ -247,6 +247,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
inline bool _IsClosing() const noexcept
{
#ifndef NDEBUG
// TODO! This may not be strictly true if the core is running out of
// proc with XAML. I keep hitting this assertion every time it
// exits, so we might need a better solution.
if (_dispatcher)
{
// _closing isn't atomic and may only be accessed from the main thread.

View file

@ -571,7 +571,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (auto control{ weakThis.get() })
{
const HANDLE chainHandle = reinterpret_cast<HANDLE>(control->_core.SwapChainHandle());
// TODO! very good chance we leak this handle
const HANDLE chainHandle = reinterpret_cast<HANDLE>(control->_contentProc ?
control->_contentProc.RequestSwapChainHandle(GetCurrentProcessId()) :
control->_core.SwapChainHandle());
_AttachDxgiSwapChainToXaml(chainHandle);
}
}
@ -659,7 +662,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
_interactivity.Initialize();
_AttachDxgiSwapChainToXaml(reinterpret_cast<HANDLE>(_core.SwapChainHandle()));
// TODO! very good chance we leak this handle
const HANDLE chainHandle = reinterpret_cast<HANDLE>(_contentProc ?
_contentProc.RequestSwapChainHandle(GetCurrentProcessId()) :
_core.SwapChainHandle());
_AttachDxgiSwapChainToXaml(chainHandle);
// Tell the DX Engine to notify us when the swap chain changes. We do
// this after we initially set the swapchain so as to avoid unnecessary

View file

@ -83,7 +83,7 @@ static bool _messageIsAltKeyup(const MSG& message)
return (message.message == WM_KEYUP || message.message == WM_SYSKEYUP) && message.wParam == VK_MENU;
}
static bool checkIfContentProcess(winrt::guid& contentProcessGuid)
static bool checkIfContentProcess(winrt::guid& contentProcessGuid, HANDLE& eventHandle)
{
std::vector<std::wstring> args;
@ -101,7 +101,9 @@ static bool checkIfContentProcess(winrt::guid& contentProcessGuid)
}
}
}
if (args.size() > 2 && args.at(1) == L"--content")
if (args.size() == 5 &&
args.at(1) == L"--content" &&
args.at(3) == L"--signal")
{
auto& guidString{ args.at(2) };
auto canConvert = guidString.length() == 38 && guidString.front() == '{' && guidString.back() == '}';
@ -110,6 +112,10 @@ static bool checkIfContentProcess(winrt::guid& contentProcessGuid)
GUID result{};
THROW_IF_FAILED(IIDFromString(guidString.c_str(), &result));
contentProcessGuid = result;
eventHandle = reinterpret_cast<HANDLE>(wcstoul(args.at(4).c_str(),
nullptr /*endptr*/,
16 /*base*/));
return true;
}
}
@ -157,7 +163,7 @@ private:
winrt::guid _guid;
};
static void doContentProcessThing(const winrt::guid& contentProcessGuid)
static void doContentProcessThing(const winrt::guid& contentProcessGuid, const HANDLE& eventHandle)
{
// !! LOAD BEARING !! - important to be a MTA
winrt::init_apartment();
@ -169,6 +175,11 @@ static void doContentProcessThing(const winrt::guid& contentProcessGuid)
REGCLS_MULTIPLEUSE,
&registrationHostClass));
// Signal the event handle that was passed to us that we're now set up and
// ready to go.
SetEvent(eventHandle);
CloseHandle(eventHandle);
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] { return dtored; });
}
@ -197,9 +208,10 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
EnsureNativeArchitecture();
winrt::guid contentProcessGuid{};
if (checkIfContentProcess(contentProcessGuid))
HANDLE eventHandle{ INVALID_HANDLE_VALUE };
if (checkIfContentProcess(contentProcessGuid, eventHandle))
{
doContentProcessThing(contentProcessGuid);
doContentProcessThing(contentProcessGuid, eventHandle);
// If we were told to not have a window, exit early. Make sure to use
// ExitProcess to die here. If you try just `return 0`, then
// the XAML app host will crash during teardown. ExitProcess avoids