/*++ Copyright (c) Microsoft Corporation Licensed under the MIT license. Module Name: - UiaTextRange.hpp Abstract: - This module provides UI Automation access to the text of the console window to support both automation tests and accessibility (screen reading) applications. Author(s): - Austin Diviness (AustDi) 2017 --*/ #pragma once #include "precomp.h" #include "../inc/IConsoleWindow.hpp" #include "../types/inc/viewport.hpp" #include "../../buffer/out/cursor.h" #include #include #ifdef UNIT_TESTING class UiaTextRangeTests; #endif // The UiaTextRange deals with several data structures that have // similar semantics. In order to keep the information from these data // structures separated, each structure has its own naming for a // row. // // There is the generic Row, which does not know which data structure // the row came from. // // There is the ViewportRow, which is a 0-indexed row value from the // viewport. The top row of the viewport is at 0, rows below the top // row increase in value and rows above the top row get increasingly // negative. // // ScreenInfoRow is a row from the screen info data structure. They // start at 0 at the top of screen info buffer. Their positions do not // change but their associated row in the text buffer does change each // time a new line is written. // // TextBufferRow is a row from the text buffer. It is not a ROW // struct, but rather the index of a row. This is also 0-indexed. A // TextBufferRow with a value of 0 does not necessarily refer to the // top row of the console. typedef int Row; typedef int ViewportRow; typedef unsigned int ScreenInfoRow; typedef unsigned int TextBufferRow; typedef unsigned long long IdType; // A Column is a row agnostic value that refers to the column an // endpoint is equivalent to. It is 0-indexed. typedef unsigned int Column; // an endpoint is a char location in the text buffer. endpoint 0 is // the first char of the 0th row in the text buffer row array. typedef unsigned int Endpoint; constexpr IdType InvalidId = 0; namespace Microsoft::Console::Interactivity::Win32 { class UiaTextRange final : public ITextRangeProvider { private: static IdType id; protected: // indicates which direction a movement operation // is going enum class MovementDirection { Forward, Backward }; // valid increment amounts for forward and // backward movement enum class MovementIncrement { Forward = 1, Backward = -1 }; // common information used by the variety of // movement operations struct MoveState { // screen/column position of _start ScreenInfoRow StartScreenInfoRow; Column StartColumn; // screen/column position of _end ScreenInfoRow EndScreenInfoRow; Column EndColumn; // last row in the direction being moved ScreenInfoRow LimitingRow; // first column in the direction being moved Column FirstColumnInRow; // last column in the direction being moved Column LastColumnInRow; // increment amount MovementIncrement Increment; // direction moving MovementDirection Direction; MoveState(const UiaTextRange& range, const MovementDirection direction); private: MoveState(const ScreenInfoRow startScreenInfoRow, const Column startColumn, const ScreenInfoRow endScreenInfoRow, const Column endColumn, const ScreenInfoRow limitingRow, const Column firstColumnInRow, const Column lastColumnInRow, const MovementIncrement increment, const MovementDirection direction); #ifdef UNIT_TESTING friend class ::UiaTextRangeTests; #endif }; public: static std::deque GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider); // degenerate range static UiaTextRange* Create(_In_ IRawElementProviderSimple* const pProvider); // degenerate range at cursor position static UiaTextRange* Create(_In_ IRawElementProviderSimple* const pProvider, const Cursor& cursor); // specific endpoint range static UiaTextRange* Create(_In_ IRawElementProviderSimple* const pProvider, const Endpoint start, const Endpoint end, const bool degenerate); // range from a UiaPoint static UiaTextRange* Create(_In_ IRawElementProviderSimple* const pProvider, const UiaPoint point); ~UiaTextRange(); const IdType GetId() const; const Endpoint GetStart() const; const Endpoint GetEnd() const; const bool IsDegenerate() const; // IUnknown methods IFACEMETHODIMP_(ULONG) AddRef(); IFACEMETHODIMP_(ULONG) Release(); IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_result_maybenull_ void** ppInterface); // ITextRangeProvider methods IFACEMETHODIMP Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal); IFACEMETHODIMP Compare(_In_opt_ ITextRangeProvider* pRange, _Out_ BOOL* pRetVal); IFACEMETHODIMP CompareEndpoints(_In_ TextPatternRangeEndpoint endpoint, _In_ ITextRangeProvider* pTargetRange, _In_ TextPatternRangeEndpoint targetEndpoint, _Out_ int* pRetVal); IFACEMETHODIMP ExpandToEnclosingUnit(_In_ TextUnit unit); IFACEMETHODIMP FindAttribute(_In_ TEXTATTRIBUTEID textAttributeId, _In_ VARIANT val, _In_ BOOL searchBackward, _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal); IFACEMETHODIMP FindText(_In_ BSTR text, _In_ BOOL searchBackward, _In_ BOOL ignoreCase, _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal); IFACEMETHODIMP GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, _Out_ VARIANT* pRetVal); IFACEMETHODIMP GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal); IFACEMETHODIMP GetEnclosingElement(_Outptr_result_maybenull_ IRawElementProviderSimple** ppRetVal); IFACEMETHODIMP GetText(_In_ int maxLength, _Out_ BSTR* pRetVal); IFACEMETHODIMP Move(_In_ TextUnit unit, _In_ int count, _Out_ int* pRetVal); IFACEMETHODIMP MoveEndpointByUnit(_In_ TextPatternRangeEndpoint endpoint, _In_ TextUnit unit, _In_ int count, _Out_ int* pRetVal); IFACEMETHODIMP MoveEndpointByRange(_In_ TextPatternRangeEndpoint endpoint, _In_ ITextRangeProvider* pTargetRange, _In_ TextPatternRangeEndpoint targetEndpoint); IFACEMETHODIMP Select(); IFACEMETHODIMP AddToSelection(); IFACEMETHODIMP RemoveFromSelection(); IFACEMETHODIMP ScrollIntoView(_In_ BOOL alignToTop); IFACEMETHODIMP GetChildren(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal); protected: #if _DEBUG void _outputRowConversions(); void _outputObjectState(); #endif IRawElementProviderSimple* const _pProvider; private: // degenerate range UiaTextRange(_In_ IRawElementProviderSimple* const pProvider); // degenerate range at cursor position UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, const Cursor& cursor); // specific endpoint range UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, const Endpoint start, const Endpoint end, const bool degenerate); // range from a UiaPoint UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, const UiaPoint point); UiaTextRange(const UiaTextRange& a); // used to debug objects passed back and forth // between the provider and the client IdType _id; // Ref counter for COM object ULONG _cRefs; // measure units in the form [_start, _end]. _start // may be a bigger number than _end if the range // wraps around the end of the text buffer. // // In this scenario, _start <= _end // 0 ............... N (text buffer line indices) // s-----e (_start to _end) // // In this scenario, _start >= end // 0 ............... N (text buffer line indices) // ---e s----- (_start to _end) // Endpoint _start; Endpoint _end; // The msdn documentation (and hence this class) talks a bunch about a // degenerate range. A range is degenerate if it contains // no text (both the start and end endpoints are the same). Note that // a degenerate range may have a position in the text. We indicate a // degenerate range internally with a bool. If a range is degenerate // then both endpoints will contain the same value. bool _degenerate; static const Microsoft::Console::Types::Viewport& _getViewport(); static HWND _getWindowHandle(); static IConsoleWindow* const _getIConsoleWindow(); static SCREEN_INFORMATION& _getScreenInfo(); static TextBuffer& _getTextBuffer(); static const COORD _getScreenBufferCoords(); static const unsigned int _getTotalRows(); static const unsigned int _getRowWidth(); static const unsigned int _getFirstScreenInfoRowIndex(); static const unsigned int _getLastScreenInfoRowIndex(); static const Column _getFirstColumnIndex(); static const Column _getLastColumnIndex(); const unsigned int _rowCountInRange() const; static const TextBufferRow _endpointToTextBufferRow(const Endpoint endpoint); static const ScreenInfoRow _textBufferRowToScreenInfoRow(const TextBufferRow row); static const TextBufferRow _screenInfoRowToTextBufferRow(const ScreenInfoRow row); static const Endpoint _textBufferRowToEndpoint(const TextBufferRow row); static const ScreenInfoRow _endpointToScreenInfoRow(const Endpoint endpoint); static const Endpoint _screenInfoRowToEndpoint(const ScreenInfoRow row); static COORD _endpointToCoord(const Endpoint endpoint); static Endpoint _coordToEndpoint(const COORD coord); static const Column _endpointToColumn(const Endpoint endpoint); static const Row _normalizeRow(const Row row); static const ViewportRow _screenInfoRowToViewportRow(const ScreenInfoRow row); static const ViewportRow _screenInfoRowToViewportRow(const ScreenInfoRow row, const SMALL_RECT viewport); static const bool _isScreenInfoRowInViewport(const ScreenInfoRow row); static const bool _isScreenInfoRowInViewport(const ScreenInfoRow row, const SMALL_RECT viewport); static const unsigned int _getViewportHeight(const SMALL_RECT viewport); static const unsigned int _getViewportWidth(const SMALL_RECT viewport); void _addScreenInfoRowBoundaries(const ScreenInfoRow screenInfoRow, _Inout_ std::vector& coords) const; static const int _compareScreenCoords(const ScreenInfoRow rowA, const Column colA, const ScreenInfoRow rowB, const Column colB); static std::pair _moveByCharacter(const int moveCount, const MoveState moveState, _Out_ int* const pAmountMoved); static std::pair _moveByCharacterForward(const int moveCount, const MoveState moveState, _Out_ int* const pAmountMoved); static std::pair _moveByCharacterBackward(const int moveCount, const MoveState moveState, _Out_ int* const pAmountMoved); static std::pair _moveByLine(const int moveCount, const MoveState moveState, _Out_ int* const pAmountMoved); static std::pair _moveByDocument(const int moveCount, const MoveState moveState, _Out_ int* const pAmountMoved); static std::tuple _moveEndpointByUnitCharacter(const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, _Out_ int* const pAmountMoved); static std::tuple _moveEndpointByUnitCharacterForward(const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, _Out_ int* const pAmountMoved); static std::tuple _moveEndpointByUnitCharacterBackward(const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, _Out_ int* const pAmountMoved); static std::tuple _moveEndpointByUnitLine(const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, _Out_ int* const pAmountMoved); static std::tuple _moveEndpointByUnitDocument(const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, _Out_ int* const pAmountMoved); #ifdef UNIT_TESTING friend class ::UiaTextRangeTests; #endif }; namespace UiaTextRangeTracing { enum class ApiCall { Constructor, AddRef, Release, QueryInterface, Clone, Compare, CompareEndpoints, ExpandToEnclosingUnit, FindAttribute, FindText, GetAttributeValue, GetBoundingRectangles, GetEnclosingElement, GetText, Move, MoveEndpointByUnit, MoveEndpointByRange, Select, AddToSelection, RemoveFromSelection, ScrollIntoView, GetChildren }; struct IApiMsg { }; struct ApiMsgConstructor : public IApiMsg { IdType Id; }; struct ApiMsgClone : public IApiMsg { IdType CloneId; }; struct ApiMsgCompare : public IApiMsg { IdType OtherId; bool Equal; }; struct ApiMsgCompareEndpoints : public IApiMsg { IdType OtherId; TextPatternRangeEndpoint Endpoint; TextPatternRangeEndpoint TargetEndpoint; int Result; }; struct ApiMsgExpandToEnclosingUnit : public IApiMsg { TextUnit Unit; Endpoint OriginalStart; Endpoint OriginalEnd; }; struct ApiMsgGetText : IApiMsg { const wchar_t* Text; }; struct ApiMsgMove : IApiMsg { Endpoint OriginalStart; Endpoint OriginalEnd; TextUnit Unit; int RequestedCount; int MovedCount; }; struct ApiMsgMoveEndpointByUnit : IApiMsg { Endpoint OriginalStart; Endpoint OriginalEnd; TextPatternRangeEndpoint Endpoint; TextUnit Unit; int RequestedCount; int MovedCount; }; struct ApiMsgMoveEndpointByRange : IApiMsg { Endpoint OriginalStart; Endpoint OriginalEnd; TextPatternRangeEndpoint Endpoint; TextPatternRangeEndpoint TargetEndpoint; IdType OtherId; }; struct ApiMsgScrollIntoView : IApiMsg { bool AlignToTop; }; } }