зеркало из https://github.com/microsoft/terminal.git
Use DECCRA/DECFRA for ScrollConsoleScreenBuffer (#17747)
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 ✅
This commit is contained in:
Родитель
0a91023df8
Коммит
040f26175f
|
@ -9,7 +9,6 @@ ABORTIFHUNG
|
|||
ACCESSTOKEN
|
||||
acidev
|
||||
ACIOSS
|
||||
ACover
|
||||
acp
|
||||
actctx
|
||||
ACTCTXW
|
||||
|
@ -87,6 +86,7 @@ Autowrap
|
|||
AVerify
|
||||
awch
|
||||
azurecr
|
||||
AZZ
|
||||
backgrounded
|
||||
Backgrounder
|
||||
backgrounding
|
||||
|
@ -180,7 +180,6 @@ CFuzz
|
|||
cgscrn
|
||||
chafa
|
||||
changelists
|
||||
charinfo
|
||||
CHARSETINFO
|
||||
chh
|
||||
chshdng
|
||||
|
@ -264,7 +263,6 @@ consolegit
|
|||
consolehost
|
||||
CONSOLEIME
|
||||
consoleinternal
|
||||
Consoleroot
|
||||
CONSOLESETFOREGROUND
|
||||
consoletaeftemplates
|
||||
consoleuwp
|
||||
|
@ -386,7 +384,7 @@ DECCIR
|
|||
DECCKM
|
||||
DECCKSR
|
||||
DECCOLM
|
||||
DECCRA
|
||||
deccra
|
||||
DECCTR
|
||||
DECDC
|
||||
DECDHL
|
||||
|
@ -398,7 +396,7 @@ DECEKBD
|
|||
DECERA
|
||||
DECFI
|
||||
DECFNK
|
||||
DECFRA
|
||||
decfra
|
||||
DECGCI
|
||||
DECGCR
|
||||
DECGNL
|
||||
|
@ -727,7 +725,6 @@ GHIJKL
|
|||
gitcheckin
|
||||
gitfilters
|
||||
gitlab
|
||||
gitmodules
|
||||
gle
|
||||
GLOBALFOCUS
|
||||
GLYPHENTRY
|
||||
|
@ -1021,7 +1018,6 @@ lstatus
|
|||
lstrcmp
|
||||
lstrcmpi
|
||||
LTEXT
|
||||
LTLTLTLTL
|
||||
ltsc
|
||||
LUID
|
||||
luma
|
||||
|
@ -1116,7 +1112,6 @@ msrc
|
|||
MSVCRTD
|
||||
MTSM
|
||||
Munged
|
||||
munges
|
||||
murmurhash
|
||||
muxes
|
||||
myapplet
|
||||
|
@ -1218,7 +1213,6 @@ ntlpcapi
|
|||
ntm
|
||||
ntrtl
|
||||
ntstatus
|
||||
NTSYSCALLAPI
|
||||
nttree
|
||||
nturtl
|
||||
ntuser
|
||||
|
@ -1526,7 +1520,6 @@ rftp
|
|||
rgbi
|
||||
RGBQUAD
|
||||
rgbs
|
||||
rgci
|
||||
rgfae
|
||||
rgfte
|
||||
rgn
|
||||
|
@ -1604,6 +1597,7 @@ SELECTALL
|
|||
SELECTEDFONT
|
||||
SELECTSTRING
|
||||
Selfhosters
|
||||
Serbo
|
||||
SERVERDLL
|
||||
SETACTIVE
|
||||
SETBUDDYINT
|
||||
|
@ -1832,8 +1826,6 @@ TOPDOWNDIB
|
|||
TOpt
|
||||
tosign
|
||||
touchpad
|
||||
Tpp
|
||||
Tpqrst
|
||||
tracelogging
|
||||
traceviewpp
|
||||
trackbar
|
||||
|
@ -1958,7 +1950,6 @@ VPACKMANIFESTDIRECTORY
|
|||
VPR
|
||||
VREDRAW
|
||||
vsc
|
||||
vsconfig
|
||||
vscprintf
|
||||
VSCROLL
|
||||
vsdevshell
|
||||
|
@ -2000,7 +1991,6 @@ wcswidth
|
|||
wddm
|
||||
wddmcon
|
||||
WDDMCONSOLECONTEXT
|
||||
WDK
|
||||
wdm
|
||||
webpage
|
||||
websites
|
||||
|
@ -2074,7 +2064,6 @@ winuserp
|
|||
WINVER
|
||||
wistd
|
||||
wmain
|
||||
wmemory
|
||||
WMSZ
|
||||
wnd
|
||||
WNDALLOC
|
||||
|
@ -2173,6 +2162,7 @@ yact
|
|||
YCast
|
||||
YCENTER
|
||||
YCount
|
||||
yizz
|
||||
YLimit
|
||||
YPan
|
||||
YSubstantial
|
||||
|
@ -2186,3 +2176,4 @@ ZCtrl
|
|||
ZWJs
|
||||
ZYXWVU
|
||||
ZYXWVUTd
|
||||
zzf
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -163,38 +163,37 @@ bool VtIo::IsUsingVt() const
|
|||
{
|
||||
Writer writer{ this };
|
||||
|
||||
// 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[?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.
|
||||
// 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.
|
||||
// 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
|
||||
);
|
||||
|
||||
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)
|
||||
|
@ -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.
|
||||
|
@ -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 }
|
||||
{
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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").
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "_stream.h"
|
||||
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../terminal/parser/InputStateMachineEngine.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../types/inc/viewport.hpp"
|
||||
|
||||
|
@ -993,29 +994,36 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
|||
{
|
||||
try
|
||||
{
|
||||
// Just in case if the client application didn't check if this request is useless.
|
||||
if (source.left == target.x && source.top == target.y)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
LockConsole();
|
||||
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
if (auto writer = gci.GetVtWriterForBuffer(&context))
|
||||
auto& buffer = context.GetActiveBuffer();
|
||||
const auto bufferSize = buffer.GetBufferSize();
|
||||
auto writer = gci.GetVtWriterForBuffer(&context);
|
||||
|
||||
// Applications like to pass 0/0 for the fill char/attribute.
|
||||
// What they want is the whitespace and current attributes.
|
||||
if (fillCharacter == UNICODE_NULL && fillAttribute == 0)
|
||||
{
|
||||
auto& buffer = context.GetActiveBuffer();
|
||||
fillAttribute = buffer.GetAttributes().GetLegacyAttributes();
|
||||
}
|
||||
|
||||
// However, if the character is null and we were given a null attribute (represented as legacy 0),
|
||||
// then we'll just fill with spaces and whatever the buffer's default colors are.
|
||||
if (fillCharacter == UNICODE_NULL && fillAttribute == 0)
|
||||
{
|
||||
fillCharacter = UNICODE_SPACE;
|
||||
fillAttribute = buffer.GetAttributes().GetLegacyAttributes();
|
||||
}
|
||||
// Avoid writing control characters into the buffer.
|
||||
// A null character will get translated to whitespace.
|
||||
fillCharacter = Microsoft::Console::VirtualTerminal::VtIo::SanitizeUCS2(fillCharacter);
|
||||
|
||||
if (writer)
|
||||
{
|
||||
// GH#3126 - This is a shim for cmd's `cls` function. In the
|
||||
// legacy console, `cls` is supposed to clear the entire buffer. In
|
||||
// conpty however, there's no difference between the viewport and the
|
||||
// entirety of the buffer. We're going to see if this API call exactly
|
||||
// matched the way we expect cmd to call it. If it does, then
|
||||
// let's manually emit a Full Reset (RIS).
|
||||
const auto bufferSize = buffer.GetBufferSize();
|
||||
// legacy console, `cls` is supposed to clear the entire buffer.
|
||||
// We always use a VT sequence, even if ConPTY isn't used, because those are faster nowadays.
|
||||
if (enableCmdShim &&
|
||||
source.left <= 0 && source.top <= 0 &&
|
||||
source.right >= bufferSize.RightInclusive() && source.bottom >= bufferSize.BottomInclusive() &&
|
||||
|
@ -1028,36 +1036,96 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
const auto clipViewport = clip ? Viewport::FromInclusive(*clip) : bufferSize;
|
||||
const auto clipViewport = clip ? Viewport::FromInclusive(*clip).Clamp(bufferSize) : bufferSize;
|
||||
const auto sourceViewport = Viewport::FromInclusive(source);
|
||||
Viewport readViewport;
|
||||
Viewport writtenViewport;
|
||||
|
||||
const auto w = std::max(0, sourceViewport.Width());
|
||||
const auto h = std::max(0, sourceViewport.Height());
|
||||
const auto a = static_cast<size_t>(w * h);
|
||||
if (a == 0)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
til::small_vector<CHAR_INFO, 1024> backup;
|
||||
til::small_vector<CHAR_INFO, 1024> fill;
|
||||
|
||||
backup.resize(a, CHAR_INFO{ fillCharacter, fillAttribute });
|
||||
fill.resize(a, CHAR_INFO{ fillCharacter, fillAttribute });
|
||||
const auto fillViewport = sourceViewport.Clamp(clipViewport);
|
||||
|
||||
writer.BackupCursor();
|
||||
|
||||
RETURN_IF_FAILED(ReadConsoleOutputWImplHelper(context, backup, sourceViewport, readViewport));
|
||||
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, fill, w, sourceViewport.Clamp(clipViewport), writtenViewport));
|
||||
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, backup, w, Viewport::FromDimensions(target, readViewport.Dimensions()).Clamp(clipViewport), writtenViewport));
|
||||
if (gci.GetVtIo()->GetDeviceAttributes().test(Microsoft::Console::VirtualTerminal::DeviceAttribute::RectangularAreaOperations))
|
||||
{
|
||||
// This calculates just the positive offsets caused by out-of-bounds (OOB) source and target coordinates.
|
||||
//
|
||||
// If the source rectangle is OOB to the bottom-right, then the size of the rectangle that can
|
||||
// be copied shrinks, but its origin stays the same. However, if the rectangle is OOB to the
|
||||
// top-left then the origin of the to-be-copied rectangle will be offset by an inverse amount.
|
||||
// Similarly, if the *target* rectangle is OOB to the bottom-right, its size shrinks while
|
||||
// the origin stays the same, and if it's OOB to the top-left, then the origin is offset.
|
||||
//
|
||||
// In other words, this calculates the total offset that needs to be applied to the to-be-copied rectangle.
|
||||
// Later down below we'll then clamp that rectangle which will cause its size to shrink as needed.
|
||||
const til::point offset{
|
||||
std::max(0, -source.left) + std::max(0, clipViewport.Left() - target.x),
|
||||
std::max(0, -source.top) + std::max(0, clipViewport.Top() - target.y),
|
||||
};
|
||||
|
||||
const auto copyTargetViewport = Viewport::FromDimensions(target + offset, sourceViewport.Dimensions()).Clamp(clipViewport);
|
||||
const auto copySourceViewport = Viewport::FromDimensions(sourceViewport.Origin() + offset, copyTargetViewport.Dimensions()).Clamp(bufferSize);
|
||||
const auto fills = Viewport::Subtract(fillViewport, copyTargetViewport);
|
||||
std::wstring buf;
|
||||
|
||||
if (!fills.empty())
|
||||
{
|
||||
Microsoft::Console::VirtualTerminal::VtIo::FormatAttributes(buf, TextAttribute{ fillAttribute });
|
||||
}
|
||||
|
||||
if (copySourceViewport.IsValid() && copyTargetViewport.IsValid())
|
||||
{
|
||||
// DECCRA: Copy Rectangular Area
|
||||
fmt::format_to(
|
||||
std::back_inserter(buf),
|
||||
FMT_COMPILE(L"\x1b[{};{};{};{};;{};{}$v"),
|
||||
copySourceViewport.Top() + 1,
|
||||
copySourceViewport.Left() + 1,
|
||||
copySourceViewport.BottomExclusive(),
|
||||
copySourceViewport.RightExclusive(),
|
||||
copyTargetViewport.Top() + 1,
|
||||
copyTargetViewport.Left() + 1);
|
||||
}
|
||||
|
||||
for (const auto& fill : fills)
|
||||
{
|
||||
// DECFRA: Fill Rectangular Area
|
||||
fmt::format_to(
|
||||
std::back_inserter(buf),
|
||||
FMT_COMPILE(L"\x1b[{};{};{};{};{}$x"),
|
||||
static_cast<int>(fillCharacter),
|
||||
fill.Top() + 1,
|
||||
fill.Left() + 1,
|
||||
fill.BottomExclusive(),
|
||||
fill.RightExclusive());
|
||||
}
|
||||
|
||||
WriteCharsVT(context, buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto w = std::max(0, sourceViewport.Width());
|
||||
const auto h = std::max(0, sourceViewport.Height());
|
||||
const auto a = static_cast<size_t>(w * h);
|
||||
if (a == 0)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
til::small_vector<CHAR_INFO, 1024> backup;
|
||||
til::small_vector<CHAR_INFO, 1024> fill;
|
||||
|
||||
backup.resize(a, CHAR_INFO{ fillCharacter, fillAttribute });
|
||||
fill.resize(a, CHAR_INFO{ fillCharacter, fillAttribute });
|
||||
|
||||
Viewport readViewport;
|
||||
Viewport writtenViewport;
|
||||
|
||||
RETURN_IF_FAILED(ReadConsoleOutputWImplHelper(context, backup, sourceViewport, readViewport));
|
||||
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, fill, w, fillViewport, writtenViewport));
|
||||
RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, backup, w, Viewport::FromDimensions(target, readViewport.Dimensions()).Clamp(clipViewport), writtenViewport));
|
||||
}
|
||||
|
||||
writer.Submit();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& buffer = context.GetActiveBuffer();
|
||||
TextAttribute useThisAttr(fillAttribute);
|
||||
ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "precomp.h"
|
||||
|
||||
#include "CommonState.hpp"
|
||||
#include "../../terminal/parser/InputStateMachineEngine.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
|
@ -29,6 +30,8 @@ constexpr CHAR_INFO ci_blu(wchar_t ch) noexcept
|
|||
}
|
||||
|
||||
#define cup(y, x) "\x1b[" #y ";" #x "H" // CUP: Cursor Position
|
||||
#define deccra(t, l, b, r, y, x) "\x1b[" #t ";" #l ";" #b ";" #r ";;" #y ";" #x "$v" // DECCRA: Copy Rectangular Area
|
||||
#define decfra(ch, t, l, b, r) "\x1b[" #ch ";" #t ";" #l ";" #b ";" #r "$x"
|
||||
#define decawm(h) "\x1b[?7" #h // DECAWM: Autowrap Mode
|
||||
#define decsc() "\x1b\x37" // DECSC: DEC Save Cursor (+ attributes)
|
||||
#define decrc() "\x1b\x38" // DECRC: DEC Restore Cursor (+ attributes)
|
||||
|
@ -554,12 +557,224 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
|
|||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Copying from a partially out-of-bounds source to a partially out-of-bounds target,
|
||||
// while source and target overlap and there's a partially out-of-bounds clip rect.
|
||||
//
|
||||
// Before:
|
||||
// clip rect
|
||||
// +~~~~~~~~~~~~~~~~~~~~~+
|
||||
// +--------------$--------+ $
|
||||
// | A Z Z$ b C | D c Y $
|
||||
// | $+-------+------------$--+
|
||||
// | E z z$| f G | H g Y $ |
|
||||
// | src $| | $ |
|
||||
// | i z z$| J d | B E L $ |
|
||||
// | $| | dst $ |
|
||||
// | m n M$| N h | F i P $ |
|
||||
// +--------------$+-------+ $ |
|
||||
// +~e~~~~~~~~~~~~~~~~~~~+ |
|
||||
// +-----------------------+
|
||||
//
|
||||
// After:
|
||||
//
|
||||
// +-----------------------+
|
||||
// | A Z Z y y | D c Y
|
||||
// | +-------+---------------+
|
||||
// | E z z | y A | Z Z b |
|
||||
// | | | |
|
||||
// | i z z | y E | z z f |
|
||||
// | | | |
|
||||
// | m n M | y i | z z J |
|
||||
// +---------------+-------+ |
|
||||
// | |
|
||||
// +-----------------------+
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { -1, 0, 4, 3 }, { 3, 1 }, til::inclusive_rect{ 3, -1, 7, 9 }, L'y', blu, false));
|
||||
expected =
|
||||
decsc() //
|
||||
cup(1, 4) sgr_blu("yy") //
|
||||
cup(2, 4) sgr_blu("yy") //
|
||||
cup(3, 4) sgr_blu("yy") //
|
||||
cup(4, 4) sgr_blu("yy") //
|
||||
cup(2, 4) sgr_blu("y") sgr_red("AZZ") sgr_blu("b") //
|
||||
cup(3, 4) sgr_blu("y") sgr_red("E") sgr_blu("zzf") //
|
||||
cup(4, 4) sgr_blu("yizz") sgr_red("J") //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
static constexpr std::array<CHAR_INFO, 8 * 4> expectedContents{ {
|
||||
// clang-format off
|
||||
ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('b'), ci_red('C'), ci_red('D'), ci_blu('c'), ci_red('Y'),
|
||||
ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('f'), ci_red('G'), ci_red('H'), ci_blu('g'), ci_red('Y'),
|
||||
ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_red('J'), ci_blu('d'), ci_red('B'), ci_red('E'), ci_red('L'),
|
||||
ci_blu('m'), ci_blu('n'), ci_red('M'), ci_red('N'), ci_blu('h'), ci_red('F'), ci_blu('i'), ci_red('P'),
|
||||
ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('y'), ci_blu('y'), ci_red('D'), ci_blu('c'), ci_red('Y'),
|
||||
ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('y'), ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('b'),
|
||||
ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_blu('y'), ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('f'),
|
||||
ci_blu('m'), ci_blu('n'), ci_red('M'), ci_blu('y'), ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_red('J'),
|
||||
// clang-format on
|
||||
} };
|
||||
std::array<CHAR_INFO, 8 * 4> actualContents{};
|
||||
Viewport actualContentsRead;
|
||||
THROW_IF_FAILED(routines.ReadConsoleOutputWImpl(*screenInfo, actualContents, Viewport::FromDimensions({}, { 8, 4 }), actualContentsRead));
|
||||
VERIFY_IS_TRUE(memcmp(expectedContents.data(), actualContents.data(), sizeof(actualContents)) == 0);
|
||||
}
|
||||
|
||||
TEST_METHOD(ScrollConsoleScreenBufferW_DECCRA)
|
||||
{
|
||||
ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->SetDeviceAttributes({ DeviceAttribute::RectangularAreaOperations });
|
||||
const auto cleanup = wil::scope_exit([]() {
|
||||
ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->SetDeviceAttributes({});
|
||||
});
|
||||
|
||||
std::string_view expected;
|
||||
std::string_view actual;
|
||||
|
||||
setupInitialContents();
|
||||
|
||||
// Scrolling from nowhere to somewhere are no-ops and should not emit anything.
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, -1, -1 }, {}, std::nullopt, L' ', 0, false));
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { -10, -10, -9, -9 }, {}, std::nullopt, L' ', 0, false));
|
||||
expected = "";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Scrolling from somewhere to nowhere should clear the area.
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, 1, 1 }, { 10, 10 }, std::nullopt, L' ', red, false));
|
||||
expected =
|
||||
decsc() //
|
||||
sgr_red() //
|
||||
decfra(32, 1, 1, 2, 2) // ' ' = 32
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// cmd uses ScrollConsoleScreenBuffer to clear the buffer contents and that gets translated to a clear screen sequence.
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, 7, 3 }, { 0, -4 }, std::nullopt, 0, 0, true));
|
||||
expected = "\x1b[H\x1b[2J\x1b[3J";
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
//
|
||||
// A B a b C D c d
|
||||
//
|
||||
// E F e f G H g h
|
||||
//
|
||||
// i j I J k l K L
|
||||
//
|
||||
// m n M N o p O P
|
||||
//
|
||||
setupInitialContents();
|
||||
|
||||
// Scrolling from somewhere to somewhere.
|
||||
//
|
||||
// +-------+
|
||||
// A | Z Z | b C D c d
|
||||
// | src |
|
||||
// E | Z Z | f G H g h
|
||||
// +-------+ +-------+
|
||||
// i j I J k | B a | L
|
||||
// | dst |
|
||||
// m n M N o | F e | P
|
||||
// +-------+
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 1, 0, 2, 1 }, { 5, 2 }, std::nullopt, L'Z', red, false));
|
||||
expected =
|
||||
decsc() //
|
||||
sgr_red() //
|
||||
deccra(1, 2, 2, 3, 3, 6) //
|
||||
decfra(90, 1, 2, 2, 3) // 'Z' = 90
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Same, but with a partially out-of-bounds target and clip rect. Clip rects affect both
|
||||
// the source area that gets filled and the target area that gets a copy of the source contents.
|
||||
//
|
||||
// A Z Z b C D c d
|
||||
// +---+~~~~~~~~~~~~~~~~~~~~~~~+
|
||||
// | E $ z z | f G H g $ h
|
||||
// | $ src | +---$-------+
|
||||
// | i $ z z | J k B | E $ L |
|
||||
// +---$-------+ | $ dst |
|
||||
// m $ n M N o F | i $ P |
|
||||
// +~~~~~~~~~~~~~~~~~~~~~~~+-------+
|
||||
// clip rect
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 1, 2, 2 }, { 6, 2 }, til::inclusive_rect{ 1, 1, 6, 3 }, L'z', blu, false));
|
||||
expected =
|
||||
decsc() //
|
||||
sgr_blu() //
|
||||
deccra(2, 1, 3, 1, 3, 7) //
|
||||
decfra(122, 2, 2, 3, 3) // 'z' = 122
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Same, but with a partially out-of-bounds source.
|
||||
// The boundaries of the buffer act as a clip rect for reading and so only 2 cells get copied.
|
||||
//
|
||||
// +-------+
|
||||
// A Z Z b C D c | Y |
|
||||
// | src |
|
||||
// E z z f G H g | Y |
|
||||
// +---+ +-------+
|
||||
// i z z J | d | B E L
|
||||
// |dst|
|
||||
// m n M N | h | F i P
|
||||
// +---+
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 7, 0, 8, 1 }, { 4, 2 }, std::nullopt, L'Y', red, false));
|
||||
expected =
|
||||
decsc() //
|
||||
sgr_red() //
|
||||
deccra(1, 8, 2, 8, 3, 5) //
|
||||
decfra(89, 1, 8, 2, 8) // 'Y' = 89
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
// Copying from a partially out-of-bounds source to a partially out-of-bounds target,
|
||||
// while source and target overlap and there's a partially out-of-bounds clip rect.
|
||||
//
|
||||
// Before:
|
||||
// clip rect
|
||||
// +~~~~~~~~~~~~~~~~~~~~~+
|
||||
// +--------------$--------+ $
|
||||
// | A Z Z$ b C | D c Y $
|
||||
// | $+-------+------------$--+
|
||||
// | E z z$| f G | H g Y $ |
|
||||
// | src $| | $ |
|
||||
// | i z z$| J d | B E L $ |
|
||||
// | $| | dst $ |
|
||||
// | m n M$| N h | F i P $ |
|
||||
// +--------------$+-------+ $ |
|
||||
// +~e~~~~~~~~~~~~~~~~~~~+ |
|
||||
// +-----------------------+
|
||||
//
|
||||
// After:
|
||||
//
|
||||
// +-----------------------+
|
||||
// | A Z Z y y | D c Y
|
||||
// | +-------+---------------+
|
||||
// | E z z | y A | Z Z b |
|
||||
// | | | |
|
||||
// | i z z | y E | z z f |
|
||||
// | | | |
|
||||
// | m n M | y i | z z J |
|
||||
// +---------------+-------+ |
|
||||
// | |
|
||||
// +-----------------------+
|
||||
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { -1, 0, 4, 3 }, { 3, 1 }, til::inclusive_rect{ 3, -1, 7, 9 }, L'y', blu, false));
|
||||
expected =
|
||||
decsc() //
|
||||
sgr_blu() //
|
||||
deccra(1, 1, 3, 4, 2, 5) //
|
||||
decfra(121, 1, 4, 1, 5) // 'y' = 121
|
||||
decfra(121, 2, 4, 4, 4) //
|
||||
decrc();
|
||||
actual = readOutput();
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
static constexpr std::array<CHAR_INFO, 8 * 4> expectedContents{ {
|
||||
// clang-format off
|
||||
ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('y'), ci_blu('y'), ci_red('D'), ci_blu('c'), ci_red('Y'),
|
||||
ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('y'), ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('b'),
|
||||
ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_blu('y'), ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('f'),
|
||||
ci_blu('m'), ci_blu('n'), ci_red('M'), ci_blu('y'), ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_red('J'),
|
||||
// clang-format on
|
||||
} };
|
||||
std::array<CHAR_INFO, 8 * 4> actualContents{};
|
||||
|
|
|
@ -24,6 +24,13 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
static_assert(std::is_unsigned_v<UnderlyingType>);
|
||||
|
||||
public:
|
||||
static constexpr enumset from_bits(UnderlyingType data) noexcept
|
||||
{
|
||||
enumset result;
|
||||
result._data = data;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Constructs a new bitset with the given list of positions set to true.
|
||||
TIL_ENUMSET_VARARG
|
||||
|
|
|
@ -89,11 +89,6 @@ static bool operator==(const Ss3ToVkey& pair, const Ss3ActionCodes code) noexcep
|
|||
return pair.action == code;
|
||||
}
|
||||
|
||||
InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch) :
|
||||
InputStateMachineEngine(std::move(pDispatch), false)
|
||||
{
|
||||
}
|
||||
|
||||
InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, const bool lookingForDSR) :
|
||||
_pDispatch(std::move(pDispatch)),
|
||||
_lookingForDSR(lookingForDSR),
|
||||
|
@ -102,14 +97,28 @@ InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispat
|
|||
THROW_HR_IF_NULL(E_INVALIDARG, _pDispatch.get());
|
||||
}
|
||||
|
||||
void InputStateMachineEngine::WaitUntilDSR(DWORD timeout) const noexcept
|
||||
til::enumset<DeviceAttribute, uint64_t> InputStateMachineEngine::WaitUntilDA1(DWORD timeout) const noexcept
|
||||
{
|
||||
uint64_t val = 0;
|
||||
|
||||
// atomic_wait() returns false when the timeout expires.
|
||||
// Technically we should decrement the timeout with each iteration,
|
||||
// but I suspect infinite spurious wake-ups are a theoretical problem.
|
||||
while (_lookingForDSR.load(std::memory_order::relaxed) && til::atomic_wait(_lookingForDSR, true, timeout))
|
||||
for (;;)
|
||||
{
|
||||
val = _deviceAttributes.load(std::memory_order::relaxed);
|
||||
if (val)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!til::atomic_wait(_deviceAttributes, val, timeout))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return til::enumset<DeviceAttribute, uint64_t>::from_bits(val);
|
||||
}
|
||||
|
||||
bool InputStateMachineEngine::EncounteredWin32InputModeSequence() const noexcept
|
||||
|
@ -411,13 +420,12 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter
|
|||
// The F3 case is special - it shares a code with the DeviceStatusResponse.
|
||||
// If we're looking for that response, then do that, and break out.
|
||||
// Else, fall though to the _GetCursorKeysModifierState handler.
|
||||
if (_lookingForDSR.load(std::memory_order::relaxed))
|
||||
if (_lookingForDSR)
|
||||
{
|
||||
_pDispatch->MoveCursor(parameters.at(0), parameters.at(1));
|
||||
// Right now we're only looking for on initial cursor
|
||||
// position response. After that, only look for F3.
|
||||
_lookingForDSR.store(false, std::memory_order::relaxed);
|
||||
til::atomic_notify_all(_lookingForDSR);
|
||||
_lookingForDSR = false;
|
||||
return true;
|
||||
}
|
||||
// Heuristic: If the hosting terminal used the win32 input mode, chances are high
|
||||
|
@ -464,6 +472,32 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter
|
|||
case CsiActionCodes::FocusOut:
|
||||
_pDispatch->FocusChanged(false);
|
||||
return true;
|
||||
case CsiActionCodes::DA_DeviceAttributes:
|
||||
// This assumes that InputStateMachineEngine is tightly coupled with VtInputThread and the rest of the ConPTY system (VtIo).
|
||||
// On startup, ConPTY will send a DA1 request to get more information about the hosting terminal.
|
||||
// We catch it here and store the information for later retrieval.
|
||||
if (_deviceAttributes.load(std::memory_order_relaxed) == 0)
|
||||
{
|
||||
til::enumset<DeviceAttribute, uint64_t> attributes;
|
||||
|
||||
// The first parameter denotes the conformance level.
|
||||
if (parameters.at(0).value() >= 61)
|
||||
{
|
||||
parameters.subspan(1).for_each([&](auto p) {
|
||||
attributes.set(static_cast<DeviceAttribute>(p));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
_deviceAttributes.fetch_or(attributes.bits(), std::memory_order_relaxed);
|
||||
til::atomic_notify_all(_deviceAttributes);
|
||||
|
||||
// VtIo first sends a DSR CPR and then a DA1 request.
|
||||
// If we encountered a DA1 response here, the DSR request is definitely done now.
|
||||
_lookingForDSR = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case CsiActionCodes::Win32KeyboardInput:
|
||||
{
|
||||
// Use WriteCtrlKey here, even for keys that _aren't_ control keys,
|
||||
|
|
|
@ -49,6 +49,32 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
// CAPSLOCK_ON 0x0080
|
||||
// ENHANCED_KEY 0x0100
|
||||
|
||||
enum class DeviceAttribute : uint64_t
|
||||
{
|
||||
Columns132 = 1,
|
||||
PrinterPort = 2,
|
||||
Sixel = 4,
|
||||
SelectiveErase = 6,
|
||||
SoftCharacterSet = 7,
|
||||
UserDefinedKeys = 8,
|
||||
NationalReplacementCharacterSets = 9,
|
||||
SerboCroatianCharacterSet = 12,
|
||||
EightBitInterfaceArchitecture = 14,
|
||||
TechnicalCharacterSet = 15,
|
||||
WindowingCapability = 18,
|
||||
Sessions = 19,
|
||||
HorizontalScrolling = 21,
|
||||
Color = 22,
|
||||
GreekCharacterSet = 23,
|
||||
TurkishCharacterSet = 24,
|
||||
RectangularAreaOperations = 28,
|
||||
TextMacros = 32,
|
||||
Latin2CharacterSet = 42,
|
||||
PCTerm = 44,
|
||||
SoftKeyMapping = 45,
|
||||
AsciiTerminalEmulation = 46,
|
||||
};
|
||||
|
||||
enum CsiActionCodes : uint64_t
|
||||
{
|
||||
ArrowUp = VTID("A"),
|
||||
|
@ -67,6 +93,7 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
CSI_F3 = VTID("R"), // Both F3 and DSR are on R.
|
||||
// DSR_DeviceStatusReportResponse = VTID("R"),
|
||||
CSI_F4 = VTID("S"),
|
||||
DA_DeviceAttributes = VTID("?c"),
|
||||
DTTERM_WindowManipulation = VTID("t"),
|
||||
CursorBackTab = VTID("Z"),
|
||||
Win32KeyboardInput = VTID("_")
|
||||
|
@ -128,11 +155,9 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
class InputStateMachineEngine : public IStateMachineEngine
|
||||
{
|
||||
public:
|
||||
InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch);
|
||||
InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch,
|
||||
const bool lookingForDSR);
|
||||
InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, const bool lookingForDSR = false);
|
||||
|
||||
void WaitUntilDSR(DWORD timeout) const noexcept;
|
||||
til::enumset<DeviceAttribute, uint64_t> WaitUntilDA1(DWORD timeout) const noexcept;
|
||||
|
||||
bool EncounteredWin32InputModeSequence() const noexcept override;
|
||||
|
||||
|
@ -159,7 +184,8 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
|
||||
private:
|
||||
const std::unique_ptr<IInteractDispatch> _pDispatch;
|
||||
std::atomic<bool> _lookingForDSR{ false };
|
||||
std::atomic<uint64_t> _deviceAttributes{ 0 };
|
||||
bool _lookingForDSR = false;
|
||||
bool _encounteredWin32InputModeSequence = false;
|
||||
DWORD _mouseButtonState = 0;
|
||||
std::chrono::milliseconds _doubleClickTime;
|
||||
|
|
|
@ -603,7 +603,11 @@ try
|
|||
const auto intersection = Viewport::Intersect(original, removeMe);
|
||||
|
||||
// If there's no intersection, there's nothing to remove.
|
||||
if (!intersection.IsValid())
|
||||
if (!original.IsValid())
|
||||
{
|
||||
// Nothing to do here.
|
||||
}
|
||||
else if (!intersection.IsValid())
|
||||
{
|
||||
// Just put the original rectangle into the results and return early.
|
||||
result.push_back(original);
|
||||
|
|
|
@ -38,6 +38,9 @@ static Pipes createPipes()
|
|||
VERIFY_IS_TRUE(SetHandleInformation(p.our.in.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(p.our.out.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
|
||||
// ConPTY requests a DA1 report on startup. Emulate the response from the terminal.
|
||||
WriteFile(p.our.in.get(), "\x1b[?61c", 6, nullptr, nullptr);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
@ -189,25 +192,12 @@ void ConPtyTests::CreateConPtyBadSize()
|
|||
void ConPtyTests::GoodCreate()
|
||||
{
|
||||
PseudoConsole pcon{};
|
||||
wil::unique_handle outPipeOurSide;
|
||||
wil::unique_handle inPipeOurSide;
|
||||
wil::unique_handle outPipePseudoConsoleSide;
|
||||
wil::unique_handle inPipePseudoConsoleSide;
|
||||
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
|
||||
VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
auto pipes = createPipes();
|
||||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
pipes.conpty.in.get(),
|
||||
pipes.conpty.out.get(),
|
||||
0,
|
||||
&pcon));
|
||||
|
||||
|
@ -220,24 +210,12 @@ void ConPtyTests::GoodCreateMultiple()
|
|||
{
|
||||
PseudoConsole pcon1{};
|
||||
PseudoConsole pcon2{};
|
||||
wil::unique_handle outPipeOurSide;
|
||||
wil::unique_handle inPipeOurSide;
|
||||
wil::unique_handle outPipePseudoConsoleSide;
|
||||
wil::unique_handle inPipePseudoConsoleSide;
|
||||
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
auto pipes = createPipes();
|
||||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
pipes.conpty.in.get(),
|
||||
pipes.conpty.out.get(),
|
||||
0,
|
||||
&pcon1));
|
||||
auto closePty1 = wil::scope_exit([&] {
|
||||
|
@ -246,8 +224,8 @@ void ConPtyTests::GoodCreateMultiple()
|
|||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
pipes.conpty.in.get(),
|
||||
pipes.conpty.out.get(),
|
||||
0,
|
||||
&pcon2));
|
||||
auto closePty2 = wil::scope_exit([&] {
|
||||
|
@ -258,23 +236,12 @@ void ConPtyTests::GoodCreateMultiple()
|
|||
void ConPtyTests::SurvivesOnBreakOutput()
|
||||
{
|
||||
PseudoConsole pty = { 0 };
|
||||
wil::unique_handle outPipeOurSide;
|
||||
wil::unique_handle inPipeOurSide;
|
||||
wil::unique_handle outPipePseudoConsoleSide;
|
||||
wil::unique_handle inPipePseudoConsoleSide;
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
auto pipes = createPipes();
|
||||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
pipes.conpty.in.get(),
|
||||
pipes.conpty.out.get(),
|
||||
0,
|
||||
&pty));
|
||||
auto closePty1 = wil::scope_exit([&] {
|
||||
|
@ -292,7 +259,7 @@ void ConPtyTests::SurvivesOnBreakOutput()
|
|||
VERIFY_IS_TRUE(GetExitCodeProcess(piClient.hProcess, &dwExit));
|
||||
VERIFY_ARE_EQUAL(dwExit, (DWORD)STILL_ACTIVE);
|
||||
|
||||
VERIFY_IS_TRUE(CloseHandle(outPipeOurSide.get()));
|
||||
pipes.our.out.reset();
|
||||
|
||||
// Wait for a couple seconds, make sure the child is still alive.
|
||||
VERIFY_ARE_EQUAL(WaitForSingleObject(pty.hConPtyProcess, 2000), (DWORD)WAIT_TIMEOUT);
|
||||
|
@ -317,23 +284,12 @@ void ConPtyTests::DiesOnClose()
|
|||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"commandline", testCommandline), L"Get a commandline to test");
|
||||
|
||||
PseudoConsole pty = { 0 };
|
||||
wil::unique_handle outPipeOurSide;
|
||||
wil::unique_handle inPipeOurSide;
|
||||
wil::unique_handle outPipePseudoConsoleSide;
|
||||
wil::unique_handle inPipePseudoConsoleSide;
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
VERIFY_IS_TRUE(CreatePipe(inPipePseudoConsoleSide.addressof(), inPipeOurSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(CreatePipe(outPipeOurSide.addressof(), outPipePseudoConsoleSide.addressof(), &sa, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(inPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
VERIFY_IS_TRUE(SetHandleInformation(outPipeOurSide.get(), HANDLE_FLAG_INHERIT, 0));
|
||||
auto pipes = createPipes();
|
||||
|
||||
VERIFY_SUCCEEDED(
|
||||
_CreatePseudoConsole(defaultSize,
|
||||
inPipePseudoConsoleSide.get(),
|
||||
outPipePseudoConsoleSide.get(),
|
||||
pipes.conpty.in.get(),
|
||||
pipes.conpty.out.get(),
|
||||
0,
|
||||
&pty));
|
||||
auto closePty1 = wil::scope_exit([&] {
|
||||
|
|
|
@ -169,7 +169,7 @@ function Invoke-OpenConsoleTests()
|
|||
[switch]$FTOnly,
|
||||
|
||||
[parameter(Mandatory=$false)]
|
||||
[ValidateSet('host', 'interactivityWin32', 'terminal', 'adapter', 'feature', 'uia', 'textbuffer', 'til', 'types', 'terminalCore', 'terminalApp', 'localTerminalApp', 'unitSettingsModel', 'unitRemoting', 'unitControl')]
|
||||
[ValidateSet('host', 'interactivityWin32', 'terminal', 'adapter', 'feature', 'uia', 'textbuffer', 'til', 'types', 'terminalCore', 'terminalApp', 'localTerminalApp', 'unitSettingsModel', 'unitRemoting', 'unitControl', 'winconpty')]
|
||||
[string]$Test,
|
||||
|
||||
[parameter(Mandatory=$false)]
|
||||
|
|
|
@ -15,4 +15,5 @@
|
|||
<test name="til" type="unit" binary="til.unit.tests.dll" />
|
||||
<test name="feature" type="ft" binary="Conhost.Feature.Tests.dll" />
|
||||
<test name="uia" type="ft" binary="Conhost.UIA.Tests.dll" />
|
||||
<test name="winconpty" type="ft" binary="winconpty.Feature.Tests.dll" />
|
||||
</tests>
|
||||
|
|
Загрузка…
Ссылка в новой задаче