Compare commits
24 commits
main
...
dev/migrie
Author | SHA1 | Date | |
---|---|---|---|
5c17603a94 | |||
354e4b00a3 | |||
8707c03715 | |||
31b2763be5 | |||
a000d81fa9 | |||
d36a08186c | |||
3b1bb455d8 | |||
901bc78966 | |||
4150609b42 | |||
d5920a8c69 | |||
fd364db727 | |||
3a0fbd9f59 | |||
64533c838a | |||
aa6b08118f | |||
b0b44410c6 | |||
b541179333 | |||
56f1223dc5 | |||
88d974280d | |||
d84a31801a | |||
6910677a11 | |||
9331cc8e59 | |||
2f64db2765 | |||
4cc3d39de9 | |||
d8dcb6f570 |
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
101
src/cascadia/TerminalControl/ContentProcess.cpp
Normal file
101
src/cascadia/TerminalControl/ContentProcess.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
32
src/cascadia/TerminalControl/ContentProcess.h
Normal file
32
src/cascadia/TerminalControl/ContentProcess.h
Normal 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);
|
||||
}
|
21
src/cascadia/TerminalControl/ContentProcess.idl
Normal file
21
src/cascadia/TerminalControl/ContentProcess.idl
Normal 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);
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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{};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -12,5 +12,6 @@ namespace Microsoft.Terminal.Control
|
|||
|
||||
void UpdateControlBounds();
|
||||
void SetControlPadding(Microsoft.Terminal.Core.Padding padding);
|
||||
Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple GetParentProvider();
|
||||
}
|
||||
}
|
||||
|
|
154
src/cascadia/TerminalControl/TermControlContentManagement.cpp
Normal file
154
src/cascadia/TerminalControl/TermControlContentManagement.cpp
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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) :
|
||||
|
|
10
src/cascadia/TerminalControl/XamlUiaTextRange.idl
Normal file
10
src/cascadia/TerminalControl/XamlUiaTextRange.idl
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
150
src/cascadia/WindowsTerminal/ContentProcessMain.cpp
Normal file
150
src/cascadia/WindowsTerminal/ContentProcessMain.cpp
Normal 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,
|
||||
®istrationHostClass));
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue