blob: 9808d5481c0087ad272b697c10eeda2662ef44af [file] [log] [blame]
/****************************************************************************
**
** 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 <QtGui/qtguiglobal.h>
#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 "qwindowsuiaexpandcollapseprovider.h"
#include "qwindowscombase.h"
#include "qwindowscontext.h"
#include "qwindowsuiautils.h"
#include "qwindowsuiaprovidercache.h"
#include <QtCore/qloggingcategory.h>
#include <QtGui/qaccessible.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qwindow.h>
#if !defined(Q_CC_BOR) && !defined (Q_CC_GNU)
#include <comdef.h>
#endif
#include <QtCore/qt_windows.h>
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<QWindowsUiaMainProvider *>(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)) {
// 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);
}
}
} 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::notifyNameChange(QAccessibleEvent *event)
{
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
VARIANT oldVal, newVal;
clearVariant(&oldVal);
setVariantString(accessible->text(QAccessible::Name), &newVal);
QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_NamePropertyId, oldVal, newVal);
::SysFreeString(newVal.bstrVal);
}
}
}
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<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;
}
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>(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:
// Checkboxes and other checkable controls.
if (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;
case UIA_ExpandCollapsePatternId:
// Menu items with submenus.
if (accessible->role() == QAccessible::MenuItem
&& accessible->childCount() > 0
&& accessible->child(0)->role() == QAccessible::PopupMenu) {
*pRetVal = new QWindowsUiaExpandCollapseProvider(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_DisableNativeVirtualKeyboard);
// 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_IsDialogPropertyId:
setVariantBool(accessible->role() == QAccessible::Dialog
|| accessible->role() == QAccessible::AlertMessage, 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)