Proof of concept - the Terminal hosting an OOP TermControl

ONE WEIRD TRICK: I had to
  ```
    copy bin\x64\Debug\TerminalCore\Microsoft.Terminal.Core.winmd src\cascadia\CascadiaPackage\bin\x64\Debug\AppX
  ```
  to make this work right. I'll work on the packaging next. Weird that we never
  needed that one before?

  This doesn't work for multiple tabs, or for splits, or debug tap, and I once
  had it just crash and disappear randomly.

  But it did work _once_.
This commit is contained in:
Mike Griese 2021-08-25 10:29:56 -05:00
parent 5c17603a94
commit 015e3211fc
5 changed files with 259 additions and 38 deletions

View file

@ -56,39 +56,41 @@ namespace winrt::TerminalApp::implementation
// - existingConnection: An optional connection that is already established to a PTY
// for this tab to host instead of creating one.
// If not defined, the tab will create the connection.
HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection)
HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs,
TerminalConnection::ITerminalConnection /*existingConnection*/)
try
{
const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) };
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
_CreateNewTabWithProfileAndSettings(profile, settings, existingConnection);
// _CreateNewTabWithProfileAndSettings(profile, settings, existingConnection);
_CreateTabWithContent(profile, settings);
const uint32_t tabCount = _tabs.Size();
const bool usedManualProfile = (newTerminalArgs != nullptr) &&
(newTerminalArgs.ProfileIndex() != nullptr ||
newTerminalArgs.Profile().empty());
// const uint32_t tabCount = _tabs.Size();
// const bool usedManualProfile = (newTerminalArgs != nullptr) &&
// (newTerminalArgs.ProfileIndex() != nullptr ||
// newTerminalArgs.Profile().empty());
// Lookup the name of the color scheme used by this profile.
const auto scheme = _settings.GetColorSchemeForProfile(profile);
// If they explicitly specified `null` as the scheme (indicating _no_ scheme), log
// that as the empty string.
const auto schemeName = scheme ? scheme.Name() : L"\0";
// // Lookup the name of the color scheme used by this profile.
// const auto scheme = _settings.GetColorSchemeForProfile(profile);
// // If they explicitly specified `null` as the scheme (indicating _no_ scheme), log
// // that as the empty string.
// const auto schemeName = scheme ? scheme.Name() : L"\0";
TraceLoggingWrite(
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
"TabInformation",
TraceLoggingDescription("Event emitted upon new tab creation in TerminalApp"),
TraceLoggingUInt32(1u, "EventVer", "Version of this event"),
TraceLoggingUInt32(tabCount, "TabCount", "Count of tabs currently opened in TerminalApp"),
TraceLoggingBool(usedManualProfile, "ProfileSpecified", "Whether the new tab specified a profile explicitly"),
TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The GUID of the profile spawned in the new tab"),
TraceLoggingBool(settings.DefaultSettings().UseAcrylic(), "UseAcrylic", "The acrylic preference from the settings"),
TraceLoggingFloat64(settings.DefaultSettings().TintOpacity(), "TintOpacity", "Opacity preference from the settings"),
TraceLoggingWideString(settings.DefaultSettings().FontFace().c_str(), "FontFace", "Font face chosen in the settings"),
TraceLoggingWideString(schemeName.data(), "SchemeName", "Color scheme set in the settings"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
// TraceLoggingWrite(
// g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
// "TabInformation",
// TraceLoggingDescription("Event emitted upon new tab creation in TerminalApp"),
// TraceLoggingUInt32(1u, "EventVer", "Version of this event"),
// TraceLoggingUInt32(tabCount, "TabCount", "Count of tabs currently opened in TerminalApp"),
// TraceLoggingBool(usedManualProfile, "ProfileSpecified", "Whether the new tab specified a profile explicitly"),
// TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The GUID of the profile spawned in the new tab"),
// TraceLoggingBool(settings.DefaultSettings().UseAcrylic(), "UseAcrylic", "The acrylic preference from the settings"),
// TraceLoggingFloat64(settings.DefaultSettings().TintOpacity(), "TintOpacity", "Opacity preference from the settings"),
// TraceLoggingWideString(settings.DefaultSettings().FontFace().c_str(), "FontFace", "Font face chosen in the settings"),
// TraceLoggingWideString(schemeName.data(), "SchemeName", "Color scheme set in the settings"),
// TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
// TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
return S_OK;
}
@ -228,6 +230,97 @@ namespace winrt::TerminalApp::implementation
_InitializeTab(newTabImpl);
}
static wil::unique_process_information _createHostClassProcess(const winrt::guid& g)
{
auto guidStr{ ::Microsoft::Console::Utils::GuidToString(g) };
// 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;
auto succeeded = CreateProcessW(
nullptr,
commandline.data(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
true, // bInheritHandles
CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // startingDirectory
&siOne, // lpStartupInfo
&piOne // lpProcessInformation
);
THROW_IF_WIN32_BOOL_FALSE(succeeded);
// Wait for the child process to signal that they're ready.
WaitForSingleObject(ev.get(), INFINITE);
return std::move(piOne);
}
winrt::fire_and_forget TerminalPage::_CreateTabWithContent(Profile profile,
TerminalSettingsCreateResult settings)
{
// Capture calling context.
winrt::apartment_context ui_thread;
winrt::guid contentGuid{ ::Microsoft::Console::Utils::CreateGuid() };
co_await winrt::resume_background();
// Spawn a wt.exe, with the guid on the commandline
auto piContentProcess{ _createHostClassProcess(contentGuid) };
// THIS MUST TAKE PLACE AFTER _createHostClassProcess.
// * If we're creating a new OOP control, _createHostClassProcess will
// spawn the process that will actually host the ContentProcess
// object.
// * If we're attaching, then that process already exists.
ContentProcess content{ nullptr };
try
{
content = create_instance<ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
}
catch (winrt::hresult_error hr)
{
// _writeToLog(L"CreateInstance the ContentProces object");
// _writeToLog(fmt::format(L" HR ({}): {}", hr.code(), hr.message().c_str()));
co_return; // be sure to co_return or we'll fall through to the part where we clear the log
}
if (content == nullptr)
{
// _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{ _CreateConnectionInfoFromSettings(profile, settings.DefaultSettings()) };
// TODO! how do we init the content proc with the focused/unfocused pair correctly?
if (!content.Initialize(settings.DefaultSettings(), connectInfo))
{
// _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
}
// Switch back to the UI thread.
co_await ui_thread;
// Create the XAML control that will be attached to the content process.
// We're not passing in a connection, because the contentGuid will be used instead.
auto term = _InitControl(settings, contentGuid);
auto newTabImpl = winrt::make_self<TerminalTab>(profile, term);
_RegisterTerminalEvents(term);
_InitializeTab(newTabImpl);
co_return;
}
// Method Description:
// - Creates a new tab with the given settings. If the tab bar is not being
// currently displayed, it will be shown.
@ -235,7 +328,9 @@ namespace winrt::TerminalApp::implementation
// - profile: profile settings for this connection
// - settings: the TerminalSettings object to use to create the TerminalControl with.
// - existingConnection: optionally receives a connection from the outside world instead of attempting to create one
void TerminalPage::_CreateNewTabWithProfileAndSettings(const Profile& profile, const TerminalSettingsCreateResult& settings, TerminalConnection::ITerminalConnection existingConnection)
void TerminalPage::_CreateNewTabWithProfileAndSettings(const Profile& profile,
const TerminalSettingsCreateResult& settings,
TerminalConnection::ITerminalConnection existingConnection)
{
// Initialize the new tab
// Create a connection based on the values in our settings object if we weren't given one.
@ -360,16 +455,18 @@ namespace winrt::TerminalApp::implementation
settingsCreateResult.DefaultSettings().StartingDirectory(workingDirectory);
}
_CreateNewTabWithProfileAndSettings(profile, settingsCreateResult);
_CreateTabWithContent(profile, settingsCreateResult);
const auto runtimeTabText{ tab.GetTabText() };
if (!runtimeTabText.empty())
{
if (auto newTab{ _GetFocusedTabImpl() })
{
newTab->SetTabText(runtimeTabText);
}
}
// TODO!
//
// const auto runtimeTabText{ tab.GetTabText() };
// if (!runtimeTabText.empty())
// {
// if (auto newTab{ _GetFocusedTabImpl() })
// {
// newTab->SetTabText(runtimeTabText);
// }
// }
}
}
CATCH_LOG();

