From bd0a6e83079efd8a26d9ece6055f584a8c89c1fd Mon Sep 17 00:00:00 2001 From: Michael Weghorn Date: Sat, 11 Nov 2023 00:03:48 +0100 Subject: [PATCH] a11y uia: Bridge QAccessibleAttributesInterface to UIA This bridges the 2 currently existing attribute types for the newly added QAccessibleAttributesInterface to UIA. As described in the AT-SPI bridge implementation already, QAccessible::Attribute::Level semantically matches the "aria-level" ARIA attribute, and is mapped to a "level" property for the UIA_AriaPropertiesPropertyId as described in the Core Accessibility API Mappings specification for both, headings [1] and non-headings [2]. Map all attributes set in QAccessible::Attribute::Custom to that property as well, keeping key and value as they are. As described in the UIA Element Properties Identifiers doc [3] for UIA_AriaPropertiesPropertyId: > AriaProperties is a collection of Name/Value pairs with > delimiters of = (equals) and ; (semicolon), for example, > "checked=true;disabled=false". In addition, if the level attribute is set for an object of role heading, report the corresponding StyleId_Heading for UIA_StyleIdAttributeId, as also described in the Core Accessibility API Mappings spec [1]. For MingW, add UIA_StyleIdAttributeId and StyleId_Heading defines to qwindowsuiautomation.h, as the MingW headers apparently don't have them yet (see log of failed MingW builds without those defines: [4] [5]). [1] https://www.w3.org/TR/core-aam-1.2/#ariaLevelHeading [2] https://www.w3.org/TR/core-aam-1.2/#ariaLevel [3] https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-automation-element-propids [4] https://testresults.qt.io/logs/qt/qtbase/95edcdf493fedcc1ea8f8e824952bdb7c3a34ade/WindowsWindows_11_23H2x86_64WindowsWindows_11_23H2x86_64Clangqtci-windows-11_23H2-x86_64-52-70a4f4Sccache_UseConfigure/051e46739b880c22b4046471996739d0ea38dfd2/build_1716364832/log.txt.gz [5] https://testresults.qt.io/logs/qt/qtbase/c62cb39d9177e0ea724751e6346717855c91b2c7/WindowsWindows_10_22H2x86_64WindowsWindows_10_22H2x86_64Mingwqtci-windows-10_22H2-x86_64-51-30b063Sccache_UseConfigure_WarningsAreErrors/b8e624167842d77c2f66d5bb28543b39336b4b80/build_1716370445/log.txt.gz Fixes: QTBUG-119057 Change-Id: I00b15e95c35c0f31ba34161bc061a3085fc28682 Reviewed-by: Volker Hilsheimer --- .../uiautomation/qwindowsuiamainprovider.cpp | 93 +++++++++++++++++++ .../uiautomation/qwindowsuiamainprovider.h | 4 + .../uiautomation/qwindowsuiautomation.h | 11 +++ 3 files changed, 108 insertions(+) diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index b8f2d0eadd..be88ab4ae8 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -388,6 +388,93 @@ void QWindowsUiaMainProvider::fillVariantArrayForRelation(QAccessibleInterface* pRetVal->parray = elements; } +void QWindowsUiaMainProvider::setAriaProperties(QAccessibleInterface *accessible, VARIANT *pRetVal) +{ + Q_ASSERT(accessible); + + QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface(); + if (!attributesIface) + return; + + QString ariaString; + const QList attrKeys = attributesIface->attributeKeys(); + for (qsizetype i = 0; i < attrKeys.size(); ++i) { + if (i != 0) + ariaString += QStringLiteral(";"); + const QAccessible::Attribute key = attrKeys.at(i); + const QVariant value = attributesIface->attributeValue(key); + // see "Core Accessibility API Mappings" spec: https://www.w3.org/TR/core-aam-1.2/ + switch (key) { + case QAccessible::Attribute::Custom: + { + // forward custom attributes as-is + Q_ASSERT((value.canConvert>())); + const QHash attrMap = value.value>(); + for (auto [name, val] : attrMap.asKeyValueRange()) { + if (name != *attrMap.keyBegin()) + ariaString += QStringLiteral(";"); + ariaString += name + QStringLiteral("=") + val; + } + break; + } + case QAccessible::Attribute::Level: + Q_ASSERT(value.canConvert()); + ariaString += QStringLiteral("level=") + QString::number(value.toInt()); + break; + default: + break; + } + } + + setVariantString(ariaString, pRetVal); +} + +void QWindowsUiaMainProvider::setStyle(QAccessibleInterface *accessible, VARIANT *pRetVal) +{ + Q_ASSERT(accessible); + + QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface(); + if (!attributesIface) + return; + + // currently, only heading styles are implemented here + if (accessible->role() != QAccessible::Role::Heading) + return; + + const QVariant levelVariant = attributesIface->attributeValue(QAccessible::Attribute::Level); + if (!levelVariant.isValid()) + return; + + Q_ASSERT(levelVariant.canConvert()); + // UIA only has styles for heading levels 1-9 + const int level = levelVariant.toInt(); + if (level < 1 || level > 9) + return; + + const int styleId = styleIdForHeadingLevel(level); + setVariantI4(styleId, pRetVal); +} + +int QWindowsUiaMainProvider::styleIdForHeadingLevel(int headingLevel) +{ + // only heading levels 1-9 have a corresponding UIA style ID + Q_ASSERT(headingLevel > 0 && headingLevel <= 9); + + static constexpr int styles[] = { + StyleId_Heading1, + StyleId_Heading2, + StyleId_Heading3, + StyleId_Heading4, + StyleId_Heading5, + StyleId_Heading6, + StyleId_Heading7, + StyleId_Heading8, + StyleId_Heading9, + }; + + return styles[headingLevel - 1]; +} + HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idProp; @@ -411,6 +498,9 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR // Accelerator key. setVariantString(accessible->text(QAccessible::Accelerator), pRetVal); break; + case UIA_AriaPropertiesPropertyId: + setAriaProperties(accessible, pRetVal); + break; case UIA_AutomationIdPropertyId: // Automation ID, which can be used by tools to select a specific control in the UI. setVariantString(automationIdForAccessible(accessible), pRetVal); @@ -511,6 +601,9 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR setVariantString(name, pRetVal); break; } + case UIA_StyleIdAttributeId: + setStyle(accessible, pRetVal); + break; default: break; } diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h index b24f4a6cc3..dafe877974 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h @@ -61,6 +61,10 @@ public: private: QString automationIdForAccessible(const QAccessibleInterface *accessible); static void fillVariantArrayForRelation(QAccessibleInterface *accessible, QAccessible::Relation relation, VARIANT *pRetVal); + static void setAriaProperties(QAccessibleInterface *accessible, VARIANT *pRetVal); + static void setStyle(QAccessibleInterface *accessible, VARIANT *pRetVal); + /** Returns the UIA style ID for a heading level from 1 to 9. */ + static int styleIdForHeadingLevel(int headingLevel); static QMutex m_mutex; }; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h index a192b9b0fb..4eb37bafa0 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h @@ -14,8 +14,19 @@ #define UIA_SelectionPattern2Id 10034 #define UIA_IsReadOnlyAttributeId 40015 #define UIA_StrikethroughStyleAttributeId 40026 +#define UIA_StyleIdAttributeId 40034 #define UIA_CaretPositionAttributeId 40038 +#define StyleId_Heading1 70001 +#define StyleId_Heading2 70002 +#define StyleId_Heading3 70003 +#define StyleId_Heading4 70004 +#define StyleId_Heading5 70005 +#define StyleId_Heading6 70006 +#define StyleId_Heading7 70007 +#define StyleId_Heading8 70008 +#define StyleId_Heading9 70009 + enum CaretPosition { CaretPosition_Unknown = 0, CaretPosition_EndOfLine = 1,