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.

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