Skip to content

Commit

Permalink
Add support for start /B and FreeConsole
Browse files Browse the repository at this point in the history
  • Loading branch information
lhecker committed Dec 13, 2022
1 parent 68cce10 commit 3e011e6
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 84 deletions.
52 changes: 20 additions & 32 deletions src/cascadia/TerminalConnection/ConptyConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
// Licensed under the MIT license.

#include "pch.h"

#include "ConptyConnection.h"

#include <conpty-static.h>
#include <winternl.h>

#include "ConptyConnection.g.cpp"
#include "CTerminalHandoff.h"

#include "../../types/inc/utils.hpp"
#include "../../types/inc/Environment.hpp"
#include "LibraryResources.h"
#include "../../types/inc/Environment.hpp"
#include "../../types/inc/utils.hpp"

#include "ConptyConnection.g.cpp"

using namespace ::Microsoft::Console;
using namespace std::string_view_literals;
Expand Down Expand Up @@ -343,6 +343,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}

THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(til::unwrap_coord_size(dimensions), flags, &_inPipe, &_outPipe, &_hPC));
THROW_IF_FAILED(ConptySetPseudoConsoleAutoExit(_hPC.get()));

if (_initialParentHwnd != 0)
{
Expand Down Expand Up @@ -371,6 +372,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));

THROW_IF_FAILED(ConptySetPseudoConsoleAutoExit(_hPC.get()));
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), til::unwrap_coord_size(dimensions)));
THROW_IF_FAILED(ConptyReparentPseudoConsole(_hPC.get(), reinterpret_cast<HWND>(_initialParentHwnd)));

Expand Down Expand Up @@ -404,19 +406,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation

LOG_IF_FAILED(SetThreadDescription(_hOutputThread.get(), L"ConptyConnection Output Thread"));

_clientExitWait.reset(CreateThreadpoolWait(
[](PTP_CALLBACK_INSTANCE /*callbackInstance*/, PVOID context, PTP_WAIT /*wait*/, TP_WAIT_RESULT /*waitResult*/) noexcept {
const auto pInstance = static_cast<ConptyConnection*>(context);
if (pInstance)
{
pInstance->_ClientTerminated();
}
},
this,
nullptr));

SetThreadpoolWait(_clientExitWait.get(), _piClient.hProcess, nullptr);

_transitionToState(ConnectionState::Connected);
}
catch (...)
Expand Down Expand Up @@ -469,20 +458,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ConptyConnection::_ClientTerminated() noexcept
try
{
if (_isStateAtOrBeyond(ConnectionState::Closing))
{
// This termination was expected.
return;
}

// EXIT POINT
DWORD exitCode{ 0 };
GetExitCodeProcess(_piClient.hProcess, &exitCode);

// Signal the closing or failure of the process.
// Load bearing. Terminating the pseudoconsole will make the output thread exit unexpectedly,
// so we need to signal entry into the correct closing state before we do that.
_transitionToState(exitCode == 0 ? ConnectionState::Closed : ConnectionState::Failed);
_transitionToState(exitCode == 0 || exitCode == STILL_ACTIVE ? ConnectionState::Closed : ConnectionState::Failed);

// Close the pseudoconsole and wait for all output to drain.
_hPC.reset();
Expand Down Expand Up @@ -569,10 +552,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
// EXIT POINT

// _clientExitWait holds a CreateThreadpoolWait() which holds a weak reference to "this".
// This manual reset() ensures we wait for it to be teared down via WaitForThreadpoolWaitCallbacks().
_clientExitWait.reset();

_hPC.reset(); // tear down the pseudoconsole (this is like clicking X on a console window)

// CloseHandle() on pipes blocks until any current WriteFile()/ReadFile() has returned.
Expand Down Expand Up @@ -657,15 +636,19 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation

if (readFail) // reading failed (we must check this first, because read will also be 0.)
{
// EXIT POINT
const auto lastError = GetLastError();
if (lastError != ERROR_BROKEN_PIPE)
if (lastError == ERROR_BROKEN_PIPE)
{
_ClientTerminated();
return S_OK;
}
else
{
// EXIT POINT
_indicateExitWithStatus(HRESULT_FROM_WIN32(lastError)); // print a message
_transitionToState(ConnectionState::Failed);
return gsl::narrow_cast<DWORD>(HRESULT_FROM_WIN32(lastError));
}
// else we call convertUTF8ChunkToUTF16 with an empty string_view to convert possible remaining partials to U+FFFD
}

const auto result{ til::u8u16(std::string_view{ _buffer.data(), read }, _u16Str, _u8State) };
Expand Down Expand Up @@ -710,6 +693,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
winrt::event_token ConptyConnection::NewConnection(const NewConnectionHandler& handler) { return _newConnectionHandlers.add(handler); };
void ConptyConnection::NewConnection(const winrt::event_token& token) { _newConnectionHandlers.remove(token); };

void ConptyConnection::closePseudoConsoleAsync(HPCON hPC) noexcept
{
::ConptyClosePseudoConsoleTimeout(hPC, 0);
}

HRESULT ConptyConnection::NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client, TERMINAL_STARTUP_INFO startupInfo) noexcept
try
{
Expand Down
12 changes: 2 additions & 10 deletions src/cascadia/TerminalConnection/ConptyConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,8 @@
#include "ConptyConnection.g.h"
#include "ConnectionStateHolder.h"

#include <conpty-static.h>

#include "ITerminalHandoff.h"

namespace wil
{
// These belong in WIL upstream, so when we reingest the change that has them we'll get rid of ours.
using unique_static_pseudoconsole_handle = wil::unique_any<HPCON, decltype(&::ConptyClosePseudoConsole), ::ConptyClosePseudoConsoleNoWait>;
}

namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
struct ConptyConnection : ConptyConnectionT<ConptyConnection>, ConnectionStateHolder<ConptyConnection>
Expand Down Expand Up @@ -65,6 +57,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);

