Compare commits

...

24 commits

Author SHA1 Message Date
Mike Griese 5c17603a94 more cleanup 2021-08-25 08:25:20 -05:00
Mike Griese 354e4b00a3 BODGY, don't raise an event on destruction, that would be too... 2021-08-24 16:11:14 -05:00
Mike Griese 8707c03715 simplify the interface here a bit 2021-08-24 15:18:36 -05:00
Mike Griese 31b2763be5 move the content process main to another file as well 2021-08-24 15:07:26 -05:00
Mike Griese a000d81fa9 Move the content process handling to a separate file in TermControl project 2021-08-24 12:33:53 -05:00
Mike Griese d36a08186c add a dialog internally to the bounds of the control, not outside of the control 2021-08-24 12:20:36 -05:00
Mike Griese 3b1bb455d8 some cleanup 2021-08-24 10:40:30 -05:00
Mike Griese 901bc78966 Merge remote-tracking branch 'origin/main' into dev/migrie/oop/infinity-war 2021-08-24 10:05:34 -05:00
Mike Griese 4150609b42 This doesn't immediately crash, but it does crash when you start asking for the actual text ranges. That's not what you want. 2021-08-16 13:04:55 -05:00
Mike Griese d5920a8c69 Revert "This too didn't work. Creating the XAML thing not on the XAML thing isn't going to work"
This reverts commit fd364db727.
2021-08-16 11:17:16 -05:00
Mike Griese fd364db727 This too didn't work. Creating the XAML thing not on the XAML thing isn't going to work 2021-08-16 11:17:05 -05:00
Mike Griese 3a0fbd9f59 Revert "this was the part where I realized I dun goofed"
This reverts commit 64533c838a.
2021-08-16 10:18:54 -05:00
Mike Griese 64533c838a this was the part where I realized I dun goofed 2021-08-16 10:18:47 -05:00
Mike Griese aa6b08118f The sample app has a hard time loading TermControl resources so we're just going to disable this for now 2021-08-16 10:18:16 -05:00
Mike Griese b0b44410c6 a fix for a crash when closing 2021-08-16 10:17:41 -05:00
Mike Griese b541179333 This works to kill the content and have the app live 2021-08-12 12:55:03 -05:00
Mike Griese 56f1223dc5 Add a kill button for manually killing the content 2021-08-12 11:28:01 -05:00
Mike Griese 88d974280d You know, there's 0% chance that this is the right pattern for this, but it _works_ 2021-08-12 10:32:24 -05:00
Mike Griese d84a31801a some short-circuits for these inits, to make them more stable 2021-08-12 10:00:57 -05:00
Mike Griese 6910677a11 A close button, and more logging 2021-08-12 09:47:50 -05:00
Mike Griese 9331cc8e59 Add a signal that the content can use to tell the window it's ready 2021-08-12 08:45:43 -05:00
Mike Griese 2f64db2765 Some comments because everything is hard 2021-08-10 16:25:44 -05:00
Mike Griese 4cc3d39de9 I believe this merges the buisness of connection-factory, though there are many issues. 2021-08-10 16:17:27 -05:00
Mike Griese d8dcb6f570 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 2021-08-10 10:25:29 -05:00
27 changed files with 952 additions and 48 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,7 +28,7 @@ namespace winrt::SampleApp::implementation
void MyPage::Create()
{
auto settings = winrt::make_self<implementation::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{},
@ -44,6 +46,212 @@ namespace winrt::SampleApp::implementation
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) };
// 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 MyPage::_writeToLog(std::wstring_view str)
{
winrt::hstring copy{ str };
// Switch back to the UI thread.
co_await resume_foreground(Dispatcher());
winrt::WUX::Controls::TextBlock block;
block.Text(copy);
Log().Children().Append(block);
}
winrt::fire_and_forget MyPage::CreateClicked(const IInspectable& sender,
const WUX::Input::TappedRoutedEventArgs& eventArgs)
{
auto guidString = GuidInput().Text();
// Capture calling context.
winrt::apartment_context ui_thread;
auto canConvert = guidString.size() == 38 &&
guidString.front() == '{' &&
guidString.back() == '}';
bool tryingToAttach = false;
winrt::guid contentGuid{ ::Microsoft::Console::Utils::CreateGuid() };
if (canConvert)
{
GUID result{};
if (SUCCEEDED(IIDFromString(guidString.c_str(), &result)))
{
contentGuid = result;
tryingToAttach = true;
}
}
_writeToLog(tryingToAttach ? L"Attaching to existing content process" : L"Creating new content process");
co_await winrt::resume_background();
if (!tryingToAttach)
{
// Spawn a wt.exe, with the guid on the commandline
piContentProcess = std::move(_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.
Control::ContentProcess content{nullptr};
try
{
content = create_instance<Control::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{ nullptr };
Control::IControlSettings settings{ *winrt::make_self<implementation::MySettings>() };
// When creating a terminal for the first time, pass it a connection
// info
//
// otherwise, when attaching to an existing one, just pass null, because
// we don't need the connection info.
if (!tryingToAttach)
{
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted out-of-proc...",
winrt::hstring{},
L"",
nullptr,
32,
80,
winrt::guid()) };
// "Microsoft.Terminal.TerminalConnection.ConptyConnection"
winrt::hstring myClass{ winrt::name_of<TerminalConnection::ConptyConnection>() };
connectInfo = TerminalConnection::ConnectionInformation(myClass, connectionSettings);
if (!content.Initialize(settings, 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
}
}
else
{
// If we're attaching, we don't really need to do anything special.
}
// 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.
Control::TermControl control{ contentGuid, settings, nullptr };
control.RaiseNotice([this](auto&&, auto& args) {
_writeToLog(L"Content process died, probably.");
_writeToLog(args.Message());
OutOfProcContent().Children().Clear();
GuidInput().Text(L"");
if (piContentProcess.hProcess)
{
piContentProcess.reset();
}
});
control.ConnectionStateChanged([this, control](auto&&, auto&) {
const auto newConnectionState = control.ConnectionState();
if (newConnectionState == TerminalConnection::ConnectionState::Closed)
{
_writeToLog(L"Connection was closed");
OutOfProcContent().Children().Clear();
GuidInput().Text(L"");
if (piContentProcess.hProcess)
{
piContentProcess.reset();
}
}
});
Log().Children().Clear();
OutOfProcContent().Children().Append(control);
if (!tryingToAttach)
{
auto guidStr{ ::Microsoft::Console::Utils::GuidToString(contentGuid) };
GuidInput().Text(guidStr);
}
}
void MyPage::CloseClicked(const IInspectable& /*sender*/,
const WUX::Input::TappedRoutedEventArgs& /*eventArgs*/)
{
OutOfProcContent().Children().Clear();
GuidInput().Text(L"");
if (piContentProcess.hProcess)
{
piContentProcess.reset();
}
}
void MyPage::KillClicked(const IInspectable& /*sender*/,
const WUX::Input::TappedRoutedEventArgs& /*eventArgs*/)
{
if (piContentProcess.hProcess)
{
TerminateProcess(piContentProcess.hProcess, (UINT)-1);
piContentProcess.reset();
}
}
// Method Description:

View file

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

View file

@ -23,9 +23,23 @@
<TextBox x:Name="GuidInput"
Width="400"
PlaceholderText="{}{guid here}" />
<Button Grid.Row="0">
<Button x:Name="CreateOopControl"
Grid.Row="0"
Tapped="CreateClicked">
Create
</Button>
<Button x:Name="CloseOopControl"
Grid.Row="0"
Margin="4,0,0,0"
Tapped="CloseClicked">
Close
</Button>
<Button x:Name="KillOopControl"
Grid.Row="0"
Margin="4,0,0,0"
Tapped="KillClicked">
Kill
</Button>
</StackPanel>
@ -46,14 +60,26 @@
VerticalAlignment="Stretch"
Background="#ff0000" />
<Grid x:Name="OutOfProcContent"
Grid.Column="1"
Padding="16"
<Grid Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="#0000ff" />
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel x:Name="Log"
Grid.Row="0"
Orientation="Vertical" />
<Grid x:Name="OutOfProcContent"
Grid.Row="1"
Padding="16"
HorizontalAlignment="Stretch"
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,8 @@ namespace winrt::SampleApp::implementation
winrt::Microsoft::Terminal::Core::ICoreAppearance UnfocusedAppearance() { return {}; };
WINRT_PROPERTY(bool, TrimBlockSelection, false);
WINRT_PROPERTY(bool, DetectURLs, true);
WINRT_PROPERTY(bool, IntenseIsBright, true);
// ------------------------ End of Core Settings -----------------------
WINRT_PROPERTY(winrt::hstring, ProfileName);
@ -78,7 +83,10 @@ namespace winrt::SampleApp::implementation
WINRT_PROPERTY(winrt::hstring, PixelShaderPath);
WINRT_PROPERTY(bool, DetectURLs, true);
WINRT_PROPERTY(IFontFeatureMap, FontFeatures);
WINRT_PROPERTY(IFontAxesMap, FontAxes);
WINRT_PROPERTY(bool, IntenseIsBold, true);
private:
std::array<winrt::Microsoft::Terminal::Core::Color, COLOR_TABLE_SIZE> _ColorTable;

View file

@ -36,21 +36,59 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
::IInspectable** raw = reinterpret_cast<::IInspectable**>(pointer);
#pragma warning(pop)
// RoActivateInstance() will try to create an instance of the object,
// who's fully qualified name is the string in Name().
TerminalConnection::ITerminalConnection connection{ nullptr };
// A couple short-circuits, for connections that _we_ implement.
// Sometimes, RoActivateInstance is weird and fails with errors like the
// following
//
// The class has to be activatable. For the Terminal, this is easy
// enough - we're not hosting anything that's not already in our
// manifest, or living as a .dll & .winmd SxS.
//
// When we get to extensions (GH#4000), we may want to revisit.
if (LOG_IF_FAILED(RoActivateInstance(name, raw)))
/*
onecore\com\combase\inc\RegistryKey.hpp(527)\combase.dll!00007FFF75E1F855:
(caller: 00007FFF75D3BC29) LogHr(2) tid(83a8) 800700A1 The specified
path is invalid.
Msg:[StaticNtOpen failed with
path:\REGISTRY\A\{A41685A4-AD85-4C4C-BA5D-A849ADBF3C40}\ActivatableClassId
\REGISTRY\MACHINE\Software\Classes\ActivatableClasses]
...\src\cascadia\TerminalConnection\ConnectionInformation.cpp(47)\TerminalConnection.dll!00007FFEC1381FC5:
(caller: 00007FFEC13810A5) LogHr(1) tid(83a8) 800700A1 The specified
path is invalid.
[...TerminalConnection::implementation::ConnectionInformation::CreateConnection(RoActivateInstance(name,
raw))]
*/
//
// So to avoid those, we'll manually instantiate these
if (info.ClassName() == winrt::name_of<TerminalConnection::ConptyConnection>())
{
return nullptr;
connection = TerminalConnection::ConptyConnection();
}
else if (info.ClassName() == winrt::name_of<TerminalConnection::AzureConnection>())
{
connection = TerminalConnection::AzureConnection();
}
else if (info.ClassName() == winrt::name_of<TerminalConnection::EchoConnection>())
{
connection = TerminalConnection::EchoConnection();
}
else
{
// RoActivateInstance() will try to create an instance of the object,
// who's fully qualified name is the string in Name().
//
// The class has to be activatable. For the Terminal, this is easy
// enough - we're not hosting anything that's not already in our
// manifest, or living as a .dll & .winmd SxS.
//
// When we get to extensions (GH#4000), we may want to revisit.
if (LOG_IF_FAILED(RoActivateInstance(name, raw)))
{
return nullptr;
}
connection = inspectable.try_as<TerminalConnection::ITerminalConnection>();
}
// Now that thing we made, make sure it's actually a ITerminalConnection
if (const auto connection{ inspectable.try_as<TerminalConnection::ITerminalConnection>() })
if (connection)
{
// Initialize it, and return it.
connection.Initialize(info.Settings());

View file

@ -0,0 +1,101 @@
// 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() :
_ourPID{ GetCurrentProcessId() } {}
bool ContentProcess::Initialize(Control::IControlSettings settings,
TerminalConnection::ConnectionInformation connectionInfo)
{
auto conn{ TerminalConnection::ConnectionInformation::CreateConnection(connectionInfo) };
if (conn == nullptr)
{
return false;
}
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, conn);
return true;
}
ContentProcess::~ContentProcess()
{
// DANGER - We're straight up going to EXIT THE ENTIRE PROCESS when we
// get destructed. This eliminates the need to do any sort of
// refcounting 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
// that our job is complete.
ExitProcess(0);
}
Control::ControlInteractivity ContentProcess::GetInteractivity()
{
return _interactivity;
}
uint64_t ContentProcess::GetPID()
{
return _ourPID;
}
// Method Description:
// - Duplicate the swap chain handle to the provided process.
// - If the provided PID is our pid, then great - we don't need to do anything.
// Arguments:
// - callersPid: the PID of the process calling this method.
// Return Value:
// - The value of the swapchain handle in the callers process
// Notes:
// - This is BODGY! We're basically asking to marshal a HANDLE here. WinRT
// has no good mechanism for doing this, so we're doing it by casting the
// value to a uint64_t. In all reality, we _should_ be using a COM
// interface for this, because it can set up the security on these handles
// more appropriately. Fortunately, all we're dealing with is swapchains,
// so the security doesn't matter all that much.
uint64_t ContentProcess::RequestSwapChainHandle(const uint64_t callersPid)
{
auto ourPid = GetCurrentProcessId();
HANDLE ourHandle = reinterpret_cast<HANDLE>(_interactivity.Core().SwapChainHandle());
if (callersPid == ourPid)
{
return reinterpret_cast<uint64_t>(ourHandle);
}
wil::unique_handle hWindowProcess{ OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
static_cast<DWORD>(callersPid)) };
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;
}
// At this point, the handle is now in their process space, with value
// theirHandle
return reinterpret_cast<uint64_t>(theirHandle);
}
}

