From 61ee7b0ef6a4aa761f2d385829455710ef6ad74d Mon Sep 17 00:00:00 2001 From: James Holderness Date: Tue, 2 Jul 2024 18:42:37 +0100 Subject: [PATCH] Add support for querying the character cell size (#17504) This PR add supports for two query sequences that are used to determine the pixel size of a character cell: * `CSI 16 t` reports the pixel size of a character cell directly. * `CSI 14 t` reports the pixel size of the text area, and when divided by the character size of the text area, you can get the character cell size indirectly (this method predates the introduction of `CSI 16 t`). These queries are used by Sixel applications that want to fit an image within specific text boundaries, so need to know how many cells would be covered by a particular pixel size, or vice versa. Our implementation of Sixel uses a virtual cell size that is always 10x20 (in order to emulate the VT340 more accurately), so these queries shouldn't really be needed, but some applications will fail to work without them. ## References and Relevant Issues Sixel support was added to conhost in PR #17421. ## Validation Steps Performed I've added some unit tests to verify that these queries are producing the expected responses, and I've manually tested on [XtermDOOM] (which uses `CSI 16 t`), and the [Notcurses] library (which uses `CSI 14 t`). [XtermDOOM]: https://gitlab.com/AutumnMeowMeow/xtermdoom [Notcurses]: https://github.com/dankamongmen/notcurses ## PR Checklist - [x] Tests added/passed --- src/terminal/adapter/DispatchTypes.hpp | 2 ++ src/terminal/adapter/PageManager.cpp | 5 +++++ src/terminal/adapter/PageManager.hpp | 1 + src/terminal/adapter/SixelParser.hpp | 4 ++-- src/terminal/adapter/adaptDispatch.cpp | 22 +++++++++++++++---- .../adapter/ut_adapter/adapterTest.cpp | 17 ++++++++++++-- 6 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index 7ba0132c07c..cb8adee0151 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -589,6 +589,8 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes IconifyWindow = 2, RefreshWindow = 7, ResizeWindowInCharacters = 8, + ReportTextSizeInPixels = 14, + ReportCharacterCellSize = 16, ReportTextSizeInCharacters = 18 }; diff --git a/src/terminal/adapter/PageManager.cpp b/src/terminal/adapter/PageManager.cpp index 284bee8530a..7c9862611cc 100644 --- a/src/terminal/adapter/PageManager.cpp +++ b/src/terminal/adapter/PageManager.cpp @@ -52,6 +52,11 @@ void Page::SetAttributes(const TextAttribute& attr, ITerminalApi* api) const } } +til::size Page::Size() const noexcept +{ + return { Width(), Height() }; +} + til::CoordType Page::Top() const noexcept { // If we ever support vertical window panning, the page top won't diff --git a/src/terminal/adapter/PageManager.hpp b/src/terminal/adapter/PageManager.hpp index 652c98b83ca..7b498e62abe 100644 --- a/src/terminal/adapter/PageManager.hpp +++ b/src/terminal/adapter/PageManager.hpp @@ -26,6 +26,7 @@ namespace Microsoft::Console::VirtualTerminal Cursor& Cursor() const noexcept; const TextAttribute& Attributes() const noexcept; void SetAttributes(const TextAttribute& attr, ITerminalApi* api = nullptr) const; + til::size Size() const noexcept; til::CoordType Top() const noexcept; til::CoordType Bottom() const noexcept; til::CoordType Width() const noexcept; diff --git a/src/terminal/adapter/SixelParser.hpp b/src/terminal/adapter/SixelParser.hpp index db62cd089fd..1966228d77f 100644 --- a/src/terminal/adapter/SixelParser.hpp +++ b/src/terminal/adapter/SixelParser.hpp @@ -28,8 +28,8 @@ namespace Microsoft::Console::VirtualTerminal public: static constexpr VTInt DefaultConformance = 9; - static til::size CellSizeForLevel(const VTInt conformanceLevel) noexcept; - static size_t MaxColorsForLevel(const VTInt conformanceLevel) noexcept; + static til::size CellSizeForLevel(const VTInt conformanceLevel = DefaultConformance) noexcept; + static size_t MaxColorsForLevel(const VTInt conformanceLevel = DefaultConformance) noexcept; SixelParser(AdaptDispatch& dispatcher, const StateMachine& stateMachine, const VTInt conformanceLevel = DefaultConformance) noexcept; void SoftReset(); diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index f5b0ac777bc..9806eb46b41 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -3648,6 +3648,12 @@ bool AdaptDispatch::WindowManipulation(const DispatchTypes::WindowManipulationTy // Other Window Manipulation functions: // MSFT:13271098 - QueryViewport // MSFT:13271146 - QueryScreenSize + + const auto reportSize = [&](const auto size) { + const auto reportType = function - 10; + _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033[{};{};{}t"), reportType, size.height, size.width)); + }; + switch (function) { case DispatchTypes::WindowManipulationType::DeIconifyWindow: @@ -3663,11 +3669,19 @@ bool AdaptDispatch::WindowManipulation(const DispatchTypes::WindowManipulationTy _api.ResizeWindow(parameter2.value_or(0), parameter1.value_or(0)); return true; case DispatchTypes::WindowManipulationType::ReportTextSizeInCharacters: - { - const auto page = _pages.VisiblePage(); - _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033[8;{};{}t"), page.Height(), page.Width())); + reportSize(_pages.VisiblePage().Size()); + return true; + case DispatchTypes::WindowManipulationType::ReportTextSizeInPixels: + // Prior to the existence of the character cell size query, Sixel applications + // that wanted to know the cell size would request the text area in pixels and + // divide that by the text area in characters. But for this to work, we need to + // return the virtual pixel size, as used in the Sixel graphics emulation, and + // not the physical pixel size (which should be of no concern to applications). + reportSize(_pages.VisiblePage().Size() * SixelParser::CellSizeForLevel()); + return true; + case DispatchTypes::WindowManipulationType::ReportCharacterCellSize: + reportSize(SixelParser::CellSizeForLevel()); return true; - } default: return false; } diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index 44274d66a7d..da5a7c93cb2 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -3377,10 +3377,23 @@ class AdapterTest TEST_METHOD(WindowManipulationTypeTests) { + // Our pixel size reports are based on a virtual cell size of 10x20 pixels + // for compatibility with the VT240 and VT340 graphic terminals. + const auto cellSize = til::size{ 10, 20 }; + _testGetSet->PrepData(); - _pDispatch->WindowManipulation(DispatchTypes::WindowManipulationType::ReportTextSizeInCharacters, NULL, NULL); const auto [textBuffer, viewport, _] = _testGetSet->GetBufferAndViewport(); - const std::wstring expectedResponse = fmt::format(L"\033[8;{};{}t", viewport.height(), textBuffer.GetSize().Width()); + + _pDispatch->WindowManipulation(DispatchTypes::WindowManipulationType::ReportTextSizeInCharacters, NULL, NULL); + std::wstring expectedResponse = fmt::format(L"\033[8;{};{}t", viewport.height(), textBuffer.GetSize().Width()); + _testGetSet->ValidateInputEvent(expectedResponse.c_str()); + + _pDispatch->WindowManipulation(DispatchTypes::WindowManipulationType::ReportTextSizeInPixels, NULL, NULL); + expectedResponse = fmt::format(L"\033[4;{};{}t", viewport.height() * cellSize.height, textBuffer.GetSize().Width() * cellSize.width); + _testGetSet->ValidateInputEvent(expectedResponse.c_str()); + + _pDispatch->WindowManipulation(DispatchTypes::WindowManipulationType::ReportCharacterCellSize, NULL, NULL); + expectedResponse = fmt::format(L"\033[6;{};{}t", cellSize.height, cellSize.width); _testGetSet->ValidateInputEvent(expectedResponse.c_str()); }