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