From 0ffdbccdc03bf33a12bcd1be14ba104cabb8801d Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Tue, 11 Jun 2024 23:03:55 +0800 Subject: [PATCH] more cache fixmeups + create Accessible abstract class for reuse --- src/Avalonia.FreeDesktop/AtSpi/Accessible.cs | 122 ++++++++++++++++++ .../AtSpi/AtSpiConstants.cs | 2 + .../AtSpi/AtSpiContext.cs | 20 ++- src/Avalonia.FreeDesktop/AtSpi/CacheEntry.cs | 38 ++++++ .../AtSpi/RootAccessible.cs | 88 +++---------- .../AtSpi/RootApplication.cs | 9 +- src/Avalonia.FreeDesktop/AtSpi/RootCache.cs | 51 ++------ 7 files changed, 205 insertions(+), 125 deletions(-) create mode 100644 src/Avalonia.FreeDesktop/AtSpi/Accessible.cs create mode 100644 src/Avalonia.FreeDesktop/AtSpi/CacheEntry.cs diff --git a/src/Avalonia.FreeDesktop/AtSpi/Accessible.cs b/src/Avalonia.FreeDesktop/AtSpi/Accessible.cs new file mode 100644 index 0000000000..4e225506ce --- /dev/null +++ b/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 _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 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 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 OnGetRoleAsync() + { + return ValueTask.FromResult((uint)InternalCacheEntry.Role); + } + + protected override ValueTask OnGetRoleNameAsync() + { + return ValueTask.FromResult(InternalCacheEntry.RoleName); + } + + protected override ValueTask OnGetLocalizedRoleNameAsync() + { + return ValueTask.FromResult(InternalCacheEntry.LocalizedName); + } + + protected override ValueTask OnGetStateAsync() + { + return ValueTask.FromResult(InternalCacheEntry.ApplicableStates); + } + + protected override ValueTask> OnGetAttributesAsync() + { + return ValueTask.FromResult>(new() { { "toolkit", "Avalonia" } }); + } + + protected override ValueTask<(string, ObjectPath)> OnGetApplicationAsync() + { + return ValueTask.FromResult(InternalCacheEntry.Application); + } + + protected override ValueTask OnGetInterfacesAsync() + { + return ValueTask.FromResult(InternalCacheEntry.ApplicableInterfaces); + } +} + + diff --git a/src/Avalonia.FreeDesktop/AtSpi/AtSpiConstants.cs b/src/Avalonia.FreeDesktop/AtSpi/AtSpiConstants.cs index 640d637336..3e63e12859 100644 --- a/src/Avalonia.FreeDesktop/AtSpi/AtSpiConstants.cs +++ b/src/Avalonia.FreeDesktop/AtSpi/AtSpiConstants.cs @@ -8,6 +8,8 @@ namespace Avalonia.FreeDesktop.AtSpi; /// public class AtSpiConstants { + public const string AvaloniaPathPrefix = "/net/avaloniaui/accessibles/"; + public static readonly string[] RoleNames = [ "invalid", diff --git a/src/Avalonia.FreeDesktop/AtSpi/AtSpiContext.cs b/src/Avalonia.FreeDesktop/AtSpi/AtSpiContext.cs index 9d5750f851..36824aff62 100644 --- a/src/Avalonia.FreeDesktop/AtSpi/AtSpiContext.cs +++ b/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); diff --git a/src/Avalonia.FreeDesktop/AtSpi/CacheEntry.cs b/src/Avalonia.FreeDesktop/AtSpi/CacheEntry.cs new file mode 100644 index 0000000000..040ab64875 --- /dev/null +++ b/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); +} diff --git a/src/Avalonia.FreeDesktop/AtSpi/RootAccessible.cs b/src/Avalonia.FreeDesktop/AtSpi/RootAccessible.cs index 34d3878d17..c72c2555ca 100644 --- a/src/Avalonia.FreeDesktop/AtSpi/RootAccessible.cs +++ b/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 OnGetIndexInParentAsync() - { - return -1; - } - - protected override async ValueTask<(uint, (string, ObjectPath)[])[]> OnGetRelationSetAsync() - { - return default; - } - - protected override async ValueTask OnGetRoleAsync() - { - return (uint)CacheEntry.Role; - } - - protected override async ValueTask OnGetRoleNameAsync() - { - return CacheEntry.RoleName; - } - - protected override async ValueTask OnGetLocalizedRoleNameAsync() - { - return CacheEntry.LocalizedName; - } - - protected override async ValueTask OnGetStateAsync() - { - return CacheEntry.ApplicableStates; - } - - protected override async ValueTask> OnGetAttributesAsync() - { - return new() { { "toolkit", "Avalonia" } }; - } - - protected override async ValueTask<(string, ObjectPath)> OnGetApplicationAsync() - { - return CacheEntry.Application; - } - - protected override async ValueTask OnGetInterfacesAsync() - { - return CacheEntry.ApplicableInterfaces; - } -} \ No newline at end of file + 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; + } + +} diff --git a/src/Avalonia.FreeDesktop/AtSpi/RootApplication.cs b/src/Avalonia.FreeDesktop/AtSpi/RootApplication.cs index 1e8e8e4b86..80d0c709fc 100644 --- a/src/Avalonia.FreeDesktop/AtSpi/RootApplication.cs +++ b/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 OnGetLocaleAsync(uint lctype) + protected override ValueTask OnGetLocaleAsync(uint lctype) { - return Environment.GetEnvironmentVariable("LANG") ?? string.Empty; + return ValueTask.FromResult(Environment.GetEnvironmentVariable("LANG") ?? string.Empty); } -} \ No newline at end of file +} diff --git a/src/Avalonia.FreeDesktop/AtSpi/RootCache.cs b/src/Avalonia.FreeDesktop/AtSpi/RootCache.cs index 91328d5402..f4a67fde6f 100644 --- a/src/Avalonia.FreeDesktop/AtSpi/RootCache.cs +++ b/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 _globalCache = new(); + private Dictionary _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()); } -} \ No newline at end of file +}