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