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.

328 lines
11KB

  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. #define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
  21. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
  22. static bool isStartingUpOrShuttingDown()
  23. {
  24. if (auto* app = JUCEApplicationBase::getInstance())
  25. if (app->isInitialising())
  26. return true;
  27. if (auto* mm = MessageManager::getInstanceWithoutCreating())
  28. if (mm->hasStopMessageBeenSent())
  29. return true;
  30. return false;
  31. }
  32. static bool isHandlerValid (const AccessibilityHandler& handler)
  33. {
  34. if (auto* provider = handler.getNativeImplementation())
  35. return provider->isElementValid();
  36. return false;
  37. }
  38. //==============================================================================
  39. class AccessibilityHandler::AccessibilityNativeImpl
  40. {
  41. public:
  42. explicit AccessibilityNativeImpl (AccessibilityHandler& owner)
  43. : accessibilityElement (new AccessibilityNativeHandle (owner))
  44. {
  45. ++providerCount;
  46. }
  47. ~AccessibilityNativeImpl()
  48. {
  49. ComSmartPtr<IRawElementProviderSimple> provider;
  50. accessibilityElement->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
  51. accessibilityElement->invalidateElement();
  52. --providerCount;
  53. if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
  54. {
  55. uiaWrapper->disconnectProvider (provider);
  56. if (providerCount == 0 && JUCEApplicationBase::isStandaloneApp())
  57. uiaWrapper->disconnectAllProviders();
  58. }
  59. }
  60. //==============================================================================
  61. ComSmartPtr<AccessibilityNativeHandle> accessibilityElement;
  62. static int providerCount;
  63. //==============================================================================
  64. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
  65. };
  66. int AccessibilityHandler::AccessibilityNativeImpl::providerCount = 0;
  67. //==============================================================================
  68. AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
  69. {
  70. return nativeImpl->accessibilityElement;
  71. }
  72. static bool areAnyAccessibilityClientsActive()
  73. {
  74. const auto areClientsListening = []
  75. {
  76. if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
  77. return uiaWrapper->clientsAreListening() != 0;
  78. return false;
  79. };
  80. const auto isScreenReaderRunning = []
  81. {
  82. BOOL isRunning = FALSE;
  83. SystemParametersInfo (SPI_GETSCREENREADER, 0, (PVOID) &isRunning, 0);
  84. return isRunning != 0;
  85. };
  86. return areClientsListening() || isScreenReaderRunning();
  87. }
  88. template <typename Callback>
  89. void getProviderWithCheckedWrapper (const AccessibilityHandler& handler, Callback&& callback)
  90. {
  91. if (! areAnyAccessibilityClientsActive() || isStartingUpOrShuttingDown() || ! isHandlerValid (handler))
  92. return;
  93. if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
  94. {
  95. ComSmartPtr<IRawElementProviderSimple> provider;
  96. handler.getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
  97. callback (uiaWrapper, provider);
  98. }
  99. }
  100. void sendAccessibilityAutomationEvent (const AccessibilityHandler& handler, EVENTID event)
  101. {
  102. jassert (event != EVENTID{});
  103. getProviderWithCheckedWrapper (handler, [event] (WindowsUIAWrapper* uiaWrapper, ComSmartPtr<IRawElementProviderSimple>& provider)
  104. {
  105. uiaWrapper->raiseAutomationEvent (provider, event);
  106. });
  107. }
  108. void sendAccessibilityPropertyChangedEvent (const AccessibilityHandler& handler, PROPERTYID property, VARIANT newValue)
  109. {
  110. jassert (property != PROPERTYID{});
  111. getProviderWithCheckedWrapper (handler, [property, newValue] (WindowsUIAWrapper* uiaWrapper, ComSmartPtr<IRawElementProviderSimple>& provider)
  112. {
  113. VARIANT oldValue;
  114. VariantHelpers::clear (&oldValue);
  115. uiaWrapper->raiseAutomationPropertyChangedEvent (provider, property, oldValue, newValue);
  116. });
  117. }
  118. void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
  119. {
  120. if (eventType == InternalAccessibilityEvent::elementCreated
  121. || eventType == InternalAccessibilityEvent::elementDestroyed)
  122. {
  123. if (auto* parent = handler.getParent())
  124. sendAccessibilityAutomationEvent (*parent, ComTypes::UIA_LayoutInvalidatedEventId);
  125. return;
  126. }
  127. if (eventType == InternalAccessibilityEvent::windowOpened
  128. || eventType == InternalAccessibilityEvent::windowClosed)
  129. {
  130. if (auto* peer = handler.getComponent().getPeer())
  131. if ((peer->getStyleFlags() & ComponentPeer::windowHasTitleBar) == 0)
  132. return;
  133. }
  134. auto event = [eventType]() -> EVENTID
  135. {
  136. switch (eventType)
  137. {
  138. case InternalAccessibilityEvent::focusChanged: return ComTypes::UIA_AutomationFocusChangedEventId;
  139. case InternalAccessibilityEvent::windowOpened: return ComTypes::UIA_Window_WindowOpenedEventId;
  140. case InternalAccessibilityEvent::windowClosed: return ComTypes::UIA_Window_WindowClosedEventId;
  141. case InternalAccessibilityEvent::elementCreated:
  142. case InternalAccessibilityEvent::elementDestroyed:
  143. case InternalAccessibilityEvent::elementMovedOrResized: break;
  144. }
  145. return {};
  146. }();
  147. if (event != EVENTID{})
  148. sendAccessibilityAutomationEvent (handler, event);
  149. }
  150. void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
  151. {
  152. if (eventType == AccessibilityEvent::titleChanged)
  153. {
  154. VARIANT newValue;
  155. VariantHelpers::setString (getTitle(), &newValue);
  156. sendAccessibilityPropertyChangedEvent (*this, UIA_NamePropertyId, newValue);
  157. return;
  158. }
  159. if (eventType == AccessibilityEvent::valueChanged)
  160. {
  161. if (auto* valueInterface = getValueInterface())
  162. {
  163. const auto propertyType = getRole() == AccessibilityRole::slider ? UIA_RangeValueValuePropertyId
  164. : UIA_ValueValuePropertyId;
  165. const auto value = getRole() == AccessibilityRole::slider
  166. ? VariantHelpers::getWithValue (valueInterface->getCurrentValue())
  167. : VariantHelpers::getWithValue (valueInterface->getCurrentValueAsString());
  168. sendAccessibilityPropertyChangedEvent (*this, propertyType, value);
  169. }
  170. return;
  171. }
  172. auto event = [eventType]() -> EVENTID
  173. {
  174. switch (eventType)
  175. {
  176. case AccessibilityEvent::textSelectionChanged: return ComTypes::UIA_Text_TextSelectionChangedEventId;
  177. case AccessibilityEvent::textChanged: return ComTypes::UIA_Text_TextChangedEventId;
  178. case AccessibilityEvent::structureChanged: return ComTypes::UIA_StructureChangedEventId;
  179. case AccessibilityEvent::rowSelectionChanged: return ComTypes::UIA_SelectionItem_ElementSelectedEventId;
  180. case AccessibilityEvent::titleChanged:
  181. case AccessibilityEvent::valueChanged: break;
  182. }
  183. return {};
  184. }();
  185. if (event != EVENTID{})
  186. sendAccessibilityAutomationEvent (*this, event);
  187. }
  188. struct SpVoiceWrapper : public DeletedAtShutdown
  189. {
  190. SpVoiceWrapper()
  191. {
  192. auto hr = voice.CoCreateInstance (ComTypes::CLSID_SpVoice);
  193. jassertquiet (SUCCEEDED (hr));
  194. }
  195. ~SpVoiceWrapper() override
  196. {
  197. clearSingletonInstance();
  198. }
  199. ComSmartPtr<ISpVoice> voice;
  200. JUCE_DECLARE_SINGLETON (SpVoiceWrapper, false)
  201. };
  202. JUCE_IMPLEMENT_SINGLETON (SpVoiceWrapper)
  203. void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority priority)
  204. {
  205. if (! areAnyAccessibilityClientsActive())
  206. return;
  207. if (auto* sharedVoice = SpVoiceWrapper::getInstance())
  208. {
  209. auto voicePriority = [priority]
  210. {
  211. switch (priority)
  212. {
  213. case AnnouncementPriority::low: return SPVPRI_OVER;
  214. case AnnouncementPriority::medium: return SPVPRI_NORMAL;
  215. case AnnouncementPriority::high: return SPVPRI_ALERT;
  216. }
  217. jassertfalse;
  218. return SPVPRI_OVER;
  219. }();
  220. sharedVoice->voice->SetPriority (voicePriority);
  221. sharedVoice->voice->Speak (announcementString.toWideCharPointer(), SPF_ASYNC, nullptr);
  222. }
  223. }
  224. //==============================================================================
  225. namespace WindowsAccessibility
  226. {
  227. static long getUiaRootObjectId()
  228. {
  229. return static_cast<long> (UiaRootObjectId);
  230. }
  231. static bool handleWmGetObject (AccessibilityHandler* handler, WPARAM wParam, LPARAM lParam, LRESULT* res)
  232. {
  233. if (isStartingUpOrShuttingDown() || (handler == nullptr || ! isHandlerValid (*handler)))
  234. return false;
  235. if (auto* uiaWrapper = WindowsUIAWrapper::getInstance())
  236. {
  237. ComSmartPtr<IRawElementProviderSimple> provider;
  238. handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
  239. if (! uiaWrapper->isProviderDisconnecting (provider))
  240. *res = uiaWrapper->returnRawElementProvider ((HWND) handler->getComponent().getWindowHandle(), wParam, lParam, provider);
  241. return true;
  242. }
  243. return false;
  244. }
  245. static void revokeUIAMapEntriesForWindow (HWND hwnd)
  246. {
  247. if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
  248. uiaWrapper->returnRawElementProvider (hwnd, 0, 0, nullptr);
  249. }
  250. }
  251. JUCE_IMPLEMENT_SINGLETON (WindowsUIAWrapper)
  252. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  253. } // namespace juce