Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsuiamainprovider.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <QtGui/qtguiglobal.h>
6#if QT_CONFIG(accessibility)
7
8#include "qwindowsuiamainprovider.h"
9#include "qwindowsuiavalueprovider.h"
10#include "qwindowsuiarangevalueprovider.h"
11#include "qwindowsuiatextprovider.h"
12#include "qwindowsuiatoggleprovider.h"
13#include "qwindowsuiainvokeprovider.h"
14#include "qwindowsuiaselectionprovider.h"
15#include "qwindowsuiaselectionitemprovider.h"
16#include "qwindowsuiatableprovider.h"
17#include "qwindowsuiatableitemprovider.h"
18#include "qwindowsuiagridprovider.h"
19#include "qwindowsuiagriditemprovider.h"
20#include "qwindowsuiawindowprovider.h"
21#include "qwindowsuiaexpandcollapseprovider.h"
22#include "qwindowscontext.h"
23#include "qwindowsuiautils.h"
24#include "qwindowsuiaprovidercache.h"
25
26#include <QtCore/qloggingcategory.h>
27#include <QtGui/private/qaccessiblebridgeutils_p.h>
28#include <QtGui/qaccessible.h>
29#include <QtGui/qguiapplication.h>
30#include <QtGui/qwindow.h>
31#include <QtCore/private/qcomvariant_p.h>
32
33#if !defined(Q_CC_BOR) && !defined (Q_CC_GNU)
34#include <comdef.h>
35#endif
36
37#include <QtCore/qt_windows.h>
38
39QT_BEGIN_NAMESPACE
40
41using namespace QWindowsUiAutomation;
42
43// Returns a cached instance of the provider for a specific accessible interface.
44ComPtr<QWindowsUiaMainProvider> QWindowsUiaMainProvider::providerForAccessible(QAccessibleInterface *accessible)
45{
46 if (!accessible)
47 return nullptr;
48
49 QAccessible::Id id = QAccessible::uniqueId(accessible);
50 QWindowsUiaProviderCache *providerCache = QWindowsUiaProviderCache::instance();
51 ComPtr<QWindowsUiaMainProvider> provider = providerCache->providerForId(id);
52
53 if (!provider) {
54 provider = makeComObject<QWindowsUiaMainProvider>(accessible);
55 providerCache->insert(id, provider.Get()); // Cache holds weak references
56 }
57 return provider;
58}
59
60QWindowsUiaMainProvider::QWindowsUiaMainProvider(QAccessibleInterface *a)
61 : QWindowsUiaBaseProvider(QAccessible::uniqueId(a))
62{
63}
64
65QWindowsUiaMainProvider::~QWindowsUiaMainProvider()
66{
67}
68
69void QWindowsUiaMainProvider::notifyFocusChange(QAccessibleEvent *event)
70{
71 if (QAccessibleInterface *accessible = event->accessibleInterface()) {
72 // If this is a complex element, raise event for the focused child instead.
73 if (accessible->childCount()) {
74 if (QAccessibleInterface *child = accessible->focusChild())
75 accessible = child;
76 }
77 if (auto provider = providerForAccessible(accessible))
78 UiaRaiseAutomationEvent(provider.Get(), UIA_AutomationFocusChangedEventId);
79 }
80}
81
82void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *event)
83{
84 if (QAccessibleInterface *accessible = event->accessibleInterface()) {
85 if (event->changedStates().checked || event->changedStates().checkStateMixed) {
86 // Notifies states changes in checkboxes, switches, and checkable item view items.
87 if (accessible->role() == QAccessible::CheckBox
88 || accessible->role() == QAccessible::Switch
89 || accessible->role() == QAccessible::Cell
90 || accessible->role() == QAccessible::ListItem
91 || accessible->role() == QAccessible::TreeItem) {
92 if (auto provider = providerForAccessible(accessible)) {
93 long toggleState = ToggleState_Off;
94 if (accessible->state().checked)
95 toggleState = accessible->state().checkStateMixed ? ToggleState_Indeterminate : ToggleState_On;
96
97 QComVariant oldVal;
98 QComVariant newVal{toggleState};
99 UiaRaiseAutomationPropertyChangedEvent(
100 provider.Get(), UIA_ToggleToggleStatePropertyId, oldVal.get(), newVal.get());
101 }
102 }
103 }
104 if (event->changedStates().expanded) {
105 if (accessible->state().expandable) {
106 if (auto provider = providerForAccessible(accessible)) {
107 long expandedState = accessible->state().expanded ? ExpandCollapseState_Expanded : ExpandCollapseState_Collapsed;
108
109 QComVariant oldVal;
110 QComVariant newVal{expandedState};
111 UiaRaiseAutomationPropertyChangedEvent(
112 provider.Get(), UIA_ExpandCollapseExpandCollapseStatePropertyId, oldVal.get(), newVal.get());
113 }
114 }
115 }
116 if (event->changedStates().active) {
117 if (accessible->role() == QAccessible::Window) {
118 // Notifies window opened/closed.
119 if (auto provider = providerForAccessible(accessible)) {
120 if (accessible->state().active) {
121 UiaRaiseAutomationEvent(provider.Get(), UIA_Window_WindowOpenedEventId);
122 if (QAccessibleInterface *focused = accessible->focusChild()) {
123 if (auto focusedProvider = providerForAccessible(focused)) {
124 UiaRaiseAutomationEvent(focusedProvider.Get(),
125 UIA_AutomationFocusChangedEventId);
126 }
127 }
128 } else {
129 UiaRaiseAutomationEvent(provider.Get(), UIA_Window_WindowClosedEventId);
130 }
131 }
132 }
133 }
134 }
135}
136
137void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *event)
138{
139 if (QAccessibleInterface *accessible = event->accessibleInterface()) {
140 if (accessible->role() == QAccessible::ComboBox && accessible->childCount() > 0) {
141 QAccessibleInterface *listacc = accessible->child(0);
142 if (listacc && listacc->role() == QAccessible::List) {
143 int count = listacc->childCount();
144 for (int i = 0; i < count; ++i) {
145 QAccessibleInterface *item = listacc->child(i);
146 if (item && item->isValid() && item->text(QAccessible::Name) == event->value()) {
147 if (!item->state().selected) {
148 if (QAccessibleActionInterface *actionInterface = item->actionInterface())
149 actionInterface->doAction(QAccessibleActionInterface::toggleAction());
150 }
151 break;
152 }
153 }
154 }
155 }
156 if (event->value().typeId() == QMetaType::QString) {
157 if (auto provider = providerForAccessible(accessible)) {
158 // Notifies changes in string values.
159 const QComVariant oldVal;
160 const QComVariant newVal{ event->value().toString() };
161 UiaRaiseAutomationPropertyChangedEvent(provider.Get(), UIA_ValueValuePropertyId,
162 oldVal.get(), newVal.get());
163 }
164 } else if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) {
165 if (auto provider = providerForAccessible(accessible)) {
166 // Notifies changes in values of controls supporting the value interface.
167 const QComVariant oldVal;
168 const QComVariant newVal{ valueInterface->currentValue().toDouble() };
169 UiaRaiseAutomationPropertyChangedEvent(
170 provider.Get(), UIA_RangeValueValuePropertyId, oldVal.get(), newVal.get());
171 }
172 }
173 }
174}
175
176void QWindowsUiaMainProvider::notifyNameChange(QAccessibleEvent *event)
177{
178 if (QAccessibleInterface *accessible = event->accessibleInterface()) {
179 // Restrict notification to combo boxes and the currently focused element, which
180 // need it for accessibility, in order to avoid slowdowns with unnecessary notifications.
181 if (accessible->role() == QAccessible::ComboBox || accessible->state().focused) {
182 if (auto provider = providerForAccessible(accessible)) {
183 QComVariant oldVal;
184 QComVariant newVal{ accessible->text(QAccessible::Name) };
185 UiaRaiseAutomationPropertyChangedEvent(provider.Get(), UIA_NamePropertyId,
186 oldVal.get(), newVal.get());
187 }
188 }
189 }
190}
191
192void QWindowsUiaMainProvider::notifyRoleChange(QAccessibleEvent *event)
193{
194 if (QAccessibleInterface *accessible = event->accessibleInterface()) {
195 if (auto provider = providerForAccessible(accessible)) {
196 QComVariant oldVal;
197 QComVariant newVal{ roleToControlTypeId(accessible->role()) };
198 UiaRaiseAutomationPropertyChangedEvent(provider.Get(), UIA_ControlTypePropertyId,
199 oldVal.get(), newVal.get());
200 }
201 }
202}
203
204void QWindowsUiaMainProvider::notifySelectionChange(QAccessibleEvent *event)
205{
206 if (QAccessibleInterface *accessible = event->accessibleInterface()) {
207 if (auto provider = providerForAccessible(accessible))
208 UiaRaiseAutomationEvent(provider.Get(), UIA_SelectionItem_ElementSelectedEventId);
209 }
210}
211
212// Notifies changes in text content and selection state of text controls.
213void QWindowsUiaMainProvider::notifyTextChange(QAccessibleEvent *event)
214{
215 if (QAccessibleInterface *accessible = event->accessibleInterface()) {
216 if (accessible->textInterface()) {
217 if (auto provider = providerForAccessible(accessible)) {
218 if (event->type() == QAccessible::TextSelectionChanged) {
219 UiaRaiseAutomationEvent(provider.Get(), UIA_Text_TextSelectionChangedEventId);
220 } else if (event->type() == QAccessible::TextCaretMoved) {
221 if (!accessible->state().readOnly) {
222 UiaRaiseAutomationEvent(provider.Get(),
223 UIA_Text_TextSelectionChangedEventId);
224 }
225 } else {
226 UiaRaiseAutomationEvent(provider.Get(), UIA_Text_TextChangedEventId);
227 }
228 }
229 }
230 }
231}
232
233void QWindowsUiaMainProvider::raiseNotification(QAccessibleAnnouncementEvent *event)
234{
235 if (QAccessibleInterface *accessible = event->accessibleInterface()) {
236 if (auto provider = providerForAccessible(accessible)) {
237 QBStr message{ event->message() };
238 QAccessible::AnnouncementPoliteness prio = event->politeness();
239 NotificationProcessing processing = (prio == QAccessible::AnnouncementPoliteness::Assertive)
240 ? NotificationProcessing_ImportantAll
241 : NotificationProcessing_All;
242 QBStr activityId{ QString::fromLatin1("") };
243#if !defined(Q_CC_MSVC) || !defined(QT_WIN_SERVER_2016_COMPAT)
244 UiaRaiseNotificationEvent(provider.Get(), NotificationKind_Other, processing, message.bstr(),
245 activityId.bstr());
246#else
247 HMODULE uiautomationcore = GetModuleHandleW(L"UIAutomationCore.dll");
248 if (uiautomationcore != NULL) {
249 typedef HRESULT (WINAPI *EVENTFUNC)(IRawElementProviderSimple *, NotificationKind,
250 NotificationProcessing, BSTR, BSTR);
251
252 EVENTFUNC uiaRaiseNotificationEvent =
253 (EVENTFUNC)GetProcAddress(uiautomationcore, "UiaRaiseNotificationEvent");
254 if (uiaRaiseNotificationEvent != NULL) {
255 uiaRaiseNotificationEvent(provider.Get(), NotificationKind_Other, processing,
256 message.bstr(), activityId.bstr());
257 }
258 }
259#endif
260 }
261 }
262}
263
264HRESULT STDMETHODCALLTYPE QWindowsUiaMainProvider::QueryInterface(REFIID iid, LPVOID *iface)
265{
266 HRESULT result = QComObject::QueryInterface(iid, iface);
267
268 if (SUCCEEDED(result) && iid == __uuidof(IRawElementProviderFragmentRoot)) {
269 QAccessibleInterface *accessible = accessibleInterface();
270 if (accessible && hwndForAccessible(accessible)) {
271 result = S_OK;
272 } else {
273 Release();
274 result = E_NOINTERFACE;
275 *iface = nullptr;
276 }
277 }
278
279 return result;
280}
281
282HRESULT QWindowsUiaMainProvider::get_ProviderOptions(ProviderOptions *pRetVal)
283{
284 if (!pRetVal)
285 return E_INVALIDARG;
286 // We are STA, (OleInitialize()).
287 *pRetVal = static_cast<ProviderOptions>(ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading);
288 return S_OK;
289}
290
291// Return providers for specific control patterns
292HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknown **pRetVal)
293{
294 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idPattern;
295
296 if (!pRetVal)
297 return E_INVALIDARG;
298 *pRetVal = nullptr;
299
300 QAccessibleInterface *accessible = accessibleInterface();
301 if (!accessible)
302 return UIA_E_ELEMENTNOTAVAILABLE;
303
304 switch (idPattern) {
305 case UIA_WindowPatternId:
306 if (accessible->parent() && (accessible->parent()->role() == QAccessible::Application)) {
307 *pRetVal = makeComObject<QWindowsUiaWindowProvider>(id()).Detach();
308 }
309 break;
310 case UIA_TextPatternId:
311 case UIA_TextPattern2Id:
312 // All text controls.
313 if (accessible->textInterface()) {
314 *pRetVal = makeComObject<QWindowsUiaTextProvider>(id()).Detach();
315 }
316 break;
317 case UIA_ValuePatternId:
318 // All non-static controls support the Value pattern.
319 if (accessible->role() != QAccessible::StaticText)
320 *pRetVal = makeComObject<QWindowsUiaValueProvider>(id()).Detach();
321 break;
322 case UIA_RangeValuePatternId:
323 // Controls providing a numeric value within a range (e.g., sliders, scroll bars, dials).
324 if (accessible->valueInterface()) {
325 *pRetVal = makeComObject<QWindowsUiaRangeValueProvider>(id()).Detach();
326 }
327 break;
328 case UIA_TogglePatternId:
329 // Checkboxes and other checkable controls.
330 if (accessible->state().checkable)
331 *pRetVal = makeComObject<QWindowsUiaToggleProvider>(id()).Detach();
332 break;
333 case UIA_SelectionPatternId:
334 case UIA_SelectionPattern2Id:
335 // Selections via QAccessibleSelectionInterface or lists of items.
336 if (accessible->selectionInterface()
337 || accessible->role() == QAccessible::List
338 || accessible->role() == QAccessible::PageTabList) {
339 *pRetVal = makeComObject<QWindowsUiaSelectionProvider>(id()).Detach();
340 }
341 break;
342 case UIA_SelectionItemPatternId:
343 // Parent supports selection interface or items within a list and radio buttons.
344 if ((accessible->parent() && accessible->parent()->selectionInterface())
345 || (accessible->role() == QAccessible::RadioButton)
346 || (accessible->role() == QAccessible::ListItem)
347 || (accessible->role() == QAccessible::PageTab)) {
348 *pRetVal = makeComObject<QWindowsUiaSelectionItemProvider>(id()).Detach();
349 }
350 break;
351 case UIA_TablePatternId:
352 // Table/tree.
353 if (accessible->tableInterface()
354 && ((accessible->role() == QAccessible::Table) || (accessible->role() == QAccessible::Tree))) {
355 *pRetVal = makeComObject<QWindowsUiaTableProvider>(id()).Detach();
356 }
357 break;
358 case UIA_TableItemPatternId:
359 // Item within a table/tree.
360 if (accessible->tableCellInterface()
361 && ((accessible->role() == QAccessible::Cell) || (accessible->role() == QAccessible::TreeItem))) {
362 *pRetVal = makeComObject<QWindowsUiaTableItemProvider>(id()).Detach();
363 }
364 break;
365 case UIA_GridPatternId:
366 // Table/tree.
367 if (accessible->tableInterface()
368 && ((accessible->role() == QAccessible::Table) || (accessible->role() == QAccessible::Tree))) {
369 *pRetVal = makeComObject<QWindowsUiaGridProvider>(id()).Detach();
370 }
371 break;
372 case UIA_GridItemPatternId:
373 // Item within a table/tree.
374 if (accessible->tableCellInterface()
375 && ((accessible->role() == QAccessible::Cell) || (accessible->role() == QAccessible::TreeItem))) {
376 *pRetVal = makeComObject<QWindowsUiaGridItemProvider>(id()).Detach();
377 }
378 break;
379 case UIA_InvokePatternId:
380 // Things that have an invokable action (e.g., simple buttons).
381 if (accessible->actionInterface()) {
382 *pRetVal = makeComObject<QWindowsUiaInvokeProvider>(id()).Detach();
383 }
384 break;
385 case UIA_ExpandCollapsePatternId:
386 // Menu items with submenus.
387 if ((accessible->role() == QAccessible::MenuItem
388 && accessible->childCount() > 0
389 && accessible->child(0)->role() == QAccessible::PopupMenu)
390 || accessible->role() == QAccessible::ComboBox
391 || accessible->state().expandable) {
392 *pRetVal = makeComObject<QWindowsUiaExpandCollapseProvider>(id()).Detach();
393 }
394 break;
395 default:
396 break;
397 }
398
399 return S_OK;
400}
401
402void QWindowsUiaMainProvider::setLabelledBy(QAccessibleInterface *accessible, VARIANT *pRetVal)
403{
404 Q_ASSERT(accessible);
405
406 typedef std::pair<QAccessibleInterface*, QAccessible::Relation> RelationPair;
407 const QList<RelationPair> relationInterfaces = accessible->relations(QAccessible::Label);
408 if (relationInterfaces.empty())
409 return;
410
411 // UIA_LabeledByPropertyId only supports one relation
412 ComPtr<IRawElementProviderSimple> provider = providerForAccessible(relationInterfaces.first().first);
413 if (!provider)
414 return;
415
416 pRetVal->vt = VT_UNKNOWN;
417 pRetVal->punkVal = provider.Detach();
418}
419
420void QWindowsUiaMainProvider::fillVariantArrayForRelation(QAccessibleInterface* accessible,
421 QAccessible::Relation relation, VARIANT *pRetVal)
422{
423 Q_ASSERT(accessible);
424
425 typedef std::pair<QAccessibleInterface*, QAccessible::Relation> RelationPair;
426 const QList<RelationPair> relationInterfaces = accessible->relations(relation);
427 if (relationInterfaces.empty())
428 return;
429
430 SAFEARRAY *elements = SafeArrayCreateVector(VT_UNKNOWN, 0, relationInterfaces.size());
431 for (LONG i = 0; i < relationInterfaces.size(); ++i) {
432 if (ComPtr<IRawElementProviderSimple> provider =
433 providerForAccessible(relationInterfaces.at(i).first)) {
434 SafeArrayPutElement(elements, &i, provider.Get());
435 }
436 }
437
438 pRetVal->vt = VT_UNKNOWN | VT_ARRAY;
439 pRetVal->parray = elements;
440}
441
442void QWindowsUiaMainProvider::setAriaProperties(QAccessibleInterface *accessible, VARIANT *pRetVal)
443{
444 Q_ASSERT(accessible);
445
446 QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface();
447 if (!attributesIface)
448 return;
449
450 QString ariaString;
451 const QList<QAccessible::Attribute> attrKeys = attributesIface->attributeKeys();
452 for (qsizetype i = 0; i < attrKeys.size(); ++i) {
453 if (i != 0)
454 ariaString += QStringLiteral(";");
455 const QAccessible::Attribute key = attrKeys.at(i);
456 const QVariant value = attributesIface->attributeValue(key);
457 // see "Core Accessibility API Mappings" spec: https://www.w3.org/TR/core-aam-1.2/
458 switch (key) {
459 case QAccessible::Attribute::Custom:
460 {
461 // forward custom attributes as-is
462 Q_ASSERT((value.canConvert<QHash<QString, QString>>()));
463 const QHash<QString, QString> attrMap = value.value<QHash<QString, QString>>();
464 for (auto [name, val] : attrMap.asKeyValueRange()) {
465 if (name != *attrMap.keyBegin())
466 ariaString += QStringLiteral(";");
467 ariaString += name + QStringLiteral("=") + val;
468 }
469 break;
470 }
471 case QAccessible::Attribute::Level:
472 Q_ASSERT(value.canConvert<int>());
473 ariaString += QStringLiteral("level=") + QString::number(value.toInt());
474 break;
475 case QAccessible::Attribute::PositionInSet:
476 Q_ASSERT(value.canConvert<int>());
477 ariaString += QStringLiteral("posinset=") + QString::number(value.toInt());
478 break;
479 case QAccessible::Attribute::SizeOfSet:
480 Q_ASSERT(value.canConvert<int>());
481 ariaString += QStringLiteral("setsize=") + QString::number(value.toInt());
482 break;
483 default:
484 break;
485 }
486 }
487
488 *pRetVal = QComVariant{ ariaString }.release();
489}
490
491void QWindowsUiaMainProvider::setIntAttribute(QAccessibleInterface *accessible,
492 QAccessible::Attribute attribute,
493 VARIANT *pRetVal)
494{
495 Q_ASSERT(accessible);
496
497 QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface();
498 if (!attributesIface)
499 return;
500
501 const QVariant valueVariant = attributesIface->attributeValue(attribute);
502 if (!valueVariant.isValid())
503 return;
504
505 Q_ASSERT(valueVariant.canConvert<int>());
506 *pRetVal = QComVariant{ long(valueVariant.toInt()) }.release();
507}
508
509void QWindowsUiaMainProvider::setStyle(QAccessibleInterface *accessible, VARIANT *pRetVal)
510{
511 Q_ASSERT(accessible);
512
513 QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface();
514 if (!attributesIface)
515 return;
516
517 // currently, only heading styles are implemented here
518 if (accessible->role() != QAccessible::Role::Heading)
519 return;
520
521 const QVariant levelVariant = attributesIface->attributeValue(QAccessible::Attribute::Level);
522 if (!levelVariant.isValid())
523 return;
524
525 Q_ASSERT(levelVariant.canConvert<int>());
526 // UIA only has styles for heading levels 1-9
527 const int level = levelVariant.toInt();
528 if (level < 1 || level > 9)
529 return;
530
531 const long styleId = styleIdForHeadingLevel(level);
532 *pRetVal = QComVariant{ styleId }.release();
533}
534
535int QWindowsUiaMainProvider::styleIdForHeadingLevel(int headingLevel)
536{
537 // only heading levels 1-9 have a corresponding UIA style ID
538 Q_ASSERT(headingLevel > 0 && headingLevel <= 9);
539
540 static constexpr int styles[] = {
541 StyleId_Heading1,
542 StyleId_Heading2,
543 StyleId_Heading3,
544 StyleId_Heading4,
545 StyleId_Heading5,
546 StyleId_Heading6,
547 StyleId_Heading7,
548 StyleId_Heading8,
549 StyleId_Heading9,
550 };
551
552 return styles[headingLevel - 1];
553}
554
555HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pRetVal)
556{
557 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idProp;
558
559 if (!pRetVal)
560 return E_INVALIDARG;
561 clearVariant(pRetVal);
562
563 QAccessibleInterface *accessible = accessibleInterface();
564 if (!accessible)
565 return UIA_E_ELEMENTNOTAVAILABLE;
566
567 bool topLevelWindow = accessible->parent() && (accessible->parent()->role() == QAccessible::Application);
568
569 switch (idProp) {
570 case UIA_ProcessIdPropertyId:
571 // PID
572 *pRetVal = QComVariant{ static_cast<long>(GetCurrentProcessId()) }.release();
573 break;
574 case UIA_AccessKeyPropertyId:
575 // Accelerator key.
576 *pRetVal = QComVariant{ accessible->text(QAccessible::Accelerator) }.release();
577 break;
578 case UIA_AriaRolePropertyId:
579 if (accessible->role() == QAccessible::Heading)
580 *pRetVal = QComVariant{ QStringLiteral("heading") }.release();
581 break;
582 case UIA_AriaPropertiesPropertyId:
583 setAriaProperties(accessible, pRetVal);
584 break;
585 case UIA_AutomationIdPropertyId:
586 // Automation ID, which can be used by tools to select a specific control in the UI.
587 *pRetVal = QComVariant{ QAccessibleBridgeUtils::accessibleId(accessible) }.release();
588 break;
589 case UIA_ClassNamePropertyId:
590 // Class name.
591 if (QObject *o = accessible->object()) {
592 QString className = QLatin1StringView(o->metaObject()->className());
593 *pRetVal = QComVariant{ className }.release();
594 }
595 break;
596 case UIA_CulturePropertyId:
597 {
598 QLocale locale;
599 if (QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface()) {
600 const QVariant localeVariant = attributesIface->attributeValue(QAccessible::Attribute::Locale);
601 if (localeVariant.isValid()) {
602 Q_ASSERT(localeVariant.canConvert<QLocale>());
603 locale = localeVariant.toLocale();
604 }
605 }
606 LCID lcid = LocaleNameToLCID(qUtf16Printable(locale.bcp47Name()), 0);
607 *pRetVal = QComVariant{ long(lcid) }.release();
608 break;
609 }
610 case UIA_LevelPropertyId:
611 // Structural depth, e.g. of an item in a tree. Headings convey
612 // their level through the heading text style and the "level"
613 // ARIA property instead.
614 if (accessible->role() != QAccessible::Role::Heading)
615 setIntAttribute(accessible, QAccessible::Attribute::Level, pRetVal);
616 break;
617 case UIA_PositionInSetPropertyId:
618 setIntAttribute(accessible, QAccessible::Attribute::PositionInSet, pRetVal);
619 break;
620 case UIA_SizeOfSetPropertyId:
621 setIntAttribute(accessible, QAccessible::Attribute::SizeOfSet, pRetVal);
622 break;
623 case UIA_DescribedByPropertyId:
624 fillVariantArrayForRelation(accessible, QAccessible::DescriptionFor, pRetVal);
625 break;
626 case UIA_FlowsFromPropertyId:
627 fillVariantArrayForRelation(accessible, QAccessible::FlowsTo, pRetVal);
628 break;
629 case UIA_FlowsToPropertyId:
630 fillVariantArrayForRelation(accessible, QAccessible::FlowsFrom, pRetVal);
631 break;
632 case UIA_LabeledByPropertyId:
633 setLabelledBy(accessible, pRetVal);
634 break;
635 case UIA_FrameworkIdPropertyId:
636 *pRetVal = QComVariant{ QStringLiteral("Qt") }.release();
637 break;
638 case UIA_ControlTypePropertyId:
639 if (topLevelWindow) {
640 // Reports a top-level widget as a window, instead of "custom".
641 *pRetVal = QComVariant{ UIA_WindowControlTypeId }.release();
642 } else {
643 // Control type converted from role.
644 *pRetVal = QComVariant{ roleToControlTypeId(accessible->role()) }.release();
645 }
646 break;
647 case UIA_HelpTextPropertyId:
648 *pRetVal = QComVariant{ accessible->text(QAccessible::Help) }.release();
649 break;
650 case UIA_HasKeyboardFocusPropertyId:
651 // If the top-level window has no focused child, report the top-level
652 // widget (window). If it already has a focused widget, it will be
653 // reported automatically.
654 if (topLevelWindow) {
655 QAccessibleInterface *focusacc = accessible->focusChild();
656 if (!focusacc) {
657 *pRetVal = QComVariant{ accessible->state().active ? true : false }.release();
658 break;
659 }
660 }
661 *pRetVal = QComVariant{ accessible->state().focused ? true : false }.release();
662 break;
663 case UIA_IsKeyboardFocusablePropertyId:
664 if (topLevelWindow) {
665 // Windows should always be focusable
666 *pRetVal = QComVariant{ true }.release();
667 } else {
668 *pRetVal = QComVariant{ accessible->state().focusable ? true : false }.release();
669 }
670 break;
671 case UIA_IsOffscreenPropertyId:
672 *pRetVal = QComVariant{ accessible->state().offscreen ? true : false }.release();
673 break;
674 case UIA_IsContentElementPropertyId:
675 *pRetVal = QComVariant{ true }.release();
676 break;
677 case UIA_IsControlElementPropertyId:
678 *pRetVal = QComVariant{ true }.release();
679 break;
680 case UIA_IsEnabledPropertyId:
681 *pRetVal = QComVariant{ !accessible->state().disabled }.release();
682 break;
683 case UIA_IsPasswordPropertyId:
684 *pRetVal = QComVariant{ accessible->role() == QAccessible::EditableText
685 && accessible->state().passwordEdit }
686 .release();
687 break;
688 case UIA_IsPeripheralPropertyId:
689 // True for peripheral UIs.
690 if (QWindow *window = windowForAccessible(accessible)) {
691 const Qt::WindowType wt = window->type();
692 *pRetVal = QComVariant{ wt == Qt::Popup || wt == Qt::ToolTip || wt == Qt::SplashScreen }
693 .release();
694 }
695 break;
696 case UIA_IsDialogPropertyId:
697 *pRetVal = QComVariant{ accessible->role() == QAccessible::Dialog
698 || accessible->role() == QAccessible::AlertMessage }
699 .release();
700 break;
701 case UIA_FullDescriptionPropertyId:
702 *pRetVal = QComVariant{ accessible->text(QAccessible::Description) }.release();
703 break;
704 case UIA_LocalizedControlTypePropertyId:
705 // see Core Accessibility API Mappings spec:
706 // https://www.w3.org/TR/core-aam-1.2/#role-map-blockquote
707 if (accessible->role() == QAccessible::BlockQuote)
708 *pRetVal = QComVariant{ tr("blockquote") }.release();
709 break;
710 case UIA_NamePropertyId: {
711 QString name = accessible->text(QAccessible::Name);
712 if (name.isEmpty() && topLevelWindow)
713 name = QCoreApplication::applicationName();
714 *pRetVal = QComVariant{ name }.release();
715 break;
716 }
717 case UIA_OrientationPropertyId: {
718 OrientationType orientationType = OrientationType_None;
719 if (QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface()) {
720 const QVariant orientationVariant =
721 attributesIface->attributeValue(QAccessible::Attribute::Orientation);
722 if (orientationVariant.isValid()) {
723 Q_ASSERT(orientationVariant.canConvert<Qt::Orientation>());
724 const Qt::Orientation orientation = orientationVariant.value<Qt::Orientation>();
725 orientationType = orientation == Qt::Horizontal ? OrientationType_Horizontal
726 : OrientationType_Vertical;
727 }
728 }
729 *pRetVal = QComVariant{ long(orientationType) }.release();
730 break;
731 }
732 case UIA_StyleIdAttributeId:
733 setStyle(accessible, pRetVal);
734 break;
735 default:
736 break;
737 }
738 return S_OK;
739}
740
741HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderSimple **pRetVal)
742{
743 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
744
745 if (!pRetVal)
746 return E_INVALIDARG;
747 *pRetVal = nullptr;
748
749 // Returns a host provider only for controls associated with a native window handle. Others should return NULL.
750 if (QAccessibleInterface *accessible = accessibleInterface()) {
751 if (HWND hwnd = hwndForAccessible(accessible)) {
752 return UiaHostProviderFromHwnd(hwnd, pRetVal);
753 }
754 }
755 return S_OK;
756}
757
758// Navigates within the tree of accessible controls.
759HRESULT QWindowsUiaMainProvider::Navigate(NavigateDirection direction, IRawElementProviderFragment **pRetVal)
760{
761 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << direction << " this: " << this;
762
763 if (!pRetVal)
764 return E_INVALIDARG;
765 *pRetVal = nullptr;
766
767 QAccessibleInterface *accessible = accessibleInterface();
768 if (!accessible)
769 return UIA_E_ELEMENTNOTAVAILABLE;
770
771 QAccessibleInterface *targetacc = nullptr;
772
773 if (direction == NavigateDirection_Parent) {
774 if (QAccessibleInterface *parent = accessible->parent()) {
775 // The Application's children are considered top level objects.
776 if (parent->isValid() && parent->role() != QAccessible::Application) {
777 targetacc = parent;
778 }
779 }
780 } else {
781 QAccessibleInterface *parent = nullptr;
782 int index = 0;
783 int incr = 1;
784 switch (direction) {
785 case NavigateDirection_FirstChild:
786 parent = accessible;
787 index = 0;
788 incr = 1;
789 break;
790 case NavigateDirection_LastChild:
791 parent = accessible;
792 index = accessible->childCount() - 1;
793 incr = -1;
794 break;
795 case NavigateDirection_NextSibling:
796 if ((parent = accessible->parent()))
797 index = parent->indexOfChild(accessible) + 1;
798 incr = 1;
799 break;
800 case NavigateDirection_PreviousSibling:
801 if ((parent = accessible->parent()))
802 index = parent->indexOfChild(accessible) - 1;
803 incr = -1;
804 break;
805 default:
806 Q_UNREACHABLE();
807 break;
808 }
809
810 if (parent && parent->isValid()) {
811 for (int count = parent->childCount(); index >= 0 && index < count; index += incr) {
812 if (QAccessibleInterface *child = parent->child(index)) {
813 if (child->isValid() && !child->state().invisible) {
814 targetacc = child;
815 break;
816 }
817 }
818 }
819 }
820 }
821
822 if (targetacc)
823 *pRetVal = providerForAccessible(targetacc).Detach();
824 return S_OK;
825}
826
827// Returns a unique id assigned to the UI element, used as key by the UI Automation framework.
828HRESULT QWindowsUiaMainProvider::GetRuntimeId(SAFEARRAY **pRetVal)
829{
830 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
831
832 if (!pRetVal)
833 return E_INVALIDARG;
834 *pRetVal = nullptr;
835
836 QAccessibleInterface *accessible = accessibleInterface();
837 if (!accessible)
838 return UIA_E_ELEMENTNOTAVAILABLE;
839
840 // The UiaAppendRuntimeId constant is used to make then ID unique
841 // among multiple instances running on the system.
842 int rtId[] = { UiaAppendRuntimeId, int(id()) };
843
844 if ((*pRetVal = SafeArrayCreateVector(VT_I4, 0, 2))) {
845 for (LONG i = 0; i < 2; ++i)
846 SafeArrayPutElement(*pRetVal, &i, &rtId[i]);
847 }
848 return S_OK;
849}
850
851// Returns the bounding rectangle for the accessible control.
852HRESULT QWindowsUiaMainProvider::get_BoundingRectangle(UiaRect *pRetVal)
853{
854 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
855
856 if (!pRetVal)
857 return E_INVALIDARG;
858
859 QAccessibleInterface *accessible = accessibleInterface();
860 if (!accessible)
861 return UIA_E_ELEMENTNOTAVAILABLE;
862
863 QWindow *window = windowForAccessible(accessible);
864 if (!window)
865 return UIA_E_ELEMENTNOTAVAILABLE;
866
867 rectToNativeUiaRect(accessible->rect(), window, pRetVal);
868 return S_OK;
869}
870
871HRESULT QWindowsUiaMainProvider::GetEmbeddedFragmentRoots(SAFEARRAY **pRetVal)
872{
873 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
874
875 if (!pRetVal)
876 return E_INVALIDARG;
877 *pRetVal = nullptr;
878 // No embedded roots.
879 return S_OK;
880}
881
882// Sets focus to the control.
883HRESULT QWindowsUiaMainProvider::SetFocus()
884{
885 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
886
887 QAccessibleInterface *accessible = accessibleInterface();
888 if (!accessible)
889 return UIA_E_ELEMENTNOTAVAILABLE;
890
891 QAccessibleActionInterface *actionInterface = accessible->actionInterface();
892 if (!actionInterface)
893 return UIA_E_ELEMENTNOTAVAILABLE;
894
895 actionInterface->doAction(QAccessibleActionInterface::setFocusAction());
896 return S_OK;
897}
898
899HRESULT QWindowsUiaMainProvider::get_FragmentRoot(IRawElementProviderFragmentRoot **pRetVal)
900{
901 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
902
903 if (!pRetVal)
904 return E_INVALIDARG;
905 *pRetVal = nullptr;
906
907 // Our UI Automation implementation considers the window as the root for
908 // non-native controls/fragments.
909 if (QAccessibleInterface *accessible = accessibleInterface()) {
910 if (QWindow *window = windowForAccessible(accessible)) {
911 if (QAccessibleInterface *rootacc = window->accessibleRoot())
912 *pRetVal = providerForAccessible(rootacc).Detach();
913 }
914 }
915 return S_OK;
916}
917
918// Returns a provider for the UI element present at the specified screen coordinates.
919HRESULT QWindowsUiaMainProvider::ElementProviderFromPoint(double x, double y, IRawElementProviderFragment **pRetVal)
920{
921 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << x << y;
922
923 if (!pRetVal) {
924 return E_INVALIDARG;
925 }
926 *pRetVal = nullptr;
927
928 QAccessibleInterface *accessible = accessibleInterface();
929 if (!accessible)
930 return UIA_E_ELEMENTNOTAVAILABLE;
931
932 QWindow *window = windowForAccessible(accessible);
933 if (!window)
934 return UIA_E_ELEMENTNOTAVAILABLE;
935
936 // Scales coordinates from High DPI screens.
937 UiaPoint uiaPoint = {x, y};
938 QPoint point;
939 nativeUiaPointToPoint(uiaPoint, window, &point);
940
941 QAccessibleInterface *targetacc = accessible->childAt(point.x(), point.y());
942
943 if (targetacc) {
944 QAccessibleInterface *acc = targetacc;
945 // Controls can be embedded within grouping elements. By default returns the innermost control.
946 while (acc) {
947 targetacc = acc;
948 // For accessibility tools it may be better to return the text element instead of its subcomponents.
949 if (targetacc->textInterface()) break;
950 acc = acc->childAt(point.x(), point.y());
951 }
952 *pRetVal = providerForAccessible(targetacc).Detach();
953 }
954 return S_OK;
955}
956
957// Returns the fragment with focus.
958HRESULT QWindowsUiaMainProvider::GetFocus(IRawElementProviderFragment **pRetVal)
959{
960 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
961
962 if (!pRetVal)
963 return E_INVALIDARG;
964 *pRetVal = nullptr;
965
966 if (QAccessibleInterface *accessible = accessibleInterface()) {
967 if (QAccessibleInterface *focusacc = accessible->focusChild()) {
968 *pRetVal = providerForAccessible(focusacc).Detach();
969 }
970 }
971 return S_OK;
972}
973
974QT_END_NAMESPACE
975
976#endif // QT_CONFIG(accessibility)