/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #if QT_CONFIG(accessibility) #include "qwindowsuiamainprovider.h" #include "qwindowsuiavalueprovider.h" #include "qwindowsuiarangevalueprovider.h" #include "qwindowsuiatextprovider.h" #include "qwindowsuiatoggleprovider.h" #include "qwindowsuiainvokeprovider.h" #include "qwindowsuiaselectionprovider.h" #include "qwindowsuiaselectionitemprovider.h" #include "qwindowsuiatableprovider.h" #include "qwindowsuiatableitemprovider.h" #include "qwindowsuiagridprovider.h" #include "qwindowsuiagriditemprovider.h" #include "qwindowsuiawindowprovider.h" #include "qwindowscombase.h" #include "qwindowscontext.h" #include "qwindowsuiautils.h" #include "qwindowsuiaprovidercache.h" #include #include #include #include #if !defined(Q_CC_BOR) && !defined (Q_CC_GNU) #include #endif #include QT_BEGIN_NAMESPACE using namespace QWindowsUiAutomation; // Returns a cached instance of the provider for a specific acessible interface. QWindowsUiaMainProvider *QWindowsUiaMainProvider::providerForAccessible(QAccessibleInterface *accessible) { if (!accessible) return nullptr; QAccessible::Id id = QAccessible::uniqueId(accessible); QWindowsUiaProviderCache *providerCache = QWindowsUiaProviderCache::instance(); auto *provider = qobject_cast(providerCache->providerForId(id)); if (provider) { provider->AddRef(); } else { provider = new QWindowsUiaMainProvider(accessible); providerCache->insert(id, provider); } return provider; } QWindowsUiaMainProvider::QWindowsUiaMainProvider(QAccessibleInterface *a, int initialRefCount) : QWindowsUiaBaseProvider(QAccessible::uniqueId(a)), m_ref(initialRefCount) { } QWindowsUiaMainProvider::~QWindowsUiaMainProvider() { } void QWindowsUiaMainProvider::notifyFocusChange(QAccessibleEvent *event) { if (QAccessibleInterface *accessible = event->accessibleInterface()) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId); } } } void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *event) { if (QAccessibleInterface *accessible = event->accessibleInterface()) { if (event->changedStates().checked || event->changedStates().checkStateMixed) { // Notifies states changes in checkboxes. if (accessible->role() == QAccessible::CheckBox) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { VARIANT oldVal, newVal; clearVariant(&oldVal); int toggleState = ToggleState_Off; if (accessible->state().checked) toggleState = accessible->state().checkStateMixed ? ToggleState_Indeterminate : ToggleState_On; setVariantI4(toggleState, &newVal); QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ToggleToggleStatePropertyId, oldVal, newVal); } } } if (event->changedStates().active) { if (accessible->role() == QAccessible::Window) { // Notifies window opened/closed. if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { if (accessible->state().active) { QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowOpenedEventId); } else { QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowClosedEventId); } } } } } } void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *event) { if (QAccessibleInterface *accessible = event->accessibleInterface()) { if (accessible->role() == QAccessible::ComboBox && accessible->childCount() > 0) { QAccessibleInterface *listacc = accessible->child(0); if (listacc && listacc->role() == QAccessible::List) { int count = listacc->childCount(); for (int i = 0; i < count; ++i) { QAccessibleInterface *item = listacc->child(i); if (item && item->isValid() && item->text(QAccessible::Name) == event->value()) { if (!item->state().selected) { if (QAccessibleActionInterface *actionInterface = item->actionInterface()) actionInterface->doAction(QAccessibleActionInterface::toggleAction()); } break; } } } } if (event->value().type() == QVariant::String) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { // Notifies changes in string values. VARIANT oldVal, newVal; clearVariant(&oldVal); setVariantString(event->value().toString(), &newVal); QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ValueValuePropertyId, oldVal, newVal); } } else if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { // Notifies changes in values of controls supporting the value interface. VARIANT oldVal, newVal; clearVariant(&oldVal); setVariantDouble(valueInterface->currentValue().toDouble(), &newVal); QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_RangeValueValuePropertyId, oldVal, newVal); } } } } void QWindowsUiaMainProvider::notifySelectionChange(QAccessibleEvent *event) { if (QAccessibleInterface *accessible = event->accessibleInterface()) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_SelectionItem_ElementSelectedEventId); } } } // Notifies changes in text content and selection state of text controls. void QWindowsUiaMainProvider::notifyTextChange(QAccessibleEvent *event) { if (QAccessibleInterface *accessible = event->accessibleInterface()) { if (accessible->textInterface()) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { if (event->type() == QAccessible::TextSelectionChanged) { QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); } else if (event->type() == QAccessible::TextCaretMoved) { if (!accessible->state().readOnly) { QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId); } } else { QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextChangedEventId); } } } } } HRESULT STDMETHODCALLTYPE QWindowsUiaMainProvider::QueryInterface(REFIID iid, LPVOID *iface) { if (!iface) return E_INVALIDARG; *iface = nullptr; QAccessibleInterface *accessible = accessibleInterface(); const bool result = qWindowsComQueryUnknownInterfaceMulti(this, iid, iface) || qWindowsComQueryInterface(this, iid, iface) || qWindowsComQueryInterface(this, iid, iface) || (accessible && hwndForAccessible(accessible) && qWindowsComQueryInterface(this, iid, iface)); return result ? S_OK : E_NOINTERFACE; } ULONG QWindowsUiaMainProvider::AddRef() { return ++m_ref; } ULONG STDMETHODCALLTYPE QWindowsUiaMainProvider::Release() { if (!--m_ref) { delete this; return 0; } return m_ref; } HRESULT QWindowsUiaMainProvider::get_ProviderOptions(ProviderOptions *pRetVal) { if (!pRetVal) return E_INVALIDARG; // We are STA, (OleInitialize()). *pRetVal = static_cast(ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading); return S_OK; } // Return providers for specific control patterns HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknown **pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idPattern; if (!pRetVal) return E_INVALIDARG; *pRetVal = nullptr; QAccessibleInterface *accessible = accessibleInterface(); if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; switch (idPattern) { case UIA_WindowPatternId: if (accessible->parent() && (accessible->parent()->role() == QAccessible::Application)) { *pRetVal = new QWindowsUiaWindowProvider(id()); } break; case UIA_TextPatternId: case UIA_TextPattern2Id: // All text controls. if (accessible->textInterface()) { *pRetVal = new QWindowsUiaTextProvider(id()); } break; case UIA_ValuePatternId: // All non-static controls support the Value pattern. if (accessible->role() != QAccessible::StaticText) *pRetVal = new QWindowsUiaValueProvider(id()); break; case UIA_RangeValuePatternId: // Controls providing a numeric value within a range (e.g., sliders, scroll bars, dials). if (accessible->valueInterface()) { *pRetVal = new QWindowsUiaRangeValueProvider(id()); } break; case UIA_TogglePatternId: // Checkbox controls. if (accessible->role() == QAccessible::CheckBox || (accessible->role() == QAccessible::MenuItem && accessible->state().checkable)) { *pRetVal = new QWindowsUiaToggleProvider(id()); } break; case UIA_SelectionPatternId: // Lists of items. if (accessible->role() == QAccessible::List) { *pRetVal = new QWindowsUiaSelectionProvider(id()); } break; case UIA_SelectionItemPatternId: // Items within a list and radio buttons. if ((accessible->role() == QAccessible::RadioButton) || (accessible->role() == QAccessible::ListItem)) { *pRetVal = new QWindowsUiaSelectionItemProvider(id()); } break; case UIA_TablePatternId: // Table/tree. if (accessible->tableInterface() && ((accessible->role() == QAccessible::Table) || (accessible->role() == QAccessible::Tree))) { *pRetVal = new QWindowsUiaTableProvider(id()); } break; case UIA_TableItemPatternId: // Item within a table/tree. if (accessible->tableCellInterface() && ((accessible->role() == QAccessible::Cell) || (accessible->role() == QAccessible::TreeItem))) { *pRetVal = new QWindowsUiaTableItemProvider(id()); } break; case UIA_GridPatternId: // Table/tree. if (accessible->tableInterface() && ((accessible->role() == QAccessible::Table) || (accessible->role() == QAccessible::Tree))) { *pRetVal = new QWindowsUiaGridProvider(id()); } break; case UIA_GridItemPatternId: // Item within a table/tree. if (accessible->tableCellInterface() && ((accessible->role() == QAccessible::Cell) || (accessible->role() == QAccessible::TreeItem))) { *pRetVal = new QWindowsUiaGridItemProvider(id()); } break; case UIA_InvokePatternId: // Things that have an invokable action (e.g., simple buttons). if (accessible->actionInterface()) { *pRetVal = new QWindowsUiaInvokeProvider(id()); } break; default: break; } return S_OK; } HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idProp; if (!pRetVal) return E_INVALIDARG; clearVariant(pRetVal); QAccessibleInterface *accessible = accessibleInterface(); if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; bool topLevelWindow = accessible->parent() && (accessible->parent()->role() == QAccessible::Application); switch (idProp) { case UIA_ProcessIdPropertyId: // PID setVariantI4(int(GetCurrentProcessId()), pRetVal); break; case UIA_AccessKeyPropertyId: // Accelerator key. setVariantString(accessible->text(QAccessible::Accelerator), 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); break; case UIA_ClassNamePropertyId: // Class name. if (QObject *o = accessible->object()) { QString className = QLatin1String(o->metaObject()->className()); setVariantString(className, pRetVal); } break; case UIA_FrameworkIdPropertyId: setVariantString(QStringLiteral("Qt"), pRetVal); break; case UIA_ControlTypePropertyId: if (topLevelWindow) { // Reports a top-level widget as a window, instead of "custom". setVariantI4(UIA_WindowControlTypeId, pRetVal); } else { // Control type converted from role. auto controlType = roleToControlTypeId(accessible->role()); // The native OSK should be disbled if the Qt OSK is in use, // or if disabled via application attribute. static bool imModuleEmpty = qEnvironmentVariableIsEmpty("QT_IM_MODULE"); bool nativeVKDisabled = QCoreApplication::testAttribute(Qt::AA_MSWindowsDisableVirtualKeyboard); // If we want to disable the native OSK auto-showing // we have to report text fields as non-editable. if (controlType == UIA_EditControlTypeId && (!imModuleEmpty || nativeVKDisabled)) controlType = UIA_TextControlTypeId; setVariantI4(controlType, pRetVal); } break; case UIA_HelpTextPropertyId: setVariantString(accessible->text(QAccessible::Help), pRetVal); break; case UIA_HasKeyboardFocusPropertyId: if (topLevelWindow) { // Windows set the active state to true when they are focused setVariantBool(accessible->state().active, pRetVal); } else { setVariantBool(accessible->state().focused, pRetVal); } break; case UIA_IsKeyboardFocusablePropertyId: if (topLevelWindow) { // Windows should always be focusable setVariantBool(true, pRetVal); } else { setVariantBool(accessible->state().focusable, pRetVal); } break; case UIA_IsOffscreenPropertyId: setVariantBool(accessible->state().offscreen, pRetVal); break; case UIA_IsContentElementPropertyId: setVariantBool(true, pRetVal); break; case UIA_IsControlElementPropertyId: setVariantBool(true, pRetVal); break; case UIA_IsEnabledPropertyId: setVariantBool(!accessible->state().disabled, pRetVal); break; case UIA_IsPasswordPropertyId: setVariantBool(accessible->role() == QAccessible::EditableText && accessible->state().passwordEdit, pRetVal); break; case UIA_IsPeripheralPropertyId: // True for peripheral UIs. if (QWindow *window = windowForAccessible(accessible)) { const Qt::WindowType wt = window->type(); setVariantBool(wt == Qt::Popup || wt == Qt::ToolTip || wt == Qt::SplashScreen, pRetVal); } break; case UIA_FullDescriptionPropertyId: setVariantString(accessible->text(QAccessible::Description), pRetVal); break; case UIA_NamePropertyId: { QString name = accessible->text(QAccessible::Name); if (name.isEmpty() && topLevelWindow) name = QCoreApplication::applicationName(); setVariantString(name, 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 QString(); if (!result.isEmpty()) result.prepend(u'.'); result.prepend(name); obj = obj->parent(); } } return result; } HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderSimple **pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; if (!pRetVal) return E_INVALIDARG; *pRetVal = nullptr; // 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 S_OK; } // Navigates within the tree of accessible controls. HRESULT QWindowsUiaMainProvider::Navigate(NavigateDirection direction, IRawElementProviderFragment **pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << direction << " this: " << this; if (!pRetVal) return E_INVALIDARG; *pRetVal = nullptr; QAccessibleInterface *accessible = accessibleInterface(); if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; QAccessibleInterface *targetacc = nullptr; if (direction == NavigateDirection_Parent) { if (QAccessibleInterface *parent = accessible->parent()) { // The Application's children are considered top level objects. if (parent->isValid() && parent->role() != QAccessible::Application) { targetacc = parent; } } } else { QAccessibleInterface *parent = nullptr; int index = 0; int incr = 1; switch (direction) { case NavigateDirection_FirstChild: parent = accessible; index = 0; incr = 1; break; case NavigateDirection_LastChild: parent = accessible; index = accessible->childCount() - 1; incr = -1; break; case NavigateDirection_NextSibling: if ((parent = accessible->parent())) index = parent->indexOfChild(accessible) + 1; incr = 1; break; case NavigateDirection_PreviousSibling: if ((parent = accessible->parent())) index = parent->indexOfChild(accessible) - 1; incr = -1; break; default: Q_UNREACHABLE(); break; } if (parent && parent->isValid()) { for (int count = parent->childCount(); index >= 0 && index < count; index += incr) { if (QAccessibleInterface *child = parent->child(index)) { if (child->isValid() && !child->state().invisible) { targetacc = child; break; } } } } } if (targetacc) *pRetVal = providerForAccessible(targetacc); return S_OK; } // Returns a unique id assigned to the UI element, used as key by the UI Automation framework. HRESULT QWindowsUiaMainProvider::GetRuntimeId(SAFEARRAY **pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; if (!pRetVal) return E_INVALIDARG; *pRetVal = nullptr; QAccessibleInterface *accessible = accessibleInterface(); if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; // The UiaAppendRuntimeId constant is used to make then ID unique // among multiple instances running on the system. int rtId[] = { UiaAppendRuntimeId, int(id()) }; if ((*pRetVal = SafeArrayCreateVector(VT_I4, 0, 2))) { for (LONG i = 0; i < 2; ++i) SafeArrayPutElement(*pRetVal, &i, &rtId[i]); } return S_OK; } // Returns the bounding rectangle for the accessible control. HRESULT QWindowsUiaMainProvider::get_BoundingRectangle(UiaRect *pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; if (!pRetVal) return E_INVALIDARG; QAccessibleInterface *accessible = accessibleInterface(); if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; QWindow *window = windowForAccessible(accessible); if (!window) return UIA_E_ELEMENTNOTAVAILABLE; rectToNativeUiaRect(accessible->rect(), window, pRetVal); return S_OK; } HRESULT QWindowsUiaMainProvider::GetEmbeddedFragmentRoots(SAFEARRAY **pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; if (!pRetVal) return E_INVALIDARG; *pRetVal = nullptr; // No embedded roots. return S_OK; } // Sets focus to the control. HRESULT QWindowsUiaMainProvider::SetFocus() { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; QAccessibleInterface *accessible = accessibleInterface(); if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; QAccessibleActionInterface *actionInterface = accessible->actionInterface(); if (!actionInterface) return UIA_E_ELEMENTNOTAVAILABLE; actionInterface->doAction(QAccessibleActionInterface::setFocusAction()); return S_OK; } HRESULT QWindowsUiaMainProvider::get_FragmentRoot(IRawElementProviderFragmentRoot **pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; if (!pRetVal) return E_INVALIDARG; *pRetVal = nullptr; // Our UI Automation implementation considers the window as the root for // non-native controls/fragments. if (QAccessibleInterface *accessible = accessibleInterface()) { if (QWindow *window = windowForAccessible(accessible)) { if (QAccessibleInterface *rootacc = window->accessibleRoot()) { *pRetVal = providerForAccessible(rootacc); } } } return S_OK; } // Returns a provider for the UI element present at the specified screen coordinates. HRESULT QWindowsUiaMainProvider::ElementProviderFromPoint(double x, double y, IRawElementProviderFragment **pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << x << y; if (!pRetVal) { return E_INVALIDARG; } *pRetVal = nullptr; QAccessibleInterface *accessible = accessibleInterface(); if (!accessible) return UIA_E_ELEMENTNOTAVAILABLE; QWindow *window = windowForAccessible(accessible); if (!window) return UIA_E_ELEMENTNOTAVAILABLE; // Scales coordinates from High DPI screens. UiaPoint uiaPoint = {x, y}; QPoint point; nativeUiaPointToPoint(uiaPoint, window, &point); if (auto targetacc = accessible->childAt(point.x(), point.y())) { auto acc = accessible->childAt(point.x(), point.y()); // Reject the cases where childAt() returns a different instance in each call for the same // element (e.g., QAccessibleTree), as it causes an endless loop with Youdao Dictionary installed. if (targetacc == acc) { // Controls can be embedded within grouping elements. By default returns the innermost control. while (acc) { targetacc = acc; // For accessibility tools it may be better to return the text element instead of its subcomponents. if (targetacc->textInterface()) break; acc = targetacc->childAt(point.x(), point.y()); if (acc != targetacc->childAt(point.x(), point.y())) { qCDebug(lcQpaUiAutomation) << "Non-unique childAt() for" << targetacc; break; } } *pRetVal = providerForAccessible(targetacc); } else { qCDebug(lcQpaUiAutomation) << "Non-unique childAt() for" << accessible; } } return S_OK; } // Returns the fragment with focus. HRESULT QWindowsUiaMainProvider::GetFocus(IRawElementProviderFragment **pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; if (!pRetVal) return E_INVALIDARG; *pRetVal = nullptr; if (QAccessibleInterface *accessible = accessibleInterface()) { if (QAccessibleInterface *focusacc = accessible->focusChild()) { *pRetVal = providerForAccessible(focusacc); } } return S_OK; } QT_END_NAMESPACE #endif // QT_CONFIG(accessibility)