I think this merges the-whole-thing into this branch. The remote control doesn't render right, but I think that's because the actual HEAD of all this work is in connection-factory

This commit is contained in:
Mike Griese 2021-08-10 10:25:29 -05:00
parent 9f2d40614b
commit d8dcb6f570
12 changed files with 397 additions and 20 deletions

View file

@ -3,9 +3,11 @@
#include "pch.h"
#include "MyPage.h"
#include "MySettings.h"
#include <LibraryResources.h>
#include "MyPage.g.cpp"
#include "MySettings.h"
#include "..\..\..\src\cascadia\UnitTests_Control\MockControlSettings.h"
#include "..\..\..\src\types\inc\utils.hpp"
using namespace std::chrono_literals;
using namespace winrt::Microsoft::Terminal;
@ -26,24 +28,151 @@ namespace winrt::SampleApp::implementation
void MyPage::Create()
{
auto settings = winrt::make_self<implementation::MySettings>();
TerminalConnection::EchoConnection conn{};
auto settings = winrt::make_self<ControlUnitTests::MockControlSettings>();
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted in-proc...",
winrt::hstring{},
L"",
nullptr,
32,
80,
winrt::guid()) };
// "Microsoft.Terminal.TerminalConnection.ConptyConnection"
winrt::hstring myClass{ winrt::name_of<TerminalConnection::ConptyConnection>() };
TerminalConnection::ConnectionInformation connectInfo{ myClass, connectionSettings };
TerminalConnection::ITerminalConnection conn{ TerminalConnection::ConnectionInformation::CreateConnection(connectInfo) };
Control::TermControl control{ *settings, conn };
InProcContent().Children().Append(control);
// Once the control loads (and not before that), write some text for debugging:
control.Initialized([conn](auto&&, auto&&) {
conn.WriteInput(L"This TermControl is hosted in-proc...");
});
}
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) };
STARTUPINFO siOne{ 0 };
siOne.cb = sizeof(STARTUPINFOW);
wil::unique_process_information piOne;
auto succeeded = CreateProcessW(
nullptr,
commandline.data(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
false, // bInheritHandles
CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // startingDirectory
&siOne, // lpStartupInfo
&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)
return std::move(piOne);
}
winrt::fire_and_forget MyPage::CreateClicked(const IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& eventArgs)
{
auto guidString = GuidInput().Text();
// Capture calling context.
winrt::apartment_context ui_thread;
co_await winrt::resume_background();
auto canConvert = guidString.size() == 38 && guidString.front() == '{' && guidString.back() == '}';
bool attached = false;
winrt::guid contentGuid{ ::Microsoft::Console::Utils::CreateGuid() };
if (canConvert)
{
GUID result{};
if (SUCCEEDED(IIDFromString(guidString.c_str(), &result)))
{
contentGuid = result;
attached = true;
}
}
if (!attached)
{
// 2. Spawn a Server.exe, with the guid on the commandline
auto piContent{ std::move(_createHostClassProcess(contentGuid)) };
}
Control::ContentProcess content = create_instance<Control::ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
TerminalConnection::ITerminalConnection conn{ nullptr };
Control::IControlSettings settings{ nullptr };
settings = *winrt::make_self<implementation::MySettings>();
if (!attached)
{
conn = TerminalConnection::EchoConnection{};
// settings = *winrt::make_self<implementation::MySettings>();
content.Initialize(settings, conn);
}
// Switch back to the UI thread.
co_await ui_thread;
Control::TermControl control{ contentGuid, settings, conn };
OutOfProcContent().Children().Append(control);
if (!attached)
{
auto guidStr{ ::Microsoft::Console::Utils::GuidToString(contentGuid) };
GuidInput().Text(guidStr);
}
}
/*winrt::fire_and_forget MyPage::_attachToContent(winrt::guid contentGuid)
{
Control::ContentProcess content = create_instance<Control::ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
}*/
winrt::fire_and_forget MyPage::CreateOutOfProcTerminal()
{
// 1. Generate a GUID.
winrt::guid contentGuid{ ::Microsoft::Console::Utils::CreateGuid() };
// Capture calling context.
winrt::apartment_context ui_thread;
co_await winrt::resume_background();
// 2. Spawn a Server.exe, with the guid on the commandline
auto piContent{ std::move(_createHostClassProcess(contentGuid)) };
Control::ContentProcess content = create_instance<Control::ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
TerminalConnection::EchoConnection conn{};
auto settings = winrt::make_self<implementation::MySettings>();
Control::IControlSettings s = *settings;
if (s)
{
content.Initialize(s, conn);
// Switch back to the UI thread.
co_await ui_thread;
Control::TermControl control{ contentGuid, s, conn };
OutOfProcContent().Children().Append(control);
}
}
// Method Description:

