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:
parent
6d10eb536f
commit
269ebbb525
11 changed files with 308 additions and 149 deletions
|
|
@ -35,8 +35,6 @@
|
||||||
namespace juce
|
namespace juce
|
||||||
{
|
{
|
||||||
|
|
||||||
AccessibilityHandler* AccessibilityHandler::currentlyFocusedHandler = nullptr;
|
|
||||||
|
|
||||||
class NativeChildHandler
|
class NativeChildHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -403,10 +401,24 @@ void AccessibilityHandler::setNativeChildForComponent (Component& component, voi
|
||||||
NativeChildHandler::getInstance().setNativeChild (component, nativeChild);
|
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
|
#if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED
|
||||||
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {}
|
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {}
|
||||||
void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {}
|
void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {}
|
||||||
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nullptr; }
|
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nullptr; }
|
||||||
|
bool AccessibilityHandler::areAnyAccessibilityClientsActive() { return false; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace juce
|
} // namespace juce
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,30 @@ public:
|
||||||
*/
|
*/
|
||||||
static void postAnnouncement (const String& announcementString, AnnouncementPriority priority);
|
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 */
|
/** @internal */
|
||||||
AccessibilityNativeHandle* getNativeImplementation() const;
|
AccessibilityNativeHandle* getNativeImplementation() const;
|
||||||
|
|
@ -329,8 +353,9 @@ private:
|
||||||
void grabFocusInternal (bool);
|
void grabFocusInternal (bool);
|
||||||
void giveAwayFocusInternal() const;
|
void giveAwayFocusInternal() const;
|
||||||
void takeFocus();
|
void takeFocus();
|
||||||
|
static bool areAnyAccessibilityClientsActive();
|
||||||
|
|
||||||
static AccessibilityHandler* currentlyFocusedHandler;
|
static inline AccessibilityHandler* currentlyFocusedHandler = nullptr;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
Component& component;
|
Component& component;
|
||||||
|
|
|
||||||
|
|
@ -1055,4 +1055,9 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString,
|
||||||
javaString (announcementString).get());
|
javaString (announcementString).get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AccessibilityHandler::areAnyAccessibilityClientsActive()
|
||||||
|
{
|
||||||
|
return AccessibilityNativeHandle::areAnyAccessibilityClientsActive();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace juce
|
} // namespace juce
|
||||||
|
|
|
||||||
|
|
@ -666,4 +666,9 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, A
|
||||||
sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString));
|
sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AccessibilityHandler::areAnyAccessibilityClientsActive()
|
||||||
|
{
|
||||||
|
return juce::areAnyAccessibilityClientsActive();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace juce
|
} // namespace juce
|
||||||
|
|
|
||||||
|
|
@ -939,4 +939,9 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, A
|
||||||
NSAccessibilityPriorityKey: @(nsPriority) });
|
NSAccessibilityPriorityKey: @(nsPriority) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AccessibilityHandler::areAnyAccessibilityClientsActive()
|
||||||
|
{
|
||||||
|
return juce::areAnyAccessibilityClientsActive();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace juce
|
} // namespace juce
|
||||||
|
|
|
||||||
|
|
@ -39,26 +39,83 @@ namespace juce
|
||||||
|
|
||||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||||
|
|
||||||
static bool isStartingUpOrShuttingDown()
|
//==============================================================================
|
||||||
|
struct WindowsAccessibility
|
||||||
{
|
{
|
||||||
if (auto* app = JUCEApplicationBase::getInstance())
|
WindowsAccessibility() = delete;
|
||||||
if (app->isInitialising())
|
|
||||||
|
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 true;
|
||||||
|
}
|
||||||
|
|
||||||
if (auto* mm = MessageManager::getInstanceWithoutCreating())
|
return false;
|
||||||
if (mm->hasStopMessageBeenSent())
|
}
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
static void revokeUIAMapEntriesForWindow (HWND hwnd)
|
||||||
}
|
{
|
||||||
|
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||||
|
uiaWrapper->returnRawElementProvider (hwnd, 0, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
static bool isHandlerValid (const AccessibilityHandler& handler)
|
static bool isStartingUpOrShuttingDown()
|
||||||
{
|
{
|
||||||
if (auto* provider = handler.getNativeImplementation())
|
if (auto* app = JUCEApplicationBase::getInstance())
|
||||||
return provider->isElementValid();
|
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
|
class AccessibilityHandler::AccessibilityNativeImpl
|
||||||
|
|
@ -103,31 +160,12 @@ AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
|
||||||
return nativeImpl->accessibilityElement;
|
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>
|
template <typename Callback>
|
||||||
void getProviderWithCheckedWrapper (const AccessibilityHandler& handler, Callback&& callback)
|
void getProviderWithCheckedWrapper (const AccessibilityHandler& handler, Callback&& callback)
|
||||||
{
|
{
|
||||||
if (! areAnyAccessibilityClientsActive() || isStartingUpOrShuttingDown() || ! isHandlerValid (handler))
|
if (! WindowsAccessibility::areAnyAccessibilityClientsActive()
|
||||||
|
|| WindowsAccessibility::isStartingUpOrShuttingDown()
|
||||||
|
|| ! WindowsAccessibility::isHandlerValid (handler))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||||
|
|
@ -292,41 +330,11 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, A
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
bool AccessibilityHandler::areAnyAccessibilityClientsActive()
|
||||||
namespace WindowsAccessibility
|
|
||||||
{
|
{
|
||||||
static long getUiaRootObjectId()
|
return WindowsAccessibility::areAnyAccessibilityClientsActive();
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||||
|
|
||||||
} // namespace juce
|
} // namespace juce
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,50 @@ namespace juce
|
||||||
{
|
{
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#if ! JUCE_ANDROID && ! JUCE_IOS && ! JUCE_MAC
|
#if ! JUCE_PUSH_NOTIFICATIONS_IMPL
|
||||||
|
|
||||||
|
struct PushNotifications::Impl
|
||||||
|
{
|
||||||
|
explicit Impl (PushNotifications& o) : owner (o) {}
|
||||||
|
|
||||||
|
void requestPermissionsWithSettings (const Settings&) const
|
||||||
|
{
|
||||||
|
owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void requestSettingsUsed() const
|
||||||
|
{
|
||||||
|
owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool areNotificationsEnabled() const { return false; }
|
||||||
|
void getDeliveredNotifications() const {}
|
||||||
|
void removeAllDeliveredNotifications() const {}
|
||||||
|
String getDeviceToken() const { return {}; }
|
||||||
|
void setupChannels (const Array<ChannelGroup>&, const Array<Channel>&) const {}
|
||||||
|
void getPendingLocalNotifications() const {}
|
||||||
|
void removeAllPendingLocalNotifications() const {}
|
||||||
|
void subscribeToTopic (const String&) const {}
|
||||||
|
void unsubscribeFromTopic (const String&) const {}
|
||||||
|
void sendLocalNotification (const Notification&) const {}
|
||||||
|
void removeDeliveredNotification (const String&) const {}
|
||||||
|
void removePendingLocalNotification (const String&) const {}
|
||||||
|
void sendUpstreamMessage (const String&,
|
||||||
|
const String&,
|
||||||
|
const String&,
|
||||||
|
const String&,
|
||||||
|
int,
|
||||||
|
const StringPairArray&) const {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
PushNotifications& owner;
|
||||||
|
};
|
||||||
|
|
||||||
bool PushNotifications::Notification::isValid() const noexcept { return true; }
|
bool PushNotifications::Notification::isValid() const noexcept { return true; }
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
PushNotifications::Notification::Notification (const Notification& other)
|
PushNotifications::Notification::Notification (const Notification& other)
|
||||||
: identifier (other.identifier),
|
: identifier (other.identifier),
|
||||||
title (other.title),
|
title (other.title),
|
||||||
|
|
@ -82,9 +122,7 @@ PushNotifications::Notification::Notification (const Notification& other)
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
PushNotifications::PushNotifications()
|
PushNotifications::PushNotifications()
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
: pimpl (new Impl (*this))
|
||||||
: pimpl (new Pimpl (*this))
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,128 +131,90 @@ PushNotifications::~PushNotifications() { clearSingletonInstance(); }
|
||||||
void PushNotifications::addListener (Listener* l) { listeners.add (l); }
|
void PushNotifications::addListener (Listener* l) { listeners.add (l); }
|
||||||
void PushNotifications::removeListener (Listener* l) { listeners.remove (l); }
|
void PushNotifications::removeListener (Listener* l) { listeners.remove (l); }
|
||||||
|
|
||||||
void PushNotifications::requestPermissionsWithSettings ([[maybe_unused]] const PushNotifications::Settings& settings)
|
void PushNotifications::requestPermissionsWithSettings (const Settings& settings)
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS && (JUCE_IOS || JUCE_MAC)
|
|
||||||
pimpl->requestPermissionsWithSettings (settings);
|
pimpl->requestPermissionsWithSettings (settings);
|
||||||
#else
|
|
||||||
listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushNotifications::requestSettingsUsed()
|
void PushNotifications::requestSettingsUsed()
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS && (JUCE_IOS || JUCE_MAC)
|
|
||||||
pimpl->requestSettingsUsed();
|
pimpl->requestSettingsUsed();
|
||||||
#else
|
|
||||||
listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PushNotifications::areNotificationsEnabled() const
|
bool PushNotifications::areNotificationsEnabled() const
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
return pimpl->areNotificationsEnabled();
|
return pimpl->areNotificationsEnabled();
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushNotifications::getDeliveredNotifications() const
|
void PushNotifications::getDeliveredNotifications() const
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
pimpl->getDeliveredNotifications();
|
pimpl->getDeliveredNotifications();
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushNotifications::removeAllDeliveredNotifications()
|
void PushNotifications::removeAllDeliveredNotifications()
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
pimpl->removeAllDeliveredNotifications();
|
pimpl->removeAllDeliveredNotifications();
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String PushNotifications::getDeviceToken() const
|
String PushNotifications::getDeviceToken() const
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
return pimpl->getDeviceToken();
|
return pimpl->getDeviceToken();
|
||||||
#else
|
|
||||||
return {};
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushNotifications::setupChannels ([[maybe_unused]] const Array<ChannelGroup>& groups, [[maybe_unused]] const Array<Channel>& channels)
|
void PushNotifications::setupChannels (const Array<ChannelGroup>& groups,
|
||||||
|
const Array<Channel>& channels)
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
pimpl->setupChannels (groups, channels);
|
pimpl->setupChannels (groups, channels);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushNotifications::getPendingLocalNotifications() const
|
void PushNotifications::getPendingLocalNotifications() const
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
pimpl->getPendingLocalNotifications();
|
pimpl->getPendingLocalNotifications();
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushNotifications::removeAllPendingLocalNotifications()
|
void PushNotifications::removeAllPendingLocalNotifications()
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
pimpl->removeAllPendingLocalNotifications();
|
pimpl->removeAllPendingLocalNotifications();
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushNotifications::subscribeToTopic ([[maybe_unused]] const String& topic)
|
void PushNotifications::subscribeToTopic (const String& topic)
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
pimpl->subscribeToTopic (topic);
|
pimpl->subscribeToTopic (topic);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushNotifications::unsubscribeFromTopic ([[maybe_unused]] const String& topic)
|
void PushNotifications::unsubscribeFromTopic (const String& topic)
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
pimpl->unsubscribeFromTopic (topic);
|
pimpl->unsubscribeFromTopic (topic);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PushNotifications::sendLocalNotification (const Notification& n)
|
||||||
void PushNotifications::sendLocalNotification ([[maybe_unused]] const Notification& n)
|
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
pimpl->sendLocalNotification (n);
|
pimpl->sendLocalNotification (n);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushNotifications::removeDeliveredNotification ([[maybe_unused]] const String& identifier)
|
void PushNotifications::removeDeliveredNotification (const String& identifier)
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
pimpl->removeDeliveredNotification (identifier);
|
pimpl->removeDeliveredNotification (identifier);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushNotifications::removePendingLocalNotification ([[maybe_unused]] const String& identifier)
|
void PushNotifications::removePendingLocalNotification (const String& identifier)
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
pimpl->removePendingLocalNotification (identifier);
|
pimpl->removePendingLocalNotification (identifier);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushNotifications::sendUpstreamMessage ([[maybe_unused]] const String& serverSenderId,
|
void PushNotifications::sendUpstreamMessage (const String& serverSenderId,
|
||||||
[[maybe_unused]] const String& collapseKey,
|
const String& collapseKey,
|
||||||
[[maybe_unused]] const String& messageId,
|
const String& messageId,
|
||||||
[[maybe_unused]] const String& messageType,
|
const String& messageType,
|
||||||
[[maybe_unused]] int timeToLive,
|
int timeToLive,
|
||||||
[[maybe_unused]] const StringPairArray& additionalData)
|
const StringPairArray& additionalData)
|
||||||
{
|
{
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
|
||||||
pimpl->sendUpstreamMessage (serverSenderId,
|
pimpl->sendUpstreamMessage (serverSenderId,
|
||||||
collapseKey,
|
collapseKey,
|
||||||
messageId,
|
messageId,
|
||||||
messageType,
|
messageType,
|
||||||
timeToLive,
|
timeToLive,
|
||||||
additionalData);
|
additionalData);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -234,4 +234,92 @@ void PushNotifications::Listener::upstreamMessageSent ([[maybe_unused]] const St
|
||||||
void PushNotifications::Listener::upstreamMessageSendingError ([[maybe_unused]] const String& messageId,
|
void PushNotifications::Listener::upstreamMessageSendingError ([[maybe_unused]] const String& messageId,
|
||||||
[[maybe_unused]] const String& error) {}
|
[[maybe_unused]] const String& error) {}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void privatePostSystemNotification (const String& notificationTitle, const String& notificationBody);
|
||||||
|
void privatePostSystemNotification ([[maybe_unused]] const String& notificationTitle,
|
||||||
|
[[maybe_unused]] const String& notificationBody)
|
||||||
|
{
|
||||||
|
#if JUCE_PUSH_NOTIFICATIONS
|
||||||
|
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||||
|
auto* notificationsInstance = PushNotifications::getInstance();
|
||||||
|
|
||||||
|
if (notificationsInstance == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#if JUCE_ANDROID
|
||||||
|
notificationsInstance->requestPermissionsWithSettings ({});
|
||||||
|
|
||||||
|
static auto channels = std::invoke ([]() -> Array<PushNotifications::Channel>
|
||||||
|
{
|
||||||
|
PushNotifications::Channel chan;
|
||||||
|
|
||||||
|
chan.identifier = "1";
|
||||||
|
chan.name = "Notifications";
|
||||||
|
chan.description = "Accessibility notifications";
|
||||||
|
chan.groupId = "accessibility";
|
||||||
|
chan.ledColour = Colours::yellow;
|
||||||
|
chan.canShowBadge = true;
|
||||||
|
chan.enableLights = true;
|
||||||
|
chan.enableVibration = true;
|
||||||
|
chan.soundToPlay = URL ("default_os_sound");
|
||||||
|
chan.vibrationPattern = { 1000, 1000 };
|
||||||
|
|
||||||
|
return { chan };
|
||||||
|
});
|
||||||
|
|
||||||
|
notificationsInstance->setupChannels ({ PushNotifications::ChannelGroup { "accessibility", "accessibility" } },
|
||||||
|
channels);
|
||||||
|
#else
|
||||||
|
static auto settings = std::invoke ([]
|
||||||
|
{
|
||||||
|
PushNotifications::Settings s;
|
||||||
|
s.allowAlert = true;
|
||||||
|
s.allowBadge = true;
|
||||||
|
s.allowSound = true;
|
||||||
|
|
||||||
|
#if JUCE_IOS
|
||||||
|
PushNotifications::Settings::Category c;
|
||||||
|
c.identifier = "Accessibility";
|
||||||
|
|
||||||
|
s.categories = { c };
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
|
||||||
|
notificationsInstance->requestPermissionsWithSettings (settings);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const auto notification = std::invoke ([¬ificationTitle, ¬ificationBody]
|
||||||
|
{
|
||||||
|
PushNotifications::Notification n;
|
||||||
|
|
||||||
|
n.identifier = String (Random::getSystemRandom().nextInt());
|
||||||
|
n.title = notificationTitle;
|
||||||
|
n.body = notificationBody;
|
||||||
|
|
||||||
|
#if JUCE_IOS
|
||||||
|
n.category = "Accessibility";
|
||||||
|
#elif JUCE_ANDROID
|
||||||
|
n.channelId = "1";
|
||||||
|
n.icon = "accessibilitynotificationicon";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (notification.isValid())
|
||||||
|
notificationsInstance->sendLocalNotification (notification);
|
||||||
|
|
||||||
|
#else
|
||||||
|
SystemTrayIconComponent systemTrayIcon;
|
||||||
|
|
||||||
|
Image im (Image::ARGB, 128, 128, true);
|
||||||
|
systemTrayIcon.setIconImage (im, im);
|
||||||
|
|
||||||
|
systemTrayIcon.showInfoBubble (notificationTitle, notificationBody);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace juce
|
} // namespace juce
|
||||||
|
|
|
||||||
|
|
@ -403,8 +403,8 @@ public:
|
||||||
*/
|
*/
|
||||||
struct Category
|
struct Category
|
||||||
{
|
{
|
||||||
juce::String identifier; /**< unique identifier */
|
String identifier; /**< unique identifier */
|
||||||
juce::Array<Action> actions; /**< optional list of actions within this category */
|
Array<Action> actions; /**< optional list of actions within this category */
|
||||||
bool sendDismissAction = false; /**< whether dismiss action will be sent to the app */
|
bool sendDismissAction = false; /**< whether dismiss action will be sent to the app */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -703,12 +703,8 @@ private:
|
||||||
friend struct JuceFirebaseMessagingService;
|
friend struct JuceFirebaseMessagingService;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if JUCE_PUSH_NOTIFICATIONS
|
struct Impl;
|
||||||
struct Pimpl;
|
std::unique_ptr<Impl> pimpl;
|
||||||
friend struct Pimpl;
|
|
||||||
|
|
||||||
std::unique_ptr<Pimpl> pimpl;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace juce
|
} // namespace juce
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@
|
||||||
namespace juce
|
namespace juce
|
||||||
{
|
{
|
||||||
|
|
||||||
|
#define JUCE_PUSH_NOTIFICATIONS_IMPL 1
|
||||||
|
|
||||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||||
METHOD (constructor, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \
|
METHOD (constructor, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \
|
||||||
METHOD (enableLights, "enableLights", "(Z)V") \
|
METHOD (enableLights, "enableLights", "(Z)V") \
|
||||||
|
|
@ -274,9 +276,9 @@ bool PushNotifications::Notification::isValid() const noexcept
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
struct PushNotifications::Pimpl
|
struct PushNotifications::Impl
|
||||||
{
|
{
|
||||||
explicit Pimpl (PushNotifications& p)
|
explicit Impl (PushNotifications& p)
|
||||||
: owner (p)
|
: owner (p)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
|
@ -306,7 +308,7 @@ struct PushNotifications::Pimpl
|
||||||
const auto notifyListeners = []
|
const auto notifyListeners = []
|
||||||
{
|
{
|
||||||
if (auto* instance = PushNotifications::getInstance())
|
if (auto* instance = PushNotifications::getInstance())
|
||||||
instance->listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
|
instance->listeners.call ([] (Listener& l) { l.notificationSettingsReceived (makeDefaultSettings()); });
|
||||||
};
|
};
|
||||||
|
|
||||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||||
|
|
@ -318,7 +320,7 @@ struct PushNotifications::Pimpl
|
||||||
|
|
||||||
void requestSettingsUsed()
|
void requestSettingsUsed()
|
||||||
{
|
{
|
||||||
owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
|
owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived (makeDefaultSettings()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendLocalNotification (const Notification& n)
|
void sendLocalNotification (const Notification& n)
|
||||||
|
|
@ -1573,6 +1575,15 @@ struct PushNotifications::Pimpl
|
||||||
&& env->CallBooleanMethod (extras, AndroidBundle.containsKey, javaString ("google.message_id").get());
|
&& env->CallBooleanMethod (extras, AndroidBundle.containsKey, javaString ("google.message_id").get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Settings makeDefaultSettings()
|
||||||
|
{
|
||||||
|
Settings settings;
|
||||||
|
settings.allowAlert = true;
|
||||||
|
settings.allowBadge = true;
|
||||||
|
settings.allowSound = true;
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
PushNotifications& owner;
|
PushNotifications& owner;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1640,14 +1651,14 @@ bool juce_handleNotificationIntent (void* intent)
|
||||||
{
|
{
|
||||||
auto* instance = PushNotifications::getInstanceWithoutCreating();
|
auto* instance = PushNotifications::getInstanceWithoutCreating();
|
||||||
|
|
||||||
if (PushNotifications::Pimpl::isDeleteNotificationIntent ((jobject) intent))
|
if (PushNotifications::Impl::isDeleteNotificationIntent ((jobject) intent))
|
||||||
{
|
{
|
||||||
if (instance)
|
if (instance)
|
||||||
instance->pimpl->notifyListenersAboutLocalNotificationDeleted (LocalRef<jobject> ((jobject) intent));
|
instance->pimpl->notifyListenersAboutLocalNotificationDeleted (LocalRef<jobject> ((jobject) intent));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (PushNotifications::Pimpl::isLocalNotificationIntent ((jobject) intent))
|
else if (PushNotifications::Impl::isLocalNotificationIntent ((jobject) intent))
|
||||||
{
|
{
|
||||||
if (instance)
|
if (instance)
|
||||||
instance->pimpl->notifyListenersAboutLocalNotification (LocalRef<jobject> ((jobject) intent));
|
instance->pimpl->notifyListenersAboutLocalNotification (LocalRef<jobject> ((jobject) intent));
|
||||||
|
|
@ -1655,7 +1666,7 @@ bool juce_handleNotificationIntent (void* intent)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
|
#if defined (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME)
|
||||||
else if (PushNotifications::Pimpl::isRemoteNotificationIntent ((jobject) intent))
|
else if (PushNotifications::Impl::isRemoteNotificationIntent ((jobject) intent))
|
||||||
{
|
{
|
||||||
if (instance)
|
if (instance)
|
||||||
instance->pimpl->notifyListenersAboutRemoteNotificationFromSystemTray (LocalRef<jobject> ((jobject) intent));
|
instance->pimpl->notifyListenersAboutRemoteNotificationFromSystemTray (LocalRef<jobject> ((jobject) intent));
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@
|
||||||
namespace juce
|
namespace juce
|
||||||
{
|
{
|
||||||
|
|
||||||
|
#define JUCE_PUSH_NOTIFICATIONS_IMPL 1
|
||||||
|
|
||||||
struct PushNotificationsDelegateDetails
|
struct PushNotificationsDelegateDetails
|
||||||
{
|
{
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -301,9 +303,9 @@ bool PushNotifications::Notification::isValid() const noexcept
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
struct PushNotifications::Pimpl
|
struct PushNotifications::Impl
|
||||||
{
|
{
|
||||||
Pimpl (PushNotifications& p)
|
explicit Impl (PushNotifications& p)
|
||||||
: owner (p)
|
: owner (p)
|
||||||
{
|
{
|
||||||
Class::setThis (delegate.get(), this);
|
Class::setThis (delegate.get(), this);
|
||||||
|
|
@ -555,7 +557,7 @@ private:
|
||||||
Class()
|
Class()
|
||||||
: ObjCClass ("JucePushNotificationsDelegate_")
|
: ObjCClass ("JucePushNotificationsDelegate_")
|
||||||
{
|
{
|
||||||
addIvar<Pimpl*> ("self");
|
addIvar<Impl*> ("self");
|
||||||
|
|
||||||
addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), [] (id self, SEL, UIApplication*, NSData* data)
|
addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), [] (id self, SEL, UIApplication*, NSData* data)
|
||||||
{
|
{
|
||||||
|
|
@ -596,8 +598,8 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
static Pimpl& getThis (id self) { return *getIvar<Pimpl*> (self, "self"); }
|
static Impl& getThis (id self) { return *getIvar<Impl*> (self, "self"); }
|
||||||
static void setThis (id self, Pimpl* d) { object_setInstanceVariable (self, "self", d); }
|
static void setThis (id self, Impl* d) { object_setInstanceVariable (self, "self", d); }
|
||||||
};
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@
|
||||||
namespace juce
|
namespace juce
|
||||||
{
|
{
|
||||||
|
|
||||||
|
#define JUCE_PUSH_NOTIFICATIONS_IMPL 1
|
||||||
|
|
||||||
JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS
|
JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS
|
||||||
|
|
||||||
namespace PushNotificationsDelegateDetailsOsx
|
namespace PushNotificationsDelegateDetailsOsx
|
||||||
|
|
@ -343,9 +345,9 @@ private:
|
||||||
bool PushNotifications::Notification::isValid() const noexcept { return true; }
|
bool PushNotifications::Notification::isValid() const noexcept { return true; }
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
struct PushNotifications::Pimpl : private PushNotificationsDelegate
|
struct PushNotifications::Impl : private PushNotificationsDelegate
|
||||||
{
|
{
|
||||||
Pimpl (PushNotifications& p)
|
explicit Impl (PushNotifications& p)
|
||||||
: owner (p)
|
: owner (p)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue