-
Notifications
You must be signed in to change notification settings - Fork 8.4k
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
Conversation
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. |
d87ba29
to
3457910
Compare
@@ -18,7 +18,7 @@ Author(s): | |||
// copied typedef from uiautomationcore.h |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this 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
df0ae03
to
b618509
Compare
b5d81b3
to
4ecdcce
Compare
- 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
4ecdcce
to
a815b42
Compare
There was a problem hiding this 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.
|
||
void ScreenInfoUiaProvider::_LockConsole() noexcept | ||
{ | ||
_pData->LockConsole(); |
There was a problem hiding this comment.
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()
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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, 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 |
[[nodiscard]] HRESULT SignalUia(_In_ EVENTID id) override { return E_NOTIMPL; }; | ||
[[nodiscard]] HRESULT UiaSetTextAreaFocus() override { return E_NOTIMPL; }; | ||
|
||
RECT GetWindowRect() const noexcept override |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this because IUIAWindowNeedsIt?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Used #if 0 for tracing Made GetWindowRect() noexcept to align better between ConHost and WindowsTerminal
|
||
void ScreenInfoUiaProvider::_LockConsole() noexcept | ||
{ | ||
_pData->LockConsole(); |
There was a problem hiding this comment.
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.
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.
🎉 Handy links: |
Summary of the Pull Request
I touched A LOT of files. The main reason for this is that the
WindowUiaProvider
was moved toMicrosoft::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
Accessibility in our Project
There's a few important classes...
regarding Accessibility...
regarding the Windows Terminal window...
BaseWindow
and is actually what holds our Windows TerminalIslandWindow
regarding ConHost...
IConsoleWindow
References
PR Checklist
Detailed Description of the Pull Request / Additional comments
IConsoleWindow
changes:Microsoft::Console::Types
(a shared space)IslandWindow
extend itWindowUiaProvider
changes:Microsoft::Console::Types
(a shared space)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:
static
-fyingUiaTextRange
functions #1993IRenderData
and createIAccessibilityData
#1992