Compare commits

...

31 commits

Author SHA1 Message Date
Mike Griese 3bfe3d1d88 IT ABSOLUTELY WORKED 2021-08-25 16:24:56 -05:00
Mike Griese d8bd527996 Holy shit all the plumbing worked on the first try 2021-08-25 15:35:30 -05:00
Mike Griese 81a5a736fe I suppose this is just dead code cleanup 2021-08-25 12:51:28 -05:00
Mike Griese dbee7e6d67 splitting panes works too 2021-08-25 12:49:36 -05:00
Mike Griese 36fb572308 Apparently, event names are GLOBAL so reusing the same name is VERY DUMB 2021-08-25 10:44:37 -05:00
Mike Griese 6b6ca8fe60 This fixes the packaging issue I was seeing 2021-08-25 10:44:09 -05:00
Mike Griese 015e3211fc Proof of concept - the Terminal hosting an OOP TermControl
ONE WEIRD TRICK: I had to
  ```
    copy bin\x64\Debug\TerminalCore\Microsoft.Terminal.Core.winmd src\cascadia\CascadiaPackage\bin\x64\Debug\AppX
  ```
  to make this work right. I'll work on the packaging next. Weird that we never
  needed that one before?

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

  But it did work _once_.
2021-08-25 10:29:56 -05:00
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
49 changed files with 1466 additions and 108 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

@ -856,4 +856,24 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
};
_forAllPeasantsIgnoringTheDead(callback, onError);
}
void Monarch::RequestMovePane(winrt::hstring window,
winrt::guid contentGuid,
uint32_t tabIndex)
{
auto windowId = _lookupPeasantIdForName(window);
if (windowId == 0)
{ /* TODO! try the name as an integer ID */
return;
}
if (auto targetPeasant{ _getPeasant(windowId) })
{
auto request = winrt::make_self<implementation::AttachRequest>(contentGuid, tabIndex);
targetPeasant.AttachPaneToWindow(*request);
}
else
{ /*TODO! log */
}
}
}

View file

@ -55,6 +55,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void SummonAllWindows();
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
void RequestMovePane(winrt::hstring window, winrt::guid contentGuid, uint32_t tabIndex);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);

View file

@ -44,6 +44,8 @@ namespace Microsoft.Terminal.Remoting
void SummonAllWindows();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames { get; };
void RequestMovePane(String window, Guid contentGuid, UInt32 tabIndex);
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;

View file

@ -7,6 +7,7 @@
#include "SummonWindowBehavior.h"
#include "Peasant.g.cpp"
#include "../../types/inc/utils.hpp"
#include "AttachRequest.g.cpp"
using namespace winrt;
using namespace winrt::Microsoft::Terminal;
@ -257,4 +258,21 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::AttachPaneToWindow(Remoting::AttachRequest request)
{
try
{
_AttachRequestedHandlers(*this, request);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_AttachPaneToWindow",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}

View file

@ -6,6 +6,7 @@
#include "Peasant.g.h"
#include "../cascadia/inc/cppwinrt_utils.h"
#include "RenameRequestArgs.h"
#include "AttachRequest.g.h"
namespace RemotingUnitTests
{
@ -13,6 +14,17 @@ namespace RemotingUnitTests
};
namespace winrt::Microsoft::Terminal::Remoting::implementation
{
struct AttachRequest : public AttachRequestT<AttachRequest>
{
WINRT_PROPERTY(winrt::guid, ContentGuid);
WINRT_PROPERTY(uint32_t, TabIndex);
public:
AttachRequest(winrt::guid contentGuid,
uint32_t tabIndex) :
_ContentGuid{ contentGuid }, _TabIndex{ tabIndex } {};
};
struct Peasant : public PeasantT<Peasant>
{
Peasant();
@ -31,6 +43,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void RequestShowTrayIcon();
void RequestHideTrayIcon();
void AttachPaneToWindow(Remoting::AttachRequest request);
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
@ -44,6 +58,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(AttachRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::AttachRequest);
private:
Peasant(const uint64_t testPID);

View file

@ -47,6 +47,11 @@ namespace Microsoft.Terminal.Remoting
MonitorBehavior ToMonitor;
}
[default_interface] runtimeclass AttachRequest {
Guid ContentGuid { get; };
UInt32 TabIndex { get; };
}
interface IPeasant
{
CommandlineArgs InitialArgs { get; };
@ -66,6 +71,7 @@ namespace Microsoft.Terminal.Remoting
void Summon(SummonWindowBehavior behavior);
void RequestShowTrayIcon();
void RequestHideTrayIcon();
void AttachPaneToWindow(AttachRequest request);
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
@ -75,6 +81,7 @@ namespace Microsoft.Terminal.Remoting
event Windows.Foundation.TypedEventHandler<Object, SummonWindowBehavior> SummonRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, AttachRequest> AttachRequested;
};
[default_interface] runtimeclass Peasant : IPeasant

View file

@ -564,4 +564,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
return false;
}
winrt::fire_and_forget WindowManager::RequestMovePane(winrt::hstring window,
winrt::guid contentGuid,
uint32_t tabIndex)
{
co_await winrt::resume_background();
_monarch.RequestMovePane(window, contentGuid, tabIndex);
}
}

View file

@ -46,6 +46,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::fire_and_forget RequestShowTrayIcon();
winrt::fire_and_forget RequestHideTrayIcon();
bool DoesQuakeWindowExist();
winrt::fire_and_forget RequestMovePane(winrt::hstring window, winrt::guid contentGuid, uint32_t tabIndex);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);

