Skip to content

Commit

Permalink
Use DECCRA/DECFRA for ScrollConsoleScreenBuffer (#17747)
Browse files Browse the repository at this point in the history
This adds logic to get the DA1 report from the hosting terminal on
startup. We then use the information to figure out if it supports
rectangular area operations. If so, we can use DECCRA/DECFRA to
implement ScrollConsoleScreenBuffer.

This additionally changes `ScrollConsoleScreenBuffer` to always
forbid control characters as the fill character, even in conhost
(via `VtIo::SanitizeUCS2`). My hope is that this makes the API
more consistent and robust as it avoids another source for
invisible control characters in the text buffer.

Part of #17643

## Validation Steps Performed
* New tests ✅
  • Loading branch information
lhecker authored Aug 23, 2024
1 parent 0a91023 commit 040f261
Show file tree
Hide file tree
Showing 14 changed files with 505 additions and 182 deletions.
21 changes: 6 additions & 15 deletions .github/actions/spelling/expect/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ ABORTIFHUNG
ACCESSTOKEN
acidev
ACIOSS
ACover
acp
actctx
ACTCTXW
Expand Down Expand Up @@ -87,6 +86,7 @@ Autowrap
AVerify
awch
azurecr
AZZ
backgrounded
Backgrounder
backgrounding
Expand Down Expand Up @@ -180,7 +180,6 @@ CFuzz
cgscrn
chafa
changelists
charinfo
CHARSETINFO
chh
chshdng
Expand Down Expand Up @@ -264,7 +263,6 @@ consolegit
consolehost
CONSOLEIME
consoleinternal
Consoleroot
CONSOLESETFOREGROUND
consoletaeftemplates
consoleuwp
Expand Down Expand Up @@ -386,7 +384,7 @@ DECCIR
DECCKM
DECCKSR
DECCOLM
DECCRA
deccra
DECCTR
DECDC
DECDHL
Expand All @@ -398,7 +396,7 @@ DECEKBD
DECERA
DECFI
DECFNK
DECFRA
decfra
DECGCI
DECGCR
DECGNL
Expand Down Expand Up @@ -727,7 +725,6 @@ GHIJKL
gitcheckin
gitfilters
gitlab
gitmodules
gle
GLOBALFOCUS
GLYPHENTRY
Expand Down Expand Up @@ -1021,7 +1018,6 @@ lstatus
lstrcmp
lstrcmpi
LTEXT
LTLTLTLTL
ltsc
LUID
luma
Expand Down Expand Up @@ -1116,7 +1112,6 @@ msrc
MSVCRTD
MTSM
Munged
munges
murmurhash
muxes
myapplet
Expand Down Expand Up @@ -1218,7 +1213,6 @@ ntlpcapi
ntm
ntrtl
ntstatus
NTSYSCALLAPI
nttree
nturtl
ntuser
Expand Down Expand Up @@ -1526,7 +1520,6 @@ rftp
rgbi
RGBQUAD
rgbs
rgci
rgfae
rgfte
rgn
Expand Down Expand Up @@ -1604,6 +1597,7 @@ SELECTALL
SELECTEDFONT
SELECTSTRING
Selfhosters
Serbo
SERVERDLL
SETACTIVE
SETBUDDYINT
Expand Down Expand Up @@ -1832,8 +1826,6 @@ TOPDOWNDIB
TOpt
tosign
touchpad
Tpp
Tpqrst
tracelogging
traceviewpp
trackbar
Expand Down Expand Up @@ -1958,7 +1950,6 @@ VPACKMANIFESTDIRECTORY
VPR
VREDRAW
vsc
vsconfig
vscprintf
VSCROLL
vsdevshell
Expand Down Expand Up @@ -2000,7 +1991,6 @@ wcswidth
wddm
wddmcon
WDDMCONSOLECONTEXT
WDK
wdm
webpage
websites
Expand Down Expand Up @@ -2074,7 +2064,6 @@ winuserp
WINVER
wistd
wmain
wmemory
WMSZ
wnd
WNDALLOC
Expand Down Expand Up @@ -2173,6 +2162,7 @@ yact
YCast
YCENTER
YCount
yizz
YLimit
YPan
YSubstantial
Expand All @@ -2186,3 +2176,4 @@ ZCtrl
ZWJs
ZYXWVU
ZYXWVUTd
zzf
4 changes: 2 additions & 2 deletions src/host/VtInputThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ void VtInputThread::_InputThread()
return S_OK;
}

