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.

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