Skip to content

Commit

Permalink
Blindly, I think this is the conpty half of the ask
Browse files Browse the repository at this point in the history
  • Loading branch information
zadjii-msft committed Jul 29, 2021
1 parent 4b45bb8 commit 2436cc0
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 106 deletions.
10 changes: 10 additions & 0 deletions src/cascadia/TerminalConnection/ConptyConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,16 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
}

void ConptyConnection::ClearBuffer()
{
// If we haven't started connecting at all, it's still fair to update
// the initial rows and columns before we set things up.
if (_isConnected())
{
THROW_IF_FAILED(ConptyClearPseudoConsole(_hPC.get()));
}
}

void ConptyConnection::Close() noexcept
try
{
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalConnection/ConptyConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void WriteInput(hstring const& data);
void Resize(uint32_t rows, uint32_t columns);
void Close() noexcept;
void ClearBuffer();

winrt::guid Guid() const noexcept;

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalConnection/ConptyConnection.idl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Microsoft.Terminal.TerminalConnection
{
ConptyConnection();
Guid Guid { get; };
void ClearBuffer();

static event NewConnectionHandler NewConnection;
static void StartInboundListener();
Expand Down
20 changes: 20 additions & 0 deletions src/host/PtySignalInputThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ void PtySignalInputThread::ConnectConsole() noexcept
{
switch (signalId)
{
case PtySignal::ClearBuffer:
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });

// If the client app hasn't yet connected, stash the new size in the launchArgs.
// We'll later use the value in launchArgs to set up the console buffer
// We must be under lock here to ensure that someone else doesn't come in
// and set with `ConnectConsole` while we're looking and modifying this.
if (_consoleConnected)
{
_DoClearBuffer();
}
break;
}
case PtySignal::ResizeWindow:
{
ResizeWindowData resizeMsg = { 0 };
Expand Down Expand Up @@ -128,6 +143,11 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data)
}
}

void PtySignalInputThread::_DoClearBuffer()
{
DispatchCommon::s_EraseInDisplay(*_pConApi, DispatchTypes::EraseType::All);
}

// Method Description:
// - Retrieves bytes from the file stream and exits or throws errors should the pipe state
// be compromised.
Expand Down
2 changes: 2 additions & 0 deletions src/host/PtySignalInputThread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace Microsoft::Console
private:
enum class PtySignal : unsigned short
{
ClearBuffer = 2,
ResizeWindow = 8
};

Expand All @@ -47,6 +48,7 @@ namespace Microsoft::Console
[[nodiscard]] HRESULT _InputThread();
bool _GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, const DWORD cbBuffer);
void _DoResizeWindow(const ResizeWindowData& data);
void _DoClearBuffer();
void _Shutdown();

wil::unique_hfile _hFile;
Expand Down
1 change: 1 addition & 0 deletions src/inc/conpty.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <memory>
#pragma once

const unsigned int PTY_SIGNAL_CLEAR_WINDOW = 2u;
const unsigned int PTY_SIGNAL_RESIZE_WINDOW = 8u;

