diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings deleted file mode 100644 index cc58566622..0000000000 --- a/Avalonia.sln.DotSettings +++ /dev/null @@ -1,43 +0,0 @@ - - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - DO_NOT_SHOW - HINT - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="set_" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="_" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="False" Prefix="I" Suffix="" Style="AaBb" /> - <Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /> - <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> - True - True - True - True - True diff --git a/src/Avalonia.FreeDesktop/Automation/AtspiMainContext.cs b/src/Avalonia.FreeDesktop/Automation/AtspiMainContext.cs index af53eb9422..4f4096255b 100644 --- a/src/Avalonia.FreeDesktop/Automation/AtspiMainContext.cs +++ b/src/Avalonia.FreeDesktop/Automation/AtspiMainContext.cs @@ -12,136 +12,154 @@ using Tmds.DBus.SourceGenerator; namespace Avalonia.FreeDesktop.Automation; -// internal class HandlerAtspiApplication : OrgA11yAtspiApplication -// { -// public const string RootPath = "/org/a11y/atspi/accessible/root"; -// -// public HandlerAtspiApplication(Connection connection) -// { -// Connection = connection; -// AtspiVersion = atspiVersion; -// ToolkitName = "Avalonia"; -// Id = 0; -// Version = typeof(HandlerAtspiApplication).Assembly.GetName().Version.ToString(); -// } -// -// private const string atspiVersion = "2.1"; -// protected override Connection Connection { get; } -// public override string Path => RootPath; -// protected override async ValueTask OnGetLocaleAsync(uint lctype) -// { -// return string.Empty; -// } -// } -// -// internal class AtspiAccessibleHandler : OrgA11yAtspiAccessible -// { -// public AtspiAccessibleHandler(Connection a11YConnection, Func peer, string path) -// { -// Name = Application.Current?.Name ?? "Avalonia Application"; -// Description = string.Empty; -// Locale = Environment.GetEnvironmentVariable("LANG") ?? CultureInfo.CurrentCulture.Name; -// ChildCount = peer()?.GetChildren()?.Count ?? 0; -// Connection = a11YConnection; -// Path = path; -// } -// -// protected override Connection Connection { get; } -// public override string Path { get; } -// protected override ValueTask<(string, ObjectPath)> OnGetChildAtIndexAsync(int index) -// { -// return default; -// } -// -// protected override ValueTask<(string, ObjectPath)[]> OnGetChildrenAsync() -// { -// return default; -// } -// -// protected override ValueTask OnGetIndexInParentAsync() -// { -// return default; -// } -// -// protected override ValueTask<(uint, (string, ObjectPath)[])[]> OnGetRelationSetAsync() -// { -// return default; -// } -// -// protected override ValueTask OnGetRoleAsync() -// { -// return default; -// } -// -// protected override ValueTask OnGetRoleNameAsync() -// { -// return default; -// } -// -// protected override ValueTask OnGetLocalizedRoleNameAsync() -// { -// return default; -// } -// -// protected override ValueTask OnGetStateAsync() -// { -// return default; -// } -// -// protected override ValueTask> OnGetAttributesAsync() -// { -// return default; -// } -// -// protected override ValueTask<(string, ObjectPath)> OnGetApplicationAsync() -// { -// return default; -// } -// -// protected override ValueTask OnGetInterfacesAsync() -// { -// return default; -// } -// } - - - - -internal class AtspiMainContext +internal class HandlerAtspiApplication : OrgA11yAtspiApplication { - // private static bool s_instanceInitialized; - // private static AtspiMainContext? _instance; - // private readonly HandlerAtspiApplication _app; - // private readonly Connection _connection; + public const string RootPath = "/org/a11y/atspi/accessible/root"; + + public HandlerAtspiApplication(Connection connection) + { + Connection = connection; + AtspiVersion = atspiVersion; + ToolkitName = "Avalonia"; + Id = 0; + Version = typeof(HandlerAtspiApplication).Assembly.GetName().Version.ToString(); + } + + private const string atspiVersion = "2.1"; + public override string Path => RootPath; + + protected override Connection Connection { get; } + + protected override async ValueTask OnGetLocaleAsync(uint lctype) + { + return String.Empty; + } + + protected override async ValueTask OnRegisterEventListenerAsync(string @event) + { + } + + protected override async ValueTask OnDeregisterEventListenerAsync(string @event) + { + } +} + +internal class AtspiAccessibleHandler : OrgA11yAtspiAccessible +{ + public AtspiAccessibleHandler(Connection a11YConnection, Func peer, string path) + { + Name = Application.Current?.Name ?? "Avalonia Application"; + Description = string.Empty; + Locale = Environment.GetEnvironmentVariable("LANG") ?? CultureInfo.CurrentCulture.Name; + ChildCount = 0; //peer()?.GetChildren()?.Count ?? 0; + Connection = a11YConnection; + Path = path; + } + + protected override Connection Connection { get; } + public override string Path { get; } + + protected override ValueTask<(string, ObjectPath)> OnGetChildAtIndexAsync(int index) + { + return default; + } + + protected override ValueTask<(string, ObjectPath)[]> OnGetChildrenAsync() + { + return default; + } + + protected override ValueTask OnGetIndexInParentAsync() + { + return default; + } + + protected override ValueTask<(uint, (string, ObjectPath)[])[]> OnGetRelationSetAsync() + { + return default; + } + + protected override ValueTask OnGetRoleAsync() + { + return default; + } + + protected override ValueTask OnGetRoleNameAsync() + { + return default; + } + + protected override ValueTask OnGetLocalizedRoleNameAsync() + { + return default; + } + + protected override ValueTask OnGetStateAsync() + { + return default; + } + + protected override ValueTask> OnGetAttributesAsync() + { + return default; + } + + protected override ValueTask<(string, ObjectPath)> OnGetApplicationAsync() + { + return default; + } + + protected override ValueTask OnGetInterfacesAsync() + { + return default; + } +} + +internal class AtspiMainContext +{ + private static bool s_instanceInitialized; + private static AtspiMainContext? _instance; + private readonly HandlerAtspiApplication _app; + private readonly Connection _connection; + + private readonly MethodHandlerMultiplexer _multiplex; // private readonly List handlers = new List(); - + private AtspiMainContext(Connection a11yConnection) { - - // _connection = a11yConnection; - // _app = new HandlerAtspiApplication(_connection); - } + _connection = a11yConnection; + + _multiplex = new MethodHandlerMultiplexer(HandlerAtspiApplication.RootPath); + + _connection.AddMethodHandler(_multiplex); + + _app = new HandlerAtspiApplication(_connection); + + _multiplex.AddHandler("org.a11y.atspi.Application", _app); + + var rootAccessible = new AtspiAccessibleHandler(_connection, null, HandlerAtspiApplication.RootPath); + + _multiplex.AddHandler("org.a11y.atspi.Accessible", rootAccessible); + } public void RegisterRootPeer(Func peer) { - // var rootAccessible = new AtspiAccessibleHandler(_connection, peer, HandlerAtspiApplication.RootPath); - // handlers.Add(rootAccessible); - } + } public static AtspiMainContext? Instance { get /*=> _instance*/; } - + public static async void StartService() { - // if (s_instanceInitialized || DBusHelper.Connection is not { } sessionConnection) return; - // var bus1 = new OrgA11yBus(sessionConnection, "org.a11y.Bus", "/org/a11y/bus"); - // - // var address = await bus1.GetAddressAsync(); - // - // if (DBusHelper.TryCreateNewConnection(address) is not { } a11YConnection) return; - // - // await a11YConnection.ConnectAsync(); - // - // _instance = new AtspiMainContext(a11YConnection); - // s_instanceInitialized = true; - } + if (s_instanceInitialized || DBusHelper.Connection is not { } sessionConnection) return; + var bus1 = new OrgA11yBus(sessionConnection, "org.a11y.Bus", "/org/a11y/bus"); + + var address = await bus1.GetAddressAsync(); + + if (DBusHelper.TryCreateNewConnection(address) is not { } a11YConnection) return; + + await a11YConnection.ConnectAsync(); + + _instance = new AtspiMainContext(a11YConnection); + s_instanceInitialized = true; + } } diff --git a/src/Avalonia.FreeDesktop/Automation/MethodHandlerMultiplexer.cs b/src/Avalonia.FreeDesktop/Automation/MethodHandlerMultiplexer.cs new file mode 100644 index 0000000000..7ab77d5dc4 --- /dev/null +++ b/src/Avalonia.FreeDesktop/Automation/MethodHandlerMultiplexer.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Avalonia.Controls.Shapes; +using Tmds.DBus.Protocol; + +namespace Avalonia.FreeDesktop.Automation; + +internal class MethodHandlerMultiplexer(string rootPath) : IMethodHandler +{ + private Dictionary _handlers = new(); + + public void AddHandler(string @interface, IMethodHandler handler) + { + _handlers.Add(@interface, handler); + } + + public string Path { get; } = rootPath; + + private ReadOnlyMemory _introspectXml = + "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n"u8 + .ToArray(); + + public async ValueTask HandleMethodAsync(MethodContext context) + { + var reqInt = context.Request.InterfaceAsString; + switch (reqInt) + { + case "org.freedesktop.DBus.Properties": + switch (context.Request.MemberAsString) + { + case ("PropertiesChanged"): + case ("GetAll"): + case ("Get"): + case ("Set"): + Reply(); + + void Reply() + { + Reader reader = context.Request.GetBodyReader(); + string intfc = reader.ReadString(); + + if (_handlers.TryGetValue(intfc, out var m2)) + m2.HandleMethodAsync(context); + + } + break; + + } + + break; + + case "org.freedesktop.DBus.Introspectable": + + switch (context.Request.MemberAsString, context.Request.SignatureAsString) + { + case ("Introspect", "" or null): + { + context.ReplyIntrospectXml(new[] { _introspectXml }); + break; + } + } + + break; + default: + + if (_handlers.TryGetValue(reqInt, out var matchHandler)) + matchHandler.HandleMethodAsync(context); + + break; + } + } + + public bool RunMethodHandlerSynchronously(Message message) => true; +} diff --git a/src/Avalonia.FreeDesktop/DBusXml/org.a11y.atspi.accessible.root.xml b/src/Avalonia.FreeDesktop/DBusXml/org.a11y.atspi.accessible.root.xml index b4192e1839..88ed53933d 100644 --- a/src/Avalonia.FreeDesktop/DBusXml/org.a11y.atspi.accessible.root.xml +++ b/src/Avalonia.FreeDesktop/DBusXml/org.a11y.atspi.accessible.root.xml @@ -1,123 +1,108 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + - - + + - + - - + + - + - + - + - - + + - - + + - - + + - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + diff --git a/src/Linux/Tmds.DBus.Protocol/DBusConnection.cs b/src/Linux/Tmds.DBus.Protocol/DBusConnection.cs index 3ddcbb5a9d..6ca3336864 100644 --- a/src/Linux/Tmds.DBus.Protocol/DBusConnection.cs +++ b/src/Linux/Tmds.DBus.Protocol/DBusConnection.cs @@ -290,7 +290,7 @@ class DBusConnection : IDisposable { bool returnMessageToPool = true; MessageHandler pendingCall = default; - IMethodHandler? methodHandler = null; + IEnumerable? methodHandlers = null; Action? monitor = null; bool isMethodCall = message.MessageType == MessageType.MethodCall; MethodContext? methodContext = null; @@ -327,7 +327,7 @@ class DBusConnection : IDisposable { if (_pathNodes.TryGetValue(message.PathAsString!, out PathNode? node)) { - methodHandler = node.MethodHandler; + methodHandlers = node.MethodHandlers; bool isDBusIntrospect = message.Member.SequenceEqual("Introspect"u8) && message.Interface.SequenceEqual("org.freedesktop.DBus.Introspectable"u8); @@ -372,23 +372,29 @@ class DBusConnection : IDisposable if (isMethodCall) { Debug.Assert(methodContext is not null); - if (methodHandler is not null) + if (methodHandlers is not null) { // Suppress methodContext nullability warnings. #if NETSTANDARD2_0 #pragma warning disable CS8604 #endif - bool runHandlerSynchronously = methodHandler.RunMethodHandlerSynchronously(message); - if (runHandlerSynchronously) - { - await methodHandler.HandleMethodAsync(methodContext).ConfigureAwait(false); - HandleNoReplySent(methodContext); - } - else + + foreach (var methodHandler in methodHandlers) { - returnMessageToPool = false; - RunMethodHandler(methodHandler, methodContext); + + bool runHandlerSynchronously = methodHandler.RunMethodHandlerSynchronously(message); + if (runHandlerSynchronously) + { + await methodHandler.HandleMethodAsync(methodContext).ConfigureAwait(false); + HandleNoReplySent(methodContext); + } + else + { + returnMessageToPool = false; + RunMethodHandler(methodHandler, methodContext); + } } + } else { @@ -1287,4 +1293,4 @@ class DBusConnection : IDisposable return writer.CreateMessage(); } } -} \ No newline at end of file +} diff --git a/src/Linux/Tmds.DBus.Protocol/PathNodeDictionary.cs b/src/Linux/Tmds.DBus.Protocol/PathNodeDictionary.cs index 443a042104..ce8a51efcd 100644 --- a/src/Linux/Tmds.DBus.Protocol/PathNodeDictionary.cs +++ b/src/Linux/Tmds.DBus.Protocol/PathNodeDictionary.cs @@ -6,7 +6,7 @@ sealed class PathNode // a string if there is a single child name // a List List.Count child names private object? _childNames; - public IMethodHandler? MethodHandler; + public List? MethodHandlers = new(); public PathNode? Parent { get; set; } public int ChildNameCount => @@ -178,7 +178,7 @@ sealed class PathNodeDictionary : IMethodHandlerDictionary if (_dictionary.Remove(path, out PathNode? node)) { nodes[j++] = (path, node); - node.MethodHandler = null; + node.MethodHandlers.Clear(); } } count = j; j = 0; @@ -217,7 +217,7 @@ sealed class PathNodeDictionary : IMethodHandlerDictionary Debug.Assert(parent.ChildNameCount >= 1, "node is expected to be a known child"); if (parent.ChildNameCount == 1) // We're the only child. { - if (parent.MethodHandler is not null) + if (parent.MethodHandlers.Count > 0) { // Parent is still needed for the MethodHandler. parent.ClearChildNames(); @@ -261,11 +261,11 @@ sealed class PathNodeDictionary : IMethodHandlerDictionary PathNode node = GetOrCreateNode(path); - if (node.MethodHandler is not null) - { - throw new InvalidOperationException($"A method handler is already registered for the path '{path}'."); - } - node.MethodHandler = methodHandler; + // if (node.MethodHandler is not null) + // { + // throw new InvalidOperationException($"A method handler is already registered for the path '{path}'."); + // } + node.MethodHandlers.Add(methodHandler); } public void RemoveMethodHandler(string path) @@ -279,7 +279,7 @@ sealed class PathNodeDictionary : IMethodHandlerDictionary if (node.ChildNameCount > 0) { // Node is still needed for its children. - node.MethodHandler = null; + node.MethodHandlers.Clear(); _dictionary.Add(path, node); } else @@ -308,4 +308,4 @@ sealed class PathNodeDictionary : IMethodHandlerDictionary public int Compare((string Path, PathNode Node) x, (string Path, PathNode Node) y) => x.Path.Length - y.Path.Length; } -} \ No newline at end of file +} diff --git a/src/Linux/Tmds.DBus.SourceGenerator/Properties/launchSettings.json b/src/Linux/Tmds.DBus.SourceGenerator/Properties/launchSettings.json new file mode 100644 index 0000000000..1e7b7b4c72 --- /dev/null +++ b/src/Linux/Tmds.DBus.SourceGenerator/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Generator": { + "commandName": "DebugRoslynComponent", + "targetProject": "/var/home/strix/RiderProjects/Avalonia/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj" + } + } +}