View file

@ -17,6 +17,9 @@ namespace Microsoft.Terminal.Remoting
void RequestHideTrayIcon();
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames();
void RequestMovePane(String window, Guid contentGuid, UInt32 tabIndex);
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;

View file

@ -152,7 +152,7 @@ namespace winrt::TerminalApp::implementation
}
else if (const auto& realArgs = args.ActionArgs().try_as<MovePaneArgs>())
{
auto moved = _MovePane(realArgs.TabIndex());
auto moved = _MovePane(realArgs);
args.Handled(moved);
}
}

View file

@ -1477,4 +1477,12 @@ namespace winrt::TerminalApp::implementation
return false;
}
}
void AppLogic::AttachPane(winrt::guid contentGuid, uint32_t tabIndex)
{
if (_root)
{
_root->AttachPane(contentGuid, tabIndex);
}
}
}

View file

@ -94,6 +94,7 @@ namespace winrt::TerminalApp::implementation
bool GetMinimizeToTray();
bool GetAlwaysShowTrayIcon();
void AttachPane(winrt::guid contentGuid, uint32_t tabIndex);
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
@ -171,6 +172,7 @@ namespace winrt::TerminalApp::implementation
FORWARDED_TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs, _root, RenameWindowRequested);
FORWARDED_TYPED_EVENT(IsQuakeWindowChanged, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IsQuakeWindowChanged);
FORWARDED_TYPED_EVENT(SummonWindowRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, SummonWindowRequested);
FORWARDED_TYPED_EVENT(RequestMovePane, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestMovePaneArgs, _root, RequestMovePane);
#ifdef UNIT_TESTING
friend class TerminalAppLocalTests::CommandlineTest;

View file

@ -74,6 +74,7 @@ namespace TerminalApp
Boolean GetAlwaysShowTrayIcon();
FindTargetWindowResult FindTargetWindow(String[] args);
void AttachPane(Guid contentGuid, UInt32 tabIndex);
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Microsoft.Terminal.Settings.Model.Command> GlobalHotkeys();
@ -95,5 +96,6 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> SettingsChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> IsQuakeWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, RequestMovePaneArgs> RequestMovePane;
}
}

View file

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

View file

