2020-03-26 23:33:47 +01:00
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
|
|
// Licensed under the MIT license.
|
|
|
|
|
|
|
|
#include "pch.h"
|
|
|
|
#include "DebugTapConnection.h"
|
|
|
|
|
|
|
|
using namespace ::winrt::Microsoft::Terminal::TerminalConnection;
|
|
|
|
using namespace ::winrt::Windows::Foundation;
|
|
|
|
namespace winrt::Microsoft::TerminalApp::implementation
|
|
|
|
{
|
|
|
|
// DebugInputTapConnection is an implementation detail of DebugTapConnection.
|
|
|
|
// It wraps the _actual_ connection so it can hook WriteInput and forward it
|
|
|
|
// into the actual debug panel.
|
|
|
|
class DebugInputTapConnection : public winrt::implements<DebugInputTapConnection, ITerminalConnection>
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
DebugInputTapConnection(winrt::com_ptr<DebugTapConnection> pairedTap, ITerminalConnection wrappedConnection) :
|
|
|
|
_pairedTap{ pairedTap },
|
|
|
|
_wrappedConnection{ std::move(wrappedConnection) }
|
|
|
|
{
|
|
|
|
}
|
Switch Connections to use `ValueSet`s to initialize them (#10184)
#### ⚠️ targets #10051
## Summary of the Pull Request
This PR does one big, primary thing. It removes all the constructors from any TerminalConnections, and changes them to use an `Initialize` method that accepts a `ValueSet` of properties.
Why?
For the upcoming window/content process work, we'll need the content process to be able to initialize the connection _in the content process_. However, the window process will be the one that knows what type of connection to make. Enter `ConnectionInformation`. This class will let us specify the class name of the type we want to create, and a set of settings to use when initializing that connection.
**IMPORTANT**: As a part of this, the constructor for a connection must have 0 arguments. `RoActivateInstance` lets you just conjure a WinRT type just by class name, but that class must have a 0 arg ctor. Hence the need for `Initialize`, to actually pass the settings.
We're using a `ValueSet` here because it's basically a json blob, with more steps. In the future, when extension authors want to have custom connections, we can always deserialize the json into a `ValueSet`, pass it to their connection's `Initialize`, and let then get what they need out of it.
## References
* Tear-out: #1256
* Megathread: #5000
* Project: https://github.com/microsoft/terminal/projects/5
## PR Checklist
* [x] Closes https://github.com/microsoft/terminal/projects/5#card-50760298
* [x] I work here
* [n/a] Tests added/passed
* [n/a] Requires documentation to be updated
## Detailed Description of the Pull Request / Additional comments
`ConnectionInformation` was included as a part of this PR, to demonstrate how this will eventually be used. `ConnectionInformation` is not _currently_ used.
## Validation Steps Performed
It still builds and runs.
2021-07-20 17:02:17 +02:00
|
|
|
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) {}
|
2020-03-26 23:33:47 +01:00
|
|
|
~DebugInputTapConnection() = default;
|
2021-10-06 23:11:09 +02:00
|
|
|
winrt::fire_and_forget Start()
|
2020-03-26 23:33:47 +01:00
|
|
|
{
|
2021-10-06 23:11:09 +02:00
|
|
|
// GH#11282: It's possible that we're about to be started, _before_
|
|
|
|
// our paired connection is started. Both will get Start()'ed when
|
|
|
|
// their owning TermControl is finally laid out. However, if we're
|
|
|
|
// started first, then we'll immediately start printing to the other
|
|
|
|
// control as well, which might not have initialized yet. If we do
|
|
|
|
// that, we'll explode.
|
|
|
|
//
|
|
|
|
// Instead, wait here until the other connection is started too,
|
|
|
|
// before actually starting the connection to the client app. This
|
|
|
|
// will ensure both controls are initialized before the client app
|
|
|
|
// is.
|
|
|
|
co_await winrt::resume_background();
|
|
|
|
_pairedTap->_start.wait();
|
|
|
|
|
2020-03-26 23:33:47 +01:00
|
|
|
_wrappedConnection.Start();
|
|
|
|
}
|
|
|
|
void WriteInput(hstring const& data)
|
|
|
|
{
|
|
|
|
_pairedTap->_PrintInput(data);
|
|
|
|
_wrappedConnection.WriteInput(data);
|
|
|
|
}
|
|
|
|
void Resize(uint32_t rows, uint32_t columns) { _wrappedConnection.Resize(rows, columns); }
|
|
|
|
void Close() { _wrappedConnection.Close(); }
|
|
|
|
winrt::event_token TerminalOutput(TerminalOutputHandler const& args) { return _wrappedConnection.TerminalOutput(args); };
|
|
|
|
void TerminalOutput(winrt::event_token const& token) noexcept { _wrappedConnection.TerminalOutput(token); };
|
|
|
|
winrt::event_token StateChanged(TypedEventHandler<ITerminalConnection, IInspectable> const& handler) { return _wrappedConnection.StateChanged(handler); };
|
|
|
|
void StateChanged(winrt::event_token const& token) noexcept { _wrappedConnection.StateChanged(token); };
|
|
|
|
ConnectionState State() const noexcept { return _wrappedConnection.State(); }
|
|
|
|
|
|
|
|
private:
|
|
|
|
winrt::com_ptr<DebugTapConnection> _pairedTap;
|
|
|
|
ITerminalConnection _wrappedConnection;
|
|
|
|
};
|
|
|
|
|
|
|
|
DebugTapConnection::DebugTapConnection(ITerminalConnection wrappedConnection)
|
|
|
|
{
|
|
|
|
_outputRevoker = wrappedConnection.TerminalOutput(winrt::auto_revoke, { this, &DebugTapConnection::_OutputHandler });
|
|
|
|
_stateChangedRevoker = wrappedConnection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*e*/) {
|
|
|
|
_StateChangedHandlers(*this, nullptr);
|
|
|
|
});
|
|
|
|
_wrappedConnection = wrappedConnection;
|
|
|
|
}
|
|
|
|
|
|
|
|
DebugTapConnection::~DebugTapConnection()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void DebugTapConnection::Start()
|
|
|
|
{
|
|
|
|
// presume the wrapped connection is started.
|
2021-10-06 23:11:09 +02:00
|
|
|
|
|
|
|
// This is explained in the comment for GH#11282 above.
|
|
|
|
_start.count_down();
|
2020-03-26 23:33:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void DebugTapConnection::WriteInput(hstring const& data)
|
|
|
|
{
|
|
|
|
// If the user types into the tap side, forward it to the input side
|
|
|
|
if (auto strongInput{ _inputSide.get() })
|
|
|
|
{
|
|
|
|
auto inputAsTap{ winrt::get_self<DebugInputTapConnection>(strongInput) };
|
|
|
|
inputAsTap->WriteInput(data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DebugTapConnection::Resize(uint32_t /*rows*/, uint32_t /*columns*/)
|
|
|
|
{
|
|
|
|
// no resize events are propagated
|
|
|
|
}
|
|
|
|
|
|
|
|
void DebugTapConnection::Close()
|
|
|
|
{
|
|
|
|
_outputRevoker.revoke();
|
|
|
|
_stateChangedRevoker.revoke();
|
|
|
|
_wrappedConnection = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
ConnectionState DebugTapConnection::State() const noexcept
|
|
|
|
{
|
|
|
|
if (auto strongConnection{ _wrappedConnection.get() })
|
|
|
|
{
|
|
|
|
return strongConnection.State();
|
|
|
|
}
|
|
|
|
return ConnectionState::Failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DebugTapConnection::_OutputHandler(const hstring str)
|
|
|
|
{
|
2020-10-06 18:56:59 +02:00
|
|
|
_TerminalOutputHandlers(til::visualize_control_codes(str));
|
2020-03-26 23:33:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Called by the DebugInputTapConnection to print user input
|
|
|
|
void DebugTapConnection::_PrintInput(const hstring& str)
|
|
|
|
{
|
2020-10-06 18:56:59 +02:00
|
|
|
auto clean{ til::visualize_control_codes(str) };
|
2020-03-26 23:33:47 +01:00
|
|
|
auto formatted{ wil::str_printf<std::wstring>(L"\x1b[91m%ls\x1b[m", clean.data()) };
|
|
|
|
_TerminalOutputHandlers(formatted);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wire us up so that we can forward input through
|
|
|
|
void DebugTapConnection::SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap)
|
|
|
|
{
|
|
|
|
_inputSide = inputTap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Function Description
|
|
|
|
// - Takes one connection and returns two connections:
|
|
|
|
// 1. One that can be used in place of the original connection (wrapped)
|
|
|
|
// 2. One that will print raw VT sequences sent into and received _from_ the original connection.
|
|
|
|
std::tuple<ITerminalConnection, ITerminalConnection> OpenDebugTapConnection(ITerminalConnection baseConnection)
|
|
|
|
{
|
|
|
|
using namespace winrt::Microsoft::TerminalApp::implementation;
|
|
|
|
auto debugSide{ winrt::make_self<DebugTapConnection>(baseConnection) };
|
|
|
|
auto inputSide{ winrt::make_self<DebugInputTapConnection>(debugSide, baseConnection) };
|
|
|
|
debugSide->SetInputTap(*inputSide);
|
|
|
|
std::tuple<ITerminalConnection, ITerminalConnection> p{ *inputSide, *debugSide };
|
|
|
|
return p;
|
|
|
|
}
|