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