HRESULT CreateConPty(const std::wstring& cmdline, // _In_
Expand Down
110 changes: 110 additions & 0 deletions src/terminal/adapter/DispatchCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,113 @@ bool DispatchCommon::s_SuppressResizeRepaint(ConGetSet& conApi)
{
return conApi.PrivateSuppressResizeRepaint();
}

bool DispatchCommon::s_EraseInDisplay(ConGetSet& conApi, const DispatchTypes::EraseType eraseType)
{
RETURN_BOOL_IF_FALSE(eraseType <= DispatchTypes::EraseType::Scrollback);

// First things first. If this is a "Scrollback" clear, then just do that.
// Scrollback clears erase everything in the "scrollback" of a *nix terminal
// Everything that's scrolled off the screen so far.
// Or if it's an Erase All, then we also need to handle that specially
// by moving the current contents of the viewport into the scrollback.
if (eraseType == DispatchTypes::EraseType::Scrollback)
{
const bool eraseScrollbackResult = _EraseScrollback();
// GH#2715 - If this succeeded, but we're in a conpty, return `false` to
// make the state machine propagate this ED sequence to the connected
// terminal application. While we're in conpty mode, we don't really
// have a scrollback, but the attached terminal might.
const bool isPty = _pConApi->IsConsolePty();
return eraseScrollbackResult && (!isPty);
}
else if (eraseType == DispatchTypes::EraseType::All)
{
// GH#5683 - If this succeeded, but we're in a conpty, return `false` to
// make the state machine propagate this ED sequence to the connected
// terminal application. While we're in conpty mode, when the client
// requests a Erase All operation, we need to manually tell the
// connected terminal to do the same thing, so that the terminal will
// move it's own buffer contents into the scrollback.
const bool eraseAllResult = _EraseAll();
const bool isPty = _pConApi->IsConsolePty();
return eraseAllResult && (!isPty);
}

CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
// Make sure to reset the viewport (with MoveToBottom )to where it was
// before the user scrolled the console output
bool success = (_pConApi->MoveToBottom() && _pConApi->GetConsoleScreenBufferInfoEx(csbiex));

if (success)
{
// When erasing the display, every line that is erased in full should be
// reset to single width. When erasing to the end, this could include
// the current line, if the cursor is in the first column. When erasing
// from the beginning, though, the current line would never be included,
// because the cursor could never be in the rightmost column (assuming
// the line is double width).
if (eraseType == DispatchTypes::EraseType::FromBeginning)
{
const auto endRow = csbiex.dwCursorPosition.Y;
_pConApi->PrivateResetLineRenditionRange(csbiex.srWindow.Top, endRow);
}
if (eraseType == DispatchTypes::EraseType::ToEnd)
{
const auto startRow = csbiex.dwCursorPosition.Y + (csbiex.dwCursorPosition.X > 0 ? 1 : 0);
_pConApi->PrivateResetLineRenditionRange(startRow, csbiex.srWindow.Bottom);
}

// What we need to erase is grouped into 3 types:
// 1. Lines before cursor
// 2. Cursor Line
// 3. Lines after cursor
// We erase one or more of these based on the erase type:
// A. FromBeginning - Erase 1 and Some of 2.
// B. ToEnd - Erase some of 2 and 3.
// C. All - Erase 1, 2, and 3.

// 1. Lines before cursor line
if (eraseType == DispatchTypes::EraseType::FromBeginning)
{
// For beginning and all, erase all complete lines before (above vertically) from the cursor position.
for (SHORT startLine = csbiex.srWindow.Top; startLine < csbiex.dwCursorPosition.Y; startLine++)
{
success = _EraseSingleLineHelper(csbiex, DispatchTypes::EraseType::All, startLine);

if (!success)
{
break;
}
}
}

if (success)
{
// 2. Cursor Line
success = _EraseSingleLineHelper(csbiex, eraseType, csbiex.dwCursorPosition.Y);
}

if (success)
{
// 3. Lines after cursor line
if (eraseType == DispatchTypes::EraseType::ToEnd)
{
// For beginning and all, erase all complete lines after (below vertically) the cursor position.
// Remember that the viewport bottom value is 1 beyond the viewable area of the viewport.
for (SHORT startLine = csbiex.dwCursorPosition.Y + 1; startLine < csbiex.srWindow.Bottom; startLine++)
{
success = _EraseSingleLineHelper(csbiex, DispatchTypes::EraseType::All, startLine);

if (!success)
{
break;
}
}
}
}
}

return success;
}
1 change: 1 addition & 0 deletions src/terminal/adapter/DispatchCommon.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ namespace Microsoft::Console::VirtualTerminal
static bool s_RefreshWindow(ConGetSet& conApi);

static bool s_SuppressResizeRepaint(ConGetSet& conApi);
static bool s_EraseInDisplay(ConGetSet& conApi, const DispatchTypes::EraseType eraseType);
};
}
107 changes: 1 addition & 106 deletions src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,112 +583,7 @@ bool AdaptDispatch::EraseCharacters(const size_t numChars)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType)
{
RETURN_BOOL_IF_FALSE(eraseType <= DispatchTypes::EraseType::Scrollback);

// First things first. If this is a "Scrollback" clear, then just do that.
// Scrollback clears erase everything in the "scrollback" of a *nix terminal
// Everything that's scrolled off the screen so far.
// Or if it's an Erase All, then we also need to handle that specially
// by moving the current contents of the viewport into the scrollback.
if (eraseType == DispatchTypes::EraseType::Scrollback)
{
const bool eraseScrollbackResult = _EraseScrollback();
// GH#2715 - If this succeeded, but we're in a conpty, return `false` to
// make the state machine propagate this ED sequence to the connected
// terminal application. While we're in conpty mode, we don't really
// have a scrollback, but the attached terminal might.
const bool isPty = _pConApi->IsConsolePty();
return eraseScrollbackResult && (!isPty);
}
else if (eraseType == DispatchTypes::EraseType::All)
{
// GH#5683 - If this succeeded, but we're in a conpty, return `false` to
// make the state machine propagate this ED sequence to the connected
// terminal application. While we're in conpty mode, when the client
// requests a Erase All operation, we need to manually tell the
// connected terminal to do the same thing, so that the terminal will
// move it's own buffer contents into the scrollback.
const bool eraseAllResult = _EraseAll();
const bool isPty = _pConApi->IsConsolePty();
return eraseAllResult && (!isPty);
}

CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
// Make sure to reset the viewport (with MoveToBottom )to where it was
// before the user scrolled the console output
bool success = (_pConApi->MoveToBottom() && _pConApi->GetConsoleScreenBufferInfoEx(csbiex));

if (success)
{
// When erasing the display, every line that is erased in full should be
// reset to single width. When erasing to the end, this could include
// the current line, if the cursor is in the first column. When erasing
// from the beginning, though, the current line would never be included,
// because the cursor could never be in the rightmost column (assuming
// the line is double width).
if (eraseType == DispatchTypes::EraseType::FromBeginning)
{
const auto endRow = csbiex.dwCursorPosition.Y;
_pConApi->PrivateResetLineRenditionRange(csbiex.srWindow.Top, endRow);
}
if (eraseType == DispatchTypes::EraseType::ToEnd)
{
const auto startRow = csbiex.dwCursorPosition.Y + (csbiex.dwCursorPosition.X > 0 ? 1 : 0);
_pConApi->PrivateResetLineRenditionRange(startRow, csbiex.srWindow.Bottom);
}

// What we need to erase is grouped into 3 types:
// 1. Lines before cursor
// 2. Cursor Line
// 3. Lines after cursor
// We erase one or more of these based on the erase type:
// A. FromBeginning - Erase 1 and Some of 2.
// B. ToEnd - Erase some of 2 and 3.
// C. All - Erase 1, 2, and 3.

// 1. Lines before cursor line
if (eraseType == DispatchTypes::EraseType::FromBeginning)
{
// For beginning and all, erase all complete lines before (above vertically) from the cursor position.
for (SHORT startLine = csbiex.srWindow.Top; startLine < csbiex.dwCursorPosition.Y; startLine++)
{
success = _EraseSingleLineHelper(csbiex, DispatchTypes::EraseType::All, startLine);

if (!success)
{
break;
}
}
}

if (success)
{
// 2. Cursor Line
success = _EraseSingleLineHelper(csbiex, eraseType, csbiex.dwCursorPosition.Y);
}

if (success)
{
// 3. Lines after cursor line
if (eraseType == DispatchTypes::EraseType::ToEnd)
{
// For beginning and all, erase all complete lines after (below vertically) the cursor position.
// Remember that the viewport bottom value is 1 beyond the viewable area of the viewport.
for (SHORT startLine = csbiex.dwCursorPosition.Y + 1; startLine < csbiex.srWindow.Bottom; startLine++)
{
success = _EraseSingleLineHelper(csbiex, DispatchTypes::EraseType::All, startLine);

if (!success)
{
break;
}
}
}
}
}

return success;
return DispatchCommon::s_EraseInDisplay(*_pConApi, eraseType);
}

