Merge remote-tracking branch 'origin/main' into feature/llm

This commit is contained in:
Console Service Bot 2025-06-11 01:31:32 +00:00
commit 10f8933d15
9 changed files with 131 additions and 15 deletions

View File

@ -13,6 +13,7 @@
#include "srvinit.h"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../interactivity/win32/CustomWindowMessages.h"
#include "../types/inc/convert.hpp"
using Microsoft::Console::Interactivity::ServiceLocator;
@ -179,6 +180,32 @@ void CONSOLE_INFORMATION::SetBracketedPasteMode(const bool enabled) noexcept
_bracketedPasteMode = enabled;
}
void CONSOLE_INFORMATION::CopyTextToClipboard(const std::wstring_view text)
{
const auto window = ServiceLocator::LocateConsoleWindow();
if (window)
{
// The clipboard can only be updated from the main GUI thread, so we
// need to post a message to trigger the actual copy operation. But if
// the pending clipboard content is already set, a message would have
// already been posted, so there's no need to post another one.
const auto clipboardMessageSent = _pendingClipboardText.has_value();
_pendingClipboardText = text;
if (!clipboardMessageSent)
{
PostMessageW(window->GetWindowHandle(), CM_UPDATE_CLIPBOARD, 0, 0);
}
}
}
std::optional<std::wstring> CONSOLE_INFORMATION::UsePendingClipboardText()
{
// Once the pending text has been used, we clear the variable to let the
// CopyTextToClipboard method know that the last CM_UPDATE_CLIPBOARD message
// has been processed, and future updates will require another message.
return std::exchange(_pendingClipboardText, {});
}
// Method Description:
// - Return the active screen buffer of the console.
// Arguments:

View File

@ -280,9 +280,9 @@ unsigned int ConhostInternalGetSet::GetInputCodePage() const
// - content - the text to be copied.
// Return Value:
// - <none>
void ConhostInternalGetSet::CopyToClipboard(const wil::zwstring_view /*content*/)
void ConhostInternalGetSet::CopyToClipboard(const wil::zwstring_view content)
{
// TODO
ServiceLocator::LocateGlobals().getConsoleInformation().CopyTextToClipboard(content);
}
// Routine Description:

View File

@ -126,6 +126,8 @@ public:
bool GetBracketedPasteMode() const noexcept;
void SetBracketedPasteMode(const bool enabled) noexcept;
void CopyTextToClipboard(const std::wstring_view text);
std::optional<std::wstring> UsePendingClipboardText();
void SetTitle(const std::wstring_view newTitle);
void SetTitlePrefix(const std::wstring_view newTitlePrefix);
@ -160,6 +162,7 @@ private:
SCREEN_INFORMATION* pCurrentScreenBuffer = nullptr;
COOKED_READ_DATA* _cookedReadData = nullptr; // non-ownership pointer
bool _bracketedPasteMode = false;
std::optional<std::wstring> _pendingClipboardText;
Microsoft::Console::VirtualTerminal::VtIo _vtIo;
Microsoft::Console::CursorBlinker _blinker;

View File

@ -24,6 +24,22 @@ using namespace Microsoft::Console::Types;
#pragma region Public Methods
void Clipboard::CopyText(const std::wstring& text)
{
const auto clipboard = _openClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle());
if (!clipboard)
{
LOG_LAST_ERROR();
return;
}
EmptyClipboard();
// As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats
// CF_UNICODETEXT: [...] A null character signals the end of the data.
// --> We add +1 to the length. This works because .c_str() is null-terminated.
_copyToClipboard(CF_UNICODETEXT, text.c_str(), (text.size() + 1) * sizeof(wchar_t));
}
// Arguments:
// - fAlsoCopyFormatting - Place colored HTML & RTF text onto the clipboard as well as the usual plain text.
// Return Value:

View File

@ -29,4 +29,6 @@
#define CM_SET_KEYBOARD_LAYOUT (WM_USER+19)
#endif
#define CM_UPDATE_CLIPBOARD (WM_USER+20)
// clang-format on

