2019-05-02 15:29:04 -07:00
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
|
|
|
// Licensed under the MIT license.
|
|
|
|
|
|
|
|
|
|
#include "pch.h"
|
|
|
|
|
#include "ConhostConnection.h"
|
|
|
|
|
#include "windows.h"
|
|
|
|
|
#include <sstream>
|
|
|
|
|
// STARTF_USESTDHANDLES is only defined in WINAPI_PARTITION_DESKTOP
|
|
|
|
|
// We're just gonna manually define it for this prototyping code
|
|
|
|
|
#ifndef STARTF_USESTDHANDLES
|
|
|
|
|
#define STARTF_USESTDHANDLES 0x00000100
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <conpty-universal.h>
|
|
|
|
|
|
|
|
|
|
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
|
|
|
|
{
|
|
|
|
|
ConhostConnection::ConhostConnection(hstring const& commandline,
|
|
|
|
|
hstring const& startingDirectory,
|
|
|
|
|
uint32_t initialRows,
|
|
|
|
|
uint32_t initialCols) :
|
|
|
|
|
_connected{ false },
|
|
|
|
|
_inPipe{ INVALID_HANDLE_VALUE },
|
|
|
|
|
_outPipe{ INVALID_HANDLE_VALUE },
|
|
|
|
|
_signalPipe{ INVALID_HANDLE_VALUE },
|
|
|
|
|
_outputThreadId{ 0 },
|
|
|
|
|
_hOutputThread{ INVALID_HANDLE_VALUE },
|
|
|
|
|
_piConhost{ 0 },
|
|
|
|
|
_closing{ false }
|
|
|
|
|
{
|
|
|
|
|
_commandline = commandline;
|
|
|
|
|
_startingDirectory = startingDirectory;
|
|
|
|
|
_initialRows = initialRows;
|
|
|
|
|
_initialCols = initialCols;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
winrt::event_token ConhostConnection::TerminalOutput(Microsoft::Terminal::TerminalConnection::TerminalOutputEventArgs const& handler)
|
|
|
|
|
{
|
|
|
|
|
return _outputHandlers.add(handler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConhostConnection::TerminalOutput(winrt::event_token const& token) noexcept
|
|
|
|
|
{
|
|
|
|
|
_outputHandlers.remove(token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
winrt::event_token ConhostConnection::TerminalDisconnected(Microsoft::Terminal::TerminalConnection::TerminalDisconnectedEventArgs const& handler)
|
|
|
|
|
{
|
|
|
|
|
return _disconnectHandlers.add(handler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConhostConnection::TerminalDisconnected(winrt::event_token const& token) noexcept
|
|
|
|
|
{
|
|
|
|
|
_disconnectHandlers.remove(token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConhostConnection::Start()
|
|
|
|
|
{
|
|
|
|
|
std::wstring cmdline = _commandline.c_str();
|
|
|
|
|
std::optional<std::wstring> startingDirectory;
|
|
|
|
|
if (!_startingDirectory.empty())
|
|
|
|
|
{
|
|
|
|
|
startingDirectory = _startingDirectory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CreateConPty(cmdline,
|
|
|
|
|
startingDirectory,
|
|
|
|
|
static_cast<short>(_initialCols),
|
|
|
|
|
static_cast<short>(_initialRows),
|
|
|
|
|
&_inPipe,
|
|
|
|
|
&_outPipe,
|
|
|
|
|
&_signalPipe,
|
|
|
|
|
&_piConhost);
|
|
|
|
|
|
|
|
|
|
_connected = true;
|
|
|
|
|
|
|
|
|
|
// Create our own output handling thread
|
2019-05-21 08:15:44 +02:00
|
|
|
// Each console needs to make sure to drain the output from its backing host.
|
2019-05-02 15:29:04 -07:00
|
|
|
_outputThreadId = (DWORD)-1;
|
|
|
|
|
_hOutputThread = CreateThread(nullptr,
|
|
|
|
|
0,
|
2019-05-17 22:32:51 +02:00
|
|
|
StaticOutputThreadProc,
|
2019-05-02 15:29:04 -07:00
|
|
|
this,
|
|
|
|
|
0,
|
|
|
|
|
&_outputThreadId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConhostConnection::WriteInput(hstring const& data)
|
|
|
|
|
{
|
|
|
|
|
if (!_connected || _closing)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// convert from UTF-16LE to UTF-8 as ConPty expects UTF-8
|
|
|
|
|
std::string str = winrt::to_string(data);
|
|
|
|
|
bool fSuccess = !!WriteFile(_inPipe, str.c_str(), (DWORD)str.length(), nullptr, nullptr);
|
|
|
|
|
fSuccess;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConhostConnection::Resize(uint32_t rows, uint32_t columns)
|
|
|
|
|
{
|
|
|
|
|
if (!_connected)
|
|
|
|
|
{
|
|
|
|
|
_initialRows = rows;
|
|
|
|
|
_initialCols = columns;
|
|
|
|
|
}
|
|
|
|
|
else if (!_closing)
|
|
|
|
|
{
|
|
|
|
|
SignalResizeWindow(_signalPipe, static_cast<unsigned short>(columns), static_cast<unsigned short>(rows));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ConhostConnection::Close()
|
|
|
|
|
{
|
|
|
|
|
if (!_connected) return;
|
|
|
|
|
if (_closing) return;
|
|
|
|
|
_closing = true;
|
|
|
|
|
// TODO:
|
|
|
|
|
// terminate the output thread
|
|
|
|
|
// Close our handles
|
|
|
|
|
// Close the Pseudoconsole
|
|
|
|
|
// terminate our processes
|
|
|
|
|
CloseHandle(_signalPipe);
|
|
|
|
|
CloseHandle(_inPipe);
|
|
|
|
|
CloseHandle(_outPipe);
|
|
|
|
|
// What? CreateThread is in app partition but TerminateThread isn't?
|
|
|
|
|
//TerminateThread(_hOutputThread, 0);
|
|
|
|
|
TerminateProcess(_piConhost.hProcess, 0);
|
|
|
|
|
CloseHandle(_piConhost.hProcess);
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-17 22:32:51 +02:00
|
|
|
DWORD WINAPI ConhostConnection::StaticOutputThreadProc(LPVOID lpParameter)
|
2019-05-02 15:29:04 -07:00
|
|
|
{
|
|
|
|
|
ConhostConnection* const pInstance = (ConhostConnection*)lpParameter;
|
|
|
|
|
return pInstance->_OutputThread();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DWORD ConhostConnection::_OutputThread()
|
|
|
|
|
{
|
|
|
|
|
const size_t bufferSize = 4096;
|
|
|
|
|
BYTE buffer[bufferSize];
|
|
|
|
|
DWORD dwRead;
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
dwRead = 0;
|
|
|
|
|
bool fSuccess = false;
|
|
|
|
|
|
|
|
|
|
fSuccess = !!ReadFile(_outPipe, buffer, bufferSize, &dwRead, nullptr);
|
|
|
|
|
if (!fSuccess)
|
|
|
|
|
{
|
|
|
|
|
if (_closing)
|
|
|
|
|
{
|
|
|
|
|
// This is okay, break out to kill the thread
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_disconnectHandlers();
|
|
|
|
|
return (DWORD)-1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
if (dwRead == 0) continue;
|
|
|
|
|
// Convert buffer to hstring
|
|
|
|
|
char* pchStr = (char*)(buffer);
|
|
|
|
|
std::string str{pchStr, dwRead};
|
|
|
|
|
auto hstr = winrt::to_hstring(str);
|
|
|
|
|
|
|
|
|
|
// Pass the output to our registered event handlers
|
|
|
|
|
_outputHandlers(hstr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|