private:
static void closePseudoConsoleAsync(HPCON hPC) noexcept;
static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client, TERMINAL_STARTUP_INFO startupInfo) noexcept;
static winrt::hstring _commandlineFromProcess(HANDLE process);

Expand All @@ -90,8 +83,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
wil::unique_hfile _outPipe; // The pipe for reading output from
wil::unique_handle _hOutputThread;
wil::unique_process_information _piClient;
wil::unique_static_pseudoconsole_handle _hPC;
wil::unique_threadpool_wait _clientExitWait;
wil::unique_any<HPCON, decltype(closePseudoConsoleAsync), closePseudoConsoleAsync> _hPC;

til::u8state _u8State{};
std::wstring _u16Str{};
Expand Down
18 changes: 16 additions & 2 deletions src/host/PtySignalInputThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ try
_DoSetWindowParent(reparentMessage);
break;
}
case PtySignal::SetAutoExit:
{
_DoSetAutoExit();
break;
}
default:
THROW_HR(E_UNEXPECTED);
}
Expand Down Expand Up @@ -281,6 +286,15 @@ void PtySignalInputThread::_DoSetWindowParent(const SetParentData& data)
}
}

void PtySignalInputThread::_DoSetAutoExit()
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });

auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetVtIo()->SetAutoExit();
}

// Method Description:
// - Retrieves bytes from the file stream and exits or throws errors should the pipe state
// be compromised.
Expand Down Expand Up @@ -350,6 +364,6 @@ void PtySignalInputThread::_DoSetWindowParent(const SetParentData& data)
// - <none>
void PtySignalInputThread::_Shutdown()
{
// Trigger process shutdown.
CloseConsoleProcessState();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetVtIo()->SendCloseEvent();
}
2 changes: 2 additions & 0 deletions src/host/PtySignalInputThread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ namespace Microsoft::Console
ShowHideWindow = 1,
ClearBuffer = 2,
SetParent = 3,
SetAutoExit = 4,
ResizeWindow = 8
};

Expand All @@ -64,6 +65,7 @@ namespace Microsoft::Console
[[nodiscard]] bool _GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, const DWORD cbBuffer);
void _DoResizeWindow(const ResizeWindowData& data);
void _DoSetWindowParent(const SetParentData& data);
void _DoSetAutoExit();
void _DoClearBuffer() const;
void _DoShowHide(const ShowHideData& data);
void _Shutdown();
Expand Down
34 changes: 27 additions & 7 deletions src/host/VtIo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -443,10 +443,26 @@ void VtIo::SetWindowVisibility(bool showOrHide) noexcept
return hr;
}

void VtIo::SetAutoExit() noexcept
{
_autoExit = true;
}

void VtIo::HandleRootClientRemoved() const noexcept
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
assert(gci.IsConsoleLocked());

if (_autoExit && gci.ProcessHandleList.IsEmpty())
{
ServiceLocator::RundownAndExit(STATUS_SUCCESS);
}
}

void VtIo::CloseInput()
{
_pVtInputThread = nullptr;
_shutdownNow();
SendCloseEvent();
}

void VtIo::CloseOutput()
Expand All @@ -455,14 +471,18 @@ void VtIo::CloseOutput()
g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(nullptr);
}