@ -17,6 +17,7 @@
#include "DebugTapConnection.h"
#include "SettingsTab.h"
#include "RenameWindowRequestedArgs.g.cpp"
#include "RequestMovePaneArgs.g.cpp"
#include "../inc/WindowingBehavior.h"
#include <til/latch.h>
@ -461,7 +462,12 @@ namespace winrt::TerminalApp::implementation
// GH#6586: now that we're done processing all startup commands,
// focus the active control. This will work as expected for both
// commandline invocations and for `wt` action invocations.
_GetActiveControl().Focus(FocusState::Programmatic);
if (auto activeControl{ _GetActiveControl() })
{
// TODO! this doesn't work during startup anymore now that
// tabs are made on a BG thread
activeControl.Focus(FocusState::Programmatic);
}
}
if (initial)
{
@ -898,6 +904,103 @@ namespace winrt::TerminalApp::implementation
return connection;
}
// Method Description:
// - Creates a new connection based on the profile settings
// Arguments:
// - the profile we want the settings from
// - the terminal settings
// Return value:
// - the desired connection
TerminalConnection::ConnectionInformation TerminalPage::_CreateConnectionInfoFromSettings(Profile profile,
const TerminalSettings& settings)
{
if (!profile)
{
// Use the default profile if we didn't get one as an argument.
profile = _settings.FindProfile(_settings.GlobalSettings().DefaultProfile());
}
winrt::guid connectionType = profile.ConnectionType();
winrt::guid sessionGuid{};
Windows::Foundation::Collections::ValueSet connectionSettings{ nullptr };
winrt::hstring className;
if (connectionType == TerminalConnection::AzureConnection::ConnectionType() &&
TerminalConnection::AzureConnection::IsAzureConnectionAvailable())
{
// TODO GH#4661: Replace this with directly using the AzCon when our VT is better
std::filesystem::path azBridgePath{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
azBridgePath.replace_filename(L"TerminalAzBridge.exe");
className = winrt::name_of<TerminalConnection::ConptyConnection>();
connectionSettings = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.wstring(),
L".",
L"Azure",
nullptr,
::base::saturated_cast<uint32_t>(settings.InitialRows()),
::base::saturated_cast<uint32_t>(settings.InitialCols()),
winrt::guid());
}
else
{
// profile is guaranteed to exist here
std::wstring guidWString = Utils::GuidToString(profile.Guid());
StringMap envMap{};
envMap.Insert(L"WT_PROFILE_ID", guidWString);
envMap.Insert(L"WSLENV", L"WT_PROFILE_ID");
// Update the path to be relative to whatever our CWD is.
//
// Refer to the examples in
// https://en.cppreference.com/w/cpp/filesystem/path/append
//
// We need to do this here, to ensure we tell the ConptyConnection
// the correct starting path. If we're being invoked from another
// terminal instance (e.g. wt -w 0 -d .), then we have switched our
// CWD to the provided path. We should treat the StartingDirectory
// as relative to the current CWD.
//
// The connection must be informed of the current CWD on
// construction, because the connection might not spawn the child
// process until later, on another thread, after we've already
// restored the CWD to it's original value.
winrt::hstring newWorkingDirectory{ settings.StartingDirectory() };
if (newWorkingDirectory.size() <= 1 ||
!(newWorkingDirectory[0] == L'~' || newWorkingDirectory[0] == L'/'))
{ // We only want to resolve the new WD against the CWD if it doesn't look like a Linux path (see GH#592)
std::wstring cwdString{ wil::GetCurrentDirectoryW<std::wstring>() };
std::filesystem::path cwd{ cwdString };
cwd /= settings.StartingDirectory().c_str();
newWorkingDirectory = winrt::hstring{ cwd.wstring() };
}
className = winrt::name_of<TerminalConnection::ConptyConnection>();
connectionSettings = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
newWorkingDirectory,
settings.StartingTitle(),
envMap.GetView(),
::base::saturated_cast<uint32_t>(settings.InitialRows()),
::base::saturated_cast<uint32_t>(settings.InitialCols()),
winrt::guid());
// sessionGuid = conhostConn.Guid();
}
// TraceLoggingWrite(
// g_hTerminalAppProvider,
// "ConnectionCreated",
// TraceLoggingDescription("Event emitted upon the creation of a connection"),
// TraceLoggingGuid(connectionType, "ConnectionTypeGuid", "The type of the connection"),
// TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The profile's GUID"),
// TraceLoggingGuid(sessionGuid, "SessionGuid", "The WT_SESSION's GUID"),
// TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
// TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
return TerminalConnection::ConnectionInformation(className, connectionSettings);
}
// Method Description:
// - Called when the settings button is clicked. Launches a background
// thread to open the settings file in the default JSON editor.
@ -1257,11 +1360,14 @@ namespace winrt::TerminalApp::implementation
// - No move will occur if the tabIdx is the same as the current tab, or if
// the specified tab is not a host of terminals (such as the settings tab).
// Arguments:
// - tabIdx: The target tab index.
// - TODO!
// Return Value:
// - true if the pane was successfully moved to the new tab.
bool TerminalPage::_MovePane(const uint32_t tabIdx)
bool TerminalPage::_MovePane(MovePaneArgs args)
{
const auto tabIdx{ args.TabIndex() };
const auto windowId{ args.Window() };
auto focusedTab{ _GetFocusedTabImpl() };
if (!focusedTab)
@ -1269,6 +1375,17 @@ namespace winrt::TerminalApp::implementation
return false;
}
if (!windowId.empty())
{
if (const auto& control{ _GetActiveControl() })
{
const auto currentContentGuid{ control.ContentGuid() };
auto request = winrt::make_self<RequestMovePaneArgs>(currentContentGuid, args);
_RequestMovePaneHandlers(*this, *request);
return true;
}
}
// If we are trying to move from the current tab to the current tab do nothing.
if (_GetFocusedTabIndex() == tabIdx)
{
@ -1299,6 +1416,45 @@ namespace winrt::TerminalApp::implementation
return true;
}
winrt::fire_and_forget TerminalPage::AttachPane(winrt::guid contentGuid, uint32_t tabIndex)
{
contentGuid;
tabIndex;
co_await winrt::resume_background();
const auto contentProc = _AttachToContentProcess(contentGuid);
contentProc;
Settings::Model::NewTerminalArgs newTerminalArgs{ nullptr };
auto profile = _settings.GetProfileForArgs(newTerminalArgs);
auto controlSettings = TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings);
co_await winrt::resume_foreground(Dispatcher());
auto newControl = _InitControl(controlSettings, contentProc.Guid());
// Hookup our event handlers to the new terminal
_RegisterTerminalEvents(newControl);
_UnZoomIfNeeded();
if (_tabs.Size() > tabIndex)
{
auto targetTab = _GetTerminalTabImpl(_tabs.GetAt(tabIndex));
targetTab->SplitPane(SplitState::Vertical, .5f, profile, newControl);
// After GH#6586, the control will no longer focus itself
// automatically when it's finished being laid out. Manually focus
// the control here instead.
if (_startupState == StartupState::Initialized)
{
_GetActiveControl().Focus(FocusState::Programmatic);
}
}
// else
// {
// realSplitType = tab.PreCalculateAutoSplit(availableSpace);
// tab.SplitPane(realSplitType, splitSize, profile, newControl);
// }
}
// Method Description:
// - Split the focused pane either horizontally or vertically, and place the
// given TermControl into the newly created pane.
@ -1344,11 +1500,11 @@ namespace winrt::TerminalApp::implementation
// - newTerminalArgs: An object that may contain a blob of parameters to
// control which profile is created and with possible other
// configurations. See CascadiaSettings::BuildSettings for more details.
void TerminalPage::_SplitPane(TerminalTab& tab,
const SplitState splitType,
const SplitType splitMode,
const float splitSize,
const NewTerminalArgs& newTerminalArgs)
winrt::fire_and_forget TerminalPage::_SplitPane(TerminalTab& tab,
const SplitState splitType,
const SplitType splitMode,
const float splitSize,
const NewTerminalArgs& newTerminalArgs)
{
// Do nothing if we're requesting no split.
if (splitType == SplitState::None)
@ -1393,8 +1549,6 @@ namespace winrt::TerminalApp::implementation
controlSettings = TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings);
}
const auto controlConnection = _CreateConnectionFromSettings(profile, controlSettings.DefaultSettings());
const float contentWidth = ::base::saturated_cast<float>(_tabContent.ActualWidth());
const float contentHeight = ::base::saturated_cast<float>(_tabContent.ActualHeight());
const winrt::Windows::Foundation::Size availableSpace{ contentWidth, contentHeight };
@ -1408,10 +1562,13 @@ namespace winrt::TerminalApp::implementation
const auto canSplit = tab.PreCalculateCanSplit(realSplitType, splitSize, availableSpace);
if (!canSplit)
{
return;
co_return;
}
auto newControl = _InitControl(controlSettings, controlConnection);
co_await winrt::resume_background();
const auto contentProc = _CreateNewContentProcess(profile, controlSettings).get();
co_await winrt::resume_foreground(Dispatcher());
auto newControl = _InitControl(controlSettings, contentProc.Guid());
// Hookup our event handlers to the new terminal
_RegisterTerminalEvents(newControl);
@ -1996,6 +2153,17 @@ namespace winrt::TerminalApp::implementation
return term;
}
TermControl TerminalPage::_InitControl(const TerminalSettingsCreateResult& settings, const winrt::guid& contentGuid)
{
// Give term control a child of the settings so that any overrides go in the child
// This way, when we do a settings reload we just update the parent and the overrides remain
const auto child = TerminalSettings::CreateWithParent(settings);
TermControl term{ contentGuid, child.DefaultSettings(), nullptr };
term.UnfocusedAppearance(child.UnfocusedSettings()); // It is okay for the unfocused settings to be null
return term;
}
// Method Description:
// - Hook up keybindings, and refresh the UI of the terminal.

