The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

559 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. int AccessibilityNativeHandle::idCounter = 0;
  21. //==============================================================================
  22. static String getAutomationId (const AccessibilityHandler& handler)
  23. {
  24. auto result = handler.getTitle();
  25. auto* parentComponent = handler.getComponent().getParentComponent();
  26. while (parentComponent != nullptr)
  27. {
  28. if (auto* parentHandler = parentComponent->getAccessibilityHandler())
  29. {
  30. auto parentTitle = parentHandler->getTitle();
  31. result << "." << (parentTitle.isNotEmpty() ? parentTitle : "<empty>");
  32. }
  33. parentComponent = parentComponent->getParentComponent();
  34. }
  35. return result;
  36. }
  37. static long roleToControlTypeId (AccessibilityRole roleType)
  38. {
  39. switch (roleType)
  40. {
  41. case AccessibilityRole::button: return UIA_ButtonControlTypeId;
  42. case AccessibilityRole::toggleButton: return UIA_CheckBoxControlTypeId;
  43. case AccessibilityRole::radioButton: return UIA_RadioButtonControlTypeId;
  44. case AccessibilityRole::comboBox: return UIA_ComboBoxControlTypeId;
  45. case AccessibilityRole::image: return UIA_ImageControlTypeId;
  46. case AccessibilityRole::slider: return UIA_SliderControlTypeId;
  47. case AccessibilityRole::staticText: return UIA_TextControlTypeId;
  48. case AccessibilityRole::editableText: return UIA_EditControlTypeId;
  49. case AccessibilityRole::menuItem: return UIA_MenuItemControlTypeId;
  50. case AccessibilityRole::menuBar: return UIA_MenuBarControlTypeId;
  51. case AccessibilityRole::popupMenu: return UIA_WindowControlTypeId;
  52. case AccessibilityRole::table: return UIA_TableControlTypeId;
  53. case AccessibilityRole::tableHeader: return UIA_HeaderControlTypeId;
  54. case AccessibilityRole::column: return UIA_HeaderItemControlTypeId;
  55. case AccessibilityRole::row: return UIA_HeaderItemControlTypeId;
  56. case AccessibilityRole::cell: return UIA_DataItemControlTypeId;
  57. case AccessibilityRole::hyperlink: return UIA_HyperlinkControlTypeId;
  58. case AccessibilityRole::list: return UIA_ListControlTypeId;
  59. case AccessibilityRole::listItem: return UIA_ListItemControlTypeId;
  60. case AccessibilityRole::tree: return UIA_TreeControlTypeId;
  61. case AccessibilityRole::treeItem: return UIA_TreeItemControlTypeId;
  62. case AccessibilityRole::progressBar: return UIA_ProgressBarControlTypeId;
  63. case AccessibilityRole::group: return UIA_GroupControlTypeId;
  64. case AccessibilityRole::dialogWindow: return UIA_WindowControlTypeId;
  65. case AccessibilityRole::window: return UIA_WindowControlTypeId;
  66. case AccessibilityRole::scrollBar: return UIA_ScrollBarControlTypeId;
  67. case AccessibilityRole::tooltip: return UIA_ToolTipControlTypeId;
  68. case AccessibilityRole::splashScreen: return UIA_WindowControlTypeId;
  69. case AccessibilityRole::ignored: return UIA_CustomControlTypeId;
  70. case AccessibilityRole::unspecified: return UIA_CustomControlTypeId;
  71. };
  72. return UIA_CustomControlTypeId;
  73. }
  74. //==============================================================================
  75. AccessibilityNativeHandle::AccessibilityNativeHandle (AccessibilityHandler& handler)
  76. : ComBaseClassHelper (0),
  77. accessibilityHandler (handler)
  78. {
  79. }
  80. //==============================================================================
  81. JUCE_COMRESULT AccessibilityNativeHandle::QueryInterface (REFIID refId, void** result)
  82. {
  83. *result = nullptr;
  84. if (! isElementValid())
  85. return UIA_E_ELEMENTNOTAVAILABLE;
  86. if ((refId == __uuidof (IRawElementProviderFragmentRoot) && ! isFragmentRoot()))
  87. return E_NOINTERFACE;
  88. return ComBaseClassHelper::QueryInterface (refId, result);
  89. }
  90. //==============================================================================
  91. JUCE_COMRESULT AccessibilityNativeHandle::get_HostRawElementProvider (IRawElementProviderSimple** pRetVal)
  92. {
  93. return withCheckedComArgs (pRetVal, *this, [&]
  94. {
  95. if (isFragmentRoot())
  96. if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
  97. return wrapper->hostProviderFromHwnd ((HWND) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
  98. return S_OK;
  99. });
  100. }
  101. JUCE_COMRESULT AccessibilityNativeHandle::get_ProviderOptions (ProviderOptions* options)
  102. {
  103. if (options == nullptr)
  104. return E_INVALIDARG;
  105. *options = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading;
  106. return S_OK;
  107. }
  108. JUCE_COMRESULT AccessibilityNativeHandle::GetPatternProvider (PATTERNID pId, IUnknown** pRetVal)
  109. {
  110. return withCheckedComArgs (pRetVal, *this, [&]
  111. {
  112. *pRetVal = [&]() -> IUnknown*
  113. {
  114. const auto role = accessibilityHandler.getRole();
  115. const auto fragmentRoot = isFragmentRoot();
  116. switch (pId)
  117. {
  118. case UIA_WindowPatternId:
  119. {
  120. if (fragmentRoot)
  121. return new UIAWindowProvider (this);
  122. break;
  123. }
  124. case UIA_TransformPatternId:
  125. {
  126. if (fragmentRoot)
  127. return new UIATransformProvider (this);
  128. break;
  129. }
  130. case UIA_TextPatternId:
  131. case UIA_TextPattern2Id:
  132. {
  133. if (accessibilityHandler.getTextInterface() != nullptr)
  134. return new UIATextProvider (this);
  135. break;
  136. }
  137. case UIA_ValuePatternId:
  138. {
  139. if (accessibilityHandler.getValueInterface() != nullptr
  140. || isEditableText (accessibilityHandler)
  141. || nameIsAccessibilityValue (role))
  142. {
  143. return new UIAValueProvider (this);
  144. }
  145. break;
  146. }
  147. case UIA_RangeValuePatternId:
  148. {
  149. if (accessibilityHandler.getValueInterface() != nullptr
  150. && accessibilityHandler.getValueInterface()->getRange().isValid())
  151. {
  152. return new UIARangeValueProvider (this);
  153. }
  154. break;
  155. }
  156. case UIA_TogglePatternId:
  157. {
  158. if (accessibilityHandler.getActions().contains (AccessibilityActionType::toggle)
  159. && accessibilityHandler.getCurrentState().isCheckable())
  160. {
  161. return new UIAToggleProvider (this);
  162. }
  163. break;
  164. }
  165. case UIA_SelectionPatternId:
  166. {
  167. if (role == AccessibilityRole::list
  168. || role == AccessibilityRole::popupMenu
  169. || role == AccessibilityRole::tree)
  170. {
  171. return new UIASelectionProvider (this);
  172. }
  173. break;
  174. }
  175. case UIA_SelectionItemPatternId:
  176. {
  177. auto state = accessibilityHandler.getCurrentState();
  178. if (state.isSelectable() || state.isMultiSelectable()
  179. || role == AccessibilityRole::radioButton)
  180. {
  181. return new UIASelectionItemProvider (this);
  182. }
  183. break;
  184. }
  185. case UIA_GridPatternId:
  186. {
  187. if (accessibilityHandler.getTableInterface() != nullptr)
  188. return new UIAGridProvider (this);
  189. break;
  190. }
  191. case UIA_GridItemPatternId:
  192. {
  193. if (accessibilityHandler.getCellInterface() != nullptr)
  194. return new UIAGridItemProvider (this);
  195. break;
  196. }
  197. case UIA_InvokePatternId:
  198. {
  199. if (accessibilityHandler.getActions().contains (AccessibilityActionType::press))
  200. return new UIAInvokeProvider (this);
  201. break;
  202. }
  203. case UIA_ExpandCollapsePatternId:
  204. {
  205. if (accessibilityHandler.getActions().contains (AccessibilityActionType::showMenu))
  206. return new UIAExpandCollapseProvider (this);
  207. break;
  208. }
  209. default:
  210. break;
  211. }
  212. return nullptr;
  213. }();
  214. return S_OK;
  215. });
  216. }
  217. JUCE_COMRESULT AccessibilityNativeHandle::GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal)
  218. {
  219. return withCheckedComArgs (pRetVal, *this, [&]
  220. {
  221. VariantHelpers::clear (pRetVal);
  222. const auto fragmentRoot = isFragmentRoot();
  223. const auto role = accessibilityHandler.getRole();
  224. const auto state = accessibilityHandler.getCurrentState();
  225. switch (propertyId)
  226. {
  227. case UIA_AutomationIdPropertyId:
  228. VariantHelpers::setString (getAutomationId (accessibilityHandler), pRetVal);
  229. break;
  230. case UIA_ControlTypePropertyId:
  231. VariantHelpers::setInt (roleToControlTypeId (role), pRetVal);
  232. break;
  233. case UIA_FrameworkIdPropertyId:
  234. VariantHelpers::setString ("JUCE", pRetVal);
  235. break;
  236. case UIA_FullDescriptionPropertyId:
  237. VariantHelpers::setString (accessibilityHandler.getDescription(), pRetVal);
  238. break;
  239. case UIA_HelpTextPropertyId:
  240. VariantHelpers::setString (accessibilityHandler.getHelp(), pRetVal);
  241. break;
  242. case UIA_IsContentElementPropertyId:
  243. VariantHelpers::setBool (! accessibilityHandler.isIgnored() && accessibilityHandler.isVisibleWithinParent(),
  244. pRetVal);
  245. break;
  246. case UIA_IsControlElementPropertyId:
  247. VariantHelpers::setBool (true, pRetVal);
  248. break;
  249. case UIA_IsDialogPropertyId:
  250. VariantHelpers::setBool (role == AccessibilityRole::dialogWindow, pRetVal);
  251. break;
  252. case UIA_IsEnabledPropertyId:
  253. VariantHelpers::setBool (accessibilityHandler.getComponent().isEnabled(), pRetVal);
  254. break;
  255. case UIA_IsKeyboardFocusablePropertyId:
  256. VariantHelpers::setBool (state.isFocusable(), pRetVal);
  257. break;
  258. case UIA_HasKeyboardFocusPropertyId:
  259. VariantHelpers::setBool (accessibilityHandler.hasFocus (true), pRetVal);
  260. break;
  261. case UIA_IsOffscreenPropertyId:
  262. VariantHelpers::setBool (! accessibilityHandler.isVisibleWithinParent(), pRetVal);
  263. break;
  264. case UIA_IsPasswordPropertyId:
  265. if (auto* textInterface = accessibilityHandler.getTextInterface())
  266. VariantHelpers::setBool (textInterface->isDisplayingProtectedText(), pRetVal);
  267. break;
  268. case UIA_IsPeripheralPropertyId:
  269. VariantHelpers::setBool (role == AccessibilityRole::tooltip
  270. || role == AccessibilityRole::popupMenu
  271. || role == AccessibilityRole::splashScreen,
  272. pRetVal);
  273. break;
  274. case UIA_NamePropertyId:
  275. VariantHelpers::setString (getElementName(), pRetVal);
  276. break;
  277. case UIA_ProcessIdPropertyId:
  278. VariantHelpers::setInt ((int) GetCurrentProcessId(), pRetVal);
  279. break;
  280. case UIA_NativeWindowHandlePropertyId:
  281. if (fragmentRoot)
  282. VariantHelpers::setInt ((int) (pointer_sized_int) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
  283. break;
  284. default:
  285. break;
  286. }
  287. return S_OK;
  288. });
  289. }
  290. //==============================================================================
  291. JUCE_COMRESULT AccessibilityNativeHandle::Navigate (NavigateDirection direction, IRawElementProviderFragment** pRetVal)
  292. {
  293. return withCheckedComArgs (pRetVal, *this, [&]
  294. {
  295. auto* handler = [&]() -> AccessibilityHandler*
  296. {
  297. if (direction == NavigateDirection_Parent)
  298. return accessibilityHandler.getParent();
  299. if (direction == NavigateDirection_FirstChild
  300. || direction == NavigateDirection_LastChild)
  301. {
  302. auto children = accessibilityHandler.getChildren();
  303. return children.empty() ? nullptr
  304. : (direction == NavigateDirection_FirstChild ? children.front()
  305. : children.back());
  306. }
  307. if (direction == NavigateDirection_NextSibling
  308. || direction == NavigateDirection_PreviousSibling)
  309. {
  310. if (auto* parent = accessibilityHandler.getParent())
  311. {
  312. const auto siblings = parent->getChildren();
  313. const auto iter = std::find (siblings.cbegin(), siblings.cend(), &accessibilityHandler);
  314. if (iter == siblings.end())
  315. return nullptr;
  316. if (direction == NavigateDirection_NextSibling && iter != std::prev (siblings.cend()))
  317. return *std::next (iter);
  318. if (direction == NavigateDirection_PreviousSibling && iter != siblings.cbegin())
  319. return *std::prev (iter);
  320. }
  321. }
  322. return nullptr;
  323. }();
  324. if (handler != nullptr)
  325. if (auto* provider = handler->getNativeImplementation())
  326. if (provider->isElementValid())
  327. provider->QueryInterface (IID_PPV_ARGS (pRetVal));
  328. return S_OK;
  329. });
  330. }
  331. JUCE_COMRESULT AccessibilityNativeHandle::GetRuntimeId (SAFEARRAY** pRetVal)
  332. {
  333. return withCheckedComArgs (pRetVal, *this, [&]
  334. {
  335. if (! isFragmentRoot())
  336. {
  337. *pRetVal = SafeArrayCreateVector (VT_I4, 0, 2);
  338. if (*pRetVal == nullptr)
  339. return E_OUTOFMEMORY;
  340. for (LONG i = 0; i < 2; ++i)
  341. {
  342. auto hr = SafeArrayPutElement (*pRetVal, &i, &rtid[i]);
  343. if (FAILED (hr))
  344. return E_FAIL;
  345. }
  346. }
  347. return S_OK;
  348. });
  349. }
  350. JUCE_COMRESULT AccessibilityNativeHandle::get_BoundingRectangle (UiaRect* pRetVal)
  351. {
  352. return withCheckedComArgs (pRetVal, *this, [&]
  353. {
  354. auto bounds = Desktop::getInstance().getDisplays()
  355. .logicalToPhysical (accessibilityHandler.getComponent().getScreenBounds());
  356. pRetVal->left = bounds.getX();
  357. pRetVal->top = bounds.getY();
  358. pRetVal->width = bounds.getWidth();
  359. pRetVal->height = bounds.getHeight();
  360. return S_OK;
  361. });
  362. }
  363. JUCE_COMRESULT AccessibilityNativeHandle::GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal)
  364. {
  365. return withCheckedComArgs (pRetVal, *this, []
  366. {
  367. return S_OK;
  368. });
  369. }
  370. JUCE_COMRESULT AccessibilityNativeHandle::SetFocus()
  371. {
  372. if (! isElementValid())
  373. return UIA_E_ELEMENTNOTAVAILABLE;
  374. const WeakReference<Component> safeComponent (&accessibilityHandler.getComponent());
  375. accessibilityHandler.getActions().invoke (AccessibilityActionType::focus);
  376. if (safeComponent != nullptr)
  377. accessibilityHandler.grabFocus();
  378. return S_OK;
  379. }
  380. JUCE_COMRESULT AccessibilityNativeHandle::get_FragmentRoot (IRawElementProviderFragmentRoot** pRetVal)
  381. {
  382. return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
  383. {
  384. auto* handler = [&]() -> AccessibilityHandler*
  385. {
  386. if (isFragmentRoot())
  387. return &accessibilityHandler;
  388. if (auto* peer = accessibilityHandler.getComponent().getPeer())
  389. if (auto* handler = peer->getComponent().getAccessibilityHandler())
  390. return handler;
  391. return nullptr;
  392. }();
  393. if (handler != nullptr)
  394. {
  395. handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
  396. return S_OK;
  397. }
  398. return UIA_E_ELEMENTNOTAVAILABLE;
  399. });
  400. }
  401. //==============================================================================
  402. JUCE_COMRESULT AccessibilityNativeHandle::ElementProviderFromPoint (double x, double y, IRawElementProviderFragment** pRetVal)
  403. {
  404. return withCheckedComArgs (pRetVal, *this, [&]
  405. {
  406. auto* handler = [&]
  407. {
  408. auto logicalScreenPoint = Desktop::getInstance().getDisplays()
  409. .physicalToLogical (Point<int> (roundToInt (x),
  410. roundToInt (y)));
  411. if (auto* child = accessibilityHandler.getChildAt (logicalScreenPoint))
  412. return child;
  413. return &accessibilityHandler;
  414. }();
  415. handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
  416. return S_OK;
  417. });
  418. }
  419. JUCE_COMRESULT AccessibilityNativeHandle::GetFocus (IRawElementProviderFragment** pRetVal)
  420. {
  421. return withCheckedComArgs (pRetVal, *this, [&]
  422. {
  423. const auto getFocusHandler = [this]() -> AccessibilityHandler*
  424. {
  425. if (auto* modal = Component::getCurrentlyModalComponent())
  426. {
  427. const auto& component = accessibilityHandler.getComponent();
  428. if (! component.isParentOf (modal)
  429. && component.isCurrentlyBlockedByAnotherModalComponent())
  430. {
  431. if (auto* modalHandler = modal->getAccessibilityHandler())
  432. {
  433. if (auto* focusChild = modalHandler->getChildFocus())
  434. return focusChild;
  435. return modalHandler;
  436. }
  437. }
  438. }
  439. if (auto* focusChild = accessibilityHandler.getChildFocus())
  440. return focusChild;
  441. return nullptr;
  442. };
  443. if (auto* focusHandler = getFocusHandler())
  444. focusHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
  445. return S_OK;
  446. });
  447. }
  448. //==============================================================================
  449. String AccessibilityNativeHandle::getElementName() const
  450. {
  451. const auto role = accessibilityHandler.getRole();
  452. if (nameIsAccessibilityValue (role))
  453. return {};
  454. if (role == AccessibilityRole::tooltip)
  455. return accessibilityHandler.getDescription();
  456. auto name = accessibilityHandler.getTitle();
  457. if (name.isEmpty() && isFragmentRoot())
  458. return getAccessibleApplicationOrPluginName();
  459. return name;
  460. }
  461. } // namespace juce