void VtIo::_shutdownNow()
void VtIo::SendCloseEvent()
{
// At this point, we no longer have a renderer or inthread. So we've
// effectively been disconnected from the terminal.
LockConsole();
const auto unlock = wil::scope_exit([] { UnlockConsole(); });

// If we have any remaining attached processes, this will prepare us to send a ctrl+close to them
// if we don't, this will cause us to rundown and exit.
CloseConsoleProcessState();
// This function is called when the ConPTY signal pipe is closed (PtySignalInputThread) and when the input
// pipe is closed (VtIo). Usually these two happen at about the same time. This if condition is a bit of
// a premature optimization and prevents us from sending out a CTRL_CLOSE_EVENT right after another.
if (!std::exchange(_closeEventSent, true))
{
CloseConsoleProcessState();
}
}

// Method Description:
Expand Down
9 changes: 5 additions & 4 deletions src/host/VtIo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ namespace Microsoft::Console::VirtualTerminal
[[nodiscard]] HRESULT StartIfNeeded();

[[nodiscard]] static HRESULT ParseIoMode(const std::wstring& VtMode, _Out_ VtIoMode& ioMode);

[[nodiscard]] HRESULT SuppressResizeRepaint();
[[nodiscard]] HRESULT SetCursorPosition(const til::point coordCursor);

[[nodiscard]] HRESULT SwitchScreenBuffer(const bool useAltBuffer);
void SetAutoExit() noexcept;
void HandleRootClientRemoved() const noexcept;
void SendCloseEvent();

void CloseInput();
void CloseOutput();
Expand Down Expand Up @@ -71,15 +72,15 @@ namespace Microsoft::Console::VirtualTerminal
bool _resizeQuirk{ false };
bool _win32InputMode{ false };
bool _passthroughMode{ false };
bool _autoExit{ false };
bool _closeEventSent{ false };

std::unique_ptr<Microsoft::Console::Render::VtEngine> _pVtRenderEngine;
std::unique_ptr<Microsoft::Console::VtInputThread> _pVtInputThread;
std::unique_ptr<Microsoft::Console::PtySignalInputThread> _pPtySignalInputThread;

[[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, const std::wstring& VtMode, _In_opt_ const HANDLE SignalHandle);

void _shutdownNow();

#ifdef UNIT_TESTING
friend class VtIoTests;
#endif
Expand Down
3 changes: 0 additions & 3 deletions src/host/output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -499,9 +499,6 @@ void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo)
// TODO: MSFT 9450717 This should join the ProcessList class when CtrlEvents become moved into the server. https://osgvsowi/9450717
void CloseConsoleProcessState()
{
LockConsole();
const auto unlock = wil::scope_exit([] { UnlockConsole(); });

auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();

// If there are no connected processes, sending control events is pointless as there's no one do send them to. In
Expand Down
8 changes: 6 additions & 2 deletions src/host/srvinit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "renderFontDefaults.hpp"

#include "ApiRoutines.h"
#include "output.h"

#include "../types/inc/GlyphWidth.hpp"

Expand Down Expand Up @@ -276,11 +277,14 @@ static bool s_IsOnDesktop()

if (fRecomputeOwner)
{
auto pWindow = ServiceLocator::LocateConsoleWindow();
if (pWindow != nullptr)
if (const auto pWindow = ServiceLocator::LocateConsoleWindow())
{
pWindow->SetOwner();
}
if (gci.IsInVtIoMode())
{
gci.GetVtIo()->HandleRootClientRemoved();
}
}

UnlockConsole();
Expand Down
8 changes: 2 additions & 6 deletions src/inc/conpty-static.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,15 @@
#define PSEUDOCONSOLE_PASSTHROUGH_MODE (8u)

CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);

CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsoleAsUser(HANDLE hToken, COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);

CONPTY_EXPORT HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size);

CONPTY_EXPORT HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC);

CONPTY_EXPORT HRESULT WINAPI ConptyShowHidePseudoConsole(HPCON hPC, bool show);

CONPTY_EXPORT HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent);
CONPTY_EXPORT HRESULT WINAPI ConptySetPseudoConsoleAutoExit(HPCON hPC);

CONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsole(HPCON hPC);

CONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsoleNoWait(HPCON hPC);
CONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsoleTimeout(HPCON hPC, DWORD dwMilliseconds);

CONPTY_EXPORT HRESULT WINAPI ConptyPackPseudoConsole(HANDLE hServerProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC);
3 changes: 2 additions & 1 deletion src/winconpty/dll/winconpty.def
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ EXPORTS
ConptyCreatePseudoConsoleAsUser
ConptyResizePseudoConsole
ConptyClosePseudoConsole
ConptyClosePseudoConsoleNoWait
ConptyClosePseudoConsoleTimeout
ConptyClearPseudoConsole
ConptyShowHidePseudoConsole
ConptyReparentPseudoConsole
ConptySetPseudoConsoleAutoExit
ConptyPackPseudoConsole

; Compatibility aliases for P/Invoke; only required for compatibility
Expand Down
Loading

0 comments on commit 3e011e6

Please sign in to comment.