View file

@ -8,6 +8,7 @@
#include "AppKeyBindings.h"
#include "AppCommandlineArgs.h"
#include "RenameWindowRequestedArgs.g.h"
#include "RequestMovePaneArgs.g.h"
#include "Toast.h"
#define DECLARE_ACTION_HANDLER(action) void _Handle##action(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
@ -45,6 +46,16 @@ namespace winrt::TerminalApp::implementation
_ProposedName{ name } {};
};
struct RequestMovePaneArgs : RequestMovePaneArgsT<RequestMovePaneArgs>
{
WINRT_PROPERTY(winrt::guid, ContentGuid);
WINRT_PROPERTY(Microsoft::Terminal::Settings::Model::MovePaneArgs, Args, nullptr);
public:
RequestMovePaneArgs(const winrt::guid& g, Microsoft::Terminal::Settings::Model::MovePaneArgs args) :
_ContentGuid{ g }, _Args{ args } {};
};
struct TerminalPage : TerminalPageT<TerminalPage>
{
public:
@ -107,6 +118,7 @@ namespace winrt::TerminalApp::implementation
winrt::hstring WindowIdForDisplay() const noexcept;
winrt::hstring WindowNameForDisplay() const noexcept;
bool IsQuakeWindow() const noexcept;
winrt::fire_and_forget AttachPane(winrt::guid contentGuid, uint32_t tabIndex);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
@ -124,6 +136,7 @@ namespace winrt::TerminalApp::implementation
TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs);
TYPED_EVENT(IsQuakeWindowChanged, IInspectable, IInspectable);
TYPED_EVENT(SummonWindowRequested, IInspectable, IInspectable);
TYPED_EVENT(RequestMovePane, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestMovePaneArgs);
private:
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
@ -190,7 +203,13 @@ namespace winrt::TerminalApp::implementation
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane);
void _CreateNewTabWithProfileAndSettings(const Microsoft::Terminal::Settings::Model::Profile& profile, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
winrt::Windows::Foundation::IAsyncOperation<Microsoft::Terminal::Control::ContentProcess> _CreateNewContentProcess(Microsoft::Terminal::Settings::Model::Profile profile,
Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings);
Microsoft::Terminal::Control::ContentProcess _AttachToContentProcess(const winrt::guid contentGuid);
winrt::fire_and_forget _CreateTabWithContent(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings, Microsoft::Terminal::Control::ContentProcess existingContentProc);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings);
winrt::Microsoft::Terminal::TerminalConnection::ConnectionInformation _CreateConnectionInfoFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, const Microsoft::Terminal::Settings::Model::TerminalSettings& settings);
winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
@ -240,7 +259,7 @@ namespace winrt::TerminalApp::implementation
bool _SelectTab(uint32_t tabIndex);
bool _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _MovePane(const uint32_t tabIdx);
bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args);
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl();
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
@ -259,11 +278,11 @@ namespace winrt::TerminalApp::implementation
const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual,
const float splitSize = 0.5f,
const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr);
void _SplitPane(TerminalTab& tab,
const Microsoft::Terminal::Settings::Model::SplitState splitType,
const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual,
const float splitSize = 0.5f,
const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr);
winrt::fire_and_forget _SplitPane(TerminalTab& tab,
const Microsoft::Terminal::Settings::Model::SplitState splitType,
const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual,
const float splitSize = 0.5f,
const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr);
void _ResizePane(const Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
void _ToggleSplitOrientation();
@ -310,6 +329,9 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection);
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::guid& contentGuid);
void _RefreshUIForSettingsReload();
void _SetNonClientAreaColors(const Windows::UI::Color& selectedTabColor);

