From bc128676c456d3040f5c34b7040ce9d635aec185 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 10 Nov 2021 23:48:54 +0100 Subject: [PATCH] Make OSX a11y work again. Pretty major refactor of that code. --- native/Avalonia.Native/src/OSX/AvnString.mm | 2 +- native/Avalonia.Native/src/OSX/automation.h | 10 +- native/Avalonia.Native/src/OSX/automation.mm | 234 ++++++++++++------- native/Avalonia.Native/src/OSX/common.h | 3 - native/Avalonia.Native/src/OSX/main.mm | 8 +- native/Avalonia.Native/src/OSX/window.mm | 130 +++-------- src/Avalonia.Native/AutomationNode.cs | 40 ---- src/Avalonia.Native/Avalonia.Native.csproj | 4 + src/Avalonia.Native/AvnAutomationPeer.cs | 47 +++- src/Avalonia.Native/WindowImplBase.cs | 11 +- src/Avalonia.Native/avn.idl | 9 +- 11 files changed, 247 insertions(+), 251 deletions(-) delete mode 100644 src/Avalonia.Native/AutomationNode.cs diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index c69cef5c87..e0266a127c 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -160,7 +160,7 @@ NSString* GetNSStringAndRelease(IAvnString* s) { char* p; - if (s->Pointer((void**)&p) == S_OK) + if (s->Pointer((void**)&p) == S_OK && p != nullptr) { return [NSString stringWithUTF8String:p]; } diff --git a/native/Avalonia.Native/src/OSX/automation.h b/native/Avalonia.Native/src/OSX/automation.h index 65e1153248..4a12a965fd 100644 --- a/native/Avalonia.Native/src/OSX/automation.h +++ b/native/Avalonia.Native/src/OSX/automation.h @@ -1,16 +1,12 @@ #import +#include "window.h" NS_ASSUME_NONNULL_BEGIN class IAvnAutomationPeer; -@interface AvnAutomationNode : NSAccessibilityElement -- (AvnAutomationNode *)initWithPeer:(IAvnAutomationPeer *)peer; +@interface AvnAccessibilityElement : NSAccessibilityElement ++ (AvnAccessibilityElement *) acquire:(IAvnAutomationPeer *) peer; @end -struct INSAccessibilityHolder -{ - virtual NSObject* _Nonnull GetNSAccessibility () = 0; -}; - NS_ASSUME_NONNULL_END diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index b1a2b9e94e..ca845d0ec8 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -1,66 +1,108 @@ -#import "automation.h" #include "common.h" +#include "automation.h" #include "AvnString.h" #include "window.h" -class AutomationNode : public ComSingleObject, - public INSAccessibilityHolder +@interface AvnAccessibilityElement (Events) +- (void) raiseChildrenChanged; +@end + +@interface AvnRootAccessibilityElement : AvnAccessibilityElement +- (AvnView *) ownerView; +- (AvnRootAccessibilityElement *) initWithPeer:(IAvnAutomationPeer *) peer owner:(AvnView*) owner; +- (void) raiseFocusChanged; +@end + +class AutomationNode : public ComSingleObject { -private: - NSAccessibilityElement* _node; public: FORWARD_IUNKNOWN() - AutomationNode(NSAccessibilityElement* node) + AutomationNode(AvnAccessibilityElement* owner) { - _node = node; + _owner = owner; } - - AutomationNode(IAvnAutomationPeer* peer) + + AvnAccessibilityElement* GetOwner() { - _node = [[AvnAutomationNode alloc] initWithPeer: peer]; + return _owner; } - virtual void ChildrenChanged() override + virtual void Dispose() override { - NSAccessibilityPostNotification(_node, NSAccessibilityLayoutChangedNotification); } - virtual void PropertyChanged(AvnAutomationProperty property) override + virtual void ChildrenChanged () override { - switch (property) { - case RangeValueProvider_Value: - NSAccessibilityPostNotification(_node, NSAccessibilityValueChangedNotification); - break; - default: - break; - } + [_owner raiseChildrenChanged]; } - - virtual void FocusChanged(IAvnAutomationPeer* peer) override + + virtual void PropertyChanged (AvnAutomationProperty property) override { - // Only implemented in top-level nodes, i.e. AvnWindow. + } - - virtual NSObject* GetNSAccessibility() override + + virtual void FocusChanged () override { - return _node; + [(AvnRootAccessibilityElement*)_owner raiseFocusChanged]; } + +private: + AvnAccessibilityElement* _owner; }; -@implementation AvnAutomationNode +@implementation AvnAccessibilityElement { IAvnAutomationPeer* _peer; + AutomationNode* _node; NSMutableArray* _children; } -- (AvnAutomationNode *)initWithPeer:(IAvnAutomationPeer *)peer ++ (AvnAccessibilityElement *)acquire:(IAvnAutomationPeer *)peer +{ + if (peer == nullptr) + return nil; + + auto instance = peer->GetNode(); + + if (instance != nullptr) + return dynamic_cast(instance)->GetOwner(); + + if (peer->IsRootProvider()) + { + auto window = peer->RootProvider_GetWindow(); + auto holder = dynamic_cast(window); + auto view = holder->GetNSView(); + return [[AvnRootAccessibilityElement alloc] initWithPeer:peer owner:view]; + } + else + { + return [[AvnAccessibilityElement alloc] initWithPeer:peer]; + } +} + +- (AvnAccessibilityElement *)initWithPeer:(IAvnAutomationPeer *)peer { self = [super init]; _peer = peer; + _node = new AutomationNode(self); + _peer->SetNode(_node); return self; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ '%@' (%p)", + GetNSStringAndRelease(_peer->GetClassName()), + GetNSStringAndRelease(_peer->GetName()), + _peer]; +} + +- (IAvnAutomationPeer *)peer +{ + return _peer; +} + - (BOOL)isAccessibilityElement { return _peer->IsControlElement(); @@ -96,7 +138,7 @@ public: case AutomationToolBar: return NSAccessibilityToolbarRole; case AutomationToolTip: return NSAccessibilityPopoverRole; case AutomationTree: return NSAccessibilityOutlineRole; - case AutomationTreeItem: return NSAccessibilityOutlineRowSubrole; + case AutomationTreeItem: return NSAccessibilityCellRole; case AutomationCustom: return NSAccessibilityUnknownRole; case AutomationGroup: return NSAccessibilityGroupRole; case AutomationThumb: return NSAccessibilityHandleRole; @@ -110,8 +152,10 @@ public: case AutomationHeaderItem: return NSAccessibilityButtonRole; case AutomationTable: return NSAccessibilityTableRole; case AutomationTitleBar: return NSAccessibilityGroupRole; - case AutomationSeparator: return NSAccessibilityUnknownRole; - default: return NSAccessibilityUnknownRole; + // Treat unknown roles as generic group container items. Returning + // NSAccessibilityUnknownRole is also possible but makes the screen + // reader focus on the item instead of passing focus to child items. + default: return NSAccessibilityGroupRole; } } @@ -177,6 +221,15 @@ public: return [super accessibilityMaxValue]; } +- (BOOL)isAccessibilityEnabled +{ + return _peer->IsEnabled(); +} + +- (BOOL)isAccessibilityFocused +{ + return _peer->HasKeyboardFocus(); +} - (NSArray *)accessibilityChildren { @@ -195,52 +248,55 @@ public: if (childPeers->Get(i, &child) == S_OK) { - NSObject* element = ::GetAccessibilityElement(child->GetNode()); + auto element = [AvnAccessibilityElement acquire:child]; [_children addObject:element]; } } } } - + return _children; } - (NSRect)accessibilityFrame { - auto view = [self getAvnView]; - auto window = [self getAvnWindow]; + id topLevel = [self accessibilityTopLevelUIElement]; + auto result = NSZeroRect; - if (view != nullptr) + if ([topLevel isKindOfClass:[AvnRootAccessibilityElement class]]) { - auto bounds = ToNSRect(_peer->GetBoundingRectangle()); - auto windowBounds = [view convertRect:bounds toView:nil]; - auto screenBounds = [window convertRectToScreen:windowBounds]; - return screenBounds; + auto root = (AvnRootAccessibilityElement*)topLevel; + auto view = [root ownerView]; + + if (view) + { + auto window = [view window]; + auto bounds = ToNSRect(_peer->GetBoundingRectangle()); + auto windowBounds = [view convertRect:bounds toView:nil]; + auto screenBounds = [window convertRectToScreen:windowBounds]; + result = screenBounds; + } } - - return NSRect(); + + return result; } - (id)accessibilityParent { auto parentPeer = _peer->GetParent(); - - if (parentPeer != nullptr) - { - return GetAccessibilityElement(parentPeer); - } - - return [NSApplication sharedApplication]; + return parentPeer ? [AvnAccessibilityElement acquire:parentPeer] : [NSApplication sharedApplication]; } - (id)accessibilityTopLevelUIElement { - return GetAccessibilityElement([self getRootNode]); + auto rootPeer = _peer->GetRootPeer(); + return [AvnAccessibilityElement acquire:rootPeer]; } - (id)accessibilityWindow { - return [self accessibilityTopLevelUIElement]; + id topLevel = [self accessibilityTopLevelUIElement]; + return [topLevel isKindOfClass:[NSWindow class]] ? topLevel : nil; } - (BOOL)isAccessibilityExpanded @@ -326,58 +382,68 @@ public: return [super isAccessibilitySelectorAllowed:selector]; } -- (IAvnAutomationNode*)getRootNode +- (void)raiseChildrenChanged { - auto rootPeer = _peer->GetRootPeer(); - return rootPeer != nullptr ? rootPeer->GetNode() : nullptr; + NSAccessibilityPostNotification(self, NSAccessibilityLayoutChangedNotification); } -- (IAvnWindowBase*)getWindow +- (void)raisePropertyChanged { - auto rootNode = [self getRootNode]; +} - if (rootNode != nullptr) - { - IAvnWindowBase* window; - if (rootNode->QueryInterface(&IID_IAvnWindow, (void**)&window) == S_OK) - { - return window; - } - } - - return nullptr; +- (void)setAccessibilityFocused:(BOOL)accessibilityFocused +{ + if (accessibilityFocused) + _peer->SetFocus(); } -- (AvnWindow*) getAvnWindow +@end + +@implementation AvnRootAccessibilityElement { - auto window = [self getWindow]; - return window != nullptr ? dynamic_cast(window)->GetNSWindow() : nullptr; + AvnView* _owner; } -- (AvnView*) getAvnView +- (AvnRootAccessibilityElement *)initWithPeer:(IAvnAutomationPeer *)peer owner:(AvnView *)owner { - auto window = [self getWindow]; - return window != nullptr ? dynamic_cast(window)->GetNSView() : nullptr; + self = [super initWithPeer:peer]; + _owner = owner; + return self; } -@end +- (AvnView *)ownerView +{ + return _owner; +} -extern IAvnAutomationNode* CreateAutomationNode(IAvnAutomationPeer* peer) +- (id)accessibilityFocusedUIElement { - @autoreleasepool - { - return new AutomationNode(peer); - } + auto focusedPeer = [self peer]->RootProvider_GetFocus(); + return [AvnAccessibilityElement acquire:focusedPeer]; } -extern NSObject* GetAccessibilityElement(IAvnAutomationPeer* peer) +- (id)accessibilityHitTest:(NSPoint)point { - auto node = peer != nullptr ? peer->GetNode() : nullptr; - return GetAccessibilityElement(node); + auto clientPoint = [[_owner window] convertPointFromScreen:point]; + auto localPoint = [_owner translateLocalPoint:ToAvnPoint(clientPoint)]; + auto hit = [self peer]->RootProvider_GetPeerFromPoint(localPoint); + return [AvnAccessibilityElement acquire:hit]; } -extern NSObject* GetAccessibilityElement(IAvnAutomationNode* node) +- (id)accessibilityParent { - auto holder = dynamic_cast(node); - return holder != nullptr ? holder->GetNSAccessibility() : nil; + return _owner; } + +- (void)raiseFocusChanged +{ + id focused = [self accessibilityFocusedUIElement]; + NSAccessibilityPostNotification(focused, NSAccessibilityFocusedUIElementChangedNotification); +} + +- (void)accessibilityPerformAction:(NSAccessibilityActionName)action +{ + [_owner accessibilityPerformAction:action]; +} + +@end diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 53ff46cf32..091856fcf7 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -31,9 +31,6 @@ extern IAvnMenu* GetAppMenu (); extern NSMenuItem* GetAppMenuItem (); extern void SetAutoGenerateDefaultAppMenuItems (bool enabled); extern bool GetAutoGenerateDefaultAppMenuItems (); -extern IAvnAutomationNode* CreateAutomationNode(IAvnAutomationPeer* peer); -extern NSObject* GetAccessibilityElement(IAvnAutomationPeer* peer); -extern NSObject* GetAccessibilityElement(IAvnAutomationNode* node); extern void InitializeAvnApp(IAvnApplicationEvents* events); extern NSApplicationActivationPolicy AvnDesiredActivationPolicy; diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 68afb51f40..9dc9da1cd1 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -347,13 +347,7 @@ public: return S_OK; } } - - virtual HRESULT CreateAutomationNode (IAvnAutomationPeer* peer, IAvnAutomationNode** ppv) override - { - *ppv = ::CreateAutomationNode(peer); - return S_OK; - } - + virtual HRESULT SetAppMenu (IAvnMenu* appMenu) override { START_COM_CALL; diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 47d605a311..5b8a7a1c0e 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -10,9 +10,7 @@ class WindowBaseImpl : public virtual ComObject, public virtual IAvnWindowBase, - public virtual IAvnAutomationNode, - public INSWindowHolder, - public INSAccessibilityHolder + public INSWindowHolder { private: NSCursor* cursor; @@ -21,7 +19,6 @@ public: FORWARD_IUNKNOWN() BEGIN_INTERFACE_MAP() INTERFACE_MAP_ENTRY(IAvnWindowBase, IID_IAvnWindowBase) - INTERFACE_MAP_ENTRY(IAvnAutomationNode, IID_IAvnAutomationNode) END_INTERFACE_MAP() virtual ~WindowBaseImpl() @@ -131,11 +128,6 @@ public: return View; } - virtual NSObject* GetNSAccessibility() override - { - return Window; - } - virtual HRESULT Show(bool activate, bool isDialog) override { START_COM_CALL; @@ -603,25 +595,6 @@ public: return S_OK; } - virtual void ChildrenChanged() override - { - NSAccessibilityPostNotification(Window, NSAccessibilityLayoutChangedNotification); - } - - virtual void PropertyChanged(AvnAutomationProperty property) override - { - } - - virtual void FocusChanged(IAvnAutomationPeer* peer) override - { - auto element = GetAccessibilityElement(peer); - - if (element != nullptr) - { - NSAccessibilityPostNotification(element, NSAccessibilityFocusedUIElementChangedNotification); - } - } - virtual bool IsDialog() { return false; @@ -1432,6 +1405,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent AvnPixelSize _lastPixelSize; NSObject* _renderTarget; AvnPlatformResizeReason _resizeReason; + AvnAccessibilityElement* _accessibilityChild; } - (void)onClosed @@ -2055,6 +2029,37 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _resizeReason = reason; } +- (AvnAccessibilityElement *) accessibilityChild +{ + if (_accessibilityChild == nil) + { + auto peer = _parent->BaseEvents->GetAutomationPeer(); + + if (peer == nil) + return nil; + + _accessibilityChild = [AvnAccessibilityElement acquire:peer]; + } + + return _accessibilityChild; +} + +- (NSArray *)accessibilityChildren +{ + auto child = [self accessibilityChild]; + return NSAccessibilityUnignoredChildrenForOnlyChild(child); +} + +- (id)accessibilityHitTest:(NSPoint)point +{ + return [[self accessibilityChild] accessibilityHitTest:point]; +} + +- (id)accessibilityFocusedUIElement +{ + return [[self accessibilityChild] accessibilityFocusedUIElement]; +} + @end @@ -2466,75 +2471,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent } } -- (BOOL)isAccessibilityElement -{ - [self getAutomationPeer]; - return YES; -} - -- (NSString *)accessibilityIdentifier -{ - auto peer = [self getAutomationPeer]; - return GetNSStringAndRelease(peer->GetAutomationId()); -} - -- (NSArray *)accessibilityChildren -{ - auto peer = [self getAutomationPeer]; - - if (_automationChildren == nullptr) - { - _automationChildren = (NSMutableArray*)[super accessibilityChildren]; - - auto childPeers = peer->GetChildren(); - auto childCount = childPeers != nullptr ? childPeers->GetCount() : 0; - - if (childCount > 0) - { - for (int i = 0; i < childCount; ++i) - { - IAvnAutomationPeer* child; - - if (childPeers->Get(i, &child) == S_OK) - { - auto element = GetAccessibilityElement(child); - [_automationChildren addObject:element]; - } - } - } - } - - return _automationChildren; -} - -- (id)accessibilityHitTest:(NSPoint)point -{ - point = [self convertPointFromScreen:point]; - auto p = [_parent->View translateLocalPoint:ToAvnPoint(point)]; - auto peer = [self getAutomationPeer]; - auto hit = peer->RootProvider_GetPeerFromPoint(p); - return GetAccessibilityElement(hit); -} - -- (id)accessibilityFocusedUIElement -{ - auto peer = [self getAutomationPeer]; - - if (peer->IsRootProvider()) - { - return GetAccessibilityElement(peer->RootProvider_GetFocus()); - } - - return [super accessibilityFocusedUIElement]; -} - -- (IAvnAutomationPeer*) getAutomationPeer -{ - if (_automationPeer == nullptr) - _automationPeer = _parent->BaseEvents->AutomationStarted(_parent); - return _automationPeer; -} - @end class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup diff --git a/src/Avalonia.Native/AutomationNode.cs b/src/Avalonia.Native/AutomationNode.cs deleted file mode 100644 index 251b7156f1..0000000000 --- a/src/Avalonia.Native/AutomationNode.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Avalonia.Automation; -using Avalonia.Automation.Peers; -using Avalonia.Native.Interop; - -#nullable enable - -namespace Avalonia.Native -{ - internal class AutomationNode - { - public AutomationNode(IAvnAutomationNode native) - { - Native = native; - } - - public IAvnAutomationNode Native { get; } - - public void ChildrenChanged() => Native.ChildrenChanged(); - - public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue) - { - AvnAutomationProperty p; - - if (property == AutomationElementIdentifiers.BoundingRectangleProperty) - p = AvnAutomationProperty.AutomationPeer_BoundingRectangle; - else if (property == AutomationElementIdentifiers.ClassNameProperty) - p = AvnAutomationProperty.AutomationPeer_ClassName; - else if (property == AutomationElementIdentifiers.NameProperty) - p = AvnAutomationProperty.AutomationPeer_Name; - else if (property == RangeValuePatternIdentifiers.ValueProperty) - p = AvnAutomationProperty.RangeValueProvider_Value; - else - return; - - Native.PropertyChanged(p); - } - - public void FocusChanged(AutomationPeer? focus) => Native.FocusChanged(AvnAutomationPeer.Wrap(focus)); - } -} diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 3913484431..b430483419 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -16,6 +16,10 @@ PreserveNewest + + + + diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index b50b0d5111..bcb3d54fcf 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; using Avalonia.Automation; using Avalonia.Automation.Peers; using Avalonia.Automation.Provider; +using Avalonia.Controls; using Avalonia.Native.Interop; #nullable enable @@ -12,11 +15,20 @@ namespace Avalonia.Native { internal class AvnAutomationPeer : CallbackBase, IAvnAutomationPeer { + private static readonly ConditionalWeakTable s_wrappers = new(); private readonly AutomationPeer _inner; - public AvnAutomationPeer(AutomationPeer inner) => _inner = inner; + private AvnAutomationPeer(AutomationPeer inner) + { + _inner = inner; + _inner.ChildrenChanged += (_, _) => Node?.ChildrenChanged(); + if (inner is WindowBaseAutomationPeer window) + window.FocusChanged += (_, _) => Node?.FocusChanged(); + } - public IAvnAutomationNode Node => throw new NotImplementedException(); + ~AvnAutomationPeer() => Node?.Dispose(); + + public IAvnAutomationNode? Node { get; private set; } public IAvnString? AcceleratorKey => _inner.GetAcceleratorKey().ToAvnString(); public IAvnString? AccessKey => _inner.GetAccessKey().ToAvnString(); public AvnAutomationControlType AutomationControlType => (AvnAutomationControlType)_inner.GetAutomationControlType(); @@ -43,17 +55,31 @@ namespace Avalonia.Native var peer = _inner; var parent = peer.GetParent(); - while (!(peer is IRootProvider) && parent is object) + while (peer is not IRootProvider && parent is not null) { peer = parent; parent = peer.GetParent(); } - return new AvnAutomationPeer(peer); + return Wrap(peer); } } + public void SetNode(IAvnAutomationNode node) + { + if (Node is not null) + throw new InvalidOperationException("The AvnAutomationPeer already has a node."); + Node = node; + } + public int IsRootProvider() => (_inner is IRootProvider).AsComBool(); + + public IAvnWindowBase RootProvider_GetWindow() + { + var window = (WindowBase)((ControlAutomationPeer)_inner).Owner; + return ((WindowBaseImpl)window.PlatformImpl!).Native; + } + public IAvnAutomationPeer? RootProvider_GetFocus() => Wrap(((IRootProvider)_inner).GetFocus()); public IAvnAutomationPeer? RootProvider_GetPeerFromPoint(AvnPoint point) @@ -68,7 +94,7 @@ namespace Avalonia.Native { var parent = result.GetParent(); - if (parent is object) + if (parent is not null) result = parent; else break; @@ -108,9 +134,12 @@ namespace Avalonia.Native public int IsValueProvider() => (_inner is IValueProvider).AsComBool(); public IAvnString ValueProvider_GetValue() => ((IValueProvider)_inner).Value.ToAvnString(); public void ValueProvider_SetValue(string value) => ((IValueProvider)_inner).SetValue(value); - - public static AvnAutomationPeer? Wrap(AutomationPeer? peer) => - peer != null ? new AvnAutomationPeer(peer) : null; + + [return: NotNullIfNotNull("peer")] + public static AvnAutomationPeer? Wrap(AutomationPeer? peer) + { + return peer is null ? null : s_wrappers.GetValue(peer, x => new(peer)); + } } internal class AvnAutomationPeerArray : CallbackBase, IAvnAutomationPeerArray @@ -119,7 +148,7 @@ namespace Avalonia.Native public AvnAutomationPeerArray(IReadOnlyList items) { - _items = items.Select(x => new AvnAutomationPeer(x)).ToArray(); + _items = items.Select(x => AvnAutomationPeer.Wrap(x)).ToArray(); } public uint Count => (uint)_items.Length; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index d055e4a1c4..fab72dfe47 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -62,7 +62,6 @@ namespace Avalonia.Native private GlPlatformSurface _glSurface; private NativeControlHostImpl _nativeControlHost; private IGlContext _glContext; - private AvnAutomationPeer _automationPeer; internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOpenGlInterface glFeature) @@ -158,6 +157,11 @@ namespace Avalonia.Native public IMouseDevice MouseDevice => _mouse; public abstract IPopupImpl CreatePopup(); + public AutomationPeer GetAutomationPeer() + { + return _inputRoot is Control c ? ControlAutomationPeer.CreatePeerForElement(c) : null; + } + protected unsafe class WindowBaseEvents : CallbackBase, IAvnWindowBaseEvents { private readonly WindowBaseImpl _parent; @@ -262,6 +266,11 @@ namespace Avalonia.Native return (AvnDragDropEffects)args.Effects; } } + + IAvnAutomationPeer IAvnWindowBaseEvents.AutomationPeer + { + get => AvnAutomationPeer.Wrap(_parent.GetAutomationPeer()); + } } public void Activate() diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index e16b5174b0..954aeb3fb4 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -419,6 +419,7 @@ enum AvnPlatformResizeReason enum AvnAutomationControlType { + AutomationNone, AutomationButton, AutomationCalendar, AutomationCheckBox, @@ -480,7 +481,6 @@ interface IAvaloniaNativeFactory : IUnknown HRESULT CreateMenuItem(IAvnMenuItem** ppv); HRESULT CreateMenuItemSeparator(IAvnMenuItem** ppv); HRESULT CreateTrayIcon(IAvnTrayIcon** ppv); - HRESULT CreateAutomationNode(IAvnAutomationPeer* peer, IAvnAutomationNode** ppv); } [uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)] @@ -570,6 +570,7 @@ interface IAvnWindowBaseEvents : IUnknown AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position, AvnInputModifiers modifiers, AvnDragDropEffects effects, IAvnClipboard* clipboard, [intptr]void* dataObjectHandle); + IAvnAutomationPeer* GetAutomationPeer(); } [uuid(1ae178ee-1fcc-447f-b6dd-b7bb727f934c)] @@ -811,6 +812,8 @@ interface IAvnApplicationEvents : IUnknown interface IAvnAutomationPeer : IUnknown { IAvnAutomationNode* GetNode(); + void SetNode(IAvnAutomationNode* node); + IAvnString* GetAcceleratorKey(); IAvnString* GetAccessKey(); AvnAutomationControlType GetAutomationControlType(); @@ -832,6 +835,7 @@ interface IAvnAutomationPeer : IUnknown IAvnAutomationPeer* GetRootPeer(); bool IsRootProvider(); + IAvnWindowBase* RootProvider_GetWindow(); IAvnAutomationPeer* RootProvider_GetFocus(); IAvnAutomationPeer* RootProvider_GetPeerFromPoint(AvnPoint point); @@ -871,7 +875,8 @@ interface IAvnAutomationPeerArray : IUnknown [uuid(004dc40b-e435-49dc-bac5-6272ee35382a)] interface IAvnAutomationNode : IUnknown { + void Dispose(); void ChildrenChanged(); void PropertyChanged(AvnAutomationProperty property); - void FocusChanged(IAvnAutomationPeer* peer); + void FocusChanged(); }