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.

632 lines
24KB

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