Several accessibility issues on Windows

Hi all,

I tested our app with JAWS today, which I’m told is the most popular screen reader app. It works as expected with the new JUCE 6.1 accessibility features (which are great, btw), but there’s a problem.

When a button is focused, JAWS insists on telling users “To activate press spacebar”. That’s fine in a normal Windows dialog box, but space doesn’t activate JUCE buttons!

The Microsoft “Guidelines for Keyboard User Interface Design” state:

In Windows applications, users navigate by pressing the TAB key to move the input focus from one UI element to another. They press the SPACEBAR or ENTER key to choose the currently selected active region or to activate a control or command.

https://docs.microsoft.com/en-us/previous-versions/windows/desktop/dnacc/guidelines-for-keyboard-user-interface-design

It seems like something that should be fixed on the JUCE side. Is there anything that can be done about it?

Many thanks,
Ben

3 Likes

More issues. Decided to post everything here, but happy to break out into separate threads if necessary. All of the following occurs on Windows. Haven’t tested on Mac yet. I’m mostly using the free NVDA screen reader since JAWS only has a 40 minute free trial.

Issue #2
AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent::valueChanged) is currently a no-op in juce_win32_Accessibility.cpp. It’s not possible to notify accessibility clients that a UI elements value has changed. Adding the following code gets it working:

if (eventType == AccessibilityEvent::valueChanged)
{
    //jassert (interfaces.value != nullptr); // Don't assert. See issue #4 below.
    if (interfaces.value != nullptr)
    {
        VARIANT newValue;
        VariantHelpers::setString (interfaces.value->getCurrentValueAsString(), &newValue);

        sendAccessibilityPropertyChangedEvent (*this, UIA_ValueValuePropertyId, newValue);
    }
}

Issue 3
juce::ComboBox doesn’t notify accessibility clients when the selected item changes via keyboard (up/down/left/right). As a result, screen readers don’t inform the user what the new value is. Adding the following code to the bottom of juce::ComboBox::setSelectedId() gets it working (assuming issue 2 is fixed as above):

    if (auto handler = getAccessibilityHandler())
        handler->notifyAccessibilityEvent (AccessibilityEvent::valueChanged);

Edit: I forgot I also had to make ComboBoxAccessibilityHandler implement AccessibilityTextValueInterface.

Issue #4
juce::Button::setToggledState() calls notifyAccessibilityEvent (AccessibilityEvent::valueChanged), but ButtonAccessibilityHandler doesn’t implement AccessibilityValueInterface or AccessibilityTextValueInterface, so it’s effectively a no-op. Seems like an oversight.

Wish I’d had time to run through this during the JUCE 6.1 beta phase (unfortunately, other projects took precedence), but it’s otherwise a pretty solid first version. I’ll keep testing and report back here.

Tagging @ed95 Ed Davies for info.

Thanks! These all seem like good suggestions, I’m testing them at the moment and will hopefully get something onto develop soon.

I’m hesitant to change the default key trigger behaviour for all JUCE buttons though as it will be a silent change that may cause issues in unaware JUCE apps. There is a Button::addShortcut() method if you want to add this behaviour for your buttons though, so you can do the following:

myButton.addShortcut (juce::KeyPress (juce::KeyPress::spaceKey));

and it will trigger on space key presses. Would that work for you?

Thanks for the quick response, Ed.

I totally understand your reluctance to change default key trigger behaviour. It would be risky to do so given all the JUCE apps and plugins that are already out there.

That said, calling addShortcut() on every button in the app would be a major pain, and easy to forget going forward.

Would adding some kind of opt-in mechanism be possible? Maybe a static Button method or even a #define of some kind so it can be enabled app wide in one fell swoop.

Thanks again!
Ben

As a workaround, you could derive your own classes from the Button types that you use (such as ImageButton or whatever), make your buttons use those derived classes instead, and call addShortcut() from their constructors. We do something similar to implement AAX highlighting of our controls.

Thanks for the suggestions. There are indeed ways to work around it, but I’m more concerned with not having to work around it, if you see what I mean.

I’m not blocked by this. Just highlighting something that I think warrants a JUCE library tweak.

I’d be hesitant to add something like that to the library since it can be done quite easily with a free function in user code with something like the following:

static juce::Button& makeAccessibleButton (juce::Button& buttonIn)
{
    buttonIn.addShortcut (juce::KeyPress (juce::KeyPress::spaceKey));
    return buttonIn;
}

...

addAndMakeVisible (makeAccessibleButton (myButton));

I’ve pushed some fixes to develop, please give them a test and let me know if you are still seeing issues with JAWS/NVDA:

Thank you Ed! Will do some testing today and get back to you.

The fixes seem to be working well so far @ed95. Thanks again.

1 Like

@ed95
Unfortunately, some of the component accessibility classes are awkward to re-use in custom components that inherit from one of the JUCE components.

For example, we have our own Button class which inherits from juce::Button. We need to make it assume different accessibility roles (normal, toggle, etc) depending on how it’s being used.

Under normal circumstances, I would just do something like this:

std::unique_ptr MyCustomButton::createAccessibilityHandler() override
{
return std::make_unique<juce::ButtonAccessibilityHandler> (*this, AccessibilityRole::whatever);
}

But I can’t access juce::ButtonAccessibilityHandler in my code because it’s defined in juce_Button.cpp (not juce_Button.h). This pattern is used elsewhere in JUCE too.

So I’m forced to duplicate the entire juce::ButtonAccessibilityHandler class, which isn’t an insignificant amount of code. And obviously my duplicate version won’t get automatically updated should you guys decide to change it in future.

Can you suggest any workarounds? If not, is there any chance the accessibility handler classes could be moved into component header files instead?

Thanks again,
Ben

1 Like