Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wpf] Add UIA events #14097

Merged
7 commits merged into from
Oct 6, 2022
Merged

[wpf] Add UIA events #14097

7 commits merged into from
Oct 6, 2022

Conversation

carlos-zamora
Copy link
Member

@carlos-zamora carlos-zamora commented Sep 28, 2022

Adds UIA events to the WPF control for the following items:

  • selection changed
  • text changed (and output)
  • cursor changed

Automation Peer

Similar to the architecture of the UWP TermControl, we added a
HwndTerminalAutomationPeer which acts as the
TermControlAutomationPeer in UWP. However, we don't need a XAML
wrapper here, so really we just need it to inherit from
TermControlUiaProvider (the ITextProvider implementation shared
across conhost and WT) and IUiaEventDispatcher (the event dispatching
interface that is responsible for signaling the screen reader that
something has changed).

Removing the local echo

As with WT, we need to record key events to remove the local echo. These
recorded events are matched up with the output text. Each sequential
match is removed in the output text so that it's not read by the screen
reader.

Detecting what to send events for

As with WT, a UiaEngine was added to the renderer and it is set up
when a UIA client is detected. WT would normally stop sending events
when focus was lost from the control. We do the same here.

Automation properties

TermControlUiaProvider was upgraded to support property values. Such
properties include class name and control type. These align with those
set in TermControlAutomationPeer. Realistically, those should point to
these, but that requires a lot more work and a localization burden
(because we need to move the localized word "terminal").

HwndTerminalAutomationPeer takes this a step further and overrides the
class name to be WPFTermControl. This allows screen readers to provide
special handling for the WPFTermControl vs the UWP term control since
they will be updating at different speeds.

Build fixes

To build the WPF test app, I had to mess with the dependencies a little
bit. Really just add the atlas engine and uia renderer to the build
steps.

HwndTerminal initialization

The initialization order with WM_NCCREATE was changed to match that of
Windows Terminal (BaseWindow/IslandWindow). This is safer now. I also
removed the static window because it was unnecessary.

Handling WM_GETOBJECT

WPF's HwndHost likes to mark the WM_GETOBJECT message as handled to
force the usage of the WPF automation peer. We now explicitly mark it as
not handled and don't return an automation peer. This forces the message
to go down to the HwndTerminal where we return terminal's UiaProvider.

Remove TermControl layer from UIA tree

TermContol (the top-most layer in the UIA tree) would pop up and not do
anything. This PR also overrides the automation peer at that layer and
marks IsContentElement/IsControlElement=false (the equivalent to
AccessibilityView=Raw). This makes the layer only appear in the UIA tree
if you are using the raw view (i.e. you know what you're doing and you
want to see each individual layer even if you can't directly interact
with it).

Validation Steps Performed

Tested with Narrator/NVDA using WpfTerminalTestNetCore project in our
repo.

  • New output is read out (not just key events, but also other output
    text)
  • Local echo does not occur (i.e. pressing 'A' should only read 'A'
    once, not twice [key event and rendered letter]).
  • selection events are read out properly
  • cursor change events are read out properly (tested with text
    cursor indicator preview in Settings App > Accessibility > Text
    Cursor)

NOTE: test this with Release builds. Debug builds may be too slow and
not read out properly

Closes #12642

@ghost ghost added Area-Accessibility Issues related to accessibility Area-WPFControl Things related to the WPF version of the TermControl Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-1 A description (P1) Product-Terminal The new Windows Terminal. labels Sep 28, 2022
@codeofdusk
Copy link
Contributor

Does this new control use the same UIA class name? How can I test it?