View file

@ -461,7 +461,12 @@ namespace winrt::TerminalApp::implementation
// GH#6586: now that we're done processing all startup commands,
// focus the active control. This will work as expected for both
// commandline invocations and for `wt` action invocations.
_GetActiveControl().Focus(FocusState::Programmatic);
if (auto activeControl{ _GetActiveControl() })
{
// TODO! this doesn't work during startup anymore now that
// tabs are made on a BG thread
activeControl.Focus(FocusState::Programmatic);
}
}
if (initial)
{
@ -898,6 +903,103 @@ namespace winrt::TerminalApp::implementation
return connection;
}
// Method Description:
// - Creates a new connection based on the profile settings
// Arguments:
// - the profile we want the settings from
// - the terminal settings
// Return value:
// - the desired connection
TerminalConnection::ConnectionInformation TerminalPage::_CreateConnectionInfoFromSettings(Profile profile,
const TerminalSettings& settings)
{
if (!profile)
{
// Use the default profile if we didn't get one as an argument.
profile = _settings.FindProfile(_settings.GlobalSettings().DefaultProfile());
}
winrt::guid connectionType = profile.ConnectionType();
winrt::guid sessionGuid{};
Windows::Foundation::Collections::ValueSet connectionSettings{ nullptr };
winrt::hstring className;
if (connectionType == TerminalConnection::AzureConnection::ConnectionType() &&
TerminalConnection::AzureConnection::IsAzureConnectionAvailable())
{
// TODO GH#4661: Replace this with directly using the AzCon when our VT is better
std::filesystem::path azBridgePath{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
azBridgePath.replace_filename(L"TerminalAzBridge.exe");
className = winrt::name_of<TerminalConnection::ConptyConnection>();
connectionSettings = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.wstring(),
L".",
L"Azure",
nullptr,
::base::saturated_cast<uint32_t>(settings.InitialRows()),
::base::saturated_cast<uint32_t>(settings.InitialCols()),
winrt::guid());
}
else
{
// profile is guaranteed to exist here
std::wstring guidWString = Utils::GuidToString(profile.Guid());
StringMap envMap{};
envMap.Insert(L"WT_PROFILE_ID", guidWString);
envMap.Insert(L"WSLENV", L"WT_PROFILE_ID");
// Update the path to be relative to whatever our CWD is.
//
// Refer to the examples in
// https://en.cppreference.com/w/cpp/filesystem/path/append
//
// We need to do this here, to ensure we tell the ConptyConnection
// the correct starting path. If we're being invoked from another
// terminal instance (e.g. wt -w 0 -d .), then we have switched our
// CWD to the provided path. We should treat the StartingDirectory
// as relative to the current CWD.
//
// The connection must be informed of the current CWD on
// construction, because the connection might not spawn the child
// process until later, on another thread, after we've already
// restored the CWD to it's original value.
winrt::hstring newWorkingDirectory{ settings.StartingDirectory() };
if (newWorkingDirectory.size() <= 1 ||
!(newWorkingDirectory[0] == L'~' || newWorkingDirectory[0] == L'/'))
{ // We only want to resolve the new WD against the CWD if it doesn't look like a Linux path (see GH#592)
std::wstring cwdString{ wil::GetCurrentDirectoryW<std::wstring>() };
std::filesystem::path cwd{ cwdString };
cwd /= settings.StartingDirectory().c_str();
newWorkingDirectory = winrt::hstring{ cwd.wstring() };
}
className = winrt::name_of<TerminalConnection::ConptyConnection>();
connectionSettings = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
newWorkingDirectory,
settings.StartingTitle(),
envMap.GetView(),
::base::saturated_cast<uint32_t>(settings.InitialRows()),
::base::saturated_cast<uint32_t>(settings.InitialCols()),
winrt::guid());
// sessionGuid = conhostConn.Guid();
}
// TraceLoggingWrite(
// g_hTerminalAppProvider,
// "ConnectionCreated",
// TraceLoggingDescription("Event emitted upon the creation of a connection"),
// TraceLoggingGuid(connectionType, "ConnectionTypeGuid", "The type of the connection"),
// TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The profile's GUID"),
// TraceLoggingGuid(sessionGuid, "SessionGuid", "The WT_SESSION's GUID"),
// TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
// TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
return TerminalConnection::ConnectionInformation(className, connectionSettings);
}
// Method Description:
// - Called when the settings button is clicked. Launches a background
// thread to open the settings file in the default JSON editor.
@ -1996,6 +2098,17 @@ namespace winrt::TerminalApp::implementation
return term;
}
TermControl TerminalPage::_InitControl(const TerminalSettingsCreateResult& settings, const winrt::guid& contentGuid)
{
// Give term control a child of the settings so that any overrides go in the child
// This way, when we do a settings reload we just update the parent and the overrides remain
const auto child = TerminalSettings::CreateWithParent(settings);
TermControl term{ contentGuid, child.DefaultSettings(), nullptr };
term.UnfocusedAppearance(child.UnfocusedSettings()); // It is okay for the unfocused settings to be null
return term;
}
// Method Description:
// - Hook up keybindings, and refresh the UI of the terminal.

