Browse Source

more cache fixmeups + create Accessible abstract class for reuse

feature/linux-automation
Jumar Macato 2 years ago
parent
commit
0ffdbccdc0
  1. 122
      src/Avalonia.FreeDesktop/AtSpi/Accessible.cs
  2. 2
      src/Avalonia.FreeDesktop/AtSpi/AtSpiConstants.cs
  3. 20
      src/Avalonia.FreeDesktop/AtSpi/AtSpiContext.cs
  4. 38
      src/Avalonia.FreeDesktop/AtSpi/CacheEntry.cs
  5. 88
      src/Avalonia.FreeDesktop/AtSpi/RootAccessible.cs
  6. 9
      src/Avalonia.FreeDesktop/AtSpi/RootApplication.cs
  7. 51
      src/Avalonia.FreeDesktop/AtSpi/RootCache.cs

122
src/Avalonia.FreeDesktop/AtSpi/Accessible.cs

@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Logging;
using Tmds.DBus.Protocol;
using Tmds.DBus.SourceGenerator;
namespace Avalonia.FreeDesktop.AtSpi;
internal abstract class Accessible : OrgA11yAtspiAccessible
{
private readonly Accessible? _internalParent;
public Guid InternalGuid { get; protected set; }
public CacheEntry InternalCacheEntry { get; } = new();
public string DbusPath { get; }
public string ServiceName { get; }
public Accessible? InternalParent => _internalParent;
protected Accessible(string serviceName, Accessible? internalParent)
{
_internalParent = internalParent;
ServiceName = serviceName;
InternalGuid = new Guid();
DbusPath = Path.Combine(AtSpiConstants.AvaloniaPathPrefix, InternalGuid.ToString("N"));
}
public (string, ObjectPath) Convert() => (ServiceName, (ObjectPath)DbusPath);
private readonly List<Accessible> _internalChildren = new();
public void AddChild(Accessible accessible)
{
if (AtSpiContext.Cache != null && AtSpiContext.Cache.TryAddEntry(accessible))
{
_internalChildren.Add(accessible);
ChildCount = _internalChildren.Count;
}
else
{
Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this,
$"Unable to add Accessible object to the root AT-SPI cache. {accessible.InternalGuid}");
}
}
public void RemoveChild(Accessible accessible)
{
_internalChildren.Remove(accessible);
ChildCount = _internalChildren.Count;
}
public IList<Accessible> InternalChildren => _internalChildren;
protected override ValueTask<(string, ObjectPath)> OnGetChildAtIndexAsync(int index)
{
var accessible = InternalChildren.ElementAtOrDefault(index);
if (accessible is not null)
{
return ValueTask.FromResult(accessible.Convert());
}
return ValueTask.FromResult<(string, ObjectPath)>((":0.0", "/org/a11y/atspi/accessible/null"));
}
protected override ValueTask<(string, ObjectPath)[]> OnGetChildrenAsync()
{
return ValueTask.FromResult(InternalChildren.Select(x => x.Convert()).ToArray());
}
protected override ValueTask<int> OnGetIndexInParentAsync()
{
if (_internalParent is null) return ValueTask.FromResult(-1);
return ValueTask.FromResult(_internalParent.InternalChildren.IndexOf(this));
}
protected override ValueTask<(uint, (string, ObjectPath)[])[]> OnGetRelationSetAsync()
{
return ValueTask.FromResult<(uint, (string, ObjectPath)[])[]>(default!);
}
protected override ValueTask<uint> OnGetRoleAsync()
{
return ValueTask.FromResult((uint)InternalCacheEntry.Role);
}
protected override ValueTask<string> OnGetRoleNameAsync()
{
return ValueTask.FromResult(InternalCacheEntry.RoleName);
}
protected override ValueTask<string> OnGetLocalizedRoleNameAsync()
{
return ValueTask.FromResult(InternalCacheEntry.LocalizedName);
}
protected override ValueTask<uint[]> OnGetStateAsync()
{
return ValueTask.FromResult(InternalCacheEntry.ApplicableStates);
}
protected override ValueTask<Dictionary<string, string>> OnGetAttributesAsync()
{
return ValueTask.FromResult<Dictionary<string, string>>(new() { { "toolkit", "Avalonia" } });
}
protected override ValueTask<(string, ObjectPath)> OnGetApplicationAsync()
{
return ValueTask.FromResult(InternalCacheEntry.Application);
}
protected override ValueTask<string[]> OnGetInterfacesAsync()
{
return ValueTask.FromResult(InternalCacheEntry.ApplicableInterfaces);
}
}

