diff --git a/src/buffer/out/textBufferCellIterator.cpp b/src/buffer/out/textBufferCellIterator.cpp index 7ab512c72cd..64ed7acefc4 100644 --- a/src/buffer/out/textBufferCellIterator.cpp +++ b/src/buffer/out/textBufferCellIterator.cpp @@ -265,3 +265,8 @@ const OutputCellView* TextBufferCellIterator::operator->() const noexcept { return &_view; } + +COORD TextBufferCellIterator::Pos() const noexcept +{ + return _pos; +} diff --git a/src/buffer/out/textBufferCellIterator.hpp b/src/buffer/out/textBufferCellIterator.hpp index 8b7604bb6e4..db8a3c89ce6 100644 --- a/src/buffer/out/textBufferCellIterator.hpp +++ b/src/buffer/out/textBufferCellIterator.hpp @@ -47,6 +47,8 @@ class TextBufferCellIterator const OutputCellView& operator*() const noexcept; const OutputCellView* operator->() const noexcept; + COORD Pos() const noexcept; + protected: void _SetPos(const COORD newPos); void _GenerateView(); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 7e9e9c7d78c..932042a8b1f 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -592,6 +592,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation const int newDpi = static_cast(static_cast(USER_DEFAULT_SCREEN_DPI) * _compositionScale); + _terminal->SetFontInfo(_actualFont); + // TODO: MSFT:20895307 If the font doesn't exist, this doesn't // actually fail. We need a way to gracefully fallback. _renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont); diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp index 7fb8d3b3d74..7e5dbcf6767 100644 --- a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -5,6 +5,7 @@ #include "XamlUiaTextRange.h" #include "../types/TermControlUiaTextRange.hpp" #include +#include // the same as COR_E_NOTSUPPORTED // we don't want to import the CLR headers to get it @@ -89,12 +90,52 @@ namespace winrt::Microsoft::Terminal::Control::implementation winrt::Windows::Foundation::IInspectable XamlUiaTextRange::GetAttributeValue(int32_t textAttributeId) const { - // Copied functionality from Types::UiaTextRange.cpp - if (textAttributeId == UIA_IsReadOnlyAttributeId) + // Call the function off of the underlying UiaTextRange. + VARIANT result; + THROW_IF_FAILED(_uiaProvider->GetAttributeValue(textAttributeId, &result)); + + // Convert the resulting VARIANT into a format that is consumable by XAML. + switch (result.vt) + { + case VT_BSTR: + { + return box_value(result.bstrVal); + } + case VT_I4: + { + // Surprisingly, `long` is _not_ a WinRT type. + // So we have to use `int32_t` to make sure this is output properly. + // Otherwise, you'll get "Attribute does not exist" out the other end. + return box_value(result.lVal); + } + case VT_R8: + { + return box_value(result.dblVal); + } + case VT_BOOL: { - return winrt::box_value(false); + return box_value(result.boolVal); } - else + case VT_UNKNOWN: + { + // This one is particularly special. + // We might return a special value like UiaGetReservedMixedAttributeValue + // or UiaGetReservedNotSupportedValue. + // Some text attributes may return a real value, however, none of those + // are supported at this time. + // So we need to figure out what was actually intended to be returned. + + com_ptr mixedAttributeVal; + UiaGetReservedMixedAttributeValue(mixedAttributeVal.put()); + + if (result.punkVal == mixedAttributeVal.get()) + { + return Windows::UI::Xaml::DependencyProperty::UnsetValue(); + } + + [[fallthrough]]; + } + default: { // We _need_ to return XAML_E_NOT_SUPPORTED here. // Returning nullptr is an improper implementation of it being unsupported. @@ -103,6 +144,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Magically, this doesn't affect other forms of navigation... winrt::throw_hresult(XAML_E_NOT_SUPPORTED); } + } } void XamlUiaTextRange::GetBoundingRectangles(com_array& returnValue) const diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 9d1ae957009..d0dfbe604ab 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -6,7 +6,6 @@ #include "../../terminal/parser/OutputStateMachineEngine.hpp" #include "TerminalDispatch.hpp" #include "../../inc/unicode.hpp" -#include "../../inc/DefaultSettings.h" #include "../../inc/argb.h" #include "../../types/inc/utils.hpp" #include "../../types/inc/colorTable.hpp" diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index df9b1bc71ae..e1feeafd1b9 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -5,6 +5,7 @@ #include +#include "../../inc/DefaultSettings.h" #include "../../buffer/out/textBuffer.hpp" #include "../../types/inc/sgrStack.hpp" #include "../../renderer/inc/BlinkingState.hpp" @@ -68,6 +69,7 @@ class Microsoft::Terminal::Core::Terminal final : void UpdateSettings(winrt::Microsoft::Terminal::Core::ICoreSettings settings); void UpdateAppearance(const winrt::Microsoft::Terminal::Core::ICoreAppearance& appearance); + void SetFontInfo(const FontInfo& fontInfo); // Write goes through the parser void Write(std::wstring_view stringView); @@ -160,6 +162,7 @@ class Microsoft::Terminal::Core::Terminal final : COORD GetTextBufferEndPosition() const noexcept override; const TextBuffer& GetTextBuffer() noexcept override; const FontInfo& GetFontInfo() noexcept override; + std::pair GetAttributeColors(const TextAttribute& attr) const noexcept override; void LockConsole() noexcept override; void UnlockConsole() noexcept override; @@ -168,7 +171,6 @@ class Microsoft::Terminal::Core::Terminal final : #pragma region IRenderData // These methods are defined in TerminalRenderData.cpp const TextAttribute GetDefaultBrushColors() noexcept override; - std::pair GetAttributeColors(const TextAttribute& attr) const noexcept override; COORD GetCursorPosition() const noexcept override; bool IsCursorVisible() const noexcept override; bool IsCursorOn() const noexcept override; @@ -276,6 +278,10 @@ class Microsoft::Terminal::Core::Terminal final : size_t _hyperlinkPatternId; std::wstring _workingDirectory; + + // This default fake font value is only used to check if the font is a raster font. + // Otherwise, the font is changed to a real value with the renderer via TriggerFontChange. + FontInfo _fontInfo{ DEFAULT_FONT_FACE, TMPF_TRUETYPE, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false }; #pragma region Text Selection // a selection is represented as a range between two COORDs (start and end) // the pivot is the COORD that remains selected when you extend a selection in any direction diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 17b711db748..b2ed30f1566 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -27,23 +27,15 @@ const TextBuffer& Terminal::GetTextBuffer() noexcept return *_buffer; } -// Creating a FontInfo can technically throw (on string allocation) and this is noexcept. -// That means this will std::terminate. We could come back and make there be a default constructor -// backup to FontInfo that throws no exceptions and allocates a default FontInfo structure. -#pragma warning(push) -#pragma warning(disable : 26447) const FontInfo& Terminal::GetFontInfo() noexcept { - // TODO: This font value is only used to check if the font is a raster font. - // Otherwise, the font is changed with the renderer via TriggerFontChange. - // The renderer never uses any of the other members from the value returned - // by this method. - // We could very likely replace this with just an IsRasterFont method - // (which would return false) - static const FontInfo _fakeFontInfo(DEFAULT_FONT_FACE, TMPF_TRUETYPE, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false); - return _fakeFontInfo; + return _fontInfo; +} + +void Terminal::SetFontInfo(const FontInfo& fontInfo) +{ + _fontInfo = fontInfo; } -#pragma warning(pop) const TextAttribute Terminal::GetDefaultBrushColors() noexcept { diff --git a/src/host/renderData.hpp b/src/host/renderData.hpp index 9e1e79b0b56..8b858c541e5 100644 --- a/src/host/renderData.hpp +++ b/src/host/renderData.hpp @@ -27,6 +27,7 @@ class RenderData final : COORD GetTextBufferEndPosition() const noexcept override; const TextBuffer& GetTextBuffer() noexcept override; const FontInfo& GetFontInfo() noexcept override; + std::pair GetAttributeColors(const TextAttribute& attr) const noexcept override; std::vector GetSelectionRects() noexcept override; @@ -37,8 +38,6 @@ class RenderData final : #pragma region IRenderData const TextAttribute GetDefaultBrushColors() noexcept override; - std::pair GetAttributeColors(const TextAttribute& attr) const noexcept override; - COORD GetCursorPosition() const noexcept override; bool IsCursorVisible() const noexcept override; bool IsCursorOn() const noexcept override; diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index dcdbb32b3fe..5457b98537c 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -9,6 +9,7 @@ #include "uiaTextRange.hpp" #include "../types/ScreenInfoUiaProviderBase.h" #include "../../../buffer/out/textBuffer.hpp" +#include "../types/UiaTracing.h" using namespace WEX::Common; using namespace WEX::Logging; @@ -1347,6 +1348,299 @@ class UiaTextRangeTests } } + TEST_METHOD(GetAttributeValue) + { + Log::Comment(L"Check supported attributes"); + Microsoft::WRL::ComPtr notSupportedVal; + UiaGetReservedNotSupportedValue(¬SupportedVal); + + // Iterate over UIA's Text Attribute Identifiers + // Validate that we know which ones are (not) supported + // source: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-textattribute-ids + for (long uiaAttributeId = UIA_AnimationStyleAttributeId; uiaAttributeId <= UIA_AfterParagraphSpacingAttributeId; ++uiaAttributeId) + { + Microsoft::WRL::ComPtr utr; + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider)); + THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit_Character)); + + Log::Comment(NoThrowString().Format(L"Attribute ID: %d", uiaAttributeId)); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(uiaAttributeId, &result)); + + switch (uiaAttributeId) + { + case UIA_FontNameAttributeId: + { + VERIFY_ARE_EQUAL(VT_BSTR, result.vt); + break; + } + case UIA_BackgroundColorAttributeId: + case UIA_FontWeightAttributeId: + case UIA_ForegroundColorAttributeId: + case UIA_StrikethroughStyleAttributeId: + case UIA_UnderlineStyleAttributeId: + { + VERIFY_ARE_EQUAL(VT_I4, result.vt); + break; + } + case UIA_IsItalicAttributeId: + case UIA_IsReadOnlyAttributeId: + { + VERIFY_ARE_EQUAL(VT_BOOL, result.vt); + break; + } + default: + { + // Expected: not supported + VERIFY_ARE_EQUAL(VT_UNKNOWN, result.vt); + VERIFY_ARE_EQUAL(notSupportedVal.Get(), result.punkVal); + break; + } + } + } + + // This is the text attribute we'll use to update the text buffer. + // We'll modify it, then test if the UiaTextRange can extract/interpret the data properly. + // updateBuffer() will write that text attribute to the first cell in the buffer. + TextAttribute attr; + auto updateBuffer = [&](TextAttribute outputAttr) { + _pTextBuffer->Write({ outputAttr }, { 0, 0 }); + }; + + Microsoft::WRL::ComPtr utr; + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider)); + THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit_Character)); + { + Log::Comment(L"Test Background"); + const auto rawBackgroundColor{ RGB(255, 0, 0) }; + attr.SetBackground(rawBackgroundColor); + updateBuffer(attr); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_BackgroundColorAttributeId, &result)); + + const COLORREF realBackgroundColor{ _pUiaData->GetAttributeColors(attr).second & 0x00ffffff }; + VERIFY_ARE_EQUAL(realBackgroundColor, static_cast(result.lVal)); + } + { + Log::Comment(L"Test Font Weight"); + attr.SetBold(true); + updateBuffer(attr); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_FontWeightAttributeId, &result)); + VERIFY_ARE_EQUAL(FW_BOLD, result.lVal); + + attr.SetBold(false); + updateBuffer(attr); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_FontWeightAttributeId, &result)); + VERIFY_ARE_EQUAL(FW_NORMAL, result.lVal); + ; + } + { + Log::Comment(L"Test Foreground"); + const auto rawForegroundColor{ RGB(255, 0, 0) }; + attr.SetForeground(rawForegroundColor); + updateBuffer(attr); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_ForegroundColorAttributeId, &result)); + + const auto realForegroundColor{ _pUiaData->GetAttributeColors(attr).first & 0x00ffffff }; + VERIFY_ARE_EQUAL(realForegroundColor, static_cast(result.lVal)); + } + { + Log::Comment(L"Test Italic"); + attr.SetItalic(true); + updateBuffer(attr); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_IsItalicAttributeId, &result)); + VERIFY_IS_TRUE(result.boolVal); + + attr.SetItalic(false); + updateBuffer(attr); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_IsItalicAttributeId, &result)); + VERIFY_IS_FALSE(result.boolVal); + } + { + Log::Comment(L"Test Strikethrough"); + attr.SetCrossedOut(true); + updateBuffer(attr); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_StrikethroughStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_Single, result.lVal); + + attr.SetCrossedOut(false); + updateBuffer(attr); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_StrikethroughStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_None, result.lVal); + } + { + Log::Comment(L"Test Underline"); + + // Single underline + attr.SetUnderlined(true); + updateBuffer(attr); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_Single, result.lVal); + + // Double underline (double supercedes single) + attr.SetDoublyUnderlined(true); + updateBuffer(attr); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_Double, result.lVal); + + // Double underline (double on its own) + attr.SetUnderlined(false); + updateBuffer(attr); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_Double, result.lVal); + + // No underline + attr.SetDoublyUnderlined(false); + updateBuffer(attr); + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result)); + VERIFY_ARE_EQUAL(TextDecorationLineStyle_None, result.lVal); + } + { + Log::Comment(L"Test Font Name (special)"); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_FontNameAttributeId, &result)); + const std::wstring actualFontName{ result.bstrVal }; + const auto expectedFontName{ _pUiaData->GetFontInfo().GetFaceName() }; + VERIFY_ARE_EQUAL(expectedFontName, actualFontName); + } + { + Log::Comment(L"Test Read Only (special)"); + VARIANT result; + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_IsReadOnlyAttributeId, &result)); + VERIFY_IS_FALSE(result.boolVal); + } + { + // "Mixed" is when the desired attribute value is inconsistent across the range. + // We'll make our life easier by setting an attribute on a character, + // but getting the attribute for the entire line. + Log::Comment(L"Test Mixed"); + VARIANT result; + THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit_Line)); + + // set first cell as underlined, but second cell as not underlined + attr.SetUnderlined(true); + _pTextBuffer->Write({ attr }, { 0, 0 }); + attr.SetUnderlined(false); + _pTextBuffer->Write({ attr }, { 1, 0 }); + + VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result)); + + // Expected: mixed + Microsoft::WRL::ComPtr mixedVal; + THROW_IF_FAILED(UiaGetReservedMixedAttributeValue(&mixedVal)); + VERIFY_ARE_EQUAL(VT_UNKNOWN, result.vt); + VERIFY_ARE_EQUAL(mixedVal.Get(), result.punkVal); + } + } + + TEST_METHOD(FindAttribute) + { + Microsoft::WRL::ComPtr utr; + const COORD startPos{ 0, 0 }; + const COORD endPos{ 0, 2 }; + THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&utr, _pUiaData, &_dummyProvider, startPos, endPos)); + { + Log::Comment(L"Test Font Name (special)"); + + // Populate query with font name currently in use. + const auto fontName{ _pUiaData->GetFontInfo().GetFaceName() }; + VARIANT var{}; + var.vt = VT_BSTR; + var.bstrVal = SysAllocString(fontName.data()); + + Microsoft::WRL::ComPtr result; + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_FontNameAttributeId, var, false, result.GetAddressOf())); + + // Expecting the same text range endpoints + BOOL isEqual; + THROW_IF_FAILED(utr->Compare(result.Get(), &isEqual)); + VERIFY_IS_TRUE(isEqual); + + // Now perform the same test, but searching backwards + Log::Comment(L"Test Font Name (special) - Backwards"); + Microsoft::WRL::ComPtr resultBackwards; + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_FontNameAttributeId, var, true, resultBackwards.GetAddressOf())); + + // Expecting the same text range endpoints + THROW_IF_FAILED(result->Compare(resultBackwards.Get(), &isEqual)); + VERIFY_IS_TRUE(isEqual); + } + { + Log::Comment(L"Test Read Only (special)"); + + VARIANT var{}; + var.vt = VT_BOOL; + var.boolVal = false; + + Microsoft::WRL::ComPtr result; + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsReadOnlyAttributeId, var, false, result.GetAddressOf())); + + // Expecting the same text range endpoints + BOOL isEqual; + THROW_IF_FAILED(utr->Compare(result.Get(), &isEqual)); + VERIFY_IS_TRUE(isEqual); + + // Now perform the same test, but searching backwards + Log::Comment(L"Test Read Only (special) - Backwards"); + Microsoft::WRL::ComPtr resultBackwards; + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsReadOnlyAttributeId, var, true, resultBackwards.GetAddressOf())); + + // Expecting the same text range endpoints + THROW_IF_FAILED(result->Compare(resultBackwards.Get(), &isEqual)); + VERIFY_IS_TRUE(isEqual); + } + { + Log::Comment(L"Test IsItalic (standard attribute)"); + + // Since all of the other attributes operate very similarly, + // we're just going to pick one of them and test that. + // The "GetAttribute" tests provide code coverage for + // retrieving an attribute verification function. + // This test is intended to provide code coverage for + // finding a text range with the desired attribute. + + // Set up the buffer's attributes. + TextAttribute italicAttr; + italicAttr.SetItalic(true); + auto iter{ _pUiaData->GetTextBuffer().GetCellDataAt(startPos) }; + for (auto i = 0; i < 5; ++i) + { + _pTextBuffer->Write({ L"X", italicAttr }, iter.Pos()); + ++iter; + } + + // set the expected end (exclusive) + const auto expectedEndPos{ iter.Pos() }; + + VARIANT var{}; + var.vt = VT_BOOL; + var.boolVal = true; + + Microsoft::WRL::ComPtr result; + THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit_Document)); + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsItalicAttributeId, var, false, result.GetAddressOf())); + + Microsoft::WRL::ComPtr resultUtr{ static_cast(result.Get()) }; + VERIFY_ARE_EQUAL(startPos, resultUtr->_start); + VERIFY_ARE_EQUAL(expectedEndPos, resultUtr->_end); + + // Now perform the same test, but searching backwards + Log::Comment(L"Test IsItalic (standard attribute) - Backwards"); + Microsoft::WRL::ComPtr resultBackwards; + VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsItalicAttributeId, var, true, resultBackwards.GetAddressOf())); + + // Expecting the same text range endpoints + BOOL isEqual; + THROW_IF_FAILED(result->Compare(resultBackwards.Get(), &isEqual)); + VERIFY_IS_TRUE(isEqual); + } + } + TEST_METHOD(BlockRange) { // This test replicates GH#7960. diff --git a/src/renderer/base/FontInfoBase.cpp b/src/renderer/base/FontInfoBase.cpp index 8de26d8baf3..40891cf4940 100644 --- a/src/renderer/base/FontInfoBase.cpp +++ b/src/renderer/base/FontInfoBase.cpp @@ -61,7 +61,7 @@ unsigned int FontInfoBase::GetWeight() const return _weight; } -const std::wstring_view FontInfoBase::GetFaceName() const +const std::wstring_view FontInfoBase::GetFaceName() const noexcept { return _faceName; } diff --git a/src/renderer/inc/FontInfoBase.hpp b/src/renderer/inc/FontInfoBase.hpp index 8e3f32d8210..aa9f3cddbb6 100644 --- a/src/renderer/inc/FontInfoBase.hpp +++ b/src/renderer/inc/FontInfoBase.hpp @@ -38,7 +38,7 @@ class FontInfoBase unsigned char GetFamily() const; unsigned int GetWeight() const; - const std::wstring_view GetFaceName() const; + const std::wstring_view GetFaceName() const noexcept; unsigned int GetCodePage() const; HRESULT FillLegacyNameBuffer(gsl::span buffer) const; diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp index 56ab872e217..a8c96be268a 100644 --- a/src/renderer/inc/IRenderData.hpp +++ b/src/renderer/inc/IRenderData.hpp @@ -48,8 +48,6 @@ namespace Microsoft::Console::Render virtual const TextAttribute GetDefaultBrushColors() noexcept = 0; - virtual std::pair GetAttributeColors(const TextAttribute& attr) const noexcept = 0; - virtual COORD GetCursorPosition() const noexcept = 0; virtual bool IsCursorVisible() const noexcept = 0; virtual bool IsCursorOn() const noexcept = 0; diff --git a/src/types/IBaseData.h b/src/types/IBaseData.h index 58804c38269..6b9f8b722f7 100644 --- a/src/types/IBaseData.h +++ b/src/types/IBaseData.h @@ -37,6 +37,7 @@ namespace Microsoft::Console::Types virtual COORD GetTextBufferEndPosition() const noexcept = 0; virtual const TextBuffer& GetTextBuffer() noexcept = 0; virtual const FontInfo& GetFontInfo() noexcept = 0; + virtual std::pair GetAttributeColors(const TextAttribute& attr) const noexcept = 0; virtual std::vector GetSelectionRects() noexcept = 0; diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index e254134f381..69f531c09cc 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -9,6 +9,12 @@ using namespace Microsoft::Console::Types; +// Foreground/Background text color doesn't care about the alpha. +static constexpr long _RemoveAlpha(COLORREF color) noexcept +{ + return color & 0x00ffffff; +} + // degenerate range constructor. #pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ std::wstring_view wordDelimiters) noexcept @@ -311,15 +317,274 @@ IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) noexc CATCH_RETURN(); } -// we don't support this currently -IFACEMETHODIMP UiaTextRangeBase::FindAttribute(_In_ TEXTATTRIBUTEID /*textAttributeId*/, - _In_ VARIANT /*val*/, - _In_ BOOL /*searchBackward*/, - _Outptr_result_maybenull_ ITextRangeProvider** /*ppRetVal*/) noexcept +// Method Description: +// - Verify that the given attribute has the desired formatting saved in the attributeId and val +// Arguments: +// - attributeId - the UIA text attribute identifier we're looking for +// - val - the attributeId's sub-type we're looking for +// - attr - the text attribute we're checking +// Return Value: +// - true, if the given attribute has the desired formatting. +// - false, if the given attribute does not have the desired formatting. +// - nullopt, if checking for the desired formatting is not supported. +std::optional UiaTextRangeBase::_verifyAttr(TEXTATTRIBUTEID attributeId, VARIANT val, const TextAttribute& attr) const { - UiaTracing::TextRange::FindAttribute(*this); - return E_NOTIMPL; + // Most of the attributes we're looking for just require us to check TextAttribute. + // So if we support it, we'll return a function to verify if the TextAttribute + // has the desired attribute. + switch (attributeId) + { + case UIA_BackgroundColorAttributeId: + { + // Expected type: VT_I4 + THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); + + // The foreground color is stored as a COLORREF. + const auto queryBackgroundColor{ val.lVal }; + return _RemoveAlpha(_pData->GetAttributeColors(attr).second) == queryBackgroundColor; + } + case UIA_FontWeightAttributeId: + { + // Expected type: VT_I4 + THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); + + // The font weight can be any value from 0 to 900. + // The text buffer doesn't store the actual value, + // we just store "IsBold" and "IsFaint". + const auto queryFontWeight{ val.lVal }; + + if (queryFontWeight > FW_NORMAL) + { + // we're looking for a bold font weight + return attr.IsBold(); + } + else + { + // we're looking for "normal" font weight + return !attr.IsBold(); + } + } + case UIA_ForegroundColorAttributeId: + { + // Expected type: VT_I4 + THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); + + // The foreground color is stored as a COLORREF. + const auto queryForegroundColor{ val.lVal }; + return _RemoveAlpha(_pData->GetAttributeColors(attr).first) == queryForegroundColor; + } + case UIA_IsItalicAttributeId: + { + // Expected type: VT_I4 + THROW_HR_IF(E_INVALIDARG, val.vt != VT_BOOL); + + // The text is either italic or it isn't. + const auto queryIsItalic{ val.boolVal }; + return queryIsItalic ? attr.IsItalic() : !attr.IsItalic(); + } + case UIA_StrikethroughStyleAttributeId: + { + // Expected type: VT_I4 + THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); + + // The strikethrough style is stored as a TextDecorationLineStyle. + // However, The text buffer doesn't have different styles for being crossed out. + // Instead, we just store whether or not the text is crossed out. + switch (val.lVal) + { + case TextDecorationLineStyle_None: + return !attr.IsCrossedOut(); + case TextDecorationLineStyle_Single: + return attr.IsCrossedOut(); + default: + return std::nullopt; + } + } + case UIA_UnderlineStyleAttributeId: + { + // Expected type: VT_I4 + THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); + + // The underline style is stored as a TextDecorationLineStyle. + // However, The text buffer doesn't have that many different styles for being underlined. + // Instead, we only have single and double underlined. + switch (val.lVal) + { + case TextDecorationLineStyle_None: + return !attr.IsUnderlined() && !attr.IsDoublyUnderlined(); + case TextDecorationLineStyle_Double: + return attr.IsDoublyUnderlined(); + case TextDecorationLineStyle_Single: + return attr.IsUnderlined(); + default: + return std::nullopt; + } + } + default: + return std::nullopt; + } +} + +IFACEMETHODIMP UiaTextRangeBase::FindAttribute(_In_ TEXTATTRIBUTEID attributeId, + _In_ VARIANT val, + _In_ BOOL searchBackwards, + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) noexcept +try +{ + RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); + *ppRetVal = nullptr; + + // AttributeIDs that require special handling + switch (attributeId) + { + case UIA_FontNameAttributeId: + { + RETURN_HR_IF(E_INVALIDARG, val.vt != VT_BSTR); + + // Technically, we'll truncate early if there's an embedded null in the BSTR. + // But we're probably fine in this circumstance. + + const std::wstring queryFontName{ val.bstrVal }; + if (queryFontName == _pData->GetFontInfo().GetFaceName()) + { + Clone(ppRetVal); + } + UiaTracing::TextRange::FindAttribute(*this, attributeId, val, searchBackwards, static_cast(**ppRetVal)); + return S_OK; + } + case UIA_IsReadOnlyAttributeId: + { + RETURN_HR_IF(E_INVALIDARG, val.vt != VT_BOOL); + if (!val.boolVal) + { + Clone(ppRetVal); + } + UiaTracing::TextRange::FindAttribute(*this, attributeId, val, searchBackwards, static_cast(**ppRetVal)); + return S_OK; + } + default: + break; + } + + // AttributeIDs that are exposed via TextAttribute + try + { + if (!_verifyAttr(attributeId, val, {}).has_value()) + { + // The AttributeID is not supported. + UiaTracing::TextRange::FindAttribute(*this, attributeId, val, searchBackwards, static_cast(**ppRetVal), UiaTracing::AttributeType::Unsupported); + return E_NOTIMPL; + } + } + catch (...) + { + LOG_HR(wil::ResultFromCaughtException()); + UiaTracing::TextRange::FindAttribute(*this, attributeId, val, searchBackwards, static_cast(**ppRetVal), UiaTracing::AttributeType::Error); + return E_INVALIDARG; + } + + // Get some useful variables + const auto& buffer{ _pData->GetTextBuffer() }; + const auto bufferSize{ buffer.GetSize() }; + const auto inclusiveEnd{ _getInclusiveEnd() }; + + // Start/End for the resulting range. + // NOTE: we store these as "first" and "second" anchor because, + // we just want to know what the inclusive range is. + // We'll do some post-processing to fix this on the way out. + std::optional resultFirstAnchor; + std::optional resultSecondAnchor; + const auto attemptUpdateAnchors = [=, &resultFirstAnchor, &resultSecondAnchor](const TextBufferCellIterator iter) { + const auto attrFound{ _verifyAttr(attributeId, val, iter->TextAttr()).value() }; + if (attrFound) + { + // populate the first anchor if it's not populated. + // otherwise, populate the second anchor. + if (!resultFirstAnchor.has_value()) + { + resultFirstAnchor = iter.Pos(); + resultSecondAnchor = iter.Pos(); + } + else + { + resultSecondAnchor = iter.Pos(); + } + } + return attrFound; + }; + + // Start/End for the direction to perform the search in + // We need searchEnd to be exclusive. This allows the for-loop below to + // iterate up until the exclusive searchEnd, and not attempt to read the + // data at that position. + const auto searchStart{ searchBackwards ? inclusiveEnd : _start }; + const auto searchEndInclusive{ searchBackwards ? _start : inclusiveEnd }; + auto searchEndExclusive{ searchEndInclusive }; + if (searchBackwards) + { + bufferSize.DecrementInBounds(searchEndExclusive, true); + } + else + { + bufferSize.IncrementInBounds(searchEndExclusive, true); + } + + // Iterate from searchStart to searchEnd in the buffer. + // If we find the attribute we're looking for, we update resultFirstAnchor/SecondAnchor appropriately. + Viewport viewportRange{ bufferSize }; + if (_blockRange) + { + const auto originX{ std::min(_start.X, inclusiveEnd.X) }; + const auto originY{ std::min(_start.Y, inclusiveEnd.Y) }; + const auto width{ gsl::narrow_cast(std::abs(inclusiveEnd.X - _start.X + 1)) }; + const auto height{ gsl::narrow_cast(std::abs(inclusiveEnd.Y - _start.Y + 1)) }; + viewportRange = Viewport::FromDimensions({ originX, originY }, width, height); + } + auto iter{ buffer.GetCellDataAt(searchStart, viewportRange) }; + const auto iterStep{ searchBackwards ? -1 : 1 }; + for (; iter && iter.Pos() != searchEndExclusive; iter += iterStep) + { + if (!attemptUpdateAnchors(iter) && resultFirstAnchor.has_value() && resultSecondAnchor.has_value()) + { + // Exit the loop early if... + // - the cell we're looking at doesn't have the attr we're looking for + // - the anchors have been populated + // This means that we've found a contiguous range where the text attribute was found. + // No point in searching through the rest of the search space. + // TLDR: keep updating the second anchor and make the range wider until the attribute changes. + break; + } + } + + // Corner case: we couldn't actually move the searchEnd to make it exclusive + // (i.e. DecrementInBounds on Origin doesn't move it) + if (searchEndInclusive == searchEndExclusive) + { + attemptUpdateAnchors(iter); + } + + // If a result was found, populate ppRetVal with the UiaTextRange + // representing the found selection anchors. + if (resultFirstAnchor.has_value() && resultSecondAnchor.has_value()) + { + RETURN_IF_FAILED(Clone(ppRetVal)); + UiaTextRangeBase& range = static_cast(**ppRetVal); + + // IMPORTANT: resultFirstAnchor and resultSecondAnchor make up an inclusive range. + range._start = searchBackwards ? *resultSecondAnchor : *resultFirstAnchor; + range._end = searchBackwards ? *resultFirstAnchor : *resultSecondAnchor; + + // We need to make the end exclusive! + // But be careful here, we might be a block range + auto exclusiveIter{ buffer.GetCellDataAt(range._end, viewportRange) }; + ++exclusiveIter; + range._end = exclusiveIter.Pos(); + } + + UiaTracing::TextRange::FindAttribute(*this, attributeId, val, searchBackwards, static_cast(**ppRetVal)); + return S_OK; } +CATCH_RETURN(); IFACEMETHODIMP UiaTextRangeBase::FindText(_In_ BSTR text, _In_ BOOL searchBackward, @@ -373,24 +638,172 @@ try } CATCH_RETURN(); -IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, +// Method Description: +// - (1) Checks the current range for the attributeId's sub-type +// - (2) Record the attributeId's sub-type +// Arguments: +// - attributeId - the UIA text attribute identifier we're looking for +// - pRetVal - the attributeId's sub-type for the first cell in the range (i.e. foreground color) +// - attr - the text attribute we're checking +// Return Value: +// - true, if the attributeId is supported. false, otherwise. +// - pRetVal is populated with the appropriate response relevant to the returned bool. +bool UiaTextRangeBase::_initializeAttrQuery(TEXTATTRIBUTEID attributeId, VARIANT* pRetVal, const TextAttribute& attr) const +{ + THROW_HR_IF(E_INVALIDARG, pRetVal == nullptr); + + switch (attributeId) + { + case UIA_BackgroundColorAttributeId: + { + pRetVal->vt = VT_I4; + pRetVal->lVal = _RemoveAlpha(_pData->GetAttributeColors(attr).second); + return true; + } + case UIA_FontWeightAttributeId: + { + // The font weight can be any value from 0 to 900. + // The text buffer doesn't store the actual value, + // we just store "IsBold" and "IsFaint". + // Source: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-textattribute-ids + pRetVal->vt = VT_I4; + pRetVal->lVal = attr.IsBold() ? FW_BOLD : FW_NORMAL; + return true; + } + case UIA_ForegroundColorAttributeId: + { + pRetVal->vt = VT_I4; + pRetVal->lVal = _RemoveAlpha(_pData->GetAttributeColors(attr).first); + return true; + } + case UIA_IsItalicAttributeId: + { + pRetVal->vt = VT_BOOL; + pRetVal->boolVal = attr.IsItalic(); + return true; + } + case UIA_StrikethroughStyleAttributeId: + { + pRetVal->vt = VT_I4; + pRetVal->lVal = attr.IsCrossedOut() ? TextDecorationLineStyle_Single : TextDecorationLineStyle_None; + return true; + } + case UIA_UnderlineStyleAttributeId: + { + pRetVal->vt = VT_I4; + if (attr.IsDoublyUnderlined()) + { + pRetVal->lVal = TextDecorationLineStyle_Double; + } + else if (attr.IsUnderlined()) + { + pRetVal->lVal = TextDecorationLineStyle_Single; + } + else + { + pRetVal->lVal = TextDecorationLineStyle_None; + } + return true; + } + default: + // This attribute is not supported. + pRetVal->vt = VT_UNKNOWN; + UiaGetReservedNotSupportedValue(&pRetVal->punkVal); + return false; + } +} + +IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID attributeId, _Out_ VARIANT* pRetVal) noexcept +try { RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); + VariantInit(pRetVal); - if (textAttributeId == UIA_IsReadOnlyAttributeId) + // AttributeIDs that require special handling + switch (attributeId) + { + case UIA_FontNameAttributeId: + { + pRetVal->vt = VT_BSTR; + pRetVal->bstrVal = SysAllocString(_pData->GetFontInfo().GetFaceName().data()); + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal); + return S_OK; + } + case UIA_IsReadOnlyAttributeId: { pRetVal->vt = VT_BOOL; pRetVal->boolVal = VARIANT_FALSE; + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal); + return S_OK; } - else + default: + break; + } + + // AttributeIDs that are exposed via TextAttribute + try { - pRetVal->vt = VT_UNKNOWN; - UiaGetReservedNotSupportedValue(&pRetVal->punkVal); + // Unlike a normal text editor, which applies formatting at the caret, + // we don't know what attributes are written at a degenerate range. + // So instead, we'll use GetCurrentAttributes to get an idea of the default + // text attributes used. And return a result based off of that. + const auto attr{ IsDegenerate() ? _pData->GetTextBuffer().GetCurrentAttributes() : + _pData->GetTextBuffer().GetCellDataAt(_start)->TextAttr() }; + if (!_initializeAttrQuery(attributeId, pRetVal, attr)) + { + // The AttributeID is not supported. + pRetVal->vt = VT_UNKNOWN; + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal, UiaTracing::AttributeType::Unsupported); + return UiaGetReservedNotSupportedValue(&pRetVal->punkVal); + } + else if (IsDegenerate()) + { + // If we're a degenerate range, we have all the information we need. + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal); + return S_OK; + } } - UiaTracing::TextRange::GetAttributeValue(*this, textAttributeId, *pRetVal); + catch (...) + { + LOG_HR(wil::ResultFromCaughtException()); + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal, UiaTracing::AttributeType::Error); + return E_INVALIDARG; + } + + // Get some useful variables + const auto& buffer{ _pData->GetTextBuffer() }; + const auto bufferSize{ buffer.GetSize() }; + const auto inclusiveEnd{ _getInclusiveEnd() }; + + // Check if the entire text range has that text attribute + Viewport viewportRange{ bufferSize }; + if (_blockRange) + { + const auto originX{ std::min(_start.X, inclusiveEnd.X) }; + const auto originY{ std::min(_start.Y, inclusiveEnd.Y) }; + const auto width{ gsl::narrow_cast(std::abs(inclusiveEnd.X - _start.X + 1)) }; + const auto height{ gsl::narrow_cast(std::abs(inclusiveEnd.Y - _start.Y + 1)) }; + viewportRange = Viewport::FromDimensions({ originX, originY }, width, height); + } + auto iter{ buffer.GetCellDataAt(_start, viewportRange) }; + for (; iter && iter.Pos() != inclusiveEnd; ++iter) + { + if (!_verifyAttr(attributeId, *pRetVal, iter->TextAttr()).value()) + { + // The value of the specified attribute varies over the text range + // return UiaGetReservedMixedAttributeValue. + // Source: https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nf-uiautomationcore-itextrangeprovider-getattributevalue + pRetVal->vt = VT_UNKNOWN; + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal, UiaTracing::AttributeType::Mixed); + return UiaGetReservedMixedAttributeValue(&pRetVal->punkVal); + } + } + + UiaTracing::TextRange::GetAttributeValue(*this, attributeId, *pRetVal); return S_OK; } +CATCH_RETURN(); IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) noexcept { @@ -1245,3 +1658,10 @@ RECT UiaTextRangeBase::_getTerminalRect() const gsl::narrow(result.top + result.height) }; } + +COORD UiaTextRangeBase::_getInclusiveEnd() noexcept +{ + auto result{ _end }; + _pData->GetTextBuffer().GetSize().DecrementInBounds(result, true); + return result; +} diff --git a/src/types/UiaTextRangeBase.hpp b/src/types/UiaTextRangeBase.hpp index d8d7fcb1de1..47b9778a0b0 100644 --- a/src/types/UiaTextRangeBase.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -177,6 +177,11 @@ namespace Microsoft::Console::Types gsl::not_null const pAmountMoved, _In_ const bool preventBufferEnd = false) noexcept; + std::optional _verifyAttr(TEXTATTRIBUTEID attributeId, VARIANT val, const TextAttribute& attr) const; + bool _initializeAttrQuery(TEXTATTRIBUTEID attributeId, VARIANT* pRetVal, const TextAttribute& attr) const; + + COORD _getInclusiveEnd() noexcept; + #ifdef UNIT_TESTING friend class ::UiaTextRangeTests; #endif diff --git a/src/types/UiaTracing.cpp b/src/types/UiaTracing.cpp index efee217d0b9..8c8a1c0d315 100644 --- a/src/types/UiaTracing.cpp +++ b/src/types/UiaTracing.cpp @@ -126,6 +126,44 @@ inline std::wstring UiaTracing::_getValue(const TextUnit unit) noexcept } } +inline std::wstring UiaTracing::_getValue(const VARIANT val) noexcept +{ + // This is not a comprehensive conversion of VARIANT result to string + // We're only including the one's we need at this time. + switch (val.vt) + { + case VT_BSTR: + return val.bstrVal; + case VT_R8: + return std::to_wstring(val.dblVal); + case VT_BOOL: + return std::to_wstring(val.boolVal); + case VT_I4: + return std::to_wstring(val.lVal); + case VT_UNKNOWN: + default: + { + return L"unknown"; + } + } +} + +inline std::wstring UiaTracing::_getValue(const AttributeType attrType) noexcept +{ + switch (attrType) + { + case AttributeType::Mixed: + return L"Mixed"; + case AttributeType::Unsupported: + return L"Unsupported"; + case AttributeType::Error: + return L"Error"; + case AttributeType::Standard: + default: + return L"Standard"; + } +} + void UiaTracing::TextRange::Constructor(UiaTextRangeBase& result) noexcept { EnsureRegistration(); @@ -206,15 +244,20 @@ void UiaTracing::TextRange::ExpandToEnclosingUnit(TextUnit unit, const UiaTextRa } } -void UiaTracing::TextRange::FindAttribute(const UiaTextRangeBase& utr) noexcept +void UiaTracing::TextRange::FindAttribute(const UiaTextRangeBase& utr, TEXTATTRIBUTEID id, VARIANT val, BOOL searchBackwards, const UiaTextRangeBase& result, AttributeType attrType) noexcept { EnsureRegistration(); if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) { TraceLoggingWrite( g_UiaProviderTraceProvider, - "UiaTextRange::FindAttribute (UNSUPPORTED)", + "UiaTextRange::FindAttribute", TraceLoggingValue(_getValue(utr).c_str(), "base"), + TraceLoggingValue(id, "text attribute ID"), + TraceLoggingValue(_getValue(val).c_str(), "text attribute sub-data"), + TraceLoggingValue(searchBackwards ? L"true" : L"false", "search backwards"), + TraceLoggingValue(_getValue(attrType).c_str(), "attribute type"), + TraceLoggingValue(_getValue(result).c_str(), "result"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); } @@ -238,7 +281,7 @@ void UiaTracing::TextRange::FindText(const UiaTextRangeBase& base, std::wstring } } -void UiaTracing::TextRange::GetAttributeValue(const UiaTextRangeBase& base, TEXTATTRIBUTEID id, VARIANT result) noexcept +void UiaTracing::TextRange::GetAttributeValue(const UiaTextRangeBase& base, TEXTATTRIBUTEID id, VARIANT result, AttributeType attrType) noexcept { EnsureRegistration(); if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) @@ -247,8 +290,9 @@ void UiaTracing::TextRange::GetAttributeValue(const UiaTextRangeBase& base, TEXT g_UiaProviderTraceProvider, "UiaTextRange::GetAttributeValue", TraceLoggingValue(_getValue(base).c_str(), "base"), - TraceLoggingValue(id, "textAttributeId"), - TraceLoggingValue(result.vt, "result (type)"), + TraceLoggingValue(id, "text attribute ID"), + TraceLoggingValue(_getValue(result).c_str(), "result"), + TraceLoggingValue(_getValue(attrType).c_str(), "attribute type"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); } diff --git a/src/types/UiaTracing.h b/src/types/UiaTracing.h index 6a8c0a00885..3cb4b7e9106 100644 --- a/src/types/UiaTracing.h +++ b/src/types/UiaTracing.h @@ -29,6 +29,14 @@ namespace Microsoft::Console::Types class UiaTracing final { public: + enum class AttributeType + { + Standard, + Mixed, + Unsupported, + Error + }; + class TextRange final { public: @@ -37,9 +45,9 @@ namespace Microsoft::Console::Types static void Compare(const UiaTextRangeBase& base, const UiaTextRangeBase& other, bool result) noexcept; static void CompareEndpoints(const UiaTextRangeBase& base, const TextPatternRangeEndpoint endpoint, const UiaTextRangeBase& other, TextPatternRangeEndpoint otherEndpoint, int result) noexcept; static void ExpandToEnclosingUnit(TextUnit unit, const UiaTextRangeBase& result) noexcept; - static void FindAttribute(const UiaTextRangeBase& base) noexcept; + static void FindAttribute(const UiaTextRangeBase& base, TEXTATTRIBUTEID attributeId, VARIANT val, BOOL searchBackwards, const UiaTextRangeBase& result, AttributeType attrType = AttributeType::Standard) noexcept; static void FindText(const UiaTextRangeBase& base, std::wstring text, bool searchBackward, bool ignoreCase, const UiaTextRangeBase& result) noexcept; - static void GetAttributeValue(const UiaTextRangeBase& base, TEXTATTRIBUTEID id, VARIANT result) noexcept; + static void GetAttributeValue(const UiaTextRangeBase& base, TEXTATTRIBUTEID id, VARIANT result, AttributeType attrType = AttributeType::Standard) noexcept; static void GetBoundingRectangles(const UiaTextRangeBase& base) noexcept; static void GetEnclosingElement(const UiaTextRangeBase& base) noexcept; static void GetText(const UiaTextRangeBase& base, int maxLength, std::wstring result) noexcept; @@ -101,6 +109,9 @@ namespace Microsoft::Console::Types static inline std::wstring _getValue(const TextPatternRangeEndpoint endpoint) noexcept; static inline std::wstring _getValue(const TextUnit unit) noexcept; + static inline std::wstring _getValue(const AttributeType attrType) noexcept; + static inline std::wstring _getValue(const VARIANT val) noexcept; + // these are used to assign IDs to new UiaTextRanges and ScreenInfoUiaProviders respectively static IdType _utrId; static IdType _siupId;