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> /// </summary>
public class AtSpiConstants public class AtSpiConstants
{ {
public const string AvaloniaPathPrefix = "/net/avaloniaui/accessibles/";
public static readonly string[] RoleNames = public static readonly string[] RoleNames =
[ [
"invalid", "invalid",

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

@ -8,7 +8,6 @@ namespace Avalonia.FreeDesktop.AtSpi;
internal class AtSpiContext internal class AtSpiContext
{ {
public const string AvaloniaPathPrefix = "/net/avaloniaui/accessibles/";
private static bool s_instanced; private static bool s_instanced;
public static RootCache? Cache; public static RootCache? Cache;
public static string? ServiceName; public static string? ServiceName;
@ -18,8 +17,9 @@ internal class AtSpiContext
{ {
_connection = connection; _connection = connection;
var ac0 = new RootAccessible(); if (ServiceName == null) return;
var ac1 = new RootApplication(); var ac0 = new RootAccessible(connection, ServiceName);
var ac1 = new RootApplication(connection);
var path = "/org/a11y/atspi/accessible/root"; var path = "/org/a11y/atspi/accessible/root";
var pathHandler = new PathHandler(path); var pathHandler = new PathHandler(path);
@ -32,14 +32,11 @@ internal class AtSpiContext
var res = socket.EmbedAsync((ServiceName, new ObjectPath(RootPath))!).GetAwaiter().GetResult(); 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.Parent = res;
ac0.Name = Application.Current?.Name ?? "Avalonia Application"; ac0.Name = Application.Current?.Name ?? "Avalonia Application";
if (!Cache.TryAddEntry(Guid.Empty, ac0.CacheEntry)) Cache?.TryAddEntry(ac0);
{
// shouldnt happen.
}
} }
public const string RootPath = "/org/a11y/atspi/accessible/root"; public const string RootPath = "/org/a11y/atspi/accessible/root";
@ -50,7 +47,7 @@ internal class AtSpiContext
public void RegisterRootAutomationPeer(AutomationPeer peer) public void RegisterRootAutomationPeer(AutomationPeer peer)
{ {
// DelayedInit();
} }
public static async void Initialize() public static async void Initialize()
@ -65,15 +62,14 @@ internal class AtSpiContext
await a11YConnection.ConnectAsync(); await a11YConnection.ConnectAsync();
Cache = new RootCache(); Cache = new RootCache(a11YConnection);
var cachePathHandler = new PathHandler("/org/a11y/atspi/cache"); var cachePathHandler = new PathHandler("/org/a11y/atspi/cache");
cachePathHandler.Add(Cache); cachePathHandler.Add(Cache);
a11YConnection.AddMethodHandler(cachePathHandler); a11YConnection.AddMethodHandler(cachePathHandler);
ServiceName = a11YConnection.UniqueName; ServiceName = a11YConnection.UniqueName;
Instance = new AtSpiContext(a11YConnection); 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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Tmds.DBus.Protocol; using Tmds.DBus.Protocol;
using Tmds.DBus.SourceGenerator; using Tmds.DBus.SourceGenerator;
namespace Avalonia.FreeDesktop.AtSpi; namespace Avalonia.FreeDesktop.AtSpi;
internal class RootAccessible : OrgA11yAtspiAccessible internal class RootAccessible : Accessible
{ {
public override Connection Connection { get; } public override Connection Connection { get; }
public RootCache.CacheEntry CacheEntry { get; } = new();
public RootAccessible() public RootAccessible(Connection connection, string serviceName) : base(serviceName, null)
{ {
CacheEntry.Accessible = (AtSpiContext.ServiceName, AtSpiContext.RootPath)!; Connection = connection;
CacheEntry.Application = (AtSpiContext.ServiceName, AtSpiContext.RootPath)!; InternalCacheEntry.Accessible = (serviceName, AtSpiContext.RootPath)!;
CacheEntry.ApplicableInterfaces = ["org.a11y.atspi.Accessible", "org.a11y.atspi.Application"]; InternalCacheEntry.Application = (serviceName, AtSpiContext.RootPath)!;
CacheEntry.Role = AtSpiConstants.Role.Application; InternalCacheEntry.ApplicableInterfaces = ["org.a11y.atspi.Accessible", "org.a11y.atspi.Application"];
CacheEntry.LocalizedName = AtSpiConstants.RoleNames[(int)CacheEntry.Role]; InternalCacheEntry.Role = AtSpiConstants.Role.Application;
CacheEntry.RoleName = AtSpiConstants.RoleNames[(int)CacheEntry.Role]; InternalCacheEntry.LocalizedName = AtSpiConstants.RoleNames[(int)InternalCacheEntry.Role];
CacheEntry.ChildCount = 0; //TODO InternalCacheEntry.RoleName = AtSpiConstants.RoleNames[(int)InternalCacheEntry.Role];
CacheEntry.ApplicableStates = [0, 0]; InternalCacheEntry.ChildCount = 0; //TODO
} InternalCacheEntry.ApplicableStates = [0, 0];
protected override async ValueTask<(string, ObjectPath)> OnGetChildAtIndexAsync(int index) this.InternalGuid = Guid.Empty;
{ }
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;
}
}

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

@ -7,8 +7,9 @@ namespace Avalonia.FreeDesktop.AtSpi;
internal class RootApplication : OrgA11yAtspiApplication internal class RootApplication : OrgA11yAtspiApplication
{ {
public RootApplication() public RootApplication(Connection connection)
{ {
Connection = connection;
AtspiVersion = AVersion; AtspiVersion = AVersion;
ToolkitName = "Avalonia"; ToolkitName = "Avalonia";
Id = 0; Id = 0;
@ -19,8 +20,8 @@ internal class RootApplication : OrgA11yAtspiApplication
public override Connection Connection { get; } 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 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; return false;
} }
if (Connection is not null) if (Connection is not null)
EmitAddAccessible(cacheEntry.Convert()); EmitAddAccessible(accessible.InternalCacheEntry.Convert());
return true; return true;
} }
public CacheEntry? GetCacheEntry(Guid id) public Accessible? GetCacheEntry(Guid id)
{ {
return _globalCache.TryGetValue(id, out var entry) ? entry : default; 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; if (!_globalCache.TryGetValue(id, out var item)) return false;
var ret = _globalCache.Remove(id); var ret = _globalCache.Remove(id);
if (ret && Connection is not null) if (ret && Connection is not null)
EmitRemoveAccessible(item.Accessible); EmitRemoveAccessible(item.Convert());
return ret; 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"); Connection = a11YConnection;
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);
} }
public override Connection? Connection { get; } public override Connection? Connection { get; }
protected override async ValueTask<( protected override async ValueTask<(
@ -86,7 +57,7 @@ internal class RootCache : OrgA11yAtspiCache
string, string,
uint[])[]> OnGetItemsAsync() uint[])[]> OnGetItemsAsync()
{ {
return (_globalCache.Values.Select(x => x.Convert() return (_globalCache.Values.Select(x => x.InternalCacheEntry.Convert()
).ToArray()); ).ToArray());
} }
} }

Loading…
Cancel
Save