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