View file

@ -10,6 +10,11 @@ namespace TerminalApp
{
String ProposedName { get; };
};
[default_interface] runtimeclass RequestMovePaneArgs
{
Microsoft.Terminal.Settings.Model.MovePaneArgs Args { get; };
Guid ContentGuid { get; };
};
interface IDialogPresenter
{
@ -44,6 +49,7 @@ namespace TerminalApp
String KeyboardServiceDisabledText { get; };
TaskbarState TaskbarState{ get; };
void AttachPane(Guid contentGuid, UInt32 tabIndex);
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
@ -57,5 +63,6 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, RenameWindowRequestedArgs> RenameWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> IsQuakeWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, RequestMovePaneArgs> RequestMovePane;
}
}

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,105 @@
// 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(winrt::guid g) :
_ourPID{ GetCurrentProcessId() }, _guid{ g } {}
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;
}
winrt::guid ContentProcess::Guid()
{
return _guid;
}
// 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,33 @@
// 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(winrt::guid g);
~ContentProcess();
bool Initialize(Control::IControlSettings settings,
TerminalConnection::ConnectionInformation connectionInfo);
Control::ControlInteractivity GetInteractivity();
uint64_t GetPID();
winrt::guid Guid();
uint64_t RequestSwapChainHandle(const uint64_t pid);
private:
Control::ControlInteractivity _interactivity{ nullptr };
uint64_t _ourPID;
winrt::guid _guid;
};
}
namespace winrt::Microsoft::Terminal::Control::factory_implementation
{
BASIC_FACTORY(ContentProcess);
}