View file

@ -0,0 +1,32 @@
// 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();
~ContentProcess();
bool Initialize(Control::IControlSettings settings,
TerminalConnection::ConnectionInformation connectionInfo);
Control::ControlInteractivity GetInteractivity();
uint64_t GetPID();
uint64_t RequestSwapChainHandle(const uint64_t pid);
private:
Control::ControlInteractivity _interactivity{ nullptr };
uint64_t _ourPID;
};
}
namespace winrt::Microsoft::Terminal::Control::factory_implementation
{
BASIC_FACTORY(ContentProcess);
}

View file

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

View file

@ -247,14 +247,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
inline bool _IsClosing() const noexcept
{
#ifndef NDEBUG
if (_dispatcher)
{
// _closing isn't atomic and may only be accessed from the main thread.
//
// Though, the unit tests don't actually run in TAEF's main
// thread, so we don't care when we're running in tests.
assert(_inUnitTests || _dispatcher.HasThreadAccess());
}
// // 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.
// //
// // Though, the unit tests don't actually run in TAEF's main
// // thread, so we don't care when we're running in tests.
// assert(_inUnitTests || _dispatcher.HasThreadAccess());
// }
#endif
return _closing;
}

View file

@ -44,6 +44,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_controlPadding = padding;
}
void InteractivityAutomationPeer::SetParentProvider(Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple parentProvider)
{
_parentProvider = parentProvider;
}
// Method Description:
// - Signals the ui automation client that the terminal's selection has
// changed and should be updated
@ -111,7 +116,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
THROW_IF_FAILED(_uiaProvider->RangeFromChild(/* IRawElementProviderSimple */ nullptr,
&returnVal));
const auto parentProvider = this->ProviderFromPeer(*this);
// const auto parentProvider = this->ProviderFromPeer(*this);
const auto parentProvider = _parentProvider;
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
return xutr.as<XamlAutomation::ITextRangeProvider>();
}
@ -121,7 +127,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
UIA::ITextRangeProvider* returnVal;
THROW_IF_FAILED(_uiaProvider->RangeFromPoint({ screenLocation.X, screenLocation.Y }, &returnVal));
const auto parentProvider = this->ProviderFromPeer(*this);
// const auto parentProvider = this->ProviderFromPeer(*this);
const auto parentProvider = _parentProvider;
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
return xutr.as<XamlAutomation::ITextRangeProvider>();
}
@ -131,7 +138,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
UIA::ITextRangeProvider* returnVal;
THROW_IF_FAILED(_uiaProvider->get_DocumentRange(&returnVal));
const auto parentProvider = this->ProviderFromPeer(*this);
// const auto parentProvider = this->ProviderFromPeer(*this);
const auto parentProvider = _parentProvider;
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
return xutr.as<XamlAutomation::ITextRangeProvider>();
}
@ -194,7 +202,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
std::vector<XamlAutomation::ITextRangeProvider> vec;
vec.reserve(count);
auto parentProvider = this->ProviderFromPeer(*this);
// auto parentProvider = this->ProviderFromPeer(*this);
const auto parentProvider = _parentProvider;
for (int i = 0; i < count; i++)
{
auto xutr = make_self<XamlUiaTextRange>(providers[i].detach(), parentProvider);

View file

@ -43,6 +43,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void SetControlBounds(const Windows::Foundation::Rect bounds);
void SetControlPadding(const Core::Padding padding);
void SetParentProvider(Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple parentProvider);
#pragma region IUiaEventDispatcher
void SignalSelectionChanged() override;
@ -76,6 +77,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
private:
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;
winrt::Microsoft::Terminal::Control::implementation::ControlInteractivity* _interactivity;
winrt::Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple _parentProvider{ nullptr };
til::rectangle _controlBounds{};
til::rectangle _controlPadding{};

View file

@ -3,13 +3,21 @@
namespace Microsoft.Terminal.Control
{
[default_interface] runtimeclass InteractivityAutomationPeer :
[default_interface] runtimeclass InteractivityAutomationPeer/* :
Windows.UI.Xaml.Automation.Peers.AutomationPeer,
Windows.UI.Xaml.Automation.Provider.ITextProvider
Windows.UI.Xaml.Automation.Provider.ITextProvider*/
{
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider[] GetSelection();
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider[] GetVisibleRanges();
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider RangeFromChild(Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple childElement);
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider RangeFromPoint(Windows.Foundation.Point screenLocation);
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider DocumentRange();
Windows.UI.Xaml.Automation.SupportedTextSelection SupportedTextSelection();
void SetControlBounds(Windows.Foundation.Rect bounds);
void SetControlPadding(Microsoft.Terminal.Core.Padding padding);
void SetParentProvider(Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple provider);
event Windows.Foundation.TypedEventHandler<Object, Object> SelectionChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TextChanged;

View file

@ -200,4 +200,10 @@ Please either install the missing font or choose another one.</value>
<data name="TermControlReadOnly" xml:space="preserve">
<value>Read-only mode is enabled.</value>
</data>
<data name="TermControl_ContentDiedTextBlock.Text" xml:space="preserve">
<value>The content of this terminal was closed unexpectedly.</value>
</data>
<data name="TermControl_ContentDiedButton.Content" xml:space="preserve">
<value>Close</value>
</data>
</root>

View file

@ -1,5 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// The functions for handling content processes in the TermControl are largely
// in TermControlContentManageent.cpp
#include "pch.h"
#include "TermControl.h"
@ -32,8 +35,10 @@ using namespace winrt::Windows::ApplicationModel::DataTransfer;
// The updates are throttled to limit power usage.
constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8);
// The minimum delay between updating the TSF input control.
// This is already throttled primarily in the ControlCore, with a timeout of 100ms. We're adding another smaller one here, as the (potentially x-proc) call will come in off the UI thread
// The minimum delay between updating the TSF input control. This is already
// throttled primarily in the ControlCore, with a timeout of 100ms. We're adding
// another smaller one here, as the (potentially x-proc) call will come in off
// the UI thread
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(8);
// The minimum delay between updating the locations of regex patterns
@ -50,7 +55,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 +74,21 @@ 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();
_contentWaitInterrupt.create();
_createContentWaitThread();
}
}
if (_interactivity == nullptr)
{
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, connection);
}
_core = _interactivity.Core();
// These events might all be triggered by the connection, but that
@ -75,6 +101,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// This event is specifically triggered by the renderer thread, a BG thread. Use a weak ref here.
_core.RendererEnteredErrorState({ get_weak(), &TermControl::_RendererEnteredErrorState });
_core.ConnectionStateChanged({ get_weak(), &TermControl::_coreConnectionStateChanged });
// These callbacks can only really be triggered by UI interactions. So
// they don't need weak refs - they can't be triggered unless we're
// alive.
@ -500,6 +528,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TermControl::~TermControl()
{
if (_contentIsOutOfProc())
{
_contentWaitInterrupt.SetEvent();
_contentWaitThread.join();
}
Close();
}
@ -526,6 +559,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
margins.Right,
margins.Bottom };
_automationPeer = winrt::make<implementation::TermControlAutomationPeer>(this, padding, interactivityAutoPeer);
interactivityAutoPeer.SetParentProvider(_automationPeer.GetParentProvider());
return _automationPeer;
}
}
@ -546,7 +580,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TerminalConnection::ConnectionState TermControl::ConnectionState() const
{
return _core.ConnectionState();
try
{
return _core.ConnectionState();
}
CATCH_LOG();
return TerminalConnection::ConnectionState::Closed;
}
winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged(IInspectable /*sender*/, IInspectable /*args*/)
@ -560,7 +599,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->_contentIsOutOfProc() ?
control->_contentProc.RequestSwapChainHandle(GetCurrentProcessId()) :
control->_core.SwapChainHandle());
_AttachDxgiSwapChainToXaml(chainHandle);
}
}
@ -648,7 +690,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>(_contentIsOutOfProc() ?
_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
@ -1734,8 +1780,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Disconnect the TSF input control so it doesn't receive EditContext events.
TSFInputControl().Close();
_autoScrollTimer.Stop();
_core.Close();
if (!_contentIsOutOfProc())
{
_core.Close();
}
}
}
@ -2568,4 +2616,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_playWarningBell->Run();
}
void TermControl::_coreConnectionStateChanged(const IInspectable& /*sender*/, const IInspectable& /*args*/)
{
_ConnectionStateChangedHandlers(*this, nullptr);
}
}

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();
@ -70,6 +71,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::fire_and_forget _RendererEnteredErrorState(IInspectable sender, IInspectable args);
void _RenderRetryButton_Click(IInspectable const& button, IInspectable const& args);
void _ContentDiedCloseButton_Click(IInspectable const& button, IInspectable const& args);
winrt::fire_and_forget _RendererWarning(IInspectable sender,
Control::RendererWarningArgs args);
@ -115,7 +117,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
PROJECTED_FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs, _core, TitleChanged);
PROJECTED_FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable, _core, TabColorChanged);
PROJECTED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable, _core, TaskbarProgressChanged);
PROJECTED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable, _core, ConnectionStateChanged);
TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable);
PROJECTED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs, _interactivity, PasteFromClipboard);
@ -145,6 +147,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;
@ -183,6 +186,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
wil::unique_event _contentWaitInterrupt;
std::thread _contentWaitThread;
void _createContentWaitThread();
bool _contentIsOutOfProc() const;
inline bool _IsClosing() const noexcept
{
#ifndef NDEBUG
@ -270,6 +278,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::fire_and_forget _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args);
void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args);
void _coreWarningBell(const IInspectable& sender, const IInspectable& args);
winrt::fire_and_forget _raiseContentDied();
void _coreConnectionStateChanged(const IInspectable& sender, const IInspectable& args);
};
}

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