2
src/Avalonia.FreeDesktop/AtSpi/AtSpiConstants.cs

@ -8,6 +8,8 @@ namespace Avalonia.FreeDesktop.AtSpi;
/// </summary>
public class AtSpiConstants
{
public const string AvaloniaPathPrefix = "/net/avaloniaui/accessibles/";
public static readonly string[] RoleNames =
[
"invalid",

20
src/Avalonia.FreeDesktop/AtSpi/AtSpiContext.cs

@ -8,7 +8,6 @@ namespace Avalonia.FreeDesktop.AtSpi;
internal class AtSpiContext
{
public const string AvaloniaPathPrefix = "/net/avaloniaui/accessibles/";
private static bool s_instanced;
public static RootCache? Cache;
public static string? ServiceName;
@ -18,8 +17,9 @@ internal class AtSpiContext
{
_connection = connection;
var ac0 = new RootAccessible();
var ac1 = new RootApplication();
if (ServiceName == null) return;
var ac0 = new RootAccessible(connection, ServiceName);
var ac1 = new RootApplication(connection);
var path = "/org/a11y/atspi/accessible/root";
var pathHandler = new PathHandler(path);
@ -32,14 +32,11 @@ internal class AtSpiContext
var res = socket.EmbedAsync((ServiceName, new ObjectPath(RootPath))!).GetAwaiter().GetResult();
if (!res.Item1.StartsWith(":") || res.Item2.ToString() != RootPath || ServiceName is not { }) return;
if (!res.Item1.StartsWith(":") || res.Item2.ToString() != RootPath) return;
ac0.Parent = res;
ac0.Name = Application.Current?.Name ?? "Avalonia Application";
if (!Cache.TryAddEntry(Guid.Empty, ac0.CacheEntry))
{
// shouldnt happen.
}
Cache?.TryAddEntry(ac0);
}
public const string RootPath = "/org/a11y/atspi/accessible/root";
@ -50,7 +47,7 @@ internal class AtSpiContext
public void RegisterRootAutomationPeer(AutomationPeer peer)
{
// DelayedInit();
}
public static async void Initialize()
@ -65,15 +62,14 @@ internal class AtSpiContext
await a11YConnection.ConnectAsync();
Cache = new RootCache();
Cache = new RootCache(a11YConnection);
var cachePathHandler = new PathHandler("/org/a11y/atspi/cache");
cachePathHandler.Add(Cache);
a11YConnection.AddMethodHandler(cachePathHandler);
ServiceName = a11YConnection.UniqueName;
Instance = new AtSpiContext(a11YConnection);

38
src/Avalonia.FreeDesktop/AtSpi/CacheEntry.cs

@ -0,0 +1,38 @@
using Tmds.DBus.Protocol;
namespace Avalonia.FreeDesktop.AtSpi;
public class CacheEntry
{
public (string, ObjectPath) Accessible = (":0.0", "/org/a11y/atspi/accessible/object");
public (string, ObjectPath) Application = (":0.0", "/org/a11y/atspi/accessible/application");
public (string, ObjectPath) Parent = (":0.0", "/org/a11y/atspi/accessible/parent");
public int IndexInParent = 0;
public int ChildCount = 0;
public string[] ApplicableInterfaces = ["org.a11y.atspi.Accessible"];
public string LocalizedName = string.Empty;
public AtSpiConstants.Role Role = default;
public string RoleName = string.Empty;
public uint[] ApplicableStates = [];
public (
(string, ObjectPath),
(string, ObjectPath),
(string, ObjectPath),
int,
int,
string[],
string,
uint,
string,
uint[]) Convert() => (Accessible,
Application,
Parent,
IndexInParent,
ChildCount,
ApplicableInterfaces,
LocalizedName,
(uint)Role,
RoleName,
ApplicableStates);
}

88
src/Avalonia.FreeDesktop/AtSpi/RootAccessible.cs

@ -1,79 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Tmds.DBus.Protocol;
using Tmds.DBus.SourceGenerator;
namespace Avalonia.FreeDesktop.AtSpi;
internal class RootAccessible : OrgA11yAtspiAccessible
internal class RootAccessible : Accessible
{
public override Connection Connection { get; }
public RootCache.CacheEntry CacheEntry { get; } = new();
public RootAccessible()
{
CacheEntry.Accessible = (AtSpiContext.ServiceName, AtSpiContext.RootPath)!;
CacheEntry.Application = (AtSpiContext.ServiceName, AtSpiContext.RootPath)!;
CacheEntry.ApplicableInterfaces = ["org.a11y.atspi.Accessible", "org.a11y.atspi.Application"];
CacheEntry.Role = AtSpiConstants.Role.Application;
CacheEntry.LocalizedName = AtSpiConstants.RoleNames[(int)CacheEntry.Role];
CacheEntry.RoleName = AtSpiConstants.RoleNames[(int)CacheEntry.Role];
CacheEntry.ChildCount = 0; //TODO
CacheEntry.ApplicableStates = [0, 0];
}
protected override async ValueTask<(string, ObjectPath)> OnGetChildAtIndexAsync(int index)
{
return (":0.0", "/org/a11y/atspi/accessible/null");
}
protected override async ValueTask<(string, ObjectPath)[]> OnGetChildrenAsync()
{
return default;
}
protected override async ValueTask<int> OnGetIndexInParentAsync()
{
return -1;
}
protected override async ValueTask<(uint, (string, ObjectPath)[])[]> OnGetRelationSetAsync()
{
return default;
}
protected override async ValueTask<uint> OnGetRoleAsync()
{
return (uint)CacheEntry.Role;
}
protected override async ValueTask<string> OnGetRoleNameAsync()
{
return CacheEntry.RoleName;
}
protected override async ValueTask<string> OnGetLocalizedRoleNameAsync()
{
return CacheEntry.LocalizedName;
}
protected override async ValueTask<uint[]> OnGetStateAsync()
{
return CacheEntry.ApplicableStates;
}
protected override async ValueTask<Dictionary<string, string>> OnGetAttributesAsync()
{
return new() { { "toolkit", "Avalonia" } };
}
protected override async ValueTask<(string, ObjectPath)> OnGetApplicationAsync()
{
return CacheEntry.Application;
}
protected override async ValueTask<string[]> OnGetInterfacesAsync()
{
return CacheEntry.ApplicableInterfaces;
}
}
public RootAccessible(Connection connection, string serviceName) : base(serviceName, null)
{
Connection = connection;
InternalCacheEntry.Accessible = (serviceName, AtSpiContext.RootPath)!;
InternalCacheEntry.Application = (serviceName, AtSpiContext.RootPath)!;
InternalCacheEntry.ApplicableInterfaces = ["org.a11y.atspi.Accessible", "org.a11y.atspi.Application"];
InternalCacheEntry.Role = AtSpiConstants.Role.Application;
InternalCacheEntry.LocalizedName = AtSpiConstants.RoleNames[(int)InternalCacheEntry.Role];
InternalCacheEntry.RoleName = AtSpiConstants.RoleNames[(int)InternalCacheEntry.Role];
InternalCacheEntry.ChildCount = 0; //TODO
InternalCacheEntry.ApplicableStates = [0, 0];
this.InternalGuid = Guid.Empty;
}
}

9
src/Avalonia.FreeDesktop/AtSpi/RootApplication.cs

@ -7,8 +7,9 @@ namespace Avalonia.FreeDesktop.AtSpi;
internal class RootApplication : OrgA11yAtspiApplication
{
public RootApplication()
public RootApplication(Connection connection)
{
Connection = connection;
AtspiVersion = AVersion;
ToolkitName = "Avalonia";
Id = 0;
@ -19,8 +20,8 @@ internal class RootApplication : OrgA11yAtspiApplication
public override Connection Connection { get; }
protected override async ValueTask<string> OnGetLocaleAsync(uint lctype)
protected override ValueTask<string> OnGetLocaleAsync(uint lctype)
{
return Environment.GetEnvironmentVariable("LANG") ?? string.Empty;
return ValueTask.FromResult(Environment.GetEnvironmentVariable("LANG") ?? string.Empty);
}
}
}

51
src/Avalonia.FreeDesktop/AtSpi/RootCache.cs

@ -9,19 +9,19 @@ namespace Avalonia.FreeDesktop.AtSpi;
internal class RootCache : OrgA11yAtspiCache
{
public bool TryAddEntry(Guid id, CacheEntry cacheEntry)
public bool TryAddEntry(Accessible accessible)
{
if (!_globalCache.TryAdd(id, cacheEntry))
if (!_globalCache.TryAdd(accessible.InternalGuid, accessible))
{
return false;
}
if (Connection is not null)
EmitAddAccessible(cacheEntry.Convert());
EmitAddAccessible(accessible.InternalCacheEntry.Convert());
return true;
}
public CacheEntry? GetCacheEntry(Guid id)
public Accessible? GetCacheEntry(Guid id)
{
return _globalCache.TryGetValue(id, out var entry) ? entry : default;
}
@ -31,47 +31,18 @@ internal class RootCache : OrgA11yAtspiCache
if (!_globalCache.TryGetValue(id, out var item)) return false;
var ret = _globalCache.Remove(id);
if (ret && Connection is not null)
EmitRemoveAccessible(item.Accessible);
EmitRemoveAccessible(item.Convert());
return ret;
}
private Dictionary<Guid, CacheEntry> _globalCache = new();
private Dictionary<Guid, Accessible> _globalCache = new();
public class CacheEntry
public RootCache(Connection a11YConnection)
{
public (string, ObjectPath) Accessible = (":0.0", "/org/a11y/atspi/accessible/object");
public (string, ObjectPath) Application = (":0.0", "/org/a11y/atspi/accessible/application");
public (string, ObjectPath) Parent = (":0.0", "/org/a11y/atspi/accessible/parent");
public int IndexInParent = 0;
public int ChildCount = 0;
public string[] ApplicableInterfaces = [];
public string LocalizedName = string.Empty;
public AtSpiConstants.Role Role = default;
public string RoleName = string.Empty;
public uint[] ApplicableStates = [];
public (
(string, ObjectPath),
(string, ObjectPath),
(string, ObjectPath),
int,
int,
string[],
string,
uint,
string,
uint[]) Convert() => (Accessible,
Application,
Parent,
IndexInParent,
ChildCount,
ApplicableInterfaces,
LocalizedName,
(uint)Role,
RoleName,
ApplicableStates);
Connection = a11YConnection;
}
public override Connection? Connection { get; }
protected override async ValueTask<(
@ -86,7 +57,7 @@ internal class RootCache : OrgA11yAtspiCache
string,
uint[])[]> OnGetItemsAsync()
{
return (_globalCache.Values.Select(x => x.Convert()
return (_globalCache.Values.Select(x => x.InternalCacheEntry.Convert()
).ToArray());
}
}
}

Loading…
Cancel
Save