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. static bool isEditableText (const AccessibilityHandler& handler)
  75. {
  76. return handler.getRole() == AccessibilityRole::editableText
  77. && handler.getTextInterface() != nullptr;
  78. }
  79. //==============================================================================
  80. AccessibilityNativeHandle::AccessibilityNativeHandle (AccessibilityHandler& handler)
  81. : ComBaseClassHelper (0),
  82. accessibilityHandler (handler)
  83. {
  84. }
  85. //==============================================================================
  86. JUCE_COMRESULT AccessibilityNativeHandle::QueryInterface (REFIID refId, void** result)
  87. {
  88. *result = nullptr;
  89. if (! isElementValid())
  90. return UIA_E_ELEMENTNOTAVAILABLE;
  91. if ((refId == __uuidof (IRawElementProviderFragmentRoot) && ! isFragmentRoot()))
  92. return E_NOINTERFACE;
  93. return ComBaseClassHelper::QueryInterface (refId, result);
  94. }
  95. //==============================================================================
  96. JUCE_COMRESULT AccessibilityNativeHandle::get_HostRawElementProvider (IRawElementProviderSimple** pRetVal)
  97. {
  98. return withCheckedComArgs (pRetVal, *this, [&]
  99. {
  100. if (isFragmentRoot())
  101. if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
  102. return wrapper->hostProviderFromHwnd ((HWND) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
  103. return S_OK;
  104. });
  105. }
  106. JUCE_COMRESULT AccessibilityNativeHandle::get_ProviderOptions (ProviderOptions* options)
  107. {
  108. if (options == nullptr)
  109. return E_INVALIDARG;
  110. *options = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading;
  111. return S_OK;
  112. }
  113. JUCE_COMRESULT AccessibilityNativeHandle::GetPatternProvider (PATTERNID pId, IUnknown** pRetVal)
  114. {
  115. return withCheckedComArgs (pRetVal, *this, [&]
  116. {
  117. *pRetVal = [&]() -> IUnknown*
  118. {
  119. const auto role = accessibilityHandler.getRole();
  120. const auto fragmentRoot = isFragmentRoot();
  121. switch (pId)
  122. {
  123. case UIA_WindowPatternId:
  124. {
  125. if (fragmentRoot)
  126. return new UIAWindowProvider (this);
  127. break;
  128. }
  129. case UIA_TransformPatternId:
  130. {
  131. if (fragmentRoot)
  132. return new UIATransformProvider (this);
  133. break;
  134. }
  135. case UIA_TextPatternId:
  136. case UIA_TextPattern2Id:
  137. {
  138. if (accessibilityHandler.getTextInterface() != nullptr)
  139. return new UIATextProvider (this);
  140. break;
  141. }
  142. case UIA_ValuePatternId:
  143. {
  144. auto editableText = isEditableText (accessibilityHandler);
  145. if (accessibilityHandler.getValueInterface() != nullptr || editableText)
  146. return new UIAValueProvider (this, editableText);
  147. break;
  148. }
  149. case UIA_RangeValuePatternId:
  150. {
  151. if (accessibilityHandler.getValueInterface() != nullptr
  152. && accessibilityHandler.getValueInterface()->getRange().isValid())
  153. {
  154. return new UIARangeValueProvider (this);
  155. }
  156. break;
  157. }
  158. case UIA_TogglePatternId:
  159. {
  160. if (accessibilityHandler.getActions().contains (AccessibilityActionType::toggle)
  161. && accessibilityHandler.getCurrentState().isCheckable())
  162. {
  163. return new UIAToggleProvider (this);
  164. }
  165. break;
  166. }
  167. case UIA_SelectionPatternId:
  168. {
  169. if (role == AccessibilityRole::list
  170. || role == AccessibilityRole::popupMenu
  171. || role == AccessibilityRole::tree)
  172. {
  173. return new UIASelectionProvider (this);
  174. }
  175. break;
  176. }
  177. case UIA_SelectionItemPatternId:
  178. {
  179. auto state = accessibilityHandler.getCurrentState();
  180. if (state.isSelectable() || state.isMultiSelectable()
  181. || role == AccessibilityRole::radioButton)
  182. {
  183. return new UIASelectionItemProvider (this);
  184. }
  185. break;
  186. }
  187. case UIA_GridPatternId:
  188. {
  189. if ((role == AccessibilityRole::table || role == AccessibilityRole::tree)
  190. && accessibilityHandler.getTableInterface() != nullptr)
  191. {
  192. return new UIAGridProvider (this);
  193. }
  194. break;
  195. }
  196. case UIA_GridItemPatternId:
  197. {
  198. if ((role == AccessibilityRole::cell || role == AccessibilityRole::treeItem)
  199. && accessibilityHandler.getCellInterface() != nullptr)
  200. {
  201. return new UIAGridItemProvider (this);
  202. }
  203. break;
  204. }
  205. case UIA_InvokePatternId:
  206. {
  207. if (accessibilityHandler.getActions().contains (AccessibilityActionType::press))
  208. return new UIAInvokeProvider (this);
  209. break;
  210. }
  211. case UIA_ExpandCollapsePatternId:
  212. {
  213. if (role == AccessibilityRole::menuItem
  214. && accessibilityHandler.getActions().contains (AccessibilityActionType::showMenu))
  215. {
  216. return new UIAExpandCollapseProvider (this);
  217. }
  218. break;
  219. }
  220. default:
  221. break;
  222. }
  223. return nullptr;
  224. }();
  225. return S_OK;
  226. });
  227. }
  228. JUCE_COMRESULT AccessibilityNativeHandle::GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal)
  229. {
  230. return withCheckedComArgs (pRetVal, *this, [&]
  231. {
  232. VariantHelpers::clear (pRetVal);
  233. const auto fragmentRoot = isFragmentRoot();
  234. switch (propertyId)
  235. {
  236. case UIA_AutomationIdPropertyId:
  237. VariantHelpers::setString (getAutomationId (accessibilityHandler), pRetVal);
  238. break;
  239. case UIA_ControlTypePropertyId:
  240. VariantHelpers::setInt (roleToControlTypeId (accessibilityHandler.getRole()),
  241. pRetVal);
  242. break;
  243. case UIA_FrameworkIdPropertyId:
  244. VariantHelpers::setString ("JUCE", pRetVal);
  245. break;
  246. case UIA_FullDescriptionPropertyId:
  247. VariantHelpers::setString (accessibilityHandler.getDescription(), pRetVal);
  248. break;
  249. case UIA_HelpTextPropertyId:
  250. VariantHelpers::setString (accessibilityHandler.getHelp(), pRetVal);
  251. break;
  252. case UIA_IsContentElementPropertyId:
  253. VariantHelpers::setBool (! accessibilityHandler.isIgnored(), pRetVal);
  254. break;
  255. case UIA_IsControlElementPropertyId:
  256. VariantHelpers::setBool (true, pRetVal);
  257. break;
  258. case UIA_IsDialogPropertyId:
  259. VariantHelpers::setBool (accessibilityHandler.getRole() == AccessibilityRole::dialogWindow, pRetVal);
  260. break;
  261. case UIA_IsEnabledPropertyId:
  262. VariantHelpers::setBool (accessibilityHandler.getComponent().isEnabled(), pRetVal);
  263. break;
  264. case UIA_IsKeyboardFocusablePropertyId:
  265. VariantHelpers::setBool (accessibilityHandler.getCurrentState().isFocusable(), pRetVal);
  266. break;
  267. case UIA_HasKeyboardFocusPropertyId:
  268. VariantHelpers::setBool (accessibilityHandler.hasFocus (true), pRetVal);
  269. break;
  270. case UIA_IsOffscreenPropertyId:
  271. VariantHelpers::setBool (false, pRetVal);
  272. break;
  273. case UIA_IsPasswordPropertyId:
  274. if (auto* textInterface = accessibilityHandler.getTextInterface())
  275. VariantHelpers::setBool (textInterface->isDisplayingProtectedText(), pRetVal);
  276. break;
  277. case UIA_IsPeripheralPropertyId:
  278. VariantHelpers::setBool (accessibilityHandler.getRole() == AccessibilityRole::tooltip
  279. || accessibilityHandler.getRole() == AccessibilityRole::popupMenu
  280. || accessibilityHandler.getRole() == AccessibilityRole::splashScreen,
  281. pRetVal);
  282. break;
  283. case UIA_NamePropertyId:
  284. VariantHelpers::setString (getElementName(), pRetVal);
  285. break;
  286. case UIA_ProcessIdPropertyId:
  287. VariantHelpers::setInt ((int) GetCurrentProcessId(), pRetVal);
  288. break;
  289. case UIA_NativeWindowHandlePropertyId:
  290. if (fragmentRoot)
  291. VariantHelpers::setInt ((int) (pointer_sized_int) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
  292. break;
  293. default:
  294. break;
  295. }
  296. return S_OK;
  297. });
  298. }
  299. //==============================================================================
  300. JUCE_COMRESULT AccessibilityNativeHandle::Navigate (NavigateDirection direction, IRawElementProviderFragment** pRetVal)
  301. {
  302. return withCheckedComArgs (pRetVal, *this, [&]
  303. {
  304. auto* handler = [&]() -> AccessibilityHandler*
  305. {
  306. if (direction == NavigateDirection_Parent)
  307. return accessibilityHandler.getParent();
  308. if (direction == NavigateDirection_FirstChild
  309. || direction == NavigateDirection_LastChild)
  310. {
  311. auto children = accessibilityHandler.getChildren();
  312. return children.empty() ? nullptr
  313. : (direction == NavigateDirection_FirstChild ? children.front()
  314. : children.back());
  315. }
  316. if (direction == NavigateDirection_NextSibling
  317. || direction == NavigateDirection_PreviousSibling)
  318. {
  319. if (auto* parent = accessibilityHandler.getParent())
  320. {
  321. const auto siblings = parent->getChildren();
  322. const auto iter = std::find (siblings.cbegin(), siblings.cend(), &accessibilityHandler);
  323. if (iter == siblings.end())
  324. return nullptr;
  325. if (direction == NavigateDirection_NextSibling && iter != std::prev (siblings.cend()))
  326. return *std::next (iter);
  327. if (direction == NavigateDirection_PreviousSibling && iter != siblings.cbegin())
  328. return *std::prev (iter);
  329. }
  330. }
  331. return nullptr;
  332. }();
  333. if (handler != nullptr)
  334. if (auto* provider = handler->getNativeImplementation())
  335. if (provider->isElementValid())
  336. provider->QueryInterface (IID_PPV_ARGS (pRetVal));
  337. return S_OK;
  338. });
  339. }
  340. JUCE_COMRESULT AccessibilityNativeHandle::GetRuntimeId (SAFEARRAY** pRetVal)
  341. {
  342. return withCheckedComArgs (pRetVal, *this, [&]
  343. {
  344. if (! isFragmentRoot())
  345. {
  346. *pRetVal = SafeArrayCreateVector (VT_I4, 0, 2);
  347. if (*pRetVal == nullptr)
  348. return E_OUTOFMEMORY;
  349. for (LONG i = 0; i < 2; ++i)
  350. {
  351. auto hr = SafeArrayPutElement (*pRetVal, &i, &rtid[i]);
  352. if (FAILED (hr))
  353. return E_FAIL;
  354. }
  355. }
  356. return S_OK;
  357. });
  358. }
  359. JUCE_COMRESULT AccessibilityNativeHandle::get_BoundingRectangle (UiaRect* pRetVal)
  360. {
  361. return withCheckedComArgs (pRetVal, *this, [&]
  362. {
  363. auto bounds = Desktop::getInstance().getDisplays()
  364. .logicalToPhysical (accessibilityHandler.getComponent().getScreenBounds());
  365. pRetVal->left = bounds.getX();
  366. pRetVal->top = bounds.getY();
  367. pRetVal->width = bounds.getWidth();
  368. pRetVal->height = bounds.getHeight();
  369. return S_OK;
  370. });
  371. }
  372. JUCE_COMRESULT AccessibilityNativeHandle::GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal)
  373. {
  374. return withCheckedComArgs (pRetVal, *this, []
  375. {
  376. return S_OK;
  377. });
  378. }
  379. JUCE_COMRESULT AccessibilityNativeHandle::SetFocus()
  380. {
  381. if (! isElementValid())
  382. return UIA_E_ELEMENTNOTAVAILABLE;
  383. accessibilityHandler.grabFocus();
  384. return S_OK;
  385. }
  386. JUCE_COMRESULT AccessibilityNativeHandle::get_FragmentRoot (IRawElementProviderFragmentRoot** pRetVal)
  387. {
  388. return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
  389. {
  390. auto* handler = [&]() -> AccessibilityHandler*
  391. {
  392. if (isFragmentRoot())
  393. return &accessibilityHandler;
  394. if (auto* peer = accessibilityHandler.getComponent().getPeer())
  395. if (auto* handler = peer->getComponent().getAccessibilityHandler())
  396. return handler;
  397. return nullptr;
  398. }();
  399. if (handler != nullptr)
  400. {
  401. handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
  402. return S_OK;
  403. }
  404. return UIA_E_ELEMENTNOTAVAILABLE;
  405. });
  406. }
  407. //==============================================================================
  408. JUCE_COMRESULT AccessibilityNativeHandle::ElementProviderFromPoint (double x, double y, IRawElementProviderFragment** pRetVal)
  409. {
  410. return withCheckedComArgs (pRetVal, *this, [&]
  411. {
  412. auto* handler = [&]
  413. {
  414. auto logicalScreenPoint = Desktop::getInstance().getDisplays()
  415. .physicalToLogical (Point<int> (roundToInt (x),
  416. roundToInt (y)));
  417. if (auto* child = accessibilityHandler.getChildAt (logicalScreenPoint))
  418. return child;
  419. return &accessibilityHandler;
  420. }();
  421. handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
  422. return S_OK;
  423. });
  424. }
  425. JUCE_COMRESULT AccessibilityNativeHandle::GetFocus (IRawElementProviderFragment** pRetVal)
  426. {
  427. return withCheckedComArgs (pRetVal, *this, [&]
  428. {
  429. const auto getFocusHandler = [this]() -> AccessibilityHandler*
  430. {
  431. if (auto* modal = Component::getCurrentlyModalComponent())
  432. {
  433. const auto& component = accessibilityHandler.getComponent();
  434. if (! component.isParentOf (modal)
  435. && component.isCurrentlyBlockedByAnotherModalComponent())
  436. {
  437. if (auto* modalHandler = modal->getAccessibilityHandler())
  438. {
  439. if (auto* focusChild = modalHandler->getChildFocus())
  440. return focusChild;
  441. return modalHandler;
  442. }
  443. }
  444. }
  445. if (auto* focusChild = accessibilityHandler.getChildFocus())
  446. return focusChild;
  447. return nullptr;
  448. };
  449. if (auto* focusHandler = getFocusHandler())
  450. focusHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
  451. return S_OK;
  452. });
  453. }
  454. //==============================================================================
  455. String AccessibilityNativeHandle::getElementName() const
  456. {
  457. if (accessibilityHandler.getRole() == AccessibilityRole::tooltip)
  458. return accessibilityHandler.getDescription();
  459. auto name = accessibilityHandler.getTitle();
  460. if (name.isEmpty() && isFragmentRoot())
  461. return getAccessibleApplicationOrPluginName();
  462. return name;
  463. }
  464. } // namespace juce