View file

@ -16,6 +16,9 @@ namespace winrt::SampleApp::implementation
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

View file

@ -23,7 +23,9 @@
<TextBox x:Name="GuidInput"
Width="400"
PlaceholderText="{}{guid here}" />
<Button Grid.Row="0">
<Button x:Name="CreateOopControl"
Grid.Row="0"
Tapped="CreateClicked">
Create
</Button>
@ -53,8 +55,6 @@
VerticalAlignment="Stretch"
Background="#0000ff" />
</Grid>
</Grid>

View file

@ -10,6 +10,9 @@ Licensed under the MIT license.
#include <conattrs.hpp>
#include "MySettings.g.h"
using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
namespace winrt::SampleApp::implementation
{
struct MySettings : MySettingsT<MySettings>
@ -41,6 +44,7 @@ namespace winrt::SampleApp::implementation
winrt::Microsoft::Terminal::Core::ICoreAppearance UnfocusedAppearance() { return {}; };
WINRT_PROPERTY(bool, TrimBlockSelection, false);
WINRT_PROPERTY(bool, DetectURLs, true);
// ------------------------ End of Core Settings -----------------------
WINRT_PROPERTY(winrt::hstring, ProfileName);
@ -78,7 +82,8 @@ namespace winrt::SampleApp::implementation
WINRT_PROPERTY(winrt::hstring, PixelShaderPath);
WINRT_PROPERTY(bool, DetectURLs, true);
WINRT_PROPERTY(IFontFeatureMap, FontFeatures);
WINRT_PROPERTY(IFontAxesMap, FontAxes);
private:
std::array<winrt::Microsoft::Terminal::Core::Color, COLOR_TABLE_SIZE> _ColorTable;

View file

@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ContentProcess.h"
#include "ContentProcess.g.cpp"
namespace winrt::Microsoft::Terminal::Control::implementation
{
ContentProcess::ContentProcess() {}
void ContentProcess::Initialize(Control::IControlSettings settings, TerminalConnection::ITerminalConnection connection)
{
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, connection);
}
Control::ControlInteractivity ContentProcess::GetInteractivity()
{
return _interactivity;
}
uint64_t ContentProcess::RequestSwapChainHandle(const uint64_t pid)
{
auto ourPid = GetCurrentProcessId();
HANDLE ourHandle = reinterpret_cast<HANDLE>(_interactivity.Core().SwapChainHandle());
if (pid == ourPid)
{
return reinterpret_cast<uint64_t>(ourHandle);
}
wil::unique_handle hWindowProcess{ OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
static_cast<DWORD>(pid)) };
if (hWindowProcess.get() == nullptr)
{
const auto gle = GetLastError();
gle;
// TODO! tracelog an error here
return 0;
}
HANDLE theirHandle{ nullptr };
BOOL success = DuplicateHandle(GetCurrentProcess(),
ourHandle,
hWindowProcess.get(),
&theirHandle,
0,
FALSE,
DUPLICATE_SAME_ACCESS);
if (!success)
{
const auto gle = GetLastError();
gle;
// TODO! tracelog an error here
return 0;
}
return reinterpret_cast<uint64_t>(theirHandle);
}
}

View file

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "ContentProcess.g.h"
#include "ControlInteractivity.h"
namespace winrt::Microsoft::Terminal::Control::implementation
{
struct ContentProcess : ContentProcessT<ContentProcess>
{
ContentProcess();
void Initialize(Control::IControlSettings settings, TerminalConnection::ITerminalConnection connection);
Control::ControlInteractivity GetInteractivity();
uint64_t RequestSwapChainHandle(const uint64_t pid);
private:
Control::ControlInteractivity _interactivity{ nullptr };
};
}
namespace winrt::Microsoft::Terminal::Control::factory_implementation
{
BASIC_FACTORY(ContentProcess);
}

View file

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "ControlInteractivity.idl";
namespace Microsoft.Terminal.Control
{
runtimeclass ContentProcess {
ContentProcess();
void Initialize(IControlSettings settings,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
ControlInteractivity GetInteractivity();
UInt64 RequestSwapChainHandle(UInt64 pid);
};
}

View file

@ -50,7 +50,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
TermControl::TermControl(IControlSettings settings,
TerminalConnection::ITerminalConnection connection) :
TermControl(winrt::guid{}, settings, connection) {}
TermControl::TermControl(winrt::guid contentGuid,
IControlSettings settings,
TerminalConnection::ITerminalConnection connection) :
_initializedTerminal{ false },
_settings{ settings },
_closing{ false },
_isInternalScrollBarUpdate{ false },
_autoScrollVelocity{ 0 },
_autoScrollingPointerPoint{ std::nullopt },
@ -62,7 +69,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
InitializeComponent();
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, connection);
if (contentGuid != winrt::guid{})
{
_contentProc = create_instance<Control::ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
if (_contentProc != nullptr)
{
_interactivity = _contentProc.GetInteractivity();
}
}
if (_interactivity == nullptr)
{
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, connection);
}
_core = _interactivity.Core();
// These events might all be triggered by the connection, but that

