diff options
Diffstat (limited to 'src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp')
-rw-r--r-- | src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp | 263 |
1 files changed, 183 insertions, 80 deletions
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index 16f0a3f9a6..0f2e2d8a5c 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -18,15 +18,16 @@ #include "qwindowsuiagriditemprovider.h" #include "qwindowsuiawindowprovider.h" #include "qwindowsuiaexpandcollapseprovider.h" -#include "qwindowscombase.h" #include "qwindowscontext.h" #include "qwindowsuiautils.h" #include "qwindowsuiaprovidercache.h" #include <QtCore/qloggingcategory.h> +#include <QtGui/private/qaccessiblebridgeutils_p.h> #include <QtGui/qaccessible.h> #include <QtGui/qguiapplication.h> #include <QtGui/qwindow.h> +#include <qpa/qplatforminputcontextfactory_p.h> #if !defined(Q_CC_BOR) && !defined (Q_CC_GNU) #include <comdef.h> @@ -61,9 +62,8 @@ QWindowsUiaMainProvider *QWindowsUiaMainProvider::providerForAccessible(QAccessi return provider; } -QWindowsUiaMainProvider::QWindowsUiaMainProvider(QAccessibleInterface *a, int initialRefCount) - : QWindowsUiaBaseProvider(QAccessible::uniqueId(a)), - m_ref(initialRefCount) +QWindowsUiaMainProvider::QWindowsUiaMainProvider(QAccessibleInterface *a) + : QWindowsUiaBaseProvider(QAccessible::uniqueId(a)) { } @@ -80,7 +80,7 @@ void QWindowsUiaMainProvider::notifyFocusChange(QAccessibleEvent *event) accessible = child; } if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId); + UiaRaiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId); } } @@ -97,7 +97,7 @@ void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *eve if (accessible->state().checked) toggleState = accessible->state().checkStateMixed ? ToggleState_Indeterminate : ToggleState_On; setVariantI4(toggleState, &newVal); - QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ToggleToggleStatePropertyId, oldVal, newVal); + UiaRaiseAutomationPropertyChangedEvent(provider, UIA_ToggleToggleStatePropertyId, oldVal, newVal); } } } @@ -106,13 +106,13 @@ void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *eve // Notifies window opened/closed. if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { if (accessible->state().active) { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowOpenedEventId); + UiaRaiseAutomationEvent(provider, UIA_Window_WindowOpenedEventId); if (QAccessibleInterface *focused = accessible->focusChild()) { if (QWindowsUiaMainProvider *focusedProvider = providerForAccessible(focused)) - QWindowsUiaWrapper::instance()->raiseAutomationEvent(focusedProvider, UIA_AutomationFocusChangedEventId); + UiaRaiseAutomationEvent(focusedProvider, UIA_AutomationFocusChangedEventId); } } else { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowClosedEventId); + UiaRaiseAutomationEvent(provider, UIA_Window_WindowClosedEventId); } } } @@ -141,27 +141,11 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve } if (event->value().typeId() == QMetaType::QString) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { - - // Tries to notify the change using UiaRaiseNotificationEvent(), which is only available on - // Windows 10 version 1709 or newer. Otherwise uses UiaRaiseAutomationPropertyChangedEvent(). - - BSTR displayString = bStrFromQString(event->value().toString()); - BSTR activityId = bStrFromQString(QString()); - - HRESULT hr = QWindowsUiaWrapper::instance()->raiseNotificationEvent(provider, NotificationKind_Other, - NotificationProcessing_ImportantMostRecent, - displayString, activityId); - - ::SysFreeString(displayString); - ::SysFreeString(activityId); - - if (hr == static_cast<HRESULT>(UIA_E_NOTSUPPORTED)) { - VARIANT oldVal, newVal; - clearVariant(&oldVal); - setVariantString(event->value().toString(), &newVal); - QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ValueValuePropertyId, oldVal, newVal); - ::SysFreeString(newVal.bstrVal); - } + // Notifies changes in string values. + VARIANT oldVal, newVal; + clearVariant(&oldVal); + setVariantString(event->value().toString(), &newVal); + UiaRaiseAutomationPropertyChangedEvent(provider, UIA_ValueValuePropertyId, oldVal, newVal); } } else if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { @@ -169,7 +153,7 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve VARIANT oldVal, newVal; clearVariant(&oldVal); setVariantDouble(valueInterface->currentValue().toDouble(), &newVal); - QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_RangeValueValuePropertyId, oldVal, newVal); + UiaRaiseAutomationPropertyChangedEvent(provider, UIA_RangeValueValuePropertyId, oldVal, newVal); } } } @@ -185,7 +169,7 @@ void QWindowsUiaMainProvider::notifyNameChange(QAccessibleEvent *event) VARIANT oldVal, newVal; clearVariant(&oldVal); setVariantString(accessible->text(QAccessible::Name), &newVal); - QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_NamePropertyId, oldVal, newVal); + UiaRaiseAutomationPropertyChangedEvent(provider, UIA_NamePropertyId, oldVal, newVal); ::SysFreeString(newVal.bstrVal); } } @@ -196,7 +180,7 @@ void QWindowsUiaMainProvider::notifySelectionChange(QAccessibleEvent *event) { if (QAccessibleInterface *accessible = event->accessibleInterface()) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_SelectionItem_ElementSelectedEventId); + UiaRaiseAutomationEvent(provider, UIA_SelectionItem_ElementSelectedEventId); } } } @@ -208,48 +192,59 @@ void QWindowsUiaMainProvider::notifyTextChange(QAccessibleEvent *event) if (accessible->textInterface()) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { if (event->type() == QAccessible::TextSelectionChanged) { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); + UiaRaiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); } else if (event->type() == QAccessible::TextCaretMoved) { if (!accessible->state().readOnly) { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); + UiaRaiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); } } else { - QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextChangedEventId); + UiaRaiseAutomationEvent(provider, UIA_Text_TextChangedEventId); } } } } } -HRESULT STDMETHODCALLTYPE QWindowsUiaMainProvider::QueryInterface(REFIID iid, LPVOID *iface) +void QWindowsUiaMainProvider::raiseNotification(QAccessibleAnnouncementEvent *event) { - if (!iface) - return E_INVALIDARG; - *iface = nullptr; - - QAccessibleInterface *accessible = accessibleInterface(); + if (QAccessibleInterface *accessible = event->accessibleInterface()) { + if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { + BSTR message = bStrFromQString(event->message()); + QAccessible::AnnouncementPoliteness prio = event->politeness(); + NotificationProcessing processing = (prio == QAccessible::AnnouncementPoliteness::Assertive) + ? NotificationProcessing_ImportantAll + : NotificationProcessing_All; + BSTR activityId = bStrFromQString(QString::fromLatin1("")); + UiaRaiseNotificationEvent(provider, NotificationKind_Other, processing, message, activityId); - const bool result = qWindowsComQueryUnknownInterfaceMulti<IRawElementProviderSimple>(this, iid, iface) - || qWindowsComQueryInterface<IRawElementProviderSimple>(this, iid, iface) - || qWindowsComQueryInterface<IRawElementProviderFragment>(this, iid, iface) - || (accessible && hwndForAccessible(accessible) && qWindowsComQueryInterface<IRawElementProviderFragmentRoot>(this, iid, iface)); - return result ? S_OK : E_NOINTERFACE; + ::SysFreeString(message); + ::SysFreeString(activityId); + } + } } -ULONG QWindowsUiaMainProvider::AddRef() +HRESULT STDMETHODCALLTYPE QWindowsUiaMainProvider::QueryInterface(REFIID iid, LPVOID *iface) { - return ++m_ref; + HRESULT result = QComObject::QueryInterface(iid, iface); + + if (SUCCEEDED(result) && iid == __uuidof(IRawElementProviderFragmentRoot)) { + QAccessibleInterface *accessible = accessibleInterface(); + if (accessible && hwndForAccessible(accessible)) { + result = S_OK; + } else { + result = E_NOINTERFACE; + iface = nullptr; + } + } + + return result; } ULONG STDMETHODCALLTYPE QWindowsUiaMainProvider::Release() { QMutexLocker locker(&m_mutex); - if (!--m_ref) { - delete this; - return 0; - } - return m_ref; + return QComObject::Release(); } HRESULT QWindowsUiaMainProvider::get_ProviderOptions(ProviderOptions *pRetVal) @@ -304,15 +299,18 @@ HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknow *pRetVal = new QWindowsUiaToggleProvider(id()); break; case UIA_SelectionPatternId: - // Lists of items. - if (accessible->role() == QAccessible::List + case UIA_SelectionPattern2Id: + // Selections via QAccessibleSelectionInterface or lists of items. + if (accessible->selectionInterface() + || accessible->role() == QAccessible::List || accessible->role() == QAccessible::PageTabList) { *pRetVal = new QWindowsUiaSelectionProvider(id()); } break; case UIA_SelectionItemPatternId: - // Items within a list and radio buttons. - if ((accessible->role() == QAccessible::RadioButton) + // Parent supports selection interface or items within a list and radio buttons. + if ((accessible->parent() && accessible->parent()->selectionInterface()) + || (accessible->role() == QAccessible::RadioButton) || (accessible->role() == QAccessible::ListItem) || (accessible->role() == QAccessible::PageTab)) { *pRetVal = new QWindowsUiaSelectionItemProvider(id()); @@ -369,6 +367,115 @@ HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknow return S_OK; } +void QWindowsUiaMainProvider::fillVariantArrayForRelation(QAccessibleInterface* accessible, + QAccessible::Relation relation, VARIANT *pRetVal) +{ + Q_ASSERT(accessible); + + typedef QPair<QAccessibleInterface*, QAccessible::Relation> RelationPair; + const QList<RelationPair> relationInterfaces = accessible->relations(relation); + if (relationInterfaces.empty()) + return; + + SAFEARRAY *elements = SafeArrayCreateVector(VT_UNKNOWN, 0, relationInterfaces.size()); + for (LONG i = 0; i < relationInterfaces.size(); ++i) { + if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(relationInterfaces.at(i).first)) { + SafeArrayPutElement(elements, &i, static_cast<IRawElementProviderSimple*>(childProvider)); + childProvider->Release(); + } + } + + pRetVal->vt = VT_UNKNOWN | VT_ARRAY; + pRetVal->parray = elements; +} + +void QWindowsUiaMainProvider::setAriaProperties(QAccessibleInterface *accessible, VARIANT *pRetVal) +{ + Q_ASSERT(accessible); + + QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface(); + if (!attributesIface) + return; + + QString ariaString; + const QList<QAccessible::Attribute> 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<QHash<QString, QString>>())); + const QHash<QString, QString> attrMap = value.value<QHash<QString, QString>>(); + 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<int>()); + 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<int>()); + // 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; @@ -392,9 +499,12 @@ 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); + setVariantString(QAccessibleBridgeUtils::accessibleId(accessible), pRetVal); break; case UIA_ClassNamePropertyId: // Class name. @@ -403,6 +513,15 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR setVariantString(className, pRetVal); } break; + case UIA_DescribedByPropertyId: + fillVariantArrayForRelation(accessible, QAccessible::DescriptionFor, pRetVal); + break; + case UIA_FlowsFromPropertyId: + fillVariantArrayForRelation(accessible, QAccessible::FlowsTo, pRetVal); + break; + case UIA_FlowsToPropertyId: + fillVariantArrayForRelation(accessible, QAccessible::FlowsFrom, pRetVal); + break; case UIA_FrameworkIdPropertyId: setVariantString(QStringLiteral("Qt"), pRetVal); break; @@ -416,7 +535,7 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR // The native OSK should be disabled if the Qt OSK is in use, // or if disabled via application attribute. - static bool imModuleEmpty = qEnvironmentVariableIsEmpty("QT_IM_MODULE"); + static bool imModuleEmpty = QPlatformInputContextFactory::requested().isEmpty(); bool nativeVKDisabled = QCoreApplication::testAttribute(Qt::AA_DisableNativeVirtualKeyboard); // If we want to disable the native OSK auto-showing @@ -483,31 +602,15 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR setVariantString(name, pRetVal); break; } + case UIA_StyleIdAttributeId: + setStyle(accessible, pRetVal); + break; default: break; } return S_OK; } -// Generates an ID based on the name of the controls and their parents. -QString QWindowsUiaMainProvider::automationIdForAccessible(const QAccessibleInterface *accessible) -{ - QString result; - if (accessible) { - QObject *obj = accessible->object(); - while (obj) { - QString name = obj->objectName(); - if (name.isEmpty()) - return result; - if (!result.isEmpty()) - result.prepend(u'.'); - result.prepend(name); - obj = obj->parent(); - } - } - return result; -} - HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderSimple **pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; @@ -519,7 +622,7 @@ HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderS // Returns a host provider only for controls associated with a native window handle. Others should return NULL. if (QAccessibleInterface *accessible = accessibleInterface()) { if (HWND hwnd = hwndForAccessible(accessible)) { - return QWindowsUiaWrapper::instance()->hostProviderFromHwnd(hwnd, pRetVal); + return UiaHostProviderFromHwnd(hwnd, pRetVal); } } return S_OK; |