void VtInputThread::WaitUntilDSR(DWORD timeout) const noexcept
til::enumset<DeviceAttribute, uint64_t> VtInputThread::WaitUntilDA1(DWORD timeout) const noexcept
{
const auto& engine = static_cast<InputStateMachineEngine&>(_pInputStateMachine->Engine());
engine.WaitUntilDSR(timeout);
return engine.WaitUntilDA1(timeout);
}
7 changes: 6 additions & 1 deletion src/host/VtInputThread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ Author(s):

namespace Microsoft::Console
{
namespace VirtualTerminal
{
enum class DeviceAttribute : uint64_t;
}

class VtInputThread
{
public:
VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor);

[[nodiscard]] HRESULT Start();
void WaitUntilDSR(DWORD timeout) const noexcept;
til::enumset<VirtualTerminal::DeviceAttribute, uint64_t> WaitUntilDA1(DWORD timeout) const noexcept;

private:
static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter);
Expand Down
109 changes: 61 additions & 48 deletions src/host/VtIo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,38 +163,37 @@ bool VtIo::IsUsingVt() const
{
Writer writer{ this };

// MSFT: 15813316
// If the terminal application wants us to inherit the cursor position,
// we're going to emit a VT sequence to ask for the cursor position.
// If we get a response, the InteractDispatch will call SetCursorPosition,
// which will call to our VtIo::SetCursorPosition method.
//
// By sending the request before sending the DA1 one, we can simply
// wait for the DA1 response below and effectively wait for both.
if (_lookingForCursorPosition)
{
writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR)
}

// GH#4999 - Send a sequence to the connected terminal to request
// win32-input-mode from them. This will enable the connected terminal to
// send us full INPUT_RECORDs as input. If the terminal doesn't understand
// this sequence, it'll just ignore it.

writer.WriteUTF8(
"\x1b[c" // DA1 Report (Primary Device Attributes)
"\x1b[?1004h" // Focus Event Mode
"\x1b[?9001h" // Win32 Input Mode
);

// MSFT: 15813316
// If the terminal application wants us to inherit the cursor position,
// we're going to emit a VT sequence to ask for the cursor position, then
// wait 1s until we get a response.
// If we get a response, the InteractDispatch will call SetCursorPosition,
// which will call to our VtIo::SetCursorPosition method.
if (_lookingForCursorPosition)
{
writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR)
}

writer.Submit();
}

if (_lookingForCursorPosition)
{
_lookingForCursorPosition = false;

// Allow the input thread to momentarily gain the console lock.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto suspension = gci.SuspendLock();
_pVtInputThread->WaitUntilDSR(3000);
_deviceAttributes = _pVtInputThread->WaitUntilDA1(3000);
}

if (_pPtySignalInputThread)
Expand All @@ -211,6 +210,16 @@ bool VtIo::IsUsingVt() const
return S_OK;
}

void VtIo::SetDeviceAttributes(const til::enumset<DeviceAttribute, uint64_t> attributes) noexcept
{
_deviceAttributes = attributes;
}

til::enumset<DeviceAttribute, uint64_t> VtIo::GetDeviceAttributes() const noexcept
{
return _deviceAttributes;
}

// Method Description:
// - Create our pseudo window. This is exclusively called by
// ConsoleInputThreadProcWin32 on the console input thread.
Expand Down Expand Up @@ -359,6 +368,40 @@ void VtIo::FormatAttributes(std::wstring& target, const TextAttribute& attribute
target.append(bufW, len);
}

wchar_t VtIo::SanitizeUCS2(wchar_t ch)
{
// If any of the values in the buffer are C0 or C1 controls, we need to
// convert them to printable codepoints, otherwise they'll end up being
// evaluated as control characters by the receiving terminal. We use the
// DOS 437 code page for the C0 controls and DEL, and just a `?` for the
// C1 controls, since that's what you would most likely have seen in the
// legacy v1 console with raster fonts.
if (ch < 0x20)
{
static constexpr wchar_t lut[] = {
// clang-format off
L' ', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'',
L'', L'', L'', L'', L'', L'§', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'',
// clang-format on
};
ch = lut[ch];
}
else if (ch == 0x7F)
{
ch = L'';
}
else if (ch > 0x7F && ch < 0xA0)
{
ch = L'?';
}
else if (til::is_surrogate(ch))
{
ch = UNICODE_REPLACEMENT;
}

return ch;
}