View file

@ -25,6 +25,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
struct TermControl : TermControlT<TermControl>
{
TermControl(winrt::guid contentGuid, IControlSettings settings, TerminalConnection::ITerminalConnection connection);
TermControl(IControlSettings settings, TerminalConnection::ITerminalConnection connection);
winrt::fire_and_forget UpdateSettings();
@ -145,6 +146,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Control::TermControlAutomationPeer _automationPeer{ nullptr };
Control::ControlInteractivity _interactivity{ nullptr };
Control::ControlCore _core{ nullptr };
Control::ContentProcess _contentProc{ nullptr };
winrt::com_ptr<SearchBoxControl> _searchBox;

View file

@ -15,6 +15,10 @@ namespace Microsoft.Terminal.Control
IMouseWheelListener,
ICoreState
{
TermControl(Guid contentGuid,
IControlSettings settings,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
TermControl(IControlSettings settings,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);

View file

@ -28,6 +28,9 @@
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="ContentProcess.h">
<DependentUpon>ContentProcess.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ControlCore.h">
<DependentUpon>ControlCore.idl</DependentUpon>
</ClInclude>
@ -65,6 +68,9 @@
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ContentProcess.cpp">
<DependentUpon>ContentProcess.idl</DependentUpon>
</ClCompile>
<ClCompile Include="ControlCore.cpp">
<DependentUpon>ControlCore.idl</DependentUpon>
</ClCompile>
@ -101,6 +107,7 @@
</ItemGroup>
<!-- ========================= idl Files ======================== -->
<ItemGroup>
<Midl Include="ContentProcess.idl" />
<Midl Include="ControlCore.idl" />
<Midl Include="ControlInteractivity.idl" />
<Midl Include="ICoreState.idl" />

View file

@ -83,6 +83,96 @@ 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)
{
std::vector<std::wstring> args;
if (auto commandline{ GetCommandLineW() })
{
int argc = 0;
// Get the argv, and turn them into a hstring array to pass to the app.
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);
}
}
}
if (args.size() > 2 && args.at(1) == L"--content")
{
auto& guidString{ args.at(2) };
auto canConvert = guidString.length() == 38 && guidString.front() == '{' && guidString.back() == '}';
if (canConvert)
{
GUID result{};
THROW_IF_FAILED(IIDFromString(guidString.c_str(), &result));
contentProcessGuid = result;
return true;
}
}
return false;
}
std::mutex m;
std::condition_variable cv;
bool dtored = false;
winrt::weak_ref<winrt::Microsoft::Terminal::Control::ContentProcess> g_weak{ nullptr };
struct HostClassFactory : implements<HostClassFactory, IClassFactory>
{
HostClassFactory(winrt::guid g) :
_guid{ g } {};
HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) noexcept final
{
*result = nullptr;
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
if (!g_weak)
{
winrt::Microsoft::Terminal::Control::ContentProcess strong{}; // = winrt::make<winrt::Microsoft::Terminal::Control::ContentProcess>();
winrt::weak_ref<winrt::Microsoft::Terminal::Control::ContentProcess> weak{ strong };
g_weak = weak;
return strong.as(iid, result);
}
else
{
auto strong = g_weak.get();
return strong.as(iid, result);
}
}
HRESULT __stdcall LockServer(BOOL) noexcept final
{
return S_OK;
}
private:
winrt::guid _guid;
};
static void doContentProcessThing(const winrt::guid& contentProcessGuid)
{
// !! LOAD BEARING !! - important to be a MTA
winrt::init_apartment();
DWORD registrationHostClass{};
check_hresult(CoRegisterClassObject(contentProcessGuid,
make<HostClassFactory>(contentProcessGuid).get(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&registrationHostClass));
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] { return dtored; });
}
int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
{
TraceLoggingRegister(g_hWindowsTerminalProvider);
@ -106,6 +196,17 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
// should choose and install the correct one from the bundle.
EnsureNativeArchitecture();
winrt::guid contentProcessGuid{};
if (checkIfContentProcess(contentProcessGuid))
{
doContentProcessThing(contentProcessGuid);
// 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
// that.
ExitProcess(0);
}
// Make sure to call this so we get WM_POINTER messages.
EnableMouseInPointer(true);