View file

@ -190,7 +190,9 @@ namespace winrt::TerminalApp::implementation
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane);
void _CreateNewTabWithProfileAndSettings(const Microsoft::Terminal::Settings::Model::Profile& profile, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
winrt::fire_and_forget _CreateTabWithContent(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings);
winrt::Microsoft::Terminal::TerminalConnection::ConnectionInformation _CreateConnectionInfoFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, const Microsoft::Terminal::Settings::Model::TerminalSettings& settings);
winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
@ -310,6 +312,9 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection);
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::guid& contentGuid);
void _RefreshUIForSettingsReload();
void _SetNonClientAreaColors(const Windows::UI::Color& selectedTabColor);

View file

@ -595,7 +595,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_desiredFont = { _actualFont };
// Update the terminal core with its new Core settings
_terminal->UpdateSettings(_settings);
// Are you seeing a crash specifically on this line?
Core::ICoreSettings coreSettings{ _settings };
// Then you might not have Microsoft.Terminal.Core.winmd in the package
// or SxS with windowsterminal.exe!
_terminal->UpdateSettings(coreSettings);
if (!_initializedTerminal)
{

View file

@ -22,7 +22,9 @@ namespace Microsoft.Terminal.Settings.Model
// and pass along the Core properties to the terminal core.
[default_interface]
runtimeclass TerminalSettings : Microsoft.Terminal.Core.ICoreSettings,
Microsoft.Terminal.Control.IControlSettings
Microsoft.Terminal.Control.IControlSettings,
Microsoft.Terminal.Core.ICoreAppearance,
Microsoft.Terminal.Control.IControlAppearance
{
TerminalSettings();