@ -130,6 +130,27 @@
</Border>
</Grid>
<Grid x:Name="ContentDiedNotice"
HorizontalAlignment="Center"
VerticalAlignment="Center"
x:Load="False">
<Border Margin="8,8,8,8"
Padding="8,8,8,8"
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
BorderBrush="{ThemeResource SystemAccentColor}"
BorderThickness="2,2,2,2"
CornerRadius="{ThemeResource OverlayCornerRadius}">
<StackPanel>
<TextBlock x:Uid="TermControl_ContentDiedTextBlock"
HorizontalAlignment="Center"
TextWrapping="WrapWholeWords" />
<Button x:Uid="TermControl_ContentDiedButton"
HorizontalAlignment="Right"
Click="_ContentDiedCloseButton_Click" />
</StackPanel>
</Border>
</Grid>
</Grid>
</UserControl>

View file

@ -67,6 +67,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_contentAutomationPeer.SetControlPadding(padding);
}
XamlAutomation::IRawElementProviderSimple TermControlAutomationPeer::GetParentProvider()
{
const auto parentProvider = this->ProviderFromPeer(*this);
return parentProvider;
}
// Method Description:
// - Signals the ui automation client that the terminal's selection has changed and should be updated
// Arguments:
@ -153,7 +159,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
hstring TermControlAutomationPeer::GetLocalizedControlTypeCore() const
{
return RS_(L"TerminalControl_ControlType");
// return RS_(L"TerminalControl_ControlType");
return L"foo";
}
Windows::Foundation::IInspectable TermControlAutomationPeer::GetPatternCore(PatternInterface patternInterface) const

