/* ============================================================================== This file is part of the JUCE 7 technical preview. Copyright (c) 2022 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For the technical preview this file cannot be licensed commercially. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { #define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") static bool isStartingUpOrShuttingDown() { if (auto* app = JUCEApplicationBase::getInstance()) if (app->isInitialising()) return true; if (auto* mm = MessageManager::getInstanceWithoutCreating()) if (mm->hasStopMessageBeenSent()) return true; return false; } static bool isHandlerValid (const AccessibilityHandler& handler) { if (auto* provider = handler.getNativeImplementation()) return provider->isElementValid(); return false; } //============================================================================== class AccessibilityHandler::AccessibilityNativeImpl { public: explicit AccessibilityNativeImpl (AccessibilityHandler& owner) : accessibilityElement (new AccessibilityNativeHandle (owner)) { ++providerCount; } ~AccessibilityNativeImpl() { ComSmartPtr provider; accessibilityElement->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress())); accessibilityElement->invalidateElement(); --providerCount; if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) { uiaWrapper->disconnectProvider (provider); if (providerCount == 0 && JUCEApplicationBase::isStandaloneApp()) uiaWrapper->disconnectAllProviders(); } } //============================================================================== ComSmartPtr accessibilityElement; static int providerCount; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl) }; int AccessibilityHandler::AccessibilityNativeImpl::providerCount = 0; //============================================================================== AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nativeImpl->accessibilityElement; } static bool areAnyAccessibilityClientsActive() { const auto areClientsListening = [] { if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) return uiaWrapper->clientsAreListening() != 0; return false; }; const auto isScreenReaderRunning = [] { BOOL isRunning = FALSE; SystemParametersInfo (SPI_GETSCREENREADER, 0, (PVOID) &isRunning, 0); return isRunning != 0; }; return areClientsListening() || isScreenReaderRunning(); } template void getProviderWithCheckedWrapper (const AccessibilityHandler& handler, Callback&& callback) { if (! areAnyAccessibilityClientsActive() || isStartingUpOrShuttingDown() || ! isHandlerValid (handler)) return; if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) { ComSmartPtr provider; handler.getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress())); callback (uiaWrapper, provider); } } void sendAccessibilityAutomationEvent (const AccessibilityHandler& handler, EVENTID event) { jassert (event != EVENTID{}); getProviderWithCheckedWrapper (handler, [event] (WindowsUIAWrapper* uiaWrapper, ComSmartPtr& provider) { uiaWrapper->raiseAutomationEvent (provider, event); }); } void sendAccessibilityPropertyChangedEvent (const AccessibilityHandler& handler, PROPERTYID property, VARIANT newValue) { jassert (property != PROPERTYID{}); getProviderWithCheckedWrapper (handler, [property, newValue] (WindowsUIAWrapper* uiaWrapper, ComSmartPtr& provider) { VARIANT oldValue; VariantHelpers::clear (&oldValue); uiaWrapper->raiseAutomationPropertyChangedEvent (provider, property, oldValue, newValue); }); } void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType) { if (eventType == InternalAccessibilityEvent::elementCreated || eventType == InternalAccessibilityEvent::elementDestroyed) { if (auto* parent = handler.getParent()) sendAccessibilityAutomationEvent (*parent, ComTypes::UIA_LayoutInvalidatedEventId); return; } if (eventType == InternalAccessibilityEvent::windowOpened || eventType == InternalAccessibilityEvent::windowClosed) { if (auto* peer = handler.getComponent().getPeer()) if ((peer->getStyleFlags() & ComponentPeer::windowHasTitleBar) == 0) return; } auto event = [eventType]() -> EVENTID { switch (eventType) { case InternalAccessibilityEvent::focusChanged: return ComTypes::UIA_AutomationFocusChangedEventId; case InternalAccessibilityEvent::windowOpened: return ComTypes::UIA_Window_WindowOpenedEventId; case InternalAccessibilityEvent::windowClosed: return ComTypes::UIA_Window_WindowClosedEventId; case InternalAccessibilityEvent::elementCreated: case InternalAccessibilityEvent::elementDestroyed: case InternalAccessibilityEvent::elementMovedOrResized: break; } return {}; }(); if (event != EVENTID{}) sendAccessibilityAutomationEvent (handler, event); } void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const { if (eventType == AccessibilityEvent::titleChanged) { VARIANT newValue; VariantHelpers::setString (getTitle(), &newValue); sendAccessibilityPropertyChangedEvent (*this, UIA_NamePropertyId, newValue); return; } if (eventType == AccessibilityEvent::valueChanged) { if (auto* valueInterface = getValueInterface()) { VARIANT newValue; VariantHelpers::setString (valueInterface->getCurrentValueAsString(), &newValue); sendAccessibilityPropertyChangedEvent (*this, UIA_ValueValuePropertyId, newValue); } return; } auto event = [eventType]() -> EVENTID { switch (eventType) { case AccessibilityEvent::textSelectionChanged: return ComTypes::UIA_Text_TextSelectionChangedEventId; case AccessibilityEvent::textChanged: return ComTypes::UIA_Text_TextChangedEventId; case AccessibilityEvent::structureChanged: return ComTypes::UIA_StructureChangedEventId; case AccessibilityEvent::rowSelectionChanged: return ComTypes::UIA_SelectionItem_ElementSelectedEventId; case AccessibilityEvent::titleChanged: case AccessibilityEvent::valueChanged: break; } return {}; }(); if (event != EVENTID{}) sendAccessibilityAutomationEvent (*this, event); } struct SpVoiceWrapper : public DeletedAtShutdown { SpVoiceWrapper() { auto hr = voice.CoCreateInstance (ComTypes::CLSID_SpVoice); jassertquiet (SUCCEEDED (hr)); } ~SpVoiceWrapper() override { clearSingletonInstance(); } ComSmartPtr voice; JUCE_DECLARE_SINGLETON (SpVoiceWrapper, false) }; JUCE_IMPLEMENT_SINGLETON (SpVoiceWrapper) void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority priority) { if (! areAnyAccessibilityClientsActive()) return; if (auto* sharedVoice = SpVoiceWrapper::getInstance()) { auto voicePriority = [priority] { switch (priority) { case AnnouncementPriority::low: return SPVPRI_OVER; case AnnouncementPriority::medium: return SPVPRI_NORMAL; case AnnouncementPriority::high: return SPVPRI_ALERT; } jassertfalse; return SPVPRI_OVER; }(); sharedVoice->voice->SetPriority (voicePriority); sharedVoice->voice->Speak (announcementString.toWideCharPointer(), SPF_ASYNC, nullptr); } } //============================================================================== namespace WindowsAccessibility { static long getUiaRootObjectId() { return static_cast (UiaRootObjectId); } static bool handleWmGetObject (AccessibilityHandler* handler, WPARAM wParam, LPARAM lParam, LRESULT* res) { if (isStartingUpOrShuttingDown() || (handler == nullptr || ! isHandlerValid (*handler))) return false; if (auto* uiaWrapper = WindowsUIAWrapper::getInstance()) { ComSmartPtr provider; handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress())); if (! uiaWrapper->isProviderDisconnecting (provider)) *res = uiaWrapper->returnRawElementProvider ((HWND) handler->getComponent().getWindowHandle(), wParam, lParam, provider); return true; } return false; } static void revokeUIAMapEntriesForWindow (HWND hwnd) { if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) uiaWrapper->returnRawElementProvider (hwnd, 0, 0, nullptr); } } JUCE_IMPLEMENT_SINGLETON (WindowsUIAWrapper) JUCE_END_IGNORE_WARNINGS_GCC_LIKE } // namespace juce