- remove extra static window
- improve window initialization order
- pass WM_GETOBJECT to HwndTerminal
- set accessibility view to raw on TermControl
- rename UIA class name to WPFTermControl
- fix build for WpfTerminalTestNetCore (Debug and Release)
if (WM_NCCREATE == uMsg)
{
#pragma warning(suppress : 26490) // Win32 APIs can only store void*, have to use reinterpret_cast
auto cs = reinterpret_cast<CREATESTRUCT*>(lParam);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW you can now use std::bit_cast for this. But it's not particularly important.

Comment on lines +133 to +136
// the key event's character (i.e. the "A" key) matches
// the output character (i.e. "a" or "A" text).
// We can assume that the output character resulted from
// the pressed key, so we can ignore it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no way this works with Unicode, right? ._.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not, but that should be fine. We're really just using this to suppress key events. So, when you press the A key, normally you'd hear "A" from pressing the key and "A" from rendering the key. If we suppress that second event, it sounds/feels more natural.

src/types/TermControlUiaProvider.cpp Outdated Show resolved Hide resolved
@DHowett DHowett added this to To Cherry Pick in 1.16 Servicing Pipeline via automation Oct 6, 2022
@DHowett DHowett added this to To Cherry Pick in 1.15 Servicing Pipeline via automation Oct 6, 2022
Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

memory questions!

@ghost ghost added Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something and removed Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something labels Oct 6, 2022
@DHowett DHowett added the AutoMerge Marked for automatic merge by the bot when requirements are met label Oct 6, 2022
@ghost
Copy link

ghost commented Oct 6, 2022

Hello @DHowett!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit 5608cf1 into main Oct 6, 2022
@ghost ghost deleted the dev/cazamor/wpf/uia-events branch October 6, 2022 23:11
@DHowett DHowett moved this from To Cherry Pick to Cherry Picked in 1.15 Servicing Pipeline Oct 6, 2022
DHowett pushed a commit that referenced this pull request Oct 6, 2022
Adds UIA events to the WPF control for the following items:
- selection changed
- text changed (and output)
- cursor changed

Similar to the architecture of the UWP TermControl, we added a
`HwndTerminalAutomationPeer` which acts as the
`TermControlAutomationPeer` in UWP. However, we don't need a XAML
wrapper here, so really we just need it to inherit from
`TermControlUiaProvider` (the `ITextProvider` implementation shared
across conhost and WT) and `IUiaEventDispatcher` (the event dispatching
interface that is responsible for signaling the screen reader that
something has changed).

As with WT, we need to record key events to remove the local echo. These
recorded events are matched up with the output text. Each sequential
match is removed in the output text so that it's not read by the screen
reader.

As with WT, a `UiaEngine` was added to the renderer and it is set up
when a UIA client is detected. WT would normally stop sending events
when focus was lost from the control. We do the same here.

`TermControlUiaProvider` was upgraded to support property values. Such
properties include class name and control type. These align with those
set in `TermControlAutomationPeer`. Realistically, those should point to
these, but that requires a lot more work and a localization burden
(because we need to move the localized word "terminal").

`HwndTerminalAutomationPeer` takes this a step further and overrides the
class name to be `WPFTermControl`. This allows screen readers to provide
special handling for the `WPFTermControl` vs the UWP term control since
they will be updating at different speeds.

To build the WPF test app, I had to mess with the dependencies a little
bit. Really just add the atlas engine and uia renderer to the build
steps.

The initialization order with `WM_NCCREATE` was changed to match that of
Windows Terminal (BaseWindow/IslandWindow). This is safer now. I also
removed the `static` window because it was unnecessary.

WPF's HwndHost likes to mark the `WM_GETOBJECT` message as handled to
force the usage of the WPF automation peer. We now explicitly mark it as
not handled and don't return an automation peer. This forces the message
to go down to the HwndTerminal where we return terminal's UiaProvider.

TermContol (the top-most layer in the UIA tree) would pop up and not do
anything. This PR also overrides the automation peer at that layer and
marks IsContentElement/IsControlElement=false (the equivalent to
AccessibilityView=Raw). This makes the layer only appear in the UIA tree
if you are using the raw view (i.e. you know what you're doing and you
want to see each individual layer even if you can't directly interact
with it).

Tested with Narrator/NVDA using WpfTerminalTestNetCore project in our
repo.
- [X] New output is read out (not just key events, but also other output
  text)
- [X] Local echo does not occur (i.e. pressing 'A' should only read 'A'
  once, not twice [key event and rendered letter]).
- [X] selection events are read out properly
- [X] cursor change events are read out properly (tested with text
  cursor indicator preview in Settings App > Accessibility > Text
  Cursor)

NOTE: test this with Release builds. Debug builds may be too slow and
not read out properly

Closes #12642

(cherry picked from commit 5608cf1)
Service-Card-Id: 86081128
Service-Version: 1.15
@DHowett DHowett moved this from To Cherry Pick to Cherry Picked in 1.16 Servicing Pipeline Oct 6, 2022
DHowett pushed a commit that referenced this pull request Oct 6, 2022
Adds UIA events to the WPF control for the following items:
- selection changed
- text changed (and output)
- cursor changed

### Automation Peer
Similar to the architecture of the UWP TermControl, we added a
`HwndTerminalAutomationPeer` which acts as the
`TermControlAutomationPeer` in UWP. However, we don't need a XAML
wrapper here, so really we just need it to inherit from
`TermControlUiaProvider` (the `ITextProvider` implementation shared
across conhost and WT) and `IUiaEventDispatcher` (the event dispatching
interface that is responsible for signaling the screen reader that
something has changed).

### Removing the local echo
As with WT, we need to record key events to remove the local echo. These
recorded events are matched up with the output text. Each sequential
match is removed in the output text so that it's not read by the screen
reader.

### Detecting what to send events for
As with WT, a `UiaEngine` was added to the renderer and it is set up
when a UIA client is detected. WT would normally stop sending events
when focus was lost from the control. We do the same here.

### Automation properties
`TermControlUiaProvider` was upgraded to support property values. Such
properties include class name and control type. These align with those
set in `TermControlAutomationPeer`. Realistically, those should point to
these, but that requires a lot more work and a localization burden
(because we need to move the localized word "terminal").

`HwndTerminalAutomationPeer` takes this a step further and overrides the
class name to be `WPFTermControl`. This allows screen readers to provide
special handling for the `WPFTermControl` vs the UWP term control since
they will be updating at different speeds.

### Build fixes
To build the WPF test app, I had to mess with the dependencies a little
bit. Really just add the atlas engine and uia renderer to the build
steps.

### HwndTerminal initialization
The initialization order with `WM_NCCREATE` was changed to match that of
Windows Terminal (BaseWindow/IslandWindow). This is safer now. I also
removed the `static` window because it was unnecessary.

### Handling `WM_GETOBJECT`
WPF's HwndHost likes to mark the `WM_GETOBJECT` message as handled to
force the usage of the WPF automation peer. We now explicitly mark it as
not handled and don't return an automation peer. This forces the message
to go down to the HwndTerminal where we return terminal's UiaProvider.

### Remove TermControl layer from UIA tree
TermContol (the top-most layer in the UIA tree) would pop up and not do
anything. This PR also overrides the automation peer at that layer and
marks IsContentElement/IsControlElement=false (the equivalent to
AccessibilityView=Raw). This makes the layer only appear in the UIA tree
if you are using the raw view (i.e. you know what you're doing and you
want to see each individual layer even if you can't directly interact
with it).

## Validation Steps Performed
Tested with Narrator/NVDA using WpfTerminalTestNetCore project in our
repo.
- [X] New output is read out (not just key events, but also other output
  text)
- [X] Local echo does not occur (i.e. pressing 'A' should only read 'A'
  once, not twice [key event and rendered letter]).
- [X] selection events are read out properly
- [X] cursor change events are read out properly (tested with text
  cursor indicator preview in Settings App > Accessibility > Text
  Cursor)

NOTE: test this with Release builds. Debug builds may be too slow and
not read out properly

Closes #12642

(cherry picked from commit 5608cf1)
Service-Card-Id: 86081129
Service-Version: 1.16
@carlos-zamora carlos-zamora restored the dev/cazamor/wpf/uia-events branch October 7, 2022 18:48
@ghost
Copy link

ghost commented Oct 18, 2022

🎉Windows Terminal v1.15.2874 has been released which incorporates this pull request.:tada:

Handy links:

@DHowett DHowett moved this from Cherry Picked to Shipped in 1.15 Servicing Pipeline Dec 1, 2022
@DHowett DHowett moved this from Cherry Picked to Shipped in 1.16 Servicing Pipeline Dec 1, 2022
@ghost
Copy link

ghost commented Dec 14, 2022

🎉Windows Terminal Preview v1.16.3463.0 and v1.16.3464.0 has been released which incorporates this pull request.:tada:

Handy links:

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Accessibility Issues related to accessibility Area-WPFControl Things related to the WPF version of the TermControl AutoMerge Marked for automatic merge by the bot when requirements are met Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-1 A description (P1) Product-Terminal The new Windows Terminal.
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

Terminal WPF control doesn't support screen readers
4 participants