VtIo::Writer::Writer(VtIo* io) noexcept :
_io{ io }
{
Expand Down Expand Up @@ -592,7 +635,7 @@ void VtIo::Writer::WriteUTF16StripControlChars(std::wstring_view str) const

for (it = begControlChars; it != end && IsControlCharacter(*it); ++it)
{
WriteUCS2StripControlChars(*it);
WriteUCS2(SanitizeUCS2(*it));
}
}
}
Expand Down Expand Up @@ -626,36 +669,6 @@ void VtIo::Writer::WriteUCS2(wchar_t ch) const
_io->_back.append(buf, len);
}

void VtIo::Writer::WriteUCS2StripControlChars(wchar_t ch) const
{
// If any of the values in the buffer are C0 or C1 controls, we need to
// convert them to printable codepoints, otherwise they'll end up being
// evaluated as control characters by the receiving terminal. We use the
// DOS 437 code page for the C0 controls and DEL, and just a `?` for the
// C1 controls, since that's what you would most likely have seen in the
// legacy v1 console with raster fonts.
if (ch < 0x20)
{
static constexpr wchar_t lut[] = {
// clang-format off
L' ', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'',
L'', L'', L'', L'', L'', L'§', L'', L'', L'', L'', L'', L'', L'', L'', L'', L'',
// clang-format on
};
ch = lut[ch];
}
else if (ch == 0x7F)
{
ch = L'';
}
else if (ch > 0x7F && ch < 0xA0)
{
ch = L'?';
}

WriteUCS2(ch);
}

// CUP: Cursor Position
void VtIo::Writer::WriteCUP(til::point position) const
{
Expand Down Expand Up @@ -773,7 +786,7 @@ void VtIo::Writer::WriteInfos(til::point target, std::span<const CHAR_INFO> info

do
{
WriteUCS2StripControlChars(ch);
WriteUCS2(SanitizeUCS2(ch));
} while (--repeat);
}
}
5 changes: 4 additions & 1 deletion src/host/VtIo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ namespace Microsoft::Console::VirtualTerminal
void WriteUTF16TranslateCRLF(std::wstring_view str) const;
void WriteUTF16StripControlChars(std::wstring_view str) const;
void WriteUCS2(wchar_t ch) const;
void WriteUCS2StripControlChars(wchar_t ch) const;
void WriteCUP(til::point position) const;
void WriteDECTCEM(bool enabled) const;
void WriteSGR1006(bool enabled) const;
Expand All @@ -54,6 +53,7 @@ namespace Microsoft::Console::VirtualTerminal

static void FormatAttributes(std::string& target, const TextAttribute& attributes);
static void FormatAttributes(std::wstring& target, const TextAttribute& attributes);
static wchar_t SanitizeUCS2(wchar_t ch);

[[nodiscard]] HRESULT Initialize(const ConsoleArguments* const pArgs);
[[nodiscard]] HRESULT CreateAndStartSignalThread() noexcept;
Expand All @@ -62,6 +62,8 @@ namespace Microsoft::Console::VirtualTerminal
bool IsUsingVt() const;
[[nodiscard]] HRESULT StartIfNeeded();

void SetDeviceAttributes(til::enumset<DeviceAttribute, uint64_t> attributes) noexcept;
til::enumset<DeviceAttribute, uint64_t> GetDeviceAttributes() const noexcept;
void SendCloseEvent();
void CreatePseudoWindow();

Expand All @@ -79,6 +81,7 @@ namespace Microsoft::Console::VirtualTerminal

std::unique_ptr<Microsoft::Console::VtInputThread> _pVtInputThread;
std::unique_ptr<Microsoft::Console::PtySignalInputThread> _pPtySignalInputThread;
til::enumset<DeviceAttribute, uint64_t> _deviceAttributes;

// We use two buffers: A front and a back buffer. The front buffer is the one we're currently
// sending to the terminal (it's being "presented" = it's on the "front" & "visible").
Expand Down
Loading

0 comments on commit 040f261

Please sign in to comment.