View File

@ -29,6 +29,7 @@ namespace Microsoft::Console::Interactivity::Win32
public:
static Clipboard& Instance();
void CopyText(const std::wstring& text);
void Copy(_In_ const bool fAlsoCopyFormatting = false);
void Paste();
void PasteDrop(HDROP drop);

View File

@ -773,6 +773,15 @@ static constexpr TsfDataProvider s_tsfDataProvider;
}
#endif // DBG
case CM_UPDATE_CLIPBOARD:
{
if (const auto clipboardText = gci.UsePendingClipboardText())
{
Clipboard::Instance().CopyText(clipboardText.value());
}
break;
}
case EVENT_CONSOLE_CARET:
case EVENT_CONSOLE_UPDATE_REGION:
case EVENT_CONSOLE_UPDATE_SIMPLE:

View File

@ -237,6 +237,7 @@ void SixelParser::_executeNextLine()
_imageCursor.y += _sixelHeight;
_availablePixelHeight -= _sixelHeight;
_resizeImageBuffer(_sixelHeight);
_fillImageBackgroundWhenScrolled();
}
void SixelParser::_executeMoveToHome()
@ -341,6 +342,12 @@ void SixelParser::_initRasterAttributes(const VTInt macroParameter, const Dispat
// By default, the filled area will cover the maximum extent allowed.
_backgroundSize = { til::CoordTypeMax, til::CoordTypeMax };
// If the image ends up extending beyond the bottom of the page, we may need
// to perform additional background filling as the screen is scrolled, which
// requires us to track the area filled so far. This will be initialized, if
// necessary, in the _fillImageBackground method below.
_filledBackgroundHeight = std::nullopt;
}
void SixelParser::_updateRasterAttributes(const VTParameters& rasterAttributes)
@ -373,6 +380,15 @@ void SixelParser::_updateRasterAttributes(const VTParameters& rasterAttributes)
// back to the dimensions from an earlier raster attributes command.
_backgroundSize.width = width > 0 ? width : _backgroundSize.width;
_backgroundSize.height = height > 0 ? height : _backgroundSize.height;
// If the aspect ratio has changed, the image height may increase, and that
// could potentially trigger a scroll requiring the background to be filled.
_fillImageBackgroundWhenScrolled();
// And while not documented, we know from testing on a VT330 that the raster
// attributes command should also trigger a carriage return. This applies
// regardless of whether the requested aspect ratio is valid or not.
_executeCarriageReturn();
}
void SixelParser::_scrollTextBuffer(Page& page, const int scrollAmount)
@ -656,24 +672,60 @@ void SixelParser::_fillImageBackground()
{
_backgroundFillRequired = false;
const auto backgroundHeight = std::min(_backgroundSize.height, _availablePixelHeight);
const auto backgroundWidth = std::min(_backgroundSize.width, _availablePixelWidth);
_resizeImageBuffer(backgroundHeight);
// When a background fill is requested, we prefill the buffer with the 0
// color index, up to the boundaries set by the raster attributes (or if
// none were given, up to the page boundaries). The actual image output
// isn't limited by the background dimensions though.
static constexpr auto backgroundPixel = IndexedPixel{};
const auto backgroundOffset = _imageCursor.y * _imageMaxWidth;
auto dst = std::next(_imageBuffer.begin(), backgroundOffset);
for (auto i = 0; i < backgroundHeight; i++)
{
std::fill_n(dst, backgroundWidth, backgroundPixel);
std::advance(dst, _imageMaxWidth);
}
const auto backgroundHeight = std::min(_backgroundSize.height, _availablePixelHeight);
_resizeImageBuffer(backgroundHeight);
_fillImageBackground(backgroundHeight);
// When the image extends beyond the page boundaries, and the screen is
// scrolled, we also need to fill the newly exposed lines, so we keep a
// record of the area filled so far. Initially this is considered to be
// the available height, even if it wasn't all filled to start with.
_filledBackgroundHeight = _imageCursor.y + _availablePixelHeight;
_fillImageBackgroundWhenScrolled();
}
}
_imageWidth = std::max(_imageWidth, backgroundWidth);
void SixelParser::_fillImageBackground(const int backgroundHeight)
{
static constexpr auto backgroundPixel = IndexedPixel{};
const auto backgroundWidth = std::min(_backgroundSize.width, _availablePixelWidth);
const auto backgroundOffset = _imageCursor.y * _imageMaxWidth;
auto dst = std::next(_imageBuffer.begin(), backgroundOffset);
for (auto i = 0; i < backgroundHeight; i++)
{
std::fill_n(dst, backgroundWidth, backgroundPixel);
std::advance(dst, _imageMaxWidth);
}
_imageWidth = std::max(_imageWidth, backgroundWidth);
}
void SixelParser::_fillImageBackgroundWhenScrolled()
{
// If _filledBackgroundHeight is set, that means a background fill has been
// requested, and we need to extend that area whenever the image is about to
// overrun it. The newly filled area is a multiple of the cell height (this
// is to match the behavior of the original hardware terminals).
const auto imageHeight = _imageCursor.y + _sixelHeight;
if (_filledBackgroundHeight && imageHeight > _filledBackgroundHeight) [[unlikely]]
{
_filledBackgroundHeight = (imageHeight + _cellSize.height - 1) / _cellSize.height * _cellSize.height;
const auto additionalFillHeight = *_filledBackgroundHeight - _imageCursor.y;
_resizeImageBuffer(additionalFillHeight);
_fillImageBackground(additionalFillHeight);
}
}
void SixelParser::_decreaseFilledBackgroundHeight(const int decreasedHeight) noexcept
{
// Sometimes the top of the image buffer may be clipped (e.g. when the image
// scrolls off the top of a margin area). When that occurs, our record of
// the filled height will need to be decreased to account for the new start.
if (_filledBackgroundHeight) [[unlikely]]
{
_filledBackgroundHeight = *_filledBackgroundHeight - decreasedHeight;
}
}
@ -717,11 +769,13 @@ void SixelParser::_eraseImageBufferRows(const int rowCount, const til::CoordType
const auto bufferOffsetEnd = bufferOffset + pixelCount * _imageMaxWidth;
if (static_cast<size_t>(bufferOffsetEnd) >= _imageBuffer.size()) [[unlikely]]
{
_decreaseFilledBackgroundHeight(_imageCursor.y);
_imageBuffer.clear();
_imageCursor.y = 0;
}
else
{
_decreaseFilledBackgroundHeight(pixelCount);
_imageBuffer.erase(_imageBuffer.begin() + bufferOffset, _imageBuffer.begin() + bufferOffsetEnd);
_imageCursor.y -= pixelCount;
}

View File

@ -89,6 +89,7 @@ namespace Microsoft::Console::VirtualTerminal
til::CoordType _pendingTextScrollCount = 0;
til::size _backgroundSize;
bool _backgroundFillRequired = false;
std::optional<til::CoordType> _filledBackgroundHeight;
void _initColorMap(const VTParameter backgroundColor);
void _defineColor(const VTParameters& colorParameters);
@ -109,6 +110,9 @@ namespace Microsoft::Console::VirtualTerminal
void _initImageBuffer();
void _resizeImageBuffer(const til::CoordType requiredHeight);
void _fillImageBackground();
void _fillImageBackground(const int backgroundHeight);
void _fillImageBackgroundWhenScrolled();
void _decreaseFilledBackgroundHeight(const int decreasedHeight) noexcept;
void _writeToImageBuffer(const int sixelValue, const int repeatCount);
void _eraseImageBufferRows(const int rowCount, const til::CoordType startRow = 0) noexcept;
void _maybeFlushImageBuffer(const bool endOfSequence = false);