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

Accessibility: Set-up UIA Tree #1691

Merged
merged 13 commits into from
Jul 29, 2019
Merged

Conversation

carlos-zamora
Copy link
Member

@carlos-zamora carlos-zamora commented Jun 28, 2019

Summary of the Pull Request

I touched A LOT of files. The main reason for this is that the WindowUiaProvider was moved to Microsoft::Console::Types so that it can be accessed by both projects. This meant we had to make a lot of propagating changes. But more info on that later.

The Basics of Accessibility

  • What is a User Interaction Automation (UIA) Tree?
  • Other projects (i.e.: Narrator) can take advantage of this UIA tree and are used to present information within it.
  • Some things like XAML already have a UIA Tree. So some UIA tree navigation and features are already there. It's just a matter of getting them hooked up and looking right.

Accessibility in our Project
There's a few important classes...
regarding Accessibility...

  • WindowUiaProvider: This sets up the UIA tree for a window. So this is the top-level for the UIA tree.
  • ScreenInfoUiaProvider: This sets up the UIA tree for a terminal buffer.
  • UiaTextRange: This is essential to interacting with the UIA tree for the terminal buffer. Actually gets portions of the buffer and presents them.

regarding the Windows Terminal window...

  • BaseWindow: The foundation to a window. Deals with HWNDs and that kind of stuff.
  • IslandWindow: This extends BaseWindow and is actually what holds our Windows Terminal
  • NonClientIslandWindow: An extension of the IslandWindow

regarding ConHost...

  • IConsoleWindow: This is an interface for the console window.
  • Window: This is the actual window for ConHost. Extends IConsoleWindow

References

PR Checklist

Detailed Description of the Pull Request / Additional comments

  • IConsoleWindow changes:
    • move into Microsoft::Console::Types (a shared space)
    • Have IslandWindow extend it
  • WindowUiaProvider changes:
    • move into Microsoft::Console::Types (a shared space)
  • Hook up WindowUiaProvider to IslandWindow (yay! we now have a tree)

Validation Steps Performed

I used "inspect.exe" to verify that the tree is hooked up. Verified this on ConHost and Windows Terminal.

Comments after merging in PR #1915

Please refer to #1915 for a large chunk of the work done here. There are also a few known issues/TODOs that carry over:

@carlos-zamora carlos-zamora self-assigned this Jun 28, 2019
@carlos-zamora
Copy link
Member Author

This might also close #1351 ? We can access the tab control through the UIA tree. But I think there will need to be some polishing afterwards.

@@ -18,7 +18,7 @@ Author(s):
// copied typedef from uiautomationcore.h
Copy link
Contributor

Choose a reason for hiding this comment

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

this file should be moved back to where it was. that'll also eliminate a bunch of the changes in the PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

(Just writing what we discussed in person):

  • IConsoleWindow, WindowUiaProvider, and more files to come will be moved into this shared space
  • They will be moved into a new shared LIB (name TBD) to keep Types independent of dependencies

Upcoming changes to ScreenInfoUiaProvider include:

  • Moving it to a shared space (same LIB as above)
  • Adding some pure virtual functions to abstract out the ServiceLocator and other things we don't need from ConHost

@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 Jun 28, 2019
Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

I have minor nits, and one big question on IConsoleWindow

src/interactivity/win32/windowproc.cpp Outdated Show resolved Hide resolved
src/host/tracing.cpp Show resolved Hide resolved
src/types/WindowUiaProvider.cpp Outdated Show resolved Hide resolved
src/cascadia/WindowsTerminal/IslandWindow.h Outdated Show resolved Hide resolved
@carlos-zamora carlos-zamora marked this pull request as ready for review July 17, 2019 01:51
carlos-zamora and others added 8 commits July 24, 2019 16:01
- Moved WindowUiaProvider to Types (Shared)
- Hooked up WindowUiaProvider to BaseWindow (WT)

UIATree is now accessible in WT
…g used.

TODO:
- replace IWindow with IConsoleWindow (for my own sanity)
- turn SIUP into an abstract class where any use of ServiceLocator and Selection is abstracted out
- Made IslandWindow inherit IConsoleWindow
- Moved some UIATree logic to IslandWindow
- Fixed ConHost to work with UIATree (sorta)

At the moment, ConHost is using a temp ScreenInfoUIAProvider. This will be fixed with next inheritance patch.

TODO:
- turn SIUP into an abstract class where any use of ServiceLocator and Selection is abstracted out
Added correct strings to WindowUiaProvider description fields
Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

