2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
23 ==============================================================================
29 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
31 int AccessibilityNativeHandle::idCounter
= 0;
33 //==============================================================================
34 static String
getAutomationId (const AccessibilityHandler
& handler
)
36 auto result
= handler
.getTitle();
37 auto* parentComponent
= handler
.getComponent().getParentComponent();
39 while (parentComponent
!= nullptr)
41 if (auto* parentHandler
= parentComponent
->getAccessibilityHandler())
43 auto parentTitle
= parentHandler
->getTitle();
44 result
<< "." << (parentTitle
.isNotEmpty() ? parentTitle
: "<empty>");
47 parentComponent
= parentComponent
->getParentComponent();
53 static auto roleToControlTypeId (AccessibilityRole roleType
)
57 case AccessibilityRole::popupMenu
:
58 case AccessibilityRole::dialogWindow
:
59 case AccessibilityRole::splashScreen
:
60 case AccessibilityRole::window
: return ComTypes::UIA_WindowControlTypeId
;
62 case AccessibilityRole::label
:
63 case AccessibilityRole::staticText
: return ComTypes::UIA_TextControlTypeId
;
65 case AccessibilityRole::column
:
66 case AccessibilityRole::row
: return ComTypes::UIA_HeaderItemControlTypeId
;
68 case AccessibilityRole::button
: return ComTypes::UIA_ButtonControlTypeId
;
69 case AccessibilityRole::toggleButton
: return ComTypes::UIA_CheckBoxControlTypeId
;
70 case AccessibilityRole::radioButton
: return ComTypes::UIA_RadioButtonControlTypeId
;
71 case AccessibilityRole::comboBox
: return ComTypes::UIA_ComboBoxControlTypeId
;
72 case AccessibilityRole::image
: return ComTypes::UIA_ImageControlTypeId
;
73 case AccessibilityRole::slider
: return ComTypes::UIA_SliderControlTypeId
;
74 case AccessibilityRole::editableText
: return ComTypes::UIA_EditControlTypeId
;
75 case AccessibilityRole::menuItem
: return ComTypes::UIA_MenuItemControlTypeId
;
76 case AccessibilityRole::menuBar
: return ComTypes::UIA_MenuBarControlTypeId
;
77 case AccessibilityRole::table
: return ComTypes::UIA_TableControlTypeId
;
78 case AccessibilityRole::tableHeader
: return ComTypes::UIA_HeaderControlTypeId
;
79 case AccessibilityRole::cell
: return ComTypes::UIA_DataItemControlTypeId
;
80 case AccessibilityRole::hyperlink
: return ComTypes::UIA_HyperlinkControlTypeId
;
81 case AccessibilityRole::list
: return ComTypes::UIA_ListControlTypeId
;
82 case AccessibilityRole::listItem
: return ComTypes::UIA_ListItemControlTypeId
;
83 case AccessibilityRole::tree
: return ComTypes::UIA_TreeControlTypeId
;
84 case AccessibilityRole::treeItem
: return ComTypes::UIA_TreeItemControlTypeId
;
85 case AccessibilityRole::progressBar
: return ComTypes::UIA_ProgressBarControlTypeId
;
86 case AccessibilityRole::group
: return ComTypes::UIA_GroupControlTypeId
;
87 case AccessibilityRole::scrollBar
: return ComTypes::UIA_ScrollBarControlTypeId
;
88 case AccessibilityRole::tooltip
: return ComTypes::UIA_ToolTipControlTypeId
;
90 case AccessibilityRole::ignored
:
91 case AccessibilityRole::unspecified
: break;
94 return ComTypes::UIA_CustomControlTypeId
;
97 //==============================================================================
98 AccessibilityNativeHandle::AccessibilityNativeHandle (AccessibilityHandler
& handler
)
99 : ComBaseClassHelper (0),
100 accessibilityHandler (handler
)
104 //==============================================================================
105 JUCE_COMRESULT
AccessibilityNativeHandle::QueryInterface (REFIID refId
, void** result
)
109 if (! isElementValid())
110 return (HRESULT
) UIA_E_ELEMENTNOTAVAILABLE
;
112 if ((refId
== __uuidof (ComTypes::IRawElementProviderFragmentRoot
) && ! isFragmentRoot()))
113 return E_NOINTERFACE
;
115 return ComBaseClassHelper::QueryInterface (refId
, result
);
118 //==============================================================================
119 JUCE_COMRESULT
AccessibilityNativeHandle::get_HostRawElementProvider (IRawElementProviderSimple
** pRetVal
)
121 return withCheckedComArgs (pRetVal
, *this, [&]
123 if (isFragmentRoot())
124 if (auto* wrapper
= WindowsUIAWrapper::getInstanceWithoutCreating())
125 return wrapper
->hostProviderFromHwnd ((HWND
) accessibilityHandler
.getComponent().getWindowHandle(), pRetVal
);
131 JUCE_COMRESULT
AccessibilityNativeHandle::get_ProviderOptions (ProviderOptions
* options
)
133 if (options
== nullptr)
136 *options
= (ProviderOptions
) (ProviderOptions_ServerSideProvider
| ProviderOptions_UseComThreading
);
140 JUCE_COMRESULT
AccessibilityNativeHandle::GetPatternProvider (PATTERNID pId
, IUnknown
** pRetVal
)
142 return withCheckedComArgs (pRetVal
, *this, [&]
144 *pRetVal
= [&]() -> IUnknown
*
146 const auto role
= accessibilityHandler
.getRole();
147 const auto fragmentRoot
= isFragmentRoot();
151 case ComTypes::UIA_WindowPatternId
:
154 return new UIAWindowProvider (this);
158 case ComTypes::UIA_TransformPatternId
:
161 return new UIATransformProvider (this);
165 case ComTypes::UIA_TextPatternId
:
166 case ComTypes::UIA_TextPattern2Id
:
168 if (accessibilityHandler
.getTextInterface() != nullptr)
169 return new UIATextProvider (this);
173 case ComTypes::UIA_ValuePatternId
:
175 if (accessibilityHandler
.getValueInterface() != nullptr)
176 return new UIAValueProvider (this);
180 case ComTypes::UIA_RangeValuePatternId
:
182 if (accessibilityHandler
.getValueInterface() != nullptr
183 && accessibilityHandler
.getValueInterface()->getRange().isValid())
185 return new UIARangeValueProvider (this);
190 case ComTypes::UIA_TogglePatternId
:
192 if (accessibilityHandler
.getCurrentState().isCheckable()
193 && (accessibilityHandler
.getActions().contains (AccessibilityActionType::toggle
)
194 || accessibilityHandler
.getActions().contains (AccessibilityActionType::press
)))
196 return new UIAToggleProvider (this);
201 case ComTypes::UIA_SelectionPatternId
:
203 if (role
== AccessibilityRole::list
204 || role
== AccessibilityRole::popupMenu
205 || role
== AccessibilityRole::tree
)
207 return new UIASelectionProvider (this);
212 case ComTypes::UIA_SelectionItemPatternId
:
214 auto state
= accessibilityHandler
.getCurrentState();
216 if (state
.isSelectable() || state
.isMultiSelectable()
217 || role
== AccessibilityRole::radioButton
)
219 return new UIASelectionItemProvider (this);
224 case ComTypes::UIA_GridPatternId
:
226 if (accessibilityHandler
.getTableInterface() != nullptr)
227 return new UIAGridProvider (this);
231 case ComTypes::UIA_GridItemPatternId
:
233 if (accessibilityHandler
.getCellInterface() != nullptr)
234 return new UIAGridItemProvider (this);
238 case ComTypes::UIA_InvokePatternId
:
240 if (accessibilityHandler
.getActions().contains (AccessibilityActionType::press
))
241 return new UIAInvokeProvider (this);
245 case ComTypes::UIA_ExpandCollapsePatternId
:
247 if (accessibilityHandler
.getActions().contains (AccessibilityActionType::showMenu
)
248 && accessibilityHandler
.getCurrentState().isExpandable())
249 return new UIAExpandCollapseProvider (this);
262 JUCE_COMRESULT
AccessibilityNativeHandle::GetPropertyValue (PROPERTYID propertyId
, VARIANT
* pRetVal
)
264 return withCheckedComArgs (pRetVal
, *this, [&]
266 VariantHelpers::clear (pRetVal
);
268 const auto role
= accessibilityHandler
.getRole();
269 const auto state
= accessibilityHandler
.getCurrentState();
270 const auto ignored
= accessibilityHandler
.isIgnored();
274 case UIA_AutomationIdPropertyId
:
275 VariantHelpers::setString (getAutomationId (accessibilityHandler
), pRetVal
);
277 case UIA_ControlTypePropertyId
:
278 VariantHelpers::setInt (roleToControlTypeId (role
), pRetVal
);
280 case UIA_FrameworkIdPropertyId
:
281 VariantHelpers::setString ("JUCE", pRetVal
);
283 case UIA_FullDescriptionPropertyId
:
284 VariantHelpers::setString (accessibilityHandler
.getDescription(), pRetVal
);
286 case UIA_HelpTextPropertyId
:
287 VariantHelpers::setString (accessibilityHandler
.getHelp(), pRetVal
);
289 case UIA_IsContentElementPropertyId
:
290 VariantHelpers::setBool (! ignored
&& accessibilityHandler
.isVisibleWithinParent(),
293 case UIA_IsControlElementPropertyId
:
294 VariantHelpers::setBool (true, pRetVal
);
296 case UIA_IsDialogPropertyId
:
297 VariantHelpers::setBool (role
== AccessibilityRole::dialogWindow
, pRetVal
);
299 case UIA_IsEnabledPropertyId
:
300 VariantHelpers::setBool (accessibilityHandler
.getComponent().isEnabled(), pRetVal
);
302 case UIA_IsKeyboardFocusablePropertyId
:
303 VariantHelpers::setBool (state
.isFocusable(), pRetVal
);
305 case UIA_HasKeyboardFocusPropertyId
:
306 VariantHelpers::setBool (accessibilityHandler
.hasFocus (true), pRetVal
);
308 case UIA_IsOffscreenPropertyId
:
309 VariantHelpers::setBool (! accessibilityHandler
.isVisibleWithinParent(), pRetVal
);
311 case UIA_IsPasswordPropertyId
:
312 if (auto* textInterface
= accessibilityHandler
.getTextInterface())
313 VariantHelpers::setBool (textInterface
->isDisplayingProtectedText(), pRetVal
);
316 case ComTypes::UIA_IsPeripheralPropertyId
:
317 VariantHelpers::setBool (role
== AccessibilityRole::tooltip
318 || role
== AccessibilityRole::popupMenu
319 || role
== AccessibilityRole::splashScreen
,
322 case UIA_NamePropertyId
:
324 VariantHelpers::setString (getElementName(), pRetVal
);
327 case UIA_ProcessIdPropertyId
:
328 VariantHelpers::setInt ((int) GetCurrentProcessId(), pRetVal
);
330 case UIA_NativeWindowHandlePropertyId
:
331 if (isFragmentRoot())
332 VariantHelpers::setInt ((int) (pointer_sized_int
) accessibilityHandler
.getComponent().getWindowHandle(), pRetVal
);
341 //==============================================================================
342 JUCE_COMRESULT
AccessibilityNativeHandle::Navigate (ComTypes::NavigateDirection direction
, ComTypes::IRawElementProviderFragment
** pRetVal
)
344 return withCheckedComArgs (pRetVal
, *this, [&]
346 auto* handler
= [&]() -> AccessibilityHandler
*
348 if (direction
== ComTypes::NavigateDirection_Parent
)
349 return accessibilityHandler
.getParent();
351 if (direction
== ComTypes::NavigateDirection_FirstChild
352 || direction
== ComTypes::NavigateDirection_LastChild
)
354 auto children
= accessibilityHandler
.getChildren();
356 return children
.empty() ? nullptr
357 : (direction
== ComTypes::NavigateDirection_FirstChild
? children
.front()
361 if (direction
== ComTypes::NavigateDirection_NextSibling
362 || direction
== ComTypes::NavigateDirection_PreviousSibling
)
364 if (auto* parent
= accessibilityHandler
.getParent())
366 const auto siblings
= parent
->getChildren();
367 const auto iter
= std::find (siblings
.cbegin(), siblings
.cend(), &accessibilityHandler
);
369 if (iter
== siblings
.end())
372 if (direction
== ComTypes::NavigateDirection_NextSibling
&& iter
!= std::prev (siblings
.cend()))
373 return *std::next (iter
);
375 if (direction
== ComTypes::NavigateDirection_PreviousSibling
&& iter
!= siblings
.cbegin())
376 return *std::prev (iter
);
383 if (handler
!= nullptr)
384 if (auto* provider
= handler
->getNativeImplementation())
385 if (provider
->isElementValid())
386 provider
->QueryInterface (IID_PPV_ARGS (pRetVal
));
392 JUCE_COMRESULT
AccessibilityNativeHandle::GetRuntimeId (SAFEARRAY
** pRetVal
)
394 return withCheckedComArgs (pRetVal
, *this, [&]
396 if (! isFragmentRoot())
398 *pRetVal
= SafeArrayCreateVector (VT_I4
, 0, 2);
400 if (*pRetVal
== nullptr)
401 return E_OUTOFMEMORY
;
403 for (LONG i
= 0; i
< 2; ++i
)
405 auto hr
= SafeArrayPutElement (*pRetVal
, &i
, &rtid
[(size_t) i
]);
416 JUCE_COMRESULT
AccessibilityNativeHandle::get_BoundingRectangle (ComTypes::UiaRect
* pRetVal
)
418 return withCheckedComArgs (pRetVal
, *this, [&]
420 auto bounds
= Desktop::getInstance().getDisplays()
421 .logicalToPhysical (accessibilityHandler
.getComponent().getScreenBounds());
423 pRetVal
->left
= bounds
.getX();
424 pRetVal
->top
= bounds
.getY();
425 pRetVal
->width
= bounds
.getWidth();
426 pRetVal
->height
= bounds
.getHeight();
432 JUCE_COMRESULT
AccessibilityNativeHandle::GetEmbeddedFragmentRoots (SAFEARRAY
** pRetVal
)
434 return withCheckedComArgs (pRetVal
, *this, []
440 JUCE_COMRESULT
AccessibilityNativeHandle::SetFocus()
442 if (! isElementValid())
443 return (HRESULT
) UIA_E_ELEMENTNOTAVAILABLE
;
445 const WeakReference
<Component
> safeComponent (&accessibilityHandler
.getComponent());
447 accessibilityHandler
.getActions().invoke (AccessibilityActionType::focus
);
449 if (safeComponent
!= nullptr)
450 accessibilityHandler
.grabFocus();
455 JUCE_COMRESULT
AccessibilityNativeHandle::get_FragmentRoot (ComTypes::IRawElementProviderFragmentRoot
** pRetVal
)
457 return withCheckedComArgs (pRetVal
, *this, [&]() -> HRESULT
459 auto* handler
= [&]() -> AccessibilityHandler
*
461 if (isFragmentRoot())
462 return &accessibilityHandler
;
464 if (auto* peer
= accessibilityHandler
.getComponent().getPeer())
465 return peer
->getComponent().getAccessibilityHandler();
470 if (handler
!= nullptr)
472 handler
->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal
));
476 return (HRESULT
) UIA_E_ELEMENTNOTAVAILABLE
;
480 //==============================================================================
481 JUCE_COMRESULT
AccessibilityNativeHandle::ElementProviderFromPoint (double x
, double y
, ComTypes::IRawElementProviderFragment
** pRetVal
)
483 return withCheckedComArgs (pRetVal
, *this, [&]
487 auto logicalScreenPoint
= Desktop::getInstance().getDisplays()
488 .physicalToLogical (Point
<int> (roundToInt (x
),
491 if (auto* child
= accessibilityHandler
.getChildAt (logicalScreenPoint
))
494 return &accessibilityHandler
;
497 handler
->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal
));
503 JUCE_COMRESULT
AccessibilityNativeHandle::GetFocus (ComTypes::IRawElementProviderFragment
** pRetVal
)
505 return withCheckedComArgs (pRetVal
, *this, [&]
507 const auto getFocusHandler
= [this]() -> AccessibilityHandler
*
509 if (auto* modal
= Component::getCurrentlyModalComponent())
511 const auto& component
= accessibilityHandler
.getComponent();
513 if (! component
.isParentOf (modal
)
514 && component
.isCurrentlyBlockedByAnotherModalComponent())
516 if (auto* modalHandler
= modal
->getAccessibilityHandler())
518 if (auto* focusChild
= modalHandler
->getChildFocus())
526 if (auto* focusChild
= accessibilityHandler
.getChildFocus())
532 if (auto* focusHandler
= getFocusHandler())
533 focusHandler
->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal
));
539 //==============================================================================
540 String
AccessibilityNativeHandle::getElementName() const
542 if (accessibilityHandler
.getRole() == AccessibilityRole::tooltip
)
543 return accessibilityHandler
.getDescription();
545 auto name
= accessibilityHandler
.getTitle();
547 if (name
.isEmpty() && isFragmentRoot())
548 return getAccessibleApplicationOrPluginName();
553 JUCE_END_IGNORE_WARNINGS_GCC_LIKE