Accessibility: How to set the app name in a hybrid JUCE/Win32 app?

Hi all,

We have an app that was originally just a pure Windows app (Win32/MFC). We recently added some JUCE views to it, which live alongside the old MFC UI. So it’s a kind of hybrid app, not a JUCE standalone app (it wasn’t created with Projucer).

There’s an issue with accessibility. When the app window is activated/focused, screen readers say “JUCE Application” instead of the actual name of the app!

It seems that the “JUCE Application” string is hard coded in a function called getAccessibleApplicationOrPluginName() which is defined in juce_AccessibilityHandler.cpp:

inline String getAccessibleApplicationOrPluginName()
{
   #if defined (JucePlugin_Name)
    return JucePlugin_Name;
   #else
    if (auto* app = JUCEApplicationBase::getInstance())
        return app->getApplicationName();

    return "JUCE Application";
   #endif
}

And this is returned to screen readers via AccessibilityNativeHandle::getElementName().

It was tempting to just #define JucePlugin_Name "My App", but it’s not that simple because the app consumes JUCE as a static library which is built independetly of the parent app, and is also used in other apps.

Obviously, I could change the JUCE code to get my app name in somehow, but I’d rather not
because it seems like something that JUCE should provide.

Could JUCE be changed to let hybrid, non-Projucer apps such as ours define an app name for accessibility purposes? Or maybe there’s an existing way that I’m missing?

@ed95 I hope you don’t mind me tagging you again, but you seem like the right person to nudge given that it’s accessibility related.

Many thanks,
Ben

getAccessibleApplicationOrPluginName() will be used in the case where the component is on the desktop but doesn’t provide a title via the AccessibilityHandler::getTitle() method since we need to provide names for top-level UI elements to screen reader clients.

You can override AccessibilityHandler::getTitle() or use Component::setTitle() to set the name for your component that is on the desktop and this will be used.

1 Like

Thanks Ed. Unlike a normal JUCE app/plugin, we have several embedded JUCE child windows inside our main non-JUCE app window (a bit like having multiple MainComponents). That kind of threw me a bit there. Luckily I made a base class for our JUCE views a while back, and calling setTitle("App Name") in there does the trick (or even just setAccessible(false) to bypass them altogether, since they’re intermediate windows, not top level app windows).

@ed95 Unfortunately, it’s not working as expected in my hybrid app.

If I call setTitle("App Name") on my JUCE views, screen readers read out the app name twice when I activate the app by clicking in one of them.

This is because I have several JUCE views embedded as child windows inside a non-JUCE app window. When the app is activated, screen readers get the app name from the main app window (not JUCE), which is then followed by the JUCE child window also providing the app name, so screen readers wind up saying it twice!

I tried calling setAccessible(false) on my JUCE child views, but that broke accessibility for child components of the views.

Finally, I tried doing this in the base class of my JUCE child views…

	std::unique_ptr<juce::AccessibilityHandler> createAccessibilityHandler() override
	{
		return createIgnoredAccessibilityHandler (*this);
	}

…but it didn’t make any difference.

As a temporary workaround, I added this check to the top of AccessibilityNativeHandle::getElementName() which does the trick:

if (accessibilityHandler.isIgnored())
    return "";

I suppose I could have done the following instead in the same method:

if (name.isEmpty() && isFragmentRoot() && !accessibilityHandler.isIgnored()) // <-- Added an isIgnored() check here.
    return getAccessibleApplicationOrPluginName();

Obviously, I’d rather avoid messing with JUCE code though (and there may be a better way to do it anyway).

Can you suggest any other workarounds? Is there a way to skip/ignore accessibility in a ‘fragment root’ component?

Edit: Hehe, I even tried setTitle(" ") but the screen reader literally said “Space”!

I think returning an empty string for ignored elements makes sense. I was assuming that an ignored element (which sets the UIA_IsContentElementPropertyId to false) would not be queried by the screen reader for its name but it looks like we need to check it ourselves. I’ll get something for this added shortly.

1 Like

This change is on the develop branch now:

1 Like

Thank you once again, Ed! Much appreciated.