View file

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

View file

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

View file

@ -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,8 +25,11 @@ 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::guid ContentGuid() const;
winrt::fire_and_forget UpdateSettings();
winrt::fire_and_forget UpdateAppearance(const IControlAppearance newAppearance);
@ -70,6 +73,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 +119,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 +149,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 +188,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 +280,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,11 +15,17 @@ namespace Microsoft.Terminal.Control
IMouseWheelListener,
ICoreState
{
TermControl(Guid contentGuid,
IControlSettings settings,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
TermControl(IControlSettings settings,
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
static Windows.Foundation.Size GetProposedDimensions(IControlSettings settings, UInt32 dpi);
Guid ContentGuid{ get; };
void UpdateSettings();
Microsoft.Terminal.Control.IControlSettings Settings;

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,159 @@
// 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
{
winrt::guid TermControl::ContentGuid() const
{
return _contentIsOutOfProc() ? _contentProc.Guid() : winrt::guid{};
}
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

@ -108,6 +108,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
ACTION_ARG(winrt::hstring, Profile, L"");
ACTION_ARG(Windows::Foundation::IReference<bool>, SuppressApplicationTitle, nullptr);
ACTION_ARG(winrt::hstring, ColorScheme);
ACTION_ARG(winrt::guid, ContentGuid);
static constexpr std::string_view CommandlineKey{ "commandline" };
static constexpr std::string_view StartingDirectoryKey{ "startingDirectory" };
@ -293,8 +294,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
MovePaneArgs(uint32_t& tabIndex) :
_TabIndex{ tabIndex } {};
ACTION_ARG(uint32_t, TabIndex, 0);
ACTION_ARG(winrt::hstring, Window, L"");
static constexpr std::string_view TabIndexKey{ "index" };
static constexpr std::string_view WindowKey{ "window" };
public:
hstring GenerateName() const;
@ -304,7 +307,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
auto otherAsUs = other.try_as<MovePaneArgs>();
if (otherAsUs)
{
return otherAsUs->_TabIndex == _TabIndex;
return otherAsUs->_TabIndex == _TabIndex && otherAsUs->_Window == _Window;
}
return false;
};
@ -313,6 +316,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<MovePaneArgs>();
JsonUtils::GetValueForKey(json, TabIndexKey, args->_TabIndex);
JsonUtils::GetValueForKey(json, WindowKey, args->_Window);
return { *args, {} };
}
static Json::Value ToJson(const IActionArgs& val)
@ -324,17 +328,19 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Json::Value json{ Json::ValueType::objectValue };
const auto args{ get_self<MovePaneArgs>(val) };
JsonUtils::SetValueForKey(json, TabIndexKey, args->_TabIndex);
JsonUtils::SetValueForKey(json, WindowKey, args->_Window);
return json;
}
IActionArgs Copy() const
{
auto copy{ winrt::make_self<MovePaneArgs>() };
copy->_TabIndex = _TabIndex;
copy->_Window = _Window;
return *copy;
}
size_t Hash() const
{
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(TabIndex());
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(TabIndex(), Window());
}
};