View file

@ -48,6 +48,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void UpdateControlBounds();
void SetControlPadding(const Core::Padding padding);
Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple GetParentProvider();
#pragma region FrameworkElementAutomationPeer
hstring GetClassNameCore() const;

View file

@ -12,5 +12,6 @@ namespace Microsoft.Terminal.Control
void UpdateControlBounds();
void SetControlPadding(Microsoft.Terminal.Core.Padding padding);
Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple GetParentProvider();
}
}

View file

@ -0,0 +1,154 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// The functions in this class are specific to the handling of out-of-proc
// content processes by the TermControl. Putting them all in one file keeps
// TermControl.cpp a little less cluttered.
#include "pch.h"
#include "TermControl.h"
using namespace ::Microsoft::Console::Types;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::System;
namespace winrt::Microsoft::Terminal::Control::implementation
{
bool TermControl::_contentIsOutOfProc() const
{
return _contentProc != nullptr;
}
bool s_waitOnContentProcess(uint64_t contentPid, HANDLE contentWaitInterrupt)
{
// This is the array of HANDLEs that we're going to wait on in
// WaitForMultipleObjects below.
// * waits[0] will be the handle to the content process. It gets
// signalled when the process exits / dies.
// * waits[1] is the handle to our _contentWaitInterrupt event. Another
// thread can use that to manually break this loop. We'll do that when
// we're getting torn down.
HANDLE waits[2];
waits[1] = contentWaitInterrupt;
bool displayError = true;
// At any point in all this, the content process might die. If it does,
// we want to raise an error message, to inform that this control is now
// dead.
try
{
// This might fail to even ask the content for it's PID.
wil::unique_handle hContent{ OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
static_cast<DWORD>(contentPid)) };
// If we fail to open the content, then they don't exist
// anymore! We'll need to immediately raise the notification that the content has died.
if (hContent.get() == nullptr)
{
const auto gle = GetLastError();
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_FailedToOpenContent",
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
return displayError;
}
waits[0] = hContent.get();
switch (WaitForMultipleObjects(2, waits, FALSE, INFINITE))
{
case WAIT_OBJECT_0 + 0: // waits[0] was signaled, the handle to the content process
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_ContentDied",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
break;
case WAIT_OBJECT_0 + 1: // waits[1] was signaled, our manual interrupt
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_ContentWaitInterrupted",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
displayError = false;
break;
case WAIT_TIMEOUT:
// This should be impossible.
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_ContentWaitTimeout",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
break;
default:
{
// Returning any other value is invalid. Just die.
const auto gle = GetLastError();
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_WaitFailed",
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
}
catch (...)
{
// Theoretically, if window[1] dies when we're trying to get
// it's PID we'll get here. We can probably just exit here.
TraceLoggingWrite(g_hTerminalControlProvider,
"TermControl_ExceptionInWaitThread",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
return displayError;
}
void TermControl::_createContentWaitThread()
{
_contentWaitThread = std::thread([weakThis = get_weak(), contentPid = _contentProc.GetPID(), contentWaitInterrupt = _contentWaitInterrupt.get()] {
if (s_waitOnContentProcess(contentPid, contentWaitInterrupt))
{
// When s_waitOnContentProcess returns, if it returned true, we
// should display a dialog in our bounds to indicate that we
// were closed unexpectedly. If we closed in an expected way,
// then s_waitOnContentProcess will return false.
if (auto control{ weakThis.get() })
{
control->_raiseContentDied();
}
}
});
}
winrt::fire_and_forget TermControl::_raiseContentDied()
{
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(Dispatcher());
if (auto control{ weakThis.get() })
{
if (auto loadedUiElement{ FindName(L"ContentDiedNotice") })
{
if (auto uiElement{ loadedUiElement.try_as<::winrt::Windows::UI::Xaml::UIElement>() })
{
uiElement.Visibility(Visibility::Visible);
}
}
}
}
// Method Description:
// - Handler for when the "Content Died" dialog's button is clicked.
void TermControl::_ContentDiedCloseButton_Click(IInspectable const& /*sender*/, IInspectable const& /*args*/)
{
// Alert whoever's hosting us that the connection was closed.
// When they come asking what the new connection state is, we'll reply with Closed
_ConnectionStateChangedHandlers(*this, nullptr);
}
}

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>
@ -58,13 +61,18 @@
<ClInclude Include="TSFInputControl.h">
<DependentUpon>TSFInputControl.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="XamlUiaTextRange.h" />
<ClInclude Include="XamlUiaTextRange.h" >
<DependentUpon>XamlUiaTextRange.idl</DependentUpon>
</ClInclude>
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<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>
@ -87,6 +95,9 @@
<ClCompile Include="TermControl.cpp">
<DependentUpon>TermControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="TermControlContentManagement.cpp">
<DependentUpon>TermControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="TSFInputControl.cpp">
<DependentUpon>TSFInputControl.xaml</DependentUpon>
</ClCompile>
@ -97,10 +108,13 @@
<ClCompile Include="InteractivityAutomationPeer.cpp">
<DependentUpon>InteractivityAutomationPeer.idl</DependentUpon>
</ClCompile>
<ClCompile Include="XamlUiaTextRange.cpp" />
<ClCompile Include="XamlUiaTextRange.cpp" >
<DependentUpon>XamlUiaTextRange.idl</DependentUpon>
</ClCompile>
</ItemGroup>
<!-- ========================= idl Files ======================== -->
<ItemGroup>
<Midl Include="ContentProcess.idl" />
<Midl Include="ControlCore.idl" />
<Midl Include="ControlInteractivity.idl" />
<Midl Include="ICoreState.idl" />
@ -123,6 +137,7 @@
<Midl Include="TSFInputControl.idl">
<DependentUpon>TSFInputControl.xaml</DependentUpon>
</Midl>
<Midl Include="XamlUiaTextRange.idl" />
</ItemGroup>
<!-- ========================= XAML Files ======================== -->
<ItemGroup>

View file

@ -21,13 +21,13 @@ Author(s):
#pragma once
#include "TermControlAutomationPeer.h"
#include "XamlUiaTextRange.g.h"
#include <UIAutomationCore.h>
#include "../types/TermControlUiaTextRange.hpp"
namespace winrt::Microsoft::Terminal::Control::implementation
{
class XamlUiaTextRange :
public winrt::implements<XamlUiaTextRange, Windows::UI::Xaml::Automation::Provider::ITextRangeProvider>
class XamlUiaTextRange : public XamlUiaTextRangeT<XamlUiaTextRange>
{
public:
XamlUiaTextRange(::ITextRangeProvider* uiaProvider, Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple parentProvider) :

View file

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Control
{
[default_interface] runtimeclass XamlUiaTextRange :
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider
{
}
}

View file

@ -0,0 +1,150 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "AppHost.h"
#include "resource.h"
#include "../types/inc/User32Utils.hpp"
#include <WilErrorReporting.h>
using namespace winrt;
using namespace winrt::Windows::UI;
using namespace winrt::Windows::UI::Composition;
using namespace winrt::Windows::UI::Xaml::Hosting;
using namespace winrt::Windows::Foundation::Numerics;
// We keep a weak ref to our ContentProcess singleton here.
// Why?
//
// We need to always return the _same_ ContentProcess when someone comes to
// instantiate this class. So we want to track the single instance we make. We
// also want to track when the last outstanding reference to this object is
// removed. If we're keeping a strong ref, then the ref count will always be > 1
winrt::weak_ref<winrt::Microsoft::Terminal::Control::ContentProcess> g_weak{ nullptr };
wil::unique_event g_canExitThread;
struct ContentProcessFactory : implements<ContentProcessFactory, IClassFactory>
{
ContentProcessFactory(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)
{
// Instantiate the ContentProcess here
winrt::Microsoft::Terminal::Control::ContentProcess strong{};
// Now, create a weak ref to that ContentProcess object.
winrt::weak_ref<winrt::Microsoft::Terminal::Control::ContentProcess> weak{ strong };
// Stash away that weak ref for future callers.
g_weak = weak;
return strong.as(iid, result);
}
else
{
auto strong = g_weak.get();
// !! LOAD BEARING !! If you set this event in the _first_ branch
// here, when we first create the object, then there will be _no_
// referernces to the ContentProcess object for a small slice. We'll
// stash the ContentProcess in the weak_ptr, and return it, and at
// that moment, there will be 0 outstanding references, it'll dtor,
// and wei'll ExitProcess.
//
// Instead, set the event here, once there's already a reference
// outside of just the weak one we keep. Experimentation showed this
// waw always hit when creating the ContentProcess at least once.
g_canExitThread.SetEvent();
return strong.as(iid, result);
}
}
HRESULT __stdcall LockServer(BOOL) noexcept final
{
return S_OK;
}
private:
winrt::guid _guid;
};
static bool checkIfContentProcess(winrt::guid& contentProcessGuid, HANDLE& eventHandle)
{
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() == 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() == '}';
if (canConvert)
{
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;
}
}
return false;
}
static void doContentProcessThing(const winrt::guid& contentProcessGuid, const HANDLE& eventHandle)
{
// !! LOAD BEARING !! - important to be a MTA for these COM calls.
winrt::init_apartment();
DWORD registrationHostClass{};
check_hresult(CoRegisterClassObject(contentProcessGuid,
make<ContentProcessFactory>(contentProcessGuid).get(),
CLSCTX_LOCAL_SERVER,
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);
}
void TryRunAsContentProcess()
{
winrt::guid contentProcessGuid{};
HANDLE eventHandle{ INVALID_HANDLE_VALUE };
if (checkIfContentProcess(contentProcessGuid, eventHandle))
{
g_canExitThread = wil::unique_event{ CreateEvent(nullptr, true, false, L"ContentProcessReady") };
doContentProcessThing(contentProcessGuid, eventHandle);
WaitForSingleObject(g_canExitThread.get(), INFINITE);
// This is the conhost thing - if we ExitThread the main thread, the
// other threads can keep running till one calls ExitProcess.
ExitThread(0);
}
}

View file

@ -55,6 +55,7 @@
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="main.cpp" />
<ClCompile Include="ContentProcessMain.cpp" />
<ClCompile Include="AppHost.cpp" />
<ClCompile Include="IslandWindow.cpp" />
<ClCompile Include="NonClientIslandWindow.cpp" />
@ -177,4 +178,4 @@
</Target>
<Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" />
<Import Project="..\..\..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.3.210521003\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets" Condition="Exists('..\..\..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.3.210521003\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets')" />
</Project>
</Project>

View file

@ -31,6 +31,8 @@ TRACELOGGING_DEFINE_PROVIDER(
#include <LibraryResources.h>
UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"TerminalApp/Resources");
void TryRunAsContentProcess();
// Routine Description:
// - Takes an image architecture and locates a string resource that maps to that architecture.
// Arguments:
@ -119,6 +121,13 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
// should choose and install the correct one from the bundle.
EnsureNativeArchitecture();
// If we _are_ a content process, then this function will call ExitThread(),
// after spawning some COM threads to deal with inbound COM requests to the
// ContentProcess object.
TryRunAsContentProcess();
// If we weren't a content process, then we'll just move on, and do the
// normal WindowsTerminal thing.
// Make sure to call this so we get WM_POINTER messages.
EnableMouseInPointer(true);