1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Accessibility: Add AccessibilityHandler::postSystemNotification() function for posting an OS-specific accessible notification

This commit is contained in:
reuk 2024-11-21 21:26:55 +00:00
parent 6d10eb536f
commit 269ebbb525
No known key found for this signature in database
11 changed files with 308 additions and 149 deletions

View file

@ -35,8 +35,6 @@
namespace juce
{
AccessibilityHandler* AccessibilityHandler::currentlyFocusedHandler = nullptr;
class NativeChildHandler
{
public:
@ -403,10 +401,24 @@ void AccessibilityHandler::setNativeChildForComponent (Component& component, voi
NativeChildHandler::getInstance().setNativeChild (component, nativeChild);
}
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
void privatePostSystemNotification (const String&, const String&);
#endif
void AccessibilityHandler::postSystemNotification ([[maybe_unused]] const String& notificationTitle,
[[maybe_unused]] const String& notificationBody)
{
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
if (areAnyAccessibilityClientsActive())
privatePostSystemNotification (notificationTitle, notificationBody);
#endif
}
#if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {}
void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {}
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nullptr; }
bool AccessibilityHandler::areAnyAccessibilityClientsActive() { return false; }
#endif
} // namespace juce

View file

@ -295,6 +295,30 @@ public:
*/
static void postAnnouncement (const String& announcementString, AnnouncementPriority priority);
/** Posts a local system notification.
In order for this to do anything, the following conditions must be met.
- At build time:
- The juce_gui_extra module must be included in the project.
- Push notifications must be enabled by setting the preprocessor definition
JUCE_PUSH_NOTIFICATIONS=1
- At run time:
- An accessibility client (narrator, voiceover etc.) must be active.
Additionally, on Android, an icon is required for notifications.
This must be specified by adding the path to the icon file called
"accessibilitynotificationicon" in the "Extra Android Raw Resources" setting
in the Projucer.
This will use the push notification client on macOS, iOS and Android.
On Windows this will create a system tray icon to post the notification.
@param notificationTitle the title of the notification
@param notificationBody the main body text of the notification
*/
static void postSystemNotification (const String& notificationTitle,
const String& notificationBody);
//==============================================================================
/** @internal */
AccessibilityNativeHandle* getNativeImplementation() const;
@ -329,8 +353,9 @@ private:
void grabFocusInternal (bool);
void giveAwayFocusInternal() const;
void takeFocus();
static bool areAnyAccessibilityClientsActive();
static AccessibilityHandler* currentlyFocusedHandler;
static inline AccessibilityHandler* currentlyFocusedHandler = nullptr;
//==============================================================================
Component& component;

View file

@ -1055,4 +1055,9 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString,
javaString (announcementString).get());
}
bool AccessibilityHandler::areAnyAccessibilityClientsActive()
{
return AccessibilityNativeHandle::areAnyAccessibilityClientsActive();
}
} // namespace juce

View file

@ -666,4 +666,9 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, A
sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString));
}
bool AccessibilityHandler::areAnyAccessibilityClientsActive()
{
return juce::areAnyAccessibilityClientsActive();
}
} // namespace juce

View file

@ -939,4 +939,9 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, A
NSAccessibilityPriorityKey: @(nsPriority) });
}
bool AccessibilityHandler::areAnyAccessibilityClientsActive()
{
return juce::areAnyAccessibilityClientsActive();
}
} // namespace juce

View file

@ -39,26 +39,83 @@ namespace juce
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
static bool isStartingUpOrShuttingDown()
//==============================================================================
struct WindowsAccessibility
{
if (auto* app = JUCEApplicationBase::getInstance())
if (app->isInitialising())
WindowsAccessibility() = delete;
static long getUiaRootObjectId()
{
return static_cast<long> (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<IRawElementProviderSimple> 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;
}
if (auto* mm = MessageManager::getInstanceWithoutCreating())
if (mm->hasStopMessageBeenSent())
return true;
return false;
}
return false;
}
static void revokeUIAMapEntriesForWindow (HWND hwnd)
{
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
uiaWrapper->returnRawElementProvider (hwnd, 0, 0, nullptr);
}
static bool isHandlerValid (const AccessibilityHandler& handler)
{
if (auto* provider = handler.getNativeImplementation())
return provider->isElementValid();
static bool isStartingUpOrShuttingDown()
{
if (auto* app = JUCEApplicationBase::getInstance())
if (app->isInitialising())
return true;
return false;
}
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;
}
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();
}
};
//==============================================================================
class AccessibilityHandler::AccessibilityNativeImpl
@ -103,31 +160,12 @@ 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 <typename Callback>
void getProviderWithCheckedWrapper (const AccessibilityHandler& handler, Callback&& callback)
{
if (! areAnyAccessibilityClientsActive() || isStartingUpOrShuttingDown() || ! isHandlerValid (handler))
if (! WindowsAccessibility::areAnyAccessibilityClientsActive()
|| WindowsAccessibility::isStartingUpOrShuttingDown()
|| ! WindowsAccessibility::isHandlerValid (handler))
return;
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
@ -292,41 +330,11 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, A
}
}
//==============================================================================
namespace WindowsAccessibility
bool AccessibilityHandler::areAnyAccessibilityClientsActive()
{
static long getUiaRootObjectId()
{
return static_cast<long> (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<IRawElementProviderSimple> 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);
}
return WindowsAccessibility::areAnyAccessibilityClientsActive();
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
} // namespace juce