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 UiaRaiseNotificationEvent(provider.Get(), NotificationKind_Other, processing, message.bstr(),
244 activityId.bstr());
245 }
246 }
247}
248
249HRESULT STDMETHODCALLTYPE QWindowsUiaMainProvider::QueryInterface(REFIID iid, LPVOID *iface)
250{
251 HRESULT result = QComObject::QueryInterface(iid, iface);
252
253 if (SUCCEEDED(result) && iid == __uuidof(IRawElementProviderFragmentRoot)) {
254 QAccessibleInterface *accessible = accessibleInterface();
255 if (accessible && hwndForAccessible(accessible)) {
256 result = S_OK;
257 } else {
258 Release();
259 result = E_NOINTERFACE;
260 *iface = nullptr;
261 }
262 }
263
264 return result;
265}
266
267HRESULT QWindowsUiaMainProvider::get_ProviderOptions(ProviderOptions *pRetVal)
268{
269 if (!pRetVal)
270 return E_INVALIDARG;
271 // We are STA, (OleInitialize()).
272 *pRetVal = static_cast<ProviderOptions>(ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading);
273 return S_OK;
274}
275
276// Return providers for specific control patterns
277HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknown **pRetVal)
278{
279 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idPattern;
280
281 if (!pRetVal)
282 return E_INVALIDARG;
283 *pRetVal = nullptr;
284
285 QAccessibleInterface *accessible = accessibleInterface();
286 if (!accessible)
287 return UIA_E_ELEMENTNOTAVAILABLE;
288
289 switch (idPattern) {
290 case UIA_WindowPatternId:
291 if (accessible->parent() && (accessible->parent()->role() == QAccessible::Application)) {
292 *pRetVal = makeComObject<QWindowsUiaWindowProvider>(id()).Detach();
293 }
294 break;
295 case UIA_TextPatternId:
296 case UIA_TextPattern2Id:
297 // All text controls.
298 if (accessible->textInterface()) {
299 *pRetVal = makeComObject<QWindowsUiaTextProvider>(id()).Detach();
300 }
301 break;
302 case UIA_ValuePatternId:
303 // All non-static controls support the Value pattern.
304 if (accessible->role() != QAccessible::StaticText)
305 *pRetVal = makeComObject<QWindowsUiaValueProvider>(id()).Detach();
306 break;
307 case UIA_RangeValuePatternId:
308 // Controls providing a numeric value within a range (e.g., sliders, scroll bars, dials).
309 if (accessible->valueInterface()) {
310 *pRetVal = makeComObject<QWindowsUiaRangeValueProvider>(id()).Detach();
311 }
312 break;
313 case UIA_TogglePatternId:
314 // Checkboxes and other checkable controls.
315 if (accessible->state().checkable)
316 *pRetVal = makeComObject<QWindowsUiaToggleProvider>(id()).Detach();
317 break;
318 case UIA_SelectionPatternId:
319 case UIA_SelectionPattern2Id:
320 // Selections via QAccessibleSelectionInterface or lists of items.
321 if (accessible->selectionInterface()
322 || accessible->role() == QAccessible::List
323 || accessible->role() == QAccessible::PageTabList) {
324 *pRetVal = makeComObject<QWindowsUiaSelectionProvider>(id()).Detach();
325 }
326 break;
327 case UIA_SelectionItemPatternId:
328 // Parent supports selection interface or items within a list and radio buttons.
329 if ((accessible->parent() && accessible->parent()->selectionInterface())
330 || (accessible->role() == QAccessible::RadioButton)
331 || (accessible->role() == QAccessible::ListItem)
332 || (accessible->role() == QAccessible::PageTab)) {
333 *pRetVal = makeComObject<QWindowsUiaSelectionItemProvider>(id()).Detach();
334 }
335 break;
336 case UIA_TablePatternId:
337 // Table/tree.
338 if (accessible->tableInterface()
339 && ((accessible->role() == QAccessible::Table) || (accessible->role() == QAccessible::Tree))) {
340 *pRetVal = makeComObject<QWindowsUiaTableProvider>(id()).Detach();
341 }
342 break;
343 case UIA_TableItemPatternId:
344 // Item within a table/tree.
345 if (accessible->tableCellInterface()
346 && ((accessible->role() == QAccessible::Cell) || (accessible->role() == QAccessible::TreeItem))) {
347 *pRetVal = makeComObject<QWindowsUiaTableItemProvider>(id()).Detach();
348 }
349 break;
350 case UIA_GridPatternId:
351 // Table/tree.
352 if (accessible->tableInterface()
353 && ((accessible->role() == QAccessible::Table) || (accessible->role() == QAccessible::Tree))) {
354 *pRetVal = makeComObject<QWindowsUiaGridProvider>(id()).Detach();
355 }
356 break;
357 case UIA_GridItemPatternId:
358 // Item within a table/tree.
359 if (accessible->tableCellInterface()
360 && ((accessible->role() == QAccessible::Cell) || (accessible->role() == QAccessible::TreeItem))) {
361 *pRetVal = makeComObject<QWindowsUiaGridItemProvider>(id()).Detach();
362 }
363 break;
364 case UIA_InvokePatternId:
365 // Things that have an invokable action (e.g., simple buttons).
366 if (accessible->actionInterface()) {
367 *pRetVal = makeComObject<QWindowsUiaInvokeProvider>(id()).Detach();
368 }
369 break;
370 case UIA_ExpandCollapsePatternId:
371 // Menu items with submenus.
372 if ((accessible->role() == QAccessible::MenuItem
373 && accessible->childCount() > 0
374 && accessible->child(0)->role() == QAccessible::PopupMenu)
375 || accessible->role() == QAccessible::ComboBox
376 || accessible->state().expandable) {
377 *pRetVal = makeComObject<QWindowsUiaExpandCollapseProvider>(id()).Detach();
378 }
379 break;
380 default:
381 break;
382 }
383
384 return S_OK;
385}
386
387void QWindowsUiaMainProvider::setLabelledBy(QAccessibleInterface *accessible, VARIANT *pRetVal)
388{
389 Q_ASSERT(accessible);
390
391 typedef std::pair<QAccessibleInterface*, QAccessible::Relation> RelationPair;
392 const QList<RelationPair> relationInterfaces = accessible->relations(QAccessible::Label);
393 if (relationInterfaces.empty())
394 return;
395
396 // UIA_LabeledByPropertyId only supports one relation
397 ComPtr<IRawElementProviderSimple> provider = providerForAccessible(relationInterfaces.first().first);
398 if (!provider)
399 return;
400
401 pRetVal->vt = VT_UNKNOWN;
402 pRetVal->punkVal = provider.Detach();
403}
404
405void QWindowsUiaMainProvider::fillVariantArrayForRelation(QAccessibleInterface* accessible,
406 QAccessible::Relation relation, VARIANT *pRetVal)
407{
408 Q_ASSERT(accessible);
409
410 typedef std::pair<QAccessibleInterface*, QAccessible::Relation> RelationPair;
411 const QList<RelationPair> relationInterfaces = accessible->relations(relation);
412 if (relationInterfaces.empty())
413 return;
414
415 SAFEARRAY *elements = SafeArrayCreateVector(VT_UNKNOWN, 0, relationInterfaces.size());
416 for (LONG i = 0; i < relationInterfaces.size(); ++i) {
417 if (ComPtr<IRawElementProviderSimple> provider =
418 providerForAccessible(relationInterfaces.at(i).first)) {
419 SafeArrayPutElement(elements, &i, provider.Get());
420 }
421 }
422
423 pRetVal->vt = VT_UNKNOWN | VT_ARRAY;
424 pRetVal->parray = elements;
425}
426
427void QWindowsUiaMainProvider::setAriaProperties(QAccessibleInterface *accessible, VARIANT *pRetVal)
428{
429 Q_ASSERT(accessible);
430
431 QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface();
432 if (!attributesIface)
433 return;
434
435 QString ariaString;
436 const QList<QAccessible::Attribute> attrKeys = attributesIface->attributeKeys();
437 for (qsizetype i = 0; i < attrKeys.size(); ++i) {
438 if (i != 0)
439 ariaString += QStringLiteral(";");
440 const QAccessible::Attribute key = attrKeys.at(i);
441 const QVariant value = attributesIface->attributeValue(key);
442 // see "Core Accessibility API Mappings" spec: https://www.w3.org/TR/core-aam-1.2/
443 switch (key) {
444 case QAccessible::Attribute::Custom:
445 {
446 // forward custom attributes as-is
447 Q_ASSERT((value.canConvert<QHash<QString, QString>>()));
448 const QHash<QString, QString> attrMap = value.value<QHash<QString, QString>>();
449 for (auto [name, val] : attrMap.asKeyValueRange()) {
450 if (name != *attrMap.keyBegin())
451 ariaString += QStringLiteral(";");
452 ariaString += name + QStringLiteral("=") + val;
453 }
454 break;
455 }
456 case QAccessible::Attribute::Level:
457 Q_ASSERT(value.canConvert<int>());
458 ariaString += QStringLiteral("level=") + QString::number(value.toInt());
459 break;
460 case QAccessible::Attribute::PositionInSet:
461 Q_ASSERT(value.canConvert<int>());
462 ariaString += QStringLiteral("posinset=") + QString::number(value.toInt());
463 break;
464 case QAccessible::Attribute::SizeOfSet:
465 Q_ASSERT(value.canConvert<int>());
466 ariaString += QStringLiteral("setsize=") + QString::number(value.toInt());
467 break;
468 default:
469 break;
470 }
471 }
472
473 *pRetVal = QComVariant{ ariaString }.release();
474}
475
476void QWindowsUiaMainProvider::setIntAttribute(QAccessibleInterface *accessible,
477 QAccessible::Attribute attribute,
478 VARIANT *pRetVal)
479{
480 Q_ASSERT(accessible);
481
482 QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface();
483 if (!attributesIface)
484 return;
485
486 const QVariant valueVariant = attributesIface->attributeValue(attribute);
487 if (!valueVariant.isValid())
488 return;
489
490 Q_ASSERT(valueVariant.canConvert<int>());
491 *pRetVal = QComVariant{ long(valueVariant.toInt()) }.release();
492}
493
494void QWindowsUiaMainProvider::setHeadingLevel(QAccessibleInterface *accessible, VARIANT *pRetVal)
495{
496 Q_ASSERT(accessible);
497
498 if (accessible->role() != QAccessible::Heading)
499 return;
500
501 static constexpr long levels[] = { HeadingLevel1, HeadingLevel2, HeadingLevel3,
502 HeadingLevel4, HeadingLevel5, HeadingLevel6,
503 HeadingLevel7, HeadingLevel8, HeadingLevel9 };
504
505 long headingLevel = HeadingLevel_None;
506 if (QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface()) {
507 const QVariant levelVariant =
508 attributesIface->attributeValue(QAccessible::Attribute::Level);
509 if (levelVariant.isValid()) {
510 Q_ASSERT(levelVariant.canConvert<int>());
511 const int level = levelVariant.toInt();
512 if (level <= 9 && level > 0) {
513 headingLevel = levels[level - 1];
514 }
515 }
516 }
517 *pRetVal = QComVariant{ headingLevel }.release();
518}
519
520void QWindowsUiaMainProvider::setStyle(QAccessibleInterface *accessible, VARIANT *pRetVal)
521{
522 Q_ASSERT(accessible);
523
524 QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface();
525 if (!attributesIface)
526 return;
527
528 // currently, only heading styles are implemented here
529 if (accessible->role() != QAccessible::Role::Heading)
530 return;
531
532 const QVariant levelVariant = attributesIface->attributeValue(QAccessible::Attribute::Level);
533 if (!levelVariant.isValid())
534 return;
535
536 Q_ASSERT(levelVariant.canConvert<int>());
537 // UIA only has styles for heading levels 1-9
538 const int level = levelVariant.toInt();
539 if (level < 1 || level > 9)
540 return;
541
542 const long styleId = styleIdForHeadingLevel(level);
543 *pRetVal = QComVariant{ styleId }.release();
544}
545
546int QWindowsUiaMainProvider::styleIdForHeadingLevel(int headingLevel)
547{
548 // only heading levels 1-9 have a corresponding UIA style ID
549 Q_ASSERT(headingLevel > 0 && headingLevel <= 9);
550
551 static constexpr int styles[] = {
552 StyleId_Heading1,
553 StyleId_Heading2,
554 StyleId_Heading3,
555 StyleId_Heading4,
556 StyleId_Heading5,
557 StyleId_Heading6,
558 StyleId_Heading7,
559 StyleId_Heading8,
560 StyleId_Heading9,
561 };
562
563 return styles[headingLevel - 1];
564}
565
566HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pRetVal)
567{
568 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idProp;
569
570 if (!pRetVal)
571 return E_INVALIDARG;
572 clearVariant(pRetVal);
573
574 QAccessibleInterface *accessible = accessibleInterface();
575 if (!accessible)
576 return UIA_E_ELEMENTNOTAVAILABLE;
577
578 bool topLevelWindow = accessible->parent() && (accessible->parent()->role() == QAccessible::Application);
579
580 switch (idProp) {
581 case UIA_ProcessIdPropertyId:
582 // PID
583 *pRetVal = QComVariant{ static_cast<long>(GetCurrentProcessId()) }.release();
584 break;
585 case UIA_AccessKeyPropertyId:
586 // Accelerator key.
587 *pRetVal = QComVariant{ accessible->text(QAccessible::Accelerator) }.release();
588 break;
589 case UIA_AriaRolePropertyId:
590 if (accessible->role() == QAccessible::Heading)
591 *pRetVal = QComVariant{ QStringLiteral("heading") }.release();
592 break;
593 case UIA_AriaPropertiesPropertyId:
594 setAriaProperties(accessible, pRetVal);
595 break;
596 case UIA_AutomationIdPropertyId:
597 // Automation ID, which can be used by tools to select a specific control in the UI.
598 *pRetVal = QComVariant{ QAccessibleBridgeUtils::accessibleId(accessible) }.release();
599 break;
600 case UIA_ClassNamePropertyId:
601 // Class name.
602 if (QObject *o = accessible->object()) {
603 QString className = QLatin1StringView(o->metaObject()->className());
604 *pRetVal = QComVariant{ className }.release();
605 }
606 break;
607 case UIA_CulturePropertyId:
608 {
609 QLocale locale;
610 if (QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface()) {
611 const QVariant localeVariant = attributesIface->attributeValue(QAccessible::Attribute::Locale);
612 if (localeVariant.isValid()) {
613 Q_ASSERT(localeVariant.canConvert<QLocale>());
614 locale = localeVariant.toLocale();
615 }
616 }
617 LCID lcid = LocaleNameToLCID(qUtf16Printable(locale.bcp47Name()), 0);
618 *pRetVal = QComVariant{ long(lcid) }.release();
619 break;
620 }
621 case UIA_LevelPropertyId:
622 // Structural depth, e.g. of an item in a tree. Headings convey
623 // their level through the heading text style and the "level"
624 // ARIA property instead.
625 if (accessible->role() != QAccessible::Role::Heading)
626 setIntAttribute(accessible, QAccessible::Attribute::Level, pRetVal);
627 break;
628 case UIA_PositionInSetPropertyId:
629 setIntAttribute(accessible, QAccessible::Attribute::PositionInSet, pRetVal);
630 break;
631 case UIA_SizeOfSetPropertyId:
632 setIntAttribute(accessible, QAccessible::Attribute::SizeOfSet, pRetVal);
633 break;
634 case UIA_DescribedByPropertyId:
635 fillVariantArrayForRelation(accessible, QAccessible::DescriptionFor, pRetVal);
636 break;
637 case UIA_FlowsFromPropertyId:
638 fillVariantArrayForRelation(accessible, QAccessible::FlowsTo, pRetVal);
639 break;
640 case UIA_FlowsToPropertyId:
641 fillVariantArrayForRelation(accessible, QAccessible::FlowsFrom, pRetVal);
642 break;
643 case UIA_LabeledByPropertyId:
644 setLabelledBy(accessible, pRetVal);
645 break;
646 case UIA_FrameworkIdPropertyId:
647 *pRetVal = QComVariant{ QStringLiteral("Qt") }.release();
648 break;
649 case UIA_ControlTypePropertyId:
650 if (topLevelWindow) {
651 // Reports a top-level widget as a window, instead of "custom".
652 *pRetVal = QComVariant{ UIA_WindowControlTypeId }.release();
653 } else {
654 // Control type converted from role.
655 *pRetVal = QComVariant{ roleToControlTypeId(accessible->role()) }.release();
656 }
657 break;
658 case UIA_HelpTextPropertyId:
659 *pRetVal = QComVariant{ accessible->text(QAccessible::Help) }.release();
660 break;
661 case UIA_HasKeyboardFocusPropertyId:
662 // If the top-level window has no focused child, report the top-level
663 // widget (window). If it already has a focused widget, it will be
664 // reported automatically.
665 if (topLevelWindow) {
666 QAccessibleInterface *focusacc = accessible->focusChild();
667 if (!focusacc) {
668 *pRetVal = QComVariant{ accessible->state().active ? true : false }.release();
669 break;
670 }
671 }
672 *pRetVal = QComVariant{ accessible->state().focused ? true : false }.release();
673 break;
674 case UIA_IsKeyboardFocusablePropertyId:
675 if (topLevelWindow) {
676 // Windows should always be focusable
677 *pRetVal = QComVariant{ true }.release();
678 } else {
679 *pRetVal = QComVariant{ accessible->state().focusable ? true : false }.release();
680 }
681 break;
682 case UIA_IsOffscreenPropertyId:
683 *pRetVal = QComVariant{ accessible->state().offscreen ? true : false }.release();
684 break;
685 case UIA_IsContentElementPropertyId:
686 *pRetVal = QComVariant{ true }.release();
687 break;
688 case UIA_IsControlElementPropertyId:
689 *pRetVal = QComVariant{ true }.release();
690 break;
691 case UIA_IsEnabledPropertyId:
692 *pRetVal = QComVariant{ !accessible->state().disabled }.release();
693 break;
694 case UIA_IsPasswordPropertyId:
695 *pRetVal = QComVariant{ accessible->role() == QAccessible::EditableText
696 && accessible->state().passwordEdit }
697 .release();
698 break;
699 case UIA_IsPeripheralPropertyId:
700 // True for peripheral UIs.
701 if (QWindow *window = windowForAccessible(accessible)) {
702 const Qt::WindowType wt = window->type();
703 *pRetVal = QComVariant{ wt == Qt::Popup || wt == Qt::ToolTip || wt == Qt::SplashScreen }
704 .release();
705 }
706 break;
707 case UIA_HeadingLevelPropertyId:
708 setHeadingLevel(accessible, pRetVal);
709 break;
710 case UIA_IsDialogPropertyId:
711 *pRetVal = QComVariant{ accessible->role() == QAccessible::Dialog
712 || accessible->role() == QAccessible::AlertMessage }
713 .release();
714 break;
715 case UIA_FullDescriptionPropertyId:
716 *pRetVal = QComVariant{ accessible->text(QAccessible::Description) }.release();
717 break;
718 case UIA_LocalizedControlTypePropertyId:
719 // see Core Accessibility API Mappings spec:
720 // https://www.w3.org/TR/core-aam-1.2/#role-map-blockquote
721 if (accessible->role() == QAccessible::BlockQuote)
722 *pRetVal = QComVariant{ tr("blockquote") }.release();
723 break;
724 case UIA_NamePropertyId: {
725 QString name = accessible->text(QAccessible::Name);
726 if (name.isEmpty() && topLevelWindow)
727 name = QCoreApplication::applicationName();
728 *pRetVal = QComVariant{ name }.release();
729 break;
730 }
731 case UIA_OrientationPropertyId: {
732 OrientationType orientationType = OrientationType_None;
733 if (QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface()) {
734 const QVariant orientationVariant =
735 attributesIface->attributeValue(QAccessible::Attribute::Orientation);
736 if (orientationVariant.isValid()) {
737 Q_ASSERT(orientationVariant.canConvert<Qt::Orientation>());
738 const Qt::Orientation orientation = orientationVariant.value<Qt::Orientation>();
739 orientationType = orientation == Qt::Horizontal ? OrientationType_Horizontal
740 : OrientationType_Vertical;
741 }
742 }
743 *pRetVal = QComVariant{ long(orientationType) }.release();
744 break;
745 }
746 case UIA_StyleIdAttributeId:
747 setStyle(accessible, pRetVal);
748 break;
749 default:
750 break;
751 }
752 return S_OK;
753}
754
755HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderSimple **pRetVal)
756{
757 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
758
759 if (!pRetVal)
760 return E_INVALIDARG;
761 *pRetVal = nullptr;
762
763 // Returns a host provider only for controls associated with a native window handle. Others should return NULL.
764 if (QAccessibleInterface *accessible = accessibleInterface()) {
765 if (HWND hwnd = hwndForAccessible(accessible)) {
766 return UiaHostProviderFromHwnd(hwnd, pRetVal);
767 }
768 }
769 return S_OK;
770}
771
772// Navigates within the tree of accessible controls.
773HRESULT QWindowsUiaMainProvider::Navigate(NavigateDirection direction, IRawElementProviderFragment **pRetVal)
774{
775 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << direction << " this: " << this;
776
777 if (!pRetVal)
778 return E_INVALIDARG;
779 *pRetVal = nullptr;
780
781 QAccessibleInterface *accessible = accessibleInterface();
782 if (!accessible)
783 return UIA_E_ELEMENTNOTAVAILABLE;
784
785 QAccessibleInterface *targetacc = nullptr;
786
787 if (direction == NavigateDirection_Parent) {
788 if (QAccessibleInterface *parent = accessible->parent()) {
789 // The Application's children are considered top level objects.
790 if (parent->isValid() && parent->role() != QAccessible::Application) {
791 targetacc = parent;
792 }
793 }
794 } else {
795 QAccessibleInterface *parent = nullptr;
796 int index = 0;
797 int incr = 1;
798 switch (direction) {
799 case NavigateDirection_FirstChild:
800 parent = accessible;
801 index = 0;
802 incr = 1;
803 break;
804 case NavigateDirection_LastChild:
805 parent = accessible;
806 index = accessible->childCount() - 1;
807 incr = -1;
808 break;
809 case NavigateDirection_NextSibling:
810 if ((parent = accessible->parent()))
811 index = parent->indexOfChild(accessible) + 1;
812 incr = 1;
813 break;
814 case NavigateDirection_PreviousSibling:
815 if ((parent = accessible->parent()))
816 index = parent->indexOfChild(accessible) - 1;
817 incr = -1;
818 break;
819 default:
820 Q_UNREACHABLE();
821 break;
822 }
823
824 if (parent && parent->isValid()) {
825 for (int count = parent->childCount(); index >= 0 && index < count; index += incr) {
826 if (QAccessibleInterface *child = parent->child(index)) {
827 if (child->isValid() && !child->state().invisible) {
828 targetacc = child;
829 break;
830 }
831 }
832 }
833 }
834 }
835
836 if (targetacc)
837 *pRetVal = providerForAccessible(targetacc).Detach();
838 return S_OK;
839}
840
841// Returns a unique id assigned to the UI element, used as key by the UI Automation framework.
842HRESULT QWindowsUiaMainProvider::GetRuntimeId(SAFEARRAY **pRetVal)
843{
844 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
845
846 if (!pRetVal)
847 return E_INVALIDARG;
848 *pRetVal = nullptr;
849
850 QAccessibleInterface *accessible = accessibleInterface();
851 if (!accessible)
852 return UIA_E_ELEMENTNOTAVAILABLE;
853
854 // The UiaAppendRuntimeId constant is used to make then ID unique
855 // among multiple instances running on the system.
856 int rtId[] = { UiaAppendRuntimeId, int(id()) };
857
858 if ((*pRetVal = SafeArrayCreateVector(VT_I4, 0, 2))) {
859 for (LONG i = 0; i < 2; ++i)
860 SafeArrayPutElement(*pRetVal, &i, &rtId[i]);
861 }
862 return S_OK;
863}
864
865// Returns the bounding rectangle for the accessible control.
866HRESULT QWindowsUiaMainProvider::get_BoundingRectangle(UiaRect *pRetVal)
867{
868 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
869
870 if (!pRetVal)
871 return E_INVALIDARG;
872
873 QAccessibleInterface *accessible = accessibleInterface();
874 if (!accessible)
875 return UIA_E_ELEMENTNOTAVAILABLE;
876
877 QWindow *window = windowForAccessible(accessible);
878 if (!window)
879 return UIA_E_ELEMENTNOTAVAILABLE;
880
881 rectToNativeUiaRect(accessible->rect(), window, pRetVal);
882 return S_OK;
883}
884
885HRESULT QWindowsUiaMainProvider::GetEmbeddedFragmentRoots(SAFEARRAY **pRetVal)
886{
887 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
888
889 if (!pRetVal)
890 return E_INVALIDARG;
891 *pRetVal = nullptr;
892 // No embedded roots.
893 return S_OK;
894}
895
896// Sets focus to the control.
897HRESULT QWindowsUiaMainProvider::SetFocus()
898{
899 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
900
901 QAccessibleInterface *accessible = accessibleInterface();
902 if (!accessible)
903 return UIA_E_ELEMENTNOTAVAILABLE;
904
905 QAccessibleActionInterface *actionInterface = accessible->actionInterface();
906 if (!actionInterface)
907 return UIA_E_ELEMENTNOTAVAILABLE;
908
909 actionInterface->doAction(QAccessibleActionInterface::setFocusAction());
910 return S_OK;
911}
912
913HRESULT QWindowsUiaMainProvider::get_FragmentRoot(IRawElementProviderFragmentRoot **pRetVal)
914{
915 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
916
917 if (!pRetVal)
918 return E_INVALIDARG;
919 *pRetVal = nullptr;
920
921 // Our UI Automation implementation considers the window as the root for
922 // non-native controls/fragments.
923 if (QAccessibleInterface *accessible = accessibleInterface()) {
924 if (QWindow *window = windowForAccessible(accessible)) {
925 if (QAccessibleInterface *rootacc = window->accessibleRoot())
926 *pRetVal = providerForAccessible(rootacc).Detach();
927 }
928 }
929 return S_OK;
930}
931
932// Returns a provider for the UI element present at the specified screen coordinates.
933HRESULT QWindowsUiaMainProvider::ElementProviderFromPoint(double x, double y, IRawElementProviderFragment **pRetVal)
934{
935 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << x << y;
936
937 if (!pRetVal) {
938 return E_INVALIDARG;
939 }
940 *pRetVal = nullptr;
941
942 QAccessibleInterface *accessible = accessibleInterface();
943 if (!accessible)
944 return UIA_E_ELEMENTNOTAVAILABLE;
945
946 QWindow *window = windowForAccessible(accessible);
947 if (!window)
948 return UIA_E_ELEMENTNOTAVAILABLE;
949
950 // Scales coordinates from High DPI screens.
951 UiaPoint uiaPoint = {x, y};
952 QPoint point;
953 nativeUiaPointToPoint(uiaPoint, window, &point);
954
955 QAccessibleInterface *targetacc = accessible->childAt(point.x(), point.y());
956
957 if (targetacc) {
958 QAccessibleInterface *acc = targetacc;
959 // Controls can be embedded within grouping elements. By default returns the innermost control.
960 while (acc) {
961 targetacc = acc;
962 // For accessibility tools it may be better to return the text element instead of its subcomponents.
963 if (targetacc->textInterface()) break;
964 acc = acc->childAt(point.x(), point.y());
965 }
966 *pRetVal = providerForAccessible(targetacc).Detach();
967 }
968 return S_OK;
969}
970
971// Returns the fragment with focus.
972HRESULT QWindowsUiaMainProvider::GetFocus(IRawElementProviderFragment **pRetVal)
973{
974 qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
975
976 if (!pRetVal)
977 return E_INVALIDARG;
978 *pRetVal = nullptr;
979
980 if (QAccessibleInterface *accessible = accessibleInterface()) {
981 if (QAccessibleInterface *focusacc = accessible->focusChild()) {
982 *pRetVal = providerForAccessible(focusacc).Detach();
983 }
984 }
985 return S_OK;
986}
987
988QT_END_NAMESPACE
989
990#endif // QT_CONFIG(accessibility)