View file

@ -119,6 +119,8 @@ namespace Microsoft.Terminal.Settings.Model
String ColorScheme;
Guid ContentGuid{ get; };
Boolean Equals(NewTerminalArgs other);
String GenerateName();
String ToCommandline();
@ -147,6 +149,7 @@ namespace Microsoft.Terminal.Settings.Model
{
MovePaneArgs(UInt32 tabIndex);
UInt32 TabIndex;
String Window;
};
[default_interface] runtimeclass SwitchToTabArgs : IActionArgs

View file

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

View file

@ -214,6 +214,10 @@ void AppHost::_HandleCommandlineArgs()
peasant.DisplayWindowIdRequested({ this, &AppHost::_DisplayWindowId });
peasant.AttachRequested([this](auto&&, Remoting::AttachRequest args) {
_logic.AttachPane(args.ContentGuid(), args.TabIndex());
});
_logic.WindowName(peasant.WindowName());
_logic.WindowId(peasant.GetID());
}
@ -271,6 +275,9 @@ void AppHost::Initialize()
_logic.SettingsChanged({ this, &AppHost::_HandleSettingsChanged });
_logic.IsQuakeWindowChanged({ this, &AppHost::_IsQuakeWindowChanged });
_logic.SummonWindowRequested({ this, &AppHost::_SummonWindowRequested });
_logic.RequestMovePane([this](auto&&, winrt::TerminalApp::RequestMovePaneArgs args) {
_windowManager.RequestMovePane(args.Args().Window(), args.ContentGuid(), args.Args().TabIndex());
});
_window->UpdateTitle(_logic.Title());

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{ _guid };
// 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, nullptr /*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" />
@ -99,8 +100,8 @@
<Reference Include="Microsoft.Terminal.Core">
<HintPath>$(OpenConsoleCommonOutDir)TerminalCore\Microsoft.Terminal.Core.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
<Private>true</Private>
<CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
</Reference>
</ItemGroup>
<!--
@ -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);