I feel better about this. I have some nits, and I'm still annoyed by the methods added to IRenderData, but it's not worth blocking this any longer on that. We can follow up to add another interface later.
I'm thinking IUiaData : IRenderData, which just adds the methods you've added here.

src/renderer/inc/IRenderData.hpp Show resolved Hide resolved

void ScreenInfoUiaProvider::_LockConsole() noexcept
{
_pData->LockConsole();
Copy link
Member

Choose a reason for hiding this comment

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

small danger:
::LockConsole() != gci.LockConsole(). The gci one will dispatch control events (ctrl+C) in unlock, while the global one won't.

RenderData calls the global one, while this used to call the gci one.

This might be okay, since I don't think people are going to be writing Ctrl+C (as an interrupt) to the console via the UIA provider, but if you were for some reason, be aware it might not actually get dispatched until a gci.Unlock().

Copy link
Member Author

Choose a reason for hiding this comment

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

So, to fix this in the long run, does that mean we need to change how Lock/Unlock works in the Terminal class?

Copy link
Member

Choose a reason for hiding this comment

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

The whole "dispatching events on unlock" thing needs to be revisited completely at some point. I just filed #2141 for that.

src/interactivity/win32/window.cpp Outdated Show resolved Hide resolved
src/interactivity/win32/window.cpp Outdated Show resolved Hide resolved
@DHowett-MSFT
Copy link
Contributor

Carlos, when you merge this, please make sure you manually copy the body of teh pull request description (preferably in raw markdown) into the body of the commit. It's a great description. Otherwise, github will take your individual commit messages and concatenate them

src/cascadia/WindowsTerminal/BaseWindow.h Outdated Show resolved Hide resolved
[[nodiscard]] HRESULT SignalUia(_In_ EVENTID id) override { return E_NOTIMPL; };
[[nodiscard]] HRESULT UiaSetTextAreaFocus() override { return E_NOTIMPL; };

RECT GetWindowRect() const noexcept override
Copy link
Contributor

Choose a reason for hiding this comment

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

why override to just call parent?

Copy link
Contributor

Choose a reason for hiding this comment

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

is this because IUIAWindowNeedsIt?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup. BaseWindow AND IUiaWindow both have GetWindowRect() and GetWindowHandle() (BaseWindow just calls it GetHandle()). When I remove these from here, I get "ambiguous access of 'GetWindowRect'" in NonClientIslandWindow (inherits from IslandWindow). Is there a C++-ism that you know about to get around this?

}
catch (...)
{
if (nullptr != pWindowProvider)
Copy link
Contributor

Choose a reason for hiding this comment

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

okay but 100% we absolutely have to use real and correct lifetime tracking types for this

Copy link
Member Author

Choose a reason for hiding this comment

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

This part was copied over from the ConHost code when I created the inheritance model with WindowUiaProviderBase. Since we've been talking about switching over to wil in this area and the WindowUiaProvider is going to be important for changing the navigation of the tree (one of the upcoming tasks), could I just add a work item here and call it a day?

Copy link
Contributor

Choose a reason for hiding this comment

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

just so long as we do get to it.

src/host/tracing.cpp Show resolved Hide resolved
src/interactivity/win32/WindowMetrics.cpp Outdated Show resolved Hide resolved
Used #if 0 for tracing
Made GetWindowRect() noexcept to align better between ConHost and WindowsTerminal
src/cascadia/WindowsTerminal/WindowUiaProvider.cpp Outdated Show resolved Hide resolved
src/cascadia/WindowsTerminal/WindowUiaProvider.cpp Outdated Show resolved Hide resolved
src/interactivity/win32/windowUiaProvider.cpp Outdated Show resolved Hide resolved

void ScreenInfoUiaProvider::_LockConsole() noexcept
{
_pData->LockConsole();
Copy link
Member

Choose a reason for hiding this comment

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

The whole "dispatching events on unlock" thing needs to be revisited completely at some point. I just filed #2141 for that.

@carlos-zamora carlos-zamora merged commit 96496d8 into master Jul 29, 2019
DHowett-MSFT pushed a commit that referenced this pull request Jul 30, 2019
Builds on the work of #1691 and #1915 

Let's start with the easy change:
- `TermControl`'s `controlRoot` was removed. `TermControl` is a `UserControl`
  now.

Ok. Now we've got a story to tell here....

### TermControlAP - the Automation Peer
Here's an in-depth guide on custom automation peers:
https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers

We have a custom XAML element (TermControl). So XAML can't really hold our
hands and determine an accessible behavior for us. So this automation peer is
responsible for enabling that interaction.

We made it a FrameworkElementAutomationPeer to get as much accessibility as
possible from it just being a XAML element (i.e.: where are we on the screen?
what are my dimensions?). This is recommended. Any functions with "Core" at the
end, are overwritten here to tweak this automation peer into what we really
need.

But what kind of interactions can a user expect from this XAML element?
Introducing ControlPatterns! There's a ton of interfaces that just define "what
can I do". Thankfully, we already know that we're supposed to be
`ScreenInfoUiaProvider` and that was an `ITextProvider`, so let's just make the
TermControlAP an `ITextProvider` too.

So now we have a way to define what accessible actions can be performed on us,
but what should those actions do? Well let's just use the automation providers
from ConHost that are now in a shared space! (Note: this is a great place to
stop and get some coffee. We're about to hop into the .cpp file in the next
section)


### Wrapping our shared Automation Providers

Unfortunately, we can't just use the automation providers from ConHost. Or, at
least not just hook them up as easily as we wish. ConHost's UIA Providers were
written using UIAutomationCore and ITextRangeProiuder. XAML's interfaces
ITextProvider and ITextRangeProvider are lined up to be exactly the same.

So we need to wrap our ConHost UIA Providers (UIAutomationCore) with the XAML
ones. We had two providers, so that means we have two wrappers.

#### TermControlAP (XAML) <----> ScreenInfoUiaProvider (UIAutomationCore)
Each of the functions in the pragma region `ITextProvider` for
TermControlAP.cpp is just wrapping what we do in `ScreenInfoUiaProvider`, and
returning an acceptable version of it.

Most of `ScreenInfoUiaProvider`'s functions return `UiaTextRange`s. So we need
to wrap that too. That's this next section...

#### XamlUiaTextRange (XAML) <----> UiaTextRange (UIAutomationCore)
Same idea.  We're wrapping everything that we could do with `UiaTextRange` and
putting it inside of `XamlUiaTextRange`.


### Additional changes to `UiaTextRange` and `ScreenInfoUiaProvider`
If you don't know what I just said, please read this background:
- #1691: how accessibility works and the general responsibility of these two
  classes
- #1915: how we pulled these Accessibility Providers into a shared area

TL;DR: `ScreenInfoUiaProvider` lets you interact with the displayed text.
`UiaTextRange` is specific ranges of text in the display and navigate the text.

Thankfully, we didn't do many changes here. I feel like some of it is hacked
together but now that we have a somewhat working system, making changes
shouldn't be too hard...I hope.

#### UiaTextRange
We don't have access to the window handle. We really only need it to draw the
bounding rects using WinUser's `ScreenToClient()` and `ClientToScreen()`. I
need to figure out how to get around this.

In the meantime, I made the window handle optional. And if we don't have
one....well, we need to figure that out. But other than that, we have a
`UiaTextRange`.

#### ScreenInfoUiaProvider
At some point, we need to hook up this automation provider to the
WindowUiaProvider. This should help with navigation of the UIA Tree and make
everything just look waaaay better. For now, let's just do the same approach
and make the pUiaParent optional.

This one's the one I'm not that proud of, but it works. We need the parent to
get a bounding rect of the terminal. While we figure out how to attach the
WindowUiaProvider, we should at the very least be able to get a bunch of info
from our xaml automation peer. So, I've added a _getBoundingRect optional
function. This is what's called when we don't have a WindowUiaProvider as our
parent.


## Validation Steps Performed
I've been using inspect.exe to see the UIA tree.
I was able to interact with the terminal mostly fine. A few known issues below.

Unfortunately, I tried running Narrator on this and it didn't seem to like it
(by that I mean WT crashed). Then again, I don't really know how to use
narrator other than "click on object" --> "listen voice". I feel like there's a
way to get the other interactions with narrator, but I'll be looking into more
of that soon. I bet if I fix the two issues below, Narrator will be happy.

## Miscellaneous Known Issues
- `GetSelection()` and `GetVisibleRanges()` crashes. I need to debug through
  these. I want to include them in this PR.

Fixes #1353.
@carlos-zamora carlos-zamora deleted the dev/cazamor/accessibility branch July 31, 2019 16:59
@ghost
Copy link

ghost commented Aug 3, 2019

🎉Windows Terminal Preview v0.3.2142.0 has been released which incorporates this pull request.:tada:

Handy links:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Windows Terminal: Set up UIA tree and hook it up properly
5 participants