From a08666b58eb65b85ddf94e69892fa3e0a084ba98 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 30 Jul 2019 16:43:10 -0700 Subject: [PATCH] Accessibility: TermControl Automation Peer (#2083) 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. --- src/cascadia/TerminalApp/App.cpp | 2 +- src/cascadia/TerminalApp/Pane.cpp | 12 +- src/cascadia/TerminalApp/Tab.cpp | 4 +- src/cascadia/TerminalControl/TermControl.cpp | 39 ++-- src/cascadia/TerminalControl/TermControl.h | 6 +- src/cascadia/TerminalControl/TermControl.idl | 4 +- .../TermControlAutomationPeer.cpp | 154 ++++++++++++++ .../TermControlAutomationPeer.h | 63 ++++++ .../TermControlAutomationPeer.idl | 14 ++ .../TerminalControl/TerminalControl.vcxproj | 11 +- .../TerminalControl.vcxproj.filters | 12 +- .../TerminalControl/XamlUiaTextRange.cpp | 199 ++++++++++++++++++ .../TerminalControl/XamlUiaTextRange.h | 75 +++++++ src/cascadia/TerminalControl/pch.h | 2 + src/cascadia/inc/cppwinrt_utils.h | 32 ++- src/interactivity/win32/window.cpp | 6 +- src/types/ScreenInfoUiaProvider.cpp | 42 +++- src/types/ScreenInfoUiaProvider.h | 8 + src/types/UiaTextRange.cpp | 21 +- src/types/lib/types.vcxproj.filters | 191 ++++++++++++++--- 20 files changed, 811 insertions(+), 86 deletions(-) create mode 100644 src/cascadia/TerminalControl/TermControlAutomationPeer.cpp create mode 100644 src/cascadia/TerminalControl/TermControlAutomationPeer.h create mode 100644 src/cascadia/TerminalControl/TermControlAutomationPeer.idl create mode 100644 src/cascadia/TerminalControl/XamlUiaTextRange.cpp create mode 100644 src/cascadia/TerminalControl/XamlUiaTextRange.h diff --git a/src/cascadia/TerminalApp/App.cpp b/src/cascadia/TerminalApp/App.cpp index a822f7b6f13..aa42e859543 100644 --- a/src/cascadia/TerminalApp/App.cpp +++ b/src/cascadia/TerminalApp/App.cpp @@ -881,7 +881,7 @@ namespace winrt::TerminalApp::implementation _UpdateTitle(tab); }); - term.GetControl().GotFocus([this, weakTabPtr](auto&&, auto&&) { + term.GotFocus([this, weakTabPtr](auto&&, auto&&) { auto tab = weakTabPtr.lock(); if (!tab) { diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 173d5ef14a1..5262bf45148 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -19,7 +19,7 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus _lastFocused{ lastFocused }, _profile{ profile } { - _root.Children().Append(_control.GetControl()); + _root.Children().Append(_control); _connectionClosedToken = _control.ConnectionClosed({ this, &Pane::_ControlClosedHandler }); // Set the background of the pane to match that of the theme's default grid @@ -426,7 +426,7 @@ bool Pane::_HasFocusedChild() const noexcept // We're intentionally making this one giant expression, so the compiler // will skip the following lookups if one of the lookups before it returns // true - return (_control && _control.GetControl().FocusState() != FocusState::Unfocused) || + return (_control && _control.FocusState() != FocusState::Unfocused) || (_firstChild && _firstChild->_HasFocusedChild()) || (_secondChild && _secondChild->_HasFocusedChild()); } @@ -445,7 +445,7 @@ void Pane::UpdateFocus() if (_IsLeaf()) { const auto controlFocused = _control && - _control.GetControl().FocusState() != FocusState::Unfocused; + _control.FocusState() != FocusState::Unfocused; _lastFocused = controlFocused; } @@ -468,7 +468,7 @@ void Pane::_FocusFirstChild() { if (_IsLeaf()) { - _control.GetControl().Focus(FocusState::Programmatic); + _control.Focus(FocusState::Programmatic); } else { @@ -564,11 +564,11 @@ void Pane::_CloseChild(const bool closeFirst) _separatorRoot = { nullptr }; // Reattach the TermControl to our grid. - _root.Children().Append(_control.GetControl()); + _root.Children().Append(_control); if (_lastFocused) { - _control.GetControl().Focus(FocusState::Programmatic); + _control.Focus(FocusState::Programmatic); } _splitState = SplitState::None; diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 94acf9feee1..ad8a938c3b1 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -124,7 +124,7 @@ void Tab::_Focus() auto lastFocusedControl = _rootPane->GetFocusedTerminalControl(); if (lastFocusedControl) { - lastFocusedControl.GetControl().Focus(FocusState::Programmatic); + lastFocusedControl.Focus(FocusState::Programmatic); } } @@ -181,7 +181,7 @@ void Tab::SetTabText(const winrt::hstring& text) void Tab::Scroll(const int delta) { auto control = GetFocusedTerminalControl(); - control.GetControl().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [control, delta]() { + control.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [control, delta]() { const auto currentOffset = control.GetScrollOffset(); control.KeyboardScrollViewport(currentOffset + delta); }); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 14e3b776c40..2f263ac4bcf 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -11,6 +11,7 @@ #include "..\..\types\inc\GlyphWidth.hpp" #include "TermControl.g.cpp" +#include "TermControlAutomationPeer.h" using namespace ::Microsoft::Console::Types; using namespace ::Microsoft::Terminal::Core; @@ -30,7 +31,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _connection{ connection }, _initializedTerminal{ false }, _root{ nullptr }, - _controlRoot{ nullptr }, _swapChainPanel{ nullptr }, _settings{ settings }, _closing{ false }, @@ -52,11 +52,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void TermControl::_Create() { - // Create a dummy UserControl to use as the "root" of our control we'll - // build manually. - Controls::UserControl myControl; - _controlRoot = myControl; - Controls::Grid container; Controls::ColumnDefinition contentColumn{}; @@ -108,20 +103,20 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _bgImageLayer = bgImageLayer; _swapChainPanel = swapChainPanel; - _controlRoot.Content(_root); + this->Content(_root); _ApplyUISettings(); // These are important: // 1. When we get tapped, focus us - _controlRoot.Tapped([this](auto&, auto& e) { - _controlRoot.Focus(FocusState::Pointer); + this->Tapped([this](auto&, auto& e) { + this->Focus(FocusState::Pointer); e.Handled(true); }); // 2. Make sure we can be focused (why this isn't `Focusable` I'll never know) - _controlRoot.IsTabStop(true); + this->IsTabStop(true); // 3. Actually not sure about this one. Maybe it isn't necessary either. - _controlRoot.AllowFocusOnInteraction(true); + this->AllowFocusOnInteraction(true); // DON'T CALL _InitializeTerminal here - wait until the swap chain is loaded to do that. @@ -345,14 +340,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation Close(); } - UIElement TermControl::GetRoot() + Windows::UI::Xaml::Automation::Peers::AutomationPeer TermControl::OnCreateAutomationPeer() { - return _root; + // create a custom automation peer with this code pattern: + // (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers) + return winrt::make(*this); } - Controls::UserControl TermControl::GetControl() + ::Microsoft::Console::Render::IRenderData* TermControl::GetRenderData() const { - return _controlRoot; + return _terminal.get(); } void TermControl::SwapChainChanged() @@ -506,9 +503,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // through CharacterRecieved. // I don't believe there's a difference between KeyDown and // PreviewKeyDown for our purposes - // These two handlers _must_ be on _controlRoot, not _root. - _controlRoot.PreviewKeyDown({ this, &TermControl::_KeyDownHandler }); - _controlRoot.CharacterReceived({ this, &TermControl::_CharacterHandler }); + // These two handlers _must_ be on this, not _root. + this->PreviewKeyDown({ this, &TermControl::_KeyDownHandler }); + this->CharacterReceived({ this, &TermControl::_CharacterHandler }); auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1); _terminal->SetTitleChangedCallback(pfnTitleChanged); @@ -542,14 +539,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // import value from WinUser (convert from milli-seconds to micro-seconds) _multiClickTimer = GetDoubleClickTime() * 1000; - _gotFocusRevoker = _controlRoot.GotFocus(winrt::auto_revoke, { this, &TermControl::_GotFocusHandler }); - _lostFocusRevoker = _controlRoot.LostFocus(winrt::auto_revoke, { this, &TermControl::_LostFocusHandler }); + _gotFocusRevoker = this->GotFocus(winrt::auto_revoke, { this, &TermControl::_GotFocusHandler }); + _lostFocusRevoker = this->LostFocus(winrt::auto_revoke, { this, &TermControl::_LostFocusHandler }); // Focus the control here. If we do it up above (in _Create_), then the // focus won't actually get passed to us. I believe this is because // we're not technically a part of the UI tree yet, so focusing us // becomes a no-op. - _controlRoot.Focus(FocusState::Programmatic); + this->Focus(FocusState::Programmatic); _connection.Start(); _initializedTerminal = true; diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 57892b4924a..eb4072a98ff 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -35,8 +35,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation TermControl(); TermControl(Settings::IControlSettings settings, TerminalConnection::ITerminalConnection connection); - Windows::UI::Xaml::UIElement GetRoot(); - Windows::UI::Xaml::Controls::UserControl GetControl(); void UpdateSettings(Settings::IControlSettings newSettings); hstring Title(); @@ -55,6 +53,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void SwapChainChanged(); ~TermControl(); + Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer(); + ::Microsoft::Console::Render::IRenderData* GetRenderData() const; + static Windows::Foundation::Point GetProposedDimensions(Microsoft::Terminal::Settings::IControlSettings const& settings, const uint32_t dpi); // clang-format off @@ -71,7 +72,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation TerminalConnection::ITerminalConnection _connection; bool _initializedTerminal; - Windows::UI::Xaml::Controls::UserControl _controlRoot; Windows::UI::Xaml::Controls::Grid _root; Windows::UI::Xaml::Controls::Image _bgImageLayer; Windows::UI::Xaml::Controls::SwapChainPanel _swapChainPanel; diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 69ef35670e7..d9d23806324 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -14,15 +14,13 @@ namespace Microsoft.Terminal.TerminalControl } [default_interface] - runtimeclass TermControl + runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl { TermControl(); TermControl(Microsoft.Terminal.Settings.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection); static Windows.Foundation.Point GetProposedDimensions(Microsoft.Terminal.Settings.IControlSettings settings, UInt32 dpi); - Windows.UI.Xaml.UIElement GetRoot(); - Windows.UI.Xaml.Controls.UserControl GetControl(); void UpdateSettings(Microsoft.Terminal.Settings.IControlSettings newSettings); event TitleChangedEventArgs TitleChanged; diff --git a/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp b/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp new file mode 100644 index 00000000000..ab887080784 --- /dev/null +++ b/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include +#include "TermControlAutomationPeer.h" +#include "TermControl.h" +#include "TermControlAutomationPeer.g.cpp" + +#include "XamlUiaTextRange.h" + +using namespace Microsoft::Console::Types; +using namespace winrt::Windows::UI::Xaml::Automation::Peers; + +namespace UIA +{ + using ::ITextRangeProvider; + using ::SupportedTextSelection; +} + +namespace XamlAutomation +{ + using winrt::Windows::UI::Xaml::Automation::SupportedTextSelection; + using winrt::Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple; + using winrt::Windows::UI::Xaml::Automation::Provider::ITextRangeProvider; +} + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + TermControlAutomationPeer::TermControlAutomationPeer(winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl const& owner) : + TermControlAutomationPeerT(owner), // pass owner to FrameworkElementAutomationPeer + _uiaProvider{ owner.GetRenderData(), nullptr, std::bind(&TermControlAutomationPeer::GetBoundingRectWrapped, this) } {}; + + winrt::hstring TermControlAutomationPeer::GetClassNameCore() const + { + return L"TermControl"; + } + + AutomationControlType TermControlAutomationPeer::GetAutomationControlTypeCore() const + { + return AutomationControlType::Text; + } + + winrt::hstring TermControlAutomationPeer::GetLocalizedControlTypeCore() const + { + // TODO GitHub #2142: Localize string + return L"TerminalControl"; + } + + winrt::Windows::Foundation::IInspectable TermControlAutomationPeer::GetPatternCore(PatternInterface patternInterface) const + { + switch (patternInterface) + { + case PatternInterface::Text: + return *this; + break; + default: + return nullptr; + } + } + +#pragma region ITextProvider + winrt::com_array TermControlAutomationPeer::GetSelection() + { + SAFEARRAY* pReturnVal; + THROW_IF_FAILED(_uiaProvider.GetSelection(&pReturnVal)); + return WrapArrayOfTextRangeProviders(pReturnVal); + } + + winrt::com_array TermControlAutomationPeer::GetVisibleRanges() + { + SAFEARRAY* pReturnVal; + THROW_IF_FAILED(_uiaProvider.GetVisibleRanges(&pReturnVal)); + return WrapArrayOfTextRangeProviders(pReturnVal); + } + + XamlAutomation::ITextRangeProvider TermControlAutomationPeer::RangeFromChild(XamlAutomation::IRawElementProviderSimple childElement) + { + UIA::ITextRangeProvider* returnVal; + // ScreenInfoUiaProvider doesn't actually use parameter, so just pass in nullptr + THROW_IF_FAILED(_uiaProvider.RangeFromChild(/* IRawElementProviderSimple */ nullptr, + &returnVal)); + + auto parentProvider = this->ProviderFromPeer(*this); + auto xutr = winrt::make_self(returnVal, parentProvider); + return xutr.as(); + } + + XamlAutomation::ITextRangeProvider TermControlAutomationPeer::RangeFromPoint(Windows::Foundation::Point screenLocation) + { + UIA::ITextRangeProvider* returnVal; + THROW_IF_FAILED(_uiaProvider.RangeFromPoint({ screenLocation.X, screenLocation.Y }, &returnVal)); + + auto parentProvider = this->ProviderFromPeer(*this); + auto xutr = winrt::make_self(returnVal, parentProvider); + return xutr.as(); + } + + XamlAutomation::ITextRangeProvider TermControlAutomationPeer::DocumentRange() + { + UIA::ITextRangeProvider* returnVal; + THROW_IF_FAILED(_uiaProvider.get_DocumentRange(&returnVal)); + + auto parentProvider = this->ProviderFromPeer(*this); + auto xutr = winrt::make_self(returnVal, parentProvider); + return xutr.as(); + } + + Windows::UI::Xaml::Automation::SupportedTextSelection TermControlAutomationPeer::SupportedTextSelection() + { + UIA::SupportedTextSelection returnVal; + THROW_IF_FAILED(_uiaProvider.get_SupportedTextSelection(&returnVal)); + return static_cast(returnVal); + } + +#pragma endregion + + RECT TermControlAutomationPeer::GetBoundingRectWrapped() + { + auto rect = GetBoundingRectangle(); + return { + gsl::narrow(rect.X), + gsl::narrow(rect.Y), + gsl::narrow(rect.X + rect.Width), + gsl::narrow(rect.Y + rect.Height) + }; + } + + // Method Description: + // - extracts the UiaTextRanges from the SAFEARRAY and converts them to Xaml ITextRangeProviders + // Arguments: + // - SAFEARRAY of UIA::UiaTextRange (ITextRangeProviders) + // Return Value: + // - com_array of Xaml Wrapped UiaTextRange (ITextRangeProviders) + winrt::com_array TermControlAutomationPeer::WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges) + { + // transfer ownership of UiaTextRanges to this new vector + auto providers = SafeArrayToOwningVector<::Microsoft::Console::Types::UiaTextRange>(textRanges); + int count = providers.size(); + + std::vector vec; + vec.reserve(count); + auto parentProvider = this->ProviderFromPeer(*this); + for (int i = 0; i < count; i++) + { + auto xutr = winrt::make_self(providers[i].detach(), parentProvider); + vec.emplace_back(xutr.as()); + } + + winrt::com_array result{ vec }; + + return result; + } +} diff --git a/src/cascadia/TerminalControl/TermControlAutomationPeer.h b/src/cascadia/TerminalControl/TermControlAutomationPeer.h new file mode 100644 index 00000000000..e5db0b405da --- /dev/null +++ b/src/cascadia/TerminalControl/TermControlAutomationPeer.h @@ -0,0 +1,63 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- TermControlAutomationPeer.h + +Abstract: +- This module provides UI Automation access to the TermControl + to support both automation tests and accessibility (screen + reading) applications. This mainly interacts with ScreenInfoUiaProvider + to allow for shared code between ConHost and Windows Terminal + accessibility providers. +- Based on the Custom Automation Peers guide on msdn + (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers) +- Wraps the UIAutomationCore ITextProvider + (https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-itextprovider) + with a XAML ITextProvider + (https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.provider.itextprovider) + +Author(s): +- Carlos Zamora (CaZamor) 2019 +--*/ + +#pragma once + +#include "TermControl.h" +#include "TermControlAutomationPeer.g.h" +#include +#include "../../renderer/inc/IRenderData.hpp" +#include "../types/ScreenInfoUiaProvider.h" +#include "../types/WindowUiaProviderBase.hpp" + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + struct TermControlAutomationPeer : + public TermControlAutomationPeerT + { + public: + TermControlAutomationPeer(winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl const& owner); + + winrt::hstring GetClassNameCore() const; + winrt::Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const; + winrt::hstring GetLocalizedControlTypeCore() const; + winrt::Windows::Foundation::IInspectable GetPatternCore(winrt::Windows::UI::Xaml::Automation::Peers::PatternInterface patternInterface) const; + +#pragma region ITextProvider Pattern + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider RangeFromPoint(Windows::Foundation::Point screenLocation); + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider RangeFromChild(Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple childElement); + winrt::com_array GetVisibleRanges(); + winrt::com_array GetSelection(); + Windows::UI::Xaml::Automation::SupportedTextSelection SupportedTextSelection(); + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider DocumentRange(); +#pragma endregion + + RECT GetBoundingRectWrapped(); + + private: + ::Microsoft::Console::Types::ScreenInfoUiaProvider _uiaProvider; + + winrt::com_array WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges); + }; +} diff --git a/src/cascadia/TerminalControl/TermControlAutomationPeer.idl b/src/cascadia/TerminalControl/TermControlAutomationPeer.idl new file mode 100644 index 00000000000..d979c82471f --- /dev/null +++ b/src/cascadia/TerminalControl/TermControlAutomationPeer.idl @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "TermControl.idl"; + +namespace Microsoft.Terminal.TerminalControl +{ + [default_interface] + runtimeclass TermControlAutomationPeer : + Windows.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer, + Windows.UI.Xaml.Automation.Provider.ITextProvider + { + } +} diff --git a/src/cascadia/TerminalControl/TerminalControl.vcxproj b/src/cascadia/TerminalControl/TerminalControl.vcxproj index 8dca317e41e..c50e9c096ef 100644 --- a/src/cascadia/TerminalControl/TerminalControl.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControl.vcxproj @@ -20,6 +20,10 @@ TermControl.idl + + TermControlAutomationPeer.idl + + @@ -30,9 +34,14 @@ TermControl.idl + + TermControlAutomationPeer.idl + + + @@ -50,7 +59,7 @@ false - + false false diff --git a/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters b/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters index f2869cf5bea..d3862cb0fea 100644 --- a/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters +++ b/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters @@ -11,20 +11,20 @@ - + + - + + - - - + @@ -33,4 +33,4 @@ - \ No newline at end of file + diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp new file mode 100644 index 00000000000..4640019cb83 --- /dev/null +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "XamlUiaTextRange.h" +#include "../types/UiaTextRange.hpp" + +namespace UIA +{ + using ::ITextRangeProvider; + using ::SupportedTextSelection; + using ::TextPatternRangeEndpoint; + using ::TextUnit; +} + +namespace XamlAutomation +{ + using winrt::Windows::UI::Xaml::Automation::SupportedTextSelection; + using winrt::Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple; + using winrt::Windows::UI::Xaml::Automation::Provider::ITextRangeProvider; + using winrt::Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint; + using winrt::Windows::UI::Xaml::Automation::Text::TextUnit; +} + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + XamlAutomation::ITextRangeProvider XamlUiaTextRange::Clone() const + { + UIA::ITextRangeProvider* pReturn; + THROW_IF_FAILED(_uiaProvider->Clone(&pReturn)); + auto xutr = winrt::make_self(pReturn, _parentProvider); + return xutr.as(); + } + + bool XamlUiaTextRange::Compare(XamlAutomation::ITextRangeProvider pRange) const + { + auto self = winrt::get_self(pRange); + + BOOL returnVal; + THROW_IF_FAILED(_uiaProvider->Compare(self->_uiaProvider.get(), &returnVal)); + return returnVal; + } + + int32_t XamlUiaTextRange::CompareEndpoints(XamlAutomation::TextPatternRangeEndpoint endpoint, + XamlAutomation::ITextRangeProvider pTargetRange, + XamlAutomation::TextPatternRangeEndpoint targetEndpoint) + { + auto self = winrt::get_self(pTargetRange); + + int32_t returnVal; + THROW_IF_FAILED(_uiaProvider->CompareEndpoints(static_cast(endpoint), + self->_uiaProvider.get(), + static_cast(targetEndpoint), + &returnVal)); + return returnVal; + } + + void XamlUiaTextRange::ExpandToEnclosingUnit(XamlAutomation::TextUnit unit) const + { + THROW_IF_FAILED(_uiaProvider->ExpandToEnclosingUnit(static_cast(unit))); + } + + XamlAutomation::ITextRangeProvider XamlUiaTextRange::FindAttribute(int32_t textAttributeId, + winrt::Windows::Foundation::IInspectable val, + bool searchBackward) + { + // TODO GitHub #2161: potential accessibility improvement + // we don't support this currently + throw winrt::hresult_not_implemented(); + } + + XamlAutomation::ITextRangeProvider XamlUiaTextRange::FindText(winrt::hstring text, + bool searchBackward, + bool ignoreCase) + { + // TODO GitHub #605: Search functionality + // we need to wrap this around the UiaTextRange FindText() function + // but right now it returns E_NOTIMPL, so let's just return nullptr for now. + throw winrt::hresult_not_implemented(); + } + + winrt::Windows::Foundation::IInspectable XamlUiaTextRange::GetAttributeValue(int32_t textAttributeId) const + { + // Copied functionality from Types::UiaTextRange.cpp + if (textAttributeId == UIA_IsReadOnlyAttributeId) + { + return winrt::box_value(false); + } + else + { + return nullptr; + } + } + + void XamlUiaTextRange::GetBoundingRectangles(com_array& returnValue) const + { + returnValue = {}; + try + { + SAFEARRAY* pReturnVal; + THROW_IF_FAILED(_uiaProvider->GetBoundingRectangles(&pReturnVal)); + + double* pVals; + THROW_IF_FAILED(SafeArrayAccessData(pReturnVal, (void**)&pVals)); + + long lBound, uBound; + THROW_IF_FAILED(SafeArrayGetLBound(pReturnVal, 1, &lBound)); + THROW_IF_FAILED(SafeArrayGetUBound(pReturnVal, 1, &uBound)); + + long count = uBound - lBound + 1; + + std::vector vec; + vec.reserve(count); + for (int i = 0; i < count; i++) + { + double element = pVals[i]; + vec.push_back(element); + } + + winrt::com_array result{ vec }; + returnValue = std::move(result); + } + catch (...) + { + } + } + + XamlAutomation::IRawElementProviderSimple XamlUiaTextRange::GetEnclosingElement() + { + return _parentProvider; + } + + winrt::hstring XamlUiaTextRange::GetText(int32_t maxLength) const + { + BSTR returnVal; + THROW_IF_FAILED(_uiaProvider->GetText(maxLength, &returnVal)); + return winrt::to_hstring(returnVal); + } + + int32_t XamlUiaTextRange::Move(XamlAutomation::TextUnit unit, + int32_t count) + { + int returnVal; + THROW_IF_FAILED(_uiaProvider->Move(static_cast(unit), + count, + &returnVal)); + return returnVal; + } + + int32_t XamlUiaTextRange::MoveEndpointByUnit(XamlAutomation::TextPatternRangeEndpoint endpoint, + XamlAutomation::TextUnit unit, + int32_t count) const + { + int returnVal; + THROW_IF_FAILED(_uiaProvider->MoveEndpointByUnit(static_cast(endpoint), + static_cast(unit), + count, + &returnVal)); + return returnVal; + } + + void XamlUiaTextRange::MoveEndpointByRange(XamlAutomation::TextPatternRangeEndpoint endpoint, + XamlAutomation::ITextRangeProvider pTargetRange, + XamlAutomation::TextPatternRangeEndpoint targetEndpoint) const + { + auto self = winrt::get_self(pTargetRange); + THROW_IF_FAILED(_uiaProvider->MoveEndpointByRange(static_cast(endpoint), + /*pTargetRange*/ self->_uiaProvider.get(), + static_cast(targetEndpoint))); + } + + void XamlUiaTextRange::Select() const + { + THROW_IF_FAILED(_uiaProvider->Select()); + } + + void XamlUiaTextRange::AddToSelection() const + { + // we don't support this + throw winrt::hresult_not_implemented(); + } + + void XamlUiaTextRange::RemoveFromSelection() const + { + // we don't support this + throw winrt::hresult_not_implemented(); + } + + void XamlUiaTextRange::ScrollIntoView(bool alignToTop) const + { + THROW_IF_FAILED(_uiaProvider->ScrollIntoView(alignToTop)); + } + + winrt::com_array XamlUiaTextRange::GetChildren() const + { + // we don't have any children + return {}; + } +} diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.h b/src/cascadia/TerminalControl/XamlUiaTextRange.h new file mode 100644 index 00000000000..6560f997bbb --- /dev/null +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.h @@ -0,0 +1,75 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- XamlUiaTextRange.h + +Abstract: +- This module is a wrapper for the UiaTextRange + (a text range accessibility provider). It allows + for UiaTextRange to be used in Windows Terminal. +- Wraps the UIAutomationCore ITextRangeProvider + (https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-itextrangeprovider) + with a XAML ITextRangeProvider + (https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.provider.itextrangeprovider) + +Author(s): +- Carlos Zamora (CaZamor) 2019 +--*/ + +#pragma once + +#include "TermControlAutomationPeer.h" +#include +#include "../types/UiaTextRange.hpp" + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + class XamlUiaTextRange : + public winrt::implements + { + public: + XamlUiaTextRange(::ITextRangeProvider* uiaProvider, Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple parentProvider) : + _parentProvider{ parentProvider } + { + _uiaProvider.attach(uiaProvider); + } + +#pragma region ITextRangeProvider + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider Clone() const; + bool Compare(Windows::UI::Xaml::Automation::Provider::ITextRangeProvider pRange) const; + int32_t CompareEndpoints(Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint endpoint, + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider pTargetRange, + Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint targetEndpoint); + void ExpandToEnclosingUnit(Windows::UI::Xaml::Automation::Text::TextUnit unit) const; + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider FindAttribute(int32_t textAttributeId, + winrt::Windows::Foundation::IInspectable val, + bool searchBackward); + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider FindText(winrt::hstring text, + bool searchBackward, + bool ignoreCase); + winrt::Windows::Foundation::IInspectable GetAttributeValue(int32_t textAttributeId) const; + void GetBoundingRectangles(winrt::com_array& returnValue) const; + Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple GetEnclosingElement(); + winrt::hstring GetText(int32_t maxLength) const; + int32_t Move(Windows::UI::Xaml::Automation::Text::TextUnit unit, + int32_t count); + int32_t MoveEndpointByUnit(Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint endpoint, + Windows::UI::Xaml::Automation::Text::TextUnit unit, + int32_t count) const; + void MoveEndpointByRange(Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint endpoint, + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider pTargetRange, + Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint targetEndpoint) const; + void Select() const; + void AddToSelection() const; + void RemoveFromSelection() const; + void ScrollIntoView(bool alignToTop) const; + winrt::com_array GetChildren() const; +#pragma endregion ITextRangeProvider + + private: + wil::com_ptr<::ITextRangeProvider> _uiaProvider; + Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple _parentProvider; + }; +} diff --git a/src/cascadia/TerminalControl/pch.h b/src/cascadia/TerminalControl/pch.h index a70b22fdbb1..c8e879242d4 100644 --- a/src/cascadia/TerminalControl/pch.h +++ b/src/cascadia/TerminalControl/pch.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index 8a36d12c39f..f088734b2d5 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -55,4 +55,34 @@ private: // Use this if you have a Windows.Foundation.TypedEventHandler #define DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(className, name, eventHandler, sender, args) \ winrt::event_token className::name(Windows::Foundation::TypedEventHandler const& handler) { return eventHandler.add(handler); } \ - void className::name(winrt::event_token const& token) noexcept { eventHandler.remove(token); } \ No newline at end of file + void className::name(winrt::event_token const& token) noexcept { eventHandler.remove(token); } + +// This is a helper method for deserializing a SAFEARRAY of +// COM objects and converting it to a vector that +// owns the extracted COM objects +template +std::vector> SafeArrayToOwningVector(SAFEARRAY* safeArray) +{ + T** pVals; + THROW_IF_FAILED(SafeArrayAccessData(safeArray, (void**)&pVals)); + + THROW_HR_IF(E_UNEXPECTED, SafeArrayGetDim(safeArray) != 1); + + long lBound, uBound; + THROW_IF_FAILED(SafeArrayGetLBound(safeArray, 1, &lBound)); + THROW_IF_FAILED(SafeArrayGetUBound(safeArray, 1, &uBound)); + + long count = uBound - lBound + 1; + + // If any of the above fail, we cannot destruct/release + // any of the elements in the SAFEARRAY because we + // cannot identify how many elements there are. + + std::vector> result{ gsl::narrow(count) }; + for (int i = 0; i < count; i++) + { + result[i].attach(pVals[i]); + } + + return result; +} diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp index 6e4f5e2cfde..a8451f3b0c9 100644 --- a/src/interactivity/win32/window.cpp +++ b/src/interactivity/win32/window.cpp @@ -314,16 +314,16 @@ void Window::_UpdateSystemMetrics() const if (useDx) { - status = NTSTATUS_FROM_HRESULT(pDxEngine->SetHwnd(hWnd)); + status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pDxEngine->SetHwnd(hWnd)))); if (NT_SUCCESS(status)) { - status = NTSTATUS_FROM_HRESULT(pDxEngine->Enable()); + status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pDxEngine->Enable()))); } } else { - status = NTSTATUS_FROM_HRESULT(pGdiEngine->SetHwnd(hWnd)); + status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pGdiEngine->SetHwnd(hWnd)))); } if (NT_SUCCESS(status)) diff --git a/src/types/ScreenInfoUiaProvider.cpp b/src/types/ScreenInfoUiaProvider.cpp index e6dd03b016f..fbc4c190a33 100644 --- a/src/types/ScreenInfoUiaProvider.cpp +++ b/src/types/ScreenInfoUiaProvider.cpp @@ -31,9 +31,22 @@ SAFEARRAY* BuildIntSafeArray(_In_reads_(length) const int* const data, const int return psa; } +ScreenInfoUiaProvider::ScreenInfoUiaProvider(_In_ Microsoft::Console::Render::IRenderData* pData, + _In_ WindowUiaProviderBase* const pUiaParent, + _In_ std::function GetBoundingRect) : + _pUiaParent(pUiaParent), + _signalFiringMapping{}, + _cRefs(1), + _pData(THROW_HR_IF_NULL(E_INVALIDARG, pData)), + _getBoundingRect(GetBoundingRect) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(nullptr, ApiCall::Constructor, nullptr); +} + ScreenInfoUiaProvider::ScreenInfoUiaProvider(_In_ Microsoft::Console::Render::IRenderData* pData, _In_ WindowUiaProviderBase* const pUiaParent) : - _pUiaParent(THROW_HR_IF_NULL(E_INVALIDARG, pUiaParent)), + _pUiaParent(pUiaParent), _signalFiringMapping{}, _cRefs(1), _pData(THROW_HR_IF_NULL(E_INVALIDARG, pData)) @@ -253,6 +266,9 @@ IFACEMETHODIMP ScreenInfoUiaProvider::get_HostRawElementProvider(_COM_Outptr_res IFACEMETHODIMP ScreenInfoUiaProvider::Navigate(_In_ NavigateDirection direction, _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) { + // TODO GitHub 2120: _pUiaParent should not be allowed to be null + RETURN_HR_IF(E_NOTIMPL, _pUiaParent == nullptr); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree /*ApiMsgNavigate apiMsg; apiMsg.Direction = direction; @@ -299,7 +315,16 @@ IFACEMETHODIMP ScreenInfoUiaProvider::get_BoundingRectangle(_Out_ UiaRect* pRect // TODO GitHub #1914: Re-attach Tracing to UIA Tree //Tracing::s_TraceUia(this, ApiCall::GetBoundingRectangle, nullptr); - RECT rc = _pUiaParent->GetWindowRect(); + RECT rc; + // TODO GitHub 2120: _pUiaParent should not be allowed to be null + if (_pUiaParent == nullptr) + { + rc = _getBoundingRect(); + } + else + { + rc = _pUiaParent->GetWindowRect(); + } pRect->left = rc.left; pRect->top = rc.top; @@ -328,6 +353,9 @@ IFACEMETHODIMP ScreenInfoUiaProvider::SetFocus() IFACEMETHODIMP ScreenInfoUiaProvider::get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider) { + // TODO GitHub 2120: _pUiaParent should not be allowed to be null + RETURN_HR_IF(E_NOTIMPL, _pUiaParent == nullptr); + //Tracing::s_TraceUia(this, ApiCall::GetFragmentRoot, nullptr); try { @@ -657,10 +685,20 @@ void ScreenInfoUiaProvider::_UnlockConsole() noexcept HWND ScreenInfoUiaProvider::GetWindowHandle() const { + // TODO GitHub 2120: _pUiaParent should not be allowed to be null + if (_pUiaParent == nullptr) + { + return nullptr; + } return _pUiaParent->GetWindowHandle(); } void ScreenInfoUiaProvider::ChangeViewport(const SMALL_RECT NewWindow) { + // TODO GitHub 2120: _pUiaParent should not be allowed to be null + if (_pUiaParent == nullptr) + { + return; + } _pUiaParent->ChangeViewport(NewWindow); } diff --git a/src/types/ScreenInfoUiaProvider.h b/src/types/ScreenInfoUiaProvider.h index 707bd0428bd..7d4cb374f1f 100644 --- a/src/types/ScreenInfoUiaProvider.h +++ b/src/types/ScreenInfoUiaProvider.h @@ -36,6 +36,11 @@ namespace Microsoft::Console::Types public ITextProvider { public: + ScreenInfoUiaProvider(_In_ Microsoft::Console::Render::IRenderData* pData, + _In_ WindowUiaProviderBase* const pUiaParent, + _In_ std::function GetBoundingRect); + + // TODO GitHub 2120: pUiaParent should not be allowed to be null ScreenInfoUiaProvider(_In_ Microsoft::Console::Render::IRenderData* pData, _In_ WindowUiaProviderBase* const pUiaParent); virtual ~ScreenInfoUiaProvider(); @@ -108,6 +113,9 @@ namespace Microsoft::Console::Types const Viewport _getViewport() const; void _LockConsole() noexcept; void _UnlockConsole() noexcept; + + // these functions are reserved for Windows Terminal + std::function _getBoundingRect; }; namespace ScreenInfoUiaProviderTracing diff --git a/src/types/UiaTextRange.cpp b/src/types/UiaTextRange.cpp index aa96f4db59a..5439c9a8841 100644 --- a/src/types/UiaTextRange.cpp +++ b/src/types/UiaTextRange.cpp @@ -307,7 +307,14 @@ UiaTextRange::UiaTextRange(_In_ Microsoft::Console::Render::IRenderData* pData, { // change point coords to pixels relative to window HWND hwnd = _getWindowHandle(); - ScreenToClient(hwnd, &clientPoint); + if (hwnd == nullptr) + { + // TODO GitHub #2103: NON-HWND IMPLEMENTATION OF SCREENTOCLIENT() + } + else + { + ScreenToClient(hwnd, &clientPoint); + } const COORD currentFontSize = _getScreenFontSize(); row = (clientPoint.y / currentFontSize.Y) + viewport.Top; @@ -1495,8 +1502,16 @@ void UiaTextRange::_addScreenInfoRowBoundaries(Microsoft::Console::Render::IRend // convert the coords to be relative to the screen instead of // the client window HWND hwnd = _getWindowHandle(); - ClientToScreen(hwnd, &topLeft); - ClientToScreen(hwnd, &bottomRight); + + if (hwnd == nullptr) + { + // TODO GitHub #2103: NON-HWND IMPLEMENTATION OF CLIENTTOSCREEN() + } + else + { + ClientToScreen(hwnd, &topLeft); + ClientToScreen(hwnd, &bottomRight); + } const LONG width = bottomRight.x - topLeft.x; const LONG height = bottomRight.y - topLeft.y; diff --git a/src/types/lib/types.vcxproj.filters b/src/types/lib/types.vcxproj.filters index 11b565e87f6..35b6f908f1a 100644 --- a/src/types/lib/types.vcxproj.filters +++ b/src/types/lib/types.vcxproj.filters @@ -1,42 +1,165 @@  - + + {4FC737F1-C7A5-4376-A066-2A32D752A3FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52ECFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {77DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + - - - - - - - - - - - - - - - - - - + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + - - - - - - - - - - - - - - + - \ No newline at end of file +