// Routine Description:
Expand Down
1 change: 1 addition & 0 deletions src/winconpty/dll/winconpty.def
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ EXPORTS
CreatePseudoConsole = ConptyCreatePseudoConsole
ResizePseudoConsole = ConptyResizePseudoConsole
ClosePseudoConsole = ConptyClosePseudoConsole
ClearPseudoConsole = ConptyClearPseudoConsole
34 changes: 34 additions & 0 deletions src/winconpty/winconpty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,27 @@ HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const CO
return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}

// Function Description:
// - Clears the conpty
// Arguments:
// - hSignal: A signal pipe as returned by CreateConPty.
// Return Value:
// - S_OK if the call succeeded, else an appropriate HRESULT for failing to
// write the clear message to the pty.
HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty)
{
if (pPty == nullptr)
{
return E_INVALIDARG;
}

unsigned short signalPacket[1];
signalPacket[0] = PTY_SIGNAL_CLEAR_WINDOW;

const BOOL fSuccess = WriteFile(pPty->hSignal, signalPacket, sizeof(signalPacket), nullptr, nullptr);
return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}

// Function Description:
// - This closes each of the members of a PseudoConsole. It does not free the
// data associated with the PseudoConsole. This is helpful for testing,
Expand Down Expand Up @@ -385,6 +406,19 @@ extern "C" HRESULT WINAPI ConptyResizePseudoConsole(_In_ HPCON hPC, _In_ COORD s
return hr;
}

// Function Description:
// TODO!
extern "C" HRESULT WINAPI ConptyClearPseudoConsole(_In_ HPCON hPC)
{
const PseudoConsole* const pPty = (PseudoConsole*)hPC;
HRESULT hr = pPty == nullptr ? E_INVALIDARG : S_OK;
if (SUCCEEDED(hr))
{
hr = _ClearPseudoConsole(pPty, size);
}
return hr;
}

// Function Description:
// Closes the conpty and all associated state.
// Client applications attached to the conpty will also behave as though the
Expand Down
2 changes: 2 additions & 0 deletions src/winconpty/winconpty.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ typedef struct _PseudoConsole
// Signals
// These are not defined publicly, but are used for controlling the conpty via
// the signal pipe.
#define PTY_SIGNAL_CLEAR_WINDOW (2u)
#define PTY_SIGNAL_RESIZE_WINDOW (8u)

// CreatePseudoConsole Flags
Expand All @@ -34,6 +35,7 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken,
_Inout_ PseudoConsole* pPty);

HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const COORD size);
HRESULT _ClearPseudoConsole(_In_ const PseudoConsole* const pPty);
void _ClosePseudoConsoleMembers(_In_ PseudoConsole* pPty);
VOID _ClosePseudoConsole(_In_ PseudoConsole* pPty);

Expand Down

0 comments on commit 2436cc0

Please sign in to comment.