18 changed files with 684 additions and 9 deletions
@ -0,0 +1,16 @@ |
|||||
|
#import <Cocoa/Cocoa.h> |
||||
|
|
||||
|
NS_ASSUME_NONNULL_BEGIN |
||||
|
|
||||
|
class IAvnAutomationPeer; |
||||
|
|
||||
|
@interface AvnAutomationNode : NSAccessibilityElement |
||||
|
- (AvnAutomationNode *)initWithPeer:(IAvnAutomationPeer *)peer; |
||||
|
@end |
||||
|
|
||||
|
struct INSAccessibilityHolder |
||||
|
{ |
||||
|
virtual NSObject* _Nonnull GetNSAccessibility () = 0; |
||||
|
}; |
||||
|
|
||||
|
NS_ASSUME_NONNULL_END |
||||
@ -0,0 +1,220 @@ |
|||||
|
#import "automation.h" |
||||
|
#include "common.h" |
||||
|
#include "AvnString.h" |
||||
|
#include "window.h" |
||||
|
|
||||
|
class AutomationNode : public ComSingleObject<IAvnAutomationNode, &IID_IAvnAutomationNode>, |
||||
|
public INSAccessibilityHolder |
||||
|
{ |
||||
|
private: |
||||
|
NSAccessibilityElement* _node; |
||||
|
public: |
||||
|
FORWARD_IUNKNOWN() |
||||
|
|
||||
|
AutomationNode(NSAccessibilityElement* node) |
||||
|
{ |
||||
|
_node = node; |
||||
|
} |
||||
|
|
||||
|
AutomationNode(IAvnAutomationPeer* peer) |
||||
|
{ |
||||
|
_node = [[AvnAutomationNode alloc] initWithPeer: peer]; |
||||
|
} |
||||
|
|
||||
|
virtual NSObject* GetNSAccessibility() override |
||||
|
{ |
||||
|
return _node; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
@implementation AvnAutomationNode |
||||
|
{ |
||||
|
IAvnAutomationPeer* _peer; |
||||
|
NSMutableArray* _children; |
||||
|
} |
||||
|
|
||||
|
- (AvnAutomationNode *)initWithPeer:(IAvnAutomationPeer *)peer |
||||
|
{ |
||||
|
self = [super init]; |
||||
|
_peer = peer; |
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
- (BOOL)isAccessibilityElement |
||||
|
{ |
||||
|
return _peer->IsControlElement(); |
||||
|
} |
||||
|
|
||||
|
- (NSAccessibilityRole)accessibilityRole |
||||
|
{ |
||||
|
auto controlType = _peer->GetAutomationControlType(); |
||||
|
|
||||
|
switch (controlType) { |
||||
|
case AutomationButton: |
||||
|
return NSAccessibilityButtonRole; |
||||
|
case AutomationCheckBox: |
||||
|
return NSAccessibilityCheckBoxRole; |
||||
|
case AutomationComboBox: |
||||
|
return NSAccessibilityPopUpButtonRole; |
||||
|
case AutomationGroup: |
||||
|
case AutomationPane: |
||||
|
return NSAccessibilityGroupRole; |
||||
|
case AutomationSlider: |
||||
|
return NSAccessibilitySliderRole; |
||||
|
case AutomationTab: |
||||
|
return NSAccessibilityTabGroupRole; |
||||
|
case AutomationTabItem: |
||||
|
return NSAccessibilityRadioButtonRole; |
||||
|
case AutomationWindow: |
||||
|
return NSAccessibilityWindowRole; |
||||
|
default: |
||||
|
return NSAccessibilityUnknownRole; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (NSString *)accessibilityIdentifier |
||||
|
{ |
||||
|
return GetNSStringAndRelease(_peer->GetAutomationId()); |
||||
|
} |
||||
|
|
||||
|
- (NSString *)accessibilityTitle |
||||
|
{ |
||||
|
return GetNSStringAndRelease(_peer->GetName()); |
||||
|
} |
||||
|
|
||||
|
- (NSArray *)accessibilityChildren |
||||
|
{ |
||||
|
if (_children == nullptr && _peer != nullptr) |
||||
|
{ |
||||
|
auto childPeers = _peer->GetChildren(); |
||||
|
auto childCount = childPeers != nullptr ? childPeers->GetCount() : 0; |
||||
|
|
||||
|
if (childCount > 0) |
||||
|
{ |
||||
|
_children = [[NSMutableArray alloc] initWithCapacity:childCount]; |
||||
|
|
||||
|
for (int i = 0; i < childCount; ++i) |
||||
|
{ |
||||
|
IAvnAutomationPeer* child; |
||||
|
|
||||
|
if (childPeers->Get(i, &child) == S_OK) |
||||
|
{ |
||||
|
NSObject* element = ::GetAccessibilityElement(child->GetNode()); |
||||
|
[_children addObject:element]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return _children; |
||||
|
} |
||||
|
|
||||
|
- (NSRect)accessibilityFrame |
||||
|
{ |
||||
|
auto view = [self getAvnView]; |
||||
|
auto window = [self getAvnWindow]; |
||||
|
|
||||
|
if (view != nullptr) |
||||
|
{ |
||||
|
auto bounds = ToNSRect(_peer->GetBoundingRectangle()); |
||||
|
auto windowBounds = [view convertRect:bounds toView:nil]; |
||||
|
auto screenBounds = [window convertRectToScreen:windowBounds]; |
||||
|
return screenBounds; |
||||
|
} |
||||
|
|
||||
|
return NSRect(); |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityParent |
||||
|
{ |
||||
|
auto parentPeer = _peer->GetParent(); |
||||
|
|
||||
|
if (parentPeer != nullptr) |
||||
|
{ |
||||
|
return GetAccessibilityElement(parentPeer); |
||||
|
} |
||||
|
|
||||
|
return [NSApplication sharedApplication]; |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityTopLevelUIElement |
||||
|
{ |
||||
|
return GetAccessibilityElement([self getRootNode]); |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityWindow |
||||
|
{ |
||||
|
return [self accessibilityTopLevelUIElement]; |
||||
|
} |
||||
|
|
||||
|
- (BOOL)accessibilityPerformPress |
||||
|
{ |
||||
|
_peer->InvokeProvider_Invoke(); |
||||
|
return YES; |
||||
|
} |
||||
|
|
||||
|
- (BOOL)isAccessibilitySelectorAllowed:(SEL)selector |
||||
|
{ |
||||
|
if (selector == @selector(accessibilityPerformPress)) |
||||
|
{ |
||||
|
return _peer->IsInvokeProvider(); |
||||
|
} |
||||
|
|
||||
|
return [super isAccessibilitySelectorAllowed:selector]; |
||||
|
} |
||||
|
|
||||
|
- (IAvnAutomationNode*)getRootNode |
||||
|
{ |
||||
|
auto rootPeer = _peer->GetRootPeer(); |
||||
|
return rootPeer != nullptr ? rootPeer->GetNode() : nullptr; |
||||
|
} |
||||
|
|
||||
|
- (IAvnWindowBase*)getWindow |
||||
|
{ |
||||
|
auto rootNode = [self getRootNode]; |
||||
|
|
||||
|
if (rootNode != nullptr) |
||||
|
{ |
||||
|
IAvnWindowBase* window; |
||||
|
if (rootNode->QueryInterface(&IID_IAvnWindow, (void**)&window) == S_OK) |
||||
|
{ |
||||
|
return window; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
- (AvnWindow*) getAvnWindow |
||||
|
{ |
||||
|
auto window = [self getWindow]; |
||||
|
return dynamic_cast<INSWindowHolder*>(window)->GetNSWindow(); |
||||
|
} |
||||
|
|
||||
|
- (AvnView*) getAvnView |
||||
|
{ |
||||
|
auto window = [self getWindow]; |
||||
|
return dynamic_cast<INSWindowHolder*>(window)->GetNSView(); |
||||
|
} |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
extern IAvnAutomationNode* CreateAutomationNode(IAvnAutomationPeer* peer) |
||||
|
{ |
||||
|
@autoreleasepool |
||||
|
{ |
||||
|
return new AutomationNode(peer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
extern NSObject* GetAccessibilityElement(IAvnAutomationPeer* peer) |
||||
|
{ |
||||
|
auto node = peer != nullptr ? peer->GetNode() : nullptr; |
||||
|
return GetAccessibilityElement(node); |
||||
|
} |
||||
|
|
||||
|
extern NSObject* GetAccessibilityElement(IAvnAutomationNode* node) |
||||
|
{ |
||||
|
auto holder = dynamic_cast<INSAccessibilityHolder*>(node); |
||||
|
return holder != nullptr ? holder->GetNSAccessibility() : nil; |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using Avalonia.Automation; |
||||
|
using Avalonia.Automation.Peers; |
||||
|
using Avalonia.Automation.Platform; |
||||
|
using Avalonia.Native.Interop; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
|
namespace Avalonia.Native |
||||
|
{ |
||||
|
internal class AutomationNode : IRootAutomationNode |
||||
|
{ |
||||
|
public AutomationNode(AutomationNodeFactory factory, IAvnAutomationNode native) |
||||
|
{ |
||||
|
Native = native; |
||||
|
Factory = factory; |
||||
|
} |
||||
|
|
||||
|
public IAvnAutomationNode Native { get; } |
||||
|
public IAutomationNodeFactory Factory { get; } |
||||
|
|
||||
|
public void ChildrenChanged() |
||||
|
{ |
||||
|
// TODO
|
||||
|
} |
||||
|
|
||||
|
public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue) |
||||
|
{ |
||||
|
// TODO
|
||||
|
} |
||||
|
|
||||
|
public void FocusChanged(AutomationPeer? focus) |
||||
|
{ |
||||
|
// TODO
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using Avalonia.Automation.Peers; |
||||
|
using Avalonia.Automation.Platform; |
||||
|
using Avalonia.Native.Interop; |
||||
|
|
||||
|
namespace Avalonia.Native |
||||
|
{ |
||||
|
internal class AutomationNodeFactory : IAutomationNodeFactory |
||||
|
{ |
||||
|
private static AutomationNodeFactory _instance; |
||||
|
private readonly IAvaloniaNativeFactory _native; |
||||
|
|
||||
|
public static AutomationNodeFactory GetInstance(IAvaloniaNativeFactory native) |
||||
|
{ |
||||
|
return _instance ??= new AutomationNodeFactory(native); |
||||
|
} |
||||
|
|
||||
|
private AutomationNodeFactory(IAvaloniaNativeFactory native) => _native = native; |
||||
|
|
||||
|
public IAutomationNode CreateNode(AutomationPeer peer) |
||||
|
{ |
||||
|
return new AutomationNode(this, _native.CreateAutomationNode(new AvnAutomationPeer(peer))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Automation.Peers; |
||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Native.Interop; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
|
namespace Avalonia.Native |
||||
|
{ |
||||
|
internal class AvnAutomationPeer : CallbackBase, IAvnAutomationPeer |
||||
|
{ |
||||
|
private readonly AutomationPeer _inner; |
||||
|
|
||||
|
public AvnAutomationPeer(AutomationPeer inner) => _inner = inner; |
||||
|
|
||||
|
public IAvnAutomationNode Node => ((AutomationNode)_inner.Node).Native; |
||||
|
public IAvnString? AcceleratorKey => _inner.GetAcceleratorKey().ToAvnString(); |
||||
|
public IAvnString? AccessKey => _inner.GetAccessKey().ToAvnString(); |
||||
|
public AvnAutomationControlType AutomationControlType => (AvnAutomationControlType)_inner.GetAutomationControlType(); |
||||
|
public IAvnString? AutomationId => _inner.GetAutomationId().ToAvnString(); |
||||
|
public AvnRect BoundingRectangle => _inner.GetBoundingRectangle().ToAvnRect(); |
||||
|
public IAvnAutomationPeerArray Children => new AvnAutomationPeerArray(_inner.GetChildren()); |
||||
|
public IAvnString ClassName => _inner.GetClassName().ToAvnString(); |
||||
|
public IAvnAutomationPeer? LabeledBy => Wrap(_inner.GetLabeledBy()); |
||||
|
public IAvnString Name => _inner.GetName().ToAvnString(); |
||||
|
public IAvnAutomationPeer? Parent => Wrap(_inner.GetParent()); |
||||
|
|
||||
|
public int HasKeyboardFocus() => _inner.HasKeyboardFocus().AsComBool(); |
||||
|
public int IsContentElement() => _inner.IsContentElement().AsComBool(); |
||||
|
public int IsControlElement() => _inner.IsControlElement().AsComBool(); |
||||
|
public int IsEnabled() => _inner.IsEnabled().AsComBool(); |
||||
|
public int IsKeyboardFocusable() => _inner.IsKeyboardFocusable().AsComBool(); |
||||
|
public void SetFocus() => _inner.SetFocus(); |
||||
|
public int ShowContextMenu() => _inner.ShowContextMenu().AsComBool(); |
||||
|
|
||||
|
public IAvnAutomationPeer? RootPeer |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
var peer = _inner; |
||||
|
var parent = peer.GetParent(); |
||||
|
|
||||
|
while (!(peer is IRootProvider) && parent is object) |
||||
|
{ |
||||
|
peer = parent; |
||||
|
parent = peer.GetParent(); |
||||
|
} |
||||
|
|
||||
|
return new AvnAutomationPeer(peer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public int IsRootProvider() => (_inner is IRootProvider).AsComBool(); |
||||
|
|
||||
|
public IAvnAutomationPeer? RootProvider_GetPeerFromPoint(AvnPoint point) |
||||
|
{ |
||||
|
var result = ((IRootProvider)_inner).GetPeerFromPoint(point.ToAvaloniaPoint()); |
||||
|
|
||||
|
if (result is null) |
||||
|
return null; |
||||
|
|
||||
|
// The OSX accessibility APIs expect non-ignored elements when hit-testing.
|
||||
|
while (!result.IsControlElement()) |
||||
|
{ |
||||
|
var parent = result.GetParent(); |
||||
|
|
||||
|
if (parent is object) |
||||
|
result = parent; |
||||
|
else |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return Wrap(result); |
||||
|
} |
||||
|
|
||||
|
public int IsInvokeProvider() => (_inner is IInvokeProvider).AsComBool(); |
||||
|
|
||||
|
public void InvokeProvider_Invoke() => ((IInvokeProvider)_inner).Invoke(); |
||||
|
|
||||
|
public static AvnAutomationPeer? Wrap(AutomationPeer? peer) => |
||||
|
peer != null ? new AvnAutomationPeer(peer) : null; |
||||
|
} |
||||
|
|
||||
|
internal class AvnAutomationPeerArray : CallbackBase, IAvnAutomationPeerArray |
||||
|
{ |
||||
|
private readonly AvnAutomationPeer[] _items; |
||||
|
|
||||
|
public AvnAutomationPeerArray(IReadOnlyList<AutomationPeer> items) |
||||
|
{ |
||||
|
_items = items.Select(x => new AvnAutomationPeer(x)).ToArray(); |
||||
|
} |
||||
|
|
||||
|
public uint Count => (uint)_items.Length; |
||||
|
public IAvnAutomationPeer Get(uint index) => _items[index]; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue